@truelies/osm-dybuf 0.3.2 → 0.4.0
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 +17 -6
- package/grid_index.mjs +1 -1
- package/osm_dybuf.d.mts +1 -1
- package/osm_dybuf.mjs +1 -4
- package/package.json +1 -1
- package/schema_ids.d.mts +5 -0
- package/schema_ids.mjs +67 -2
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Gridex `osm.dybuf` parser utilities shared by the exporter, inspection scripts, and Next.js frontend.
|
|
4
4
|
This package wraps the shared schema tables (`schema_ids`) and exposes a high-level `parseOsmDybuf` helper.
|
|
5
5
|
It depends on the published [`dybuf`](https://www.npmjs.com/package/dybuf) runtime (≥ 0.4.2).
|
|
6
|
-
Feature IDs are packed as `feature_id = sub_id * 64 + main_id` (both 0–63).
|
|
6
|
+
Feature IDs are packed as `feature_id = sub_id * 64 + main_id` (both 0–63). The parser now returns numeric `featureId`; use `schema_ids` helpers to decode debug strings or subtypes when needed.
|
|
7
|
+
`schema_ids` also exports `packFeatureId(...)`, `unpackFeatureId(...)`, and `labelFeatureId(...)` for subtype-aware features such as future `label` overlays.
|
|
7
8
|
Current road groups are `road_express`, `road_major`, `road_minor`, `road_local`, and `road_trail`.
|
|
8
9
|
|
|
9
10
|
## Current Compatibility Status
|
|
@@ -21,8 +22,13 @@ Support matrix:
|
|
|
21
22
|
Current frontend-facing schema notes:
|
|
22
23
|
|
|
23
24
|
- `admin_boundary` is retained in schema IDs for compatibility, but new exports no longer send boundary lines to the frontend.
|
|
24
|
-
-
|
|
25
|
-
- `
|
|
25
|
+
- `place_label` remains in schema IDs for compatibility, but current exports no longer emit place-name labels to the frontend.
|
|
26
|
+
- `label` main feature ID is now reserved for future overlay labels; `sub_id` is intended to carry `label kind` (`worship`, `station`, `custom`, etc.).
|
|
27
|
+
- Current exporter-oriented subtypes are `label_worship` and `label_station`, both derived from existing `building` tasks. Consumers should group all future `label_*` subtypes by `unpackFeatureId(featureId).mainKey === "label"` if they want a single overlay layer.
|
|
28
|
+
- Breaking change in `0.4.0`: `GridFeature.featureId` is numeric. Consumers should map it with `ID_TO_FEATURE_KIND[String(featureId)]` or `unpackFeatureId(featureId)` when string/debug output is needed.
|
|
29
|
+
- `road_express` covers motorway corridors plus `trunk` ways tagged with `motorroad=yes` (for example many Taiwan expressway mainlines).
|
|
30
|
+
- `water_wetland` separates marsh / bog / fen / wetland polygons from deeper open water.
|
|
31
|
+
- Current project max level is `13`; newer exports no longer emit `lv14~16`.
|
|
26
32
|
|
|
27
33
|
## Usage
|
|
28
34
|
|
|
@@ -35,6 +41,8 @@ npm install @truelies/osm-dybuf
|
|
|
35
41
|
import { parseOsmDybuf } from "@truelies/osm-dybuf";
|
|
36
42
|
import {
|
|
37
43
|
FEATURE_KIND_IDS,
|
|
44
|
+
ID_TO_FEATURE_KIND,
|
|
45
|
+
unpackFeatureId,
|
|
38
46
|
REGION_NUMERIC_CODES,
|
|
39
47
|
} from "@truelies/osm-dybuf/schema_ids";
|
|
40
48
|
|
|
@@ -44,6 +52,9 @@ const parsed = parseOsmDybuf(data, cell);
|
|
|
44
52
|
|
|
45
53
|
if (parsed.format === "flat_v1") {
|
|
46
54
|
console.log(parsed.features.length);
|
|
55
|
+
console.log(parsed.features[0].featureId); // numeric feature id
|
|
56
|
+
console.log(ID_TO_FEATURE_KIND[String(parsed.features[0].featureId)]);
|
|
57
|
+
console.log(unpackFeatureId(parsed.features[0].featureId));
|
|
47
58
|
} else if (parsed.format === "v2_indexed") {
|
|
48
59
|
console.log(parsed.entries.length);
|
|
49
60
|
} else {
|
|
@@ -83,7 +94,7 @@ const parent = new GridUnit(9).toUpperLevelGridex(children[0]); // back to level
|
|
|
83
94
|
|
|
84
95
|
- `toLowerLevelGridexes(gridex)`:
|
|
85
96
|
- returns 4 child cells at `level + 1`
|
|
86
|
-
- throws when current unit is already max level (`
|
|
97
|
+
- throws when current unit is already max level (`13`)
|
|
87
98
|
- `toUpperLevelGridex(gridex)`:
|
|
88
99
|
- returns the parent cell at `level - 1`
|
|
89
100
|
- throws when current unit is level `0`
|
|
@@ -99,7 +110,7 @@ npm test # runs unit tests (node:test)
|
|
|
99
110
|
Unit test example (`tests/grid_index.test.mjs`) covers:
|
|
100
111
|
- `gridexesAt` near axis (no zero index cell)
|
|
101
112
|
- `toLowerLevelGridexes` and `toUpperLevelGridex` roundtrip
|
|
102
|
-
- level boundary errors (`lv0` has no upper level, `
|
|
113
|
+
- level boundary errors (`lv0` has no upper level, `lv13` has no lower level)
|
|
103
114
|
|
|
104
115
|
- `npm pack`: build a tarball so other repos can install via `npm install ./path/to/osm-dybuf-*.tgz`
|
|
105
116
|
- `npm link`: link the package locally (`npm link` here, then `npm link @truelies/osm-dybuf` in the consumer repo)
|
|
@@ -121,4 +132,4 @@ Unit test example (`tests/grid_index.test.mjs`) covers:
|
|
|
121
132
|
- `region_numeric_codes.json`: latest region ID table (generated via `scripts/dump_region_codes.py`)
|
|
122
133
|
- `inspect_dybuf.mjs`: CLI tool that uses the package locally
|
|
123
134
|
|
|
124
|
-
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. Version `0.
|
|
135
|
+
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. Version `0.4.0` aligns the package with the current `lv0~13` export range, the current frontend feature set (`water_wetland`, `building`, no exported `place_label`), and the numeric `featureId` parser contract.
|
package/grid_index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const GRID_LV0_UNIT = 30 * INT_COORD_SCALE;
|
|
|
6
6
|
const GRID_LV0_UNIT_FINE = GRID_LV0_UNIT * GRID_FINE_RES;
|
|
7
7
|
const GRID_LV0_LONDEX = 6;
|
|
8
8
|
const GRID_LV0_LATDEX = 3;
|
|
9
|
-
const MAX_LEVEL =
|
|
9
|
+
const MAX_LEVEL = 13;
|
|
10
10
|
const MAX_LONGITUDE_FINE = 180 * INT_COORD_SCALE * GRID_FINE_RES;
|
|
11
11
|
const MIN_LONGITUDE_FINE = -MAX_LONGITUDE_FINE;
|
|
12
12
|
const MAX_LATITUDE_FINE = 90 * INT_COORD_SCALE * GRID_FINE_RES;
|
package/osm_dybuf.d.mts
CHANGED
package/osm_dybuf.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "dybuf";
|
|
7
7
|
import {
|
|
8
8
|
ID_TO_ENTITY_TYPE,
|
|
9
|
-
ID_TO_FEATURE_KIND,
|
|
10
9
|
ID_TO_GEOMETRY_KIND,
|
|
11
10
|
ID_TO_SEGMENT_ROLE,
|
|
12
11
|
SEGMENT_ROLE_ID_UNSET,
|
|
@@ -194,15 +193,13 @@ function decodePayload(payloadBytes, cell) {
|
|
|
194
193
|
const features = [];
|
|
195
194
|
for (let i = 0; i < featureCount; i += 1) {
|
|
196
195
|
const featureNumericId = readVarUint(buf);
|
|
197
|
-
const featureId =
|
|
198
|
-
ID_TO_FEATURE_KIND[String(featureNumericId)] ?? `feature:${featureNumericId}`;
|
|
199
196
|
const entryCount = readVarUint(buf);
|
|
200
197
|
const elements = [];
|
|
201
198
|
for (let j = 0; j < entryCount; j += 1) {
|
|
202
199
|
const entryBytes = readVarBytes(buf);
|
|
203
200
|
elements.push(decodeGridPayload(entryBytes, cell));
|
|
204
201
|
}
|
|
205
|
-
features.push({ featureId, elements });
|
|
202
|
+
features.push({ featureId: featureNumericId, elements });
|
|
206
203
|
}
|
|
207
204
|
if (buf.position() !== buf.limit()) {
|
|
208
205
|
throw new Error(
|
package/package.json
CHANGED
package/schema_ids.d.mts
CHANGED
|
@@ -3,6 +3,11 @@ export declare const ID_TO_ENTITY_TYPE: Readonly<Record<number, string>>;
|
|
|
3
3
|
export declare const GEOMETRY_KIND_IDS: Readonly<Record<string, number>>;
|
|
4
4
|
export declare const ID_TO_GEOMETRY_KIND: Readonly<Record<number, string>>;
|
|
5
5
|
export declare const MAIN_FEATURE_IDS: Readonly<Record<string, number>>;
|
|
6
|
+
export declare const LABEL_KIND_SUBTYPE_IDS: Readonly<Record<string, number>>;
|
|
7
|
+
export declare function packFeatureId(mainKey: string, subId: number): number;
|
|
8
|
+
export declare function unpackFeatureId(featureNumericId: number): { mainKey: string; subId: number };
|
|
9
|
+
export declare function labelFeatureId(labelKind?: string): number;
|
|
10
|
+
export declare function labelKindFromFeatureId(featureNumericId: number): string;
|
|
6
11
|
export declare const FEATURE_KIND_IDS: Readonly<Record<string, number>>;
|
|
7
12
|
export declare const ID_TO_FEATURE_KIND: Readonly<Record<number, string>>;
|
|
8
13
|
export declare const REGION_NUMERIC_CODES: Readonly<Record<string, number>>;
|
package/schema_ids.mjs
CHANGED
|
@@ -32,6 +32,8 @@ export const MAIN_FEATURE_IDS = Object.freeze({
|
|
|
32
32
|
road: 7,
|
|
33
33
|
waterway: 8,
|
|
34
34
|
place_label: 9,
|
|
35
|
+
building: 10,
|
|
36
|
+
label: 11,
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
const FEATURE_KIND_CODES = Object.freeze({
|
|
@@ -42,6 +44,13 @@ const FEATURE_KIND_CODES = Object.freeze({
|
|
|
42
44
|
natural_vegetation: ["natural", 1],
|
|
43
45
|
natural_bare: ["natural", 2],
|
|
44
46
|
waterbody: ["waterbody", 0],
|
|
47
|
+
water_wetland: ["waterbody", 1],
|
|
48
|
+
building: ["building", 0],
|
|
49
|
+
label: ["label", 0],
|
|
50
|
+
label_worship: ["label", 1],
|
|
51
|
+
label_station: ["label", 2],
|
|
52
|
+
label_custom: ["label", 3],
|
|
53
|
+
label_admin: ["label", 4],
|
|
45
54
|
landuse: ["landuse", 0],
|
|
46
55
|
park: ["park", 0],
|
|
47
56
|
road_express: ["road", 0],
|
|
@@ -55,11 +64,62 @@ const FEATURE_KIND_CODES = Object.freeze({
|
|
|
55
64
|
place_label: ["place_label", 0],
|
|
56
65
|
});
|
|
57
66
|
|
|
67
|
+
export const LABEL_KIND_SUBTYPE_IDS = Object.freeze({
|
|
68
|
+
generic: 0,
|
|
69
|
+
worship: 1,
|
|
70
|
+
station: 2,
|
|
71
|
+
custom: 3,
|
|
72
|
+
admin: 4,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export function packFeatureId(mainKey, subId) {
|
|
76
|
+
const mainId = MAIN_FEATURE_IDS[mainKey];
|
|
77
|
+
if (mainId === undefined) {
|
|
78
|
+
throw new Error(`Unknown feature main key: ${mainKey}`);
|
|
79
|
+
}
|
|
80
|
+
if (subId < 0 || subId >= 64) {
|
|
81
|
+
throw new Error(`Feature sub id must be 0-63 (got ${subId})`);
|
|
82
|
+
}
|
|
83
|
+
return subId * 64 + mainId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function unpackFeatureId(featureNumericId) {
|
|
87
|
+
const numericId = Number(featureNumericId);
|
|
88
|
+
const mainId = numericId & 0x3f;
|
|
89
|
+
const subId = numericId >> 6;
|
|
90
|
+
const mainKey =
|
|
91
|
+
Object.entries(MAIN_FEATURE_IDS).find(([, value]) => value === mainId)?.[0] ?? null;
|
|
92
|
+
if (!mainKey) {
|
|
93
|
+
throw new Error(`Unknown feature main id ${mainId} in feature id ${numericId}`);
|
|
94
|
+
}
|
|
95
|
+
return { mainKey, subId };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function labelFeatureId(labelKind = "generic") {
|
|
99
|
+
const normalized = String(labelKind).trim().toLowerCase();
|
|
100
|
+
const subId = LABEL_KIND_SUBTYPE_IDS[normalized];
|
|
101
|
+
if (subId === undefined) {
|
|
102
|
+
throw new Error(`Unknown label kind: ${labelKind}`);
|
|
103
|
+
}
|
|
104
|
+
return packFeatureId("label", subId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function labelKindFromFeatureId(featureNumericId) {
|
|
108
|
+
const { mainKey, subId } = unpackFeatureId(featureNumericId);
|
|
109
|
+
if (mainKey !== "label") {
|
|
110
|
+
throw new Error(`Feature id ${featureNumericId} is not a label feature`);
|
|
111
|
+
}
|
|
112
|
+
return (
|
|
113
|
+
Object.entries(LABEL_KIND_SUBTYPE_IDS).find(([, value]) => value === subId)?.[0] ??
|
|
114
|
+
"generic"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
export const FEATURE_KIND_IDS = Object.freeze(
|
|
59
119
|
Object.fromEntries(
|
|
60
120
|
Object.entries(FEATURE_KIND_CODES).map(([kind, [main, sub]]) => [
|
|
61
121
|
kind,
|
|
62
|
-
|
|
122
|
+
packFeatureId(main, sub),
|
|
63
123
|
])
|
|
64
124
|
)
|
|
65
125
|
);
|
|
@@ -67,7 +127,12 @@ export const FEATURE_KIND_IDS = Object.freeze(
|
|
|
67
127
|
export const REGION_NUMERIC_CODES = Object.freeze(REGION_CODES_RAW);
|
|
68
128
|
|
|
69
129
|
export const ID_TO_FEATURE_KIND = Object.freeze(
|
|
70
|
-
|
|
130
|
+
{
|
|
131
|
+
...Object.fromEntries(Object.entries(FEATURE_KIND_IDS).map(([k, v]) => [String(v), k])),
|
|
132
|
+
...Object.fromEntries(
|
|
133
|
+
Object.values(LABEL_KIND_SUBTYPE_IDS).map((subId) => [String(packFeatureId("label", subId)), "label"])
|
|
134
|
+
),
|
|
135
|
+
}
|
|
71
136
|
);
|
|
72
137
|
|
|
73
138
|
export const SEGMENT_ROLE_ID_UNSET = 0x00;
|