@osmix/shared 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.
Files changed (208) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +49 -19
  3. package/dist/assert.d.ts +24 -0
  4. package/dist/assert.d.ts.map +1 -0
  5. package/dist/assert.js +28 -0
  6. package/dist/assert.js.map +1 -0
  7. package/dist/bbox-intersects.d.ts +15 -0
  8. package/dist/bbox-intersects.d.ts.map +1 -0
  9. package/dist/bbox-intersects.js +24 -0
  10. package/dist/bbox-intersects.js.map +1 -0
  11. package/dist/bytes-to-stream.d.ts +18 -0
  12. package/dist/bytes-to-stream.d.ts.map +1 -0
  13. package/dist/bytes-to-stream.js +25 -0
  14. package/dist/bytes-to-stream.js.map +1 -0
  15. package/dist/color.d.ts +4 -0
  16. package/dist/color.d.ts.map +1 -0
  17. package/dist/color.js +33 -0
  18. package/dist/color.js.map +1 -0
  19. package/dist/concat-bytes.d.ts +5 -0
  20. package/dist/concat-bytes.d.ts.map +1 -0
  21. package/dist/concat-bytes.js +14 -0
  22. package/dist/concat-bytes.js.map +1 -0
  23. package/dist/coordinates.d.ts +28 -0
  24. package/dist/coordinates.d.ts.map +1 -0
  25. package/dist/coordinates.js +38 -0
  26. package/dist/coordinates.js.map +1 -0
  27. package/dist/haversine-distance.d.ts +16 -0
  28. package/dist/haversine-distance.d.ts.map +1 -0
  29. package/dist/haversine-distance.js +26 -0
  30. package/dist/haversine-distance.js.map +1 -0
  31. package/dist/lineclip.d.ts +2 -0
  32. package/dist/lineclip.d.ts.map +1 -0
  33. package/dist/lineclip.js +3 -0
  34. package/dist/lineclip.js.map +1 -0
  35. package/dist/progress.d.ts +42 -0
  36. package/dist/progress.d.ts.map +1 -0
  37. package/dist/progress.js +53 -0
  38. package/dist/progress.js.map +1 -0
  39. package/dist/relation-kind.d.ts +69 -0
  40. package/dist/relation-kind.d.ts.map +1 -0
  41. package/dist/relation-kind.js +375 -0
  42. package/dist/relation-kind.js.map +1 -0
  43. package/dist/relation-multipolygon.d.ts +43 -0
  44. package/dist/relation-multipolygon.d.ts.map +1 -0
  45. package/dist/relation-multipolygon.js +195 -0
  46. package/dist/relation-multipolygon.js.map +1 -0
  47. package/dist/src/assert.d.ts +20 -1
  48. package/dist/src/assert.d.ts.map +1 -1
  49. package/dist/src/assert.js +20 -1
  50. package/dist/src/assert.js.map +1 -1
  51. package/dist/src/bbox-intersects.d.ts +15 -0
  52. package/dist/src/bbox-intersects.d.ts.map +1 -0
  53. package/dist/src/bbox-intersects.js +24 -0
  54. package/dist/src/bbox-intersects.js.map +1 -0
  55. package/dist/src/bytes-to-stream.d.ts +17 -1
  56. package/dist/src/bytes-to-stream.d.ts.map +1 -1
  57. package/dist/src/bytes-to-stream.js +16 -0
  58. package/dist/src/bytes-to-stream.js.map +1 -1
  59. package/dist/src/color.d.ts +4 -0
  60. package/dist/src/color.d.ts.map +1 -0
  61. package/dist/src/color.js +33 -0
  62. package/dist/src/color.js.map +1 -0
  63. package/dist/src/coordinates.d.ts +28 -0
  64. package/dist/src/coordinates.d.ts.map +1 -0
  65. package/dist/src/coordinates.js +38 -0
  66. package/dist/src/coordinates.js.map +1 -0
  67. package/dist/src/haversine-distance.d.ts +9 -1
  68. package/dist/src/haversine-distance.d.ts.map +1 -1
  69. package/dist/src/haversine-distance.js +9 -1
  70. package/dist/src/haversine-distance.js.map +1 -1
  71. package/dist/src/progress.d.ts +42 -0
  72. package/dist/src/progress.d.ts.map +1 -0
  73. package/dist/src/progress.js +53 -0
  74. package/dist/src/progress.js.map +1 -0
  75. package/dist/src/relation-kind.d.ts +69 -0
  76. package/dist/src/relation-kind.d.ts.map +1 -0
  77. package/dist/src/relation-kind.js +375 -0
  78. package/dist/src/relation-kind.js.map +1 -0
  79. package/dist/src/relation-multipolygon.d.ts +43 -0
  80. package/dist/src/relation-multipolygon.d.ts.map +1 -0
  81. package/dist/src/relation-multipolygon.js +195 -0
  82. package/dist/src/relation-multipolygon.js.map +1 -0
  83. package/dist/src/stream-to-bytes.d.ts +16 -0
  84. package/dist/src/stream-to-bytes.d.ts.map +1 -1
  85. package/dist/src/stream-to-bytes.js +16 -0
  86. package/dist/src/stream-to-bytes.js.map +1 -1
  87. package/dist/src/test/fixtures.d.ts +1 -1
  88. package/dist/src/test/fixtures.d.ts.map +1 -1
  89. package/dist/src/test/fixtures.js +16 -8
  90. package/dist/src/test/fixtures.js.map +1 -1
  91. package/dist/src/throttle.d.ts +25 -0
  92. package/dist/src/throttle.d.ts.map +1 -0
  93. package/dist/src/throttle.js +34 -0
  94. package/dist/src/throttle.js.map +1 -0
  95. package/dist/src/tile.d.ts +34 -0
  96. package/dist/src/tile.d.ts.map +1 -0
  97. package/dist/src/tile.js +72 -0
  98. package/dist/src/tile.js.map +1 -0
  99. package/dist/src/transform-bytes.d.ts +22 -0
  100. package/dist/src/transform-bytes.d.ts.map +1 -1
  101. package/dist/src/transform-bytes.js +22 -0
  102. package/dist/src/transform-bytes.js.map +1 -1
  103. package/dist/src/types.d.ts +76 -1
  104. package/dist/src/types.d.ts.map +1 -1
  105. package/dist/src/types.js +8 -0
  106. package/dist/src/types.js.map +1 -1
  107. package/dist/src/utils.d.ts +30 -0
  108. package/dist/src/utils.d.ts.map +1 -0
  109. package/dist/src/utils.js +70 -0
  110. package/dist/src/utils.js.map +1 -0
  111. package/dist/src/way-is-area.d.ts +24 -0
  112. package/dist/src/way-is-area.d.ts.map +1 -0
  113. package/dist/src/way-is-area.js +104 -0
  114. package/dist/src/way-is-area.js.map +1 -0
  115. package/dist/src/zigzag.d.ts +33 -0
  116. package/dist/src/zigzag.d.ts.map +1 -0
  117. package/dist/src/zigzag.js +40 -0
  118. package/dist/src/zigzag.js.map +1 -0
  119. package/dist/stream-to-bytes.d.ts +18 -0
  120. package/dist/stream-to-bytes.d.ts.map +1 -0
  121. package/dist/stream-to-bytes.js +30 -0
  122. package/dist/stream-to-bytes.js.map +1 -0
  123. package/dist/test/fixtures.d.ts +36 -0
  124. package/dist/test/fixtures.d.ts.map +1 -0
  125. package/dist/test/fixtures.js +175 -0
  126. package/dist/test/fixtures.js.map +1 -0
  127. package/dist/test/haversine-distance.test.js +2 -2
  128. package/dist/test/haversine-distance.test.js.map +1 -1
  129. package/dist/test/relation-kind.test.d.ts +2 -0
  130. package/dist/test/relation-kind.test.d.ts.map +1 -0
  131. package/dist/test/relation-kind.test.js +367 -0
  132. package/dist/test/relation-kind.test.js.map +1 -0
  133. package/dist/test/relation-multipolygon.test.d.ts +2 -0
  134. package/dist/test/relation-multipolygon.test.d.ts.map +1 -0
  135. package/dist/test/relation-multipolygon.test.js +237 -0
  136. package/dist/test/relation-multipolygon.test.js.map +1 -0
  137. package/dist/test/utils.test.d.ts +2 -0
  138. package/dist/test/utils.test.d.ts.map +1 -0
  139. package/dist/test/utils.test.js +76 -0
  140. package/dist/test/utils.test.js.map +1 -0
  141. package/dist/test/way-is-area.test.d.ts +2 -0
  142. package/dist/test/way-is-area.test.d.ts.map +1 -0
  143. package/dist/test/way-is-area.test.js +31 -0
  144. package/dist/test/way-is-area.test.js.map +1 -0
  145. package/dist/throttle.d.ts +25 -0
  146. package/dist/throttle.d.ts.map +1 -0
  147. package/dist/throttle.js +34 -0
  148. package/dist/throttle.js.map +1 -0
  149. package/dist/tile.d.ts +34 -0
  150. package/dist/tile.d.ts.map +1 -0
  151. package/dist/tile.js +72 -0
  152. package/dist/tile.js.map +1 -0
  153. package/dist/transform-bytes.d.ts +24 -0
  154. package/dist/transform-bytes.d.ts.map +1 -0
  155. package/dist/transform-bytes.js +28 -0
  156. package/dist/transform-bytes.js.map +1 -0
  157. package/dist/types.d.ts +99 -0
  158. package/dist/types.d.ts.map +1 -0
  159. package/dist/types.js +9 -0
  160. package/dist/types.js.map +1 -0
  161. package/dist/utils.d.ts +30 -0
  162. package/dist/utils.d.ts.map +1 -0
  163. package/dist/utils.js +70 -0
  164. package/dist/utils.js.map +1 -0
  165. package/dist/way-is-area.d.ts +24 -0
  166. package/dist/way-is-area.d.ts.map +1 -0
  167. package/dist/way-is-area.js +104 -0
  168. package/dist/way-is-area.js.map +1 -0
  169. package/dist/zigzag.d.ts +33 -0
  170. package/dist/zigzag.d.ts.map +1 -0
  171. package/dist/zigzag.js +40 -0
  172. package/dist/zigzag.js.map +1 -0
  173. package/package.json +11 -10
  174. package/src/assert.ts +21 -1
  175. package/src/bbox-intersects.ts +30 -0
  176. package/src/bytes-to-stream.ts +17 -0
  177. package/src/color.ts +37 -0
  178. package/src/coordinates.ts +45 -0
  179. package/src/haversine-distance.ts +10 -1
  180. package/src/progress.ts +74 -0
  181. package/src/relation-kind.ts +446 -0
  182. package/src/relation-multipolygon.ts +225 -0
  183. package/src/stream-to-bytes.ts +17 -0
  184. package/src/test/fixtures.ts +16 -12
  185. package/src/throttle.ts +37 -0
  186. package/src/tile.ts +89 -0
  187. package/src/transform-bytes.ts +23 -0
  188. package/src/types.ts +93 -1
  189. package/src/utils.ts +79 -0
  190. package/src/way-is-area.ts +107 -0
  191. package/src/zigzag.ts +42 -0
  192. package/test/haversine-distance.test.ts +2 -2
  193. package/test/relation-kind.test.ts +426 -0
  194. package/test/relation-multipolygon.test.ts +265 -0
  195. package/test/utils.test.ts +103 -0
  196. package/test/way-is-area.test.ts +42 -0
  197. package/tsconfig/test.json +1 -0
  198. package/tsconfig.build.json +5 -0
  199. package/dist/src/spherical-mercator.d.ts +0 -15
  200. package/dist/src/spherical-mercator.d.ts.map +0 -1
  201. package/dist/src/spherical-mercator.js +0 -35
  202. package/dist/src/spherical-mercator.js.map +0 -1
  203. package/dist/src/spherical-mercator.test.d.ts +0 -2
  204. package/dist/src/spherical-mercator.test.d.ts.map +0 -1
  205. package/dist/src/spherical-mercator.test.js +0 -25
  206. package/dist/src/spherical-mercator.test.js.map +0 -1
  207. package/src/spherical-mercator.test.ts +0 -42
  208. package/src/spherical-mercator.ts +0 -42
