caprigis/webgis/js/gis.js
2024-11-30 18:37:01 +01:00

523 lines
15 KiB
JavaScript

'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',
},
};
const clusterOptions = {
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
disableClusteringAtZoom: 19,
};
/**
* 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}
*/
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: 11,
}).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,
};
// TEMP!! Remove point for Lo Pozzo...
sitesGroup.removeLayer(sitesGroup.customGetLayer('lopozzo'));
markersGroup.addTo(map);
sitesGroup.addTo(map);
notConservedGroup.addTo(map);
findingsGroup.addTo(map);
L.control.layers(baseMap, archeo).addTo(map);
return map;
}
/**
* Create markers for sites
* @param {L.LayerGroup} sitesGroup
* @returns {L.markerClusterGroup}
*/
GIS.sitesMarkers = async function (sitesGroup) {
let sitesMarkers = L.markerClusterGroup(
clusterOptions
);
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] + '<br>(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(
clusterOptions
);
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(
clusterOptions
);
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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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: '&copy; 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 = `
<table class="table is-striped is-size-6 m-2">
<tr><th>Oggetto</th><td>${feature.properties.OGGETTO}</td></tr>
<tr><th>Anno</th><td>${feature.properties.ANNO}</td></tr>
<tr><th>Comune</th><td>${capitalize(feature.properties.COMUNE)}</td></tr>
<tr><th>Località</th><td>${capitalize(feature.properties.LOCALITA)}</td></tr>
<tr><th>Proprietà</th><td>${capitalize(feature.properties.PROPRIETA)}</td></tr>
</table>
`;
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;