@osmix/vt 0.0.2 → 0.0.7

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":"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"}
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,11 +2,12 @@
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.7",
6
6
  "description": "Encode Osmix binary overlay tiles directly into Mapbox Vector Tiles",
7
7
  "main": "./src/index.ts",
8
8
  "publishConfig": {
9
9
  "access": "public",
10
+ "registry": "https://registry.npmjs.org/",
10
11
  "main": "./dist/index.js",
11
12
  "types": "./dist/index.d.ts",
12
13
  "exports": {
@@ -32,21 +33,21 @@
32
33
  },
33
34
  "sideEffects": false,
34
35
  "scripts": {
35
- "build": "tsc",
36
- "prepublishOnly": "tsc",
37
- "test": "vitest",
38
- "typecheck": "tsc --noEmit"
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "prepare": "bun run build",
38
+ "release": "bun publish",
39
+ "test": "bun test",
40
+ "typecheck": "tsgo --noEmit"
39
41
  },
40
42
  "dependencies": {
41
- "@osmix/shared": "workspace:*",
42
- "@osmix/json": "workspace:*",
43
- "pbf": "catalog:"
43
+ "@types/bun": "^1.3.3",
44
+ "@osmix/shared": "0.0.7",
45
+ "pbf": "^4.0.1"
44
46
  },
45
47
  "devDependencies": {
48
+ "@mapbox/tilebelt": "^2.0.3",
46
49
  "@mapbox/vector-tile": "^2.0.4",
47
- "@osmix/core": "workspace:*",
48
- "@types/node": "catalog:",
49
- "typescript": "catalog:",
50
- "vitest": "catalog:"
50
+ "@osmix/core": "0.1.2",
51
+ "typescript": "^5.9.0"
51
52
  }
52
53
  }
@@ -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", color: "ff0000" },
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,424 @@ 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)
105
+ expect(way?.properties["color"]).toBe("#FF0000")
101
106
  const wayGeom = way?.loadGeometry()
102
107
  expect(wayGeom?.[0]?.length).toBeGreaterThanOrEqual(2)
103
108
  })
