Skip to content

Commit 23903e3

Browse files
authored
Merge pull request #608 from geoadmin/feat-PB-95-kml-zoom-to-extent
PB-95: Add zoom to KML extent in import file
2 parents bd165f5 + 1428a01 commit 23903e3

28 files changed

+843
-75
lines changed

src/api/files.api.js

+15-8
Original file line numberDiff line numberDiff line change
@@ -313,14 +313,21 @@ export function loadKmlData(kmlLayer) {
313313
)
314314
)
315315
}
316-
axios.get(kmlLayer.kmlFileUrl).then((response) => {
317-
if (response.status === 200 && response.data) {
318-
resolve(response.data)
319-
} else {
320-
const msg = `Incorrect response while getting KML file data for layer ${kmlLayer.getID()}`
321-
log.error(msg, response)
316+
axios
317+
.get(kmlLayer.kmlFileUrl)
318+
.then((response) => {
319+
if (response.status === 200 && response.data) {
320+
resolve(response.data)
321+
} else {
322+
const msg = `Incorrect response while getting KML file data for layer ${kmlLayer.getID()}`
323+
log.error(msg, response)
324+
reject(new Error(msg))
325+
}
326+
})
327+
.catch((error) => {
328+
const msg = `Failed to load KML data: ${error}`
329+
log.error(msg)
322330
reject(new Error(msg))
323-
}
324-
})
331+
})
325332
})
326333
}

src/api/layers/AbstractLayer.class.js

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export default class AbstractLayer {
7777
} else {
7878
this.hasLegend = true
7979
}
80+
this.errorKey = null
81+
this.hasError = false
8082
}
8183

