Skip to content

Commit 6c617bc

Browse files
committed
Handle identification of GeoJSON feature without the help of OpenLayers
this way we can re-use the same logic for Cesium (and will then be able to use the mouse-click Composable when we rewrite the CesiumMap component into Composition API)
1 parent 3f85533 commit 6c617bc

File tree

10 files changed

+158
-67
lines changed

10 files changed

+158
-67
lines changed

package-lock.json

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"@geoblocks/ol-maplibre-layer": "^0.1.2",
3838
"@ivanv/vue-collapse-transition": "^1.0.2",
3939
"@popperjs/core": "^2.11.8",
40+
"@turf/distance": "^6.5.0",
41+
"@turf/helpers": "^6.5.0",
4042
"animate.css": "^4.1.1",
4143
"axios": "^1.6.2",
4244
"bootstrap": "^5.3.2",

src/modules/infobox/components/FeatureList.vue

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default {
4545
}
4646
</script>
4747

48-
<style lang="scss">
48+
<style lang="scss" scoped>
4949
@import 'src/scss/media-query.mixin';
5050
5151
.feature-list {
@@ -82,18 +82,18 @@ export default {
8282
}
8383
8484
// Styling for external HTML content
85-
.htmlpopup-container {
85+
:global(.htmlpopup-container) {
8686
width: 100%;
8787
font-size: 11px;
8888
text-align: start;
8989
}
90-
.htmlpopup-header {
90+
:global(.htmlpopup-header) {
9191
background-color: #e9e9e9;
9292
padding: 7px;
9393
margin-bottom: 7px;
9494
font-weight: 700;
9595
}
96-
.htmlpopup-content {
96+
:global(.htmlpopup-content) {
9797
padding: 7px;
9898
}
9999
</style>

src/modules/map/components/cesium/CesiumMap.vue

+9-15
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ import { ClickInfo, ClickType } from '@/store/modules/map.store'
9191
import { UIModes } from '@/store/modules/ui.store'
9292
import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems'
9393
import CustomCoordinateSystem from '@/utils/coordinates/CustomCoordinateSystem.class'
94-
import { createGeoJSONFeature } from '@/utils/layerUtils'
94+
import { identifyGeoJSONFeatureAt } from '@/utils/identifyOnVectorLayer'
9595
import log from '@/utils/logging'
9696
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
9797
import '@geoblocks/cesium-compass'
@@ -461,25 +461,19 @@ export default {
461461
)
462462
463463
let objects = this.viewer.scene.drillPick(event.position)
464-
const geoJsonFeatures = {}
465464
const kmlFeatures = {}
466465
// if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor
467466
this.visiblePrimitiveLayers
468467
.filter((l) => l instanceof GeoAdminGeoJsonLayer)
469468
.forEach((geoJSonLayer) => {
470-
objects
471-
.filter((obj) => obj.primitive?.olLayer?.get('id') === geoJSonLayer.getID())
472-
.forEach((obj) => {
473-
const feature = obj.primitive.olFeature
474-
if (!geoJsonFeatures[feature.getId()]) {
475-
geoJsonFeatures[feature.getId()] = createGeoJSONFeature(
476-
obj.primitive.olFeature,
477-
geoJSonLayer,
478-
feature.getGeometry()
479-
)
480-
}
481-
})
482-
features.push(...Object.values(geoJsonFeatures))
469+
features.push(
470+
...identifyGeoJSONFeatureAt(
471+
geoJSonLayer,
472+
event.position,
473+
this.projection,
474+
this.resolution
475+
)
476+
)
483477
})
484478
this.visiblePrimitiveLayers
485479
.filter((l) => l instanceof KMLLayer)

src/modules/map/components/common/mouse-click.composable.js

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import LayerTypes from '@/api/layers/LayerTypes.enum'
22
import { ClickInfo, ClickType } from '@/store/modules/map.store'
3+
import { identifyGeoJSONFeatureAt, identifyKMLFeatureAt } from '@/utils/identifyOnVectorLayer'
34
import { computed } from 'vue'
45
import { useStore } from 'vuex'
56

@@ -18,6 +19,8 @@ export function useMouseOnMap() {
1819
const visibleKMLLayers = computed(() =>
1920
store.getters.visibleLayers.filter((layer) => layer.type === LayerTypes.KML)
2021
)
22+
const currentMapResolution = computed(() => store.getters.resolution)
23+
const currentProjection = computed(() => store.state.position.projection)
2124

2225
/**
2326
* @param {[Number, Number]} screenPosition
@@ -41,17 +44,25 @@ export function useMouseOnMap() {
4144
// if we've already "handled" this click event, we do nothing more
4245
if (!hasPointerDownTriggeredLocationPopup && isStillOnStartingPosition) {
4346
const features = []
44-
// if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor
4547
visibleGeoJsonLayers.value.forEach((geoJSonLayer) => {
46-
console.log(
47-
'GeoJSON features',
48-
geoJSonLayer
49-
// booleanIntersects(geoJSonLayer, coordinate)
48+
features.push(
49+
...identifyGeoJSONFeatureAt(
50+
geoJSonLayer,
51+
coordinate,
52+
currentProjection.value,
53+
currentMapResolution.value
54+
)
5055
)
51-
// features.push(...this.handleClickOnGeoJsonLayer(event, geoJSonLayer))
5256
})
53-
visibleKMLLayers.value.forEach(() => {
54-
// features.push(...this.handleClickOnKMLLayer(event, KMLLayer))
57+
visibleKMLLayers.value.forEach((kmlLayer) => {
58+
features.push(
59+
...identifyKMLFeatureAt(
60+
kmlLayer.kmlData,
61+
coordinate,
62+
currentProjection.value,
63+
currentMapResolution.value
64+
)
65+
)
5566
})
5667
store.dispatch(
5768
'click',

src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const nonEditableFeature = computed(() =>
3030
const popoverCoordinate = computed(() => {
3131
if (selectedFeatures.value.length > 0) {
3232
const [firstFeature] = selectedFeatures.value
33+
if (firstFeature.geometry) {
34+
return firstFeature.geometry.coordinates
35+
}
3336
return Array.isArray(firstFeature.coordinates[0])
3437
? firstFeature.coordinates[firstFeature.coordinates.length - 1]
3538
: firstFeature.coordinates

src/store/modules/features.store.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const getSelectedFeatureWithId = (state, featureId) => {
77

88
export default {
99
state: {
10-
/** @type Array<Feature> */
10+
/** @type Array<SelectableFeature> */
1111
selectedFeatures: [],
1212
},
1313
getters: {
@@ -22,7 +22,8 @@ export default {
2222
* tells the store which features are selected (it does not select the features by itself)
2323
*
2424
* @param commit
25-
* @param {Feature[]} features A list of feature we want to highlight/select on the map
25+
* @param {SelectableFeature[]} features A list of feature we want to highlight/select on
26+
* the map
2627
*/
2728
setSelectedFeatures({ commit }, features) {
2829
if (Array.isArray(features)) {

src/utils/geoJsonUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function reprojectGeoJsonData(geoJsonData, toProjection, fromProj
2727
}
2828
} else if (toProjection instanceof CoordinateSystem) {
2929
// according to the IETF reference, if nothing is said about the projection used, it should be WGS84
30-
reprojectedGeoJSON = reproject(this.geojsonData, WGS84.epsg, toProjection.epsg)
30+
reprojectedGeoJSON = reproject(geoJsonData, WGS84.epsg, toProjection.epsg)
3131
}
3232
return reprojectedGeoJSON
3333
}

src/utils/identifyOnVectorLayer.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { LayerFeature } from '@/api/features.api'
2+
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
3+
import reprojectGeoJsonData from '@/utils/geoJsonUtils'
4+
import log from '@/utils/logging'
5+
import distance from '@turf/distance'
6+
import { point } from '@turf/helpers'
7+
import proj4 from 'proj4'
8+
9+
const pixelToleranceForIdentify = 10
10+
11+
/**
12+
* @param {GeoAdminGeoJsonLayer} geoJsonLayer
13+
* @param {[Number, Number]} coordinate
14+
* @param {CoordinateSystem} projection
15+
* @param {Number} resolution
16+
* @returns {LayerFeature[]}
17+
*/
18+
export function identifyGeoJSONFeatureAt(geoJsonLayer, coordinate, projection, resolution) {
19+
const features = []
20+
// if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor
21+
const coordinateWGS84 = point(proj4(projection.epsg, WGS84.epsg, coordinate))
22+
// to use turf functions, we need to have lat/lon (WGS84) coordinates
23+
const reprojectedGeoJSON = reprojectGeoJsonData(geoJsonLayer.geoJsonData, WGS84, projection)
24+
if (!reprojectedGeoJSON) {
25+
log.error(
26+
`Unable to reproject GeoJSON data in order to find features at coordinates`,
27+
geoJsonLayer.getID(),
28+
coordinate
29+
)
30+
return []
31+
}
32+
const matchingFeatures = reprojectedGeoJSON.features
33+
.filter((feature) => {
34+
const distanceWithClick = distance(
35+
coordinateWGS84,
36+
point(feature.geometry.coordinates),
37+
{
38+
units: 'meters',
39+
}
40+
)
41+
return distanceWithClick <= pixelToleranceForIdentify * resolution
42+
})
43+
.map((feature) => {
44+
// back to the starting projection
45+
feature.geometry.coordinates = proj4(
46+
WGS84.epsg,
47+
projection.epsg,
48+
feature.geometry.coordinates
49+
)
50+
return new LayerFeature(
51+
geoJsonLayer,
52+
feature.id,
53+
feature.properties.station_name || feature.id,
54+
`<div class="htmlpopup-container">
55+
<div class="htmlpopup-header">
56+
<span>${geoJsonLayer.name}</span>
57+
</div>
58+
<div class="htmlpopup-content">
59+
${feature.properties.description}
60+
</div>
61+
</div>`,
62+
proj4(WGS84.epsg, projection.epsg, feature.geometry.coordinates),
63+
null,
64+
feature.geometry
65+
)
66+
})
67+
if (matchingFeatures?.length > 0) {
68+
features.push(...matchingFeatures)
69+
}
70+
return features
71+
}
72+
73+
/**
74+
* @param {KMLLayer} _kmlLayer
75+
* @param {[Number, Number]} _coordinate
76+
* @param {CoordinateSystem} _projection
77+
* @param {Number} _resolution
78+
* @returns {LayerFeature[]}
79+
*/
80+
export function identifyKMLFeatureAt(_kmlLayer, _coordinate, _projection, _resolution) {
81+
// TODO : implement KML layer feature identification
82+
return []
83+
}

src/utils/layerUtils.js

+1-37
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA } from '@/api/layers/LayerTimeConfigEntry.class'
21
import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class'
3-
import { LayerFeature } from '@/api/features.api'
4-
import log from '@/utils/logging'
2+
import { YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA } from '@/api/layers/LayerTimeConfigEntry.class'
53

64
export class ActiveLayerConfig {
75
/**
@@ -43,37 +41,3 @@ export function getTimestampFromConfig(config, previewYear) {
4341
}
4442
return config instanceof GeoAdminWMTSLayer ? null : ''
4543
}
46-
47-
/**
48-
* Describes a GeoJSON feature from the backend
49-
*
50-
* For GeoJSON features, there's a catch as they only provide us with the inner tooltip content we
51-
* have to wrap it around the "usual" wrapper from the backend (not very fancy but otherwise the
52-
* look and feel is different from a typical backend tooltip)
53-
*
54-
* @param feature
55-
* @param geoJsonLayer
56-
* @param [geometry]
57-
* @returns {LayerFeature}
58-
*/
59-
export function createGeoJSONFeature(feature, geoJsonLayer, geometry) {
60-
const featureGeometry = feature.getGeometry()
61-
const geoJsonFeature = new LayerFeature(
62-
geoJsonLayer,
63-
geoJsonLayer.getID(),
64-
geoJsonLayer.name,
65-
`<div class="htmlpopup-container">
66-
<div class="htmlpopup-header">
67-
<span>${geoJsonLayer.name}</span>
68-
</div>
69-
<div class="htmlpopup-content">
70-
${feature.get('description')}
71-
</div>
72-
</div>`,
73-
featureGeometry.flatCoordinates,
74-
featureGeometry.getExtent(),
75-
geometry
76-
)
77-
log.debug('GeoJSON feature found', geoJsonFeature)
78-
return geoJsonFeature
79-
}

0 commit comments

Comments
 (0)