squirreling 0.9.3 → 0.9.4
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 +2 -2
- package/src/expression/geometry.d.ts +71 -0
- package/src/expression/spatial.equality.js +98 -0
- package/src/expression/spatial.geometry.js +620 -0
- package/src/expression/spatial.js +194 -1300
- package/src/expression/wkt.js +222 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Geometry } from './geometry.js'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a WKT string into a GeoJSON geometry.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} wkt
|
|
9
|
+
* @returns {Geometry | null}
|
|
10
|
+
*/
|
|
11
|
+
export function parseWkt(wkt) {
|
|
12
|
+
const s = wkt.trim()
|
|
13
|
+
const upper = s.toUpperCase()
|
|
14
|
+
|
|
15
|
+
if (upper.startsWith('POINT')) {
|
|
16
|
+
const coords = parseWktCoordinate(s.slice(5).trim())
|
|
17
|
+
if (!coords) return null
|
|
18
|
+
return { type: 'Point', coordinates: coords }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (upper.startsWith('MULTIPOINT')) {
|
|
22
|
+
const inner = extractParens(s.slice(10).trim())
|
|
23
|
+
if (inner == null) return null
|
|
24
|
+
const coords = parseWktCoordinateList(inner)
|
|
25
|
+
if (!coords) return null
|
|
26
|
+
return { type: 'MultiPoint', coordinates: coords }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (upper.startsWith('MULTILINESTRING')) {
|
|
30
|
+
const inner = extractParens(s.slice(15).trim())
|
|
31
|
+
if (inner == null) return null
|
|
32
|
+
const rings = parseWktRingList(inner)
|
|
33
|
+
if (!rings) return null
|
|
34
|
+
return { type: 'MultiLineString', coordinates: rings }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (upper.startsWith('MULTIPOLYGON')) {
|
|
38
|
+
const inner = extractParens(s.slice(12).trim())
|
|
39
|
+
if (inner == null) return null
|
|
40
|
+
const polys = parseWktPolygonList(inner)
|
|
41
|
+
if (!polys) return null
|
|
42
|
+
return { type: 'MultiPolygon', coordinates: polys }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (upper.startsWith('LINESTRING')) {
|
|
46
|
+
const inner = extractParens(s.slice(10).trim())
|
|
47
|
+
if (inner == null) return null
|
|
48
|
+
const coords = parseWktCoordinateList(inner)
|
|
49
|
+
if (!coords) return null
|
|
50
|
+
return { type: 'LineString', coordinates: coords }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (upper.startsWith('POLYGON')) {
|
|
54
|
+
const inner = extractParens(s.slice(7).trim())
|
|
55
|
+
if (inner == null) return null
|
|
56
|
+
const rings = parseWktRingList(inner)
|
|
57
|
+
if (!rings) return null
|
|
58
|
+
return { type: 'Polygon', coordinates: rings }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a GeoJSON geometry to WKT.
|
|
66
|
+
*
|
|
67
|
+
* @param {Geometry} geom
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
export function geomToWkt(geom) {
|
|
71
|
+
switch (geom.type) {
|
|
72
|
+
case 'Point':
|
|
73
|
+
return `POINT (${coordToWkt(geom.coordinates)})`
|
|
74
|
+
case 'MultiPoint':
|
|
75
|
+
return `MULTIPOINT (${geom.coordinates.map(c => `(${coordToWkt(c)})`).join(', ')})`
|
|
76
|
+
case 'LineString':
|
|
77
|
+
return `LINESTRING (${coordListToWkt(geom.coordinates)})`
|
|
78
|
+
case 'MultiLineString':
|
|
79
|
+
return `MULTILINESTRING (${geom.coordinates.map(l => `(${coordListToWkt(l)})`).join(', ')})`
|
|
80
|
+
case 'Polygon':
|
|
81
|
+
return `POLYGON (${geom.coordinates.map(r => `(${coordListToWkt(r)})`).join(', ')})`
|
|
82
|
+
case 'MultiPolygon':
|
|
83
|
+
return `MULTIPOLYGON (${geom.coordinates.map(p => `(${p.map(r => `(${coordListToWkt(r)})`).join(', ')})`).join(', ')})`
|
|
84
|
+
case 'GeometryCollection':
|
|
85
|
+
return `GEOMETRYCOLLECTION (${(geom.geometries || []).map(g => geomToWkt(g)).join(', ')})`
|
|
86
|
+
default:
|
|
87
|
+
return ''
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extract content inside outer parentheses.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} s
|
|
95
|
+
* @returns {string | null}
|
|
96
|
+
*/
|
|
97
|
+
function extractParens(s) {
|
|
98
|
+
const trimmed = s.trim()
|
|
99
|
+
if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) return null
|
|
100
|
+
return trimmed.slice(1, -1).trim()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse a single coordinate like "(1 2)" or "1 2".
|
|
105
|
+
*
|
|
106
|
+
* @param {string} s
|
|
107
|
+
* @returns {number[] | null}
|
|
108
|
+
*/
|
|
109
|
+
function parseWktCoordinate(s) {
|
|
110
|
+
const inner = s.trim().replace(/^\(/, '').replace(/\)$/, '').trim()
|
|
111
|
+
const parts = inner.split(/\s+/)
|
|
112
|
+
if (parts.length < 2) return null
|
|
113
|
+
const x = Number(parts[0])
|
|
114
|
+
const y = Number(parts[1])
|
|
115
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null
|
|
116
|
+
return [x, y]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse a comma-separated list of coordinates like "1 2, 3 4, 5 6".
|
|
121
|
+
*
|
|
122
|
+
* @param {string} s
|
|
123
|
+
* @returns {number[][] | null}
|
|
124
|
+
*/
|
|
125
|
+
function parseWktCoordinateList(s) {
|
|
126
|
+
const parts = s.split(',')
|
|
127
|
+
/** @type {number[][]} */
|
|
128
|
+
const coords = []
|
|
129
|
+
for (const part of parts) {
|
|
130
|
+
const trimmed = part.trim().replace(/^\(/, '').replace(/\)$/, '').trim()
|
|
131
|
+
const nums = trimmed.split(/\s+/)
|
|
132
|
+
if (nums.length < 2) return null
|
|
133
|
+
const x = Number(nums[0])
|
|
134
|
+
const y = Number(nums[1])
|
|
135
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null
|
|
136
|
+
coords.push([x, y])
|
|
137
|
+
}
|
|
138
|
+
return coords.length ? coords : null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse a list of rings like "(1 2, 3 4), (5 6, 7 8)".
|
|
143
|
+
*
|
|
144
|
+
* @param {string} s
|
|
145
|
+
* @returns {number[][][] | null}
|
|
146
|
+
*/
|
|
147
|
+
function parseWktRingList(s) {
|
|
148
|
+
/** @type {number[][][]} */
|
|
149
|
+
const rings = []
|
|
150
|
+
const ringStrs = splitTopLevel(s)
|
|
151
|
+
for (const ringStr of ringStrs) {
|
|
152
|
+
const inner = extractParens(ringStr.trim())
|
|
153
|
+
if (inner == null) return null
|
|
154
|
+
const coords = parseWktCoordinateList(inner)
|
|
155
|
+
if (!coords) return null
|
|
156
|
+
rings.push(coords)
|
|
157
|
+
}
|
|
158
|
+
return rings.length ? rings : null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse a list of polygons like "((ring1), (ring2)), ((ring3))".
|
|
163
|
+
*
|
|
164
|
+
* @param {string} s
|
|
165
|
+
* @returns {number[][][][] | null}
|
|
166
|
+
*/
|
|
167
|
+
function parseWktPolygonList(s) {
|
|
168
|
+
/** @type {number[][][][]} */
|
|
169
|
+
const polys = []
|
|
170
|
+
const polyStrs = splitTopLevel(s)
|
|
171
|
+
for (const polyStr of polyStrs) {
|
|
172
|
+
const inner = extractParens(polyStr.trim())
|
|
173
|
+
if (inner == null) return null
|
|
174
|
+
const rings = parseWktRingList(inner)
|
|
175
|
+
if (!rings) return null
|
|
176
|
+
polys.push(rings)
|
|
177
|
+
}
|
|
178
|
+
return polys.length ? polys : null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Split a string by commas at the top-level (not inside parentheses).
|
|
183
|
+
*
|
|
184
|
+
* @param {string} s
|
|
185
|
+
* @returns {string[]}
|
|
186
|
+
*/
|
|
187
|
+
function splitTopLevel(s) {
|
|
188
|
+
/** @type {string[]} */
|
|
189
|
+
const parts = []
|
|
190
|
+
let depth = 0
|
|
191
|
+
let start = 0
|
|
192
|
+
for (let i = 0; i < s.length; i++) {
|
|
193
|
+
if (s[i] === '(') depth++
|
|
194
|
+
else if (s[i] === ')') depth--
|
|
195
|
+
else if (s[i] === ',' && depth === 0) {
|
|
196
|
+
parts.push(s.slice(start, i))
|
|
197
|
+
start = i + 1
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
parts.push(s.slice(start))
|
|
201
|
+
return parts
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Format a single coordinate to WKT.
|
|
206
|
+
*
|
|
207
|
+
* @param {number[]} coord
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
function coordToWkt(coord) {
|
|
211
|
+
return `${coord[0]} ${coord[1]}`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Format a coordinate list to WKT.
|
|
216
|
+
*
|
|
217
|
+
* @param {number[][]} coords
|
|
218
|
+
* @returns {string}
|
|
219
|
+
*/
|
|
220
|
+
function coordListToWkt(coords) {
|
|
221
|
+
return coords.map(coordToWkt).join(', ')
|
|
222
|
+
}
|