8284
/**

src/api/layers/ExternalLayer.class.js

-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ export default class ExternalLayer extends AbstractLayer {
9797
this.extent = extent
9898
this.legends = legends
9999
this.isLoading = isLoading
100-
this.errorKey = null
101-
this.hasError = false
102100
this.hasLegend = this.abstract || this.legends?.length > 0
103101
}
104102

src/api/layers/WMSCapabilitiesParser.class.js

+9
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ export default class WMSCapabilitiesParser {
208208
}
209209

210210
_getLayerExtent(layerId, layer, parents, projection) {
211+
// TODO PB-243 handling of extent out of projection bound (currently not properly handled)
212+
// - extent totally out of projection bounds
213+
// => return null and set outOfBounds flag to true
214+
// - extent totally inside of projection bounds
215+
// => crop extent and set outOfBounds flag to true
216+
// - extent partially inside projection bounds
217+
// => take intersect extent and set outOfBounds flag to true
218+
// - no extent
219+
// => return null and set the outOfBounds flag to false (we don't know)
211220
let layerExtent = null
212221
const matchedBbox = layer.BoundingBox?.find((bbox) => bbox.crs === projection.epsg)
213222
// First try to find a matching extent from the BoundingBox

src/api/layers/WMTSCapabilitiesParser.class.js

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export default class WMTSCapabilitiesParser {
162162
}
163163

164164
_getLayerExtent(layerId, layer, projection) {
165+
// TODO PB-243 handling of extent out of projection bound (currently not properly handled)
165166
let layerExtent = null
166167
let extentEpsg = null
167168
// First try to get the extent from the default bounding box

src/modules/drawing/useKmlDataManagement.composable.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import KMLLayer from '@/api/layers/KMLLayer.class'
66
import { IS_TESTING_WITH_CYPRESS } from '@/config'
77
import { parseKml } from '@/modules/drawing/lib/drawingUtils'
88
import { DrawingState, generateKmlString } from '@/modules/drawing/lib/export-utils'
9-
import { updateKmlActiveLayer } from '@/utils/kmlUtils'
109
import log from '@/utils/logging'
1110

1211
// ref/variables outside useSaveKmlOnChange function so that they may be shared between all usages of the usaSaveKmlOnChange
@@ -81,7 +80,11 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
8180
activeKmlLayer.value.adminId,
8281
kmlData
8382
)
84-
await updateKmlActiveLayer(store, activeKmlLayer.value, kmlData, kmlMetadata)
83+
await store.dispatch('updateKmlLayer', {
84+
layerId: activeKmlLayer.value.getID(),
85+
kmlData: kmlData,
86+
kmlMetadata: kmlMetadata,
87+
})
8588
saveState.value = DrawingState.SAVED
8689
}
8790
} catch (e) {

src/modules/i18n/locales/de.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,6 @@
648648
"no_result": "Kein Ergebnis",
649649
"loading": "Laden",
650650
"search_in_catalogue_placeholder": "Suche in importierten Karten",
651-
"kml_name": "Name der KML",
652-
"name_too_long": "Name ist zu lang"
651+
"kml_gpx_file_out_of_bounds": "Der Dateiinhalt liegt außerhalb der Projektionsgrenzen",
652+
"kml_gpx_file_empty": "Die KML/GPX-Datei ist leer"
653653
}

src/modules/i18n/locales/en.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,6 @@
648648
"no_result": "No result",
649649
"loading": "Loading",
650650
"search_in_catalogue_placeholder": "Search in imported maps",
651-
"kml_name": "Name of the KML",
652-
"name_too_long": "Name is too long"
651+
"kml_gpx_file_out_of_bounds": "File content is out of projection bounds",
652+
"kml_gpx_file_empty": "KML/GPX file is empty"
653653
}

src/modules/i18n/locales/fr.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,6 @@
648648
"no_result": "Pas de résultat",
649649
"loading": "Chargement",
650650
"search_in_catalogue_placeholder": "Rechercher dans les cartes importées",
651-
"kml_name": "Nom du KML",
652-
"name_too_long": "Le nom est trop long"
651+
"kml_gpx_file_out_of_bounds": "Le contenu du fichier est hors des limites de projection",
652+
"kml_gpx_file_empty": "Le fichier KML/GPX est vide"
653653
}

src/modules/i18n/locales/it.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,6 @@
648648
"no_result": "Nessun risultato",
649649
"loading": "Caricamento",
650650
"search_in_catalogue_placeholder": "Cerca nelle mappe importate",
651-
"kml_name": "Nome del KML",
652-
"name_too_long": "Il nome è troppo lungo"
651+
"kml_gpx_file_out_of_bounds": "Il contenuto del file è fuori dai limiti della proiezione",
652+
"kml_gpx_file_empty": "Il file KML/GPX è vuoto"
653653
}

src/modules/i18n/locales/rm.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,6 @@
646646
"no_result": "Nagut resultat",
647647
"loading": "Chargiada",
648648
"search_in_catalogue_placeholder": "Cercar en mapas importads",
649-
"kml_name": "Num da la KML",
650-
"name_too_long": "Il num è massa lung"
649+
"kml_gpx_file_out_of_bounds": "Il contetn dal file è en furma da las limitas da projezziun",
650+
"kml_gpx_file_empty": "La datoteca KML/GPX è vegnida empruva"
651651
}

src/modules/menu/components/LayerCatalogueItem.vue

+10
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,16 @@ function transformExtentIntoPolygon(flattenExtent) {
151151
152152
const lv95Extent = [LV95.bounds.bottomLeft, LV95.bounds.topRight]
153153
function zoomToLayer() {
154+
// TODO PB-243 better handling of layers extent errors
155+
// - extent totally out of projection bounds
156+
// => layer should be marked as out of bounds and disabled, no zoom to layer icon
157+
// but an error icon with reason
158+
// - extent totally inside of projection bounds
159+
// => current behavior, maybe add a warning icon about partial layer display
160+
// - extent partially inside projection bounds
161+
// => take intersection as extent, maybe add a warning icon about partial layer display
162+
// - no extent
163+
// => add a warning that the layer might be out of bound
154164
log.debug(`Zoom to layer ${item.value.name}`, item.value.extent)
155165
// Only zooming to layer's extent if its extent is entirely within LV95 extent.
156166
// If part (or all) of the extent is outside LV95 extent, we zoom to LV95 extent instead.

src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useStore } from 'vuex'
66
import ImportFileButtons from '@/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue'
77
import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils'
88
import { useImportButton } from '@/modules/menu/components/advancedTools/useImportButton'
9+
import { OutOfBoundsError } from '@/utils/coordinates/coordinateUtils'
10+
import { EmptyKMLError } from '@/utils/kmlUtils'
911
import log from '@/utils/logging'
1012
1113
const LOCAL_UPLOAD_ACCEPT = '.kml,.KML,.gpx,.GPX'
@@ -72,8 +74,14 @@ async function loadFile() {
7274
handleFileContent(store, content, selectedFile.value.name)
7375
layerAdded.value = true
7476
} catch (error) {
75-
errorMessage.value = 'invalid_kml_gpx_file_error'
76-
log.error(`Failed to load file`, error)
77+
if (error instanceof OutOfBoundsError) {
78+
errorMessage.value = 'kml_gpx_file_out_of_bounds'
79+
} else if (error instanceof EmptyKMLError) {
80+
errorMessage.value = 'kml_gpx_file_empty'
81+
} else {
82+
errorMessage.value = 'invalid_kml_gpx_file_error'
83+
log.error(`Failed to load file`, error)
84+
}
7785
}
7886
}
7987

src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useStore } from 'vuex'
66
import ImportFileButtons from '@/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue'
77
import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils'
88
import TextInput from '@/utils/components/TextInput.vue'
9+
import { OutOfBoundsError } from '@/utils/coordinates/coordinateUtils'
10+
import { EmptyKMLError } from '@/utils/kmlUtils'
911
import log from '@/utils/logging'
1012
import { isValidUrl } from '@/utils/utils'
1113
@@ -74,6 +76,10 @@ async function loadFile() {
7476
if (error instanceof AxiosError || /fetch/.test(error.message)) {
7577
log.error(`Failed to load file from url ${fileUrl.value}`, error)
7678
urlError.value = 'loading_error_network_failure'
79+
} else if (error instanceof OutOfBoundsError) {
80+
urlError.value = 'kml_gpx_file_out_of_bounds'
81+
} else if (error instanceof EmptyKMLError) {
82+
urlError.value = 'kml_gpx_file_empty'
7783
} else {
7884
log.error(`Failed to parse file from url ${fileUrl.value}`, error)
7985
urlError.value = 'invalid_kml_gpx_file_error'

src/modules/menu/components/advancedTools/ImportFile/utils.js

+12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import KMLLayer from '@/api/layers/KMLLayer.class'
2+
import { OutOfBoundsError } from '@/utils/coordinates/coordinateUtils'
3+
import { EmptyKMLError, getKmlExtent, getKmlExtentForProjection } from '@/utils/kmlUtils'
24

35
/**
46
* Checks if file is KML
@@ -32,6 +34,16 @@ export function handleFileContent(store, content, source) {
3234
let layer = null
3335
if (isKml(content)) {
3436
layer = new KMLLayer(source, true, 1.0, null /* adminId */, content)
37+
const extent = getKmlExtent(content)
38+
if (!extent) {
39+
throw new EmptyKMLError()
40+
}
41+
const projectedExtent = getKmlExtentForProjection(store.state.position.projection, extent)
42+
43+
if (!projectedExtent) {
44+
throw new OutOfBoundsError(`KML out of projection bounds: ${extent}`)
45+
}
46+
store.dispatch('zoomToExtent', projectedExtent)
3547
store.dispatch('addLayer', layer)
3648
} else if (isGpx(content)) {
3749
// TODO GPX layer not done yet

src/store/modules/layers.store.js

+43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import AbstractLayer from '@/api/layers/AbstractLayer.class'
22
import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class'
33
import LayerTypes from '@/api/layers/LayerTypes.enum'
4+
import { getKmlExtent, getKmlExtentForProjection, parseKmlName } from '@/utils/kmlUtils'
45
import { ActiveLayerConfig } from '@/utils/layerUtils'
56
import log from '@/utils/logging'
67

@@ -429,6 +430,48 @@ const actions = {
429430
clearPreviewYear({ commit }) {
430431
commit('setPreviewYear', null)
431432
},
433+
setLayerErrorKey({ commit, getters }, payload) {
434+
const { layerId, errorKey } = payload
435+
const currentLayer = getters.getActiveLayerById(layerId)
436+
if (!currentLayer) {
437+
throw new Error(
438+
`Failed to update layer error key "${layerId}", layer not found in active layers`
439+
)
440+
}
441+
const updatedLayer = currentLayer.clone()
442+
updatedLayer.errorKey = errorKey
443+
updatedLayer.hasError = !!errorKey
444+
commit('updateLayer', updatedLayer)
445+
},
446+
updateKmlLayer({ commit, getters, rootState }, payload) {
447+
const { layerId, kmlData, kmlMetadata } = payload
448+
const currentLayer = getters.getActiveLayerById(layerId)
449+
if (!currentLayer) {
450+
throw new Error(
451+
`Failed to update KML layer data/metadata "${layerId}", ` +
452+
`layer not found in active layers`
453+
)
454+
}
455+
const updatedLayer = currentLayer.clone()
456+
457+
if (kmlData) {
458+
updatedLayer.name = parseKmlName(kmlData) || 'KML'
459+
updatedLayer.kmlData = kmlData
460+
updatedLayer.isLoading = false
461+
const extent = getKmlExtent(kmlData)
462+
if (!extent) {
463+
updatedLayer.errorKey = 'kml_gpx_file_empty'
464+
updatedLayer.hasError = true
465+
} else if (!getKmlExtentForProjection(rootState.position.projection, extent)) {
466+
updatedLayer.errorKey = 'kml_gpx_file_out_of_bounds'
467+
updatedLayer.hasError = true
468+
}
469+
}
470+
if (kmlMetadata) {
471+
updatedLayer.kmlMetadata = kmlMetadata
472+
}
473+
commit('updateLayer', updatedLayer)
474+
},
432475
}
433476

434477
const mutations = {

src/store/plugins/external-layers.plugin.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ async function updateExternalLayer(store, externalLayer, projection) {
5353
store.dispatch('updateLayer', updatedExternalLayer)
5454
} catch (error) {
5555
log.error(`Failed to update external layer: `, error)
56-
const erroredLayer = {
57-
id: externalLayer.getID(),
56+
store.dispatch('setLayerErrorKey', {
57+
layerId: externalLayer.getID(),
5858
errorKey: error.key ? error.key : 'error',
59-
hasError: true,
60-
}
61-
store.dispatch('updateLayer', erroredLayer)
59+
})
6260
}
6361
}
6462

src/store/plugins/load-kml-data.plugin.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { loadKmlData, loadKmlMetadata } from '@/api/files.api'
77
import KMLLayer from '@/api/layers/KMLLayer.class'
8-
import { updateKmlActiveLayer } from '@/utils/kmlUtils'
98
import log from '@/utils/logging'
109

1110
/**
@@ -17,7 +16,7 @@ async function loadMetadata(store, kmlLayer) {
1716
log.debug(`Loading metadata for added KML layer`, kmlLayer)
1817
try {
1918
const metadata = await loadKmlMetadata(kmlLayer)
20-
updateKmlActiveLayer(store, kmlLayer, null, metadata)
19+
store.dispatch('updateKmlLayer', { layerId: kmlLayer?.getID(), kmlMetadata: metadata })
2120
} catch (error) {
2221
log.error(`Error while fetching KML metadata for layer ${kmlLayer?.getID()}`)
2322
}
@@ -32,9 +31,13 @@ async function loadData(store, kmlLayer) {
3231
log.debug(`Loading data for added KML layer`, kmlLayer)
3332
try {
3433
const data = await loadKmlData(kmlLayer)
35-
updateKmlActiveLayer(store, kmlLayer, data)
34+
store.dispatch('updateKmlLayer', { layerId: kmlLayer?.getID(), kmlData: data })
3635
} catch (error) {
37-
log.error(`Error while fetching KML data for layer ${kmlLayer?.getID()}`)
36+
log.error(`Error while fetching KML data for layer ${kmlLayer?.getID()}: ${error}`)
37+
store.dispatch('setLayerErrorKey', {
38+
layerId: kmlLayer.getID(),
39+
errorKey: `loading_error_network_failure`,
40+
})
3841
}
3942
}
4043

0 commit comments

Comments
 (0)