@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 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
@@ -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
- - Administrative naming should come from `place_label`.
25
- - `road_express` was added for motorway / controlled-access corridors.
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 (`16`)
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, `lv16` has no lower 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.3.2` adds the `road_express` feature group and aligns the package docs with the current no-`admin_boundary` frontend export policy.
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 = 16;
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
@@ -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.2",
3
+ "version": "0.4.0",
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
@@ -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
- sub * 64 + MAIN_FEATURE_IDS[main],
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
- 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
+ }
71
136
  );
72
137
 
73
138
  export const SEGMENT_ROLE_ID_UNSET = 0x00;