@truelies/osm-dybuf 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @truelies/osm-dybuf
2
+
3
+ Gridex `osm.dybuf` parser utilities shared by the exporter, inspection scripts, and Next.js frontend.
4
+ This package wraps the shared schema tables (`schema_ids`) and exposes a high-level `parseOsmDybuf` helper.
5
+ It depends on the published [`dybuf`](https://www.npmjs.com/package/dybuf) runtime (≥ 0.4.2).
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ # from another project (Next.js, Node, etc.)
11
+ npm install @truelies/osm-dybuf
12
+ ```
13
+
14
+ ```ts
15
+ import { parseOsmDybuf } from "@truelies/osm-dybuf";
16
+ import {
17
+ FEATURE_KIND_IDS,
18
+ REGION_NUMERIC_CODES,
19
+ } from "@truelies/osm-dybuf/schema_ids";
20
+
21
+ const data = await fetch("/tiles/08/213/161/osm.dybuf").then((res) => res.arrayBuffer());
22
+ const cell = { level: 8, londex: 213, latdex: 161 };
23
+ const parsed = parseOsmDybuf(data, cell);
24
+ ```
25
+
26
+ - CLI inspection (development only):
27
+ ```bash
28
+ node js/inspect_dybuf.mjs \
29
+ --file /path/to/osm.dybuf \
30
+ --level 8 --londex 213 --latdex 161 --limit 5
31
+ ```
32
+
33
+ ### Grid helpers (grid_index)
34
+
35
+ `grid_index.ts` exposes the minimal Gridex helpers (integers, pole triangles) aligned with the exporter:
36
+
37
+ ```ts
38
+ import { GridUnit, Gridex, INT_COORD_SCALE } from "@truelies/osm-dybuf/grid_index";
39
+
40
+ const unit = new GridUnit(8); // level 8
41
+ const cell = new Gridex(213, 161);
42
+ const [minLon, minLat, maxLon, maxLat] = unit.boundsOfGrid(cell);
43
+ ```
44
+
45
+ ## Local Development
46
+
47
+ ```bash
48
+ cd js
49
+ npm install # installs dybuf runtime for this package
50
+ ```
51
+
52
+ - `npm pack`: build a tarball so other repos can install via `npm install ./path/to/osm-dybuf-*.tgz`
53
+ - `npm link`: link the package locally (`npm link` here, then `npm link @truelies/osm-dybuf` in the consumer repo)
54
+ - `npm publish --access public`: publish to npm (requires the `@truelies` scope). If the scope is not available, rename `package.json` → `"name": "truelies-osm-dybuf"` and publish without a scope.
55
+
56
+ ## Files
57
+
58
+ - `osm_dybuf.mjs` / `.d.mts`: DyBuf parser entry points
59
+ - `schema_ids.mjs` / `.d.mts`: shared identifiers (geometry, feature, region, segment roles)
60
+ - `region_numeric_codes.json`: latest region ID table (generated via `scripts/dump_region_codes.py`)
61
+ - `inspect_dybuf.mjs`: CLI tool that uses the package locally
62
+
63
+ When schema IDs or DyBuf layouts change in the exporter, remember to bump the version here and re-publish/install so frontends and tools stay in sync.
@@ -0,0 +1,47 @@
1
+ export declare const INT_COORD_SCALE: number;
2
+ export declare const GRID_FINE_RES: number;
3
+ export declare const GRID_LV0_UNIT: number;
4
+ export declare class Gridex {
5
+ readonly londex: number;
6
+ readonly latdex: number;
7
+ constructor(londex: number, latdex: number);
8
+ toString(): string;
9
+ equals(other: Gridex): boolean;
10
+ }
11
+ export declare class GridexAtLv extends Gridex {
12
+ readonly level: number;
13
+ constructor(unit: GridUnit, londex: number, latdex: number);
14
+ boundsInt(applyTriangle?: boolean): [number, number, number, number];
15
+ boundsFine(applyTriangle?: boolean): [number, number, number, number];
16
+ boundsFloat(applyTriangle?: boolean): {
17
+ minLon: number;
18
+ minLat: number;
19
+ maxLon: number;
20
+ maxLat: number;
21
+ };
22
+ }
23
+ export declare class GridUnit {
24
+ readonly unitFine: number;
25
+ readonly level: number;
26
+ readonly minLondex: number;
27
+ readonly maxLondex: number;
28
+ readonly minLatdex: number;
29
+ readonly maxLatdex: number;
30
+ constructor(level: number);
31
+ makeGridex(londex: number, latdex: number): GridexAtLv;
32
+ gridexesAt(longitudeInt: number, latitudeInt: number): GridexAtLv[];
33
+ gridexesWithExt(longitudeInt: number, latitudeInt: number, ext?: number): GridexAtLv[];
34
+ boundsOfGridFine(gridex: Gridex, applyTriangle?: boolean): [number, number, number, number];
35
+ boundsOfGrid(gridex: Gridex, applyTriangle?: boolean): [number, number, number, number];
36
+ gridexRangeForBounds(
37
+ minLon: number,
38
+ minLat: number,
39
+ maxLon: number,
40
+ maxLat: number
41
+ ): {
42
+ minLondex: number;
43
+ maxLondex: number;
44
+ minLatdex: number;
45
+ maxLatdex: number;
46
+ };
47
+ }
package/grid_index.mjs ADDED
@@ -0,0 +1,190 @@
1
+ // Gridex helpers (integer math) aligned with Python grid_index.py
2
+
3
+ export const INT_COORD_SCALE = 1_000_000_000; // 1e9
4
+ export const GRID_FINE_RES = 64;
5
+ export const GRID_LV0_UNIT = 30 * INT_COORD_SCALE;
6
+ const GRID_LV0_UNIT_FINE = GRID_LV0_UNIT * GRID_FINE_RES;
7
+ const GRID_LV0_LONDEX = 6;
8
+ const GRID_LV0_LATDEX = 3;
9
+ const MAX_LEVEL = 16;
10
+ const MAX_LONGITUDE_FINE = 180 * INT_COORD_SCALE * GRID_FINE_RES;
11
+ const MIN_LONGITUDE_FINE = -MAX_LONGITUDE_FINE;
12
+ const MAX_LATITUDE_FINE = 90 * INT_COORD_SCALE * GRID_FINE_RES;
13
+ const MIN_LATITUDE_FINE = -MAX_LATITUDE_FINE;
14
+
15
+ export class Gridex {
16
+ constructor(londex, latdex) {
17
+ this.londex = londex;
18
+ this.latdex = latdex;
19
+ }
20
+ toString() {
21
+ return `(${this.londex},${this.latdex})`;
22
+ }
23
+ equals(other) {
24
+ return this.londex === other.londex && this.latdex === other.latdex;
25
+ }
26
+ }
27
+
28
+ export class GridexAtLv extends Gridex {
29
+ constructor(unit, londex, latdex) {
30
+ super(londex, latdex);
31
+ this.unit = unit;
32
+ }
33
+ get level() {
34
+ return this.unit.level;
35
+ }
36
+ boundsInt(applyTriangle = false) {
37
+ return this.unit.boundsOfGrid(this, applyTriangle);
38
+ }
39
+ boundsFine(applyTriangle = false) {
40
+ return this.unit.boundsOfGridFine(this, applyTriangle);
41
+ }
42
+ boundsFloat(applyTriangle = false) {
43
+ const [minLon, minLat, maxLon, maxLat] = this.boundsInt(applyTriangle);
44
+ return {
45
+ minLon: minLon / INT_COORD_SCALE,
46
+ minLat: minLat / INT_COORD_SCALE,
47
+ maxLon: maxLon / INT_COORD_SCALE,
48
+ maxLat: maxLat / INT_COORD_SCALE,
49
+ };
50
+ }
51
+ }
52
+
53
+ export class GridUnit {
54
+ constructor(level) {
55
+ if (!Number.isInteger(level) || level < 0 || level > MAX_LEVEL) {
56
+ throw new Error(`level must be an integer between 0 and ${MAX_LEVEL}`);
57
+ }
58
+ this.level = level;
59
+ this.unitFine = GRID_LV0_UNIT_FINE >> level;
60
+ this.maxLondex = MAX_LONGITUDE_FINE / this.unitFine;
61
+ this.minLondex = -this.maxLondex;
62
+ this.maxLatdex = MAX_LATITUDE_FINE / this.unitFine;
63
+ this.minLatdex = -this.maxLatdex;
64
+ }
65
+
66
+ makeGridex(londex, latdex) {
67
+ return new GridexAtLv(this, londex, latdex);
68
+ }
69
+
70
+ _axisIndices(valueFine, axis) {
71
+ const max = axis === "lon" ? this.maxLondex : this.maxLatdex;
72
+ const min = axis === "lon" ? this.minLondex : this.minLatdex;
73
+ const unit = this.unitFine;
74
+
75
+ if (valueFine === 0) return [-1, 1];
76
+ if (valueFine === max * unit) return [max];
77
+ if (valueFine === min * unit) return [min];
78
+ if (valueFine % unit === 0) {
79
+ const idx = valueFine / unit;
80
+ return [idx, idx + (idx > 0 ? 1 : -1)];
81
+ }
82
+ return [Math.ceil(valueFine / unit)];
83
+ }
84
+
85
+ gridexesAt(longitudeInt, latitudeInt) {
86
+ const lonFine = longitudeInt * GRID_FINE_RES;
87
+ const latFine = latitudeInt * GRID_FINE_RES;
88
+ if (lonFine < MIN_LONGITUDE_FINE || lonFine > MAX_LONGITUDE_FINE) {
89
+ throw new Error("longitude out of range");
90
+ }
91
+ if (latFine < MIN_LATITUDE_FINE || latFine > MAX_LATITUDE_FINE) {
92
+ throw new Error("latitude out of range");
93
+ }
94
+ if (Math.abs(latFine) === MAX_LATITUDE_FINE) {
95
+ const latdex = latFine > 0 ? this.maxLatdex : this.minLatdex;
96
+ const res = [];
97
+ for (let i = 0; i < this.maxLondex; i++) {
98
+ res.push(this.makeGridex(i + 1, latdex));
99
+ res.push(this.makeGridex(-(i + 1), latdex));
100
+ }
101
+ return res;
102
+ }
103
+ const lonIdx = this._axisIndices(lonFine, "lon");
104
+ const latIdx = this._axisIndices(latFine, "lat");
105
+ const res = [];
106
+ for (const lx of lonIdx) {
107
+ for (const ly of latIdx) {
108
+ res.push(this.makeGridex(lx, ly));
109
+ }
110
+ }
111
+ return res;
112
+ }
113
+
114
+ gridexesWithExt(longitudeInt, latitudeInt, ext = 0) {
115
+ const radius = Number(ext);
116
+ if (!Number.isInteger(radius) || radius < 0) {
117
+ throw new Error("ext must be a non-negative integer");
118
+ }
119
+ const baseLonFine = longitudeInt * GRID_FINE_RES;
120
+ const baseLatFine = latitudeInt * GRID_FINE_RES;
121
+ const seen = new Set();
122
+ const result = [];
123
+
124
+ const wrapLon = (lonFine) => {
125
+ const span = 2 * MAX_LONGITUDE_FINE;
126
+ let v = lonFine;
127
+ while (v > MAX_LONGITUDE_FINE) v -= span;
128
+ while (v < MIN_LONGITUDE_FINE) v += span;
129
+ return v;
130
+ };
131
+ const clampLat = (latFine) => {
132
+ if (latFine > MAX_LATITUDE_FINE) return MAX_LATITUDE_FINE;
133
+ if (latFine < MIN_LATITUDE_FINE) return MIN_LATITUDE_FINE;
134
+ return latFine;
135
+ };
136
+
137
+ for (let dx = -radius; dx <= radius; dx++) {
138
+ for (let dy = -radius; dy <= radius; dy++) {
139
+ const lonFine = wrapLon(baseLonFine + dx * this.unitFine);
140
+ const latFine = clampLat(baseLatFine + dy * this.unitFine);
141
+ const lonInt = lonFine / GRID_FINE_RES;
142
+ const latInt = latFine / GRID_FINE_RES;
143
+ const cells = this.gridexesAt(lonInt, latInt);
144
+ for (const cell of cells) {
145
+ const key = `${cell.level}:${cell.londex},${cell.latdex}`;
146
+ if (!seen.has(key)) {
147
+ seen.add(key);
148
+ result.push(cell);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+
156
+ boundsOfGridFine(gridex, applyTriangle = false) {
157
+ const { londex, latdex } = gridex;
158
+ const minLonFine = (londex - (londex > 0 ? 1 : 0)) * this.unitFine;
159
+ const minLatFine = (latdex - (latdex > 0 ? 1 : 0)) * this.unitFine;
160
+ const maxLonFine = (londex + (londex > 0 ? 0 : 1)) * this.unitFine;
161
+ const maxLatFine = (latdex + (latdex > 0 ? 0 : 1)) * this.unitFine;
162
+
163
+ if (applyTriangle) {
164
+ if (latdex === this.maxLatdex) {
165
+ return [minLonFine, minLatFine, maxLonFine, minLatFine];
166
+ }
167
+ if (latdex === this.minLatdex) {
168
+ return [minLonFine, minLatFine, maxLonFine, maxLatFine];
169
+ }
170
+ }
171
+ return [minLonFine, minLatFine, maxLonFine, maxLatFine];
172
+ }
173
+
174
+ boundsOfGrid(gridex, applyTriangle = false) {
175
+ const [a, b, c, d] = this.boundsOfGridFine(gridex, applyTriangle);
176
+ return [a / GRID_FINE_RES, b / GRID_FINE_RES, c / GRID_FINE_RES, d / GRID_FINE_RES];
177
+ }
178
+
179
+ gridexRangeForBounds(minLon, minLat, maxLon, maxLat) {
180
+ if (minLon > maxLon || minLat > maxLat) {
181
+ throw new Error("min must be <= max");
182
+ }
183
+ const unit = this.unitFine;
184
+ const minLondex = Math.max(this.minLondex, Math.floor((minLon * GRID_FINE_RES) / unit) - 1);
185
+ const maxLondex = Math.min(this.maxLondex, Math.ceil((maxLon * GRID_FINE_RES) / unit) + 1);
186
+ const minLatdex = Math.max(this.minLatdex, Math.floor((minLat * GRID_FINE_RES) / unit) - 1);
187
+ const maxLatdex = Math.min(this.maxLatdex, Math.ceil((maxLat * GRID_FINE_RES) / unit) + 1);
188
+ return { minLondex, maxLondex, minLatdex, maxLatdex };
189
+ }
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truelies/osm-dybuf",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Gridex OSM DyBuf parser and schema utilities",
5
5
  "type": "module",
6
6
  "main": "./osm_dybuf.mjs",
@@ -15,6 +15,10 @@
15
15
  "import": "./schema_ids.mjs",
16
16
  "types": "./schema_ids.d.mts"
17
17
  },
18
+ "./grid_index": {
19
+ "import": "./grid_index.mjs",
20
+ "types": "./grid_index.d.ts"
21
+ },
18
22
  "./region_numeric_codes.json": "./region_numeric_codes.json"
19
23
  },
20
24
  "files": [
@@ -22,7 +26,9 @@
22
26
  "osm_dybuf.d.mts",
23
27
  "schema_ids.mjs",
24
28
  "schema_ids.d.mts",
25
- "region_numeric_codes.json"
29
+ "region_numeric_codes.json",
30
+ "grid_index.mjs",
31
+ "grid_index.d.ts"
26
32
  ],
27
33
  "keywords": [
28
34
  "dybuf",