277 lines
No EOL
9.7 KiB
HTML
277 lines
No EOL
9.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>logbook2map</title>
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css">
|
|
<style>
|
|
body, html {
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
#map {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
#input_modal {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
display: none;
|
|
|
|
textarea {
|
|
width: 80%;
|
|
height: 80%;
|
|
font-size: 1.5em;
|
|
min-width: 20em;
|
|
min-height: 20em;
|
|
max-width: 40em;
|
|
max-height: 40em;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="map"></div>
|
|
|
|
<div id="input_modal">
|
|
<textarea placeholder="ICAO_departure;ICAO_destination Validate with Ctrl+Enter Load new data with Ctrl+O"></textarea>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
|
<script>
|
|
const osmStyle = {
|
|
"version": 8,
|
|
"sources": {
|
|
"osm": {
|
|
"type": "raster",
|
|
"tiles": ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"],
|
|
"tileSize": 256,
|
|
"attribution": "© OpenStreetMap Contributors",
|
|
"maxzoom": 19
|
|
}
|
|
},
|
|
"layers": [
|
|
{
|
|
"id": "osm",
|
|
"type": "raster",
|
|
"source": "osm" // This must match the source key above
|
|
}
|
|
]
|
|
};
|
|
var map = new maplibregl.Map({
|
|
container: 'map', // container id
|
|
style: osmStyle, // style URL
|
|
center: [17.97, 57.91], // starting position [lng, lat]
|
|
zoom: 3.7 // starting zoom
|
|
});
|
|
|
|
function _updateMap(csvData, airportsData) {
|
|
const lines = csvData.split('\n');
|
|
|
|
const features_nav = new Array();
|
|
const features_local = new Array();
|
|
for(const line of lines) {
|
|
const [from, to] = line.split(',');
|
|
|
|
const fromAirport = airportsData[from];
|
|
const toAirport = airportsData[to];
|
|
|
|
if(!fromAirport) { alert(`Airport ${from} not found`); return; }
|
|
if(!toAirport) { alert(`Airport ${to} not found`); return; }
|
|
|
|
const fromLongitude = parseFloat(fromAirport.longitude);
|
|
const fromLatitude = parseFloat(fromAirport.latitude);
|
|
const toLongitude = parseFloat(toAirport.longitude);
|
|
const toLatitude = parseFloat(toAirport.latitude);
|
|
|
|
// if from == to, draw a point
|
|
if(fromAirport == toAirport) {
|
|
features_local.push( {
|
|
type: 'Feature',
|
|
properties: {
|
|
from,
|
|
to
|
|
},
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [fromLongitude, fromLatitude]
|
|
}
|
|
} );
|
|
}
|
|
else {
|
|
features_nav.push( {
|
|
type: 'Feature',
|
|
properties: {
|
|
from,
|
|
to
|
|
},
|
|
geometry: {
|
|
type: 'LineString',
|
|
coordinates: [
|
|
[fromLongitude, fromLatitude],
|
|
[toLongitude, toLatitude]
|
|
]
|
|
}
|
|
} );
|
|
|
|
// Add a point for each airport
|
|
features_nav.push( {
|
|
type: 'Feature',
|
|
properties: {
|
|
name: fromAirport.name
|
|
},
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [fromLongitude, fromLatitude]
|
|
}
|
|
} );
|
|
|
|
features_nav.push( {
|
|
type: 'Feature',
|
|
properties: {
|
|
name: toAirport.name
|
|
},
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [toLongitude, toLatitude]
|
|
}
|
|
} );
|
|
}
|
|
}
|
|
|
|
map.addSource('nav', {
|
|
type: 'geojson',
|
|
data: {
|
|
type: 'FeatureCollection',
|
|
features: features_nav
|
|
}
|
|
});
|
|
|
|
map.addSource('local', {
|
|
type: 'geojson',
|
|
data: {
|
|
type: 'FeatureCollection',
|
|
features: features_local
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'nav_lines',
|
|
type: 'line',
|
|
source: 'nav',
|
|
layout: {
|
|
'line-join': 'round',
|
|
'line-cap': 'round'
|
|
},
|
|
paint: {
|
|
'line-color': '#ce42f5',
|
|
'line-width': 2
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'nav_points',
|
|
type: 'circle',
|
|
source: 'nav',
|
|
paint: {
|
|
'circle-radius': 5,
|
|
'circle-color': '#ce42f5'
|
|
},
|
|
filter: ['==', '$type', 'Point']
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'local_points',
|
|
type: 'circle',
|
|
source: 'local',
|
|
paint: {
|
|
'circle-radius': 5,
|
|
'circle-color': '#f00'
|
|
},
|
|
filter: ['==', '$type', 'Point']
|
|
});
|
|
}
|
|
|
|
function updateMap(csvData) {
|
|
// download https://davidmegginson.github.io/ourairports-data/airports.csv if not locally available
|
|
const request = indexedDB.open('my_database', 1);
|
|
request.onupgradeneeded = function(event) {
|
|
const db = event.target.result;
|
|
const objectStore = db.createObjectStore('airports');
|
|
};
|
|
request.onerror = function(event) { alert('Error while opening database:\n' + event.target.error); };
|
|
request.onsuccess = function(event) {
|
|
const db = event.target.result;
|
|
const transaction = db.transaction(['airports'], 'readwrite');
|
|
const objectStore = transaction.objectStore('airports');
|
|
|
|
const getRequest = objectStore.get('airports');
|
|
getRequest.onerror = function(event) { alert("Error getting data:\n" + event.target.error); };
|
|
getRequest.onsuccess = function(event) {
|
|
if (!event.target.result) {
|
|
// Data not found, fetch and store it
|
|
fetch('https://davidmegginson.github.io/ourairports-data/airports.csv')
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
console.info('Downloaded airports.csv, parsing the data...');
|
|
const airports = {};
|
|
for(const airport of data.split('\n')) {
|
|
const [_id, ident, _type, name, latitude, longitude] = airport.split(',');
|
|
if(!ident) continue;
|
|
airports[ident.replace(/^"|"$/g, '')] = { name, latitude, longitude };
|
|
}
|
|
|
|
const transaction = db.transaction(['airports'], 'readwrite');
|
|
const objectStore = transaction.objectStore('airports');
|
|
objectStore.put(airports, 'airports');
|
|
_updateMap(csvData, airports);
|
|
})
|
|
.catch(error => { alert('Error while downloading airports.csv: \n' + error); });
|
|
} else {
|
|
console.log('Data found in local storage!');
|
|
_updateMap(csvData, event.target.result);
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
function showInputModal() {
|
|
document.getElementById('input_modal').style.display = 'flex';
|
|
|
|
const textarea = document.querySelector('#input_modal textarea');
|
|
textarea.focus();
|
|
textarea.addEventListener('keydown', event => {
|
|
if(event.ctrlKey && event.key === 'Enter') {
|
|
const csvData = textarea.value;
|
|
updateMap(csvData);
|
|
document.getElementById('input_modal').style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
showInputModal();
|
|
|
|
document.addEventListener('keydown', event => {
|
|
if(event.ctrlKey && event.key === 'o') {
|
|
event.preventDefault();
|
|
showInputModal();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |