From ace42261cf6a531e1c3a21183cff6a230d6970de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20P?= Date: Mon, 14 Sep 2026 16:00:23 +0200 Subject: [PATCH] WIP: test interactive wall layers --- css/app.css | 14 ++ .../murarie/tiberio/reticolata.geojson | 13 ++ webgis/img/icons/bricks.svg | 3 + webgis/js/controllers/menu_controller.js | 7 +- webgis/js/gis.js | 131 ++++++++++++++---- webgis/js/icons.js | 91 ++++++------ webgis/js/layer_options.js | 13 +- webgis/js/state.js | 4 + webgis/js/ui.js | 16 ++- 9 files changed, 210 insertions(+), 82 deletions(-) create mode 100644 webgis/geojson/murarie/tiberio/reticolata.geojson create mode 100644 webgis/img/icons/bricks.svg diff --git a/css/app.css b/css/app.css index 41c1234..54acfa4 100644 --- a/css/app.css +++ b/css/app.css @@ -210,4 +210,18 @@ input:invalid { } .mixed-tech { color: #f0280f; +} +/* Building view top bar */ +.bview-bar { + position: absolute; + top: 4.5rem; + width: 450px; + left: 50%; + transform: translateX(-50%); + border-radius: 8px; +} +/* Map container border for building view */ +.bview-border { + border:#263aac 4px solid; + border-radius: 8px; } \ No newline at end of file diff --git a/webgis/geojson/murarie/tiberio/reticolata.geojson b/webgis/geojson/murarie/tiberio/reticolata.geojson new file mode 100644 index 0000000..44aa3f2 --- /dev/null +++ b/webgis/geojson/murarie/tiberio/reticolata.geojson @@ -0,0 +1,13 @@ +{ +"type": "FeatureCollection", +"name": "reticolata", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Struttura Muraria", "Tecnica": "Opera reticolata", "Materiale": "Tufo", "Funzione": null, "Descrizione": "Murature di fondo e divisorie degli ambienti prospicienti il mare, posti all’estremità orientale del complesso dei Bagni di Tiberio", "Elemento": "Alzati", "Componenti": "Cubilia e Blocchetti di ammorsamento angolari" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.229777980072088, 40.559287936017519 ], [ 14.229767127017078, 40.559288012101483 ], [ 14.22976727121063, 40.559297587195999 ], [ 14.229760323645737, 40.559297626052299 ], [ 14.229760154396285, 40.559286387060112 ], [ 14.229750772025486, 40.559286469247525 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Struttura Muraria", "Tecnica": "Opera Reticolata", "Materiale": "Tufo", "Funzione": null, "Descrizione": "Lacerto murario in giacitura secondaria, riferibile a una struttura con fondazione in opera cementizia e alzato in opera reticolata", "Elemento": "alzato, attacco di fondazione", "Componenti": "Cubilia, scapoli di calcare, scapoli di tufo" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.229748638716192, 40.559312774822097 ], [ 14.229749854780234, 40.559313917882307 ], [ 14.229749311598642, 40.559316242764893 ], [ 14.229747641045932, 40.559318431170922 ], [ 14.229745404602166, 40.559318825800922 ], [ 14.229707320793663, 40.559318057726777 ], [ 14.229688319593144, 40.559316631832786 ], [ 14.229687414956194, 40.55931462813308 ], [ 14.229689070847257, 40.559311784892955 ], [ 14.229707054884152, 40.559311704425618 ], [ 14.22973485378067, 40.5593122650738 ], [ 14.229745138689067, 40.559312472499833 ], [ 14.229748638716192, 40.559312774822097 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Strutture murarie", "Tecnica": "Opera reticolata", "Materiale": "Tufo", "Funzione": null, "Descrizione": "Due murature, rispettivamente di fondo e laterale, pertinenti a un ambiente prospiciente il mare nell’area orientale del complesso dei Bagni di Tiberio", "Elemento": "Alzati", "Componenti": "Cubilia, blocchetti di tufo angolari e terminali" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.229694021609493, 40.55930762924018 ], [ 14.229694082338904, 40.559310341259078 ], [ 14.229682272612358, 40.559310495088383 ], [ 14.229616809841877, 40.559314335557936 ], [ 14.229615693144396, 40.559336306937055 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Strutture Murarie", "Tecnica": "Opera reticolata", "Materiale": "Tufo", "Funzione": null, "Descrizione": "Due murature pertinenti a un ambiente ubicato sul livello superiore dell’area occidentale del complesso dei Bagni di Tiberio.", "Elemento": "alzati", "Componenti": "cubilia, blocchetti di ammorsamento" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.229012969950622, 40.559279324646354 ], [ 14.22901299762786, 40.559273288155325 ], [ 14.228949305222281, 40.559271595449403 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Strutture Murarie", "Tecnica": "Opera reticolata", "Materiale": "Tufo", "Funzione": null, "Descrizione": "muratura in pessimo stato di conservazione, inglobata in costruzione moderna", "Elemento": "Alzato", "Componenti": "Cubilia" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.228945620292212, 40.559244814778907 ], [ 14.228945097860407, 40.559260882019025 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null, "Comune": "Capri", "Località": "Bagni di Tiberio", "Tipologia": "Struttura Muraria", "Tecnica": "Opera reticolata", "Materiale": "Tufo", "Funzione": "sostruttiva", "Descrizione": "Fondazione del terrazzamento prospiciente il mare, con porticato soprastante", "Elemento": "Fondazione", "Componenti": "Cubilia" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 14.22932061572207, 40.559368986602642 ], [ 14.229319898205036, 40.559370575004976 ], [ 14.229315830697367, 40.559373603453658 ], [ 14.229313913113851, 40.559377741595505 ] ] ] } } +] +} diff --git a/webgis/img/icons/bricks.svg b/webgis/img/icons/bricks.svg new file mode 100644 index 0000000..23c2c36 --- /dev/null +++ b/webgis/img/icons/bricks.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/webgis/js/controllers/menu_controller.js b/webgis/js/controllers/menu_controller.js index 4fda139..9109052 100644 --- a/webgis/js/controllers/menu_controller.js +++ b/webgis/js/controllers/menu_controller.js @@ -7,7 +7,8 @@ export default class extends Controller { 'menu', 'cartography', 'search', - 'icon' + 'icon', + 'element', ]; static values = { @@ -169,4 +170,8 @@ export default class extends Controller { return icon; } + + removeElement() { + this.elementTarget.remove(); + } } diff --git a/webgis/js/gis.js b/webgis/js/gis.js index c04e97c..05b3aa0 100644 --- a/webgis/js/gis.js +++ b/webgis/js/gis.js @@ -40,15 +40,8 @@ export const INIT_ZOOM = 14; */ function capitalize(text) { let capital = text; - if (text) { - let words = text.split(' '); - capital = ''; - - for (let w of words) { - w = w[0].toUpperCase() + w.slice(1); - capital += w + ' '; - } - capital.trimEnd(); + if (text !== null && text !== undefined && text.at && text.length) { + capital = text.at(0).toLocaleUpperCase() + text.slice(1); } return capital; @@ -62,6 +55,7 @@ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) { let map = L.map(mapId, { //attributionControl: false, minZoom: GIS.MIN_ZOOM, + maxZoom: GIS.MAX_ZOOM, }).setView(this.CENTER_COORDS, zoomLevel); map.crs = L.CRS.EPSG4326; @@ -102,15 +96,80 @@ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) { }; L.control.layers(baseMap, cartography).addTo(map); + // TEST: marker with SVG icon... + L.marker([40.5591380,14.2296069], { icon: Icons.wall, id: 'tiberio' }) + .on('click', () => this.activateBuildingView('tiberio')) + .bindTooltip('Mappa USM') + .addTo(map); + GisState.map = map; + GisState.cartography.buildings = buildings; return map; } + +/** + * Draft implementation to activate + * a dedicated view with details + * to navigate building walls + * @todo Find better name + * @param {string} layerId + */ +GIS.activateBuildingView = async function(layerId) { + const map = GisState.map; + map.setMinZoom(19); + map.setMaxZoom(25); + /** + * @type {L.GeoJSON} currentGeometry + */ + let currentGeometry = {}; + + map.removeLayer(GisState.baseLayers['OpenStreetMap']); + // Remove site layers then re-add it so vector lines + // are placed correctly above the new base map + GisState.layers.geometries.removeFrom(map); + map.addLayer(GisState.baseLayers['Limiti amministrativi']); + GisState.cartography.buildings.addTo(map); + GisState.layers.geometries.addTo(map); + + GisState.layers.geometries.eachLayer(l => { + if (l.options.id === layerId) { + currentGeometry = l; + return; + } + }); + + const center = currentGeometry.getBounds().getCenter(); + + // TODO: Use DB to handle these? + const wallTechniques = [ + 'reticolata', + 'incerta', + 'mista', + 'laterizia', + 'cementizia', + 'poligonale', + ]; + + // TODO: create color lookup table... + const wallLayer = await this.loadGeoJSON( + `murarie/${layerId}/reticolata.geojson`, + Options.walls, + true + ); + + wallLayer.addTo(map); + + map.setView(wallLayer.getBounds().getCenter(), 20); + // This is terrible... + UI.addBuildingViewBar(); + map._container.classList.add('bview-border'); +} /** * Fetches references to cartography layers * and updates GisState */ -GIS.fetchCartographyLayers = async function () { +GIS.fetchCartographyLayers = async function() { const data = await this._fetchData('geoimages/menu') .catch(error => console.error(`Could not fetch data for geo images: ${error}`)); @@ -187,6 +246,9 @@ GIS.addLayerGroups = async function (map) { sites.markers.addTo(map); sites.geom.addTo(map); + // A layer group with all site geometries (GeoJSON) + GisState.layers.geometries = sites.geom; + const groups = await Promise.all( [ this.notConserved(), @@ -213,7 +275,7 @@ GIS.sites = async function () { if (record.geojson) { const options = record.gisId === 'grotta_azzurra' ? Options.grotta : Options.site; - geom.push(await this.loadSiteLayer(record, options)); + geom.push(await this.loadSiteLayer(record, options, false)); } if (record.area) { const options = record.gisId === 'villa_bismarck' ? @@ -412,7 +474,6 @@ GIS.reuse = async function () { * @returns {{baseMap: {"OpenStreetMap": L.TileLayer}}} */ GIS.initLayers = async function(map) { - let osmap = new L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxNativeZoom : GIS.MAX_ZOOM, maxZoom: GIS.MAX_ZOOM, @@ -437,6 +498,8 @@ GIS.initLayers = async function(map) { 'Limiti amministrativi' : boundaries, }; + GisState.baseLayers = baseMap; + return baseMap; } /** @@ -488,7 +551,7 @@ GIS.toggleSpherical = async function(map) { * @param {string} geoJSON geoJSON filename * @param {{color, opacity, weight, fillColor, fillOpacity}} options Style options for features * @param {boolean} popup Should the features have a popup? - * @returns {L.Layer} + * @returns {L.GeoJSON} */ GIS.loadGeoJSON = async function (geoJSON, options, popup = true) { const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`) @@ -496,6 +559,9 @@ GIS.loadGeoJSON = async function (geoJSON, options, popup = true) { .catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`)); // Show data from feature in popUp? + /** + * @type {L.GeoJSON} layer + */ const layer = new L.geoJson(geo, { style: function () { return options; @@ -545,6 +611,8 @@ GIS.loadSiteLayer = async function (site, options, popup = true, area = false) { } }); + layer.options.id = site.gisId; + return layer; } /** @@ -595,23 +663,28 @@ GIS.cacheDBData = async function (layerId, dbId) { * @returns {string} The popup's content */ GIS.featurePopup = function (layerName, feature) { - const html = ` - - - - - - - - -
Oggetto${feature.properties.OGGETTO ?? 'n.d.'}
Anno${feature.properties.ANNO}
Comune${capitalize(feature.properties.COMUNE)}
Località${capitalize(feature.properties.LOCALITA) ?? 'n.d.'}
Proprietà${capitalize(feature.properties.PROPRIETA) ?? 'n.d.'}
Foglio${feature.properties.FOGLIO}
Particella${feature.properties.PART_BIS ?? 'n.d.'}
- `; - const content = { - 'vincoli.geojson' : html, - 'paesistici.geojson' : html, - }; + const html = String.raw; - return content[layerName]; + let content = html` + + `; + + for (let key of Object.keys(feature.properties)) { + const label = key === 'PART_BIS' ? 'Particella' : + key === 'PROPRIETA' ? 'Proprietà' : + key === 'LOCALITA' ? 'Località' : + key; + + if (key !== 'PERCENTUAL' && key !== 'AREA' && key.toLowerCase() !== 'id') { + content += html` + + `; + } + } + + content += '
${capitalize(label.toLocaleLowerCase())}${capitalize(feature.properties[key]) ?? 'n.d.'}
' + + return content; } /** * Reproject WMS layer to map's CRS diff --git a/webgis/js/icons.js b/webgis/js/icons.js index 5ac0de0..cd69bb9 100644 --- a/webgis/js/icons.js +++ b/webgis/js/icons.js @@ -14,61 +14,56 @@ const buildingTechColors = { */ const Icons = {}; -Icons.site = L.icon( - { - iconUrl: 'img/icons/siti.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); -Icons.notConserved = L.icon( - { - iconUrl: 'img/icons/non_conserv.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); +Icons.site = L.icon({ + iconUrl: 'img/icons/siti.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); +Icons.notConserved = L.icon({ + iconUrl: 'img/icons/non_conserv.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); -Icons.finding = L.icon( - { - iconUrl: 'img/icons/rinvenimenti.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); +Icons.finding = L.icon({ + iconUrl: 'img/icons/rinvenimenti.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); -Icons.prehistoric = L.icon( - { - iconUrl: 'img/icons/preistorici.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); +Icons.prehistoric = L.icon({ + iconUrl: 'img/icons/preistorici.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); -Icons.underwater = L.icon( - { - iconUrl: 'img/icons/subacquei.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); +Icons.underwater = L.icon({ + iconUrl: 'img/icons/subacquei.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); -Icons.reuse = L.icon( - { - iconUrl: 'img/icons/reimpiego.png', - iconSize: [18, 27], - iconAnchor: [10, 24], - tooltipAnchor: [0, -22], - } -); +Icons.reuse = L.icon({ + iconUrl: 'img/icons/reimpiego.png', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); Icons.camera = L.divIcon({className: 'fa fa-camera'}); +Icons.wall = L.icon({ + iconUrl: 'img/icons/bricks.svg', + iconSize: [18, 27], + iconAnchor: [10, 24], + tooltipAnchor: [0, -22], +}); + /** * Assigns a different color to the icon * based on building tech type diff --git a/webgis/js/layer_options.js b/webgis/js/layer_options.js index fc236d5..b34ee9f 100644 --- a/webgis/js/layer_options.js +++ b/webgis/js/layer_options.js @@ -42,15 +42,22 @@ Options.buildings = { color: '#222', opacity: 1, weight: 1.5, - fillColor: '#5a5b5c', + fillColor: '#717274', fillOpacity: 0.8 }; Options.boundaries = { color: '#222', opacity: 1, weight: 1.5, - fillColor: '#b9bdc0', - fillOpacity: 0.8 + fillColor: '#d8dbdd', + fillOpacity: 0.5 +}; +Options.walls = { + color: '#10ee47', + fillColor: '#10ee47', + fillOpacity: 1, + opacity: 1, + weight: 4.5, }; Options.cluster = { spiderfyOnMaxZoom: false, diff --git a/webgis/js/state.js b/webgis/js/state.js index 8bbe503..ce65349 100644 --- a/webgis/js/state.js +++ b/webgis/js/state.js @@ -11,6 +11,7 @@ /** * @typedef {Object} GisStateType * @property {L.Map|null} map + * @property {{string: L.Layer}|null} baseLayers * @property {MarkerLookup} markers * @property {LayerGroupLookup} layers * @property {Object|null} bibliography @@ -21,6 +22,7 @@ /** @type {GisStateType} */ export const GisState = { map: null, + baseLayers: null, markers: { sites: {}, notConserved: {}, @@ -31,6 +33,7 @@ export const GisState = { buildingTechs: {}, }, layers: { + geometries: {}, sites: {}, notConserved: {}, findings: {}, @@ -44,6 +47,7 @@ export const GisState = { cartography : { historic: [], cadastral: null, + buildings: null, } } diff --git a/webgis/js/ui.js b/webgis/js/ui.js index 2e35d46..3e09475 100644 --- a/webgis/js/ui.js +++ b/webgis/js/ui.js @@ -42,7 +42,7 @@ UI.addCenterMapControl = function (map, centerCoords, zoom) { let controlUI = L.DomUtil.create('a', 'leaflet-draw-edit-remove', controlDiv); controlUI.title = 'Centra la mappa'; controlUI.href = '#'; - controlUI.innerHTML = ` + controlUI.innerHTML = html` @@ -59,6 +59,20 @@ UI.addCenterMapControl = function (map, centerCoords, zoom) { let centerCtr = new L.Control.CenterControl(); map.addControl(centerCtr); } + +UI.addBuildingViewBar = function() { + const bar = document.createElement('div'); + bar.className = 'container bview-bar has-background-primary-90 p-4'; + bar.setAttribute('data-controller', 'menu'); + bar.setAttribute('data-menu-target', 'element'); + + bar.innerHTML = html` + +

Mappa Unità Stratigrafica Muraria (USM)

+ `; + + document.body.appendChild(bar); +} /** * Toggle burger menu for small screens * @param {string} burgerClass The CSS class of the burger element