@osmix/vt 0.0.2 → 0.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAA;AAE7C,MAAM,MAAM,uBAAuB,GAAG,EAAE,EAAE,EAAE,CAAA;AAE5C,MAAM,MAAM,yBAAyB,GAAG;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAA;AAEX,MAAM,MAAM,mBAAmB,GAAG;IACjC,KAAK,EAAE,CAAC,CAAA;IACR,IAAI,EAAE,CAAC,CAAA;IACP,OAAO,EAAE,CAAC,CAAA;CACV,CAAA;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,mBAAmB,CAAC,MAAM,mBAAmB,CAAC,CAAA;IACpD,UAAU,EAAE,yBAAyB,CAAA;IACrC,QAAQ,EAAE,uBAAuB,CAAA;CACjC;AAED,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,SAAS,CAAC,eAAe,CAAC,CAAA;CACpC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAA;AAEtD;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,EAAE,EAAE,EAAE,CAAA;AAE5C;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAA;AAEX;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IACjC,KAAK,EAAE,CAAC,CAAA;IACR,IAAI,EAAE,CAAC,CAAA;IACP,OAAO,EAAE,CAAC,CAAA;CACV,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,mBAAmB,CAAC,MAAM,mBAAmB,CAAC,CAAA;IACpD,UAAU,EAAE,yBAAyB,CAAA;IACrC,QAAQ,EAAE,uBAAuB,CAAA;CACjC;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,SAAS,CAAC,eAAe,CAAC,CAAA;CACpC,CAAA"}
package/dist/types.js CHANGED
@@ -1 +1,9 @@
1
+ /**
2
+ * Type definitions for vector tile encoding.
3
+ *
4
+ * These types represent the intermediate format used when converting
5
+ * OSM entities to Mapbox Vector Tile features.
6
+ *
7
+ * @module
8
+ */
1
9
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -1,6 +1,20 @@
1
+ /**
2
+ * Vector tile PBF writer.
3
+ *
4
+ * Encodes vector tile layers and features into the Mapbox Vector Tile
5
+ * binary format (PBF/protobuf). Handles key/value deduplication,
6
+ * geometry encoding with delta compression, and proper command sequences.
7
+ *
8
+ * @see https://github.com/mapbox/vector-tile-spec
9
+ *
10
+ * @module
11
+ */
1
12
  import type { VtPbfLayer } from "./types";
2
13
  /**
3
- * Write Vector Tile Layers to a PBF buffer
14
+ * Write vector tile layers to a PBF buffer.
15
+ *
16
+ * @param layers - Array of layers to encode.
17
+ * @returns ArrayBuffer containing the encoded vector tile.
4
18
  */
5
19
  export default function writeVtPbf(layers: VtPbfLayer[]): ArrayBuffer;
6
20
  //# sourceMappingURL=write-vt-pbf.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"write-vt-pbf.d.ts","sourceRoot":"","sources":["../src/write-vt-pbf.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,SAAS,CAAA;AAU1D;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,GAKxB,WAAW,CACzC"}