109
+
110
+ it("encodes area ways as polygons with proper winding order", () => {
111
+ const testOsm = new Osm()
112
+ // Create a closed way that should be treated as an area
113
+ testOsm.nodes.addNode({ id: 10, lat: 40.7, lon: -74.0 })
114
+ testOsm.nodes.addNode({ id: 11, lat: 40.71, lon: -74.0 })
115
+ testOsm.nodes.addNode({ id: 12, lat: 40.71, lon: -74.01 })
116
+ testOsm.nodes.addNode({ id: 13, lat: 40.7, lon: -74.01 })
117
+ testOsm.ways.addWay({
118
+ id: 20,
119
+ refs: [10, 11, 12, 13, 10], // Closed ring
120
+ tags: { building: "yes", area: "yes" },
121
+ })
122
+ testOsm.buildIndexes()
123
+ testOsm.buildSpatialIndexes()
124
+
125
+ const bbox = testOsm.bbox()
126
+ const tile = bboxToTile(bbox)
127
+ const encoder = new OsmixVtEncoder(testOsm)
128
+ const result = encoder.getTile(tile)
129
+
130
+ const layers = decodeTile(result)
131
+ const wayLayer = layers[`@osmix:${testOsm.id}:ways`]
132
+ expect(wayLayer?.length).toBe(1)
133
+
134
+ const feature = wayLayer?.feature(0)
135
+ if (!feature) throw new Error("Feature not found")
136
+ expect(feature.type).toBe(3) // POLYGON type
137
+ expect(feature.properties["type"]).toBe("way")
138
+ expect(feature.properties["building"]).toBe("yes")
139
+
140
+ const geometry = feature.loadGeometry()
141
+ expect(geometry.length).toBeGreaterThan(0)
142
+ // Should have at least one ring (outer ring)
143
+ expect(geometry[0]?.length).toBeGreaterThanOrEqual(4) // At least 4 points for a polygon
144
+ })
145
+
146
+ it("handles multiple rings for polygons (infrastructure for holes)", () => {
147
+ const testOsm = new Osm()
148
+ // Create an area way
149
+ testOsm.nodes.addNode({ id: 20, lat: 40.7, lon: -74.0 })
150
+ testOsm.nodes.addNode({ id: 21, lat: 40.71, lon: -74.0 })
151
+ testOsm.nodes.addNode({ id: 22, lat: 40.71, lon: -74.01 })
152
+ testOsm.nodes.addNode({ id: 23, lat: 40.7, lon: -74.01 })
153
+ testOsm.ways.addWay({
154
+ id: 30,
155
+ refs: [20, 21, 22, 23, 20],
156
+ tags: { building: "yes", area: "yes" },
157
+ })
158
+ testOsm.buildIndexes()
159
+ testOsm.buildSpatialIndexes()
160
+
161
+ const bbox = testOsm.bbox()
162
+ const tile = bboxToTile(bbox)
163
+ const encoder = new OsmixVtEncoder(testOsm)
164
+
165
+ // Test that clipProjectedPolygon returns array of rings
166
+ const way = testOsm.ways.getById(30)
167
+ expect(way).toBeDefined()
168
+ const points = way!.refs.map((ref) => {
169
+ const node = testOsm.nodes.getById(ref)
170
+ return llToTilePx([node!.lon, node!.lat], tile, extent)
171
+ })
172
+
173
+ const clippedRings = encoder["clipProjectedPolygon"](points)
174
+ expect(Array.isArray(clippedRings)).toBe(true)
175
+ expect(clippedRings.length).toBeGreaterThan(0)
176
+ expect(Array.isArray(clippedRings[0])).toBe(true)
177
+ })
178
+
179
+ it("processes polygon rings with correct winding order (outer clockwise, inner counterclockwise)", () => {
180
+ const testOsm = new Osm()
181
+ testOsm.nodes.addNode({ id: 30, lat: 40.7, lon: -74.0 })
182
+ testOsm.nodes.addNode({ id: 31, lat: 40.71, lon: -74.0 })
183
+ testOsm.nodes.addNode({ id: 32, lat: 40.71, lon: -74.01 })
184
+ testOsm.nodes.addNode({ id: 33, lat: 40.7, lon: -74.01 })
185
+ testOsm.ways.addWay({
186
+ id: 40,
187
+ refs: [30, 31, 32, 33, 30],
188
+ tags: { building: "yes", area: "yes" },
189
+ })
190
+ testOsm.buildIndexes()
191
+ testOsm.buildSpatialIndexes()
192
+
193
+ const bbox = testOsm.bbox()
194
+ const tile = bboxToTile(bbox)
195
+ const encoder = new OsmixVtEncoder(testOsm)
196
+
197
+ const way = testOsm.ways.getById(40)
198
+ const points = way!.refs.map((ref) => {
199
+ const node = testOsm.nodes.getById(ref)
200
+ return llToTilePx([node!.lon, node!.lat], tile, extent)
201
+ })
202
+
203
+ const clippedRings = encoder["clipProjectedPolygon"](points)
204
+ const outerRing = clippedRings[0]
205
+ expect(outerRing).toBeDefined()
206
+
207
+ // Process as outer ring (should be clockwise)
208
+ const processedOuter = encoder["processClippedPolygonRing"](
209
+ outerRing!,
210
+ true,
211
+ )
212
+ expect(processedOuter.length).toBeGreaterThan(0)
213
+
214
+ // Process as inner ring (should be counterclockwise)
215
+ const processedInner = encoder["processClippedPolygonRing"](
216
+ outerRing!,
217
+ false,
218
+ )
219
+ expect(processedInner.length).toBeGreaterThan(0)
220
+
221
+ // Verify they have opposite winding (area should have opposite signs)
222
+ const outerArea = processedOuter.reduce((sum, p, i) => {
223
+ const next = processedOuter[(i + 1) % processedOuter.length]
224
+ if (!next) return sum
225
+ return sum + (p[0] * next[1] - next[0] * p[1])
226
+ }, 0)
227
+ const innerArea = processedInner.reduce((sum, p, i) => {
228
+ const next = processedInner[(i + 1) % processedInner.length]
229
+ if (!next) return sum
230
+ return sum + (p[0] * next[1] - next[0] * p[1])
231
+ }, 0)
232
+
233
+ // Outer should be clockwise (negative area), inner should be counterclockwise (positive area)
234
+ expect(outerArea).toBeLessThan(0)
235
+ expect(innerArea).toBeGreaterThan(0)
236
+ })
237
+
238
+ it("encodes multipolygon relation with correct winding order", () => {
239
+ const testOsm = new Osm()
240
+ // Create nodes for outer square
241
+ testOsm.nodes.addNode({ id: 1, lat: -1.0, lon: -1.0 })
242
+ testOsm.nodes.addNode({ id: 2, lat: -1.0, lon: 1.0 })
243
+ testOsm.nodes.addNode({ id: 3, lat: 1.0, lon: 1.0 })
244
+ testOsm.nodes.addNode({ id: 4, lat: 1.0, lon: -1.0 })
245
+ // Create nodes for inner triangle
246
+ testOsm.nodes.addNode({ id: 5, lat: -0.5, lon: 0.0 })
247
+ testOsm.nodes.addNode({ id: 6, lat: 0.5, lon: 0.0 })
248
+ testOsm.nodes.addNode({ id: 7, lat: 0.0, lon: 0.5 })
249
+
250
+ // Create outer way
251
+ testOsm.ways.addWay({
252
+ id: 10,
253
+ refs: [1, 2, 3, 4, 1],
254
+ tags: {},
255
+ })
256
+ // Create inner way
257
+ testOsm.ways.addWay({
258
+ id: 11,
259
+ refs: [5, 6, 7, 5],
260
+ tags: {},
261
+ })
262
+
263
+ // Create multipolygon relation
264
+ testOsm.relations.addRelation({
265
+ id: 20,
266
+ tags: { type: "multipolygon", name: "test" },
267
+ members: [
268
+ { type: "way", ref: 10, role: "outer" },
269
+ { type: "way", ref: 11, role: "inner" },
270
+ ],
271
+ })
272
+
273
+ testOsm.buildIndexes()
274
+ testOsm.buildSpatialIndexes()
275
+
276
+ const bbox: GeoBbox2D = [-2, -2, 2, 2]
277
+ const tile = bboxToTile(bbox)
278
+ const encoder = new OsmixVtEncoder(testOsm)
279
+
280
+ const features = Array.from(
281
+ encoder.relationFeatures(bbox, (ll) => llToTilePx(ll, tile, extent)),
282
+ )
283
+ expect(features.length).toBeGreaterThan(0)
284
+
285
+ const relationFeature = features[0]
286
+ expect(relationFeature).toBeDefined()
287
+ expect(relationFeature?.type).toBe(3) // POLYGON
288
+
289
+ const geometry = relationFeature?.geometry
290
+ expect(geometry).toBeDefined()
291
+ expect(Array.isArray(geometry)).toBe(true)
292
+ if (geometry && Array.isArray(geometry) && geometry.length > 0) {
293
+ const outerRing = geometry[0]
294
+ expect(outerRing).toBeDefined()
295
+ if (outerRing && Array.isArray(outerRing) && outerRing.length > 0) {
296
+ // Verify outer ring is clockwise (negative signed area)
297
+ const outerArea = outerRing.reduce((sum, p, i) => {
298
+ const next = outerRing[(i + 1) % outerRing.length]
299
+ if (!next) return sum
300
+ return sum + (p[0] * next[1] - next[0] * p[1])
301
+ }, 0)
302
+ expect(outerArea).toBeLessThan(0) // Clockwise
303
+
304
+ // If there's an inner ring, verify it's counterclockwise
305
+ if (geometry.length > 1) {
306
+ const innerRing = geometry[1]
307
+ if (innerRing && Array.isArray(innerRing) && innerRing.length > 0) {
308
+ const innerArea = innerRing.reduce((sum, p, i) => {
309
+ const next = innerRing[(i + 1) % innerRing.length]
310
+ if (!next) return sum
311
+ return sum + (p[0] * next[1] - next[0] * p[1])
312
+ }, 0)
313
+ expect(innerArea).toBeGreaterThan(0) // Counterclockwise
314
+ }
315
+ }
316
+ }
317
+ }
318
+ })
319
+
320
+ it("encodes area way as polygon with correct winding order", () => {
321
+ const testOsm = new Osm()
322
+ // Create a square polygon
323
+ testOsm.nodes.addNode({ id: 1, lat: 40.7, lon: -74.0 })
324
+ testOsm.nodes.addNode({ id: 2, lat: 40.71, lon: -74.0 })
325
+ testOsm.nodes.addNode({ id: 3, lat: 40.71, lon: -74.01 })
326
+ testOsm.nodes.addNode({ id: 4, lat: 40.7, lon: -74.01 })
327
+ testOsm.ways.addWay({
328
+ id: 10,
329
+ refs: [1, 2, 3, 4, 1],
330
+ tags: { area: "yes", building: "yes" },
331
+ })
332
+ testOsm.buildIndexes()
333
+ testOsm.buildSpatialIndexes()
334
+
335
+ const bbox = testOsm.bbox()
336
+ const tile = bboxToTile(bbox)
337
+ const encoder = new OsmixVtEncoder(testOsm)
338
+
339
+ const features = Array.from(
340
+ encoder.wayFeatures(bbox, (ll) => llToTilePx(ll, tile, extent)),
341
+ )
342
+ const polygonFeature = features.find((f) => f.type === 3) // POLYGON
343
+ expect(polygonFeature).toBeDefined()
344
+
345
+ if (polygonFeature?.geometry) {
346
+ const geometry = polygonFeature.geometry
347
+ if (Array.isArray(geometry) && geometry.length > 0) {
348
+ const ring = geometry[0]
349
+ if (ring && Array.isArray(ring) && ring.length > 0) {
350
+ // Verify clockwise winding (negative area)
351
+ const area = ring.reduce((sum, p, i) => {
352
+ const next = ring[(i + 1) % ring.length]
353
+ if (!next) return sum
354
+ return sum + (p[0] * next[1] - next[0] * p[1])
355
+ }, 0)
356
+ expect(area).toBeLessThan(0) // Should be clockwise for MVT
357
+ }
358
+ }
359
+ }
360
+ })
361
+
362
+ it("encodes negative IDs correctly", () => {
363
+ const testOsm = new Osm()
364
+ // Create nodes with negative IDs (like from GeoJSON import)
365
+ testOsm.nodes.addNode({
366
+ id: -1,
367
+ lat: 40.7,
368
+ lon: -74.0,
369
+ tags: { name: "Negative Node" },
370
+ })
371
+ testOsm.nodes.addNode({
372
+ id: -2,
373
+ lat: 40.71,
374
+ lon: -74.01,
375
+ tags: { name: "Another Negative Node" },
376
+ })
377
+ testOsm.ways.addWay({
378
+ id: -10,
379
+ refs: [-1, -2],
380
+ tags: { highway: "primary" },
381
+ })
382
+ testOsm.buildIndexes()
383
+ testOsm.buildSpatialIndexes()
384
+
385
+ const bbox = testOsm.bbox()
386
+ const tile = bboxToTile(bbox)
387
+ const encoder = new OsmixVtEncoder(testOsm)
388
+ const result = encoder.getTile(tile)
389
+
390
+ expect(result.byteLength).toBeGreaterThan(0)
391
+
392
+ const layers = decodeTile(result)
393
+ const nodeLayer = layers[NODE_LAYER_ID]
394
+ const wayLayer = layers[WAY_LAYER_ID]
395
+
396
+ expect(nodeLayer).toBeDefined()
397
+ expect(wayLayer).toBeDefined()
398
+
399
+ // Check that negative IDs are zigzag-encoded and can be decoded back
400
+ if (nodeLayer && nodeLayer.length > 0) {
401
+ const nodeFeature = nodeLayer.feature(0)
402
+ // IDs are zigzag-encoded, so decode them
403
+ if (nodeFeature.id !== undefined) {
404
+ const decodedId = decodeZigzag(nodeFeature.id)
405
+ expect(decodedId).toBe(-1)
406
+ }
407
+ }
408
+
409
+ if (wayLayer && wayLayer.length > 0) {
410
+ const wayFeature = wayLayer.feature(0)
411
+ // IDs are zigzag-encoded, so decode them
412
+ if (wayFeature.id !== undefined) {
413
+ const decodedId = decodeZigzag(wayFeature.id)
414
+ expect(decodedId).toBe(-10)
415
+ }
416
+ }
417
+ })
418
+
419
+ it("accepts IDs at the boundaries of safe integer range", () => {
420
+ const testOsm = new Osm()
421
+ // Add nodes for ways to reference
422
+ testOsm.nodes.addNode({ id: 1, lat: 40.7, lon: -74.0 })
423
+ testOsm.nodes.addNode({ id: 2, lat: 40.71, lon: -74.01 })
424
+ // Test minimum valid ID (way)
425
+ testOsm.ways.addWay({
426
+ id: -Number.MAX_SAFE_INTEGER,
427
+ refs: [1, 2],
428
+ tags: { highway: "primary" },
429
+ })
430
+ // Test maximum valid ID (node with tags)
431
+ testOsm.nodes.addNode({
432
+ id: Number.MAX_SAFE_INTEGER,
433
+ lat: 40.72,
434
+ lon: -74.02,
435
+ tags: { name: "Max ID" },
436
+ })
437
+ testOsm.buildIndexes()
438
+ testOsm.buildSpatialIndexes()
439
+
440
+ const bbox = testOsm.bbox()
441
+ const tile = bboxToTile(bbox)
442
+ const encoder = new OsmixVtEncoder(testOsm)
443
+
444
+ // Should not throw - this verifies the IDs are within the valid range
445
+ const result = encoder.getTile(tile)
446
+ expect(result.byteLength).toBeGreaterThan(0)
447
+ })
448
+
449
+ it("encodes and decodes large negative IDs correctly", () => {
450
+ const testOsm = new Osm()
451
+ // Test with a large negative ID (much larger than 32-bit range)
452
+ const largeNegativeId = -1000000000 // -1 billion
453
+ // Add a node for the way to reference
454
+ testOsm.nodes.addNode({ id: 1, lat: 40.71, lon: -74.01 })
455
+ testOsm.nodes.addNode({
456
+ id: largeNegativeId,
457
+ lat: 40.7,
458
+ lon: -74.0,
459
+ tags: { name: "Large Negative ID" },
460
+ })
461
+ // Add a dummy way so way spatial index can be built
462
+ testOsm.ways.addWay({
463
+ id: 100,
464
+ refs: [1],
465
+ tags: {},
466
+ })
467
+ testOsm.buildIndexes()
468
+ testOsm.buildSpatialIndexes()
469
+
470
+ const bbox = testOsm.bbox()
471
+ const tile = bboxToTile(bbox)
472
+ const encoder = new OsmixVtEncoder(testOsm)
473
+ const result = encoder.getTile(tile)
474
+
475
+ const layers = decodeTile(result)
476
+ const nodeLayer = layers[NODE_LAYER_ID]
477
+
478
+ expect(nodeLayer).toBeDefined()
479
+ if (nodeLayer && nodeLayer.length > 0) {
480
+ // Find the node with the large negative ID
481
+ let found = false
482
+ for (let i = 0; i < nodeLayer.length; i++) {
483
+ const nodeFeature = nodeLayer.feature(i)
484
+ if (nodeFeature.id !== undefined) {
485
+ const decodedId = decodeZigzag(nodeFeature.id)
486
+ if (decodedId === largeNegativeId) {
487
+ found = true
488
+ break
489
+ }
490
+ }
491
+ }
492
+ expect(found).toBe(true)
493
+ }
494
+ })
104
495
  })