@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.
- package/CHANGELOG.md +46 -0
- package/README.md +42 -45
- package/dist/encode.d.ts +42 -6
- package/dist/encode.d.ts.map +1 -1
- package/dist/encode.js +231 -56
- package/dist/encode.js.map +1 -1
- package/dist/encode.test.js +365 -21
- package/dist/encode.test.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +29 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/dist/write-vt-pbf.d.ts +15 -1
- package/dist/write-vt-pbf.d.ts.map +1 -1
- package/dist/write-vt-pbf.js +24 -7
- package/dist/write-vt-pbf.js.map +1 -1
- package/package.json +13 -12
- package/src/encode.test.ts +412 -21
- package/src/encode.ts +251 -56
- package/src/index.ts +33 -1
- package/src/types.ts +30 -3
- package/src/write-vt-pbf.ts +27 -8
- package/tsconfig.build.json +5 -0
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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
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"}
|
package/dist/write-vt-pbf.d.ts
CHANGED
|
@@ -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
|
|
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":"
|
|
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"}
|
package/dist/write-vt-pbf.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
pbf.writeVarint(
|
|
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
|
});
|
package/dist/write-vt-pbf.js.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
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
|
-
"@
|
|
42
|
-
"@osmix/
|
|
43
|
-
"pbf": "
|
|
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": "
|
|
48
|
-
"
|
|
49
|
-
"typescript": "catalog:",
|
|
50
|
-
"vitest": "catalog:"
|
|
50
|
+
"@osmix/core": "0.1.2",
|
|
51
|
+
"typescript": "^5.9.0"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/src/encode.test.ts
CHANGED
|
@@ -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 {
|
|
3
|
-
import
|
|
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
|
|
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
|
-
|
|
80
|
-
expect(layers[NODE_LAYER_ID]
|
|
81
|
-
|
|
82
|
-
expect(layers[WAY_LAYER_ID]
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|