@rural-environmental-registry/map_component 1.1.0 → 2.0.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/README.md +1 -0
- package/dist/handlers/measureToolsHandler.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +16337 -14954
- package/dist/types/index.d.ts +118 -15
- package/dist/utils/geometryFileParseResult.d.ts +8 -0
- package/dist/utils/layersConfigNormalizer.d.ts +16 -0
- package/dist/utils/menuHistory.d.ts +3 -3
- 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/MapDPG.vue +90 -2
- package/src/components/coordinate/CoordinatePanel.vue +31 -8
- package/src/components/fa-icon/FontAwesomeIcon.vue +35 -5
- package/src/components/map/LeafletMap.vue +3 -1
- package/src/components/menu/ChildMenu.vue +307 -27
- package/src/components/menu/LayerMenu.vue +232 -37
- package/src/components/menu/ParentMenu.vue +261 -27
- package/src/components/menu/SectionMenu.vue +512 -0
- package/src/constants/coordinatePanel.ts +6 -5
- package/src/handlers/mapToolsHandler.ts +3 -1
- package/src/handlers/measureToolsHandler.ts +36 -27
- package/src/handlers/toolsConstants.ts +1 -0
- package/src/index.ts +47 -0
- package/src/types/index.ts +149 -14
- package/src/utils/geometryFileParseResult.ts +5 -0
- package/src/utils/layersConfigNormalizer.ts +273 -0
- package/src/utils/menuHistory.ts +35 -19
- 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
|
@@ -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
|
-
}
|