wkt-parse-and-geojson 1.0.6 → 1.0.8
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/dist/index.cjs.js +37 -245
- package/dist/index.esm.js +37 -245
- package/dist/index.umd.js +37 -245
- package/package.json +2 -7
- package/dist/geojson-builder.d.ts +0 -92
- package/dist/geojson-to-wkt.d.ts +0 -32
- package/dist/index.d.ts +0 -8
- package/dist/namespace.d.ts +0 -34
- package/dist/types.d.ts +0 -51
- package/dist/validate.d.ts +0 -29
- package/dist/wkt-builder.d.ts +0 -17
- package/dist/wkt-parser.d.ts +0 -33
- package/dist/wkt-to-geojson.d.ts +0 -35
package/dist/index.cjs.js
CHANGED
|
@@ -4,7 +4,6 @@ var types = /*#__PURE__*/Object.freeze({
|
|
|
4
4
|
__proto__: null
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
// Precompiled regex for better performance
|
|
8
7
|
const RE_WHITESPACE = /\s/;
|
|
9
8
|
const RE_NUMBER_START = /[0-9\-]/;
|
|
10
9
|
const RE_NUMBER_BODY = /[0-9\.\-eE\+]/;
|
|
@@ -30,7 +29,6 @@ class Lexer {
|
|
|
30
29
|
return RE_NUMBER_BODY.test(c);
|
|
31
30
|
}
|
|
32
31
|
nextToken() {
|
|
33
|
-
// skip whitespace
|
|
34
32
|
while (this.pos < this.input.length && this.isWhitespace(this.peek())) {
|
|
35
33
|
this.advance();
|
|
36
34
|
}
|
|
@@ -50,7 +48,6 @@ class Lexer {
|
|
|
50
48
|
this.advance();
|
|
51
49
|
return { type: 'COMMA', value: ',' };
|
|
52
50
|
}
|
|
53
|
-
// Number: starts with digit or minus
|
|
54
51
|
if (this.isNumberStart(c)) {
|
|
55
52
|
const start = this.pos;
|
|
56
53
|
while (this.pos < this.input.length && this.isNumberBody(this.peek())) {
|
|
@@ -58,7 +55,6 @@ class Lexer {
|
|
|
58
55
|
}
|
|
59
56
|
return { type: 'NUMBER', value: this.input.slice(start, this.pos) };
|
|
60
57
|
}
|
|
61
|
-
// Word (geometry type or EMPTY/Z/M keyword)
|
|
62
58
|
const start = this.pos;
|
|
63
59
|
while (this.pos < this.input.length && RE_WORD_CHAR.test(this.peek())) {
|
|
64
60
|
this.pos++;
|
|
@@ -82,7 +78,6 @@ class WKTParser {
|
|
|
82
78
|
}
|
|
83
79
|
this.tokens.push({ type: 'EOF', value: '' });
|
|
84
80
|
const geometry = this.parseGeometry();
|
|
85
|
-
// 校验尾部无多余字符(防止静默忽略垃圾输入)
|
|
86
81
|
if (this.peek().type !== 'EOF') {
|
|
87
82
|
throw new Error(`Unexpected trailing token after geometry: "${this.peek().value}"`);
|
|
88
83
|
}
|
|
@@ -94,7 +89,6 @@ class WKTParser {
|
|
|
94
89
|
advance() {
|
|
95
90
|
return this.tokens[this.pos++];
|
|
96
91
|
}
|
|
97
|
-
/** 消费当前 token 并返回,若类型不匹配则抛出错误 */
|
|
98
92
|
consume(type) {
|
|
99
93
|
const token = this.peek();
|
|
100
94
|
if (token.type !== type) {
|
|
@@ -135,14 +129,12 @@ class WKTParser {
|
|
|
135
129
|
throw new Error(`Unknown geometry type: ${token.value}`);
|
|
136
130
|
}
|
|
137
131
|
}
|
|
138
|
-
// ── 消费可选的维度修饰符(Z / M / ZM)
|
|
139
132
|
skipDimensionKeyword() {
|
|
140
133
|
const t = this.peek();
|
|
141
134
|
if (t.type === 'WORD' && (t.value === 'Z' || t.value === 'M' || t.value === 'ZM')) {
|
|
142
135
|
this.advance();
|
|
143
136
|
}
|
|
144
137
|
}
|
|
145
|
-
// ── 判断并消费 EMPTY 关键字
|
|
146
138
|
isEmptyGeometry() {
|
|
147
139
|
const t = this.peek();
|
|
148
140
|
if (t.type === 'WORD' && t.value === 'EMPTY') {
|
|
@@ -151,13 +143,10 @@ class WKTParser {
|
|
|
151
143
|
}
|
|
152
144
|
return false;
|
|
153
145
|
}
|
|
154
|
-
// ── POINT ────────────────────────────────────────────────────────
|
|
155
146
|
parsePoint() {
|
|
156
|
-
this.advance();
|
|
147
|
+
this.advance();
|
|
157
148
|
this.skipDimensionKeyword();
|
|
158
149
|
if (this.isEmptyGeometry()) {
|
|
159
|
-
// GeoJSON 无法表示空点:POINT EMPTY 在 GeoJSON 中应用 null geometry Feature,
|
|
160
|
-
// 此处直接抛出,由调用方决定如何处理。
|
|
161
150
|
throw new Error('POINT EMPTY cannot be represented as a GeoJSON Point. ' +
|
|
162
151
|
'Consider using wktToFeature() and checking Feature.geometry === null.');
|
|
163
152
|
}
|
|
@@ -166,9 +155,8 @@ class WKTParser {
|
|
|
166
155
|
this.consume('RPAREN');
|
|
167
156
|
return { type: 'Point', coordinates: coords };
|
|
168
157
|
}
|
|
169
|
-
// ── LINESTRING ───────────────────────────────────────────────────
|
|
170
158
|
parseLineString() {
|
|
171
|
-
this.advance();
|
|
159
|
+
this.advance();
|
|
172
160
|
this.skipDimensionKeyword();
|
|
173
161
|
if (this.isEmptyGeometry()) {
|
|
174
162
|
return { type: 'LineString', coordinates: [] };
|
|
@@ -176,9 +164,8 @@ class WKTParser {
|
|
|
176
164
|
const coords = this.parseCoordinatesList();
|
|
177
165
|
return { type: 'LineString', coordinates: coords };
|
|
178
166
|
}
|
|
179
|
-
// ── POLYGON ──────────────────────────────────────────────────────
|
|
180
167
|
parsePolygon() {
|
|
181
|
-
this.advance();
|
|
168
|
+
this.advance();
|
|
182
169
|
this.skipDimensionKeyword();
|
|
183
170
|
if (this.isEmptyGeometry()) {
|
|
184
171
|
return { type: 'Polygon', coordinates: [] };
|
|
@@ -192,24 +179,21 @@ class WKTParser {
|
|
|
192
179
|
this.consume('RPAREN');
|
|
193
180
|
return { type: 'Polygon', coordinates: rings };
|
|
194
181
|
}
|
|
195
|
-
// ── MULTIPOINT ───────────────────────────────────────────────────
|
|
196
182
|
parseMultiPoint() {
|
|
197
|
-
this.advance();
|
|
183
|
+
this.advance();
|
|
198
184
|
this.skipDimensionKeyword();
|
|
199
185
|
if (this.isEmptyGeometry() || this.peek().type !== 'LPAREN') {
|
|
200
186
|
return { type: 'MultiPoint', coordinates: [] };
|
|
201
187
|
}
|
|
202
|
-
this.advance();
|
|
188
|
+
this.advance();
|
|
203
189
|
const coords = [];
|
|
204
190
|
while (!this.isDone()) {
|
|
205
191
|
if (this.peek().type === 'LPAREN') {
|
|
206
|
-
|
|
207
|
-
this.advance(); // consume (
|
|
192
|
+
this.advance();
|
|
208
193
|
coords.push(this.parseCoordinates());
|
|
209
194
|
this.consume('RPAREN');
|
|
210
195
|
}
|
|
211
196
|
else {
|
|
212
|
-
// 非标准写法: MULTIPOINT (x y, x y)
|
|
213
197
|
coords.push(this.parseCoordinates());
|
|
214
198
|
}
|
|
215
199
|
this.skipComma();
|
|
@@ -217,14 +201,13 @@ class WKTParser {
|
|
|
217
201
|
this.consume('RPAREN');
|
|
218
202
|
return { type: 'MultiPoint', coordinates: coords };
|
|
219
203
|
}
|
|
220
|
-
// ── MULTILINESTRING ──────────────────────────────────────────────
|
|
221
204
|
parseMultiLineString() {
|
|
222
|
-
this.advance();
|
|
205
|
+
this.advance();
|
|
223
206
|
this.skipDimensionKeyword();
|
|
224
207
|
if (this.isEmptyGeometry() || this.peek().type !== 'LPAREN') {
|
|
225
208
|
return { type: 'MultiLineString', coordinates: [] };
|
|
226
209
|
}
|
|
227
|
-
this.advance();
|
|
210
|
+
this.advance();
|
|
228
211
|
const lines = [];
|
|
229
212
|
while (!this.isDone()) {
|
|
230
213
|
lines.push(this.parseCoordinatesList());
|
|
@@ -233,9 +216,8 @@ class WKTParser {
|
|
|
233
216
|
this.consume('RPAREN');
|
|
234
217
|
return { type: 'MultiLineString', coordinates: lines };
|
|
235
218
|
}
|
|
236
|
-
// ── MULTIPOLYGON ─────────────────────────────────────────────────
|
|
237
219
|
parseMultiPolygon() {
|
|
238
|
-
this.advance();
|
|
220
|
+
this.advance();
|
|
239
221
|
this.skipDimensionKeyword();
|
|
240
222
|
if (this.isEmptyGeometry()) {
|
|
241
223
|
return { type: 'MultiPolygon', coordinates: [] };
|
|
@@ -249,14 +231,13 @@ class WKTParser {
|
|
|
249
231
|
this.consume('RPAREN');
|
|
250
232
|
return { type: 'MultiPolygon', coordinates: polys };
|
|
251
233
|
}
|
|
252
|
-
// ── GEOMETRYCOLLECTION ───────────────────────────────────────────
|
|
253
234
|
parseGeometryCollection() {
|
|
254
|
-
this.advance();
|
|
235
|
+
this.advance();
|
|
255
236
|
this.skipDimensionKeyword();
|
|
256
237
|
if (this.isEmptyGeometry() || this.peek().type !== 'LPAREN') {
|
|
257
238
|
return { type: 'GeometryCollection', geometries: [] };
|
|
258
239
|
}
|
|
259
|
-
this.advance();
|
|
240
|
+
this.advance();
|
|
260
241
|
const geometries = [];
|
|
261
242
|
while (!this.isDone()) {
|
|
262
243
|
geometries.push(this.parseGeometry());
|
|
@@ -265,11 +246,6 @@ class WKTParser {
|
|
|
265
246
|
this.consume('RPAREN');
|
|
266
247
|
return { type: 'GeometryCollection', geometries };
|
|
267
248
|
}
|
|
268
|
-
// ── 坐标解析辅助方法 ──────────────────────────────────────────────
|
|
269
|
-
/**
|
|
270
|
-
* 读取一个坐标点(自动检测维度:X Y 或 X Y Z)
|
|
271
|
-
* 读完 X、Y 后,若下一个 token 仍是 NUMBER,则继续读 Z
|
|
272
|
-
*/
|
|
273
249
|
parseCoordinates() {
|
|
274
250
|
const xStr = this.consume('NUMBER').value;
|
|
275
251
|
const yStr = this.consume('NUMBER').value;
|
|
@@ -279,7 +255,6 @@ class WKTParser {
|
|
|
279
255
|
throw new Error(`Invalid coordinate value: "${xStr}"`);
|
|
280
256
|
if (isNaN(y))
|
|
281
257
|
throw new Error(`Invalid coordinate value: "${yStr}"`);
|
|
282
|
-
// 动态检测 Z 坐标
|
|
283
258
|
if (this.peek().type === 'NUMBER') {
|
|
284
259
|
const zStr = this.advance().value;
|
|
285
260
|
const z = parseFloat(zStr);
|
|
@@ -289,7 +264,6 @@ class WKTParser {
|
|
|
289
264
|
}
|
|
290
265
|
return [x, y];
|
|
291
266
|
}
|
|
292
|
-
/** 解析带括号的坐标序列:( x y, x y, ... ) */
|
|
293
267
|
parseCoordinatesList() {
|
|
294
268
|
this.consume('LPAREN');
|
|
295
269
|
const coords = [];
|
|
@@ -300,11 +274,10 @@ class WKTParser {
|
|
|
300
274
|
this.consume('RPAREN');
|
|
301
275
|
return coords;
|
|
302
276
|
}
|
|
303
|
-
/** 解析环列表(Polygon 级别):( (...), (...) ) */
|
|
304
277
|
parseCoordinateListList() {
|
|
305
278
|
if (this.peek().type !== 'LPAREN')
|
|
306
279
|
return [];
|
|
307
|
-
this.advance();
|
|
280
|
+
this.advance();
|
|
308
281
|
const lists = [];
|
|
309
282
|
while (!this.isDone()) {
|
|
310
283
|
lists.push(this.parseCoordinatesList());
|
|
@@ -314,11 +287,10 @@ class WKTParser {
|
|
|
314
287
|
return lists;
|
|
315
288
|
}
|
|
316
289
|
}
|
|
317
|
-
/** 将 WKT 字符串解析为 GeoJSON Geometry 对象 */
|
|
318
290
|
function parse(wkt) {
|
|
319
|
-
|
|
320
|
-
return parser.parse(wkt);
|
|
291
|
+
return WKT_PARSER.parse(wkt);
|
|
321
292
|
}
|
|
293
|
+
const WKT_PARSER = new WKTParser();
|
|
322
294
|
|
|
323
295
|
var wktParser = /*#__PURE__*/Object.freeze({
|
|
324
296
|
__proto__: null,
|
|
@@ -326,10 +298,6 @@ var wktParser = /*#__PURE__*/Object.freeze({
|
|
|
326
298
|
parse: parse
|
|
327
299
|
});
|
|
328
300
|
|
|
329
|
-
/**
|
|
330
|
-
* 将坐标数值格式化为字符串,避免科学计数法(WKT 不支持)。
|
|
331
|
-
* 例:1e-7 → "0.0000001",1.50000 → "1.5",1.0 → "1"
|
|
332
|
-
*/
|
|
333
301
|
function formatNumber(v) {
|
|
334
302
|
if (v % 1 !== 0) {
|
|
335
303
|
return Number(v.toFixed(15)).toString();
|
|
@@ -342,25 +310,24 @@ function positionToWkt(pos) {
|
|
|
342
310
|
function coordsToWkt(coords) {
|
|
343
311
|
return coords.map(positionToWkt).join(', ');
|
|
344
312
|
}
|
|
345
|
-
// 检查坐标是否包含 Z(3个分量)
|
|
346
313
|
function hasZ(coordinates) {
|
|
347
314
|
if (!Array.isArray(coordinates))
|
|
348
315
|
return false;
|
|
349
316
|
if (coordinates.length === 0)
|
|
350
317
|
return false;
|
|
351
318
|
const first = coordinates[0];
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
319
|
+
if (typeof first === 'number') {
|
|
320
|
+
return coordinates.length === 3;
|
|
321
|
+
}
|
|
322
|
+
if (Array.isArray(first) && typeof first[0] === 'number') {
|
|
323
|
+
return first.length === 3;
|
|
324
|
+
}
|
|
325
|
+
if (Array.isArray(first) && Array.isArray(first[0])) {
|
|
326
|
+
const firstRing = first;
|
|
327
|
+
return firstRing.length > 0 && firstRing[0].length === 3;
|
|
360
328
|
}
|
|
361
329
|
return false;
|
|
362
330
|
}
|
|
363
|
-
// 获取 Z 后缀字符串
|
|
364
331
|
function zSuffix(coordinates) {
|
|
365
332
|
return hasZ(coordinates) ? ' Z' : '';
|
|
366
333
|
}
|
|
@@ -399,10 +366,6 @@ class WKTBuilder {
|
|
|
399
366
|
const ringStr = geom.coordinates.map(ring => `(${coordsToWkt(ring)})`).join(', ');
|
|
400
367
|
return `POLYGON${zSuffix(geom.coordinates)} (${ringStr})`;
|
|
401
368
|
}
|
|
402
|
-
/**
|
|
403
|
-
* 按 OGC/ISO WKT 标准,MULTIPOINT 每个点用括号包裹:
|
|
404
|
-
* MULTIPOINT ((0 0), (1 1), (2 2))
|
|
405
|
-
*/
|
|
406
369
|
buildMultiPoint(geom) {
|
|
407
370
|
if (geom.coordinates.length === 0)
|
|
408
371
|
return 'MULTIPOINT EMPTY';
|
|
@@ -431,11 +394,9 @@ class WKTBuilder {
|
|
|
431
394
|
return `GEOMETRYCOLLECTION (${geoms})`;
|
|
432
395
|
}
|
|
433
396
|
}
|
|
434
|
-
/** 将 GeoJSON Geometry 对象转换为 WKT 字符串 */
|
|
435
397
|
function build(geometry) {
|
|
436
398
|
return WKT_BUILDER.build(geometry);
|
|
437
399
|
}
|
|
438
|
-
// 单例实例,避免重复创建
|
|
439
400
|
const WKT_BUILDER = new WKTBuilder();
|
|
440
401
|
|
|
441
402
|
var wktBuilder = /*#__PURE__*/Object.freeze({
|
|
@@ -444,15 +405,11 @@ var wktBuilder = /*#__PURE__*/Object.freeze({
|
|
|
444
405
|
build: build
|
|
445
406
|
});
|
|
446
407
|
|
|
447
|
-
// ─── 内部工具:判断是否为 Position([number, number] 或 [number, number, number])
|
|
448
408
|
function isPosition(v) {
|
|
449
409
|
return (Array.isArray(v) &&
|
|
450
410
|
(v.length === 2 || v.length === 3) &&
|
|
451
411
|
v.every((n) => typeof n === 'number'));
|
|
452
412
|
}
|
|
453
|
-
/**
|
|
454
|
-
* GeoJSON 几何对象构建器(类形式,方便组合使用)
|
|
455
|
-
*/
|
|
456
413
|
class GeoJSONBuilder {
|
|
457
414
|
createPoint(x, y, z) {
|
|
458
415
|
return z !== undefined
|
|
@@ -462,130 +419,56 @@ class GeoJSONBuilder {
|
|
|
462
419
|
createLineString(coordinates) {
|
|
463
420
|
return { type: 'LineString', coordinates };
|
|
464
421
|
}
|
|
465
|
-
/**
|
|
466
|
-
* 创建 Polygon。
|
|
467
|
-
* - 传入 `Position[]`:视为单个外环,自动包装为 `[ring]`
|
|
468
|
-
* - 传入 `Position[][]`:视为完整的环列表(外环 + 内环/空洞)
|
|
469
|
-
*/
|
|
470
422
|
createPolygon(coordinates) {
|
|
471
423
|
const rings = isPosition(coordinates[0])
|
|
472
|
-
? [coordinates]
|
|
473
|
-
: coordinates;
|
|
424
|
+
? [coordinates]
|
|
425
|
+
: coordinates;
|
|
474
426
|
return { type: 'Polygon', coordinates: rings };
|
|
475
427
|
}
|
|
476
|
-
/**
|
|
477
|
-
* 创建 MultiPoint。
|
|
478
|
-
* - 传入 `Position`:视为单个点,自动包装为 `[point]`
|
|
479
|
-
* - 传入 `Position[]`:视为多个点
|
|
480
|
-
*/
|
|
481
428
|
createMultiPoint(coordinates) {
|
|
482
429
|
const pts = isPosition(coordinates)
|
|
483
|
-
? [coordinates]
|
|
484
|
-
: coordinates;
|
|
430
|
+
? [coordinates]
|
|
431
|
+
: coordinates;
|
|
485
432
|
return { type: 'MultiPoint', coordinates: pts };
|
|
486
433
|
}
|
|
487
|
-
/**
|
|
488
|
-
* 创建 MultiLineString。
|
|
489
|
-
* - 传入 `Position[]`:视为单条线,自动包装为 `[line]`
|
|
490
|
-
* - 传入 `Position[][]`:视为多条线
|
|
491
|
-
*/
|
|
492
434
|
createMultiLineString(coordinates) {
|
|
493
435
|
const lines = isPosition(coordinates[0])
|
|
494
|
-
? [coordinates]
|
|
495
|
-
: coordinates;
|
|
436
|
+
? [coordinates]
|
|
437
|
+
: coordinates;
|
|
496
438
|
return { type: 'MultiLineString', coordinates: lines };
|
|
497
439
|
}
|
|
498
|
-
/**
|
|
499
|
-
* 创建 MultiPolygon。
|
|
500
|
-
* - 传入 `Position[][]`:视为单个多边形(环列表),自动包装为 `[polygon]`
|
|
501
|
-
* - 传入 `Position[][][]`:视为多个多边形
|
|
502
|
-
*/
|
|
503
440
|
createMultiPolygon(coordinates) {
|
|
504
|
-
// 判断:若第一个元素是 Position[](环),则整体是单个 polygon
|
|
505
441
|
const firstElem = coordinates[0];
|
|
506
442
|
const isSinglePolygon = Array.isArray(firstElem) && Array.isArray(firstElem[0]) && isPosition(firstElem[0]);
|
|
507
443
|
const polys = isSinglePolygon
|
|
508
|
-
? [coordinates]
|
|
509
|
-
: coordinates;
|
|
444
|
+
? [coordinates]
|
|
445
|
+
: coordinates;
|
|
510
446
|
return { type: 'MultiPolygon', coordinates: polys };
|
|
511
447
|
}
|
|
512
|
-
/**
|
|
513
|
-
* 创建 GeometryCollection。
|
|
514
|
-
* - 传入单个 `Geometry`:自动包装为 `[geometry]`
|
|
515
|
-
* - 传入 `Geometry[]`:直接使用
|
|
516
|
-
*/
|
|
517
448
|
createGeometryCollection(geometries) {
|
|
518
449
|
const geoms = Array.isArray(geometries) ? geometries : [geometries];
|
|
519
450
|
return { type: 'GeometryCollection', geometries: geoms };
|
|
520
451
|
}
|
|
521
452
|
}
|
|
522
|
-
// 单例,避免重复实例化
|
|
523
453
|
const _builder = new GeoJSONBuilder();
|
|
524
|
-
/** 创建 Point */
|
|
525
454
|
function createPoint(x, y, z) {
|
|
526
455
|
return _builder.createPoint(x, y, z);
|
|
527
456
|
}
|
|
528
|
-
/** 创建 LineString */
|
|
529
457
|
function createLineString(coordinates) {
|
|
530
458
|
return _builder.createLineString(coordinates);
|
|
531
459
|
}
|
|
532
|
-
/**
|
|
533
|
-
* 创建 Polygon。
|
|
534
|
-
* - 传入 `Position[]`:单个外环,自动包装
|
|
535
|
-
* - 传入 `Position[][]`:外环 + 内环(空洞)
|
|
536
|
-
*
|
|
537
|
-
* @example
|
|
538
|
-
* createPolygon([[0,0],[1,0],[1,1],[0,1],[0,0]])
|
|
539
|
-
* createPolygon([[[0,0],[10,0],[10,10],[0,10],[0,0]], [[2,2],[4,2],[4,4],[2,4],[2,2]]])
|
|
540
|
-
*/
|
|
541
460
|
function createPolygon(coordinates) {
|
|
542
461
|
return _builder.createPolygon(coordinates);
|
|
543
462
|
}
|
|
544
|
-
/**
|
|
545
|
-
* 创建 MultiPoint。
|
|
546
|
-
* - 传入 `Position`:单个点
|
|
547
|
-
* - 传入 `Position[]`:多个点
|
|
548
|
-
*
|
|
549
|
-
* @example
|
|
550
|
-
* createMultiPoint([0, 0])
|
|
551
|
-
* createMultiPoint([[0,0],[1,1],[2,2]])
|
|
552
|
-
*/
|
|
553
463
|
function createMultiPoint(coordinates) {
|
|
554
464
|
return _builder.createMultiPoint(coordinates);
|
|
555
465
|
}
|
|
556
|
-
/**
|
|
557
|
-
* 创建 MultiLineString。
|
|
558
|
-
* - 传入 `Position[]`:单条线
|
|
559
|
-
* - 传入 `Position[][]`:多条线
|
|
560
|
-
*
|
|
561
|
-
* @example
|
|
562
|
-
* createMultiLineString([[0,0],[1,1]])
|
|
563
|
-
* createMultiLineString([[[0,0],[1,1]], [[2,2],[3,3]]])
|
|
564
|
-
*/
|
|
565
466
|
function createMultiLineString(coordinates) {
|
|
566
467
|
return _builder.createMultiLineString(coordinates);
|
|
567
468
|
}
|
|
568
|
-
/**
|
|
569
|
-
* 创建 MultiPolygon。
|
|
570
|
-
* - 传入 `Position[][]`:单个多边形(环列表)
|
|
571
|
-
* - 传入 `Position[][][]`:多个多边形
|
|
572
|
-
*
|
|
573
|
-
* @example
|
|
574
|
-
* createMultiPolygon([[[0,0],[1,0],[1,1],[0,1],[0,0]]])
|
|
575
|
-
* createMultiPolygon([[[[0,0],[1,0],[1,1],[0,1],[0,0]]], [[[2,2],[3,2],[3,3],[2,3],[2,2]]]])
|
|
576
|
-
*/
|
|
577
469
|
function createMultiPolygon(coordinates) {
|
|
578
470
|
return _builder.createMultiPolygon(coordinates);
|
|
579
471
|
}
|
|
580
|
-
/**
|
|
581
|
-
* 创建 GeometryCollection。
|
|
582
|
-
* - 传入单个 `Geometry`:自动包装
|
|
583
|
-
* - 传入 `Geometry[]`:直接使用
|
|
584
|
-
*
|
|
585
|
-
* @example
|
|
586
|
-
* createGeometryCollection(createPoint(0, 0))
|
|
587
|
-
* createGeometryCollection([createPoint(0,0), createLineString([[0,0],[1,1]])])
|
|
588
|
-
*/
|
|
589
472
|
function createGeometryCollection(geometries) {
|
|
590
473
|
return _builder.createGeometryCollection(geometries);
|
|
591
474
|
}
|
|
@@ -602,30 +485,9 @@ var geojsonBuilder = /*#__PURE__*/Object.freeze({
|
|
|
602
485
|
createPolygon: createPolygon
|
|
603
486
|
});
|
|
604
487
|
|
|
605
|
-
/**
|
|
606
|
-
* 将 WKT 字符串转换为 GeoJSON Geometry 对象。
|
|
607
|
-
*
|
|
608
|
-
* @example
|
|
609
|
-
* wktToGeoJSON('POINT (30.5 40.5)')
|
|
610
|
-
* // → { type: 'Point', coordinates: [30.5, 40.5] }
|
|
611
|
-
*
|
|
612
|
-
* wktToGeoJSON('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))')
|
|
613
|
-
* // → { type: 'Polygon', coordinates: [[[0,0],[1,0],[1,1],[0,1],[0,0]]] }
|
|
614
|
-
*/
|
|
615
488
|
function wktToGeoJSON(wkt) {
|
|
616
489
|
return parse(wkt);
|
|
617
490
|
}
|
|
618
|
-
/**
|
|
619
|
-
* 将 WKT 字符串转换为 GeoJSON Feature 对象。
|
|
620
|
-
*
|
|
621
|
-
* @param wkt WKT 字符串
|
|
622
|
-
* @param properties 可选的 Feature 属性对象
|
|
623
|
-
* @param id 可选的 Feature ID
|
|
624
|
-
*
|
|
625
|
-
* @example
|
|
626
|
-
* wktToFeature('POINT (30.5 40.5)', { name: '北京' })
|
|
627
|
-
* // → { type: 'Feature', geometry: { type: 'Point', ... }, properties: { name: '北京' } }
|
|
628
|
-
*/
|
|
629
491
|
function wktToFeature(wkt, properties = null, id) {
|
|
630
492
|
const geometry = parse(wkt);
|
|
631
493
|
const feature = {
|
|
@@ -638,16 +500,6 @@ function wktToFeature(wkt, properties = null, id) {
|
|
|
638
500
|
}
|
|
639
501
|
return feature;
|
|
640
502
|
}
|
|
641
|
-
/**
|
|
642
|
-
* 将多个 WKT 字符串批量转换为 GeoJSON FeatureCollection。
|
|
643
|
-
*
|
|
644
|
-
* @param wkts WKT 字符串数组
|
|
645
|
-
* @param properties 可选,每个 Feature 的属性数组(长度应与 wkts 一致)
|
|
646
|
-
*
|
|
647
|
-
* @example
|
|
648
|
-
* wktToFeatureCollection(['POINT (0 0)', 'POINT (1 1)'])
|
|
649
|
-
* // → { type: 'FeatureCollection', features: [...] }
|
|
650
|
-
*/
|
|
651
503
|
function wktToFeatureCollection(wkts, properties) {
|
|
652
504
|
const features = wkts.map((wkt, i) => wktToFeature(wkt, properties ? (properties[i] ?? null) : null));
|
|
653
505
|
return { type: 'FeatureCollection', features };
|
|
@@ -660,43 +512,15 @@ var wktToGeojson = /*#__PURE__*/Object.freeze({
|
|
|
660
512
|
wktToGeoJSON: wktToGeoJSON
|
|
661
513
|
});
|
|
662
514
|
|
|
663
|
-
/**
|
|
664
|
-
* 将 GeoJSON Geometry 对象转换为 WKT 字符串。
|
|
665
|
-
*
|
|
666
|
-
* @example
|
|
667
|
-
* geojsonToWkt({ type: 'Point', coordinates: [30.5, 40.5] })
|
|
668
|
-
* // → 'POINT (30.5 40.5)'
|
|
669
|
-
*
|
|
670
|
-
* geojsonToWkt({ type: 'Polygon', coordinates: [[[0,0],[1,0],[1,1],[0,1],[0,0]]] })
|
|
671
|
-
* // → 'POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))'
|
|
672
|
-
*/
|
|
673
515
|
function geojsonToWkt(geojson) {
|
|
674
516
|
return build(geojson);
|
|
675
517
|
}
|
|
676
|
-
/**
|
|
677
|
-
* 将 GeoJSON Feature 对象转换为 WKT 字符串(取 geometry 部分)。
|
|
678
|
-
*
|
|
679
|
-
* @throws 若 Feature.geometry 为 null,则抛出错误
|
|
680
|
-
*
|
|
681
|
-
* @example
|
|
682
|
-
* featureToWkt({ type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: null })
|
|
683
|
-
* // → 'POINT (0 0)'
|
|
684
|
-
*/
|
|
685
518
|
function featureToWkt(feature) {
|
|
686
519
|
if (!feature.geometry) {
|
|
687
520
|
throw new Error('Feature.geometry is null, cannot convert to WKT');
|
|
688
521
|
}
|
|
689
522
|
return build(feature.geometry);
|
|
690
523
|
}
|
|
691
|
-
/**
|
|
692
|
-
* 将 GeoJSON FeatureCollection 中所有 Feature 转换为 WKT 字符串数组。
|
|
693
|
-
*
|
|
694
|
-
* geometry 为 null 的 Feature 会被跳过(返回数组中对应位置为 null)。
|
|
695
|
-
*
|
|
696
|
-
* @example
|
|
697
|
-
* featureCollectionToWkt({ type: 'FeatureCollection', features: [...] })
|
|
698
|
-
* // → ['POINT (0 0)', 'LINESTRING (0 0, 1 1)', ...]
|
|
699
|
-
*/
|
|
700
524
|
function featureCollectionToWkt(fc) {
|
|
701
525
|
return fc.features.map((f) => {
|
|
702
526
|
if (!f.geometry)
|
|
@@ -712,15 +536,12 @@ var geojsonToWkt$1 = /*#__PURE__*/Object.freeze({
|
|
|
712
536
|
geojsonToWkt: geojsonToWkt
|
|
713
537
|
});
|
|
714
538
|
|
|
715
|
-
// 预定义常量,避免重复创建
|
|
716
539
|
const VALID_GEOMETRY_TYPES = [
|
|
717
540
|
'Point', 'LineString', 'Polygon',
|
|
718
541
|
'MultiPoint', 'MultiLineString', 'MultiPolygon',
|
|
719
542
|
'GeometryCollection'
|
|
720
543
|
];
|
|
721
|
-
|
|
722
|
-
* 校验 WKT 字符串格式是否合法
|
|
723
|
-
*/
|
|
544
|
+
const VALID_TYPES_MESSAGE = `Invalid geometry type. Must be one of: ${VALID_GEOMETRY_TYPES.join(', ')}`;
|
|
724
545
|
function validateWKT(wkt) {
|
|
725
546
|
if (!wkt || typeof wkt !== 'string') {
|
|
726
547
|
return { valid: false, error: 'WKT must be a non-empty string' };
|
|
@@ -737,23 +558,18 @@ function validateWKT(wkt) {
|
|
|
737
558
|
return { valid: false, error: e.message };
|
|
738
559
|
}
|
|
739
560
|
}
|
|
740
|
-
/**
|
|
741
|
-
* 校验 GeoJSON Geometry 对象是否合法
|
|
742
|
-
*/
|
|
743
561
|
function validateGeoJSON(geojson) {
|
|
744
562
|
if (!geojson || typeof geojson !== 'object') {
|
|
745
563
|
return { valid: false, error: 'GeoJSON must be an object' };
|
|
746
564
|
}
|
|
747
565
|
const obj = geojson;
|
|
748
|
-
// 检查 type 字段
|
|
749
566
|
if (!obj.type || typeof obj.type !== 'string') {
|
|
750
567
|
return { valid: false, error: 'GeoJSON must have a "type" property' };
|
|
751
568
|
}
|
|
752
569
|
const type = obj.type;
|
|
753
570
|
if (!VALID_GEOMETRY_TYPES.includes(type)) {
|
|
754
|
-
return { valid: false, error:
|
|
571
|
+
return { valid: false, error: VALID_TYPES_MESSAGE };
|
|
755
572
|
}
|
|
756
|
-
// GeometryCollection 特殊处理
|
|
757
573
|
if (type === 'GeometryCollection') {
|
|
758
574
|
if (!obj.geometries || !Array.isArray(obj.geometries)) {
|
|
759
575
|
return { valid: false, error: 'GeometryCollection must have a "geometries" array' };
|
|
@@ -766,11 +582,9 @@ function validateGeoJSON(geojson) {
|
|
|
766
582
|
}
|
|
767
583
|
return { valid: true };
|
|
768
584
|
}
|
|
769
|
-
// 其他几何类型必须要有 coordinates
|
|
770
585
|
if (obj.coordinates === undefined) {
|
|
771
586
|
return { valid: false, error: `${type} must have "coordinates"` };
|
|
772
587
|
}
|
|
773
|
-
// 校验坐标格式
|
|
774
588
|
return validateCoordinates(type, obj.coordinates);
|
|
775
589
|
}
|
|
776
590
|
function validateCoordinates(type, coords) {
|
|
@@ -839,34 +653,27 @@ function validatePosition(pos) {
|
|
|
839
653
|
return { valid: false, error: `Position must have 2 or 3 coordinates, got ${pos.length}` };
|
|
840
654
|
}
|
|
841
655
|
for (let i = 0; i < pos.length; i++) {
|
|
842
|
-
if (typeof pos[i] !== 'number' ||
|
|
656
|
+
if (typeof pos[i] !== 'number' || !Number.isFinite(pos[i])) {
|
|
843
657
|
return { valid: false, error: `Position[${i}] must be a valid number` };
|
|
844
658
|
}
|
|
845
659
|
}
|
|
846
660
|
return { valid: true };
|
|
847
661
|
}
|
|
848
|
-
/**
|
|
849
|
-
* 尝试从可能不规范的 WKT 中恢复出有效结果
|
|
850
|
-
* 主要处理尾部多余字符的情况
|
|
851
|
-
*/
|
|
852
662
|
function tryFixWKT(wkt) {
|
|
853
663
|
const trimmed = wkt.trim();
|
|
854
664
|
if (!trimmed) {
|
|
855
665
|
return { fixed: wkt, changed: false };
|
|
856
666
|
}
|
|
857
|
-
// 先尝试直接解析,如果成功则不需要修复
|
|
858
667
|
try {
|
|
859
668
|
parse(trimmed);
|
|
860
669
|
return { fixed: trimmed, changed: false };
|
|
861
670
|
}
|
|
862
671
|
catch {
|
|
863
|
-
// 解析失败,尝试修复
|
|
864
672
|
}
|
|
865
|
-
// 尝试找到最后一个有效的 geometry 结束位置
|
|
866
673
|
const patterns = [
|
|
867
|
-
/\)\s*[A-Z]/i,
|
|
868
|
-
/EMPTY\s+[A-Z]/i,
|
|
869
|
-
/\)\s*$/,
|
|
674
|
+
/\)\s*[A-Z]/i,
|
|
675
|
+
/EMPTY\s+[A-Z]/i,
|
|
676
|
+
/\)\s*$/,
|
|
870
677
|
];
|
|
871
678
|
for (const pattern of patterns) {
|
|
872
679
|
const match = trimmed.match(pattern);
|
|
@@ -877,11 +684,9 @@ function tryFixWKT(wkt) {
|
|
|
877
684
|
return { fixed, changed: true };
|
|
878
685
|
}
|
|
879
686
|
catch {
|
|
880
|
-
// 这个修复方案不行,尝试下一个
|
|
881
687
|
}
|
|
882
688
|
}
|
|
883
689
|
}
|
|
884
|
-
// 尝试去除尾部垃圾字符
|
|
885
690
|
const lastValidIndex = findLastValidPosition(trimmed);
|
|
886
691
|
if (lastValidIndex > 0) {
|
|
887
692
|
const fixed = trimmed.slice(0, lastValidIndex + 1);
|
|
@@ -890,7 +695,6 @@ function tryFixWKT(wkt) {
|
|
|
890
695
|
return { fixed, changed: true };
|
|
891
696
|
}
|
|
892
697
|
catch {
|
|
893
|
-
// 修复失败
|
|
894
698
|
}
|
|
895
699
|
}
|
|
896
700
|
return { fixed: wkt, changed: false };
|
|
@@ -904,7 +708,6 @@ function findLastValidPosition(wkt) {
|
|
|
904
708
|
else if (c === '(')
|
|
905
709
|
depth--;
|
|
906
710
|
else if (c === ' ' && depth === 0 && /[A-Z]/.test(wkt.slice(i + 1))) {
|
|
907
|
-
// 如果空格后面是字母开头,可能是垃圾字符的起点
|
|
908
711
|
if (wkt.slice(0, i).trimEnd().match(/[A-Z]\s*$/)) {
|
|
909
712
|
return i - 1;
|
|
910
713
|
}
|
|
@@ -912,19 +715,12 @@ function findLastValidPosition(wkt) {
|
|
|
912
715
|
}
|
|
913
716
|
return wkt.length - 1;
|
|
914
717
|
}
|
|
915
|
-
/**
|
|
916
|
-
* 深度克隆 GeoJSON 对象(用于避免意外修改原对象)
|
|
917
|
-
*/
|
|
918
718
|
function cloneGeometry(geometry) {
|
|
919
719
|
return JSON.parse(JSON.stringify(geometry));
|
|
920
720
|
}
|
|
921
|
-
/**
|
|
922
|
-
* 判断两个几何对象是否相等(坐标对比)
|
|
923
|
-
*/
|
|
924
721
|
function geometryEquals(a, b) {
|
|
925
722
|
if (a.type !== b.type)
|
|
926
723
|
return false;
|
|
927
|
-
// Point 比较最常见,单独优化
|
|
928
724
|
if (a.type === 'Point') {
|
|
929
725
|
const aCoords = a.coordinates;
|
|
930
726
|
const bCoords = b.coordinates;
|
|
@@ -933,7 +729,6 @@ function geometryEquals(a, b) {
|
|
|
933
729
|
aCoords[1] === bCoords[1] &&
|
|
934
730
|
(aCoords.length === 2 || aCoords[2] === bCoords[2]);
|
|
935
731
|
}
|
|
936
|
-
// 其他类型使用 JSON.stringify 比较
|
|
937
732
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
938
733
|
}
|
|
939
734
|
|
|
@@ -946,9 +741,6 @@ var validate = /*#__PURE__*/Object.freeze({
|
|
|
946
741
|
validateWKT: validateWKT
|
|
947
742
|
});
|
|
948
743
|
|
|
949
|
-
// 命名空间导出 - 所有公共 API 汇总
|
|
950
|
-
// 使用方式: import WKT from 'wkt-parse-and-geojson';
|
|
951
|
-
// WKT.parse(...), WKT.build(...), etc.
|
|
952
744
|
const WKT = {
|
|
953
745
|
...types,
|
|
954
746
|
...wktParser,
|