@truelies/osm-dybuf 0.3.3 → 0.4.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 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). Use `schema_ids` to decode feature strings.
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
@@ -22,7 +23,10 @@ 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
  - `place_label` remains in schema IDs for compatibility, but current exports no longer emit place-name labels to the frontend.
25
- - `road_express` was added for motorway / controlled-access corridors.
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.x`: `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).
26
30
  - `water_wetland` separates marsh / bog / fen / wetland polygons from deeper open water.
27
31
  - Current project max level is `13`; newer exports no longer emit `lv14~16`.
28
32
 
@@ -37,6 +41,9 @@ npm install @truelies/osm-dybuf
37
41
  import { parseOsmDybuf } from "@truelies/osm-dybuf";
38
42
  import {
39
43
  FEATURE_KIND_IDS,
44
+ ID_TO_FEATURE_KIND,
45
+ MAIN_FEATURE_IDS,
46
+ unpackFeatureId,
40
47
  REGION_NUMERIC_CODES,
41
48
  } from "@truelies/osm-dybuf/schema_ids";
42
49
 
@@ -46,6 +53,10 @@ const parsed = parseOsmDybuf(data, cell);
46
53
 
47
54
  if (parsed.format === "flat_v1") {
48
55
  console.log(parsed.features.length);
56
+ console.log(parsed.features[0].featureId); // numeric feature id
57
+ console.log(ID_TO_FEATURE_KIND[String(parsed.features[0].featureId)]);
58
+ console.log(unpackFeatureId(parsed.features[0].featureId));
59
+ console.log((parsed.features[0].featureId & 0x3f) === MAIN_FEATURE_IDS.label);
49
60
  } else if (parsed.format === "v2_indexed") {
50
61
  console.log(parsed.entries.length);
51
62
  } else {
@@ -67,6 +78,11 @@ if (parsed.format === "flat_v1") {
67
78
  --level 8 --londex 213 --latdex 161 --limit 5
68
79
  ```
69
80
 
81
+ Feature ID usage notes:
82
+ - Use `FEATURE_KIND_IDS.*` for exact feature comparison, for example `featureId === FEATURE_KIND_IDS.waterbody`.
83
+ - Use `MAIN_FEATURE_IDS.*` with `featureId & 0x3f` when you want to group all subtypes under the same main feature, for example `(featureId & 0x3f) === MAIN_FEATURE_IDS.label`.
84
+ - Use `unpackFeatureId(featureId)` for debug output or subtype-aware logic where readability matters more than raw hot-path comparisons.
85
+
70
86
  ### Grid helpers (grid_index)
71
87
 
72
88
  `grid_index.mjs` exposes Gridex helpers (integers, pole triangles) aligned with the exporter:
@@ -123,4 +139,4 @@ Unit test example (`tests/grid_index.test.mjs`) covers:
123
139
  - `region_numeric_codes.json`: latest region ID table (generated via `scripts/dump_region_codes.py`)
124
140
  - `inspect_dybuf.mjs`: CLI tool that uses the package locally
125
141
 
126
- 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.3.3` aligns the package with the current `lv0~13` export range and the frontend feature set (`water_wetland`, `building`, no exported `place_label`).
142
+ 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.1` aligns the package with the current `lv0~13` export range, the current frontend feature set (`water_wetland`, `building`, derived `label_*`, no exported `place_label`), and the numeric `featureId` parser contract.
package/osm_dybuf.d.mts CHANGED
@@ -24,7 +24,7 @@ export interface GridElement {
24
24
  }
25
25
 
26
26
  export interface GridFeature {
27
- featureId: string;
27
+ featureId: number;
28
28
  elements: GridElement[];
29
29
  }
30
30
 
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truelies/osm-dybuf",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Gridex OSM DyBuf parser and schema utilities",
5
5
  "type": "module",
6
6
  "main": "./osm_dybuf.mjs",
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
@@ -33,6 +33,7 @@ export const MAIN_FEATURE_IDS = Object.freeze({
33
33
  waterway: 8,
34
34
  place_label: 9,
35
35
  building: 10,
36
+ label: 11,
36
37
  });
37
38
 
38
39
  const FEATURE_KIND_CODES = Object.freeze({
@@ -45,6 +46,11 @@ const FEATURE_KIND_CODES = Object.freeze({
45
46
  waterbody: ["waterbody", 0],
46
47
  water_wetland: ["waterbody", 1],
47
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],
48
54
  landuse: ["landuse", 0],
49
55
  park: ["park", 0],
50
56
  road_express: ["road", 0],
@@ -58,11 +64,62 @@ const FEATURE_KIND_CODES = Object.freeze({
58
64
  place_label: ["place_label", 0],
59
65
  });
60
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
+
61
118
  export const FEATURE_KIND_IDS = Object.freeze(
62
119
  Object.fromEntries(
63
120
  Object.entries(FEATURE_KIND_CODES).map(([kind, [main, sub]]) => [
64
121
  kind,
65
- sub * 64 + MAIN_FEATURE_IDS[main],
122
+ packFeatureId(main, sub),
66
123
  ])
67
124
  )
68
125
  );
@@ -70,7 +127,12 @@ export const FEATURE_KIND_IDS = Object.freeze(
70
127
  export const REGION_NUMERIC_CODES = Object.freeze(REGION_CODES_RAW);
71
128
 
72
129
  export const ID_TO_FEATURE_KIND = Object.freeze(
73
- Object.fromEntries(Object.entries(FEATURE_KIND_IDS).map(([k, v]) => [String(v), k]))
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
+ }
74
136
  );
75
137
 
76
138
  export const SEGMENT_ROLE_ID_UNSET = 0x00;