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