@openhps/core
Version:
Open Hybrid Positioning System - Core component
180 lines • 5.98 kB
JavaScript
import { DataObject } from '../data';
import { TimeUnit } from '../utils';
import { Service } from './Service';
import { TimeService } from './TimeService';
/**
* Location-Based Service
*
* ## Usage
*
* ### Creation
* ```typescript
* const service = new LocationBasedService<
* DataObject,
* GeographicalPosition
* >();
*
* ModelBuilder.create()
* .addService(service)
* .from()
* .to().build();
* ```
*
* ### Getting the current position
*
* ### Setting the current position
*
* ### Watching the position of an object
*/
export class LocationBasedService extends Service {
constructor(options) {
super();
this.watchers = new Map();
this.watchedObjects = new Map();
this.watchIndex = 1;
this.options = options || {};
this.addDependency(TimeService);
this.once('build', this._initLBS.bind(this));
this.once('destroy', this._destroy.bind(this));
}
_initLBS() {
// Default options
this.options.pullNode = this.options.pullNode || this.model.internalSink.uid;
this.options.dataService = this.options.dataService || DataObject;
this.service = this.model.findDataService(this.options.dataService);
this.service.on('insert', (uid, storedObject) => {
const watchIds = this.watchedObjects.get(uid);
if (watchIds) {
const position = storedObject.position;
watchIds.forEach(watchId => {
const watcher = this.watchers.get(watchId);
if (position) {
watcher.callback(position);
}
});
}
});
}
_destroy() {
Array.from(this.watchers.keys()).forEach(watcher => {
this.clearWatch(watcher);
});
}
/**
* Set the current position of an object
* @param {DataObject | string} object Data object to get the current position of or uid
* @param {AbsolutePosition} position Position to update
* @returns {Promise<void>} Promise of updating
*/
setCurrentPosition(object, position) {
return new Promise((resolve, reject) => {
const uid = object instanceof DataObject ? object.uid : object;
this.service.findByUID(uid).then(storedObj => {
storedObj.setPosition(position);
return this.service.insertObject(storedObj);
}).then(() => {
resolve();
}).catch(reject);
});
}
/**
* Get the current position of a specific data object.
* @param {DataObject | string} object Data object to get the current position of or uid
* @param {GeoOptions} [options] Current position options
* @returns {Promise<AbsolutePosition>} Promise of latest absolute position
*/
getCurrentPosition(object, options = {}) {
return new Promise((resolve, reject) => {
const maximumAge = options.maximumAge || Infinity;
options.timeout = options.timeout || 10000;
const uid = object instanceof DataObject ? object.uid : object;
// Force update
if (options.forceUpdate) {
this.model.findNodeByUID(this.options.pullNode).pull({
requestedObjects: [uid]
});
const timeout = setTimeout(() => {
this.clearWatch(watchId);
reject(new Error('Timeout error for getting current position!'));
}, options.timeout);
const watchId = this.watchPosition(object, (pos, err) => {
this.clearWatch(watchId);
clearTimeout(timeout);
if (err) {
return reject(err);
}
resolve(pos);
}, Object.assign(Object.assign({}, options), {
interval: -1,
forceUpdate: false
}));
} else {
this.service.findByUID(uid).then(storedObj => {
const position = storedObj.position;
const time = TimeService.getUnit().convert(TimeService.now(), TimeUnit.MILLISECOND);
if (position && position.timestamp >= time - maximumAge) {
// Stored position satisfies maximum age
resolve(position);
} else {
return this.getCurrentPosition(object, Object.assign(Object.assign({}, options), {
forceUpdate: true
}));
}
}).then(resolve).catch(reject);
}
});
}
/**
* Watch for position changes
* @param {DataObject | string} object Data object to watch for position changes for
* @param {(position: AbsolutePosition, err?: Error) => void} callback Callback function
* @param {GeoWatchOptions} [options] Watch options
* @returns {number} Watch number
*/
watchPosition(object, callback, options = {}) {
var _a;
const uid = object instanceof DataObject ? object.uid : object;
const watchId = this.watchIndex++;
const interval = (_a = options.interval) !== null && _a !== void 0 ? _a : 1000;
const timer = interval !== -1 ? setInterval(() => {
this.getCurrentPosition(object, options).then(callback).catch(ex => {
callback(undefined, ex);
});
}, interval) : undefined;
this.watchers.set(watchId, {
timer,
uid,
callback
});
this.watchObject(uid, watchId);
return watchId;
}
watchObject(uid, watchId) {
var _a;
const existingIds = (_a = this.watchedObjects.get(uid)) !== null && _a !== void 0 ? _a : [];
existingIds.push(watchId);
this.watchedObjects.set(uid, existingIds);
}
unwatchObject(uid, watchId) {
var _a;
const existingIds = (_a = this.watchedObjects.get(uid)) !== null && _a !== void 0 ? _a : [];
existingIds.splice(existingIds.indexOf(watchId), 1);
if (existingIds.length === 0) {
this.watchedObjects.delete(uid);
} else {
this.watchedObjects.set(uid, existingIds);
}
}
/**
* Clear a running position watch
* @param {number} watchId Watch identifier
*/
clearWatch(watchId) {
const watcher = this.watchers.get(watchId);
if (watcher.timer !== undefined) {
clearInterval(watcher.timer);
}
this.watchers.delete(watchId);
this.unwatchObject(watcher.uid, watchId);
}
}