<!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 = lines.map(line => { 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); return { type: 'Feature', properties: { from, to }, geometry: { type: 'LineString', coordinates: [ [fromLongitude, fromLatitude], [toLongitude, toLatitude] ] } }; }); map.addSource('routes', { type: 'geojson', data: { type: 'FeatureCollection', features } }); map.addLayer({ id: 'routes', type: 'line', source: 'routes', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': '#bd34eb', 'line-width': 4 } }); } 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>