460 lines
13 KiB
JavaScript
460 lines
13 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;
|
|
GIS.MIN_ZOOM = 11;
|
|
GIS.MAX_ZOOM = 24;
|
|
|
|
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 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) {
|
|
let map = L.map(mapId, {
|
|
//attributionControl: false,
|
|
minZoom: GIS.MIN_ZOOM,
|
|
}).setView(this.CENTER_COORDS, zoomLevel);
|
|
|
|
map.crs = L.CRS.EPSG4326;
|
|
|
|
const baseMap = 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.loadGeoJSON('vincoli.geojson', optionsVincoli);
|
|
let layerPaesistici = await this.loadGeoJSON('paesistici.geojson', optionsPaesistici);
|
|
|
|
// Refactor with separate function...
|
|
this.sites().then(data => {
|
|
data.markers.addTo(map);
|
|
data.geom.addTo(map);
|
|
window.Sites = data.markers;
|
|
});
|
|
this.notConserved().then(group => {group.addTo(map); window.NotConserved = group});
|
|
this.findings().then(group => {group.addTo(map); window.Findings = group});
|
|
this.prehistoric().then(group => {group.addTo(map); window.Prehistoric = group});
|
|
|
|
const archeo = {
|
|
'Vincoli archeologici' : layerVincoli,
|
|
'Vincoli paesistici' : layerPaesistici,
|
|
};
|
|
L.control.layers(baseMap, archeo).addTo(map);
|
|
|
|
return map;
|
|
}
|
|
/**
|
|
* Create markers for sites
|
|
* @returns {{markers: L.markerClusterGroup, geom: L.LayerGroup}}
|
|
*/
|
|
GIS.sites = async function () {
|
|
let sitesData = await fetch(`${API_URL}/sites`)
|
|
.then(data => data.json());
|
|
|
|
let sites = L.markerClusterGroup(clusterOptions);
|
|
let geom = [];
|
|
|
|
for (let record of sitesData) {
|
|
if (record.geojson) {
|
|
const options = record.gisId === 'grotta_azzurra' ?
|
|
optionsGrotta : optionsSiti;
|
|
geom.push(await this.loadSiteLayer(record, options));
|
|
}
|
|
sites.addLayer(L.marker(
|
|
record.coordinates,
|
|
{icon: Icons.site}
|
|
).bindTooltip(record.label + '<br>(Clicca per aprire scheda)')
|
|
.on(
|
|
'click',
|
|
() => UI.openSiteModal(record, '#site-data')
|
|
)
|
|
);
|
|
}
|
|
|
|
return {markers: sites, geom: L.layerGroup(geom)};
|
|
}
|
|
/**
|
|
* 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;
|
|
}
|
|
/**
|
|
* Create group for prehistoric sites
|
|
*
|
|
*/
|
|
GIS.prehistoric = async function () {
|
|
let data = await fetch(`${API_URL}/prehistoric`)
|
|
.then(data => data.json());
|
|
|
|
let prehistoric = L.markerClusterGroup(
|
|
clusterOptions
|
|
);
|
|
|
|
for (let record of data.records) {
|
|
prehistoric.addLayer(L.marker(
|
|
record.coordinates,
|
|
{icon: Icons.prehistoric}
|
|
).bindTooltip(record.denomination)
|
|
.on(
|
|
'click',
|
|
() => UI.openPrehistModal(record, '#prehist-data')
|
|
)
|
|
);
|
|
}
|
|
|
|
return prehistoric;
|
|
}
|
|
/**
|
|
* Adds layers to map and returns an object
|
|
* with {baseMap, archeoLayers, sitesLayerGroup}
|
|
* @todo Load areas for sites that have them!!
|
|
* @param {L.Map} map
|
|
* @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,
|
|
attribution: '© <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 : GIS.MAX_ZOOM,
|
|
maxZoom: GIS.MAX_ZOOM,
|
|
attribution: '© Mapbox'
|
|
});
|
|
let boundaries = await this.loadGeoJSON('confini.geojson', {}, false);
|
|
let buildings = await this.loadGeoJSON('fabbricati.geojson', optionsFabbricati, false);
|
|
let baseCatasto = new L.LayerGroup([buildings, boundaries]);
|
|
|
|
const baseGroup = new L.LayerGroup([osmap]);
|
|
baseGroup.addTo(map);
|
|
const baseMap = {
|
|
"OpenStreetMap" : osmap,
|
|
"Satellite" : mapbox,
|
|
"Cartografia catastale" : baseCatasto,
|
|
};
|
|
|
|
return baseMap;
|
|
}
|
|
/**
|
|
* 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 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}
|
|
*/
|
|
GIS.loadGeoJSON = 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}`));
|
|
|
|
// 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));
|
|
}
|
|
}
|
|
});
|
|
|
|
return layer;
|
|
}
|
|
/**
|
|
* @param {object} site Site record from DB
|
|
* @param {{color, opacity, weight, fillColor, fillOpacity}} options Style options for features
|
|
* @param {boolean} popup Should the features have a popup?
|
|
* @returns {L.Layer}
|
|
*/
|
|
GIS.loadSiteLayer = async function (site, options, popup = true) {
|
|
const geoJSON = `${site.gisId}.geojson`;
|
|
const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`)
|
|
.then(res => res.json())
|
|
.catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`));
|
|
|
|
this.cacheDBData(site.gisId, site.id);
|
|
|
|
// 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(site.gisId, site.id);
|
|
|
|
if (typeof data === 'object') {
|
|
UI.openSiteModal(data);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return layer;
|
|
}
|
|
/**
|
|
* Retrieves data for a given layer
|
|
* @param {string} layerId
|
|
* @param {string} dbId
|
|
* @returns {object} Data for this layer from DB or cache
|
|
*/
|
|
GIS.layerData = async function (layerId, dbId) {
|
|
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('sites/' + dbId);
|
|
}
|
|
|
|
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, dbId) {
|
|
const data = await this._fetchData('sites/' + dbId);
|
|
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;
|