From c336539608f157288b43efff6c2aade7bc3bacd6 Mon Sep 17 00:00:00 2001 From: Thomas Schmidt Date: Tue, 3 Feb 2026 00:12:58 +0100 Subject: [PATCH 1/8] Handle geojson layers like other layers --- .../controllers/feature/edit_controller.js | 44 ++++++------ .../controllers/feature/modal_controller.js | 25 ++++--- app/javascript/maplibre/controls/shared.js | 2 +- app/javascript/maplibre/edit.js | 9 +-- app/javascript/maplibre/feature.js | 9 +-- app/javascript/maplibre/layers/layers.js | 41 +++++++++++ app/javascript/maplibre/map.js | 67 ++++++------------ app/javascript/maplibre/routing/osrm.js | 5 +- app/javascript/maplibre/styles.js | 18 +++-- app/javascript/maplibre/undo.js | 2 +- app/models/layer.rb | 4 +- public/icons/wikipedia.png | Bin 9770 -> 9767 bytes 12 files changed, 124 insertions(+), 102 deletions(-) diff --git a/app/javascript/controllers/feature/edit_controller.js b/app/javascript/controllers/feature/edit_controller.js index 55d190e72..681809a0f 100644 --- a/app/javascript/controllers/feature/edit_controller.js +++ b/app/javascript/controllers/feature/edit_controller.js @@ -1,6 +1,6 @@ import { Controller } from '@hotwired/stimulus' import { mapChannel } from 'channels/map_channel' -import { geojsonData, redrawGeojson } from 'maplibre/map' +import { redrawGeojson } from 'maplibre/map' import { featureIcon, featureImage, uploadImageToFeature, confirmImageLocation } from 'maplibre/feature' import { handleDelete, draw } from 'maplibre/edit' import { featureColor, featureOutlineColor } from 'maplibre/styles' @@ -21,14 +21,14 @@ export default class extends Controller { delete_feature (e) { if (dom.isInputElement(e.target)) return // Don't trigger if typing in input - const feature = this.getFeature() + const feature = this.getEditFeature() if (confirm(`Really delete this ${feature.geometry.type}?`)) { handleDelete({ features: [feature] }) } } update_feature_raw () { - const feature = this.getFeature() + const feature = this.getEditFeature() document.querySelector('#feature-edit-raw .error').innerHTML = '' try { feature.properties = JSON.parse(document.querySelector('#feature-edit-raw textarea').value) @@ -42,7 +42,7 @@ export default class extends Controller { } updateTitle () { - const feature = this.getFeature() + const feature = this.getEditFeature() const title = document.querySelector('#feature-title-input input').value feature.properties.title = title document.querySelector('#feature-title').textContent = title @@ -50,7 +50,7 @@ export default class extends Controller { } updateLabel () { - const feature = this.getFeature() + const feature = this.getEditFeature() const label = document.querySelector('#feature-label input').value feature.properties.label = label redrawGeojson(false) @@ -59,7 +59,7 @@ export default class extends Controller { // called as preview on slider change updatePointSize () { - const feature = this.getFeature() + const feature = this.getEditFeature() const size = document.querySelector('#point-size').value document.querySelector('#point-size-val').textContent = size feature.properties['marker-size'] = size @@ -69,7 +69,7 @@ export default class extends Controller { } updatePointScaling() { - const feature = this.getFeature() + const feature = this.getEditFeature() const val = document.querySelector('#point-scaling').checked feature.properties['marker-scaling'] = val // draw layer feature properties aren't getting updated by draw.set() @@ -79,7 +79,7 @@ export default class extends Controller { // called as preview on slider change updateLineWidth () { - const feature = this.getFeature() + const feature = this.getEditFeature() const size = document.querySelector('#line-width').value document.querySelector('#line-width-val').textContent = size feature.properties['stroke-width'] = size @@ -90,7 +90,7 @@ export default class extends Controller { // called as preview on slider change updateOutLineWidth () { - const feature = this.getFeature() + const feature = this.getEditFeature() const size = document.querySelector('#outline-width').value document.querySelector('#outline-width-val').textContent = size feature.properties['stroke-width'] = size @@ -101,7 +101,7 @@ export default class extends Controller { // called as preview on slider change updateFillExtrusionHeight () { - const feature = this.getFeature() + const feature = this.getEditFeature() const size = document.querySelector('#fill-extrusion-height').value document.querySelector('#fill-extrusion-height-val').textContent = size + 'm' feature.properties['fill-extrusion-height'] = Number(size) @@ -112,7 +112,7 @@ export default class extends Controller { } updateOpacity () { - const feature = this.getFeature() + const feature = this.getEditFeature() const opacity = document.querySelector('#opacity').value / 10 document.querySelector('#opacity-val').textContent = opacity * 100 + '%' feature.properties['fill-opacity'] = opacity @@ -122,7 +122,7 @@ export default class extends Controller { } updateStrokeColor () { - const feature = this.getFeature() + const feature = this.getEditFeature() const color = document.querySelector('#stroke-color').value feature.properties.stroke = color // draw layer feature properties aren't getting updated by draw.set() @@ -131,7 +131,7 @@ export default class extends Controller { } updateStrokeColorTransparent () { - const feature = this.getFeature() + const feature = this.getEditFeature() let color if (document.querySelector('#stroke-color-transparent').checked) { color = 'transparent' @@ -146,7 +146,7 @@ export default class extends Controller { } updateFillColor () { - const feature = this.getFeature() + const feature = this.getEditFeature() const color = document.querySelector('#fill-color').value if (feature.geometry.type === 'Polygon' || feature.geometry.type === 'MultiPolygon') { feature.properties.fill = color } if (feature.geometry.type === 'Point') { feature.properties['marker-color'] = color } @@ -154,7 +154,7 @@ export default class extends Controller { } updateFillColorTransparent () { - const feature = this.getFeature() + const feature = this.getEditFeature() let color if (document.querySelector('#fill-color-transparent').checked) { color = 'transparent' @@ -170,7 +170,7 @@ export default class extends Controller { } updateShowKmMarkers () { - const feature = this.getFeature() + const feature = this.getEditFeature() if (document.querySelector('#show-km-markers').checked) { feature.properties['show-km-markers'] = true // feature.properties['stroke-image-url'] = "/icons/direction-arrow.png" @@ -182,7 +182,7 @@ export default class extends Controller { } updateMarkerSymbol () { - const feature = this.getFeature() + const feature = this.getEditFeature() let symbol = document.querySelector('#marker-symbol').value document.querySelector('#emoji').textContent = symbol // strip variation selector (emoji) U+FE0F to match icon file names @@ -195,7 +195,7 @@ export default class extends Controller { } async updateMarkerImage () { - const feature = this.getFeature() + const feature = this.getEditFeature() const image = document.querySelector('#marker-image').files[0] const imageLocation = await confirmImageLocation(image) if (imageLocation) { feature.geometry.coordinates = imageLocation } @@ -265,19 +265,19 @@ export default class extends Controller { } saveFeature () { - const feature = this.getFeature() + const feature = this.getEditFeature() status('Saving feature ' + feature.id) // send shallow copy of feature to avoid changes during send mapChannel.send_message('update_feature', { ...feature }) } addUndo() { - const feature = this.getFeature() + const feature = this.getEditFeature() addUndoState('Feature property update', feature) } - getFeature () { + getEditFeature () { const id = this.featureIdValue - return geojsonData.features.find(f => f.id === id) + return getFeature(id) } } diff --git a/app/javascript/controllers/feature/modal_controller.js b/app/javascript/controllers/feature/modal_controller.js index a4c460444..ce8354004 100644 --- a/app/javascript/controllers/feature/modal_controller.js +++ b/app/javascript/controllers/feature/modal_controller.js @@ -1,6 +1,5 @@ import { Controller } from '@hotwired/stimulus' import { mapChannel } from 'channels/map_channel' -import { geojsonData } from 'maplibre/map' import { defaultLineWidth, featureColor, featureOutlineColor } from 'maplibre/styles' import { AnimateLineAnimation, AnimatePolygonAnimation, animateViewFromProperties } from 'maplibre/animations' import { status } from 'helpers/status' @@ -33,12 +32,12 @@ export default class extends Controller { this.show_feature_edit_ui() // add feature to draw - const feature = this.getFeature() + const feature = this.getSelectedFeature() draw.add(feature) select(feature) } else { // repeated click on the current edit mode returns to feature description - showFeatureDetails(this.getFeature()) + showFeatureDetails(this.getSelectedFeature()) unselect() } document.querySelector('#feature-edit-raw .error').innerHTML = '' @@ -49,7 +48,7 @@ export default class extends Controller { if (this.element.classList.contains('modal-pull-down')) { this.pullUpModal(this.element) } - const feature = this.getFeature() + const feature = this.getSelectedFeature() dom.showElements(['#feature-edit-ui', '#button-add-label', '#button-add-desc']) dom.hideElements(['#feature-edit-raw', '#feature-label', '#feature-desc']) functions.e('em-emoji-picker', e => { e.remove() }) @@ -134,7 +133,7 @@ export default class extends Controller { if (this.element.classList.contains('modal-pull-down')) { this.pullUpModal(this.element) } - const feature = this.getFeature() + const feature = this.getSelectedFeature() dom.hideElements(['#feature-edit-ui']) dom.showElements(['#feature-edit-raw']) document.querySelector('#feature-edit-raw textarea') @@ -142,7 +141,7 @@ export default class extends Controller { } show_add_label () { - document.querySelector('#feature-label input').value = this.getFeature().properties.label || null + document.querySelector('#feature-label input').value = this.getSelectedFeature().properties.label || null dom.hideElements(['#button-add-label']) dom.showElements(['#feature-label']) } @@ -153,7 +152,7 @@ export default class extends Controller { // https://github.com/Ionaru/easy-markdown-editor await import('easymde') // import EasyMDE UMD bundle if (easyMDE) { easyMDE.toTextArea() } - document.querySelector('#feature-desc-input').value = this.getFeature().properties.desc || '' + document.querySelector('#feature-desc-input').value = this.getSelectedFeature().properties.desc || '' easyMDE = new window.EasyMDE({ element: document.getElementById('feature-desc-input'), placeholder: 'Add a description text', @@ -168,7 +167,7 @@ export default class extends Controller { } updateDesc () { - const feature = this.getFeature() + const feature = this.getSelectedFeature() try { if (easyMDE && feature.properties.desc !== easyMDE.value()) { feature.properties.desc = easyMDE.value() @@ -181,7 +180,7 @@ export default class extends Controller { } saveFeature () { - const feature = this.getFeature() + const feature = this.getSelectedFeature() status('Saving feature ' + feature.id) // send shallow copy of feature to avoid changes during send mapChannel.send_message('update_feature', { ...feature }) @@ -214,16 +213,16 @@ export default class extends Controller { modal.style.removeProperty('height') } - getFeature () { + getSelectedFeature () { const id = this.featureIdValue - return geojsonData.features.find(f => f.id === id) + return getFeature(id) } async copy(event) { if (functions.isFormFieldFocused()) { return } if (!highlightedFeatureId) { return } - const feature = this.getFeature() + const feature = this.getSelectedFeature() if (feature) { await navigator.clipboard.writeText(JSON.stringify(feature)) event.preventDefault() @@ -234,7 +233,7 @@ export default class extends Controller { } animate () { - const feature = this.getFeature() + const feature = this.getSelectedFeature() console.log('Animating ' + feature.id) if (feature.geometry.type === 'LineString') { new AnimateLineAnimation().run(feature) diff --git a/app/javascript/maplibre/controls/shared.js b/app/javascript/maplibre/controls/shared.js index 17d88a684..f79873d7b 100644 --- a/app/javascript/maplibre/controls/shared.js +++ b/app/javascript/maplibre/controls/shared.js @@ -204,7 +204,7 @@ export function initLayersModal () { listItem.classList.add('flex-center') listItem.classList.add('align-items-center') listItem.setAttribute('data-feature-id', feature.id) - const source = layer.type === 'geojson' ? 'geojson-source' : layer.type + '-source-' + layer.id + const source = layer.type + '-source-' + layer.id listItem.setAttribute('data-feature-source', source) listItem.setAttribute('data-controller', 'map--layers') listItem.setAttribute('data-action', 'click->map--layers#flyToLayerElement') diff --git a/app/javascript/maplibre/edit.js b/app/javascript/maplibre/edit.js index 22589315e..b6366788d 100644 --- a/app/javascript/maplibre/edit.js +++ b/app/javascript/maplibre/edit.js @@ -1,4 +1,4 @@ -import { map, geojsonData, destroyFeature, redrawGeojson, addFeature, layers, mapProperties } from 'maplibre/map' +import { map, destroyFeature, redrawGeojson, addFeature, layers, mapProperties } from 'maplibre/map' import { editStyles } from 'maplibre/edit_styles' import { highlightFeature } from 'maplibre/feature' import { getRouteUpdate, getRouteElevation } from 'maplibre/routing/openrouteservice' @@ -7,6 +7,7 @@ import { mapChannel } from 'channels/map_channel' import { resetControls, initializeDefaultControls } from 'maplibre/controls/shared' import { initializeEditControls, disableEditControls, enableEditControls } from 'maplibre/controls/edit' import { status } from 'helpers/status' +import { hasFeatures, getFeature } from 'maplibre/layers/layers' import { undo, redo, addUndoState } from 'maplibre/undo' import * as functions from 'helpers/functions' import equal from 'fast-deep-equal' // https://github.com/epoberezkin/fast-deep-equal @@ -74,14 +75,14 @@ export async function initializeEditMode () { // Show map settings modal on untouched map map.once('load', function (_e) { - if (!mapProperties.name && !geojsonData?.features?.length && !layers?.filter(l => l.type !== 'geojson').length) { + if (!mapProperties.name && !hasFeatures('geojson') && !layers?.filter(l => l.type !== 'geojson').length) { functions.e('.maplibregl-ctrl-map', e => { e.click() }) } }) map.on('geojson.load', function (_e) { const urlFeatureId = new URLSearchParams(window.location.search).get('f') - const feature = geojsonData.features.find(f => f.id === urlFeatureId) + const feature = getFeature(urlFeatureId) if (feature) { map.fire('draw.selectionchange', {features: [feature]}) } }) @@ -275,7 +276,7 @@ function handleCreate (e) { async function handleUpdate (e) { let feature = e.features[0] // Assuming one feature is updated at a time - const geojsonFeature = geojsonData.features.find(f => f.id === feature.id) + const geojsonFeature = getFeature(feature.id) // mapbox-gl-draw-waypoint sends empty update when dragging on selected feature if (equal(geojsonFeature.geometry, feature.geometry)) { // console.log('Feature update event triggered without update') diff --git a/app/javascript/maplibre/feature.js b/app/javascript/maplibre/feature.js index bd6635659..fc8911b5b 100644 --- a/app/javascript/maplibre/feature.js +++ b/app/javascript/maplibre/feature.js @@ -1,4 +1,4 @@ -import { map, geojsonData, layers, mapProperties } from 'maplibre/map' +import { map, layers, mapProperties } from 'maplibre/map' import * as f from 'helpers/functions' import * as dom from 'helpers/dom' import { marked } from 'marked' @@ -9,6 +9,7 @@ import { area } from "@turf/area" import { along } from "@turf/along" import { buffer } from "@turf/buffer" import { lineString, multiLineString, polygon, multiPolygon } from "@turf/helpers" +import { getFeature, getFeatures } from "maplibre/layers/layers" window.marked = marked @@ -89,7 +90,7 @@ export async function showFeatureDetails (feature) { dom.hideElements(['#feature-edit-raw', '#feature-edit-ui']) f.e('#edit-buttons button', (e) => { e.classList.remove('active') }) // allow edit in rw mode for geojson features only - if (window.gon.map_mode === 'rw' && geojsonData.features.find(f => f.id === feature.id)) { + if (window.gon.map_mode === 'rw' && getFeature(feature.id)) { document.querySelector('#edit-buttons').classList.remove('hidden') } dom.showElements('#feature-details-body') @@ -366,7 +367,7 @@ export function initializeKmMarkerStyles () { export function renderKmMarkers () { let kmMarkerFeatures = [] - geojsonData.features.filter(feature => (feature.geometry.type === 'LineString' && + getFeatures('geojson').filter(feature => (feature.geometry.type === 'LineString' && feature.properties['show-km-markers'] && feature.geometry.coordinates.length >= 2)).forEach((f, index) => { @@ -406,7 +407,7 @@ export function renderExtrusionLines () { // Disable extrusionlines on 3D terrain, it does not work if (mapProperties.terrain) { return [] } - let extrusionLines = geojsonData.features.filter(feature => ( + let extrusionLines = getFeatures('geojson').filter(feature => ( feature.geometry.type === 'LineString' && feature.properties['fill-extrusion-height'] && feature.geometry.coordinates.length !== 1 // don't break line animation diff --git a/app/javascript/maplibre/layers/layers.js b/app/javascript/maplibre/layers/layers.js index 2f1ebc90a..872363f89 100644 --- a/app/javascript/maplibre/layers/layers.js +++ b/app/javascript/maplibre/layers/layers.js @@ -1,18 +1,59 @@ import { initializeWikipediaLayers, loadWikipediaLayer } from 'maplibre/layers/wikipedia' import { initializeOverpassLayers, loadOverpassLayer } from 'maplibre/overpass/overpass' import { layers } from 'maplibre/map' +import { initializeViewStyles } from 'maplibre/styles' +import { map, addGeoJSONSource, redrawGeojson } from 'maplibre/map' +import * as functions from 'helpers/functions' + // initialize layers: create source, apply styles and load data export function initializeLayers(id = null) { + + // draw geojson layer before loading overpass layers + //geojsonData = mergedGeoJSONLayers() + + let initLayers = layers.filter(l => l.type === 'geojson') + if (id) { initLayers = initLayers.filter(l => l.id === id) } + console.log('Initializing geojson layers: ', initLayers) + initLayers.forEach((layer) => { + addGeoJSONSource('geojson-source-' + layer.id, false) + initializeViewStyles('geojson-source-' + layer.id) + }) + redrawGeojson() + functions.e('#maplibre-map', e => { e.setAttribute('data-geojson-loaded', true) }) + map.fire('geojson.load', { detail: { message: 'geojson source loaded' } }) + + //initializeGeoJSONLayers(id) initializeOverpassLayers(id) initializeWikipediaLayers(id) } export function loadLayer(id) { const layer = layers.find(f => f.id === id) + //if (layer.type === 'geojson') { + // return loadGeoJSONLayer(id) + //} else if (layer.type === 'wikipedia') { if (layer.type === 'wikipedia') { return loadWikipediaLayer(id) } else if (layer.type === 'overpass') { return loadOverpassLayer(id) } +} + +export function getFeature(id) { + for (const layer of layers) { + if (layer.geojson) { + const feature = layer.geojson.features.find(f => f.id === id) + if (feature) { return feature } + } + } + return null +} + +export function getFeatures(type = 'geojson') { + return layers.filter(l => l.type === type).flatMap(l => l.geojson?.features || []) +} + +export function hasFeatures(type = 'geojson') { + return layers.some(l => l.type === type && l.geojson?.features?.length > 0) } \ No newline at end of file diff --git a/app/javascript/maplibre/map.js b/app/javascript/maplibre/map.js index 46f946268..8b5dfba3f 100644 --- a/app/javascript/maplibre/map.js +++ b/app/javascript/maplibre/map.js @@ -11,12 +11,11 @@ import { draw, select } from 'maplibre/edit' import { highlightFeature, resetHighlightedFeature, renderKmMarkers, renderExtrusionLines, initializeKmMarkerStyles } from 'maplibre/feature' import { initializeViewStyles, setStyleDefaultFont, loadImage } from 'maplibre/styles' -import { initializeLayers } from 'maplibre/layers/layers' +import { initializeLayers, getFeature } from 'maplibre/layers/layers' import { centroid } from "@turf/centroid" export let map export let layers // [{ id:, type: "overpass"||"geojson", name:, query:, geojson: { type: 'FeatureCollection', features: [] } }] -export let geojsonData //= { type: 'FeatureCollection', features: [] } export let mapProperties export let lastMousePosition export let backgroundMapLayer @@ -55,13 +54,11 @@ export function initializeMaplibreProperties () { // reset map data export function resetLayers () { functions.e('#maplibre-map', e => { e.setAttribute('data-geojson-loaded', false) }) - geojsonData = null layers = [] } export function resetGeojsonLayers () { functions.e('#maplibre-map', e => { e.setAttribute('data-geojson-loaded', false) }) - geojsonData = null layers = layers.filter(l => l.type !== 'geojson') } @@ -119,7 +116,7 @@ export async function initializeMap (divId = 'maplibre-map') { console.log("Map loaded ('load')") const urlFeatureId = new URLSearchParams(window.location.search).get('f') - let feature = geojsonData?.features?.find(f => f.id === urlFeatureId) + let feature = getFeature(urlFeatureId) if (feature) { resetControls() highlightFeature(feature, true) @@ -127,7 +124,7 @@ export async function initializeMap (divId = 'maplibre-map') { map.setCenter(center.geometry.coordinates) } const urlFeatureAnimateId = new URLSearchParams(window.location.search).get('a') - feature = geojsonData?.features?.find(f => f.id === urlFeatureAnimateId) + feature = getFeature(urlFeatureAnimateId) if (feature) { console.log('Animating ' + feature.id) resetControls() @@ -188,7 +185,7 @@ export function addGeoJSONSource (sourceName, cluster=true ) { map.addSource(sourceName, { type: 'geojson', promoteId: 'id', - data: { type: 'FeatureCollection', features: [] }, // geojsonData, + data: { type: 'FeatureCollection', features: [] }, cluster: cluster, clusterMaxZoom: 14, clusterRadius: 50 @@ -214,8 +211,8 @@ export function removeGeoJSONSource(sourceName) { } export function loadLayers () { - // return if all layers already loaded (eg. in case of basemap style change) - if (geojsonData && gon.map_layers.length == layers.length) { + // do not reload from server if all layers already loaded (eg. in case of basemap style change) + if (gon.map_layers.length == layers.length) { // console.log('All layers already loaded, re-rendering from cache', layers) initializeLayers() redrawGeojson() @@ -233,26 +230,14 @@ export function loadLayers () { .then(data => { console.log('Loaded map layers from server: ', data.layers) // make sure we're still showing the map the request came from - if (window.gon.map_properties.public_id !== data.properties.public_id){ - return - } - data.layers.filter(f => f.type === 'geojson').forEach((layer) => { + if (window.gon.map_properties.public_id !== data.properties.public_id) { return } + data.layers.forEach((layer) => { if (!layers.find( l => l.id === layer.id) ) { layers.push(layer) } }) - // draw geojson layer before loading overpass layers - geojsonData = mergedGeoJSONLayers() - redrawGeojson() - functions.e('#maplibre-map', e => { e.setAttribute('data-geojson-loaded', true) }) - map.fire('geojson.load', { detail: { message: 'geojson-source loaded' } }) - - data.layers.filter(f => f.type !== 'geojson').forEach((layer) => { - if (!layers.find(l => l.id === layer.id)) { layers.push(layer) } - }) initializeLayers() }) .catch(error => { - console.error('Failed to fetch GeoJSON:', error) - console.error('GeoJSONData:', geojsonData) + console.error('Failed to fetch map layers:', error) }) } @@ -409,7 +394,7 @@ export function redrawGeojson (resetDraw = true) { draw.deleteAll() drawFeatureIds.forEach((featureId) => { - let feature = geojsonData.features.find(f => f.id === featureId) + let feature = getFeature(featureId) if (feature) { draw.add(feature) // if we're in edit mode, re-select feature @@ -420,9 +405,9 @@ export function redrawGeojson (resetDraw = true) { } // updateData requires a 'GeoJSONSourceDiff', with add/update/remove lists - map.getSource('geojson-source').setData(renderedGeojsonData()) + //map.getSource('geojson-source').setData(renderedGeojsonData()) console.log('layers:', layers) - layers.filter(f => f.type !== 'geojson').forEach((layer) => { + layers.forEach((layer) => { if (layer.geojson) { console.log("Setting layer data", layer.type, layer.id, layer.geojson) map.getSource(layer.type + '-source-' + layer.id).setData(layer.geojson, false) @@ -440,7 +425,7 @@ export function renderedGeojsonData () { } export function upsert (updatedFeature) { - const feature = geojsonData.features.find(f => f.id === updatedFeature.id) + const feature = getFeature(updatedFeature.id) if (!feature) { addFeature(updatedFeature); return } // only update feature if it was changed, disregard properties.id @@ -454,7 +439,7 @@ export function upsert (updatedFeature) { export function addFeature (feature) { feature.properties.id = feature.id layers.find(l => l.type === 'geojson').geojson.features.push(feature) - geojsonData = mergedGeoJSONLayers() + //geojsonData = mergedGeoJSONLayers() redrawGeojson(false) status('Added feature') } @@ -475,9 +460,8 @@ function updateFeature (feature, updatedFeature) { } export function destroyFeature (featureId) { - if (geojsonData.features.find(f => f.id === featureId)) { + if (getFeature(featureId)) { status('Deleting feature ' + featureId) - geojsonData.features = geojsonData.features.filter(f => f.id !== featureId) layers.forEach(l => l.geojson.features = l.geojson.features.filter(f => f.id !== featureId)) redrawGeojson() resetHighlightedFeature() @@ -488,7 +472,7 @@ export function destroyFeature (featureId) { // load geojson data function initializeStyles() { console.log('Initializing sources and layer styles after basemap load/change') - addGeoJSONSource('geojson-source', false) + addGeoJSONSource('km-marker-source', false) loadLayers() demSource.setupMaplibre(maplibregl) @@ -496,7 +480,7 @@ function initializeStyles() { if (mapProperties.hillshade) { addHillshade() } if (mapProperties.globe) { addGlobe() } if (mapProperties.contours) { addContours() } - initializeViewStyles('geojson-source') + // initializeViewStyles('geojson-source') initializeKmMarkerStyles() } @@ -556,19 +540,15 @@ export function sortLayers () { const pointsLayerHits = functions.reduceArray(layers, (e) => e.id === 'points-hit-layer_geojson-source') const directions = functions.reduceArray(layers, (e) => (e.id.startsWith('maplibre-gl-directions'))) const heatmap = functions.reduceArray(layers, (e) => (e.id.startsWith('heatmap-layer'))) + const kmMarkers = functions.reduceArray(layers, (e) => (e.id.includes('km-marker'))) layers = layers.concat(flatLayers).concat(lineLayers).concat(mapExtrusions).concat(directions) .concat(mapSymbols).concat(points).concat(heatmap).concat(editLayer) .concat(lineLayerHits).concat(pointsLayerHits) - .concat(userSymbols).concat(userLabels) + .concat(kmMarkers).concat(userSymbols).concat(userLabels) const newStyle = { ...currentStyle, layers } map.setStyle(newStyle, { diff: true }) - - // place km markers under symbols layer (icons) - layers.filter(layer => layer.id.includes('km-marker')).forEach((layer) => { - map.moveLayer(layer.id, 'symbols-layer_geojson-source') - }) console.log("Sorted layers:", map.getStyle().layers) } @@ -587,7 +567,7 @@ export function mergedGeoJSONLayers(type='geojson') { } export function frontFeature(frontFeature) { - // move feature to end of its layer's features array (for overpass) + // move feature to end of its layer's features array for (const layer of layers) { if (!layer?.geojson?.features) { continue } const features = layer.geojson.features @@ -598,13 +578,6 @@ export function frontFeature(frontFeature) { break // done, exit loop } } - // move feature to end of geojsonData features array - const features = geojsonData.features - const idx = features.findIndex(f => f.id === frontFeature.id) - if (idx !== -1) { - const [feature] = features.splice(idx, 1) // Remove it - features.push(feature) // Add to end - } redrawGeojson() } diff --git a/app/javascript/maplibre/routing/osrm.js b/app/javascript/maplibre/routing/osrm.js index f91dea733..0b5411ecf 100644 --- a/app/javascript/maplibre/routing/osrm.js +++ b/app/javascript/maplibre/routing/osrm.js @@ -1,6 +1,6 @@ import { layersFactory } from "@maplibre/maplibre-gl-directions" import CustomMapLibreGlDirections from "maplibre/routing/custom_directions" -import { map, mapProperties, upsert, geojsonData } from 'maplibre/map' +import { map, mapProperties, upsert } from 'maplibre/map' import { highlightColor } from 'maplibre/edit_styles' import { updateElevation, setSelectedFeature } from 'maplibre/edit' import { styles, featureColor } from 'maplibre/styles' @@ -11,6 +11,7 @@ import { status } from 'helpers/status' import * as functions from 'helpers/functions' import { showFeatureDetails } from 'maplibre/feature' import { addUndoState } from 'maplibre/undo' +import { getFeature } from 'maplibre/layers/layers' // https://github.com/maplibre/maplibre-gl-directions // Examples: https://maplibre.org/maplibre-gl-directions/#/examples @@ -121,7 +122,7 @@ export function initDirections (profile, feature) { } function updateTrack(feature) { - let geojsonFeature = geojsonData.features.find(f => f.id === feature.id) + let geojsonFeature = getFeature(feature.id) if (geojsonFeature) { // store undo state from unchanged feature addUndoState('Track update', geojsonFeature) diff --git a/app/javascript/maplibre/styles.js b/app/javascript/maplibre/styles.js index 25a87f1fb..662f2f35f 100644 --- a/app/javascript/maplibre/styles.js +++ b/app/javascript/maplibre/styles.js @@ -1,10 +1,11 @@ -import { map, frontFeature, removeStyleLayers, geojsonData } from 'maplibre/map' +import { map, frontFeature, removeStyleLayers } from 'maplibre/map' import { highlightedFeatureId, stickyFeatureHighlight, highlightedFeatureSource, resetHighlightedFeature, highlightFeature } from 'maplibre/feature' import { draw } from 'maplibre/edit' import { flyToFeature } from 'maplibre/animations' +import { getFeature } from 'maplibre/layers/layers' export const viewStyleNames = [ 'polygon-layer', @@ -17,7 +18,6 @@ export const viewStyleNames = [ 'points-layer-flat', 'points-layer', 'points-hit-layer', - 'heatmap-layer', 'symbols-layer-flat', 'symbols-layer', 'text-layer-flat', @@ -28,11 +28,13 @@ export const viewStyleNames = [ export function setStyleDefaultFont (font) { labelFont = [font] } -export function initializeViewStyles (sourceName) { +export function initializeViewStyles (sourceName, heatmap=false) { + console.log('Initializing view styles for source ' + sourceName) removeStyleLayers(sourceName) viewStyleNames.forEach(styleName => { map.addLayer(setSource(styles()[styleName], sourceName)) }) + if (heatmap) { map.addLayer(setSource(styles()['heatmap-layer'], sourceName)) } // console.log('View styles added for source ' + sourceName) // click is needed to select on mobile and for sticky highlight @@ -47,7 +49,7 @@ export function initializeViewStyles (sourceName) { } if (e.features[0].properties?.onclick === 'feature' && e.features[0].properties?.['onclick-target']) { const targetId = e.features[0].properties?.['onclick-target'] - const feature = geojsonData.features.find(f => f.id === targetId) + const feature = getFeature(targetId) if (feature) { flyToFeature(feature) } else { @@ -537,9 +539,11 @@ export function styles () { 'heatmap-layer': { id: 'heatmap-layer', type: 'heatmap', - filter: ['all', - ['any', ["has", "heatmap"], ["has", "user_heatmap"]], - minZoomFilter], + filter: [ + "all", + ["==", ["geometry-type"], "Point"], + minZoomFilter + ], paint: { 'heatmap-opacity': 0.7, 'heatmap-intensity': 1.3, diff --git a/app/javascript/maplibre/undo.js b/app/javascript/maplibre/undo.js index dd5faa624..9fcf3be93 100644 --- a/app/javascript/maplibre/undo.js +++ b/app/javascript/maplibre/undo.js @@ -1,4 +1,4 @@ -import { geojsonData, redrawGeojson, addFeature, destroyFeature } from 'maplibre/map' +import { redrawGeojson, addFeature, destroyFeature } from 'maplibre/map' import { select, selectedFeature } from 'maplibre/edit' import { showFeatureDetails } from 'maplibre/feature' import { resetDirections } from 'maplibre/routing/osrm' diff --git a/app/models/layer.rb b/app/models/layer.rb index 095a01220..9f65f9893 100644 --- a/app/models/layer.rb +++ b/app/models/layer.rb @@ -12,13 +12,15 @@ class Layer field :type field :name field :query + field :heatmap, type: Boolean + field :cluster, type: Boolean field :features_count, type: Integer, default: 0 after_save :broadcast_update, if: -> { map.present? } after_destroy :broadcast_destroy, if: -> { map.present? } def to_summary_json - json = { id: id, type: type, name: name } + json = { id: id, type: type, name: name, heatmap: !!heatmap, cluster: !!cluster } json[:query] = query if type == "overpass" json end diff --git a/public/icons/wikipedia.png b/public/icons/wikipedia.png index f8f7fdcc995899fc4678ca9b3fbfbf26f1aeacc1..c93881c597c77d43df8e110b920973632836b5ac 100644 GIT binary patch literal 9767 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 literal 9770 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 F{{b68fk6NO From e30eb464ad6cd689676a4a7003e17307899f787d Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 21:25:11 +0000 Subject: [PATCH 2/8] Update @turf/bbox to version 7.3.3 --- config/importmap.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/importmap.rb b/config/importmap.rb index d66cff4fd..c897e2a0d 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -75,7 +75,7 @@ pin "@turf/projection", to: "@turf--projection.js", preload: false # @7.3.2 pin "@turf/clone", to: "@turf--clone.js", preload: false # @7.3.2 pin "@turf/helpers", to: "@turf--helpers.js" # @7.3.3 -pin "@turf/meta", to: "@turf--meta.js" # @7.3.2 +pin "@turf/meta", to: "@turf--meta.js" # @7.3.3 # Turf libs needed by app pin "@turf/simplify", to: "@turf--simplify.js", preload: false # @7.3.2 pin "@turf/boolean-point-on-line", to: "@turf--boolean-point-on-line.js", preload: false # @7.3.2 @@ -89,7 +89,7 @@ pin "@turf/length", to: "@turf--length.js", preload: false # @7.3.2 pin "@turf/area", to: "@turf--area.js", preload: false # @7.3.2 pin "@turf/buffer", to: "@turf--buffer.js", preload: false # @7.3.2 -pin "@turf/bbox", to: "@turf--bbox.js", preload: false # @7.3.2 +pin "@turf/bbox", to: "@turf--bbox.js", preload: false # @7.3.3 pin "@turf/center", to: "@turf--center.js", preload: false # @7.3.2 pin "@turf/jsts", to: "@turf--jsts.js", preload: false # @2.7.2 # dependencies of turf/buffer From 0fe100320e64d9fb277c95e160195119dd577206 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 21:41:20 +0000 Subject: [PATCH 3/8] Update @turf/boolean-point-on-line to version 7.3.3 --- config/importmap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/importmap.rb b/config/importmap.rb index c897e2a0d..0a650a294 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -78,7 +78,7 @@ pin "@turf/meta", to: "@turf--meta.js" # @7.3.3 # Turf libs needed by app pin "@turf/simplify", to: "@turf--simplify.js", preload: false # @7.3.2 -pin "@turf/boolean-point-on-line", to: "@turf--boolean-point-on-line.js", preload: false # @7.3.2 +pin "@turf/boolean-point-on-line", to: "@turf--boolean-point-on-line.js", preload: false # @7.3.3 pin "@turf/clean-coords", to: "@turf--clean-coords.js", preload: false # @7.3.2 pin "@turf/invariant", to: "@turf--invariant.js", preload: false # @7.3.3 pin "@turf/centroid", to: "@turf--centroid.js", preload: false # @7.3.2 From 65b7335ef674f7037611d01693926908235cc978 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:15 +0000 Subject: [PATCH 4/8] Update rubycritic to version 4.12.0 --- Gemfile.lock | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 02aededb3..6b34d1019 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,8 +91,8 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) amazing_print (2.0.0) anyway_config (2.7.2) ruby-next-core (~> 1.0) @@ -164,23 +164,23 @@ GEM dry-configurable (1.3.0) dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-core (1.1.0) + dry-core (1.2.0) concurrent-ruby (~> 1.0) logger zeitwerk (~> 2.6) - dry-inflector (1.2.0) + dry-inflector (1.3.1) dry-initializer (3.2.0) dry-logic (1.6.0) bigdecimal concurrent-ruby (~> 1.0) dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-schema (1.14.1) + dry-schema (1.15.0) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) dry-core (~> 1.1) dry-initializer (~> 3.2) - dry-logic (~> 1.5) + dry-logic (~> 1.6) dry-types (~> 1.8) zeitwerk (~> 2.6) dry-types (1.9.0) @@ -210,14 +210,14 @@ GEM webrick (~> 1.7) websocket-driver (~> 0.7) ffi (1.17.3-x86_64-linux-gnu) - flay (2.13.3) + flay (2.14.2) erubi (~> 1.10) - path_expander (~> 1.0) - ruby_parser (~> 3.0) + path_expander (~> 2.0) + prism (~> 1.7) sexp_processor (~> 4.0) - flog (4.8.0) - path_expander (~> 1.0) - ruby_parser (~> 3.1, > 3.1.0) + flog (4.9.4) + path_expander (~> 2.0) + prism (~> 1.7) sexp_processor (~> 4.8) globalid (1.3.0) activesupport (>= 6.1) @@ -354,7 +354,7 @@ GEM parser (3.3.10.1) ast (~> 2.4.1) racc - path_expander (1.1.3) + path_expander (2.0.1) pp (0.6.3) prettyprint prettyprint (0.2.0) @@ -364,7 +364,7 @@ GEM psych (5.3.1) date stringio - public_suffix (6.0.2) + public_suffix (7.0.2) puma (7.2.0) nio4r (~> 2.0) puppeteer-ruby (0.45.6) @@ -516,11 +516,12 @@ GEM ruby_parser (3.22.0) racc (~> 1.5) sexp_processor (~> 4.16) - rubycritic (4.11.0) + rubycritic (4.12.0) flay (~> 2.13) flog (~> 4.7) launchy (>= 2.5.2) parser (>= 3.3.0.5) + prism (>= 1.6.0) rainbow (~> 3.1.1) reek (~> 6.5.0, < 7.0) rexml From ece12245c6ea1f5753525d173170318f5fd98ded Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 02:10:14 +0000 Subject: [PATCH 5/8] Update globals to version 17.3.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0660d1859..a57945df4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "@eslint/js": "^9.39.2", "eslint": "^9.39.2", - "globals": "^17.2.0", + "globals": "^17.3.0", "stylelint": "^16.26.1", "stylelint-config-standard": "^39.0.1" } @@ -1194,9 +1194,9 @@ } }, "node_modules/globals": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", - "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, "engines": { "node": ">=18" diff --git a/package.json b/package.json index a0350b173..f645248d3 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@eslint/js": "^9.39.2", "eslint": "^9.39.2", - "globals": "^17.2.0", + "globals": "^17.3.0", "stylelint": "^16.26.1", "stylelint-config-standard": "^39.0.1" } From a28ebd33aa0c1dfbc6cba81b3f5268de76c6ca87 Mon Sep 17 00:00:00 2001 From: Thomas Schmidt Date: Tue, 3 Feb 2026 23:58:31 +0100 Subject: [PATCH 6/8] generalize layer - source naming --- .../controllers/feature/modal_controller.js | 1 + app/javascript/maplibre/animations.js | 2 +- app/javascript/maplibre/edit_styles.js | 4 +-- app/javascript/maplibre/feature.js | 5 ++-- app/javascript/maplibre/layers/layers.js | 29 +++++++++++++------ app/javascript/maplibre/layers/wikipedia.js | 3 +- app/javascript/maplibre/map.js | 13 ++++++--- app/javascript/maplibre/overpass/overpass.js | 4 +-- 8 files changed, 38 insertions(+), 23 deletions(-) diff --git a/app/javascript/controllers/feature/modal_controller.js b/app/javascript/controllers/feature/modal_controller.js index ce8354004..2129170b8 100644 --- a/app/javascript/controllers/feature/modal_controller.js +++ b/app/javascript/controllers/feature/modal_controller.js @@ -7,6 +7,7 @@ import { showFeatureDetails, highlightedFeatureId } from 'maplibre/feature' import * as functions from 'helpers/functions' import * as dom from 'helpers/dom' import { draw, select, unselect } from 'maplibre/edit' +import { getFeature } from 'maplibre/layers/layers' let easyMDE diff --git a/app/javascript/maplibre/animations.js b/app/javascript/maplibre/animations.js index a5857064a..57c344898 100644 --- a/app/javascript/maplibre/animations.js +++ b/app/javascript/maplibre/animations.js @@ -153,7 +153,7 @@ export function animateViewFromProperties () { }) } -export function flyToFeature(feature, source='geojson-source') { +export function flyToFeature(feature, source) { // Calculate the centroid const center = centroid(feature) console.log('Fly to: ' + feature.id + ' ' + center.geometry.coordinates) diff --git a/app/javascript/maplibre/edit_styles.js b/app/javascript/maplibre/edit_styles.js index bb9510956..758380690 100644 --- a/app/javascript/maplibre/edit_styles.js +++ b/app/javascript/maplibre/edit_styles.js @@ -13,7 +13,7 @@ export const highlightColor = '#fbb03b' const midpointSize = 6 const vertexSize = 6 -export function editStyles () { +export function editStyles() { return [ // removeSource(styles()['polygon-layer']), // gl-draw-polygon-fill-inactive removeSource(styles()['polygon-layer-outline']), @@ -112,7 +112,7 @@ export function editStyles () { }, // inactive single point features removeSource(styles()['points-layer']), - removeSource(styles()['heatmap-layer']), + // removeSource(styles()['heatmap-layer']), // outline border of inactive vertex points on lines + polygons, // rendering outline seperately to generate nicer overlay effect diff --git a/app/javascript/maplibre/feature.js b/app/javascript/maplibre/feature.js index fc8911b5b..3cbb4b91b 100644 --- a/app/javascript/maplibre/feature.js +++ b/app/javascript/maplibre/feature.js @@ -9,7 +9,7 @@ import { area } from "@turf/area" import { along } from "@turf/along" import { buffer } from "@turf/buffer" import { lineString, multiLineString, polygon, multiPolygon } from "@turf/helpers" -import { getFeature, getFeatures } from "maplibre/layers/layers" +import { getFeature, getFeatures, getFeatureSource } from "maplibre/layers/layers" window.marked = marked @@ -253,9 +253,10 @@ export function resetHighlightedFeature () { f.e('#feature-details-modal', e => { e.classList.remove('show') }) } -export function highlightFeature (feature, sticky = false, source = 'geojson-source') { +export function highlightFeature (feature, sticky = false, source) { if (highlightedFeatureId !== feature.id) { resetHighlightedFeature() } // console.log('highlight', feature) + if (!source) { source = getFeatureSource(feature.id) } stickyFeatureHighlight = sticky highlightedFeatureId = feature?.id highlightedFeatureSource = source diff --git a/app/javascript/maplibre/layers/layers.js b/app/javascript/maplibre/layers/layers.js index 872363f89..a3d4ba179 100644 --- a/app/javascript/maplibre/layers/layers.js +++ b/app/javascript/maplibre/layers/layers.js @@ -8,16 +8,17 @@ import * as functions from 'helpers/functions' // initialize layers: create source, apply styles and load data export function initializeLayers(id = null) { - - // draw geojson layer before loading overpass layers - //geojsonData = mergedGeoJSONLayers() - - let initLayers = layers.filter(l => l.type === 'geojson') + let initLayers = layers if (id) { initLayers = initLayers.filter(l => l.id === id) } - console.log('Initializing geojson layers: ', initLayers) initLayers.forEach((layer) => { - addGeoJSONSource('geojson-source-' + layer.id, false) - initializeViewStyles('geojson-source-' + layer.id) + console.log('Adding source for layer', layer.type, layer.id, layer.cluster) + addGeoJSONSource(layer.type + '-source-' + layer.id, layer.cluster) + }) + + // draw geojson layer before loading overpass layers + console.log('Initializing geojson layers') + initLayers.filter(l => l.type === 'geojson').forEach((layer) => { + initializeViewStyles('geojson-source-' + layer.id, !!layer.cluster) }) redrawGeojson() functions.e('#maplibre-map', e => { e.setAttribute('data-geojson-loaded', true) }) @@ -43,7 +44,7 @@ export function loadLayer(id) { export function getFeature(id) { for (const layer of layers) { if (layer.geojson) { - const feature = layer.geojson.features.find(f => f.id === id) + let feature = layer.geojson.features.find(f => f.id === id) if (feature) { return feature } } } @@ -56,4 +57,14 @@ export function getFeatures(type = 'geojson') { export function hasFeatures(type = 'geojson') { return layers.some(l => l.type === type && l.geojson?.features?.length > 0) +} + +export function getFeatureSource(featureId) { + for (const layer of layers) { + if (layer.geojson) { + let feature = layer.geojson.features.find(f => f.id === featureId) + if (feature) { return layer.type + '-source-' + layer.id } + } + } + return null } \ No newline at end of file diff --git a/app/javascript/maplibre/layers/wikipedia.js b/app/javascript/maplibre/layers/wikipedia.js index 49692299b..42545ed98 100644 --- a/app/javascript/maplibre/layers/wikipedia.js +++ b/app/javascript/maplibre/layers/wikipedia.js @@ -1,4 +1,4 @@ -import { map, layers, addGeoJSONSource, redrawGeojson } from 'maplibre/map' +import { map, layers, redrawGeojson } from 'maplibre/map' import { initializeViewStyles } from 'maplibre/styles' import * as functions from 'helpers/functions' import { status } from 'helpers/status' @@ -9,7 +9,6 @@ export function initializeWikipediaLayers(id = null) { if (id) { initLayers = initLayers.filter(l => l.id === id) } initLayers.forEach((layer) => { - addGeoJSONSource('wikipedia-source-' + layer.id, false) initializeViewStyles('wikipedia-source-' + layer.id) loadWikipediaLayer(layer.id) }) diff --git a/app/javascript/maplibre/map.js b/app/javascript/maplibre/map.js index 8b5dfba3f..8849406a7 100644 --- a/app/javascript/maplibre/map.js +++ b/app/javascript/maplibre/map.js @@ -10,7 +10,7 @@ import { initializeViewControls } from 'maplibre/controls/view' import { draw, select } from 'maplibre/edit' import { highlightFeature, resetHighlightedFeature, renderKmMarkers, renderExtrusionLines, initializeKmMarkerStyles } from 'maplibre/feature' -import { initializeViewStyles, setStyleDefaultFont, loadImage } from 'maplibre/styles' +import { setStyleDefaultFont, loadImage } from 'maplibre/styles' import { initializeLayers, getFeature } from 'maplibre/layers/layers' import { centroid } from "@turf/centroid" @@ -178,10 +178,15 @@ function updateCursorPosition(e) { } } -export function addGeoJSONSource (sourceName, cluster=true ) { +// Each map layer has its own source, so different style layers can be applied +// sourceName convention: layer.type + '-source-' + layer.id +export function addGeoJSONSource(sourceName, cluster=false) { // https://maplibre.org/maplibre-style-spec/sources/#geojson // console.log("Adding source: " + sourceName) - if (map.getSource(sourceName)) { return } // source already exists + if (map.getSource(sourceName)) { + console.log('Source ' + sourceName + ' already exists, skipping add') + return + } map.addSource(sourceName, { type: 'geojson', promoteId: 'id', @@ -409,7 +414,7 @@ export function redrawGeojson (resetDraw = true) { console.log('layers:', layers) layers.forEach((layer) => { if (layer.geojson) { - console.log("Setting layer data", layer.type, layer.id, layer.geojson) + console.log("Setting source data for layer", layer.type, layer.id, layer.geojson) map.getSource(layer.type + '-source-' + layer.id).setData(layer.geojson, false) } }) diff --git a/app/javascript/maplibre/overpass/overpass.js b/app/javascript/maplibre/overpass/overpass.js index 6ab5fd702..80485cdbc 100644 --- a/app/javascript/maplibre/overpass/overpass.js +++ b/app/javascript/maplibre/overpass/overpass.js @@ -1,4 +1,4 @@ -import { map, layers, redrawGeojson, addGeoJSONSource, viewUnchanged, sortLayers } from 'maplibre/map' +import { map, layers, redrawGeojson, viewUnchanged, sortLayers } from 'maplibre/map' import { applyOverpassQueryStyle } from 'maplibre/overpass/queries' import { initializeViewStyles, initializeClusterStyles } from 'maplibre/styles' import * as functions from 'helpers/functions' @@ -12,8 +12,6 @@ export function initializeOverpassLayers(id = null) { const clustered = !layer.query.includes("heatmap=true") && !layer.query.includes("cluster=false") && !layer.query.includes("geom") // clustering breaks lines & geometries - // TODO: changing cluster setup requires a map reload - addGeoJSONSource('overpass-source-' + layer.id, clustered) initializeViewStyles('overpass-source-' + layer.id) if (clustered) { const clusterIcon = getCommentValue(layer.query, 'cluster-symbol') || getCommentValue(layer.query, 'cluster-image-url') || From e78f3659bd864ca91a6dfcf01a7baaac8d2c19af Mon Sep 17 00:00:00 2001 From: Thomas Schmidt Date: Wed, 4 Feb 2026 00:01:41 +0100 Subject: [PATCH 7/8] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/javascript/maplibre/layers/layers.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/javascript/maplibre/layers/layers.js b/app/javascript/maplibre/layers/layers.js index a3d4ba179..52e903e12 100644 --- a/app/javascript/maplibre/layers/layers.js +++ b/app/javascript/maplibre/layers/layers.js @@ -41,8 +41,9 @@ export function loadLayer(id) { } } -export function getFeature(id) { - for (const layer of layers) { +export function getFeature(id, type = 'geojson') { + const searchLayers = type ? layers.filter(l => l.type === type) : layers + for (const layer of searchLayers) { if (layer.geojson) { let feature = layer.geojson.features.find(f => f.id === id) if (feature) { return feature } From 875194f72937fd0e8ed061b263d46bfbf8cb7656 Mon Sep 17 00:00:00 2001 From: Thomas Schmidt Date: Wed, 4 Feb 2026 00:06:54 +0100 Subject: [PATCH 8/8] only select geojson features for edit --- app/javascript/maplibre/edit.js | 2 +- app/javascript/maplibre/layers/layers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/maplibre/edit.js b/app/javascript/maplibre/edit.js index b6366788d..efbf2c7a4 100644 --- a/app/javascript/maplibre/edit.js +++ b/app/javascript/maplibre/edit.js @@ -82,7 +82,7 @@ export async function initializeEditMode () { map.on('geojson.load', function (_e) { const urlFeatureId = new URLSearchParams(window.location.search).get('f') - const feature = getFeature(urlFeatureId) + const feature = getFeature(urlFeatureId, 'geojson') if (feature) { map.fire('draw.selectionchange', {features: [feature]}) } }) diff --git a/app/javascript/maplibre/layers/layers.js b/app/javascript/maplibre/layers/layers.js index 52e903e12..e85c72351 100644 --- a/app/javascript/maplibre/layers/layers.js +++ b/app/javascript/maplibre/layers/layers.js @@ -41,7 +41,7 @@ export function loadLayer(id) { } } -export function getFeature(id, type = 'geojson') { +export function getFeature(id, type = null) { const searchLayers = type ? layers.filter(l => l.type === type) : layers for (const layer of searchLayers) { if (layer.geojson) {