@@ -0,0 +1,265 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import {
3
+ buildRelationRings,
4
+ connectWaysToRings,
5
+ getWayMembersByRole,
6
+ } from "../src/relation-multipolygon"
7
+ import type { OsmRelation, OsmWay } from "../src/types"
8
+
9
+ describe("relation-multipolygon", () => {
10
+ describe("getWayMembersByRole", () => {
11
+ it("groups way members by outer and inner roles", () => {
12
+ const relation: OsmRelation = {
13
+ id: 1,
14
+ tags: { type: "multipolygon" },
15
+ members: [
16
+ { type: "way", ref: 10, role: "outer" },
17
+ { type: "way", ref: 11, role: "inner" },
18
+ { type: "way", ref: 12, role: "outer" },
19
+ { type: "node", ref: 1 },
20
+ { type: "way", ref: 13, role: "inner" },
21
+ ],
22
+ }
23
+
24
+ const { outer, inner } = getWayMembersByRole(relation)
25
+ expect(outer).toHaveLength(2)
26
+ expect(outer[0]?.ref).toBe(10)
27
+ expect(outer[1]?.ref).toBe(12)
28
+ expect(inner).toHaveLength(2)
29
+ expect(inner[0]?.ref).toBe(11)
30
+ expect(inner[1]?.ref).toBe(13)
31
+ })
32
+
33
+ it("handles case-insensitive roles", () => {
34
+ const relation: OsmRelation = {
35
+ id: 1,
36
+ tags: { type: "multipolygon" },
37
+ members: [
38
+ { type: "way", ref: 10, role: "OUTER" },
39
+ { type: "way", ref: 11, role: "Inner" },
40
+ ],
41
+ }
42
+
43
+ const { outer, inner } = getWayMembersByRole(relation)
44
+ expect(outer).toHaveLength(1)
45
+ expect(inner).toHaveLength(1)
46
+ })
47
+
48
+ it("handles missing roles", () => {
49
+ const relation: OsmRelation = {
50
+ id: 1,
51
+ tags: { type: "multipolygon" },
52
+ members: [
53
+ { type: "way", ref: 10 },
54
+ { type: "way", ref: 11, role: "outer" },
55
+ ],
56
+ }
57
+
58
+ const { outer, inner } = getWayMembersByRole(relation)
59
+ expect(outer).toHaveLength(1)
60
+ expect(inner).toHaveLength(0)
61
+ })
62
+ })
63
+
64
+ describe("connectWaysToRings", () => {
65
+ it("connects ways sharing endpoints into a single ring", () => {
66
+ const way1: OsmWay = { id: 1, refs: [1, 2] }
67
+ const way2: OsmWay = { id: 2, refs: [2, 3] }
68
+ const way3: OsmWay = { id: 3, refs: [3, 1] }
69
+
70
+ const rings = connectWaysToRings([way1, way2, way3])
71
+ expect(rings).toHaveLength(1)
72
+ expect(rings[0]).toEqual([way1, way2, way3])
73
+ })
74
+
75
+ it("handles ways that need to be reversed", () => {
76
+ // way1 ends at 2, way2 starts at 2 (normal connection)
77
+ const way1: OsmWay = { id: 1, refs: [1, 2] }
78
+ const way2: OsmWay = { id: 2, refs: [2, 3, 4] }
79
+
80
+ const rings = connectWaysToRings([way1, way2])
81
+ // Should create a ring if ways connect and form a closed loop
82
+ // This test verifies basic connection logic
83
+ expect(rings.length).toBeGreaterThanOrEqual(0)
84
+ })
85
+
86
+ it("creates separate rings for disconnected ways", () => {
87
+ const way1: OsmWay = { id: 1, refs: [1, 2, 1] } // closed ring
88
+ const way2: OsmWay = { id: 2, refs: [3, 4, 3] } // separate closed ring
89
+
90
+ const rings = connectWaysToRings([way1, way2])
91
+ expect(rings).toHaveLength(2)
92
+ })
93
+
94
+ it("only includes closed rings", () => {
95
+ const way1: OsmWay = { id: 1, refs: [1, 2, 3] } // not closed
96
+ const way2: OsmWay = { id: 2, refs: [4, 5, 4] } // closed
97
+
98
+ const rings = connectWaysToRings([way1, way2])
99
+ // Only the closed ring should be included
100
+ expect(rings.length).toBeGreaterThanOrEqual(1)
101
+ })
102
+ })
103
+
104
+ describe("buildRelationRings", () => {
105
+ it("builds simple multipolygon with outer ring only", () => {
106
+ const relation: OsmRelation = {
107
+ id: 1,
108
+ tags: { type: "multipolygon" },
109
+ members: [{ type: "way", ref: 1, role: "outer" }],
110
+ }
111
+
112
+ const way1: OsmWay = {
113
+ id: 1,
114
+ refs: [1, 2, 3, 4, 1],
115
+ }
116
+
117
+ const getWay = (id: number) => (id === 1 ? way1 : null)
118
+ const getNodeCoordinates = (id: number) => {
119
+ const coords: Record<number, [number, number]> = {
120
+ 1: [0.0, 0.0],
121
+ 2: [1.0, 0.0],
122
+ 3: [1.0, 1.0],
123
+ 4: [0.0, 1.0],
124
+ }
125
+ return coords[id]
126
+ }
127
+
128
+ const rings = buildRelationRings(relation, getWay, getNodeCoordinates)
129
+ expect(rings).toHaveLength(1)
130
+ expect(rings[0]).toHaveLength(1) // one outer ring
131
+ expect(rings[0]?.[0]).toHaveLength(5) // closed ring with 5 points
132
+ })
133
+
134
+ it("builds multipolygon with outer and inner rings (holes)", () => {
135
+ const relation: OsmRelation = {
136
+ id: 1,
137
+ tags: { type: "multipolygon" },
138
+ members: [
139
+ { type: "way", ref: 1, role: "outer" },
140
+ { type: "way", ref: 2, role: "inner" },
141
+ ],
142
+ }
143
+
144
+ const way1: OsmWay = {
145
+ id: 1,
146
+ refs: [1, 2, 3, 4, 1], // outer square
147
+ }
148
+ const way2: OsmWay = {
149
+ id: 2,
150
+ refs: [5, 6, 7, 5], // inner triangle
151
+ }
152
+
153
+ const getWay = (id: number) => {
154
+ if (id === 1) return way1
155
+ if (id === 2) return way2
156
+ return null
157
+ }
158
+ const getNodeCoordinates = (id: number) => {
159
+ const coords: Record<number, [number, number]> = {
160
+ 1: [-1.0, -1.0],
161
+ 2: [1.0, -1.0],
162
+ 3: [1.0, 1.0],
163
+ 4: [-1.0, 1.0],
164
+ 5: [-0.5, 0.0],
165
+ 6: [0.5, 0.0],
166
+ 7: [0.0, 0.5],
167
+ }
168
+ return coords[id]
169
+ }
170
+
171
+ const rings = buildRelationRings(relation, getWay, getNodeCoordinates)
172
+ expect(rings).toHaveLength(1)
173
+ expect(rings[0]).toHaveLength(2) // outer + inner
174
+ expect(rings[0]?.[0]).toBeDefined() // outer ring
175
+ expect(rings[0]?.[1]).toBeDefined() // inner ring (hole)
176
+ })
177
+
178
+ it("builds multipolygon with multiple outer rings", () => {
179
+ const relation: OsmRelation = {
180
+ id: 1,
181
+ tags: { type: "multipolygon" },
182
+ members: [
183
+ { type: "way", ref: 1, role: "outer" },
184
+ { type: "way", ref: 2, role: "outer" },
185
+ ],
186
+ }
187
+
188
+ const way1: OsmWay = {
189
+ id: 1,
190
+ refs: [1, 2, 3, 1], // first polygon
191
+ }
192
+ const way2: OsmWay = {
193
+ id: 2,
194
+ refs: [4, 5, 6, 4], // second polygon
195
+ }
196
+
197
+ const getWay = (id: number) => {
198
+ if (id === 1) return way1
199
+ if (id === 2) return way2
200
+ return null
201
+ }
202
+ const getNodeCoordinates = (id: number) => {
203
+ const coords: Record<number, [number, number]> = {
204
+ 1: [0.0, 0.0],
205
+ 2: [1.0, 0.0],
206
+ 3: [0.5, 1.0],
207
+ 4: [2.0, 0.0],
208
+ 5: [3.0, 0.0],
209
+ 6: [2.5, 1.0],
210
+ }
211
+ return coords[id]
212
+ }
213
+
214
+ const rings = buildRelationRings(relation, getWay, getNodeCoordinates)
215
+ expect(rings).toHaveLength(2) // two separate polygons
216
+ })
217
+
218
+ it("handles relation similar to osmtogeojson test case", () => {
219
+ // Based on: https://github.com/placemark/osmtogeojson/blob/main/test/osm.test.js
220
+ const relation: OsmRelation = {
221
+ id: 1,
222
+ tags: { type: "multipolygon" },
223
+ members: [
224
+ { type: "way", ref: 2, role: "outer" },
225
+ { type: "way", ref: 3, role: "inner" },
226
+ ],
227
+ }
228
+
229
+ const way2: OsmWay = {
230
+ id: 2,
231
+ refs: [4, 5, 6, 7, 4], // outer square
232
+ }
233
+ const way3: OsmWay = {
234
+ id: 3,
235
+ refs: [8, 9, 10, 8], // inner triangle
236
+ }
237
+
238
+ const getWay = (id: number) => {
239
+ if (id === 2) return way2
240
+ if (id === 3) return way3
241
+ return null
242
+ }
243
+ const getNodeCoordinates = (id: number) => {
244
+ const coords: Record<number, [number, number]> = {
245
+ 4: [-1.0, -1.0],
246
+ 5: [-1.0, 1.0],
247
+ 6: [1.0, 1.0],
248
+ 7: [1.0, -1.0],
249
+ 8: [-0.5, 0.0],
250
+ 9: [0.5, 0.0],
251
+ 10: [0.0, 0.5],
252
+ }
253
+ return coords[id]
254
+ }
255
+
256
+ const rings = buildRelationRings(relation, getWay, getNodeCoordinates)
257
+ expect(rings).toHaveLength(1)
258
+ expect(rings[0]).toHaveLength(2) // outer + inner
259
+ // Outer ring should have 5 points (closed square)
260
+ expect(rings[0]?.[0]).toHaveLength(5)
261
+ // Inner ring should have 4 points (closed triangle)
262
+ expect(rings[0]?.[1]).toHaveLength(4)
263
+ })
264
+ })
265
+ })
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import type {
3
+ OsmNode,
4
+ OsmRelation,
5
+ OsmRelationMember,
6
+ OsmWay,
7
+ } from "../src/types"
8
+ import {
9
+ entityPropertiesEqual,
10
+ getEntityType,
11
+ isMultipolygonRelation,
12
+ isNode,
13
+ isNodeEqual,
14
+ isRelation,
15
+ isRelationEqual,
16
+ isWay,
17
+ isWayEqual,
18
+ } from "../src/utils"
19
+
20
+ describe("utils", () => {
21
+ const node: OsmNode = {
22
+ id: 1,
23
+ lat: 10,
24
+ lon: 20,
25
+ tags: { name: "node" },
26
+ }
27
+ const way: OsmWay = {
28
+ id: 2,
29
+ refs: [1, 2, 3],
30
+ tags: { highway: "residential" },
31
+ }
32
+ const members: OsmRelationMember[] = [
33
+ {
34
+ type: "node",
35
+ ref: 1,
36
+ },
37
+ ]
38
+ const relation: OsmRelation = {
39
+ id: 3,
40
+ members,
41
+ tags: { type: "multipolygon" },
42
+ }
43
+
44
+ it("narrows entity types", () => {
45
+ expect(isNode(node)).toBe(true)
46
+ expect(isWay(way)).toBe(true)
47
+ expect(isRelation(relation)).toBe(true)
48
+ })
49
+
50
+ it("compares entity equality", () => {
51
+ expect(isNodeEqual(node, { ...node })).toBe(true)
52
+ expect(isWayEqual(way, { ...way })).toBe(true)
53
+ expect(isRelationEqual(relation, { ...relation })).toBe(true)
54
+ })
55
+
56
+ it("detects property differences", () => {
57
+ expect(
58
+ entityPropertiesEqual(node, { ...node, tags: { name: "changed" } }),
59
+ ).toBe(false)
60
+ expect(entityPropertiesEqual(way, { ...way, refs: [1, 2, 4] })).toBe(false)
61
+ expect(
62
+ entityPropertiesEqual(relation, {
63
+ ...relation,
64
+ members: [{ type: "node", ref: 2 }],
65
+ }),
66
+ ).toBe(false)
67
+ })
68
+
69
+ it("provides entity type", () => {
70
+ expect(getEntityType(node)).toBe("node")
71
+ expect(getEntityType(way)).toBe("way")
72
+ expect(getEntityType(relation)).toBe("relation")
73
+ })
74
+
75
+ describe("isMultipolygonRelation", () => {
76
+ it("identifies multipolygon relations", () => {
77
+ const relation: OsmRelation = {
78
+ id: 1,
79
+ tags: { type: "multipolygon" },
80
+ members: [],
81
+ }
82
+ expect(isMultipolygonRelation(relation)).toBe(true)
83
+ })
84
+
85
+ it("rejects non-multipolygon relations", () => {
86
+ const relation: OsmRelation = {
87
+ id: 1,
88
+ tags: { type: "route" },
89
+ members: [],
90
+ }
91
+ expect(isMultipolygonRelation(relation)).toBe(false)
92
+ })
93
+
94
+ it("rejects relations without type tag", () => {
95
+ const relation: OsmRelation = {
96
+ id: 1,
97
+ tags: { name: "test" },
98
+ members: [],
99
+ }
100
+ expect(isMultipolygonRelation(relation)).toBe(false)
101
+ })
102
+ })
103
+ })
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { wayIsArea } from "../src/way-is-area"
3
+
4
+ describe("wayIsArea", () => {
5
+ it("returns false for open ways", () => {
6
+ const refs = [1, 2, 3]
7
+ expect(wayIsArea({ id: 0, refs, tags: { building: "yes" } })).toBe(false)
8
+ })
9
+
10
+ it("returns true for closed way without tags", () => {
11
+ const refs = [1, 2, 3, 1]
12
+ expect(wayIsArea({ id: 0, refs })).toBe(true)
13
+ })
14
+
15
+ it("honors explicit area override", () => {
16
+ const refs = [1, 2, 3, 1]
17
+ expect(
18
+ wayIsArea({ id: 0, refs, tags: { area: "no", building: "yes" } }),
19
+ ).toBe(false)
20
+ expect(wayIsArea({ id: 0, refs, tags: { area: "yes" } })).toBe(true)
21
+ })
22
+
23
+ it("treats implied tags as areas", () => {
24
+ const refs = [1, 2, 3, 1]
25
+ expect(wayIsArea({ id: 0, refs, tags: { building: "yes" } })).toBe(true)
26
+ })
27
+
28
+ it("considers included values", () => {
29
+ const refs = [1, 2, 3, 1]
30
+ expect(wayIsArea({ id: 0, refs, tags: { highway: "rest_area" } })).toBe(
31
+ true,
32
+ )
33
+ expect(wayIsArea({ id: 0, refs, tags: { highway: "primary" } })).toBe(false)
34
+ })
35
+
36
+ it("rejects excluded values", () => {
37
+ const refs = [1, 2, 3, 1]
38
+ expect(wayIsArea({ id: 0, refs, tags: { natural: "coastline" } })).toBe(
39
+ false,
40
+ )
41
+ })
42
+ })
@@ -2,6 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "display": "@osmix/tsconfig/test",
4
4
  "extends": "./base.json",
