mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
1bc82f97fd
Paddle tours are planned on the water by TREK's own router: a water network per paddle region, built from the same Geofabrik extract as the obstacles and searched in a worker thread of the server (no sidecar). Rivers, canals and lakes (direct or along the shore), portages round weirs, locks by policy, whitewater by boat, gaps marked. Found portages and locks become points of their own; an unplannable leg says why and offers a way out. - Shaping points: a click on the line sets a point that steers the route without being a stop (hikes and paddle tours); recompute a single leg. - Lake style direct/shore, suggested from the wind forecast. - Regions load by themselves where a paddler plans (admin setting: automatic, on request, off; size cap and a daily count), with requests for the admins, a notification, and the load followed on the leg. - Portage from the last point, portages follow footways via the walking router (or stay straight), "put in here" on slipways and put-ins. - MCP plan_paddle_route with the REST route's rules.
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
/**
|
|
* The positions of the nodes a read needs, held as sorted typed arrays rather
|
|
* than a Set and a Map: a lake-rich region asks for millions of nodes, which
|
|
* as objects would cost several times the memory and run into V8's limit on
|
|
* the size of a Map. Ids are collected first, then sorted once; the pass over
|
|
* the file's nodes fills in their positions at 1e-7 degrees, OSM's own
|
|
* resolution.
|
|
*/
|
|
export class NodeStore {
|
|
private ids = new Float64Array(1024);
|
|
private count = 0;
|
|
private sealed = false;
|
|
private lats = new Int32Array(0);
|
|
private lons = new Int32Array(0);
|
|
private found = new Uint8Array(0);
|
|
|
|
get size(): number {
|
|
return this.count;
|
|
}
|
|
|
|
want(id: number): void {
|
|
if (this.sealed) throw new Error('NodeStore is sealed');
|
|
if (this.count === this.ids.length) {
|
|
const grown = new Float64Array(this.ids.length * 2);
|
|
grown.set(this.ids);
|
|
this.ids = grown;
|
|
}
|
|
this.ids[this.count++] = id;
|
|
}
|
|
|
|
wantAll(ids: readonly number[]): void {
|
|
for (const id of ids) this.want(id);
|
|
}
|
|
|
|
/** Sorts and dedupes the ids; from here on positions can be filled in. */
|
|
seal(): void {
|
|
if (this.sealed) return;
|
|
const sorted = this.ids.subarray(0, this.count).sort();
|
|
let unique = 0;
|
|
for (let i = 0; i < sorted.length; i++) {
|
|
if (i === 0 || sorted[i] !== sorted[unique - 1]) sorted[unique++] = sorted[i] as number;
|
|
}
|
|
this.ids = sorted.slice(0, unique);
|
|
this.count = unique;
|
|
this.lats = new Int32Array(unique);
|
|
this.lons = new Int32Array(unique);
|
|
this.found = new Uint8Array(unique);
|
|
this.sealed = true;
|
|
}
|
|
|
|
private indexOf(id: number): number {
|
|
let low = 0;
|
|
let high = this.count - 1;
|
|
while (low <= high) {
|
|
const mid = (low + high) >>> 1;
|
|
const at = this.ids[mid] as number;
|
|
if (at === id) return mid;
|
|
if (at < id) low = mid + 1;
|
|
else high = mid - 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Records a node's position if the read wants it. */
|
|
offer(id: number, lat: number, lon: number): void {
|
|
const index = this.indexOf(id);
|
|
if (index < 0) return;
|
|
this.lats[index] = Math.round(lat * 1e7);
|
|
this.lons[index] = Math.round(lon * 1e7);
|
|
this.found[index] = 1;
|
|
}
|
|
|
|
/** [lat, lon] of a node, or null when the file did not hold it. */
|
|
get(id: number): [number, number] | null {
|
|
const index = this.indexOf(id);
|
|
if (index < 0 || this.found[index] === 0) return null;
|
|
return [(this.lats[index] as number) / 1e7, (this.lons[index] as number) / 1e7];
|
|
}
|
|
}
|