wkt-parse-and-geojson 1.0.3 → 1.0.5
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/README.md +88 -5
- package/dist/index.cjs.js +331 -15
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +326 -16
- package/dist/index.umd.js +331 -15
- package/dist/namespace.d.ts +34 -0
- package/dist/validate.d.ts +29 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [featureToWkt(feature)](#7-featuretowktfeature)
|
|
22
22
|
- [featureCollectionToWkt(fc)](#8-featurecollectiontowktfc)
|
|
23
23
|
- [GeoJSON 工厂方法](#9-geojson-工厂方法)
|
|
24
|
+
- [校验工具](#10-校验工具)
|
|
24
25
|
- [类型定义](#类型定义)
|
|
25
26
|
- [注意事项与边界行为](#注意事项与边界行为)
|
|
26
27
|
- [本地调试](#本地调试)
|
|
@@ -82,8 +83,12 @@ npm install wkt-parse-and-geojson
|
|
|
82
83
|
// CommonJS
|
|
83
84
|
const { parse, build, wktToFeature } = require('wkt-parse-and-geojson');
|
|
84
85
|
|
|
85
|
-
// ES Module
|
|
86
|
+
// ES Module - 按需导入
|
|
86
87
|
import { parse, build, wktToFeature } from 'wkt-parse-and-geojson';
|
|
88
|
+
|
|
89
|
+
// ES Module - 命名空间导入(避免命名冲突)
|
|
90
|
+
import WKT from 'wkt-parse-and-geojson';
|
|
91
|
+
const geom = WKT.parse('POINT (116.39 39.91)');
|
|
87
92
|
```
|
|
88
93
|
|
|
89
94
|
### 浏览器 (script 标签)
|
|
@@ -92,13 +97,13 @@ import { parse, build, wktToFeature } from 'wkt-parse-and-geojson';
|
|
|
92
97
|
<!-- UMD 方式:通过 script 标签直接引入,全局变量 WKTGeoJSON -->
|
|
93
98
|
<script src="https://unpkg.com/wkt-parse-and-geojson/dist/index.umd.js"></script>
|
|
94
99
|
<script>
|
|
100
|
+
// 方式一:使用命名空间(推荐,避免命名冲突)
|
|
95
101
|
const geom = WKTGeoJSON.parse('POINT (116.39 39.91)');
|
|
96
102
|
console.log(geom);
|
|
97
|
-
// → { type: 'Point', coordinates: [116.39, 39.91] }
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
// 方式二:解构赋值(需注意命名冲突)
|
|
105
|
+
const { parse, build } = WKTGeoJSON;
|
|
106
|
+
const wkt = build(geom);
|
|
102
107
|
</script>
|
|
103
108
|
```
|
|
104
109
|
|
|
@@ -480,6 +485,84 @@ createGeometryCollection([
|
|
|
480
485
|
|
|
481
486
|
---
|
|
482
487
|
|
|
488
|
+
## 10. 校验工具
|
|
489
|
+
|
|
490
|
+
### `validateWKT(wkt)`
|
|
491
|
+
|
|
492
|
+
校验 WKT 字符串格式是否合法。
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
import { validateWKT } from 'wkt-parse-and-geojson';
|
|
496
|
+
|
|
497
|
+
validateWKT('POINT (30.5 40.5)')
|
|
498
|
+
// → { valid: true }
|
|
499
|
+
|
|
500
|
+
validateWKT('POINT EMPTY')
|
|
501
|
+
// → { valid: false, error: 'POINT EMPTY cannot be represented...' }
|
|
502
|
+
|
|
503
|
+
validateWKT('INVALID WKT')
|
|
504
|
+
// → { valid: false, error: 'Unknown geometry type: INVALID' }
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### `validateGeoJSON(geojson)`
|
|
508
|
+
|
|
509
|
+
校验 GeoJSON Geometry 对象是否合法。
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
import { validateGeoJSON } from 'wkt-parse-and-geojson';
|
|
513
|
+
|
|
514
|
+
validateGeoJSON({ type: 'Point', coordinates: [30.5, 40.5] })
|
|
515
|
+
// → { valid: true }
|
|
516
|
+
|
|
517
|
+
validateGeoJSON({ type: 'Point' })
|
|
518
|
+
// → { valid: false, error: 'Point must have "coordinates"' }
|
|
519
|
+
|
|
520
|
+
validateGeoJSON({ type: 'InvalidType', coordinates: [] })
|
|
521
|
+
// → { valid: false, error: 'Invalid geometry type: InvalidType...' }
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### `tryFixWKT(wkt)`
|
|
525
|
+
|
|
526
|
+
尝试修复不规范的 WKT 字符串(处理尾部多余字符)。
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
import { tryFixWKT } from 'wkt-parse-and-geojson';
|
|
530
|
+
|
|
531
|
+
tryFixWKT('POINT (30.5 40.5) garbage')
|
|
532
|
+
// → { fixed: 'POINT (30.5 40.5)', changed: true }
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### `cloneGeometry(geometry)`
|
|
536
|
+
|
|
537
|
+
深度克隆几何对象,避免意外修改原对象。
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
import { cloneGeometry } from 'wkt-parse-and-geojson';
|
|
541
|
+
|
|
542
|
+
const original = { type: 'Point', coordinates: [0, 0] };
|
|
543
|
+
const cloned = cloneGeometry(original);
|
|
544
|
+
cloned.coordinates[0] = 100;
|
|
545
|
+
// original.coordinates[0] === 0 (未改变)
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### `geometryEquals(a, b)`
|
|
549
|
+
|
|
550
|
+
判断两个几何对象是否相等。
|
|
551
|
+
|
|
552
|
+
```javascript
|
|
553
|
+
import { geometryEquals } from 'wkt-parse-and-geojson';
|
|
554
|
+
|
|
555
|
+
const a = { type: 'Point', coordinates: [30.5, 40.5] };
|
|
556
|
+
const b = { type: 'Point', coordinates: [30.5, 40.5] };
|
|
557
|
+
geometryEquals(a, b)
|
|
558
|
+
// → true
|
|
559
|
+
|
|
560
|
+
geometryEquals(a, { type: 'Point', coordinates: [0, 0] })
|
|
561
|
+
// → false
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
483
566
|
## 类型定义
|
|
484
567
|
|
|
485
568
|
```typescript
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var types = /*#__PURE__*/Object.freeze({
|
|
4
|
+
__proto__: null
|
|
5
|
+
});
|
|
6
|
+
|
|
3
7
|
// Precompiled regex for better performance
|
|
4
8
|
const RE_WHITESPACE = /\s/;
|
|
5
9
|
const RE_NUMBER_START = /[0-9\-]/;
|
|
@@ -316,16 +320,21 @@ function parse(wkt) {
|
|
|
316
320
|
return parser.parse(wkt);
|
|
317
321
|
}
|
|
318
322
|
|
|
323
|
+
var wktParser = /*#__PURE__*/Object.freeze({
|
|
324
|
+
__proto__: null,
|
|
325
|
+
WKTParser: WKTParser,
|
|
326
|
+
parse: parse
|
|
327
|
+
});
|
|
328
|
+
|
|
319
329
|
/**
|
|
320
330
|
* 将坐标数值格式化为字符串,避免科学计数法(WKT 不支持)。
|
|
321
331
|
* 例:1e-7 → "0.0000001",1.50000 → "1.5",1.0 → "1"
|
|
322
332
|
*/
|
|
323
333
|
function formatNumber(v) {
|
|
324
|
-
// 有小数则最多保留 15 位有效位,再去掉尾零
|
|
325
334
|
if (v % 1 !== 0) {
|
|
326
|
-
return
|
|
335
|
+
return Number(v.toFixed(15)).toString();
|
|
327
336
|
}
|
|
328
|
-
return v
|
|
337
|
+
return String(v);
|
|
329
338
|
}
|
|
330
339
|
function positionToWkt(pos) {
|
|
331
340
|
return pos.map(formatNumber).join(' ');
|
|
@@ -333,6 +342,28 @@ function positionToWkt(pos) {
|
|
|
333
342
|
function coordsToWkt(coords) {
|
|
334
343
|
return coords.map(positionToWkt).join(', ');
|
|
335
344
|
}
|
|
345
|
+
// 检查坐标是否包含 Z(3个分量)
|
|
346
|
+
function hasZ(coordinates) {
|
|
347
|
+
if (!Array.isArray(coordinates))
|
|
348
|
+
return false;
|
|
349
|
+
if (coordinates.length === 0)
|
|
350
|
+
return false;
|
|
351
|
+
const first = coordinates[0];
|
|
352
|
+
if (Array.isArray(first)) {
|
|
353
|
+
if (typeof first[0] === 'number') {
|
|
354
|
+
return first.length === 3;
|
|
355
|
+
}
|
|
356
|
+
if (Array.isArray(first[0])) {
|
|
357
|
+
const firstRing = first;
|
|
358
|
+
return firstRing.length > 0 && firstRing[0].length === 3;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
// 获取 Z 后缀字符串
|
|
364
|
+
function zSuffix(coordinates) {
|
|
365
|
+
return hasZ(coordinates) ? ' Z' : '';
|
|
366
|
+
}
|
|
336
367
|
class WKTBuilder {
|
|
337
368
|
build(geometry) {
|
|
338
369
|
switch (geometry.type) {
|
|
@@ -355,21 +386,18 @@ class WKTBuilder {
|
|
|
355
386
|
}
|
|
356
387
|
}
|
|
357
388
|
buildPoint(geom) {
|
|
358
|
-
|
|
359
|
-
return `POINT${hasZ ? ' Z' : ''} (${positionToWkt(geom.coordinates)})`;
|
|
389
|
+
return `POINT${zSuffix(geom.coordinates)} (${positionToWkt(geom.coordinates)})`;
|
|
360
390
|
}
|
|
361
391
|
buildLineString(geom) {
|
|
362
392
|
if (geom.coordinates.length === 0)
|
|
363
393
|
return 'LINESTRING EMPTY';
|
|
364
|
-
|
|
365
|
-
return `LINESTRING${hasZ ? ' Z' : ''} (${coordsToWkt(geom.coordinates)})`;
|
|
394
|
+
return `LINESTRING${zSuffix(geom.coordinates)} (${coordsToWkt(geom.coordinates)})`;
|
|
366
395
|
}
|
|
367
396
|
buildPolygon(geom) {
|
|
368
397
|
if (geom.coordinates.length === 0)
|
|
369
398
|
return 'POLYGON EMPTY';
|
|
370
|
-
const hasZ = geom.coordinates[0].length > 0 && geom.coordinates[0][0].length === 3;
|
|
371
399
|
const ringStr = geom.coordinates.map(ring => `(${coordsToWkt(ring)})`).join(', ');
|
|
372
|
-
return `POLYGON${
|
|
400
|
+
return `POLYGON${zSuffix(geom.coordinates)} (${ringStr})`;
|
|
373
401
|
}
|
|
374
402
|
/**
|
|
375
403
|
* 按 OGC/ISO WKT 标准,MULTIPOINT 每个点用括号包裹:
|
|
@@ -378,26 +406,23 @@ class WKTBuilder {
|
|
|
378
406
|
buildMultiPoint(geom) {
|
|
379
407
|
if (geom.coordinates.length === 0)
|
|
380
408
|
return 'MULTIPOINT EMPTY';
|
|
381
|
-
const hasZ = geom.coordinates[0].length === 3;
|
|
382
409
|
const pts = geom.coordinates.map(p => `(${positionToWkt(p)})`).join(', ');
|
|
383
|
-
return `MULTIPOINT${
|
|
410
|
+
return `MULTIPOINT${zSuffix(geom.coordinates)} (${pts})`;
|
|
384
411
|
}
|
|
385
412
|
buildMultiLineString(geom) {
|
|
386
413
|
if (geom.coordinates.length === 0)
|
|
387
414
|
return 'MULTILINESTRING EMPTY';
|
|
388
|
-
const hasZ = geom.coordinates[0].length > 0 && geom.coordinates[0][0].length === 3;
|
|
389
415
|
const lines = geom.coordinates.map(line => `(${coordsToWkt(line)})`).join(', ');
|
|
390
|
-
return `MULTILINESTRING${
|
|
416
|
+
return `MULTILINESTRING${zSuffix(geom.coordinates)} (${lines})`;
|
|
391
417
|
}
|
|
392
418
|
buildMultiPolygon(geom) {
|
|
393
419
|
if (geom.coordinates.length === 0)
|
|
394
420
|
return 'MULTIPOLYGON EMPTY';
|
|
395
|
-
const hasZ = geom.coordinates[0].length > 0 && geom.coordinates[0][0].length > 0 && geom.coordinates[0][0][0].length === 3;
|
|
396
421
|
const polys = geom.coordinates.map(poly => {
|
|
397
422
|
const rings = poly.map(ring => `(${coordsToWkt(ring)})`).join(', ');
|
|
398
423
|
return `(${rings})`;
|
|
399
424
|
}).join(', ');
|
|
400
|
-
return `MULTIPOLYGON${
|
|
425
|
+
return `MULTIPOLYGON${zSuffix(geom.coordinates)} (${polys})`;
|
|
401
426
|
}
|
|
402
427
|
buildGeometryCollection(geom) {
|
|
403
428
|
if (geom.geometries.length === 0)
|
|
@@ -411,6 +436,12 @@ function build(geometry) {
|
|
|
411
436
|
return new WKTBuilder().build(geometry);
|
|
412
437
|
}
|
|
413
438
|
|
|
439
|
+
var wktBuilder = /*#__PURE__*/Object.freeze({
|
|
440
|
+
__proto__: null,
|
|
441
|
+
WKTBuilder: WKTBuilder,
|
|
442
|
+
build: build
|
|
443
|
+
});
|
|
444
|
+
|
|
414
445
|
// ─── 内部工具:判断是否为 Position([number, number] 或 [number, number, number])
|
|
415
446
|
function isPosition(v) {
|
|
416
447
|
return (Array.isArray(v) &&
|
|
@@ -557,6 +588,18 @@ function createGeometryCollection(geometries) {
|
|
|
557
588
|
return _builder.createGeometryCollection(geometries);
|
|
558
589
|
}
|
|
559
590
|
|
|
591
|
+
var geojsonBuilder = /*#__PURE__*/Object.freeze({
|
|
592
|
+
__proto__: null,
|
|
593
|
+
GeoJSONBuilder: GeoJSONBuilder,
|
|
594
|
+
createGeometryCollection: createGeometryCollection,
|
|
595
|
+
createLineString: createLineString,
|
|
596
|
+
createMultiLineString: createMultiLineString,
|
|
597
|
+
createMultiPoint: createMultiPoint,
|
|
598
|
+
createMultiPolygon: createMultiPolygon,
|
|
599
|
+
createPoint: createPoint,
|
|
600
|
+
createPolygon: createPolygon
|
|
601
|
+
});
|
|
602
|
+
|
|
560
603
|
/**
|
|
561
604
|
* 将 WKT 字符串转换为 GeoJSON Geometry 对象。
|
|
562
605
|
*
|
|
@@ -608,6 +651,13 @@ function wktToFeatureCollection(wkts, properties) {
|
|
|
608
651
|
return { type: 'FeatureCollection', features };
|
|
609
652
|
}
|
|
610
653
|
|
|
654
|
+
var wktToGeojson = /*#__PURE__*/Object.freeze({
|
|
655
|
+
__proto__: null,
|
|
656
|
+
wktToFeature: wktToFeature,
|
|
657
|
+
wktToFeatureCollection: wktToFeatureCollection,
|
|
658
|
+
wktToGeoJSON: wktToGeoJSON
|
|
659
|
+
});
|
|
660
|
+
|
|
611
661
|
/**
|
|
612
662
|
* 将 GeoJSON Geometry 对象转换为 WKT 字符串。
|
|
613
663
|
*
|
|
@@ -653,10 +703,272 @@ function featureCollectionToWkt(fc) {
|
|
|
653
703
|
});
|
|
654
704
|
}
|
|
655
705
|
|
|
706
|
+
var geojsonToWkt$1 = /*#__PURE__*/Object.freeze({
|
|
707
|
+
__proto__: null,
|
|
708
|
+
featureCollectionToWkt: featureCollectionToWkt,
|
|
709
|
+
featureToWkt: featureToWkt,
|
|
710
|
+
geojsonToWkt: geojsonToWkt
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 校验 WKT 字符串格式是否合法
|
|
715
|
+
*/
|
|
716
|
+
function validateWKT(wkt) {
|
|
717
|
+
if (!wkt || typeof wkt !== 'string') {
|
|
718
|
+
return { valid: false, error: 'WKT must be a non-empty string' };
|
|
719
|
+
}
|
|
720
|
+
const trimmed = wkt.trim();
|
|
721
|
+
if (trimmed.length === 0) {
|
|
722
|
+
return { valid: false, error: 'WKT cannot be empty' };
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
parse(wkt);
|
|
726
|
+
return { valid: true };
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
return { valid: false, error: e.message };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* 校验 GeoJSON Geometry 对象是否合法
|
|
734
|
+
*/
|
|
735
|
+
function validateGeoJSON(geojson) {
|
|
736
|
+
if (!geojson || typeof geojson !== 'object') {
|
|
737
|
+
return { valid: false, error: 'GeoJSON must be an object' };
|
|
738
|
+
}
|
|
739
|
+
const obj = geojson;
|
|
740
|
+
// 检查 type 字段
|
|
741
|
+
if (!obj.type || typeof obj.type !== 'string') {
|
|
742
|
+
return { valid: false, error: 'GeoJSON must have a "type" property' };
|
|
743
|
+
}
|
|
744
|
+
const type = obj.type;
|
|
745
|
+
const validTypes = [
|
|
746
|
+
'Point', 'LineString', 'Polygon',
|
|
747
|
+
'MultiPoint', 'MultiLineString', 'MultiPolygon',
|
|
748
|
+
'GeometryCollection'
|
|
749
|
+
];
|
|
750
|
+
if (!validTypes.includes(type)) {
|
|
751
|
+
return { valid: false, error: `Invalid geometry type: "${type}". Must be one of: ${validTypes.join(', ')}` };
|
|
752
|
+
}
|
|
753
|
+
// GeometryCollection 特殊处理
|
|
754
|
+
if (type === 'GeometryCollection') {
|
|
755
|
+
if (!obj.geometries || !Array.isArray(obj.geometries)) {
|
|
756
|
+
return { valid: false, error: 'GeometryCollection must have a "geometries" array' };
|
|
757
|
+
}
|
|
758
|
+
for (let i = 0; i < obj.geometries.length; i++) {
|
|
759
|
+
const result = validateGeoJSON(obj.geometries[i]);
|
|
760
|
+
if (!result.valid) {
|
|
761
|
+
return { valid: false, error: `GeometryCollection[${i}]: ${result.error}` };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return { valid: true };
|
|
765
|
+
}
|
|
766
|
+
// 其他几何类型必须要有 coordinates
|
|
767
|
+
if (obj.coordinates === undefined) {
|
|
768
|
+
return { valid: false, error: `${type} must have "coordinates"` };
|
|
769
|
+
}
|
|
770
|
+
// 校验坐标格式
|
|
771
|
+
return validateCoordinates(type, obj.coordinates);
|
|
772
|
+
}
|
|
773
|
+
function validateCoordinates(type, coords) {
|
|
774
|
+
switch (type) {
|
|
775
|
+
case 'Point':
|
|
776
|
+
return validatePosition(coords);
|
|
777
|
+
case 'LineString':
|
|
778
|
+
case 'MultiPoint':
|
|
779
|
+
if (!Array.isArray(coords)) {
|
|
780
|
+
return { valid: false, error: `${type} coordinates must be an array` };
|
|
781
|
+
}
|
|
782
|
+
for (let i = 0; i < coords.length; i++) {
|
|
783
|
+
const result = validatePosition(coords[i]);
|
|
784
|
+
if (!result.valid) {
|
|
785
|
+
return { valid: false, error: `${type}[${i}]: ${result.error}` };
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return { valid: true };
|
|
789
|
+
case 'Polygon':
|
|
790
|
+
case 'MultiLineString':
|
|
791
|
+
if (!Array.isArray(coords)) {
|
|
792
|
+
return { valid: false, error: `${type} coordinates must be a nested array` };
|
|
793
|
+
}
|
|
794
|
+
for (let i = 0; i < coords.length; i++) {
|
|
795
|
+
if (!Array.isArray(coords[i])) {
|
|
796
|
+
return { valid: false, error: `${type}[${i}] must be an array of positions` };
|
|
797
|
+
}
|
|
798
|
+
for (let j = 0; j < coords[i].length; j++) {
|
|
799
|
+
const result = validatePosition(coords[i][j]);
|
|
800
|
+
if (!result.valid) {
|
|
801
|
+
return { valid: false, error: `${type}[${i}][${j}]: ${result.error}` };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return { valid: true };
|
|
806
|
+
case 'MultiPolygon':
|
|
807
|
+
if (!Array.isArray(coords)) {
|
|
808
|
+
return { valid: false, error: `${type} coordinates must be a deeply nested array` };
|
|
809
|
+
}
|
|
810
|
+
for (let i = 0; i < coords.length; i++) {
|
|
811
|
+
if (!Array.isArray(coords[i])) {
|
|
812
|
+
return { valid: false, error: `${type}[${i}] must be an array of rings` };
|
|
813
|
+
}
|
|
814
|
+
for (let j = 0; j < coords[i].length; j++) {
|
|
815
|
+
if (!Array.isArray(coords[i][j])) {
|
|
816
|
+
return { valid: false, error: `${type}[${i}][${j}] must be an array of positions` };
|
|
817
|
+
}
|
|
818
|
+
for (let k = 0; k < coords[i][j].length; k++) {
|
|
819
|
+
const result = validatePosition(coords[i][j][k]);
|
|
820
|
+
if (!result.valid) {
|
|
821
|
+
return { valid: false, error: `${type}[${i}][${j}][${k}]: ${result.error}` };
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return { valid: true };
|
|
827
|
+
default:
|
|
828
|
+
return { valid: true };
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function validatePosition(pos) {
|
|
832
|
+
if (!Array.isArray(pos)) {
|
|
833
|
+
return { valid: false, error: 'Position must be an array of numbers' };
|
|
834
|
+
}
|
|
835
|
+
if (pos.length < 2 || pos.length > 3) {
|
|
836
|
+
return { valid: false, error: `Position must have 2 or 3 coordinates, got ${pos.length}` };
|
|
837
|
+
}
|
|
838
|
+
for (let i = 0; i < pos.length; i++) {
|
|
839
|
+
if (typeof pos[i] !== 'number' || isNaN(pos[i])) {
|
|
840
|
+
return { valid: false, error: `Position[${i}] must be a valid number` };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return { valid: true };
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* 尝试从可能不规范的 WKT 中恢复出有效结果
|
|
847
|
+
* 主要处理尾部多余字符的情况
|
|
848
|
+
*/
|
|
849
|
+
function tryFixWKT(wkt) {
|
|
850
|
+
const trimmed = wkt.trim();
|
|
851
|
+
if (!trimmed) {
|
|
852
|
+
return { fixed: wkt, changed: false };
|
|
853
|
+
}
|
|
854
|
+
// 先尝试直接解析,如果成功则不需要修复
|
|
855
|
+
try {
|
|
856
|
+
parse(trimmed);
|
|
857
|
+
return { fixed: trimmed, changed: false };
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
// 解析失败,尝试修复
|
|
861
|
+
}
|
|
862
|
+
// 尝试找到最后一个有效的 geometry 结束位置
|
|
863
|
+
const patterns = [
|
|
864
|
+
/\)\s*[A-Z]/i, // 括号后跟字母 (如 POLYGON ((...)) POINT )
|
|
865
|
+
/EMPTY\s+[A-Z]/i, // EMPTY 后跟字母
|
|
866
|
+
/\)\s*$/, // 括号结尾后有多余内容
|
|
867
|
+
];
|
|
868
|
+
for (const pattern of patterns) {
|
|
869
|
+
const match = trimmed.match(pattern);
|
|
870
|
+
if (match) {
|
|
871
|
+
const fixed = trimmed.slice(0, match.index + (match[0].match(/\)/)?.[0].length || 0));
|
|
872
|
+
try {
|
|
873
|
+
parse(fixed);
|
|
874
|
+
return { fixed, changed: true };
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
// 这个修复方案不行,尝试下一个
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
// 尝试去除尾部垃圾字符
|
|
882
|
+
const lastValidIndex = findLastValidPosition(trimmed);
|
|
883
|
+
if (lastValidIndex > 0) {
|
|
884
|
+
const fixed = trimmed.slice(0, lastValidIndex + 1);
|
|
885
|
+
try {
|
|
886
|
+
parse(fixed);
|
|
887
|
+
return { fixed, changed: true };
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
// 修复失败
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return { fixed: wkt, changed: false };
|
|
894
|
+
}
|
|
895
|
+
function findLastValidPosition(wkt) {
|
|
896
|
+
// 从后往前找第一个有效的右括号位置
|
|
897
|
+
let depth = 0;
|
|
898
|
+
for (let i = wkt.length - 1; i >= 0; i--) {
|
|
899
|
+
const c = wkt[i];
|
|
900
|
+
if (c === ')')
|
|
901
|
+
depth++;
|
|
902
|
+
else if (c === '(')
|
|
903
|
+
depth--;
|
|
904
|
+
else if (c === ' ' && depth === 0 && i < wkt.length - 1) {
|
|
905
|
+
// 检查这个空格是否在有效位置
|
|
906
|
+
const afterSpace = wkt.slice(i + 1).trim();
|
|
907
|
+
if (!afterSpace)
|
|
908
|
+
continue;
|
|
909
|
+
if (!/^[A-Z]/.test(afterSpace))
|
|
910
|
+
continue;
|
|
911
|
+
// 如果空格后面是字母开头,可能是垃圾字符
|
|
912
|
+
if (i > 5 && /[A-Z]$/.test(wkt.slice(0, i).trim())) {
|
|
913
|
+
return i - 1;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return wkt.length - 1;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* 深度克隆 GeoJSON 对象(用于避免意外修改原对象)
|
|
921
|
+
*/
|
|
922
|
+
function cloneGeometry(geometry) {
|
|
923
|
+
return JSON.parse(JSON.stringify(geometry));
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* 判断两个几何对象是否相等(坐标对比)
|
|
927
|
+
*/
|
|
928
|
+
function geometryEquals(a, b) {
|
|
929
|
+
if (a.type !== b.type)
|
|
930
|
+
return false;
|
|
931
|
+
// Point 比较最常见,单独优化
|
|
932
|
+
if (a.type === 'Point') {
|
|
933
|
+
const aCoords = a.coordinates;
|
|
934
|
+
const bCoords = b.coordinates;
|
|
935
|
+
return aCoords.length === bCoords.length &&
|
|
936
|
+
aCoords[0] === bCoords[0] &&
|
|
937
|
+
aCoords[1] === bCoords[1] &&
|
|
938
|
+
(aCoords.length === 2 || aCoords[2] === bCoords[2]);
|
|
939
|
+
}
|
|
940
|
+
// 其他类型使用 JSON.stringify(缓存 key 优化可后续添加)
|
|
941
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
var validate = /*#__PURE__*/Object.freeze({
|
|
945
|
+
__proto__: null,
|
|
946
|
+
cloneGeometry: cloneGeometry,
|
|
947
|
+
geometryEquals: geometryEquals,
|
|
948
|
+
tryFixWKT: tryFixWKT,
|
|
949
|
+
validateGeoJSON: validateGeoJSON,
|
|
950
|
+
validateWKT: validateWKT
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// 命名空间导出 - 所有公共 API 汇总
|
|
954
|
+
// 使用方式: import WKT from 'wkt-parse-and-geojson';
|
|
955
|
+
// WKT.parse(...), WKT.build(...), etc.
|
|
956
|
+
const WKT = {
|
|
957
|
+
...types,
|
|
958
|
+
...wktParser,
|
|
959
|
+
...wktBuilder,
|
|
960
|
+
...geojsonBuilder,
|
|
961
|
+
...wktToGeojson,
|
|
962
|
+
...geojsonToWkt$1,
|
|
963
|
+
...validate,
|
|
964
|
+
};
|
|
965
|
+
|
|
656
966
|
exports.GeoJSONBuilder = GeoJSONBuilder;
|
|
967
|
+
exports.WKT = WKT;
|
|
657
968
|
exports.WKTBuilder = WKTBuilder;
|
|
658
969
|
exports.WKTParser = WKTParser;
|
|
659
970
|
exports.build = build;
|
|
971
|
+
exports.cloneGeometry = cloneGeometry;
|
|
660
972
|
exports.createGeometryCollection = createGeometryCollection;
|
|
661
973
|
exports.createLineString = createLineString;
|
|
662
974
|
exports.createMultiLineString = createMultiLineString;
|
|
@@ -667,7 +979,11 @@ exports.createPolygon = createPolygon;
|
|
|
667
979
|
exports.featureCollectionToWkt = featureCollectionToWkt;
|
|
668
980
|
exports.featureToWkt = featureToWkt;
|
|
669
981
|
exports.geojsonToWkt = geojsonToWkt;
|
|
982
|
+
exports.geometryEquals = geometryEquals;
|
|
670
983
|
exports.parse = parse;
|
|
984
|
+
exports.tryFixWKT = tryFixWKT;
|
|
985
|
+
exports.validateGeoJSON = validateGeoJSON;
|
|
986
|
+
exports.validateWKT = validateWKT;
|
|
671
987
|
exports.wktToFeature = wktToFeature;
|
|
672
988
|
exports.wktToFeatureCollection = wktToFeatureCollection;
|
|
673
989
|
exports.wktToGeoJSON = wktToGeoJSON;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,5 @@ export { build, WKTBuilder } from './wkt-builder';
|
|
|
4
4
|
export { createPoint, createLineString, createPolygon, createMultiPoint, createMultiLineString, createMultiPolygon, createGeometryCollection, GeoJSONBuilder, } from './geojson-builder';
|
|
5
5
|
export { wktToGeoJSON, wktToFeature, wktToFeatureCollection } from './wkt-to-geojson';
|
|
6
6
|
export { geojsonToWkt, featureToWkt, featureCollectionToWkt } from './geojson-to-wkt';
|
|
7
|
+
export { validateWKT, validateGeoJSON, tryFixWKT, cloneGeometry, geometryEquals, type ValidationResult, } from './validate';
|
|
8
|
+
export { default as WKT } from './namespace';
|