UNPKG

rosbag

Version:

`rosbag` is a node.js & browser compatible module for reading [rosbag](http://wiki.ros.org/rosbag) binary data files.

166 lines (142 loc) 5.61 kB
// Copyright (c) 2018-present, Cruise LLC // This source code is licensed under the Apache License, Version 2.0, // found in the LICENSE file in the root directory of this source tree. // You may not use this file except in compliance with the License. import type { Decompress } from "./BagReader"; import BagReader from "./BagReader"; import { MessageReader } from "./MessageReader"; import ReadResult from "./ReadResult"; import { BagHeader, ChunkInfo, Connection, MessageData } from "./record"; import type { Time } from "./types"; import * as TimeUtil from "./TimeUtil"; import { parseMessageDefinition } from "./parseMessageDefinition"; export type ReadOptions = { decompress?: Decompress; noParse?: boolean; topics?: string[]; startTime?: Time; endTime?: Time; freeze?: boolean; }; /** * Represents a bag that has been opened, which guarantees certain properties are available. */ // eslint-disable-next-line no-use-before-define export interface OpenBag extends Bag { header: BagHeader; connections: Record<number, Connection>; chunkInfos: ChunkInfo[]; } /** * The high level rosbag interface. * * Create a new bag by calling: * `const bag = await Bag.open('./path-to-file.bag')` in node or * `const bag = await Bag.open(files[0])` in the browser. * * After that you can consume messages by calling * `await bag.readMessages({ topics: ['/foo'] }, * (result) => console.log(result.topic, result.message))` */ export default class Bag { reader: BagReader; header?: BagHeader; connections?: Record<number, Connection>; chunkInfos?: ChunkInfo[]; startTime?: Time; endTime?: Time; // you can optionally create a bag manually passing in a bagReader instance constructor(bagReader: BagReader) { this.reader = bagReader; } static open = (_file: File | string): Promise<OpenBag> => Promise.reject( new Error( "This method should have been overridden based on the environment. Make sure you are correctly importing the node or web version of Bag." ) ); // eslint-disable-next-line no-use-before-define private assertOpen(): asserts this is OpenBag { if (!this.header || !this.connections || !this.chunkInfos) { throw new Error("Bag needs to be opened"); } } /** * If the bag is manually created with the constructor, you must call `await open()` on the bag. * Generally this is called for you if you're using `const bag = await Bag.open()`. * * Returns `this` with the type of `OpenBag`. */ async open(): Promise<OpenBag> { this.header = await this.reader.readHeaderAsync(); const { connectionCount, chunkCount, indexPosition } = this.header; const result = await this.reader.readConnectionsAndChunkInfoAsync(indexPosition, connectionCount, chunkCount); this.connections = {}; result.connections.forEach((connection) => { // Connections is definitly assigned above // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.connections![connection.conn] = connection; }); this.chunkInfos = result.chunkInfos; if (chunkCount > 0) { // Get the earliest startTime among all chunks this.startTime = this.chunkInfos .map((x) => x.startTime) .reduce((prev, current) => (TimeUtil.compare(prev, current) <= 0 ? prev : current)); // Get the latest endTime among all chunks this.endTime = this.chunkInfos .map((x) => x.endTime) .reduce((prev, current) => (TimeUtil.compare(prev, current) > 0 ? prev : current)); } return this as OpenBag; } async readMessages(opts: ReadOptions, callback: (msg: ReadResult<unknown>) => void) { this.assertOpen(); const { connections } = this; const startTime = opts.startTime || { sec: 0, nsec: 0, }; const endTime = opts.endTime || { sec: Number.MAX_VALUE, nsec: Number.MAX_VALUE, }; const topics = opts.topics || Object.values(connections).map((connection) => connection.topic); const filteredConnections = Object.values(connections) .filter((connection) => topics.indexOf(connection.topic) !== -1) .map((connection) => +connection.conn); const { decompress = {} } = opts; // filter chunks to those which fall within the time range we're attempting to read const chunkInfos = this.chunkInfos.filter( (info) => TimeUtil.compare(info.startTime, endTime) <= 0 && TimeUtil.compare(startTime, info.endTime) <= 0 ); function parseMsg(msg: MessageData, chunkOffset: number): ReadResult<unknown> { const connection = connections[msg.conn]; const { topic, type } = connection; const { data, time: timestamp } = msg; let message = null; if (!opts.noParse) { // lazily create a reader for this connection if it doesn't exist connection.reader = connection.reader || new MessageReader(parseMessageDefinition(connection.messageDefinition, type), type, { freeze: opts.freeze, }); message = connection.reader.readMessage(data); } return new ReadResult(topic, message, timestamp, data, chunkOffset, chunkInfos.length, opts.freeze); } for (let i = 0; i < chunkInfos.length; i++) { const info = chunkInfos[i]; // eslint-disable-next-line no-await-in-loop const messages = await this.reader.readChunkMessagesAsync( info, filteredConnections, startTime, endTime, decompress ); messages.forEach((msg) => callback(parseMsg(msg, i))); } } }