Skip to content

Commit 08df731

Browse files
authored
Merge pull request #904 from mapforge-org/refactor_layers
Refactor layers
2 parents 61018a0 + e1ffbe5 commit 08df731

21 files changed

Lines changed: 364 additions & 395 deletions

File tree

‎.rubocop.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ inherit_gem:
44

55
inherit_from: .rubocop_todo.yml
66

7+
inherit_mode:
8+
merge:
9+
- Exclude
10+
711
AllCops:
12+
Exclude:
13+
- 'deploy/**/*'
814
TargetRubyVersion: 4.0
915
DisplayCopNames: true
1016
DisplayStyleGuide: true

‎app/javascript/maplibre/feature/details.js‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { featureIcon, featureTitle } from 'maplibre/feature'
66
import { showElevationChart } from 'maplibre/feature/elevation'
77
import { showExtrasTotals } from 'maplibre/feature/extras_totals'
88
import { getFeature, getLayer } from "maplibre/layers/layers"
9-
import { wikipediaFeatureDescription } from 'maplibre/layers/wikipedia'
109
import { marked } from 'marked'
1110

1211
// a modal without tabs (read only map, or a feature of a non geojson layer) has no
@@ -165,8 +164,6 @@ async function featureDescription (feature) {
165164
} else if (feature?.properties?.onclick === 'feature' && feature?.properties?.['onclick-target']) {
166165
// show feature target if onclick is feature
167166
desc = `<p><i class="bi bi-geo-alt-fill"></i> ${feature.properties['onclick-target']}</p>`
168-
} else if (feature?.properties?.wikipediaId) {
169-
desc = await wikipediaFeatureDescription(feature)
170167
} else {
171168
// layers can load their description on demand, the modal shows a loading message meanwhile.
172169
// the search layer is imported here, a static import of it breaks the module init order

‎app/javascript/maplibre/layers/basemap.js‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
stickyFeatureHighlight
66
} from 'maplibre/feature'
77
import { Layer } from 'maplibre/layers/layer'
8-
import { overpassDescription } from 'maplibre/layers/overpass/overpass'
8+
import { overpassDescription } from 'maplibre/layers/osm_description'
99
import { addGeoJSONSource, map, mapProperties } from 'maplibre/map'
1010
import { basemaps } from 'maplibre/styles/basemaps'
1111
import { initializeViewStyles } from 'maplibre/styles/styles'
@@ -36,7 +36,7 @@ export class BasemapLayer extends Layer {
3636
}
3737

