logbook2map/index.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&#10;&#10;&#10;Validate with Ctrl+Enter&#10;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": "&copy; 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>