2018-03-31 13:17:08 -07:00
|
|
|
<!DOCTYPE html>
|
2022-04-17 10:41:44 -07:00
|
|
|
<html lang="en-US">
|
2018-03-31 13:17:08 -07:00
|
|
|
<head>
|
2022-04-17 10:41:44 -07:00
|
|
|
<meta charset="UTF-8">
|
2018-03-31 13:17:08 -07:00
|
|
|
<title>OwnTracks View</title>
|
2022-04-17 10:41:44 -07:00
|
|
|
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0">
|
2018-03-31 13:17:08 -07:00
|
|
|
|
|
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
2022-04-17 10:41:44 -07:00
|
|
|
<link rel="icon" sizes="192x192" href="../static/recorder.png">
|
|
|
|
<link rel="apple-touch-icon" href="../static/recorder.png">
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="../utils/map.css">
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="../static/leaflet/leaflet.css">
|
|
|
|
<script src="../static/leaflet/leaflet.js"></script>
|
|
|
|
|
|
|
|
<script type="module">
|
|
|
|
import { fetchApiData } from "../utils/network.js";
|
|
|
|
import { generatePopupHTML } from "../utils/map.js";
|
|
|
|
import { markerStyle, strokeStyle } from "../utils/map_leaflet.js";
|
|
|
|
|
|
|
|
const map = L.map('map-canvas').setView([0.0, 0.0], 1);
|
|
|
|
let lastLatLng = L.latLng(0.0, 0.0);
|
|
|
|
|
|
|
|
const osm_layer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
|
|
}).addTo(map);
|
|
|
|
|
|
|
|
const hikebike_layer = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
|
|
|
|
attribution: 'Map Data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Hillshading: SRTM3 v2 (<a href="https://www2.jpl.nasa.gov/srtm/">NASA</a>)',
|
|
|
|
});
|
|
|
|
|
|
|
|
const baseMaps = { "OpenStreetMap": osm_layer, "HikeBikeMap": hikebike_layer};
|
|
|
|
|
|
|
|
L.control.layers(baseMaps).addTo(map);
|
|
|
|
|
|
|
|
const empty_geojson = {
|
|
|
|
type: "FeatureCollection",
|
|
|
|
features: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
const trackLayer = new L.GeoJSON( empty_geojson, {
|
|
|
|
style: function(feature) {
|
|
|
|
return strokeStyle;
|
|
|
|
},
|
|
|
|
coordsToLatLng: function(coords) {
|
|
|
|
return coordsToLatLng(coords);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// this defines how the data obtained from @@@LASTPOS@@@ will be rendered
|
|
|
|
const lastposLayer = new L.GeoJSON( empty_geojson , {
|
|
|
|
pointToLayer: function (feature, latlng) {
|
|
|
|
return L.circleMarker(latlng, markerStyle);
|
|
|
|
},
|
|
|
|
onEachFeature: function(feature, layer) {
|
|
|
|
if (feature.geometry.type === "Point") {
|
|
|
|
const description = generatePopupHTML({
|
|
|
|
...feature.properties,
|
|
|
|
lat: feature.geometry.coordinates[1].toFixed(4),
|
|
|
|
lon: feature.geometry.coordinates[0].toFixed(4),
|
|
|
|
});
|
|
|
|
|
|
|
|
layer.bindPopup(description);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
style: function(feature) {
|
|
|
|
if (feature.geometry.type === "Point") {
|
|
|
|
return {};
|
|
|
|
} else {
|
|
|
|
return strokeStyle;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
coordsToLatLng: function(coords) {
|
|
|
|
return coordsToLatLng(coords);
|
|
|
|
},
|
|
|
|
});
|
2018-03-31 13:17:08 -07:00
|
|
|
|
2022-04-17 10:41:44 -07:00
|
|
|
map.addLayer(trackLayer);
|
|
|
|
map.addLayer(lastposLayer);
|
|
|
|
lastposLayer.bringToFront();
|
|
|
|
|
|
|
|
update_lastpos(true);
|
|
|
|
|
|
|
|
// define the "Load track" button in the bottom left corner, design copied from vmap.html
|
|
|
|
const LoadTrackControl = L.Control.extend({
|
|
|
|
options: {
|
|
|
|
position: 'bottomleft',
|
2018-03-31 13:17:08 -07:00
|
|
|
},
|
|
|
|
|
2022-04-17 10:41:44 -07:00
|
|
|
onAdd: function (map) {
|
|
|
|
const controlDiv = L.DomUtil.create('div', 'leaflet-control-button');
|
|
|
|
|
|
|
|
const controlUI = document.createElement('div');
|
|
|
|
controlUI.style.backgroundColor = '#fff';
|
|
|
|
controlUI.style.border = '2px solid #fff';
|
|
|
|
controlUI.style.borderRadius = '3px';
|
|
|
|
controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
|
|
|
|
controlUI.style.cursor = 'pointer';
|
|
|
|
controlUI.style.marginBottom = '22px';
|
|
|
|
controlUI.style.textAlign = 'center';
|
|
|
|
controlUI.title = 'Click to load the track onto the map';
|
|
|
|
controlDiv.appendChild(controlUI);
|
|
|
|
|
|
|
|
// Set CSS for the control interior
|
|
|
|
const controlText = document.createElement('div');
|
|
|
|
controlText.style.color = 'rgb(25,25,25)';
|
|
|
|
controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
|
|
|
|
controlText.style.fontSize = '16px';
|
|
|
|
controlText.style.lineHeight = '38px';
|
|
|
|
controlText.style.paddingLeft = '5px';
|
|
|
|
controlText.style.paddingRight = '5px';
|
|
|
|
controlText.innerHTML = 'Load track';
|
|
|
|
controlUI.appendChild(controlText);
|
|
|
|
|
|
|
|
// when the button is clicked load the @@@GEO@@@ data and display it on the trackLayer passed as an option to the LoadTrackControl
|
|
|
|
controlDiv.onclick = (async function() {
|
|
|
|
|
|
|
|
const trackLayer = this.options.trackLayer;
|
|
|
|
lastLatLng = L.latLng(0.0, 0.0);
|
|
|
|
|
|
|
|
const data = await fetchApiData({ url: "@@@GEO@@@" });
|
|
|
|
|
|
|
|
trackLayer.clearLayers();
|
|
|
|
trackLayer.addData(data);
|
|
|
|
update_lastpos(false);
|
|
|
|
map.fitBounds(trackLayer.getBounds());
|
|
|
|
|
|
|
|
}).bind(this);
|
|
|
|
|
|
|
|
return controlDiv;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
map.addControl(new LoadTrackControl({ trackLayer: trackLayer }));
|
|
|
|
|
|
|
|
// this will update the lastposLayer to fetch and show updated latest positions
|
|
|
|
async function update_lastpos(fitBounds) {
|
|
|
|
|
|
|
|
const data = await fetchApiData({ url: "@@@LASTPOS@@@" });
|
|
|
|
|
|
|
|
data.data.forEach(function(latest) {
|
|
|
|
const latLng = coordsToLatLng([latest.lon, latest.lat]);
|
|
|
|
const latest_geojson = {
|
|
|
|
"type": "Feature",
|
|
|
|
"geometry": {
|
|
|
|
"type": "Point",
|
|
|
|
"coordinates": [ latLng.lng, latLng.lat ],
|
|
|
|
},
|
|
|
|
"properties": latest,
|
|
|
|
};
|
|
|
|
|
|
|
|
lastposLayer.clearLayers();
|
|
|
|
lastposLayer.addData(latest_geojson);
|
|
|
|
if(fitBounds) {
|
|
|
|
map.fitBounds(lastposLayer.getBounds());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function coordsToLatLng([ lon, lat ]) {
|
|
|
|
const dist0 = Math.abs(lon - lastLatLng.lng);
|
|
|
|
const dist1 = Math.abs(lon + 360.0 - lastLatLng.lng);
|
|
|
|
const dist2 = Math.abs(lon - 360.0 - lastLatLng.lng);
|
|
|
|
if (dist0 > dist1 || dist0 > dist2) {
|
|
|
|
if (dist0 > dist1) {
|
|
|
|
lon += 360.0;
|
|
|
|
} else {
|
|
|
|
lon -= 360.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const latLng = L.GeoJSON.coordsToLatLng([lon, lat]);
|
|
|
|
lastLatLng = latLng;
|
|
|
|
return latLng;
|
2018-03-31 13:17:08 -07:00
|
|
|
}
|
2022-04-17 10:41:44 -07:00
|
|
|
|
2018-03-31 13:17:08 -07:00
|
|
|
</script>
|
2022-04-17 10:41:44 -07:00
|
|
|
|
2018-03-31 13:17:08 -07:00
|
|
|
</head>
|
|
|
|
<body>
|
2022-04-17 10:41:44 -07:00
|
|
|
<div id="map-canvas"></div>
|
2018-03-31 13:17:08 -07:00
|
|
|
</body>
|
|
|
|
</html>
|