const version = "2022.10.27.1"; const mapStore = localforage.createInstance({ name: "maps", storeName: "saved_maps" }); const featureStore = localforage.createInstance({ name: "maps", storeName: "saved_features" }); const map = L.map("map", { zoomSnap: 0, // tap: (L.Browser.safari && !L.Browser.mobile) ? false : true, maxZoom: 17, minZoom: 15, zoomControl: true, renderer: L.canvas({ padding: 0.5, tolerance: 10 }) }).fitWorld(); map.attributionControl.setPrefix(` `); map.once("locationfound", (e) => { hideLoader(); if (Object.values(layers.overlays)[0] instanceof L.TileLayer.MBTiles) { setTimeout(() =>{ map.fitBounds(Object.values(layers.overlays)[0].options.bounds); controls.locateCtrl.stopFollowing(); }, 1); } else { map.fitBounds(e.bounds, {maxZoom: 18}); } }); map.on("click", (e) => { layers.select.clearLayers(); }); map.on("baselayerchange", (e) => { localStorage.setItem("basemap", e.name); }); const layers = { basemaps: { "Streets": L.tileLayer("https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.@2xpng", { maxNativeZoom: 18, maxZoom: map.getMaxZoom(), attribution: '© OpenStreetMap contributors, © CARTO', }).addTo(map), "Aerial": L.tileLayer("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}", { maxNativeZoom: 16, maxZoom: map.getMaxZoom(), attribution: "USGS", }), "Topo": L.tileLayer("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", { maxNativeZoom: 16, maxZoom: map.getMaxZoom(), attribution: "USGS", }), "None": L.tileLayer("", { maxZoom: map.getMaxZoom() }) }, select: L.featureGroup(null).addTo(map), overlays: {}, groups: {} }; var starok= L.icon({ iconUrl: '../images/starok.png', iconAnchor: [16, 37], }); var marker = []; marker[1] = L.marker([39.79668394774688, 2.6944001956176677], {icon: starok}).addTo(map); marker[1].on("click", function() { parent.datosPunto(1,0); }); marker[37] = L.marker([39.79766860952766, 2.698834077149357], {icon: starok}).addTo(map); marker[37].on("click", function() { parent.datosPunto(37,0); }); marker[7] = L.marker([39.796707374063864, 2.68921602124788], {icon: starok}).addTo(map); marker[7].on("click", function() { parent.datosPunto(7,0); }); marker[8] = L.marker([39.806466609768655, 2.6985715662918253], {icon: starok}).addTo(map); marker[8].on("click", function() { parent.datosPunto(8,0); }); marker[9] = L.marker([39.810832204856375, 2.7002194166048676], {icon: starok}).addTo(map); marker[9].on("click", function() { parent.datosPunto(9,0); }); marker[11] = L.marker([39.80848412608556, 2.7033965747429267], {icon: starok}).addTo(map); marker[11].on("click", function() { parent.datosPunto(11,0); }); marker[19] = L.marker([39.8173709211968, 2.709937223455845], {icon: starok}).addTo(map); marker[19].on("click", function() { parent.datosPunto(19,0); }); marker[20] = L.marker([39.81723906978286, 2.713885435125767], {icon: starok}).addTo(map); marker[20].on("click", function() { parent.datosPunto(20,0); }); marker[21] = L.marker([39.8232710130262, 2.7283049907898294], {icon: starok}).addTo(map); marker[21].on("click", function() { parent.datosPunto(21,0); }); marker[22] = L.marker([39.82768750998747, 2.7339483912492923], {icon: starok}).addTo(map); marker[22].on("click", function() { parent.datosPunto(22,0); }); marker[23] = L.marker([39.82926946968831, 2.7368666346574955], {icon: starok}).addTo(map); marker[23].on("click", function() { parent.datosPunto(23,0); }); marker[25] = L.marker([39.85517876881287, 2.778120594918665], {icon: starok}).addTo(map); marker[25].on("click", function() { parent.datosPunto(25,0); }); marker[26] = L.marker([39.85517876881287, 2.787905293404993], {icon: starok}).addTo(map); marker[26].on("click", function() { parent.datosPunto(26,0); }); marker[27] = L.marker([39.83072946247496, 2.7602248963713016], {icon: starok}).addTo(map); marker[27].on("click", function() { parent.datosPunto(27,0); }); marker[28] = L.marker([39.84654671962563, 2.776575642526087], {icon: starok}).addTo(map); marker[28].on("click", function() { parent.datosPunto(28,0); }); marker[29] = L.marker([39.839758926344565, 2.7760606583952274], {icon: starok}).addTo(map); marker[29].on("click", function() { parent.datosPunto(29,0); }); marker[31] = L.marker([39.80824951131639, 2.7927118119596805], {icon: starok}).addTo(map); marker[31].on("click", function() { parent.datosPunto(31,0); }); marker[32] = L.marker([39.850244783822575, 2.7993902898716128], {icon: starok}).addTo(map); marker[32].on("click", function() { parent.datosPunto(32,0); }); marker[34] = L.marker([39.85169443868433, 2.8057846761631167], {icon: starok}).addTo(map); marker[34].on("click", function() { parent.datosPunto(34,0); }); marker[35] = L.marker([39.85864576725907, 2.8024372793125307], {icon: starok}).addTo(map); marker[35].on("click", function() { parent.datosPunto(35,0); }); marker[15] = L.marker([39.776601285573626, 2.7087780448903898], {icon: starok}).addTo(map); marker[15].on("click", function() { parent.datosPunto(15,0); }); marker[16] = L.marker([39.76611933154883, 2.715440195484744], {icon: starok}).addTo(map); marker[16].on("click", function() { parent.datosPunto(16,0); }); marker[17] = L.marker([39.76632744661204, 2.715303388632617], {icon: starok}).addTo(map); marker[17].on("click", function() { parent.datosPunto(17,0); }); marker[18] = L.marker([39.766223330397544, 2.7157486353290894], {icon: starok}).addTo(map); marker[18].on("click", function() { parent.datosPunto(18,0); }); const controls = { locateCtrl: L.control.locate({ icon: "icon-gps_fixed", iconLoading: "spinner icon-gps_fixed", setView: "untilPan", cacheLocation: true, position: "bottomright", flyTo: false, keepCurrentZoomLevel: true, circleStyle: { interactive: false }, markerStyle: { interactive: true }, metric: false, strings: { title: "My location", popup: (options) => { const loc = controls.locateCtrl._marker.getLatLng(); return `
You are within ${Number(options.distance).toLocaleString()} ${options.unit}
from ${loc.lat.toFixed(6)}, ${loc.lng.toFixed(6)}
`; } }, locateOptions: { enableHighAccuracy: true, maxZoom: 18 }, onLocationError: (e) => { hideLoader(); document.querySelector(".leaflet-control-locate").getElementsByTagName("span")[0].className = "icon-gps_off"; // alert(e.message); } }).addTo(map), scaleCtrl: L.control.scale({ position: "bottomleft" }).addTo(map) }; function handleFile(file) { showLoader(); const name = file.name.split(".").slice(0, -1).join("."); if (file.name.endsWith(".mbtiles")) { loadRaster(file, name); } else if (file.name.endsWith(".geojson") || file.name.endsWith(".kml") || file.name.endsWith(".gpx") || file.name.endsWith(".csv")) { const format = file.name.split(".").pop(); loadVector(file, name, format); } else { // alert("MBTiles, GeoJSON, KML, GPX, and CSV files supported."); hideLoader(); } } function loadVector(file, name, format) { const reader = new FileReader(); let geojson = null; reader.onload = function(e) { if (format == "geojson") { geojson = JSON.parse(reader.result); name = geojson.name ? geojson.name : name; } else if (format == "kml") { const kml = (new DOMParser()).parseFromString(reader.result, "text/xml"); geojson = toGeoJSON.kml(kml, {styles: true}); } else if (format == "gpx") { const gpx = (new DOMParser()).parseFromString(reader.result, "text/xml"); geojson = toGeoJSON.gpx(gpx); } else if (format == "csv") { const columns = reader.result.split(/n/).filter(Boolean)[0].split(","); const options = {}; if (columns.includes("Y") && columns.includes("X")) { options.latfield = "Y", options.lonfield = "X" } csv2geojson.csv2geojson(reader.result, options, function(err, data) { if (data) { geojson = data; } }); } createVectorLayer(name, geojson, null, true); } reader.readAsText(file); } function createVectorLayer(name, data, key, save) { let radius = 4; var key = key ? key : Date.now().toString(); const layer = L.geoJSON(data, { key: key, bubblingMouseEvents: false, style: (feature) => { return { color: feature.properties.hasOwnProperty("stroke") ? feature.properties["stroke"] : feature.properties["marker-color"] ? feature.properties["marker-color"] : feature.geometry.type == "Point" ? "#ffffff" : "#ff0000", opacity: feature.properties.hasOwnProperty("stroke-opacity") ? feature.properties["stroke-opacity"] : 1.0, weight: feature.properties.hasOwnProperty("stroke-width") ? feature.properties["stroke-width"] : feature.geometry.type == "Point" ? 1.5 : 3, fillColor: feature.properties.hasOwnProperty("fill") ? feature.properties["fill"] : feature.properties["marker-color"] ? feature.properties["marker-color"] : "#ff0000", fillOpacity: feature.properties.hasOwnProperty("fill-opacity") ? feature.properties["fill-opacity"] : feature.geometry.type != "Point" ? 0.2 : feature.geometry.type == "Point" ? 1 : "", }; }, pointToLayer: (feature, latlng) => { const size = feature.properties.hasOwnProperty("marker-size") ? feature.properties["marker-size"] : "medium"; const sizes = { small: 4, medium: 6, large: 8 }; radius = sizes[size]; return L.circleMarker(latlng, { radius: radius }); }, onEachFeature: (feature, layer) => { let table = "
"; const hiddenProps = ["styleUrl", "styleHash", "styleMapHash", "stroke", "stroke-opacity", "stroke-width", "opacity", "fill", "fill-opacity", "icon", "scale", "coordTimes", "marker-size", "marker-color", "marker-symbol"]; for (const key in feature.properties) { if (feature.properties.hasOwnProperty(key) && hiddenProps.indexOf(key) == -1) { table += ""; } } table += "
" + key.toUpperCase() + "" + formatProperty(feature.properties[key]) + "
"; layer.bindPopup(table, { // closeButton: false, autoPanPadding: [15, 15], maxHeight: 300, maxWidth: 250 }); layer.on({ popupclose: (e) => { layers.select.clearLayers(); }, click: (e) => { layers.select.clearLayers(); layers.select.addLayer(L.geoJSON(layer.toGeoJSON(), { style: { color: "#00FFFF", weight: 5 }, pointToLayer: (feature, latlng) => { return L.circleMarker(latlng, { radius: radius, color: "#00FFFF", fillColor: "#00FFFF", fillOpacity: 1 }); } })) } }); } }); if (save) { const value = { "name": name, "features": data }; featureStore.setItem(key, value).then((value) => { addOverlayLayer(layer, name, null, true); layers.overlays[L.Util.stamp(layer)] = layer; layer.addTo(map); zoomToLayer(L.Util.stamp(layer)); }).catch((err) => { alert("Error saving data!"); }); } else { addOverlayLayer(layer, name, null, true); layers.overlays[L.Util.stamp(layer)] = layer; } } function loadRaster(file, name) { const reader = new FileReader(); reader.onload = (e) => { createRasterLayer(name, reader.result); } reader.readAsArrayBuffer(file); } function createRasterLayer(name, data) { const key = Date.now().toString(); const layer = L.tileLayer.mbTiles(data, { autoScale: true, fitBounds: true, updateWhenIdle: false, key: key }).on("databaseloaded", (e) => { name = (layer.options.name ? layer.options.name : name); // addOverlayLayer(layer, name); const value = { "name": name, "mbtiles": data }; mapStore.setItem(key, value).then((value) => { addOverlayLayer(layer, name, null, false); }).catch((err) => { alert("Error saving data!"); }); }).addTo(map); layers.overlays[L.Util.stamp(layer)] = layer; } function addOverlayLayer(layer, name, group, saved) { hideLoader(); const layerState = getLayerState(); setTimeout(() => { updateLayerState(layerState); }, 100); layer.on("add", () => { // document.querySelector(`[data-layer='${L.Util.stamp(layer)}']`).disabled = false; }); layer.on("remove", () => { // document.querySelector(`[data-layer='${L.Util.stamp(layer)}']`).disabled = true; }); } function setAnimation(ok,id){ if(ok){ L.DomUtil.addClass(marker[id]._icon, "blinking"); }else{ L.DomUtil.removeClass(marker[id]._icon, "blinking"); } } function getLayerState() { const layers = {}; document.querySelectorAll(".layer-buttons input").forEach(element => { // const id = element.getAttribute("data-layer"); const layer = map._layers[id]; layers[id] = { disabled: (layer && map.hasLayer(layer)) ? false : true, opacity: element.value } }); return layers; } function updateLayerState(layers) { document.querySelectorAll(".layer-buttons input").forEach(element => { // const id = element.getAttribute("data-layer"); if (layers[id]) { element.disabled = layers[id].disabled; element.value = layers[id].opacity; } }); } function zoomToLayer(id) { const layer = layers.overlays[id]; if (!map.hasLayer(layer)) { // map.addLayer(layers.overlays[id]); alert("Layer must be active first!"); } else if (layer.options.bounds) { map.fitBounds(layer.options.bounds); controls.locateCtrl.stopFollowing(); } else { map.fitBounds(layer.getBounds(), {padding: [20, 20]}); controls.locateCtrl.stopFollowing(); } } function removeLayer(id, name, group) { if (confirm(`Remove ${name.replace(/_/g, " ")}?`)) { const layerState = getLayerState(); const layer = layers.overlays[id]; if (map.hasLayer(layer)) { map.removeLayer(layer); } if (layer instanceof L.TileLayer.MBTiles) { layer._db.close(); } if (layer && layer.options && layer.options.key) { if (layer instanceof L.TileLayer.MBTiles) { mapStore.removeItem(layer.options.key).then(function () { // controls.layerCtrl.removeLayer(layer); updateLayerState(layerState); }); } else if (layer instanceof L.GeoJSON) { featureStore.removeItem(layer.options.key).then(function () { // controls.layerCtrl.removeLayer(layer); updateLayerState(layerState); }); } } if (group) { const groupLayer = layers.groups[group]; const key = groupLayer.options.key; mapStore.removeItem(key).then(() => { // controls.layerCtrl.removeLayer(groupLayer); updateLayerState(layerState); }); }/* else { // controls.layerCtrl.removeLayer(layer); updateLayerState(layerState); }*/ } } function changeOpacity(id) { // const value = document.querySelector(`[data-layer='${id}']`).value; const layer = layers.overlays[id]; if (!map.hasLayer(layer)) { // map.addLayer(layers.overlays[id]); alert("Layer must be active first!"); } else if (layer instanceof L.TileLayer.MBTiles) { layer.setOpacity(value); } else if (layer instanceof L.GeoJSON) { layer.eachLayer((layer) => { if (layer.feature.properties["fill-opacity"] != 0) { layer.setStyle({ fillOpacity: value }); } if (layer.feature.properties["opacity"] != 0) { layer.setStyle({ opacity: value }); } }); } } function formatProperty(value) { if (typeof value == "string" && value.startsWith("http")) { return `${value}`; } else { return value; } } function showLoader() { document.getElementById("progress-bar").style.display = "block"; } function hideLoader() { document.getElementById("progress-bar").style.display = "none"; } function switchBaseLayer(name) { const basemaps = Object.keys(layers.basemaps); for (const layer of basemaps) { if (layer == name) { map.addLayer(layers.basemaps[layer]); } else { map.removeLayer(layers.basemaps[layer]); } } } function loadBasemapConfig(file) { const reader = new FileReader(); reader.onload = (e) => { const config = JSON.parse(reader.result); if (confirm("Are you sure you want to overwrite the default basemaps?")) { loadCustomBasemaps(config); localStorage.setItem("basemapConfig", JSON.stringify(config)); } } reader.readAsText(file); } function loadCustomBasemaps(config) { const basemaps = Object.keys(layers.basemaps); for (const layer of basemaps) { map.removeLayer(layers.basemaps[layer]); // controls.layerCtrl.removeLayer(layers.basemaps[layer]); } layers.basemaps = {}; config.forEach((element, index) => { let layer = null; if (element.type == "wms") { layer = L.tileLayer.wms(element.url, { maxNativeZoom: element.maxZoom ? element.maxZoom : 18, maxZoom: map.getMaxZoom(), layers: element.layers, format: element.format ? element.format : "image/png", attribution: element.attribution ? element.attribution : "" }); } else if (element.type == "xyz") { layer = L.tileLayer(element.url, { maxNativeZoom: element.maxZoom ? element.maxZoom : 18, maxZoom: map.getMaxZoom(), attribution: element.attribution ? element.attribution : "" }); } if (index == 0) { layer.addTo(map); } layers.basemaps[element.name] = layer; // controls.layerCtrl.addBaseLayer(layer, element.name); }); layers.basemaps["None"] = L.tileLayer("", {maxZoom: map.getMaxZoom()}); // controls.layerCtrl.addBaseLayer(layers.basemaps["None"], "None"); } function showInfo() { alert("Welcome to GPSMap.app, an offline capable map viewer with GPS integration!nn- Tap the + button to load a raster MBTiles, GeoJSON, KML, GPX, or CSV file directly from your device or cloud storage.n- Tap the layers button to view online basemaps and manage offline layers.nnDeveloped by Bryan McBride - mcbride.bryan@gmail.comn" + version); } function loadSavedFeatures() { featureStore.length().then((numberOfKeys) => { if (numberOfKeys > 0) { featureStore.iterate((value, key, iterationNumber) => { createVectorLayer(value.name, value.features, key, false); }).then(() => { // console.log("saved features loaded!"); }).catch((err) => { console.log(err); }); } }).catch((err) => { console.log(err); }); } function loadSavedMaps() { const urlParams = new URLSearchParams(window.location.search); mapStore.length().then((numberOfKeys) => { /*if (!urlParams.has("map") && numberOfKeys != 1) { controls.locateCtrl.start(); }*/ if (numberOfKeys > 0) { mapStore.iterate((value, key, iterationNumber) => { const group = L.layerGroup(null, {key: key}); const groupID = L.Util.stamp(group); layers.groups[groupID] = group; addOverlayLayer(group, value.name, groupID, true); group.once("add", (e) => { const layer = L.tileLayer.mbTiles(value.mbtiles, { autoScale: true, fitBounds: (urlParams.has("map") && urlParams.get("map") == key) ? true : (numberOfKeys == 1) ? true : false, updateWhenIdle: false, zIndex: 10 }); group.addLayer(layer); layers.overlays[groupID] = layer; }); if ((numberOfKeys == 1) || (urlParams.has("map") && urlParams.get("map") == key)) { map.addLayer(group); switchBaseLayer("None"); // document.querySelector(`[data-layer='${groupID}']`).disabled = false; } }).then(() => { // console.log("saved maps loaded!"); loadSavedFeatures(); }).catch((err) => { console.log(err); }); } else { loadSavedFeatures(); } }).catch((err) => { console.log(err); }); } function loadURLparams() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has("map")) { const url = urlParams.get("map"); mapStore.keys().then((keys) => { if (!keys.includes(url)) { fetchFile(url); } }).catch((err) => { console.log(err); }); // window.history.replaceState(null, null, window.location.pathname); } } function fetchFile(url) { if (navigator.onLine) { showLoader(); fetch(url) .then(response => response.arrayBuffer()) .then(data => { hideLoader(); const layer = L.tileLayer.mbTiles(data, { autoScale: true, fitBounds: true, updateWhenIdle: false }).on("databaseloaded", (e) => { const name = layer.options.name ? layer.options.name : url.split("/").pop().split(".").slice(0, -1).join("."); const value = { "name": name, "mbtiles": data }; mapStore.setItem(url, value).then((value) => { addOverlayLayer(layer, name); }).catch((err) => { alert("Error saving data!"); }); }).addTo(map); layers.overlays[L.Util.stamp(layer)] = layer; }) .catch((error) => { hideLoader(); console.error("Error:", error); alert("Error fetching remote file..."); }); } else { alert("Must be online to fetch data!"); hideLoader(); } } function updateNetworkStatus() { if (navigator.onLine) { document.getElementById("status").style.color = "green"; } else { switchBaseLayer("None"); document.getElementById("status").style.color = "red"; } } // Drag and drop files const dropArea = document.getElementById("map"); ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { dropArea.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ["dragenter", "dragover"].forEach((eventName) => { dropArea.addEventListener(eventName, showLoader, false); }); ["dragleave", "drop"].forEach(eventName => { dropArea.addEventListener(eventName, hideLoader, false); }); dropArea.addEventListener("drop", (e) => { const file = e.dataTransfer.files[0]; handleFile(file); }, false); window.addEventListener("offline", (e) => { updateNetworkStatus(); }); window.addEventListener("online", (e) => { updateNetworkStatus(); }); document.addEventListener("DOMContentLoaded", () => { showLoader(); controls.locateCtrl.start(); }); initSqlJs({ locateFile: function() { return "assets/vendor/sqljs-1.8.0/sql-wasm.wasm"; } }).then(function(SQL){ loadURLparams(); loadSavedMaps(); if (localStorage.getItem("basemapConfig")) { loadCustomBasemaps(JSON.parse(localStorage.getItem("basemapConfig"))); } if (!navigator.onLine) { switchBaseLayer("None"); } else if (localStorage.getItem("basemap")) { switchBaseLayer(localStorage.getItem("basemap")); } setTimeout(() => { switchBaseLayer("Streets"); }, 1000); });