'use strict'; import UI from "./ui.js"; import API_CONFIG from "./config.js"; import Icons from "./icons.js"; import { SphericalPhoto } from "./components/SphericalPhoto.js"; const MAPBOX_TOKEN = 'pk.eyJ1Ijoibmljb3BhIiwiYSI6ImNseWNwZjJjbjFidzcya3BoYTU0bHg4NnkifQ.3036JnCXZTEMt6jVgMzVRw'; const BASE_URL = location.href; let API_URL = ''; if (BASE_URL.includes('localhost')) { API_URL = API_CONFIG.dev; } else { API_URL = API_CONFIG.prod; } window.API_URL = API_URL; // Global leaflet /** * @namespace GIS */ const GIS = {}; GIS.CENTER_COORDS = [40.5492, 14.2317]; GIS.INIT_ZOOM = 14; const optionsVincoli = { color: '#222', opacity: 0.8, weight: 1, fillColor: '#fa7861', fillOpacity: 0.8 }; const optionsSiti = { color: '#800040', opacity: 1, weight: 1.5, fillColor: '#800040', fillOpacity: 0.8 }; const optionsGrotta = { color: '#205dac', opacity: 1, weight: 1.5, fillColor: '#205dac', fillOpacity: 0.8 } const optionsPaesistici = { color: '#222', opacity: 1, weight: 1.5, fillColor: '#88d28d', fillOpacity: 0.8 }; const optionsFabbricati = { color: '#222', opacity: 1, weight: 1.5, fillColor: '#5b5d5f', fillOpacity: 0.8 }; const MARKER_NAMES = { sites: { 'gradola' : 'Villa di Gradola', 'damecuta' : 'Villa di Damecuta', 'matermania' : 'Grotta di Matermania', 'arsenale' : 'Grotta dell\'Arsenale', 'tiberio' : 'Bagni di Tiberio', 'mura' : 'Mura greche', 'san_michele' : 'Villa San Michele', 'scala_fenicia' : 'Scala Fenicia', 'grotta_azzurra' : 'Grotta Azzurra', 'lopozzo' : 'Località Lo Pozzo', }, }; /** * Capitalize a text string * @todo Move to utils * @param {?string} text * @returns {?string} The capitalized string or null */ 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(); } return capital; } /** * @param {string} mapId * @param {number} zoomLevel * @returns {{map: Map, sites: Layer}} */ GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) { L.LayerGroup.include({ customGetLayer : function (id) { for (let l in this._layers) { if (this._layers[l].id === id) { return this._layers[l]; } } } }); let map = L.map(mapId, { //attributionControl: false, minZoom: 2, }).setView(this.CENTER_COORDS, zoomLevel); map.crs = L.CRS.EPSG4326; const {baseMap, sitesGroup} = await this.initLayers(map); // Add scale and ruler controls L.control.scale({imperial: false}).addTo(map); L.control.graphicScale({fill: 'hollow', position: 'bottomright'}).addTo(map); let layerVincoli = await this.loadLayer('vincoli.geojson', optionsVincoli); let layerPaesistici = await this.loadLayer('paesistici.geojson', optionsPaesistici); let markersGroup = await this.sitesMarkers(sitesGroup); let notConservedGroup = await this.notConserved(); let findingsGroup = await this.findings(); const archeo = { 'Beni archeologici (punti)' : markersGroup, 'Beni archeologici (strutture)' : sitesGroup, 'Beni non conservati' : notConservedGroup, 'Rinvenimenti' : findingsGroup, 'Vincoli archeologici' : layerVincoli, 'Vincoli paesistici' : layerPaesistici, }; markersGroup.addTo(map); sitesGroup.addTo(map); notConservedGroup.addTo(map); findingsGroup.addTo(map); L.control.layers( baseMap, archeo, ).addTo(map); // TEMP!! Remove point for Lo Pozzo... map.removeLayer(sitesGroup.customGetLayer('lopozzo')); // TODO Horrible? return {map: map, sites: sitesGroup}; } /** * Create markers for sites * @param {L.LayerGroup} sitesGroup * @returns {L.markerClusterGroup} */ GIS.sitesMarkers = async function (sitesGroup) { let sitesMarkers = L.markerClusterGroup(); for (let id in MARKER_NAMES.sites) { let layer = sitesGroup.customGetLayer(id); let coords = layer.getBounds().getCenter(); const fromStorage = localStorage.getItem(id); let data = {}; if (fromStorage !== 'undefined') { try { data = JSON.parse(fromStorage); const lat = data?.coordinates[0] ?? coords.lat; const lon = data?.coordinates[1] ?? coords.lng; coords = [lat, lon]; } catch (error) { console.log(error); } } else { data = await GIS._fetchData('site/' + id); } const marker = L.marker(coords, { icon: Icons.site }) .bindTooltip(MARKER_NAMES.sites[id] + '
(Clicca per aprire scheda)'); marker.id = id; marker.on('click', () => UI.openSiteModal(data, '#site-data')); sitesMarkers.addLayer(marker); } return sitesMarkers; } /** * Create not conserved group * @returns {L.MarkerClusterGroup} */ GIS.notConserved = async function () { let notConserData = await fetch(`${API_URL}/not_conserved`) .then(data => data.json()); let notConserved = L.markerClusterGroup(); for (let record of notConserData.records) { notConserved.addLayer(L.marker( record.coordinates, {icon: Icons.notConserved} ).bindTooltip(record.denomination) .on( 'click', () => UI.openNotConserModal(record, '#not-conser-data') ) ); } return notConserved; } /** * Create findings group * @returns {L.MarkerClusterGroup} */ GIS.findings = async function () { let findingsData = await fetch(`${API_URL}/finding`) .then(data => data.json()); let findings = L.markerClusterGroup(); for (let record of findingsData) { findings.addLayer(L.marker( record.coordinates, {icon: Icons.finding} ).bindTooltip(record.object) .on( 'click', () => UI.openFindingModal(record, '#finding-data') ) ); } return findings; } /* GIS._prepareLayers = async function(layer) { const fromStorage = localStorage.getItem(layer.id); let data = {}; let coords = layer.getBounds().getCenter(); if (fromStorage !== 'undefined') { try { data = JSON.parse(fromStorage); const lat = data?.lat ?? coords[0]; const lon = data?.lon ?? coords[1]; coords = [lat, lon]; } catch (error) { console.log(error); } } else { data = await GIS._fetchData(layer.id); } // TODO: terrible! if (!layer.id.includes('area')) { const marker = L.marker(coords) .addTo(map) .bindTooltip( Object.keys(archeo).find(k => archeo[k] === layer) .replace(/\s\(.*$/, '') ) .openTooltip(); if (typeof data === 'object') { marker.on('click', () => UI.openModal(data, '#site-data')); } } } */ /** * Adds layers to map and returns an object * with {baseMap, archeoLayers, sitesLayerGroup} * @param {L.Map} map * @returns {{baseMap: {"OpenStreetMap": L.TileLayer}, archeo: object, sitesGroup: L.LayerGroup}} */ GIS.initLayers = async function(map) { // TODO Move all this to separate array / object! let layerMater = await this.loadLayer('matermania.geojson', optionsSiti, false); let layerMaterArea = await this.loadLayer('matermania_area.geojson', optionsSiti, false); let layerArsenale = await this.loadLayer('arsenale.geojson', optionsSiti, false); let layerArsenaleArea = await this.loadLayer('arsenale_area.geojson', optionsSiti, false); let layerGradola = await this.loadLayer('gradola.geojson', optionsSiti, false); let layerGradolaArea = await this.loadLayer('gradola_area.geojson', optionsSiti, false); let layerMura = await this.loadLayer('mura.geojson', optionsSiti, false); let layerSanMichele = await this.loadLayer('san_michele.geojson', optionsSiti, false); let layerDamecuta = await this.loadLayer('damecuta.geojson', optionsSiti, false); let layerTiberio = await this.loadLayer('tiberio.geojson', optionsSiti, false); let layerScala = await this.loadLayer('scala_fenicia.geojson', optionsSiti, false); let layerGrotta = await this.loadLayer('grotta_azzurra.geojson', optionsGrotta, false); let layerLopozzo = await this.loadLayer('lopozzo.geojson', optionsSiti, false); layerMater.id = 'matermania'; layerMaterArea.id = 'matermania_area'; layerGradola.id = 'gradola'; layerGradolaArea.id = 'gradola_area'; layerArsenale.id = 'arsenale'; layerArsenaleArea.id = 'arsenale_area'; layerMura.id = 'mura'; layerSanMichele.id = 'san_michele'; layerDamecuta.id = 'damecuta'; layerTiberio.id = 'tiberio'; layerScala.id = 'scala_fenicia'; layerGrotta.id = 'grotta_azzurra'; layerLopozzo.id = 'lopozzo'; let osmap = new L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxNativeZoom : 22, maxZoom: 22, attribution: '© OpenStreetMap contributors' }); let mapbox = new L.tileLayer(`https://api.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.jpg90?access_token=${MAPBOX_TOKEN}`, { id : 'mapbox.satellite', tileSize : 512, zoomOffset : -1, maxNativeZoom : 22, maxZoom: 22, attribution: '© Mapbox' }); let boundaries = await this.loadLayer('confini.geojson', {}, false); let buildings = await this.loadLayer('fabbricati.geojson', optionsFabbricati, false); let baseCatasto = new L.LayerGroup([buildings, boundaries]); const sitesGroup = new L.LayerGroup([ layerMater, layerMaterArea, layerGradola, layerGradolaArea, layerArsenale, layerArsenaleArea, layerMura, layerSanMichele, layerDamecuta, layerTiberio, layerScala, layerGrotta, layerLopozzo ]); const baseGroup = new L.LayerGroup([osmap]); baseGroup.addTo(map); const baseMap = { "OpenStreetMap" : osmap, "Satellite" : mapbox, "Cartografia catastale" : baseCatasto, }; return {baseMap, sitesGroup}; } /** * Toggle spherical photos on the map * based on zoom level */ GIS.toggleSpherical = async function(map) { // TODO Create points layer const spherical = await this._fetchData('spherical'); let sphereMarkers = []; const sPhoto = new SphericalPhoto(); if (spherical) { for (let pano of spherical) { let marker = L.marker(pano.coordinates, { icon: Icons.camera, }); marker.bindTooltip('Apri foto sferica'); marker.on('click', () => { let modal = document.querySelector('#spherical-modal'); let viewer = document.querySelector('#pano-viewer'); viewer.innerHTML = ''; modal.classList.add('is-active'); modal.querySelector('.modal-background').addEventListener('click', () => { modal.classList.remove('is-active'); }); modal.querySelector('.modal-close').addEventListener('click', () => { modal.classList.remove('is-active'); }) sPhoto.setViewer(pano.filename); }); sphereMarkers.push(marker); } } let group = L.layerGroup(sphereMarkers); map.on('zoomend', () => { if (map.getZoom() >= 19) { group.addTo(map); } else { group.remove(); } }); } /** * @param {string} geoJSON * @param {{color, opacity, weight, fillColor, fillOpacity}} options Style options for features * @param {boolean} popup Should the features have a popup? */ GIS.loadLayer = async function (geoJSON, options, popup = true) { const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`) .then(res => res.json()) .catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`)); const layerId = geoJSON.replace('.geojson', ''); this.cacheDBData(layerId); // Show data from feature in popUp? const layer = new L.geoJson(geo, { style: function () { return options; }, onEachFeature: function (feature, layer) { if (popup) { layer.bindPopup(GIS.featurePopup(geoJSON, feature)); } else { layer.on("click", async () => { let data = GIS.layerData(layerId); if (typeof data === 'object') { UI.openSiteModal(data); } }); } } }); return layer; } /** * Retrieves data for a given layer * @param {string} layerId * @returns {object} Data for this layer from DB or cache */ GIS.layerData = async function (layerId) { const fromStorage = localStorage.getItem(layerId); let data = {}; if (fromStorage !== 'undefined') { try { data = JSON.parse(fromStorage); } catch (error) { console.log(error); } } else { data = await GIS._fetchData('site/' + layerId); } return data; } /** * Get spherical photos for a site * @param {int} siteId */ GIS.getSpherical = async function (siteId) { const data = await this._fetchData('spherical/' + siteId); } /** * Cache data from DB in local storage * for a given layer * @param {string} layerId */ GIS.cacheDBData = async function (layerId) { const data = await this._fetchData('site/' + layerId); localStorage.setItem(layerId, JSON.stringify(data)); } /** * Generate proper content for features popup * @todo Hard-coded names!! * * @param {string} layerName * @param {object} feature * @returns {string} The popup's content */ GIS.featurePopup = function (layerName, feature) { const html = `
Oggetto${feature.properties.OGGETTO}
Anno${feature.properties.ANNO}
Comune${capitalize(feature.properties.COMUNE)}
Località${capitalize(feature.properties.LOCALITA)}
Proprietà${capitalize(feature.properties.PROPRIETA)}
`; const content = { 'vincoli.geojson' : html, 'paesistici.geojson' : html, }; return content[layerName]; } /** * Fetch data from API * @param {string} recordId */ GIS._fetchData = async function (recordId) { const data = await fetch(`${API_URL}/${recordId}`) .then(res => res.json()) .catch(err => console.log('Error fetching data from DB: ' + err)); return data; } export default GIS;