@rural-environmental-registry/map_component 1.1.0 → 1.2.0
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.css +1 -1
- package/dist/index.mjs +11333 -10777
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/geometryFileParseResult.d.ts +8 -0
- package/dist/utils/normalizeGeoJsonToFeatures.d.ts +2 -0
- package/dist/utils/parseGeoJsonFile.d.ts +2 -0
- package/dist/utils/parseKmlFile.d.ts +2 -0
- package/dist/utils/parseShapefileZip.d.ts +2 -8
- package/dist/utils/{validateShapefileGeometry.d.ts → validateImportedPolygonGeometry.d.ts} +2 -2
- package/package.json +3 -2
- package/src/components/coordinate/CoordinatePanel.vue +31 -8
- package/src/constants/coordinatePanel.ts +6 -5
- package/src/types/index.ts +1 -0
- package/src/utils/geometryFileParseResult.ts +5 -0
- package/src/utils/normalizeGeoJsonToFeatures.ts +52 -0
- package/src/utils/parseGeoJsonFile.ts +23 -0
- package/src/utils/parseKmlFile.ts +31 -0
- package/src/utils/parseShapefileZip.ts +4 -31
- package/src/utils/validateImportedPolygonGeometry.ts +62 -0
- package/src/utils/validateShapefileGeometry.ts +0 -54
package/dist/types/index.d.ts
CHANGED
|
@@ -211,6 +211,7 @@ export type CoordinatePanelTexts = {
|
|
|
211
211
|
dragShapefileZip?: string;
|
|
212
212
|
shapefileZipInfo?: string;
|
|
213
213
|
shapefileAppliedSuccess?: string;
|
|
214
|
+
geometryImportUnsupportedFormat?: string;
|
|
214
215
|
placeholderLongitude?: string;
|
|
215
216
|
placeholderLatitude?: string;
|
|
216
217
|
placeholderAzimuth?: string;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type ShapefileParseResult =
|
|
3
|
-
ok: true;
|
|
4
|
-
features: Feature[];
|
|
5
|
-
} | {
|
|
6
|
-
ok: false;
|
|
7
|
-
error: string;
|
|
8
|
-
};
|
|
1
|
+
import type { GeometryFileParseResult } from './geometryFileParseResult';
|
|
2
|
+
export type ShapefileParseResult = GeometryFileParseResult;
|
|
9
3
|
export declare function parseShapefileZip(file: File): Promise<ShapefileParseResult>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Feature, MultiPolygon, Polygon } from 'geojson';
|
|
2
|
-
export type
|
|
2
|
+
export type ImportedPolygonValidationResult = {
|
|
3
3
|
ok: true;
|
|
4
4
|
feature: Feature<Polygon | MultiPolygon>;
|
|
5
5
|
} | {
|
|
6
6
|
ok: false;
|
|
7
7
|
error: string;
|
|
8
8
|
};
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function validateImportedPolygonGeometry(features: Feature[]): ImportedPolygonValidationResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rural-environmental-registry/map_component",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Map component for Vue.js applications",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"leaflet": "1.9.4",
|
|
48
48
|
"leaflet-draw": "1.0.4",
|
|
49
49
|
"papaparse": "5.5.3",
|
|
50
|
-
"shpjs": "6.1.0"
|
|
50
|
+
"shpjs": "6.1.0",
|
|
51
|
+
"@tmcw/togeojson": "7.1.2"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@types/geojson": "7946.0.16",
|
|
@@ -320,9 +320,9 @@
|
|
|
320
320
|
<input
|
|
321
321
|
ref="shapefileInputRef"
|
|
322
322
|
type="file"
|
|
323
|
-
accept=".zip"
|
|
323
|
+
accept=".zip,.kml,.geojson,.json"
|
|
324
324
|
class="shapefile-input-hidden"
|
|
325
|
-
@change="
|
|
325
|
+
@change="handleGeometryFileInputChange"
|
|
326
326
|
/>
|
|
327
327
|
</div>
|
|
328
328
|
</template>
|
|
@@ -353,7 +353,10 @@
|
|
|
353
353
|
import MemorialButtonControl from '../../handlers/memorialButtonControl'
|
|
354
354
|
import type { DescriptiveMemorial } from '../../types'
|
|
355
355
|
import { parseShapefileZip } from '../../utils/parseShapefileZip'
|
|
356
|
-
import {
|
|
356
|
+
import { parseGeoJsonFile } from '../../utils/parseGeoJsonFile'
|
|
357
|
+
import { parseKmlFile } from '../../utils/parseKmlFile'
|
|
358
|
+
import { validateImportedPolygonGeometry } from '../../utils/validateImportedPolygonGeometry'
|
|
359
|
+
import type { GeometryFileParseResult } from '../../utils/geometryFileParseResult'
|
|
357
360
|
import type { Feature, MultiPolygon, Polygon } from 'geojson'
|
|
358
361
|
|
|
359
362
|
interface CSVRow {
|
|
@@ -669,18 +672,38 @@
|
|
|
669
672
|
shapefileInputRef.value?.click()
|
|
670
673
|
}
|
|
671
674
|
|
|
672
|
-
const
|
|
675
|
+
const getGeometryFileExtension = (fileName: string): 'zip' | 'kml' | 'geojson' | null => {
|
|
676
|
+
const lower = fileName.toLowerCase()
|
|
677
|
+
|
|
678
|
+
if (lower.endsWith('.zip')) return 'zip'
|
|
679
|
+
if (lower.endsWith('.kml')) return 'kml'
|
|
680
|
+
if (lower.endsWith('.geojson') || lower.endsWith('.json')) return 'geojson'
|
|
681
|
+
|
|
682
|
+
return null
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const parseGeometryFile = async (file: File): Promise<GeometryFileParseResult> => {
|
|
686
|
+
const extension = getGeometryFileExtension(file.name)
|
|
687
|
+
|
|
688
|
+
if (extension === 'zip') return parseShapefileZip(file)
|
|
689
|
+
if (extension === 'kml') return parseKmlFile(file)
|
|
690
|
+
if (extension === 'geojson') return parseGeoJsonFile(file)
|
|
691
|
+
|
|
692
|
+
return { ok: false, error: texts.value.geometryImportUnsupportedFormat || 'Unsupported file format' }
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const processGeometryFile = async (file: File) => {
|
|
673
696
|
emit('geometryRemoved')
|
|
674
697
|
csvData.value = []
|
|
675
698
|
shapefileLoaded.value = false
|
|
676
699
|
|
|
677
|
-
const parseResult = await
|
|
700
|
+
const parseResult = await parseGeometryFile(file)
|
|
678
701
|
if (!parseResult.ok) {
|
|
679
702
|
ElMessage.error(parseResult.error)
|
|
680
703
|
return
|
|
681
704
|
}
|
|
682
705
|
|
|
683
|
-
const validationResult =
|
|
706
|
+
const validationResult = validateImportedPolygonGeometry(parseResult.features)
|
|
684
707
|
if (!validationResult.ok) {
|
|
685
708
|
ElMessage.error(validationResult.error)
|
|
686
709
|
return
|
|
@@ -691,12 +714,12 @@
|
|
|
691
714
|
ElMessage.success(texts.value.shapefileAppliedSuccess)
|
|
692
715
|
}
|
|
693
716
|
|
|
694
|
-
const
|
|
717
|
+
const handleGeometryFileInputChange = async (event: Event) => {
|
|
695
718
|
const input = event.target as HTMLInputElement
|
|
696
719
|
const file = input.files?.[0]
|
|
697
720
|
if (!file) return
|
|
698
721
|
|
|
699
|
-
await
|
|
722
|
+
await processGeometryFile(file)
|
|
700
723
|
input.value = ''
|
|
701
724
|
}
|
|
702
725
|
|
|
@@ -45,12 +45,13 @@ export const COORDINATE_PANEL_TEXTS: CoordinatePanelTexts = {
|
|
|
45
45
|
csvColumnsInfo: 'The file must contain the columns: X, Y, AZIMUTH, DISTANCE',
|
|
46
46
|
applyCsvCoordinates: 'Apply CSV Coordinates',
|
|
47
47
|
|
|
48
|
-
shapefileUpload: 'Shapefile Upload',
|
|
49
|
-
shapefileFileUpload: 'Shapefile Upload',
|
|
50
|
-
dragShapefileZip: 'Drag
|
|
48
|
+
shapefileUpload: 'Shapefile, KML and Geojson Upload',
|
|
49
|
+
shapefileFileUpload: 'Shapefile, KML and Geojson Upload',
|
|
50
|
+
dragShapefileZip: 'Drag file or click to upload',
|
|
51
51
|
shapefileZipInfo:
|
|
52
|
-
'Upload a .zip
|
|
53
|
-
shapefileAppliedSuccess: '
|
|
52
|
+
'Upload a .zip (shapefile), .kml, .geojson or .json with a single Polygon or MultiPolygon geometry',
|
|
53
|
+
shapefileAppliedSuccess: 'Geometry applied successfully',
|
|
54
|
+
geometryImportUnsupportedFormat: 'Unsupported file format. Use .zip, .kml, .geojson or .json',
|
|
54
55
|
|
|
55
56
|
placeholderLongitude: 'e.g., -46.6333',
|
|
56
57
|
placeholderLatitude: 'e.g., -23.5505',
|
package/src/types/index.ts
CHANGED
|
@@ -240,6 +240,7 @@ export type CoordinatePanelTexts = {
|
|
|
240
240
|
dragShapefileZip?: string
|
|
241
241
|
shapefileZipInfo?: string
|
|
242
242
|
shapefileAppliedSuccess?: string
|
|
243
|
+
geometryImportUnsupportedFormat?: string
|
|
243
244
|
placeholderLongitude?: string
|
|
244
245
|
placeholderLatitude?: string
|
|
245
246
|
placeholderAzimuth?: string
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Feature, FeatureCollection, Geometry } from 'geojson'
|
|
2
|
+
|
|
3
|
+
function isFeatureCollection(value: unknown): value is FeatureCollection {
|
|
4
|
+
return (
|
|
5
|
+
typeof value === 'object' &&
|
|
6
|
+
value !== null &&
|
|
7
|
+
'type' in value &&
|
|
8
|
+
(value as FeatureCollection).type === 'FeatureCollection' &&
|
|
9
|
+
'features' in value &&
|
|
10
|
+
Array.isArray(value.features)
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isFeature(value: unknown): value is Feature {
|
|
15
|
+
return (
|
|
16
|
+
typeof value === 'object' &&
|
|
17
|
+
value !== null &&
|
|
18
|
+
'type' in value &&
|
|
19
|
+
(value as Feature).type === 'Feature' &&
|
|
20
|
+
'geometry' in value
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isGeometry(value: unknown): value is Geometry {
|
|
25
|
+
return (
|
|
26
|
+
typeof value === 'object' &&
|
|
27
|
+
value !== null &&
|
|
28
|
+
'type' in value &&
|
|
29
|
+
!('features' in value) &&
|
|
30
|
+
(value as { type: string }).type !== 'Feature'
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeGeoJsonToFeatures(geojson: unknown): Feature[] {
|
|
35
|
+
if (Array.isArray(geojson)) {
|
|
36
|
+
return geojson.flatMap(item => normalizeGeoJsonToFeatures(item))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isFeatureCollection(geojson)) {
|
|
40
|
+
return geojson.features
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isFeature(geojson)) {
|
|
44
|
+
return [geojson]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isGeometry(geojson)) {
|
|
48
|
+
return [{ type: 'Feature', properties: null, geometry: geojson }]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return []
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { normalizeGeoJsonToFeatures } from './normalizeGeoJsonToFeatures'
|
|
2
|
+
import type { GeometryFileParseResult } from './geometryFileParseResult'
|
|
3
|
+
|
|
4
|
+
export async function parseGeoJsonFile(file: File): Promise<GeometryFileParseResult> {
|
|
5
|
+
const fileName = file.name.toLowerCase()
|
|
6
|
+
if (!fileName.endsWith('.geojson') && !fileName.endsWith('.json')) {
|
|
7
|
+
return { ok: false, error: 'The file must be a .geojson or .json file' }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const content = await file.text()
|
|
12
|
+
const parsed = JSON.parse(content) as unknown
|
|
13
|
+
const features = normalizeGeoJsonToFeatures(parsed)
|
|
14
|
+
|
|
15
|
+
if (features.length === 0) {
|
|
16
|
+
return { ok: false, error: 'The GeoJSON file contains no geometries' }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { ok: true, features }
|
|
20
|
+
} catch {
|
|
21
|
+
return { ok: false, error: 'Could not read the file. Check that it contains valid GeoJSON' }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { kml } from '@tmcw/togeojson'
|
|
2
|
+
import { normalizeGeoJsonToFeatures } from './normalizeGeoJsonToFeatures'
|
|
3
|
+
import type { GeometryFileParseResult } from './geometryFileParseResult'
|
|
4
|
+
|
|
5
|
+
export async function parseKmlFile(file: File): Promise<GeometryFileParseResult> {
|
|
6
|
+
const fileName = file.name.toLowerCase()
|
|
7
|
+
if (!fileName.endsWith('.kml')) {
|
|
8
|
+
return { ok: false, error: 'The file must be a .kml file' }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const content = await file.text()
|
|
13
|
+
const xmlDoc = new DOMParser().parseFromString(content, 'text/xml')
|
|
14
|
+
const parserError = xmlDoc.querySelector('parsererror')
|
|
15
|
+
|
|
16
|
+
if (parserError) {
|
|
17
|
+
return { ok: false, error: 'Could not read the file. Check that it contains valid KML' }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const geojson = kml(xmlDoc)
|
|
21
|
+
const features = normalizeGeoJsonToFeatures(geojson)
|
|
22
|
+
|
|
23
|
+
if (features.length === 0) {
|
|
24
|
+
return { ok: false, error: 'The KML file contains no geometries' }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { ok: true, features }
|
|
28
|
+
} catch {
|
|
29
|
+
return { ok: false, error: 'Could not read the file. Check that it contains valid KML' }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,35 +1,8 @@
|
|
|
1
1
|
import shp from 'shpjs'
|
|
2
|
-
import
|
|
2
|
+
import { normalizeGeoJsonToFeatures } from './normalizeGeoJsonToFeatures'
|
|
3
|
+
import type { GeometryFileParseResult } from './geometryFileParseResult'
|
|
3
4
|
|
|
4
|
-
export type ShapefileParseResult =
|
|
5
|
-
| { ok: true; features: Feature[] }
|
|
6
|
-
| { ok: false; error: string }
|
|
7
|
-
|
|
8
|
-
function isFeatureCollection(value: unknown): value is FeatureCollection {
|
|
9
|
-
return (
|
|
10
|
-
typeof value === 'object' &&
|
|
11
|
-
value !== null &&
|
|
12
|
-
'type' in value &&
|
|
13
|
-
(value as FeatureCollection).type === 'FeatureCollection'
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeToFeatures(geojson: FeatureCollection | Feature | Feature[] | FeatureCollection[]): Feature[] {
|
|
18
|
-
if (Array.isArray(geojson)) {
|
|
19
|
-
return geojson.flatMap((item) => {
|
|
20
|
-
if (isFeatureCollection(item)) {
|
|
21
|
-
return item.features
|
|
22
|
-
}
|
|
23
|
-
return [item as Feature]
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (isFeatureCollection(geojson)) {
|
|
28
|
-
return geojson.features
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return [geojson as Feature]
|
|
32
|
-
}
|
|
5
|
+
export type ShapefileParseResult = GeometryFileParseResult
|
|
33
6
|
|
|
34
7
|
export async function parseShapefileZip(file: File): Promise<ShapefileParseResult> {
|
|
35
8
|
const fileName = file.name.toLowerCase()
|
|
@@ -40,7 +13,7 @@ export async function parseShapefileZip(file: File): Promise<ShapefileParseResul
|
|
|
40
13
|
try {
|
|
41
14
|
const buffer = await file.arrayBuffer()
|
|
42
15
|
const geojson = await shp(buffer)
|
|
43
|
-
const features =
|
|
16
|
+
const features = normalizeGeoJsonToFeatures(geojson)
|
|
44
17
|
|
|
45
18
|
if (features.length === 0) {
|
|
46
19
|
return { ok: false, error: 'The shapefile contains no geometries' }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { area, booleanValid, kinks } from '@turf/turf'
|
|
2
|
+
import type { Feature, MultiPolygon, Polygon, Position } from 'geojson'
|
|
3
|
+
|
|
4
|
+
export type ImportedPolygonValidationResult =
|
|
5
|
+
| { ok: true; feature: Feature<Polygon | MultiPolygon> }
|
|
6
|
+
| { ok: false; error: string }
|
|
7
|
+
|
|
8
|
+
const COORD_TOLERANCE = 1e-9
|
|
9
|
+
|
|
10
|
+
function coordsEqual(a: Position, b: Position): boolean {
|
|
11
|
+
return Math.abs(a[0] - b[0]) <= COORD_TOLERANCE && Math.abs(a[1] - b[1]) <= COORD_TOLERANCE
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isRingClosed(ring: Position[]): boolean {
|
|
15
|
+
if (ring.length < 4) return false
|
|
16
|
+
return coordsEqual(ring[0], ring[ring.length - 1])
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validateRingsClosed(geometry: Polygon | MultiPolygon): boolean {
|
|
20
|
+
if (!Array.isArray(geometry?.coordinates)) return false
|
|
21
|
+
const polygons = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates
|
|
22
|
+
return polygons.every(polygon => polygon.every(ring => isRingClosed(ring)))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function validateImportedPolygonGeometry(features: Feature[]): ImportedPolygonValidationResult {
|
|
26
|
+
try {
|
|
27
|
+
if (features.length !== 1) {
|
|
28
|
+
return { ok: false, error: 'The file must contain exactly one geometry' }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const feature = features[0]
|
|
32
|
+
const geometry = feature.geometry
|
|
33
|
+
|
|
34
|
+
if (!geometry || (geometry.type !== 'Polygon' && geometry.type !== 'MultiPolygon')) {
|
|
35
|
+
return { ok: false, error: 'Geometry must be Polygon or MultiPolygon' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!validateRingsClosed(geometry)) {
|
|
39
|
+
return { ok: false, error: 'Polygon ring is not closed or is invalid' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!booleanValid(geometry)) {
|
|
43
|
+
return { ok: false, error: 'Invalid geometry' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (kinks(geometry).features.length > 0) {
|
|
47
|
+
return { ok: false, error: 'Geometry has self-intersection' }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (area(geometry) <= 0) {
|
|
51
|
+
return { ok: false, error: 'Polygon has zero area' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { ok: true, feature: feature as Feature<Polygon | MultiPolygon> }
|
|
55
|
+
} catch {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
error:
|
|
59
|
+
'An error occurred while validating the geometry. Ensure the file contains valid coordinates.'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { area, booleanValid, kinks } from '@turf/turf'
|
|
2
|
-
import type { Feature, MultiPolygon, Polygon, Position } from 'geojson'
|
|
3
|
-
|
|
4
|
-
export type ShapefileValidationResult =
|
|
5
|
-
| { ok: true; feature: Feature<Polygon | MultiPolygon> }
|
|
6
|
-
| { ok: false; error: string }
|
|
7
|
-
|
|
8
|
-
const COORD_TOLERANCE = 1e-9
|
|
9
|
-
|
|
10
|
-
function coordsEqual(a: Position, b: Position): boolean {
|
|
11
|
-
return Math.abs(a[0] - b[0]) <= COORD_TOLERANCE && Math.abs(a[1] - b[1]) <= COORD_TOLERANCE
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isRingClosed(ring: Position[]): boolean {
|
|
15
|
-
if (ring.length < 4) return false
|
|
16
|
-
return coordsEqual(ring[0], ring[ring.length - 1])
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function validateRingsClosed(geometry: Polygon | MultiPolygon): boolean {
|
|
20
|
-
if (!Array.isArray(geometry?.coordinates)) return false
|
|
21
|
-
const polygons = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates
|
|
22
|
-
return polygons.every(polygon => polygon.every(ring => isRingClosed(ring)))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function validateShapefileGeometry(features: Feature[]): ShapefileValidationResult {
|
|
26
|
-
if (features.length !== 1) {
|
|
27
|
-
return { ok: false, error: 'The shapefile must contain exactly one geometry' }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const feature = features[0]
|
|
31
|
-
const geometry = feature.geometry
|
|
32
|
-
|
|
33
|
-
if (!geometry || (geometry.type !== 'Polygon' && geometry.type !== 'MultiPolygon')) {
|
|
34
|
-
return { ok: false, error: 'Geometry must be Polygon or MultiPolygon' }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!validateRingsClosed(geometry)) {
|
|
38
|
-
return { ok: false, error: 'Polygon ring is not closed or is invalid' }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!booleanValid(geometry)) {
|
|
42
|
-
return { ok: false, error: 'Invalid geometry' }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (kinks(geometry).features.length > 0) {
|
|
46
|
-
return { ok: false, error: 'Geometry has self-intersection' }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (area(geometry) <= 0) {
|
|
50
|
-
return { ok: false, error: 'Polygon has zero area' }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return { ok: true, feature: feature as Feature<Polygon | MultiPolygon> }
|
|
54
|
-
}
|