diff --git a/src/data-index.js b/src/data-index.js index c24f92b..dd3cdf9 100644 --- a/src/data-index.js +++ b/src/data-index.js @@ -3492,7 +3492,7 @@ function _getGeoHash(obj) { return geohash; } -// Berekent welke hashes (van verschillende precisies) er in een straal liggen vanaf middelpunt +// Calculates which hashes (of different precisions) are within the radius of a point function _hashesInRadius(lat, lon, radiusM, precision) { const isInCircle = (checkLat, checkLon, lat, lon, radiusM) => { diff --git a/src/geohash.d.ts b/src/geohash.d.ts new file mode 100644 index 0000000..223ff54 --- /dev/null +++ b/src/geohash.d.ts @@ -0,0 +1,60 @@ +/** + * Encodes latitude/longitude to geohash, either to specified precision or to automatically + * evaluated precision. + * @param lat - Latitude in degrees. + * @param lon - Longitude in degrees. + * @param precision - Number of characters in resulting + * @returns Geohash of supplied latitude/longitude. + * @example + * let geohash = encode(52.205, 0.119, 7); // geohash: 'u120fxw' + */ +export declare const encode: (lat: number, lon: number, precision?: number) => string; +/** + * Decode geohash to latitude/longitude (location is approximate centre of geohash cell, + * to reasonable precision). + * @param geohash Geohash string to be converted to latitude/longitude. + * @returns (Center of) geohashed location. + * + * @example + * let latlon = decode('u120fxw'); // latlon: { lat: 52.205, lon: 0.1188 } + */ +export declare const decode: (geohash: string) => { + lat: number; + lon: number; +}; +/** + * Returns SW/NE latitude/longitude bounds of specified cell + * @param geohash Cell that bounds are required of. + */ +export declare const bounds: (geohash: string) => { + sw: { + lat: number; + lon: number; + }; + ne: { + lat: number; + lon: number; + }; +}; +/** + * Determines adjacent cell in given direction. + * @param geohash Cell to which adjacent cell is required. + * @param direction Direction from geohash (N/S/E/W). + * @returns Geocode of adjacent cell. + * @throws Invalid + */ +export declare const adjacent: (geohash: string, direction: 'N' | 'n' | 'S' | 's' | 'E' | 'e' | 'W' | 'w') => string; +/** + * Returns all 8 adjacent cells to specified cell + * @param geohash Geohash neighbours are required of. + */ +export declare const neighbours: (geohash: string) => { + n: string; + ne: string; + e: string; + se: string; + s: string; + sw: string; + w: string; + nw: string; +}; diff --git a/src/geohash.js b/src/geohash.js new file mode 100644 index 0000000..e490e77 --- /dev/null +++ b/src/geohash.js @@ -0,0 +1,207 @@ +"use strict"; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Geohash encoding/decoding and associated functions (c) Chris Veness 2014-2016 / MIT Licence */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.neighbours = exports.adjacent = exports.bounds = exports.decode = exports.encode = void 0; +/** + * Geohash encode, decode, bounds, neighbours. + */ +/* (Geohash-specific) Base32 map */ +const base32 = '0123456789bcdefghjkmnpqrstuvwxyz'; +/** + * Encodes latitude/longitude to geohash, either to specified precision or to automatically + * evaluated precision. + * @param lat - Latitude in degrees. + * @param lon - Longitude in degrees. + * @param precision - Number of characters in resulting + * @returns Geohash of supplied latitude/longitude. + * @example + * let geohash = encode(52.205, 0.119, 7); // geohash: 'u120fxw' + */ +const encode = function (lat, lon, precision) { + // infer precision? + if (typeof precision == 'undefined') { + // refine geohash until it matches precision of supplied lat/lon + for (let p = 1; p <= 12; p++) { + let hash = (0, exports.encode)(lat, lon, p); + let posn = (0, exports.decode)(hash); + if (posn.lat == lat && posn.lon == lon) + return hash; + } + precision = 12; // set to maximum + } + lat = Number(lat); + lon = Number(lon); + precision = Number(precision); + if (isNaN(lat) || isNaN(lon) || isNaN(precision)) + throw new Error('Invalid geohash'); + let idx = 0; // index into base32 map + let bit = 0; // each char holds 5 bits + let evenBit = true; + let geohash = ''; + let latMin = -90, latMax = 90; + let lonMin = -180, lonMax = 180; + while (geohash.length < precision) { + if (evenBit) { + // bisect E-W longitude + let lonMid = (lonMin + lonMax) / 2; + if (lon >= lonMid) { + idx = idx * 2 + 1; + lonMin = lonMid; + } + else { + idx = idx * 2; + lonMax = lonMid; + } + } + else { + // bisect N-S latitude + let latMid = (latMin + latMax) / 2; + if (lat >= latMid) { + idx = idx * 2 + 1; + latMin = latMid; + } + else { + idx = idx * 2; + latMax = latMid; + } + } + evenBit = !evenBit; + if (++bit == 5) { + // 5 bits gives us a character: append it and start over + geohash += base32.charAt(idx); + bit = 0; + idx = 0; + } + } + return geohash; +}; +exports.encode = encode; +/** + * Decode geohash to latitude/longitude (location is approximate centre of geohash cell, + * to reasonable precision). + * @param geohash Geohash string to be converted to latitude/longitude. + * @returns (Center of) geohashed location. + * + * @example + * let latlon = decode('u120fxw'); // latlon: { lat: 52.205, lon: 0.1188 } + */ +const decode = function (geohash) { + const b = (0, exports.bounds)(geohash); // <-- the hard work + // now just determine the centre of the cell... + const latMin = b.sw.lat, lonMin = b.sw.lon; + const latMax = b.ne.lat, lonMax = b.ne.lon; + // cell centre + let lat = (latMin + latMax) / 2; + let lon = (lonMin + lonMax) / 2; + // round to close to centre without excessive precision: ⌊2-log10(Δ°)⌋ decimal places + lat = Number(lat.toFixed(Math.floor(2 - Math.log(latMax - latMin) / Math.LN10))); + lon = Number(lon.toFixed(Math.floor(2 - Math.log(lonMax - lonMin) / Math.LN10))); + return { lat, lon }; +}; +exports.decode = decode; +/** + * Returns SW/NE latitude/longitude bounds of specified cell + * @param geohash Cell that bounds are required of. + */ +const bounds = function (geohash) { + if (geohash.length === 0) + throw new Error('Invalid geohash'); + geohash = geohash.toLowerCase(); + let evenBit = true; + let latMin = -90, latMax = 90; + let lonMin = -180, lonMax = 180; + for (let i = 0; i < geohash.length; i++) { + let chr = geohash.charAt(i); + let idx = base32.indexOf(chr); + if (idx == -1) + throw new Error('Invalid geohash'); + for (let n = 4; n >= 0; n--) { + let bitN = idx >> n & 1; + if (evenBit) { + // longitude + let lonMid = (lonMin + lonMax) / 2; + if (bitN == 1) { + lonMin = lonMid; + } + else { + lonMax = lonMid; + } + } + else { + // latitude + let latMid = (latMin + latMax) / 2; + if (bitN == 1) { + latMin = latMid; + } + else { + latMax = latMid; + } + } + evenBit = !evenBit; + } + } + let bounds = { + sw: { lat: latMin, lon: lonMin }, + ne: { lat: latMax, lon: lonMax }, + }; + return bounds; +}; +exports.bounds = bounds; +/** + * Determines adjacent cell in given direction. + * @param geohash Cell to which adjacent cell is required. + * @param direction Direction from geohash (N/S/E/W). + * @returns Geocode of adjacent cell. + * @throws Invalid + */ +const adjacent = function (geohash, direction) { + // based on github.com/davetroy/geohash-js + geohash = geohash.toLowerCase(); + direction = direction.toLowerCase(); + if (length === 0) + throw new Error('Invalid geohash'); + if ('nsew'.indexOf(direction) == -1) + throw new Error('Invalid direction'); + let neighbour = { + n: ['p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx'], + s: ['14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp'], + e: ['bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'], + w: ['238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb'], + }; + let border = { + n: ['prxz', 'bcfguvyz'], + s: ['028b', '0145hjnp'], + e: ['bcfguvyz', 'prxz'], + w: ['0145hjnp', '028b'], + }; + let lastCh = geohash.slice(-1); // last character of hash + let parent = geohash.slice(0, -1); // hash without last character + let type = geohash.length % 2; + // check for edge-cases which don't share common prefix + if (border[direction][type].indexOf(lastCh) != -1 && parent !== '') { + parent = (0, exports.adjacent)(parent, direction); + } + // append letter for direction to parent + return parent + base32.charAt(neighbour[direction][type].indexOf(lastCh)); +}; +exports.adjacent = adjacent; +/** + * Returns all 8 adjacent cells to specified cell + * @param geohash Geohash neighbours are required of. + */ +const neighbours = function (geohash) { + return { + 'n': (0, exports.adjacent)(geohash, 'n'), + 'ne': (0, exports.adjacent)((0, exports.adjacent)(geohash, 'n'), 'e'), + 'e': (0, exports.adjacent)(geohash, 'e'), + 'se': (0, exports.adjacent)((0, exports.adjacent)(geohash, 's'), 'e'), + 's': (0, exports.adjacent)(geohash, 's'), + 'sw': (0, exports.adjacent)((0, exports.adjacent)(geohash, 's'), 'w'), + 'w': (0, exports.adjacent)(geohash, 'w'), + 'nw': (0, exports.adjacent)((0, exports.adjacent)(geohash, 'n'), 'w'), + }; +}; +exports.neighbours = neighbours; +//# sourceMappingURL=geohash.js.map \ No newline at end of file diff --git a/src/geohash.js.map b/src/geohash.js.map new file mode 100644 index 0000000..29c8945 --- /dev/null +++ b/src/geohash.js.map @@ -0,0 +1 @@ +{"version":3,"file":"geohash.js","sourceRoot":"","sources":["ts/geohash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/thread-safe.d.ts b/src/thread-safe.d.ts new file mode 100644 index 0000000..6a00081 --- /dev/null +++ b/src/thread-safe.d.ts @@ -0,0 +1,63 @@ +/// +import { SimpleEventEmitter } from "acebase-core"; +export interface ThreadSafeLockOptions { + /** max amount of ms the target is allowed to be locked (and max time to wait to get it), default is 60000 (60s) */ + timeout?: number; + /** flag that indicates whether this lock does critical work, canceling queued lock requests if this lock is not released in time */ + critical?: boolean; + /** name of the lock, good for debugging purposes */ + name?: string; + /** if this lock is allowed to be shared with others also requesting a shared lock. Requested lock will be exclusive otherwise (default) */ + shared?: boolean; + /** if you are using a string to uniquely identify the locking target, you can pass the actual object target with this option; lock.target will be set to this value instead. */ + target?: any; +} +interface ThreadSafeLockQueueItem { + resolve: (lock: ThreadSafeLock) => void; + reject: (err: Error) => void; + waitTimeout: NodeJS.Timeout; + options: ThreadSafeLockOptions; +} +export interface ThreadSafeLock { + achieved: Date; + release: () => void; + target: any; + name: string; + _timeout: NodeJS.Timeout; + _queue: ThreadSafeLockQueueItem[]; + /** If DEBUG_MODE is enabled: contains stack trace of ThreadSafe.lock call */ + stack: string; +} +export declare abstract class ThreadSafe { + /** + * + * @param target Target object to lock. Do not use object references! + * @param options Locking options + * @returns returns a lock + */ + static lock(target: any, options?: ThreadSafeLockOptions): Promise; +} +/** + * New locking mechasnism that supports exclusive or shared locking + */ +export declare class ThreadSafeLock2 extends SimpleEventEmitter { + readonly target: any; + readonly achieved: Date; + private shares; + private queue; + private _shared; + get shared(): boolean; + constructor(target: any, shared: boolean); + release(): void; + request(shared: boolean): Promise; +} +export declare abstract class ThreadSafe2 { + /** + * + * @param target Target object to lock. Do not use object references! + * @param options Locking options + * @returns returns a lock + */ + static lock(target: any, shared?: boolean): Promise; +} +export {}; diff --git a/src/thread-safe.js b/src/thread-safe.js new file mode 100644 index 0000000..64d1c69 --- /dev/null +++ b/src/thread-safe.js @@ -0,0 +1,182 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ThreadSafe2 = exports.ThreadSafeLock2 = exports.ThreadSafe = void 0; +const acebase_core_1 = require("acebase-core"); +/** Set to true to add stack traces to achieved locks (performance impact!) */ +const DEBUG_MODE = false; +const _lockTimeoutMsg = 'Lock "${name}" timed out! lock.release() was not called in a timely fashion'; +const _lockWaitTimeoutMsg = 'Lock "${name}" wait time expired, failed to lock target'; +const _threadSafeLocks = new Map(); +class ThreadSafe { + /** + * + * @param target Target object to lock. Do not use object references! + * @param options Locking options + * @returns returns a lock + */ + static lock(target, options = { timeout: 60000 * 15, critical: true, name: 'unnamed lock', shared: false }) { + if (typeof options !== 'object') { + options = {}; + } + if (typeof options.timeout !== 'number') { + options.timeout = 60 * 1000; + } + if (typeof options.critical !== 'boolean') { + options.critical = true; + } + if (typeof options.name !== 'string') { + options.name = typeof target === 'string' ? target : 'unnamed lock'; + } + if (typeof options.shared !== 'boolean') { + options.shared = false; + } + if (options.shared) { + // TODO: Implement + // console.warn('shared locking not implemented yet, using exclusive lock'); + } + let lock = _threadSafeLocks.get(target); + const timeoutHandler = (critical) => { + console.error(_lockTimeoutMsg.replace('${name}', lock.name)); + // Copy lock object so we can alter the original's release method to throw an exception + let copy = Object.assign({}, lock); + let originalName = lock.name; + lock.release = () => { + throw new Error(`Cannot release lock "${originalName}" because it timed out earlier`); + }; + lock = copy; + if (critical) { + // cancel any queued requests + _threadSafeLocks.delete(target); + lock._queue.forEach(item => { + clearTimeout(item.waitTimeout); + item.reject(new Error(`Could not achieve lock because the current lock ("${lock.name}") was not released in time (and lock is flagged critical)`)); + }); + } + else { + next(); + } + }; + const next = () => { + clearTimeout(lock._timeout); + if (lock._queue.length === 0) { + return _threadSafeLocks.delete(target); + } + let item = lock._queue.shift(); + clearTimeout(item.waitTimeout); + lock._timeout = setTimeout(timeoutHandler, item.options.timeout, item.options.critical); + lock.target = item.options.target || target; + lock.achieved = new Date(); + lock.name = item.options.name; + lock.stack = DEBUG_MODE ? (new Error()).stack : 'not available'; + item.resolve(lock); + }; + if (!lock) { + // Create lock + lock = { + target: options.target || target, + achieved: new Date(), + release() { + next(); + }, + name: options.name, + stack: DEBUG_MODE ? (new Error()).stack : 'not available', + _timeout: setTimeout(timeoutHandler, options.timeout, options.critical), + _queue: [] + }; + _threadSafeLocks.set(target, lock); + return Promise.resolve(lock); + } + else { + // Add to queue + return new Promise((resolve, reject) => { + const waitTimeout = setTimeout(() => { + lock._queue.splice(lock._queue.indexOf(item), 1); + if (lock._queue.length === 0) { + _threadSafeLocks.delete(target); + } + reject(_lockWaitTimeoutMsg.replace('${name}', options.name)); + }, options.timeout); + const item = { resolve, reject, waitTimeout, options }; + lock._queue.push(item); + }); + } + } +} +exports.ThreadSafe = ThreadSafe; +/** + * New locking mechasnism that supports exclusive or shared locking + */ +class ThreadSafeLock2 extends acebase_core_1.SimpleEventEmitter { + constructor(target, shared) { + super(); + this.target = target; + this.shares = 0; + this.queue = []; + this._shared = shared; + this.achieved = new Date(); + } + get shared() { return this._shared; } + release() { + if (this.shared && this.shares > 0) { + this.shares--; + } + else if (this.queue.length > 0) { + const next = this.queue.shift(); + this._shared = next.shared; + next.grant(); + if (next.shared) { + // Also grant other pending shared requests + while (this.queue.length > 0 && this.queue[0].shared) { + this.queue.shift().grant(); + } + } + } + else { + // No more shares, no queue: this lock can be now be released entirely + this.emitOnce('released'); + } + } + async request(shared) { + if (this.shared && shared) { + // Grant! + this.shares++; + } + else { + // Add to queue, wait until granted + let grant; + const promise = new Promise(resolve => { grant = resolve; }); + this.queue.push({ shared, grant }); + await promise; + } + } +} +exports.ThreadSafeLock2 = ThreadSafeLock2; +const locks2 = new Map(); +class ThreadSafe2 { + /** + * + * @param target Target object to lock. Do not use object references! + * @param options Locking options + * @returns returns a lock + */ + static async lock(target, shared = false) { + const timeout = 60 * 1000; + if (!locks2.has(target)) { + // New lock + const lock = new ThreadSafeLock2(target, shared); + locks2.set(target, lock); + lock.once('released', () => { + locks2.delete(target); + }); + return lock; + } + else { + // Existing lock + const lock = locks2.get(target); + await lock.request(shared); + return lock; + } + } +} +exports.ThreadSafe2 = ThreadSafe2; +//# sourceMappingURL=thread-safe.js.map \ No newline at end of file diff --git a/src/thread-safe.js.map b/src/thread-safe.js.map new file mode 100644 index 0000000..ceede26 --- /dev/null +++ b/src/thread-safe.js.map @@ -0,0 +1 @@ +{"version":3,"file":"thread-safe.js","sourceRoot":"","sources":["ts/thread-safe.ts"],"names":[],"mappings":"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"} \ No newline at end of file