@osmix/geojson 0.0.10 → 0.0.11
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/package.json +26 -27
- package/CHANGELOG.md +0 -95
- package/test/entity-to-feature.test.ts +0 -65
- package/test/osm-from-geojson.test.ts +0 -868
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -2,26 +2,11 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@osmix/geojson",
|
|
4
4
|
"description": "Convert OSM files to and from GeoJSON.",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.11",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
"types": "./src/index.ts",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
9
8
|
"publishConfig": {
|
|
10
|
-
"access": "public"
|
|
11
|
-
"registry": "https://registry.npmjs.org/",
|
|
12
|
-
"main": "./dist/index.js",
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
|
-
"exports": {
|
|
15
|
-
".": {
|
|
16
|
-
"import": "./dist/index.js",
|
|
17
|
-
"types": "./dist/index.d.ts"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"files": [
|
|
21
|
-
"dist",
|
|
22
|
-
"README.md",
|
|
23
|
-
"LICENSE"
|
|
24
|
-
]
|
|
9
|
+
"access": "public"
|
|
25
10
|
},
|
|
26
11
|
"license": "MIT",
|
|
27
12
|
"repository": {
|
|
@@ -42,15 +27,29 @@
|
|
|
42
27
|
"typecheck": "tsgo --noEmit"
|
|
43
28
|
},
|
|
44
29
|
"devDependencies": {
|
|
45
|
-
"@types/bun": "
|
|
46
|
-
"@types/geojson": "
|
|
47
|
-
"typescript": "
|
|
30
|
+
"@types/bun": "catalog:",
|
|
31
|
+
"@types/geojson": "catalog:",
|
|
32
|
+
"typescript": "catalog:"
|
|
48
33
|
},
|
|
49
34
|
"dependencies": {
|
|
50
|
-
"@osmix/core": "
|
|
51
|
-
"@osmix/pbf": "
|
|
52
|
-
"@osmix/shared": "
|
|
53
|
-
"@placemarkio/geojson-rewind": "
|
|
54
|
-
"dequal": "
|
|
55
|
-
}
|
|
35
|
+
"@osmix/core": "workspace:*",
|
|
36
|
+
"@osmix/pbf": "workspace:*",
|
|
37
|
+
"@osmix/shared": "workspace:*",
|
|
38
|
+
"@placemarkio/geojson-rewind": "catalog:",
|
|
39
|
+
"dequal": "catalog:"
|
|
40
|
+
},
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./src/index.ts",
|
|
44
|
+
"bun": "./src/index.ts",
|
|
45
|
+
"development|production": "./src/index.ts",
|
|
46
|
+
"default": "./dist/index.js"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"src",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
]
|
|
56
55
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# @osmix/geojson
|
|
2
|
-
|
|
3
|
-
## 0.0.10
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies [12728ed]
|
|
8
|
-
- @osmix/shared@0.0.10
|
|
9
|
-
- @osmix/core@0.1.5
|
|
10
|
-
- @osmix/pbf@0.0.6
|
|
11
|
-
|
|
12
|
-
## 0.0.9
|
|
13
|
-
|
|
14
|
-
### Patch Changes
|
|
15
|
-
|
|
16
|
-
- f32e4ee: General cleanup
|
|
17
|
-
- Updated dependencies [f32e4ee]
|
|
18
|
-
- @osmix/core@0.1.4
|
|
19
|
-
- @osmix/pbf@0.0.6
|
|
20
|
-
- @osmix/shared@0.0.9
|
|
21
|
-
|
|
22
|
-
## 0.0.8
|
|
23
|
-
|
|
24
|
-
### Patch Changes
|
|
25
|
-
|
|
26
|
-
- f468db5: Fix publishing (2)
|
|
27
|
-
- Updated dependencies [f468db5]
|
|
28
|
-
- @osmix/core@0.1.3
|
|
29
|
-
- @osmix/pbf@0.0.5
|
|
30
|
-
- @osmix/shared@0.0.8
|
|
31
|
-
|
|
32
|
-
## 0.0.7
|
|
33
|
-
|
|
34
|
-
### Patch Changes
|
|
35
|
-
|
|
36
|
-
- 68d6bd8: Fix publishing for packages.
|
|
37
|
-
- Updated dependencies [68d6bd8]
|
|
38
|
-
- @osmix/core@0.1.2
|
|
39
|
-
- @osmix/pbf@0.0.4
|
|
40
|
-
- @osmix/shared@0.0.7
|
|
41
|
-
|
|
42
|
-
## 0.0.6
|
|
43
|
-
|
|
44
|
-
### Patch Changes
|
|
45
|
-
|
|
46
|
-
- Updated dependencies [d4f4b1f]
|
|
47
|
-
- @osmix/core@0.1.1
|
|
48
|
-
|
|
49
|
-
## 0.0.5
|
|
50
|
-
|
|
51
|
-
### Patch Changes
|
|
52
|
-
|
|
53
|
-
- cbe4273: Osm index storage and reload
|
|
54
|
-
- ff40416: Refactor main `osmix` lib external APIs
|
|
55
|
-
- Updated dependencies [803c05c]
|
|
56
|
-
- Updated dependencies [cbe4273]
|
|
57
|
-
- Updated dependencies [ff40416]
|
|
58
|
-
- Updated dependencies [29ed376]
|
|
59
|
-
- @osmix/core@0.1.0
|
|
60
|
-
|
|
61
|
-
## 0.0.4
|
|
62
|
-
|
|
63
|
-
### Patch Changes
|
|
64
|
-
|
|
65
|
-
- Updated dependencies [3846a0c]
|
|
66
|
-
- Updated dependencies [0cd8a2e]
|
|
67
|
-
- Updated dependencies [cdab4db]
|
|
68
|
-
- @osmix/core@0.0.6
|
|
69
|
-
- @osmix/shared@0.0.6
|
|
70
|
-
- @osmix/pbf@0.0.3
|
|
71
|
-
|
|
72
|
-
## 0.0.3
|
|
73
|
-
|
|
74
|
-
### Patch Changes
|
|
75
|
-
|
|
76
|
-
- edbb26b: Handle more Relation types
|
|
77
|
-
- Updated dependencies [bb629cf]
|
|
78
|
-
- Updated dependencies [a33a280]
|
|
79
|
-
- Updated dependencies [edbb26b]
|
|
80
|
-
- Updated dependencies [69a36bd]
|
|
81
|
-
- @osmix/shared@0.0.5
|
|
82
|
-
- @osmix/core@0.0.5
|
|
83
|
-
- @osmix/pbf@0.0.3
|
|
84
|
-
|
|
85
|
-
## 0.0.2
|
|
86
|
-
|
|
87
|
-
### Patch Changes
|
|
88
|
-
|
|
89
|
-
- d001d9a: Refactor to align around new main external API
|
|
90
|
-
- Updated dependencies [572cbd8]
|
|
91
|
-
- Updated dependencies [d001d9a]
|
|
92
|
-
- Updated dependencies [4303c40]
|
|
93
|
-
- @osmix/shared@0.0.4
|
|
94
|
-
- @osmix/core@0.0.4
|
|
95
|
-
- @osmix/pbf@0.0.3
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test"
|
|
2
|
-
import type { OsmNode, OsmRelation, OsmWay } from "@osmix/shared/types"
|
|
3
|
-
import {
|
|
4
|
-
nodeToFeature,
|
|
5
|
-
relationToFeature,
|
|
6
|
-
wayToFeature,
|
|
7
|
-
} from "../src/entity-to-feature"
|
|
8
|
-
|
|
9
|
-
describe("geojson helpers", () => {
|
|
10
|
-
const node0: OsmNode = { id: 1, lat: 0, lon: 0, tags: { amenity: "cafe" } }
|
|
11
|
-
const node1: OsmNode = { id: 2, lat: 1, lon: 1 }
|
|
12
|
-
const nodes: OsmNode[] = [node0, node1]
|
|
13
|
-
const nodeMap = new Map(nodes.map((n) => [n.id, n]))
|
|
14
|
-
const refToPosition = (id: number) => {
|
|
15
|
-
const node = nodeMap.get(id)
|
|
16
|
-
if (!node) throw new Error(`Node ${id} not found`)
|
|
17
|
-
return [node.lon, node.lat] as [number, number]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
it("converts node to GeoJSON Point", () => {
|
|
21
|
-
const feature = nodeToFeature(node0)
|
|
22
|
-
expect(feature.type).toBe("Feature")
|
|
23
|
-
expect(feature.geometry.type).toBe("Point")
|
|
24
|
-
expect(feature.geometry.coordinates).toEqual([node0.lon, node0.lat])
|
|
25
|
-
expect(feature.properties).toEqual({
|
|
26
|
-
id: node0.id,
|
|
27
|
-
type: "node",
|
|
28
|
-
...node0.tags,
|
|
29
|
-
...node0.info,
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it("creates polygon for closed ways", () => {
|
|
34
|
-
const way: OsmWay = {
|
|
35
|
-
id: 10,
|
|
36
|
-
refs: [1, 2, 1],
|
|
37
|
-
tags: { area: "yes" },
|
|
38
|
-
}
|
|
39
|
-
const feature = wayToFeature(way, refToPosition)
|
|
40
|
-
expect(feature.geometry.type).toBe("Polygon")
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it("builds relation geometry collection", () => {
|
|
44
|
-
// Create a way that forms a closed ring
|
|
45
|
-
const way: OsmWay = {
|
|
46
|
-
id: 10,
|
|
47
|
-
refs: [1, 2, 1], // Closed way
|
|
48
|
-
tags: { area: "yes" },
|
|
49
|
-
}
|
|
50
|
-
const wayMap = new Map([[way.id, way]])
|
|
51
|
-
const getWay = (wayId: number) => wayMap.get(wayId) ?? null
|
|
52
|
-
|
|
53
|
-
const relation: OsmRelation = {
|
|
54
|
-
id: 20,
|
|
55
|
-
members: [{ type: "way", ref: 10, role: "outer" }],
|
|
56
|
-
tags: { type: "multipolygon" },
|
|
57
|
-
}
|
|
58
|
-
const feature = relationToFeature(relation, refToPosition, getWay)
|
|
59
|
-
expect(feature.geometry.type).toBe("Polygon")
|
|
60
|
-
if (feature.geometry.type === "Polygon") {
|
|
61
|
-
expect(feature.geometry.coordinates?.[0]).toBeDefined()
|
|
62
|
-
expect(feature.geometry.coordinates[0]).toHaveLength(3) // Should have 3 coordinates (closed ring)
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
})
|
|
@@ -1,868 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test"
|
|
2
|
-
import type {
|
|
3
|
-
FeatureCollection,
|
|
4
|
-
LineString,
|
|
5
|
-
MultiPolygon,
|
|
6
|
-
Point,
|
|
7
|
-
Polygon,
|
|
8
|
-
} from "geojson"
|
|
9
|
-
import { fromGeoJSON } from "../src/osm-from-geojson"
|
|
10
|
-
|
|
11
|
-
describe("@osmix/geojson: createOsmFromGeoJSON", () => {
|
|
12
|
-
it("should convert Point features to Nodes", async () => {
|
|
13
|
-
const geojson: FeatureCollection<Point> = {
|
|
14
|
-
type: "FeatureCollection",
|
|
15
|
-
features: [
|
|
16
|
-
{
|
|
17
|
-
type: "Feature",
|
|
18
|
-
geometry: {
|
|
19
|
-
type: "Point",
|
|
20
|
-
coordinates: [-122.4194, 37.7749],
|
|
21
|
-
},
|
|
22
|
-
properties: {
|
|
23
|
-
name: "San Francisco",
|
|
24
|
-
population: 873965,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: "Feature",
|
|
29
|
-
geometry: {
|
|
30
|
-
type: "Point",
|
|
31
|
-
coordinates: [-122.4094, 37.7849],
|
|
32
|
-
},
|
|
33
|
-
properties: {
|
|
34
|
-
name: "Another Point",
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const osm = await fromGeoJSON(geojson)
|
|
41
|
-
|
|
42
|
-
expect(osm.nodes.size).toBe(2)
|
|
43
|
-
expect(osm.ways.size).toBe(0)
|
|
44
|
-
|
|
45
|
-
const node1 = osm.nodes.getById(-1)
|
|
46
|
-
expect(node1).toBeDefined()
|
|
47
|
-
expect(node1?.lon).toBe(-122.4194)
|
|
48
|
-
expect(node1?.lat).toBe(37.7749)
|
|
49
|
-
expect(node1?.tags?.["name"]).toBe("San Francisco")
|
|
50
|
-
// OSM tags are stored as strings, so numbers are converted
|
|
51
|
-
expect(node1?.tags?.["population"]).toBe("873965")
|
|
52
|
-
|
|
53
|
-
const node2 = osm.nodes.getById(-2)
|
|
54
|
-
expect(node2).toBeDefined()
|
|
55
|
-
expect(node2?.lon).toBe(-122.4094)
|
|
56
|
-
expect(node2?.lat).toBe(37.7849)
|
|
57
|
-
expect(node2?.tags?.["name"]).toBe("Another Point")
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it("should convert LineString features to Ways with Nodes", async () => {
|
|
61
|
-
const geojson: FeatureCollection<LineString> = {
|
|
62
|
-
type: "FeatureCollection",
|
|
63
|
-
features: [
|
|
64
|
-
{
|
|
65
|
-
type: "Feature",
|
|
66
|
-
geometry: {
|
|
67
|
-
type: "LineString",
|
|
68
|
-
coordinates: [
|
|
69
|
-
[-122.4194, 37.7749],
|
|
70
|
-
[-122.4094, 37.7849],
|
|
71
|
-
[-122.3994, 37.7949],
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
properties: {
|
|
75
|
-
highway: "primary",
|
|
76
|
-
name: "Main Street",
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const osm = await fromGeoJSON(geojson)
|
|
83
|
-
|
|
84
|
-
expect(osm.nodes.size).toBe(3)
|
|
85
|
-
expect(osm.ways.size).toBe(1)
|
|
86
|
-
|
|
87
|
-
const way = osm.ways.getById(-1)
|
|
88
|
-
expect(way).toBeDefined()
|
|
89
|
-
expect(way?.refs).toHaveLength(3)
|
|
90
|
-
expect(way?.tags?.["highway"]).toBe("primary")
|
|
91
|
-
expect(way?.tags?.["name"]).toBe("Main Street")
|
|
92
|
-
|
|
93
|
-
// Verify nodes were created
|
|
94
|
-
const node1 = osm.nodes.getById(way!.refs[0]!)
|
|
95
|
-
const node2 = osm.nodes.getById(way!.refs[1]!)
|
|
96
|
-
const node3 = osm.nodes.getById(way!.refs[2]!)
|
|
97
|
-
|
|
98
|
-
expect(node1?.lon).toBe(-122.4194)
|
|
99
|
-
expect(node1?.lat).toBe(37.7749)
|
|
100
|
-
expect(node2?.lon).toBe(-122.4094)
|
|
101
|
-
expect(node2?.lat).toBe(37.7849)
|
|
102
|
-
expect(node3?.lon).toBe(-122.3994)
|
|
103
|
-
expect(node3?.lat).toBe(37.7949)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it("should use feature IDs when present", async () => {
|
|
107
|
-
const geojson: FeatureCollection<Point | LineString> = {
|
|
108
|
-
type: "FeatureCollection",
|
|
109
|
-
features: [
|
|
110
|
-
{
|
|
111
|
-
type: "Feature",
|
|
112
|
-
id: 100,
|
|
113
|
-
geometry: {
|
|
114
|
-
type: "Point",
|
|
115
|
-
coordinates: [-122.4194, 37.7749],
|
|
116
|
-
},
|
|
117
|
-
properties: {
|
|
118
|
-
name: "Point with ID",
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
type: "Feature",
|
|
123
|
-
id: "200",
|
|
124
|
-
geometry: {
|
|
125
|
-
type: "LineString",
|
|
126
|
-
coordinates: [
|
|
127
|
-
[-122.4194, 37.7749],
|
|
128
|
-
[-122.4094, 37.7849],
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
properties: {
|
|
132
|
-
highway: "primary",
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const osm = await fromGeoJSON(geojson)
|
|
139
|
-
|
|
140
|
-
// Point should use ID 100
|
|
141
|
-
const node = osm.nodes.getById(100)
|
|
142
|
-
expect(node).toBeDefined()
|
|
143
|
-
expect(node?.tags?.["name"]).toBe("Point with ID")
|
|
144
|
-
|
|
145
|
-
// Way should use ID 200
|
|
146
|
-
const way = osm.ways.getById(200)
|
|
147
|
-
expect(way).toBeDefined()
|
|
148
|
-
expect(way?.tags?.["highway"]).toBe("primary")
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it("should generate sequential IDs when feature IDs are not present", async () => {
|
|
152
|
-
const geojson: FeatureCollection<Point | LineString> = {
|
|
153
|
-
type: "FeatureCollection",
|
|
154
|
-
features: [
|
|
155
|
-
{
|
|
156
|
-
type: "Feature",
|
|
157
|
-
geometry: {
|
|
158
|
-
type: "Point",
|
|
159
|
-
coordinates: [-122.4194, 37.7749],
|
|
160
|
-
},
|
|
161
|
-
properties: {},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
type: "Feature",
|
|
165
|
-
geometry: {
|
|
166
|
-
type: "Point",
|
|
167
|
-
coordinates: [-122.4094, 37.7849],
|
|
168
|
-
},
|
|
169
|
-
properties: {},
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
type: "Feature",
|
|
173
|
-
geometry: {
|
|
174
|
-
type: "LineString",
|
|
175
|
-
coordinates: [
|
|
176
|
-
[-122.3994, 37.7949],
|
|
177
|
-
[-122.3894, 37.8049],
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
properties: {},
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const osm = await fromGeoJSON(geojson)
|
|
186
|
-
|
|
187
|
-
// Nodes should have sequential IDs starting from 1
|
|
188
|
-
expect(osm.nodes.getById(-1)).toBeDefined()
|
|
189
|
-
expect(osm.nodes.getById(-2)).toBeDefined()
|
|
190
|
-
expect(osm.nodes.getById(-3)).toBeDefined()
|
|
191
|
-
expect(osm.nodes.getById(-4)).toBeDefined()
|
|
192
|
-
|
|
193
|
-
// Way should have ID 1
|
|
194
|
-
expect(osm.ways.getById(-1)).toBeDefined()
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it("should convert all properties to OSM tags", async () => {
|
|
198
|
-
const geojson: FeatureCollection<Point> = {
|
|
199
|
-
type: "FeatureCollection",
|
|
200
|
-
features: [
|
|
201
|
-
{
|
|
202
|
-
type: "Feature",
|
|
203
|
-
geometry: {
|
|
204
|
-
type: "Point",
|
|
205
|
-
coordinates: [-122.4194, 37.7749],
|
|
206
|
-
},
|
|
207
|
-
properties: {
|
|
208
|
-
name: "Test",
|
|
209
|
-
population: 1000,
|
|
210
|
-
boolean: true,
|
|
211
|
-
nullValue: null,
|
|
212
|
-
undefinedValue: undefined,
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const osm = await fromGeoJSON(geojson)
|
|
219
|
-
|
|
220
|
-
const node = osm.nodes.getById(-1)
|
|
221
|
-
expect(node?.tags?.["name"]).toBe("Test")
|
|
222
|
-
// OSM tags are stored as strings, so numbers are converted
|
|
223
|
-
expect(node?.tags?.["population"]).toBe("1000")
|
|
224
|
-
expect(node?.tags?.["boolean"]).toBe("true")
|
|
225
|
-
expect(node?.tags?.["nullValue"]).toBeUndefined()
|
|
226
|
-
expect(node?.tags?.["undefinedValue"]).toBeUndefined()
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it("should reuse nodes when LineStrings share coordinates", async () => {
|
|
230
|
-
const geojson: FeatureCollection<LineString> = {
|
|
231
|
-
type: "FeatureCollection",
|
|
232
|
-
features: [
|
|
233
|
-
{
|
|
234
|
-
type: "Feature",
|
|
235
|
-
geometry: {
|
|
236
|
-
type: "LineString",
|
|
237
|
-
coordinates: [
|
|
238
|
-
[-122.4194, 37.7749],
|
|
239
|
-
[-122.4094, 37.7849],
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
properties: {
|
|
243
|
-
highway: "primary",
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
type: "Feature",
|
|
248
|
-
geometry: {
|
|
249
|
-
type: "LineString",
|
|
250
|
-
coordinates: [
|
|
251
|
-
[-122.4094, 37.7849], // Shared coordinate
|
|
252
|
-
[-122.3994, 37.7949],
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
properties: {
|
|
256
|
-
highway: "secondary",
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const osm = await fromGeoJSON(geojson)
|
|
263
|
-
|
|
264
|
-
// Should have 3 nodes (shared coordinate is reused)
|
|
265
|
-
expect(osm.nodes.size).toBe(3)
|
|
266
|
-
expect(osm.ways.size).toBe(2)
|
|
267
|
-
|
|
268
|
-
const way1 = osm.ways.getById(-1)
|
|
269
|
-
const way2 = osm.ways.getById(-2)
|
|
270
|
-
|
|
271
|
-
// Each way should have its own nodes
|
|
272
|
-
expect(way1?.refs).toHaveLength(2)
|
|
273
|
-
expect(way2?.refs).toHaveLength(2)
|
|
274
|
-
// Nodes at the same coordinate are reused
|
|
275
|
-
expect(way1?.refs[1]).toBe(way2?.refs[0])
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
it("should error on LineStrings with less than 2 coordinates", () => {
|
|
279
|
-
const geojson: FeatureCollection<LineString> = {
|
|
280
|
-
type: "FeatureCollection",
|
|
281
|
-
features: [
|
|
282
|
-
{
|
|
283
|
-
type: "Feature",
|
|
284
|
-
geometry: {
|
|
285
|
-
type: "LineString",
|
|
286
|
-
coordinates: [[-122.4194, 37.7749]], // Only one coordinate
|
|
287
|
-
},
|
|
288
|
-
properties: {},
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
expect(() => fromGeoJSON(geojson)).toThrow(
|
|
294
|
-
"Invalid GeoJSON coordinates in LineString.",
|
|
295
|
-
)
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it("should error on LineStrings with invalid coordinates that result in less than 2 valid nodes", () => {
|
|
299
|
-
const geojson: FeatureCollection<LineString> = {
|
|
300
|
-
type: "FeatureCollection",
|
|
301
|
-
features: [
|
|
302
|
-
{
|
|
303
|
-
type: "Feature",
|
|
304
|
-
geometry: {
|
|
305
|
-
type: "LineString",
|
|
306
|
-
coordinates: [
|
|
307
|
-
[-122.4194, 37.7749], // Valid coordinate
|
|
308
|
-
[undefined, undefined] as unknown as [number, number], // Invalid coordinate
|
|
309
|
-
],
|
|
310
|
-
},
|
|
311
|
-
properties: {
|
|
312
|
-
highway: "primary",
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
type: "Feature",
|
|
317
|
-
geometry: {
|
|
318
|
-
type: "LineString",
|
|
319
|
-
coordinates: [
|
|
320
|
-
[-122.4194, 37.7749],
|
|
321
|
-
[-122.4094, 37.7849],
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
properties: {
|
|
325
|
-
highway: "secondary",
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
],
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
expect(() => fromGeoJSON(geojson)).toThrow()
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
it("should handle mixed Point and LineString features", async () => {
|
|
335
|
-
const geojson: FeatureCollection<Point | LineString> = {
|
|
336
|
-
type: "FeatureCollection",
|
|
337
|
-
features: [
|
|
338
|
-
{
|
|
339
|
-
type: "Feature",
|
|
340
|
-
geometry: {
|
|
341
|
-
type: "Point",
|
|
342
|
-
coordinates: [-122.4194, 37.7749],
|
|
343
|
-
},
|
|
344
|
-
properties: {
|
|
345
|
-
name: "Start Point",
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
type: "Feature",
|
|
350
|
-
geometry: {
|
|
351
|
-
type: "LineString",
|
|
352
|
-
coordinates: [
|
|
353
|
-
[-122.4194, 37.7749], // Same as point
|
|
354
|
-
[-122.4094, 37.7849],
|
|
355
|
-
],
|
|
356
|
-
},
|
|
357
|
-
properties: {
|
|
358
|
-
highway: "primary",
|
|
359
|
-
},
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
type: "Feature",
|
|
363
|
-
geometry: {
|
|
364
|
-
type: "Point",
|
|
365
|
-
coordinates: [-122.3994, 37.7949],
|
|
366
|
-
},
|
|
367
|
-
properties: {
|
|
368
|
-
name: "End Point",
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const osm = await fromGeoJSON(geojson)
|
|
375
|
-
|
|
376
|
-
// Should have 3 nodes (point node is reused by LineString)
|
|
377
|
-
expect(osm.nodes.size).toBe(3)
|
|
378
|
-
expect(osm.ways.size).toBe(1)
|
|
379
|
-
|
|
380
|
-
const way = osm.ways.getById(-1)
|
|
381
|
-
expect(way?.refs).toHaveLength(2)
|
|
382
|
-
// First node of way should be the same as the point node
|
|
383
|
-
const pointNode = osm.nodes.getById(-1)
|
|
384
|
-
expect(way?.refs[0]).toBe(-1)
|
|
385
|
-
expect(pointNode?.lon).toBe(-122.4194)
|
|
386
|
-
expect(pointNode?.lat).toBe(37.7749)
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
it("should build indexes after conversion", async () => {
|
|
390
|
-
const geojson: FeatureCollection<Point> = {
|
|
391
|
-
type: "FeatureCollection",
|
|
392
|
-
features: [
|
|
393
|
-
{
|
|
394
|
-
type: "Feature",
|
|
395
|
-
geometry: {
|
|
396
|
-
type: "Point",
|
|
397
|
-
coordinates: [-122.4194, 37.7749],
|
|
398
|
-
},
|
|
399
|
-
properties: {},
|
|
400
|
-
},
|
|
401
|
-
],
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const osm = await fromGeoJSON(geojson)
|
|
405
|
-
|
|
406
|
-
expect(osm.isReady()).toBe(true)
|
|
407
|
-
expect(osm.nodes.isReady()).toBe(true)
|
|
408
|
-
expect(osm.ways.isReady()).toBe(true)
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
it("should handle features without properties", async () => {
|
|
412
|
-
const geojson: FeatureCollection<Point> = {
|
|
413
|
-
type: "FeatureCollection",
|
|
414
|
-
features: [
|
|
415
|
-
{
|
|
416
|
-
type: "Feature",
|
|
417
|
-
geometry: {
|
|
418
|
-
type: "Point",
|
|
419
|
-
coordinates: [-122.4194, 37.7749],
|
|
420
|
-
},
|
|
421
|
-
properties: null,
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const osm = await fromGeoJSON(geojson)
|
|
427
|
-
|
|
428
|
-
const node = osm.nodes.getById(-1)
|
|
429
|
-
expect(node).toBeDefined()
|
|
430
|
-
expect(node?.tags).toBeUndefined()
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
it("should convert Polygon features to Ways with area tags", async () => {
|
|
434
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
435
|
-
type: "FeatureCollection",
|
|
436
|
-
features: [
|
|
437
|
-
{
|
|
438
|
-
type: "Feature",
|
|
439
|
-
geometry: {
|
|
440
|
-
type: "Polygon",
|
|
441
|
-
coordinates: [
|
|
442
|
-
[
|
|
443
|
-
[-122.4194, 37.7749],
|
|
444
|
-
[-122.4094, 37.7749],
|
|
445
|
-
[-122.4094, 37.7849],
|
|
446
|
-
[-122.4194, 37.7849],
|
|
447
|
-
[-122.4194, 37.7749], // Closed ring
|
|
448
|
-
],
|
|
449
|
-
],
|
|
450
|
-
},
|
|
451
|
-
properties: {
|
|
452
|
-
building: "yes",
|
|
453
|
-
name: "Test Building",
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
],
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const osm = await fromGeoJSON(geojson)
|
|
460
|
-
|
|
461
|
-
expect(osm.nodes.size).toBe(4) // 4 unique nodes (last is duplicate)
|
|
462
|
-
expect(osm.ways.size).toBe(1)
|
|
463
|
-
expect(osm.relations.size).toBe(0) // No relation for simple polygon
|
|
464
|
-
|
|
465
|
-
const way = osm.ways.getById(-1)
|
|
466
|
-
expect(way).toBeDefined()
|
|
467
|
-
expect(way?.tags?.["building"]).toBe("yes")
|
|
468
|
-
expect(way?.tags?.["name"]).toBe("Test Building")
|
|
469
|
-
expect(way?.tags?.["area"]).toBe("yes")
|
|
470
|
-
expect(way?.refs).toHaveLength(5) // 4 nodes + closing node
|
|
471
|
-
expect(way?.refs[0]).toBe(way?.refs[4]) // Ring is closed
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
it("should convert Polygon with holes to relation with multiple Ways", async () => {
|
|
475
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
476
|
-
type: "FeatureCollection",
|
|
477
|
-
features: [
|
|
478
|
-
{
|
|
479
|
-
type: "Feature",
|
|
480
|
-
geometry: {
|
|
481
|
-
type: "Polygon",
|
|
482
|
-
coordinates: [
|
|
483
|
-
// Outer ring
|
|
484
|
-
[
|
|
485
|
-
[-122.4194, 37.7749],
|
|
486
|
-
[-122.4094, 37.7749],
|
|
487
|
-
[-122.4094, 37.7849],
|
|
488
|
-
[-122.4194, 37.7849],
|
|
489
|
-
[-122.4194, 37.7749],
|
|
490
|
-
],
|
|
491
|
-
// Hole
|
|
492
|
-
[
|
|
493
|
-
[-122.4164, 37.7779],
|
|
494
|
-
[-122.4144, 37.7779],
|
|
495
|
-
[-122.4144, 37.7799],
|
|
496
|
-
[-122.4164, 37.7799],
|
|
497
|
-
[-122.4164, 37.7779],
|
|
498
|
-
],
|
|
499
|
-
],
|
|
500
|
-
},
|
|
501
|
-
properties: {
|
|
502
|
-
building: "yes",
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
],
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const osm = await fromGeoJSON(geojson)
|
|
509
|
-
|
|
510
|
-
// Should have outer ring way + hole way + relation
|
|
511
|
-
expect(osm.ways.size).toBe(2)
|
|
512
|
-
expect(osm.relations.size).toBe(1)
|
|
513
|
-
|
|
514
|
-
const outerWay = osm.ways.getById(-1)
|
|
515
|
-
expect(outerWay).toBeDefined()
|
|
516
|
-
expect(outerWay?.tags?.["area"]).toBe("yes")
|
|
517
|
-
expect(outerWay?.tags?.["building"]).toBeUndefined() // Tags go on relation
|
|
518
|
-
|
|
519
|
-
const holeWay = osm.ways.getById(-2)
|
|
520
|
-
expect(holeWay).toBeDefined()
|
|
521
|
-
expect(holeWay?.tags?.["area"]).toBe("yes")
|
|
522
|
-
expect(holeWay?.tags?.["building"]).toBeUndefined() // Tags go on relation
|
|
523
|
-
|
|
524
|
-
const relation = osm.relations.getById(-1)
|
|
525
|
-
expect(relation).toBeDefined()
|
|
526
|
-
expect(relation?.tags?.["type"]).toBe("multipolygon")
|
|
527
|
-
expect(relation?.tags?.["building"]).toBe("yes")
|
|
528
|
-
expect(relation?.members).toHaveLength(2)
|
|
529
|
-
expect(relation?.members[0]?.type).toBe("way")
|
|
530
|
-
expect(relation?.members[0]?.ref).toBe(-1)
|
|
531
|
-
expect(relation?.members[0]?.role).toBe("outer")
|
|
532
|
-
expect(relation?.members[1]?.type).toBe("way")
|
|
533
|
-
expect(relation?.members[1]?.ref).toBe(-2)
|
|
534
|
-
expect(relation?.members[1]?.role).toBe("inner")
|
|
535
|
-
})
|
|
536
|
-
|
|
537
|
-
it("should convert MultiPolygon features to relation with multiple Ways", async () => {
|
|
538
|
-
const geojson: FeatureCollection<MultiPolygon> = {
|
|
539
|
-
type: "FeatureCollection",
|
|
540
|
-
features: [
|
|
541
|
-
{
|
|
542
|
-
type: "Feature",
|
|
543
|
-
geometry: {
|
|
544
|
-
type: "MultiPolygon",
|
|
545
|
-
coordinates: [
|
|
546
|
-
// First polygon
|
|
547
|
-
[
|
|
548
|
-
[
|
|
549
|
-
[-122.4194, 37.7749],
|
|
550
|
-
[-122.4094, 37.7749],
|
|
551
|
-
[-122.4094, 37.7849],
|
|
552
|
-
[-122.4194, 37.7849],
|
|
553
|
-
[-122.4194, 37.7749],
|
|
554
|
-
],
|
|
555
|
-
],
|
|
556
|
-
// Second polygon
|
|
557
|
-
[
|
|
558
|
-
[
|
|
559
|
-
[-122.3994, 37.7649],
|
|
560
|
-
[-122.3894, 37.7649],
|
|
561
|
-
[-122.3894, 37.7749],
|
|
562
|
-
[-122.3994, 37.7749],
|
|
563
|
-
[-122.3994, 37.7649],
|
|
564
|
-
],
|
|
565
|
-
],
|
|
566
|
-
],
|
|
567
|
-
},
|
|
568
|
-
properties: {
|
|
569
|
-
landuse: "residential",
|
|
570
|
-
},
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const osm = await fromGeoJSON(geojson)
|
|
576
|
-
|
|
577
|
-
// Should have 2 ways + 1 relation
|
|
578
|
-
expect(osm.ways.size).toBe(2)
|
|
579
|
-
expect(osm.relations.size).toBe(1)
|
|
580
|
-
|
|
581
|
-
const way1 = osm.ways.getById(-1)
|
|
582
|
-
expect(way1).toBeDefined()
|
|
583
|
-
expect(way1?.tags?.["landuse"]).toBeUndefined() // Tags go on relation
|
|
584
|
-
expect(way1?.tags?.["area"]).toBe("yes")
|
|
585
|
-
|
|
586
|
-
const way2 = osm.ways.getById(-2)
|
|
587
|
-
expect(way2).toBeDefined()
|
|
588
|
-
expect(way2?.tags?.["landuse"]).toBeUndefined() // Tags go on relation
|
|
589
|
-
expect(way2?.tags?.["area"]).toBe("yes")
|
|
590
|
-
|
|
591
|
-
const relation = osm.relations.getById(-1)
|
|
592
|
-
expect(relation).toBeDefined()
|
|
593
|
-
expect(relation?.tags?.["type"]).toBe("multipolygon")
|
|
594
|
-
expect(relation?.tags?.["landuse"]).toBe("residential")
|
|
595
|
-
expect(relation?.members).toHaveLength(2)
|
|
596
|
-
expect(relation?.members[0]?.type).toBe("way")
|
|
597
|
-
expect(relation?.members[0]?.ref).toBe(-1)
|
|
598
|
-
expect(relation?.members[0]?.role).toBe("outer")
|
|
599
|
-
expect(relation?.members[1]?.type).toBe("way")
|
|
600
|
-
expect(relation?.members[1]?.ref).toBe(-2)
|
|
601
|
-
expect(relation?.members[1]?.role).toBe("outer")
|
|
602
|
-
})
|
|
603
|
-
|
|
604
|
-
it("should convert MultiPolygon with holes to relation with multiple Ways", async () => {
|
|
605
|
-
const geojson: FeatureCollection<MultiPolygon> = {
|
|
606
|
-
type: "FeatureCollection",
|
|
607
|
-
features: [
|
|
608
|
-
{
|
|
609
|
-
type: "Feature",
|
|
610
|
-
geometry: {
|
|
611
|
-
type: "MultiPolygon",
|
|
612
|
-
coordinates: [
|
|
613
|
-
// First polygon with hole
|
|
614
|
-
[
|
|
615
|
-
// Outer ring
|
|
616
|
-
[
|
|
617
|
-
[-122.4194, 37.7749],
|
|
618
|
-
[-122.4094, 37.7749],
|
|
619
|
-
[-122.4094, 37.7849],
|
|
620
|
-
[-122.4194, 37.7849],
|
|
621
|
-
[-122.4194, 37.7749],
|
|
622
|
-
],
|
|
623
|
-
// Hole
|
|
624
|
-
[
|
|
625
|
-
[-122.4164, 37.7779],
|
|
626
|
-
[-122.4144, 37.7779],
|
|
627
|
-
[-122.4144, 37.7799],
|
|
628
|
-
[-122.4164, 37.7799],
|
|
629
|
-
[-122.4164, 37.7779],
|
|
630
|
-
],
|
|
631
|
-
],
|
|
632
|
-
// Second polygon (no holes)
|
|
633
|
-
[
|
|
634
|
-
[
|
|
635
|
-
[-122.3994, 37.7649],
|
|
636
|
-
[-122.3894, 37.7649],
|
|
637
|
-
[-122.3894, 37.7749],
|
|
638
|
-
[-122.3994, 37.7749],
|
|
639
|
-
[-122.3994, 37.7649],
|
|
640
|
-
],
|
|
641
|
-
],
|
|
642
|
-
],
|
|
643
|
-
},
|
|
644
|
-
properties: {
|
|
645
|
-
landuse: "residential",
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
],
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const osm = await fromGeoJSON(geojson)
|
|
652
|
-
|
|
653
|
-
// Should have 3 ways + 1 relation
|
|
654
|
-
expect(osm.ways.size).toBe(3)
|
|
655
|
-
expect(osm.relations.size).toBe(1)
|
|
656
|
-
|
|
657
|
-
const outerWay = osm.ways.getById(-1)
|
|
658
|
-
expect(outerWay).toBeDefined()
|
|
659
|
-
expect(outerWay?.tags?.["area"]).toBe("yes")
|
|
660
|
-
|
|
661
|
-
const holeWay = osm.ways.getById(-2)
|
|
662
|
-
expect(holeWay).toBeDefined()
|
|
663
|
-
expect(holeWay?.tags?.["area"]).toBe("yes")
|
|
664
|
-
|
|
665
|
-
const secondPolyWay = osm.ways.getById(-3)
|
|
666
|
-
expect(secondPolyWay).toBeDefined()
|
|
667
|
-
expect(secondPolyWay?.tags?.["area"]).toBe("yes")
|
|
668
|
-
|
|
669
|
-
const relation = osm.relations.getById(-1)
|
|
670
|
-
expect(relation).toBeDefined()
|
|
671
|
-
expect(relation?.tags?.["type"]).toBe("multipolygon")
|
|
672
|
-
expect(relation?.tags?.["landuse"]).toBe("residential")
|
|
673
|
-
expect(relation?.members).toHaveLength(3)
|
|
674
|
-
expect(relation?.members[0]?.type).toBe("way")
|
|
675
|
-
expect(relation?.members[0]?.ref).toBe(-1)
|
|
676
|
-
expect(relation?.members[0]?.role).toBe("outer")
|
|
677
|
-
expect(relation?.members[1]?.type).toBe("way")
|
|
678
|
-
expect(relation?.members[1]?.ref).toBe(-2)
|
|
679
|
-
expect(relation?.members[1]?.role).toBe("inner")
|
|
680
|
-
expect(relation?.members[2]?.type).toBe("way")
|
|
681
|
-
expect(relation?.members[2]?.ref).toBe(-3)
|
|
682
|
-
expect(relation?.members[2]?.role).toBe("outer")
|
|
683
|
-
})
|
|
684
|
-
|
|
685
|
-
it("should error on Polygon with unclosed ring", () => {
|
|
686
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
687
|
-
type: "FeatureCollection",
|
|
688
|
-
features: [
|
|
689
|
-
{
|
|
690
|
-
type: "Feature",
|
|
691
|
-
geometry: {
|
|
692
|
-
type: "Polygon",
|
|
693
|
-
coordinates: [
|
|
694
|
-
[
|
|
695
|
-
[-122.4194, 37.7749],
|
|
696
|
-
[-122.4094, 37.7749],
|
|
697
|
-
[-122.4094, 37.7849],
|
|
698
|
-
[-122.4194, 37.7849],
|
|
699
|
-
// Not closed - should throw error
|
|
700
|
-
],
|
|
701
|
-
],
|
|
702
|
-
},
|
|
703
|
-
properties: {
|
|
704
|
-
building: "yes",
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
],
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
expect(() => fromGeoJSON(geojson)).toThrow(
|
|
711
|
-
"Outer ring of Polygon is not closed.",
|
|
712
|
-
)
|
|
713
|
-
})
|
|
714
|
-
|
|
715
|
-
it("should skip invalid Polygons with less than 3 coordinates", async () => {
|
|
716
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
717
|
-
type: "FeatureCollection",
|
|
718
|
-
features: [
|
|
719
|
-
{
|
|
720
|
-
type: "Feature",
|
|
721
|
-
geometry: {
|
|
722
|
-
type: "Polygon",
|
|
723
|
-
coordinates: [
|
|
724
|
-
[
|
|
725
|
-
[-122.4194, 37.7749],
|
|
726
|
-
[-122.4094, 37.7749],
|
|
727
|
-
// Only 2 coordinates - invalid
|
|
728
|
-
],
|
|
729
|
-
],
|
|
730
|
-
},
|
|
731
|
-
properties: {},
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
type: "Feature",
|
|
735
|
-
geometry: {
|
|
736
|
-
type: "Polygon",
|
|
737
|
-
coordinates: [
|
|
738
|
-
[
|
|
739
|
-
[-122.4194, 37.7749],
|
|
740
|
-
[-122.4094, 37.7749],
|
|
741
|
-
[-122.4094, 37.7849],
|
|
742
|
-
[-122.4194, 37.7849],
|
|
743
|
-
[-122.4194, 37.7749],
|
|
744
|
-
],
|
|
745
|
-
],
|
|
746
|
-
},
|
|
747
|
-
properties: {
|
|
748
|
-
building: "yes",
|
|
749
|
-
},
|
|
750
|
-
},
|
|
751
|
-
],
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const osm = await fromGeoJSON(geojson)
|
|
755
|
-
|
|
756
|
-
// Should only have one way (the valid one)
|
|
757
|
-
expect(osm.ways.size).toBe(1)
|
|
758
|
-
expect(osm.ways.getById(-1)?.tags?.["building"]).toBe("yes")
|
|
759
|
-
})
|
|
760
|
-
|
|
761
|
-
it("should normalize winding order using rewind", async () => {
|
|
762
|
-
// Create a polygon with clockwise winding (should be normalized to counterclockwise)
|
|
763
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
764
|
-
type: "FeatureCollection",
|
|
765
|
-
features: [
|
|
766
|
-
{
|
|
767
|
-
type: "Feature",
|
|
768
|
-
geometry: {
|
|
769
|
-
type: "Polygon",
|
|
770
|
-
coordinates: [
|
|
771
|
-
// Clockwise ring (will be normalized by rewind)
|
|
772
|
-
[
|
|
773
|
-
[-122.4194, 37.7749],
|
|
774
|
-
[-122.4194, 37.7849],
|
|
775
|
-
[-122.4094, 37.7849],
|
|
776
|
-
[-122.4094, 37.7749],
|
|
777
|
-
[-122.4194, 37.7749],
|
|
778
|
-
],
|
|
779
|
-
],
|
|
780
|
-
},
|
|
781
|
-
properties: {
|
|
782
|
-
building: "yes",
|
|
783
|
-
},
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const osm = await fromGeoJSON(geojson)
|
|
789
|
-
|
|
790
|
-
// Should still create the way successfully (rewind normalizes winding)
|
|
791
|
-
expect(osm.ways.size).toBe(1)
|
|
792
|
-
const way = osm.ways.getById(-1)
|
|
793
|
-
expect(way).toBeDefined()
|
|
794
|
-
expect(way?.tags?.["building"]).toBe("yes")
|
|
795
|
-
expect(way?.tags?.["area"]).toBe("yes")
|
|
796
|
-
})
|
|
797
|
-
|
|
798
|
-
it("should error on unclosed hole ring", () => {
|
|
799
|
-
const geojson: FeatureCollection<Polygon> = {
|
|
800
|
-
type: "FeatureCollection",
|
|
801
|
-
features: [
|
|
802
|
-
{
|
|
803
|
-
type: "Feature",
|
|
804
|
-
geometry: {
|
|
805
|
-
type: "Polygon",
|
|
806
|
-
coordinates: [
|
|
807
|
-
// Outer ring (closed)
|
|
808
|
-
[
|
|
809
|
-
[-122.4194, 37.7749],
|
|
810
|
-
[-122.4094, 37.7749],
|
|
811
|
-
[-122.4094, 37.7849],
|
|
812
|
-
[-122.4194, 37.7849],
|
|
813
|
-
[-122.4194, 37.7749],
|
|
814
|
-
],
|
|
815
|
-
// Hole ring (not closed - should throw error)
|
|
816
|
-
[
|
|
817
|
-
[-122.4164, 37.7779],
|
|
818
|
-
[-122.4144, 37.7779],
|
|
819
|
-
[-122.4144, 37.7799],
|
|
820
|
-
[-122.4164, 37.7799],
|
|
821
|
-
// Missing closing coordinate
|
|
822
|
-
],
|
|
823
|
-
],
|
|
824
|
-
},
|
|
825
|
-
properties: {
|
|
826
|
-
building: "yes",
|
|
827
|
-
},
|
|
828
|
-
},
|
|
829
|
-
],
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
expect(() => fromGeoJSON(geojson)).toThrow(
|
|
833
|
-
"Hole ring of Polygon is not closed.",
|
|
834
|
-
)
|
|
835
|
-
})
|
|
836
|
-
|
|
837
|
-
it("should error on unclosed MultiPolygon ring", () => {
|
|
838
|
-
const geojson: FeatureCollection<MultiPolygon> = {
|
|
839
|
-
type: "FeatureCollection",
|
|
840
|
-
features: [
|
|
841
|
-
{
|
|
842
|
-
type: "Feature",
|
|
843
|
-
geometry: {
|
|
844
|
-
type: "MultiPolygon",
|
|
845
|
-
coordinates: [
|
|
846
|
-
[
|
|
847
|
-
[
|
|
848
|
-
[-122.4194, 37.7749],
|
|
849
|
-
[-122.4094, 37.7749],
|
|
850
|
-
[-122.4094, 37.7849],
|
|
851
|
-
[-122.4194, 37.7849],
|
|
852
|
-
// Not closed - should throw error
|
|
853
|
-
],
|
|
854
|
-
],
|
|
855
|
-
],
|
|
856
|
-
},
|
|
857
|
-
properties: {
|
|
858
|
-
landuse: "residential",
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
],
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
expect(() => fromGeoJSON(geojson)).toThrow(
|
|
865
|
-
"Outer ring of Polygon is not closed.",
|
|
866
|
-
)
|
|
867
|
-
})
|
|
868
|
-
})
|
package/tsconfig.build.json
DELETED