1
+ {"version":3,"file":"write-vt-pbf.d.ts","sourceRoot":"","sources":["../src/write-vt-pbf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,SAAS,CAAA;AAW1D;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,GAKxB,WAAW,CACzC"}
@@ -1,6 +1,21 @@
1
+ /**
2
+ * Vector tile PBF writer.
3
+ *
4
+ * Encodes vector tile layers and features into the Mapbox Vector Tile
5
+ * binary format (PBF/protobuf). Handles key/value deduplication,
6
+ * geometry encoding with delta compression, and proper command sequences.
7
+ *
8
+ * @see https://github.com/mapbox/vector-tile-spec
9
+ *
10
+ * @module
11
+ */
12
+ import { zigzag, zigzag32 } from "@osmix/shared/zigzag";
1
13
  import Pbf from "pbf";
2
14
  /**
3
- * Write Vector Tile Layers to a PBF buffer
15
+ * Write vector tile layers to a PBF buffer.
16
+ *
17
+ * @param layers - Array of layers to encode.
18
+ * @returns ArrayBuffer containing the encoded vector tile.
4
19
  */
5
20
  export default function writeVtPbf(layers) {
6
21
  const pbf = new Pbf();
@@ -40,7 +55,11 @@ function writeLayer(layer, pbf) {
40
55
  }
41
56
  function writeFeature(ctx, pbf) {
42
57
  if (ctx.feature.id !== undefined) {
43
- pbf.writeVarintField(1, ctx.feature.id);
58
+ const id = ctx.feature.id;
59
+ // Use zigzag encoding for IDs to convert negative IDs to positive numbers
60
+ // that can be properly decoded. Uses arithmetic-based encoding to support
61
+ // the full safe integer range.
62
+ pbf.writeVarintField(1, zigzag(id));
44
63
  }
45
64
  pbf.writeMessage(2, writeProperties, ctx);
46
65
  pbf.writeVarintField(3, ctx.feature.type);
@@ -74,9 +93,6 @@ function writeProperties(ctx, pbf) {
74
93
  function command(cmd, length) {
75
94
  return (length << 3) + (cmd & 0x7);
76
95
  }
77
- function zigzag(num) {
78
- return (num << 1) ^ (num >> 31);
79
- }
80
96
  function writeGeometry(feature, pbf) {
81
97
  const type = feature.type;
82
98
  let x = 0;
@@ -97,8 +113,9 @@ function writeGeometry(feature, pbf) {
97
113
  }
98
114
  const dx = xy[0] - x;
99
115
  const dy = xy[1] - y;
100
- pbf.writeVarint(zigzag(dx));
101
- pbf.writeVarint(zigzag(dy));
116
+ // Use bitwise zigzag for geometry deltas (small values, 32-bit is sufficient)
117
+ pbf.writeVarint(zigzag32(dx));
118
+ pbf.writeVarint(zigzag32(dy));
102
119
  x += dx;
103
120
  y += dy;
104
121
  });
@@ -1 +1 @@
1
- {"version":3,"file":"write-vt-pbf.js","sourceRoot":"","sources":["../src/write-vt-pbf.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAA;AAWrB;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAAoB;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAA;IACrB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,MAAqB,CAAA;AAC1C,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB,EAAE,GAAQ;IAC9C,GAAG,CAAC,gBAAgB,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,CAAA;IAC5C,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IACzC,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,CAAA;IAE7C,IAAI,OAAmC,CAAA;IACvC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,GAAG;gBACT,OAAO;gBACP,IAAI,EAAE,EAAc;gBACpB,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,EAAE;gBACZ,UAAU,EAAE,EAAE;aACd,CAAA;QACF,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,OAAO,GAAG,OAAO,CAAA;QAC1B,CAAC;QACD,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAED,IAAI,CAAC,OAAO;QAAE,OAAM;IACpB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5B,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAChC,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAAmB,EAAE,GAAQ;IAClD,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAClC,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC;IAED,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,eAAe,EAAE,GAAG,CAAC,CAAA;IACzC,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;AAChD,CAAC;AAED,SAAS,eAAe,CAAC,GAAmB,EAAE,GAAQ;IACrD,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QAC/D,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAM,CAAC,qCAAqC;QAEhE,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAClB,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;YAC9B,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAA;QAC7B,CAAC;QACD,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;QAEzB,MAAM,IAAI,GAAG,OAAO,KAAK,CAAA;QACzB,MAAM,QAAQ,GACb,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ;YAC3D,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YACvB,CAAC,CAAC,KAAK,CAAA;QACT,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAA;QACtC,IAAI,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,OAAO,UAAU,KAAK,WAAW,EAAE,CAAC;YACvC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtB,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;YAClC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAA;QACtC,CAAC;QACD,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;AACH,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,MAAc;IAC3C,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IAC1B,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,aAAa,CAAC,OAAwB,EAAE,GAAQ;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YAChB,KAAK,GAAG,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC;QACD,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAA,CAAC,SAAS;QAC5C,8CAA8C;QAC9C,MAAM,SAAS,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;QAC5D,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;YACtB,IAAI,CAAC,IAAI,SAAS;gBAAE,OAAM;YAC1B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAA,CAAC,SAAS;YACrD,CAAC;YACD,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;YACpB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;YACpB,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YAC3B,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YAC3B,CAAC,IAAI,EAAE,CAAA;YACP,CAAC,IAAI,EAAE,CAAA;QACR,CAAC,CAAC,CAAA;QACF,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA,CAAC,YAAY;QAC5C,CAAC;IACF,CAAC,CAAC,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,GAAQ;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAC/B,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QACvC,GAAG,CAAC,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAChC,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;aAAM,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;aAAM,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAChC,CAAC;aAAM,CAAC;YACP,GAAG,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;IACF,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"write-vt-pbf.js","sourceRoot":"","sources":["../src/write-vt-pbf.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@osmix/vt",
4
4
  "type": "module",
5
- "version": "0.0.2",
5
+ "version": "0.0.6",
6
6
  "description": "Encode Osmix binary overlay tiles directly into Mapbox Vector Tiles",
7
7
  "main": "./src/index.ts",
8
8
  "publishConfig": {
@@ -34,19 +34,18 @@
34
34
  "scripts": {
35
35
  "build": "tsc",
36
36
  "prepublishOnly": "tsc",
37
- "test": "vitest",
38
- "typecheck": "tsc --noEmit"
37
+ "test": "bun test",
38
+ "typecheck": "tsgo --noEmit"
39
39
  },
40
40
  "dependencies": {
41
+ "@types/bun": "catalog:",
41
42
  "@osmix/shared": "workspace:*",
42
- "@osmix/json": "workspace:*",
43
43
  "pbf": "catalog:"
44
44
  },
45
45
  "devDependencies": {
46
+ "@mapbox/tilebelt": "^2.0.2",
46
47
  "@mapbox/vector-tile": "^2.0.4",
47
48
  "@osmix/core": "workspace:*",
48
- "@types/node": "catalog:",
49
- "typescript": "catalog:",
50
- "vitest": "catalog:"
49
+ "typescript": "catalog:"
51
50
  }
52
51
  }
@@ -1,12 +1,14 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { pointToTile } from "@mapbox/tilebelt"
1
3
  import { VectorTile } from "@mapbox/vector-tile"
2
- import { Osmix } from "@osmix/core"
3
- import SphericalMercatorTile from "@osmix/shared/spherical-mercator"
4
+ import { Osm } from "@osmix/core"
5
+ import { llToTilePx } from "@osmix/shared/tile"
4
6
  import type { GeoBbox2D, Tile } from "@osmix/shared/types"
7
+ import { decodeZigzag } from "@osmix/shared/zigzag"
5
8
  import Protobuf from "pbf"
6
- import { assert, describe, expect, it } from "vitest"
7
9
  import { OsmixVtEncoder } from "./encode"
8
10
 
9
- const osm = new Osmix()
11
+ const osm = new Osm()
10
12
  osm.nodes.addNode({
11
13
  id: 1,
12
14
  lat: 40,
@@ -38,6 +40,7 @@ osm.nodes.addNode({
38
40
  osm.ways.addWay({
39
41
  id: 5,
40
42
  refs: [1, 2],
43
+ tags: { highway: "primary" },
41
44
  })
42
45
  osm.buildIndexes()
43
46
  osm.buildSpatialIndexes()
@@ -51,14 +54,7 @@ function decodeTile(data: ArrayBuffer) {
51
54
  }
52
55
 
53
56
  const extent = 4096
54
- const merc = new SphericalMercatorTile({ size: extent })
55
57
 
56
- function pointToTile(lon: number, lat: number, z: number): Tile {
57
- const [px, py] = merc.px([lon, lat], z)
58
- const x = Math.floor(px / extent)
59
- const y = Math.floor(py / extent)
60
- return [x, y, z]
61
- }
62
58
  function bboxToTile(bbox: GeoBbox2D, z = 8): Tile {
63
59
  const [minX, minY, maxX, maxY] = bbox
64
60
  const centerLon = (minX + maxX) / 2
@@ -76,29 +72,423 @@ describe("OsmixVtEncoder", () => {
76
72
  expect(result.byteLength).toBeGreaterThan(0)
77
73
 
78
74
  const layers = decodeTile(result)
79
- assert.isDefined(layers[NODE_LAYER_ID])
80
- expect(layers[NODE_LAYER_ID].length).toBe(1)
81
- assert.isDefined(layers[WAY_LAYER_ID])
82
- expect(layers[WAY_LAYER_ID].length).toBe(1)
75
+ expect(layers[NODE_LAYER_ID]).toBeDefined()
76
+ expect(layers[NODE_LAYER_ID]?.length).toBe(1)
77
+ expect(layers[WAY_LAYER_ID]).toBeDefined()
78
+ expect(layers[WAY_LAYER_ID]?.length).toBe(1)
83
79
 
84
- const features = [
85
- layers[NODE_LAYER_ID].feature(0),
86
- layers[WAY_LAYER_ID].feature(0),
87
- ]
80
+ const nodeLayer = layers[NODE_LAYER_ID]
81
+ const wayLayer = layers[WAY_LAYER_ID]
82
+ if (!nodeLayer || !wayLayer) throw new Error("Layers not found")
83
+ const features = [nodeLayer.feature(0), wayLayer.feature(0)]
88
84
 
89
85
  const node = features.find(
90
86
  (feature) => feature.properties["type"] === "node",
91
87
  )
92
- expect(node?.id).toBe(1)
88
+ // IDs are zigzag-encoded, so decode them
89
+ if (node?.id !== undefined) {
90
+ const decodedId = decodeZigzag(node.id)
91
+ expect(decodedId).toBe(1)
92
+ }
93
93
  expect(node?.type).toBe(1)
94
94
  const nodeGeom = node?.loadGeometry()
95
95
  expect(nodeGeom?.[0]?.[0]?.x).toBeTypeOf("number")
96
96
  expect(nodeGeom?.[0]?.[0]?.y).toBeTypeOf("number")
97
97
 
98
98
  const way = features.find((feature) => feature.properties["type"] === "way")
99
- expect(way?.id).toBe(5)
99
+ // IDs are zigzag-encoded, so decode them
100
+ if (way?.id !== undefined) {
101
+ const decodedId = decodeZigzag(way.id)
102
+ expect(decodedId).toBe(5)
103
+ }
100
104
  expect(way?.type).toBe(2)
101
105
  const wayGeom = way?.loadGeometry()
102
106
  expect(wayGeom?.[0]?.length).toBeGreaterThanOrEqual(2)
103
107
  })
108
+
109
+ it("encodes area ways as polygons with proper winding order", () => {
110
+ const testOsm = new Osm()
111
+ // Create a closed way that should be treated as an area
112
+ testOsm.nodes.addNode({ id: 10, lat: 40.7, lon: -74.0 })
113
+ testOsm.nodes.addNode({ id: 11, lat: 40.71, lon: -74.0 })
114
+ testOsm.nodes.addNode({ id: 12, lat: 40.71, lon: -74.01 })
115
+ testOsm.nodes.addNode({ id: 13, lat: 40.7, lon: -74.01 })
116
+ testOsm.ways.addWay({
117
+ id: 20,
118
+ refs: [10, 11, 12, 13, 10], // Closed ring
119
+ tags: { building: "yes", area: "yes" },
120
+ })
121
+ testOsm.buildIndexes()
122
+ testOsm.buildSpatialIndexes()
123
+
124
+ const bbox = testOsm.bbox()
125
+ const tile = bboxToTile(bbox)
126
+ const encoder = new OsmixVtEncoder(testOsm)
127
+ const result = encoder.getTile(tile)
128
+
129
+ const layers = decodeTile(result)
130
+ const wayLayer = layers[`@osmix:${testOsm.id}:ways`]
131
+ expect(wayLayer?.length).toBe(1)
132
+
133
+ const feature = wayLayer?.feature(0)
134
+ if (!feature) throw new Error("Feature not found")
135
+ expect(feature.type).toBe(3) // POLYGON type
136
+ expect(feature.properties["type"]).toBe("way")
137
+ expect(feature.properties["building"]).toBe("yes")
138
+
139
+ const geometry = feature.loadGeometry()
140
+ expect(geometry.length).toBeGreaterThan(0)
141
+ // Should have at least one ring (outer ring)
142
+ expect(geometry[0]?.length).toBeGreaterThanOrEqual(4) // At least 4 points for a polygon
143
+ })
144
+
145
+ it("handles multiple rings for polygons (infrastructure for holes)", () => {
146
+ const testOsm = new Osm()
147
+ // Create an area way
148
+ testOsm.nodes.addNode({ id: 20, lat: 40.7, lon: -74.0 })
149
+ testOsm.nodes.addNode({ id: 21, lat: 40.71, lon: -74.0 })
150
+ testOsm.nodes.addNode({ id: 22, lat: 40.71, lon: -74.01 })
151
+ testOsm.nodes.addNode({ id: 23, lat: 40.7, lon: -74.01 })
152
+ testOsm.ways.addWay({
153
+ id: 30,
154
+ refs: [20, 21, 22, 23, 20],
155
+ tags: { building: "yes", area: "yes" },
156
+ })
157
+ testOsm.buildIndexes()
158
+ testOsm.buildSpatialIndexes()
159
+
160
+ const bbox = testOsm.bbox()
161
+ const tile = bboxToTile(bbox)
162
+ const encoder = new OsmixVtEncoder(testOsm)
163
+
164
+ // Test that clipProjectedPolygon returns array of rings
165
+ const way = testOsm.ways.getById(30)
166
+ expect(way).toBeDefined()
167
+ const points = way!.refs.map((ref) => {
168
+ const node = testOsm.nodes.getById(ref)
169
+ return llToTilePx([node!.lon, node!.lat], tile, extent)
170
+ })
171
+
172
+ const clippedRings = encoder["clipProjectedPolygon"](points)
173
+ expect(Array.isArray(clippedRings)).toBe(true)
174
+ expect(clippedRings.length).toBeGreaterThan(0)
175
+ expect(Array.isArray(clippedRings[0])).toBe(true)
176
+ })
177
+
178
+ it("processes polygon rings with correct winding order (outer clockwise, inner counterclockwise)", () => {
179
+ const testOsm = new Osm()
180
+ testOsm.nodes.addNode({ id: 30, lat: 40.7, lon: -74.0 })
181
+ testOsm.nodes.addNode({ id: 31, lat: 40.71, lon: -74.0 })
182
+ testOsm.nodes.addNode({ id: 32, lat: 40.71, lon: -74.01 })
183
+ testOsm.nodes.addNode({ id: 33, lat: 40.7, lon: -74.01 })
184
+ testOsm.ways.addWay({
185
+ id: 40,
186
+ refs: [30, 31, 32, 33, 30],
187
+ tags: { building: "yes", area: "yes" },
188
+ })
189
+ testOsm.buildIndexes()
190
+ testOsm.buildSpatialIndexes()
191
+
192
+ const bbox = testOsm.bbox()
193
+ const tile = bboxToTile(bbox)
194
+ const encoder = new OsmixVtEncoder(testOsm)
195
+
196
+ const way = testOsm.ways.getById(40)
197
+ const points = way!.refs.map((ref) => {
198
+ const node = testOsm.nodes.getById(ref)
199
+ return llToTilePx([node!.lon, node!.lat], tile, extent)
200
+ })
201
+
202
+ const clippedRings = encoder["clipProjectedPolygon"](points)
203
+ const outerRing = clippedRings[0]
204
+ expect(outerRing).toBeDefined()
205
+
206
+ // Process as outer ring (should be clockwise)
207
+ const processedOuter = encoder["processClippedPolygonRing"](
208
+ outerRing!,
209
+ true,
210
+ )
211
+ expect(processedOuter.length).toBeGreaterThan(0)
212
+
213
+ // Process as inner ring (should be counterclockwise)
214
+ const processedInner = encoder["processClippedPolygonRing"](
215
+ outerRing!,
216
+ false,
217
+ )
218
+ expect(processedInner.length).toBeGreaterThan(0)
219
+
220
+ // Verify they have opposite winding (area should have opposite signs)
221
+ const outerArea = processedOuter.reduce((sum, p, i) => {
222
+ const next = processedOuter[(i + 1) % processedOuter.length]
223
+ if (!next) return sum
224
+ return sum + (p[0] * next[1] - next[0] * p[1])
225
+ }, 0)
226
+ const innerArea = processedInner.reduce((sum, p, i) => {
227
+ const next = processedInner[(i + 1) % processedInner.length]
228
+ if (!next) return sum
229
+ return sum + (p[0] * next[1] - next[0] * p[1])
230
+ }, 0)
231
+
232
+ // Outer should be clockwise (negative area), inner should be counterclockwise (positive area)
233
+ expect(outerArea).toBeLessThan(0)
234
+ expect(innerArea).toBeGreaterThan(0)
235
+ })
236
+
237
+ it("encodes multipolygon relation with correct winding order", () => {
238
+ const testOsm = new Osm()
239
+ // Create nodes for outer square
240
+ testOsm.nodes.addNode({ id: 1, lat: -1.0, lon: -1.0 })
241
+ testOsm.nodes.addNode({ id: 2, lat: -1.0, lon: 1.0 })
242
+ testOsm.nodes.addNode({ id: 3, lat: 1.0, lon: 1.0 })
243
+ testOsm.nodes.addNode({ id: 4, lat: 1.0, lon: -1.0 })
244
+ // Create nodes for inner triangle
245
+ testOsm.nodes.addNode({ id: 5, lat: -0.5, lon: 0.0 })
246
+ testOsm.nodes.addNode({ id: 6, lat: 0.5, lon: 0.0 })
247
+ testOsm.nodes.addNode({ id: 7, lat: 0.0, lon: 0.5 })
248
+
249
+ // Create outer way
250
+ testOsm.ways.addWay({
251
+ id: 10,
252
+ refs: [1, 2, 3, 4, 1],
253
+ tags: {},
254
+ })
255
+ // Create inner way
256
+ testOsm.ways.addWay({
257
+ id: 11,
258
+ refs: [5, 6, 7, 5],
259
+ tags: {},
260
+ })
261
+
262
+ // Create multipolygon relation
263
+ testOsm.relations.addRelation({
264
+ id: 20,
265
+ tags: { type: "multipolygon", name: "test" },
266
+ members: [
267
+ { type: "way", ref: 10, role: "outer" },
268
+ { type: "way", ref: 11, role: "inner" },
269
+ ],
270
+ })
271
+
272
+ testOsm.buildIndexes()
273
+ testOsm.buildSpatialIndexes()
274
+
275
+ const bbox: GeoBbox2D = [-2, -2, 2, 2]
276
+ const tile = bboxToTile(bbox)
277
+ const encoder = new OsmixVtEncoder(testOsm)
278
+
279
+ const features = Array.from(
280
+ encoder.relationFeatures(bbox, (ll) => llToTilePx(ll, tile, extent)),
281
+ )
282
+ expect(features.length).toBeGreaterThan(0)
283
+
284
+ const relationFeature = features[0]
285
+ expect(relationFeature).toBeDefined()
286
+ expect(relationFeature?.type).toBe(3) // POLYGON
287
+
288
+ const geometry = relationFeature?.geometry
289
+ expect(geometry).toBeDefined()
290
+ expect(Array.isArray(geometry)).toBe(true)
291
+ if (geometry && Array.isArray(geometry) && geometry.length > 0) {
292
+ const outerRing = geometry[0]
293
+ expect(outerRing).toBeDefined()
294
+ if (outerRing && Array.isArray(outerRing) && outerRing.length > 0) {
295
+ // Verify outer ring is clockwise (negative signed area)
296
+ const outerArea = outerRing.reduce((sum, p, i) => {
297
+ const next = outerRing[(i + 1) % outerRing.length]
298
+ if (!next) return sum
299
+ return sum + (p[0] * next[1] - next[0] * p[1])
300
+ }, 0)
301
+ expect(outerArea).toBeLessThan(0) // Clockwise
302
+
303
+ // If there's an inner ring, verify it's counterclockwise
304
+ if (geometry.length > 1) {
305
+ const innerRing = geometry[1]
306
+ if (innerRing && Array.isArray(innerRing) && innerRing.length > 0) {
307
+ const innerArea = innerRing.reduce((sum, p, i) => {
308
+ const next = innerRing[(i + 1) % innerRing.length]
309
+ if (!next) return sum
310
+ return sum + (p[0] * next[1] - next[0] * p[1])
311
+ }, 0)
312
+ expect(innerArea).toBeGreaterThan(0) // Counterclockwise
313
+ }
314
+ }
315
+ }
316
+ }
317
+ })
318
+
319
+ it("encodes area way as polygon with correct winding order", () => {
320
+ const testOsm = new Osm()
321
+ // Create a square polygon
322
+ testOsm.nodes.addNode({ id: 1, lat: 40.7, lon: -74.0 })
323
+ testOsm.nodes.addNode({ id: 2, lat: 40.71, lon: -74.0 })
324
+ testOsm.nodes.addNode({ id: 3, lat: 40.71, lon: -74.01 })
325
+ testOsm.nodes.addNode({ id: 4, lat: 40.7, lon: -74.01 })
326
+ testOsm.ways.addWay({
327
+ id: 10,
328
+ refs: [1, 2, 3, 4, 1],
329
+ tags: { area: "yes", building: "yes" },
330
+ })
331
+ testOsm.buildIndexes()
332
+ testOsm.buildSpatialIndexes()
333
+
334
+ const bbox = testOsm.bbox()
335
+ const tile = bboxToTile(bbox)
336
+ const encoder = new OsmixVtEncoder(testOsm)
337
+
338
+ const features = Array.from(
339
+ encoder.wayFeatures(bbox, (ll) => llToTilePx(ll, tile, extent)),
340
+ )
341
+ const polygonFeature = features.find((f) => f.type === 3) // POLYGON
342
+ expect(polygonFeature).toBeDefined()
343
+
344
+ if (polygonFeature?.geometry) {
345
+ const geometry = polygonFeature.geometry
346
+ if (Array.isArray(geometry) && geometry.length > 0) {
347
+ const ring = geometry[0]
348
+ if (ring && Array.isArray(ring) && ring.length > 0) {
349
+ // Verify clockwise winding (negative area)
350
+ const area = ring.reduce((sum, p, i) => {
351
+ const next = ring[(i + 1) % ring.length]
352
+ if (!next) return sum
353
+ return sum + (p[0] * next[1] - next[0] * p[1])
354
+ }, 0)
355
+ expect(area).toBeLessThan(0) // Should be clockwise for MVT
356
+ }
357
+ }
358
+ }
359
+ })
360
+
361
+ it("encodes negative IDs correctly", () => {
362
+ const testOsm = new Osm()
363
+ // Create nodes with negative IDs (like from GeoJSON import)
364
+ testOsm.nodes.addNode({
365
+ id: -1,
366
+ lat: 40.7,
367
+ lon: -74.0,
368
+ tags: { name: "Negative Node" },
369
+ })
370
+ testOsm.nodes.addNode({
371
+ id: -2,
372
+ lat: 40.71,
373
+ lon: -74.01,
374
+ tags: { name: "Another Negative Node" },
375
+ })
376
+ testOsm.ways.addWay({
377
+ id: -10,
378
+ refs: [-1, -2],
379
+ tags: { highway: "primary" },
380
+ })
381
+ testOsm.buildIndexes()
382
+ testOsm.buildSpatialIndexes()
383
+
384
+ const bbox = testOsm.bbox()
385
+ const tile = bboxToTile(bbox)
386
+ const encoder = new OsmixVtEncoder(testOsm)
387
+ const result = encoder.getTile(tile)
388
+
389
+ expect(result.byteLength).toBeGreaterThan(0)
390
+
391
+ const layers = decodeTile(result)
392
+ const nodeLayer = layers[NODE_LAYER_ID]
393
+ const wayLayer = layers[WAY_LAYER_ID]
394
+
395
+ expect(nodeLayer).toBeDefined()
396
+ expect(wayLayer).toBeDefined()
397
+
398
+ // Check that negative IDs are zigzag-encoded and can be decoded back
399
+ if (nodeLayer && nodeLayer.length > 0) {
400
+ const nodeFeature = nodeLayer.feature(0)
401
+ // IDs are zigzag-encoded, so decode them
402
+ if (nodeFeature.id !== undefined) {
403
+ const decodedId = decodeZigzag(nodeFeature.id)
404
+ expect(decodedId).toBe(-1)
405
+ }
406
+ }
407
+
408
+ if (wayLayer && wayLayer.length > 0) {
409
+ const wayFeature = wayLayer.feature(0)
410
+ // IDs are zigzag-encoded, so decode them
411
+ if (wayFeature.id !== undefined) {
412
+ const decodedId = decodeZigzag(wayFeature.id)
413
+ expect(decodedId).toBe(-10)
414
+ }
415
+ }
416
+ })
417
+
418
+ it("accepts IDs at the boundaries of safe integer range", () => {
419
+ const testOsm = new Osm()
420
+ // Add nodes for ways to reference
421
+ testOsm.nodes.addNode({ id: 1, lat: 40.7, lon: -74.0 })
422
+ testOsm.nodes.addNode({ id: 2, lat: 40.71, lon: -74.01 })
423
+ // Test minimum valid ID (way)
424
+ testOsm.ways.addWay({
425
+ id: -Number.MAX_SAFE_INTEGER,
426
+ refs: [1, 2],
427
+ tags: { highway: "primary" },
428
+ })
429
+ // Test maximum valid ID (node with tags)
430
+ testOsm.nodes.addNode({
431
+ id: Number.MAX_SAFE_INTEGER,
432
+ lat: 40.72,
433
+ lon: -74.02,
434
+ tags: { name: "Max ID" },
435
+ })
436
+ testOsm.buildIndexes()
437
+ testOsm.buildSpatialIndexes()
438
+
439
+ const bbox = testOsm.bbox()
440
+ const tile = bboxToTile(bbox)
441
+ const encoder = new OsmixVtEncoder(testOsm)
442
+
443
+ // Should not throw - this verifies the IDs are within the valid range
444
+ const result = encoder.getTile(tile)
445
+ expect(result.byteLength).toBeGreaterThan(0)
446
+ })
447
+
448
+ it("encodes and decodes large negative IDs correctly", () => {
449
+ const testOsm = new Osm()
450
+ // Test with a large negative ID (much larger than 32-bit range)
451
+ const largeNegativeId = -1000000000 // -1 billion
452
+ // Add a node for the way to reference
453
+ testOsm.nodes.addNode({ id: 1, lat: 40.71, lon: -74.01 })
454
+ testOsm.nodes.addNode({
455
+ id: largeNegativeId,
456
+ lat: 40.7,
457
+ lon: -74.0,
458
+ tags: { name: "Large Negative ID" },
459
+ })
460
+ // Add a dummy way so way spatial index can be built
461
+ testOsm.ways.addWay({
462
+ id: 100,
463
+ refs: [1],
464
+ tags: {},
465
+ })
466
+ testOsm.buildIndexes()
467
+ testOsm.buildSpatialIndexes()
468
+
469
+ const bbox = testOsm.bbox()
470
+ const tile = bboxToTile(bbox)
471
+ const encoder = new OsmixVtEncoder(testOsm)
472
+ const result = encoder.getTile(tile)
473
+
474
+ const layers = decodeTile(result)
475
+ const nodeLayer = layers[NODE_LAYER_ID]
476
+
477
+ expect(nodeLayer).toBeDefined()
478
+ if (nodeLayer && nodeLayer.length > 0) {
479
+ // Find the node with the large negative ID
480
+ let found = false
481
+ for (let i = 0; i < nodeLayer.length; i++) {
482
+ const nodeFeature = nodeLayer.feature(i)
483
+ if (nodeFeature.id !== undefined) {
484
+ const decodedId = decodeZigzag(nodeFeature.id)
485
+ if (decodedId === largeNegativeId) {
486
+ found = true
487
+ break
488
+ }
489
+ }
490
+ }
491
+ expect(found).toBe(true)
492
+ }
493
+ })
104
494
  })