3838
/**
39-
* Override to disable click handler and provide custom mousemove for basemap layers.
39+
* Adds a context menu for the copy of the highlighted feature, and a custom mousemove.
4040
*/
4141
setupEventHandlers() {
4242
this.removeEventHandlers()
@@ -46,10 +46,7 @@ export class BasemapLayer extends Layer {
4646
this.contextMenuHandler = (e) => {
4747
e.preventDefault()
4848

49-
const basemapSource = this.sourceId
50-
const mapLayers = map.getStyle().layers
51-
const queryLayerIds = mapLayers.filter(layer => layer.source === basemapSource).map(layer => layer.id)
52-
const features = map.queryRenderedFeatures(e.point, { layers: queryLayerIds })
49+
const features = map.queryRenderedFeatures(e.point, { layers: this.getStyleLayerIds() })
5350

5451
// the hovered feature was stored by highlightFeatureAtPoint, with an id of its own
5552
if (features.length) {
@@ -133,14 +130,25 @@ export class BasemapLayer extends Layer {
133130
if (!features.length) { return }
134131

135132
const feature = features[0]
136-
// exit early when moving over same feature
137-
if (JSON.stringify(feature.geometry) === JSON.stringify(this?.selectedFeature?.geometry)) { return }
133+
if (this.sameFeature(feature, this.selectedFeature)) { return }
138134
this.selectedFeature = feature
139135
hideContextMenu()
140136

141137
this.renderHighlight(feature)
142138
}
143139

140+
// Runs on every mouse frame, and stringifying large polygons (water, landuse) is slow, so a
141+
// different id answers at once. Merged tile features can share an id, so the geometry decides
142+
// the rest, and the one of the selected feature is stringified only once.
143+
sameFeature(feature, selected) {
144+
if (!selected || feature.id !== selected.id || feature.sourceLayer !== selected.sourceLayer) { return false }
145+
if (this.selectedGeometryOf !== selected) {
146+
this.selectedGeometryOf = selected
147+
this.selectedGeometry = JSON.stringify(selected.geometry)
148+
}
149+
return JSON.stringify(feature.geometry) === this.selectedGeometry
150+
}
151+
144152
/**
145153
* Custom mousemove handler for basemap layer - queries basemap source layers.
146154
*/

‎app/javascript/maplibre/layers/geojson.js‎

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import {
2020
initializeExtrasLabelStyles,
2121
renderRouteExtras
2222
} from 'maplibre/layers/geojson/route_extras'
23-
import { Layer } from 'maplibre/layers/layer'
24-
import { getFeature, layers } from 'maplibre/layers/layers'
23+
import { highlightFeature } from 'maplibre/feature'
24+
import { Layer, queryFeaturesNear } from 'maplibre/layers/layer'
25+
import { frontFeature, getFeature, layers } from 'maplibre/layers/layers'
2526
import { addGeoJSONSource, map, mapProperties, removeGeoJSONSource } from 'maplibre/map'
2627
import { pruneShapeImages } from 'maplibre/styles/circle_image'
2728
import { prunePatternImages } from 'maplibre/styles/pattern_image'
@@ -78,6 +79,38 @@ export class GeoJSONLayer extends Layer {
7879
addGeoJSONSource(this.extrusionSourceId, false)
7980
}
8081

82+
setupClickHandler() {
83+
super.setupClickHandler()
84+
85+
// Double-click opens geometry edit mode directly
86+
this.dblClickHandler = (e) => {
87+
if (window.gon.map_mode !== 'rw') { return }
88+
if (draw && draw.getMode() !== 'simple_select') { return }
89+
if (e.defaultPrevented) { return }
90+
91+
const feature = queryFeaturesNear(e.point, { layers: this.getStyleLayerIds() })
92+
.find(f => !f.properties?.cluster)
93+
if (!feature) { return }
94+
95+
highlightFeature(feature, true, this.sourceId)
96+
requestAnimationFrame(() => frontFeature(feature))
97+
window.dispatchEvent(new CustomEvent('toggle-edit-feature', { detail: { type: 'geometry' } }))
98+
99+
// Prevent map zoom on double-click
100+
e.preventDefault()
101+
e.originalEvent.stopPropagation()
102+
}
103+
map.on('dblclick', this.dblClickHandler)
104+
}
105+
106+
removeEventHandlers() {
107+
super.removeEventHandlers()
108+
if (this.dblClickHandler) {
109+
map.off('dblclick', this.dblClickHandler)
110+
this.dblClickHandler = null
111+
}
112+
}
113+
81114
cleanup() {
82115
super.cleanup()
83116
cleanupKmMarkerImages(this.kmMarkerSourceId)
@@ -90,7 +123,7 @@ export class GeoJSONLayer extends Layer {
90123

91124
initialize() {
92125
initializeViewStyles(this.sourceId, !!this.layer.heatmap)
93-
if (this.layer.cluster) { initializeClusterStyles(this.sourceId, null) }
126+
if (this.clustered) { initializeClusterStyles(this.sourceId, null) }
94127
initializeKmMarkerStyles(this.kmMarkerSourceId)
95128
initializeViewStyles(this.routeExtrasSourceId)
96129
initializeExtrasLabelStyles(this.routeExtrasSourceId)
@@ -104,6 +137,12 @@ export class GeoJSONLayer extends Layer {
104137
map.setLayoutProperty(`line-layer_${this.routeExtrasSourceId}`, 'line-cap', 'butt')
105138

106139
this.setupEventHandlers()
140+
// Showing a layer runs initialize() again. The source kept its features while hidden (updates
141+
// reach it through updateData), unless setStyle replaced it, so only then fetch them again.
142+
if (this.renderedSource && this.renderedSource === map.getSource(this.sourceId)) {
143+
this.render(true, { sourceLoaded: true })
144+
return Promise.resolve(this.layer.geojson)
145+
}
107146
return this.loadData()
108147
}
109148

@@ -129,7 +168,7 @@ export class GeoJSONLayer extends Layer {
129168
}
130169
})
131170

132-
if (this.layer.cluster) {
171+
if (this.clustered) {
133172
clusterStyles(null).forEach(style => {
134173
const layerId = `${style.id}_${this.sourceId}`
135174
if (map.getLayer(layerId)) { map.setFilter(layerId, withLevelFilter(style.filter)) }
@@ -221,6 +260,7 @@ export class GeoJSONLayer extends Layer {
221260
})
222261
}
223262

263+
this.renderedSource = source
224264
this.resetDrawFeatures(resetDraw)
225265
pruneShapes()
226266
}

‎app/javascript/maplibre/layers/geojson/image_overlays.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { defaults } from 'maplibre/styles/defaults'
88
// feature gets its own companion source, keyed by the geojson layer id.
99
const sourcePrefix = layerId => `image-overlay-source-${layerId}-`
1010
const layerIdOf = sourceId => `image-overlay-layer_${sourceId}`
11+
// map.getStyle() would serialize the whole style on every render just to list these
12+
const overlaySources = new Set()
1113

1214
// An image source takes four corners, no more and no less, so only a quadrangle can carry one.
1315
// The ring repeats its first point, so four corners are five coordinates.
@@ -29,7 +31,7 @@ export function renderImageOverlays (features, layerId, visible = true) {
2931
wanted.add(prefix + feature.id)
3032
upsertImageOverlay(feature, layerId, visible)
3133
})
32-
Object.keys(map.getStyle().sources)
34+
Array.from(overlaySources)
3335
.filter(id => id.startsWith(prefix) && !wanted.has(id))
3436
.forEach(removeImageOverlay)
3537
}
@@ -51,6 +53,7 @@ function upsertImageOverlay (feature, layerId, visible) {
5153
const url = feature.properties['fill-image-url']
5254
const coordinates = corners(feature)
5355
const source = map.getSource(sourceId)
56+
overlaySources.add(sourceId)
5457
if (!source) {
5558
map.addSource(sourceId, { type: 'image', url, coordinates })
5659
// below the polygon outline of its own layer, sortLayers() keeps it there on a re-sort
@@ -71,6 +74,7 @@ function upsertImageOverlay (feature, layerId, visible) {
7174
}
7275

7376
function removeImageOverlay (sourceId) {
77+
overlaySources.delete(sourceId)
7478
if (map.getLayer(layerIdOf(sourceId))) { map.removeLayer(layerIdOf(sourceId)) }
7579
if (map.getSource(sourceId)) { map.removeSource(sourceId) }
7680
}

‎app/javascript/maplibre/layers/geojson/levels.js‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎app/javascript/maplibre/layers/indoor/indoor.js‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ export class IndoorLayer extends Layer {
110110
e.preventDefault()
111111
}
112112

113-
map.on('click', this.getStyleLayerIds(), this.clickHandler)
113+
// map.off must get this same list, and getStyleLayerIds() is empty once the style is gone
114+
this.clickLayerIds = this.getStyleLayerIds()
115+
map.on('click', this.clickLayerIds, this.clickHandler)
114116

115117
this.contextMenuHandler = (e_event) => {
116118
e_event.preventDefault()
@@ -139,7 +141,7 @@ export class IndoorLayer extends Layer {
139141
// this layer registers its click handler delegated to style layer ids, which
140142
// map.off('click', handler) in the base class cannot remove
141143
if (this.clickHandler) {
142-
map.off('click', this.getStyleLayerIds(), this.clickHandler)
144+
map.off('click', this.clickLayerIds, this.clickHandler)
143145
this.clickHandler = null
144146
}
145147
super.removeEventHandlers()
@@ -221,10 +223,6 @@ export class IndoorLayer extends Layer {
221223

222224
if (JSON.stringify(newLevels) !== JSON.stringify(this.levels)) {
223225
this.levels = newLevels
224-
// console.log('Indoor layer: detected levels', newLevels)
225-
this.updateLevelControl()
226-
} else if (this.levels.length > 0 && !this.levelControl) {
227-
// Recreate control if it was removed (e.g., layer was hidden then shown)
228226
this.updateLevelControl()
229227
}
230228
}, `indoor-level-${this.id}`, 500)
@@ -235,6 +233,8 @@ export class IndoorLayer extends Layer {
235233
}
236234

237235
removeLevelDetection() {
236+
// a hidden layer drops out of detectLevels(), so the next show must report its levels again
237+
this.levels = []
238238
if (this.initialTimeout) {
239239
clearTimeout(this.initialTimeout)
240240
this.initialTimeout = null

0 commit comments

Comments
 (0)