5
+ "exclude": ["node_modules", "dist"],
5
6
  "compilerOptions": {
6
7
  "noEmit": true,
7
8
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./tsconfig.json",
4
+ "include": ["src"]
5
+ }
@@ -1,15 +0,0 @@
1
- import { SphericalMercator } from "@mapbox/sphericalmercator";
2
- import type { GeoBbox2D, LonLat, Tile, XY } from "./types";
3
- /**
4
- * Extends the SphericalMercator class to provide tile-local pixel coordinate calculations and clamping.
5
- */
6
- export default class SphericalMercatorTile extends SphericalMercator {
7
- tileSize: number;
8
- tile?: Tile;
9
- constructor(options: ConstructorParameters<typeof SphericalMercator>[0] & {
10
- tile?: Tile;
11
- });
12
- llToTilePx(ll: LonLat, tile?: Tile): XY;
13
- clampAndRoundPx(px: XY, bbox?: GeoBbox2D): XY;
14
- }
15
- //# sourceMappingURL=spherical-mercator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spherical-mercator.d.ts","sourceRoot":"","sources":["../../src/spherical-mercator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAE1D;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,iBAAiB;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,IAAI,CAAA;gBAEV,OAAO,EAAE,qBAAqB,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG;QAC7D,IAAI,CAAC,EAAE,IAAI,CAAA;KACX;IAOF,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE;IAUvC,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,EAAE;CAY7C"}
@@ -1,35 +0,0 @@
1
- import { SphericalMercator } from "@mapbox/sphericalmercator";
2
- /**
3
- * Extends the SphericalMercator class to provide tile-local pixel coordinate calculations and clamping.
4
- */
5
- export default class SphericalMercatorTile extends SphericalMercator {
6
- tileSize;
7
- tile;
8
- constructor(options) {
9
- super(options);
10
- this.tile = options?.tile;
11
- this.tileSize = options?.size ?? 256;
12
- }
13
- llToTilePx(ll, tile) {
14
- if (tile == null && this.tile == null)
15
- throw Error("Tile must be set on construction or passed as an argument.");
16
- const [tx, ty, tz] = (tile ?? this.tile);
17
- const merc = this.px(ll, tz);
18
- const x = merc[0] - tx * this.tileSize;
19
- const y = merc[1] - ty * this.tileSize;
20
- return [x, y];
21
- }
22
- clampAndRoundPx(px, bbox) {
23
- const [minX, minY, maxX, maxY] = bbox ?? [
24
- 0,
25
- 0,
26
- this.tileSize,
27
- this.tileSize,
28
- ];
29
- return [
30
- Math.max(minX, Math.min(maxX, Math.round(px[0]))),
31
- Math.max(minY, Math.min(maxY, Math.round(px[1]))),
32
- ];
33
- }
34
- }
35
- //# sourceMappingURL=spherical-mercator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spherical-mercator.js","sourceRoot":"","sources":["../../src/spherical-mercator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAG7D;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,iBAAiB;IACnE,QAAQ,CAAQ;IAChB,IAAI,CAAO;IACX,YACC,OAEC;QAED,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAA;QACzB,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,IAAI,GAAG,CAAA;IACrC,CAAC;IAED,UAAU,CAAC,EAAU,EAAE,IAAW;QACjC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;YACpC,MAAM,KAAK,CAAC,4DAA4D,CAAC,CAAA;QAC1E,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAE,CAAA;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAA;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAA;QACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACd,CAAC;IAED,eAAe,CAAC,EAAM,EAAE,IAAgB;QACvC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;YACxC,CAAC;YACD,CAAC;YACD,IAAI,CAAC,QAAQ;YACb,IAAI,CAAC,QAAQ;SACb,CAAA;QACD,OAAO;YACN,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACjD,CAAA;IACF,CAAC;CACD"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=spherical-mercator.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spherical-mercator.test.d.ts","sourceRoot":"","sources":["../../src/spherical-mercator.test.ts"],"names":[],"mappings":""}
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import SphericalMercatorTile from "./spherical-mercator";
3
- function lonLatForPixel(merc, tileIndex, tileSize, px, py) {
4
- const [x, y, z] = tileIndex;
5
- return merc.ll([x * tileSize + px, y * tileSize + py], z);
6
- }
7
- describe("SphericalMercatorTile", () => {
8
- it("projects lon/lat to tile-local pixels", () => {
9
- const tile = [300, 300, 10];
10
- const [tx, ty, tz] = tile;
11
- const tileSize = 256;
12
- const merc = new SphericalMercatorTile({ size: tileSize, tile });
13
- const insideLonLat = lonLatForPixel(merc, tile, tileSize, 32, 16);
14
- expect(merc.llToTilePx(insideLonLat)).toEqual([32, 16]);
15
- const outsideTopLeft = merc.ll([tx * tileSize - 10, ty * tileSize - 10], tz);
16
- expect(outsideTopLeft).toEqual([-74.54498291015625, 59.54128017205441]);
17
- expect(merc.llToTilePx(outsideTopLeft)).toEqual([-10, -10]);
18
- const outsideBottomRight = merc.ll([(tx + 1) * tileSize + 10, (ty + 1) * tileSize + 10], tz);
19
- expect(merc.llToTilePx(outsideBottomRight, tile)).toEqual([
20
- tileSize + 10,
21
- tileSize + 10,
22
- ]);
23
- });
24
- });
25
- //# sourceMappingURL=spherical-mercator.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spherical-mercator.test.js","sourceRoot":"","sources":["../../src/spherical-mercator.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,qBAAqB,MAAM,sBAAsB,CAAA;AAGxD,SAAS,cAAc,CACtB,IAA2B,EAC3B,SAAe,EACf,QAAgB,EAChB,EAAU,EACV,EAAU;IAEV,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAC3B,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,EAAE,EAAE,CAAC,GAAG,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,CAAqB,CAAA;AAC9E,CAAC;AAED,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,IAAI,GAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;QACjC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAA;QACzB,MAAM,QAAQ,GAAG,GAAG,CAAA;QACpB,MAAM,IAAI,GAAG,IAAI,qBAAqB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAEhE,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QACjE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAEvD,MAAM,cAAc,GAAG,IAAI,CAAC,EAAE,CAC7B,CAAC,EAAE,GAAG,QAAQ,GAAG,EAAE,EAAE,EAAE,GAAG,QAAQ,GAAG,EAAE,CAAC,EACxC,EAAE,CACkB,CAAA;QACrB,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,CAAC,CAAA;QACvE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAE3D,MAAM,kBAAkB,GAAG,IAAI,CAAC,EAAE,CACjC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,QAAQ,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,QAAQ,GAAG,EAAE,CAAC,EACpD,EAAE,CACkB,CAAA;QACrB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YACzD,QAAQ,GAAG,EAAE;YACb,QAAQ,GAAG,EAAE;SACb,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -1,42 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
- import SphericalMercatorTile from "./spherical-mercator"
3
- import type { Tile } from "./types"
4
-
5
- function lonLatForPixel(
6
- merc: SphericalMercatorTile,
7
- tileIndex: Tile,
8
- tileSize: number,
9
- px: number,
10
- py: number,
11
- ): [number, number] {
12
- const [x, y, z] = tileIndex
13
- return merc.ll([x * tileSize + px, y * tileSize + py], z) as [number, number]
14
- }
15
-
16
- describe("SphericalMercatorTile", () => {
17
- it("projects lon/lat to tile-local pixels", () => {
18
- const tile: Tile = [300, 300, 10]
19
- const [tx, ty, tz] = tile
20
- const tileSize = 256
21
- const merc = new SphericalMercatorTile({ size: tileSize, tile })
22
-
23
- const insideLonLat = lonLatForPixel(merc, tile, tileSize, 32, 16)
24
- expect(merc.llToTilePx(insideLonLat)).toEqual([32, 16])
25
-
26
- const outsideTopLeft = merc.ll(
27
- [tx * tileSize - 10, ty * tileSize - 10],
28
- tz,
29
- ) as [number, number]
30
- expect(outsideTopLeft).toEqual([-74.54498291015625, 59.54128017205441])
31
- expect(merc.llToTilePx(outsideTopLeft)).toEqual([-10, -10])
32
-
33
- const outsideBottomRight = merc.ll(
34
- [(tx + 1) * tileSize + 10, (ty + 1) * tileSize + 10],
35
- tz,
36
- ) as [number, number]
37
- expect(merc.llToTilePx(outsideBottomRight, tile)).toEqual([
38
- tileSize + 10,
39
- tileSize + 10,
40
- ])
41
- })
42
- })
@@ -1,42 +0,0 @@
1
- import { SphericalMercator } from "@mapbox/sphericalmercator"
2
- import type { GeoBbox2D, LonLat, Tile, XY } from "./types"
3
-
4
- /**
5
- * Extends the SphericalMercator class to provide tile-local pixel coordinate calculations and clamping.
6
- */
7
- export default class SphericalMercatorTile extends SphericalMercator {
8
- tileSize: number
9
- tile?: Tile
10
- constructor(
11
- options: ConstructorParameters<typeof SphericalMercator>[0] & {
12
- tile?: Tile
13
- },
14
- ) {
15
- super(options)
16
- this.tile = options?.tile
17
- this.tileSize = options?.size ?? 256
18
- }
19
-
20
- llToTilePx(ll: LonLat, tile?: Tile): XY {
21
- if (tile == null && this.tile == null)
22
- throw Error("Tile must be set on construction or passed as an argument.")
23
- const [tx, ty, tz] = (tile ?? this.tile)!
24
- const merc = this.px(ll, tz)
25
- const x = merc[0] - tx * this.tileSize
26
- const y = merc[1] - ty * this.tileSize
27
- return [x, y]
28
- }
29
-
30
- clampAndRoundPx(px: XY, bbox?: GeoBbox2D): XY {
31
- const [minX, minY, maxX, maxY] = bbox ?? [
32
- 0,
33
- 0,
34
- this.tileSize,
35
- this.tileSize,
36
- ]
37
- return [
38
- Math.max(minX, Math.min(maxX, Math.round(px[0]))),
39
- Math.max(minY, Math.min(maxY, Math.round(px[1]))),
40
- ]
41
- }
42
- }