From 69e777033e1d627b84ec7bd24a0242a8833bea86 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Tue, 17 Mar 2026 16:36:00 -0500 Subject: [PATCH 1/4] Add airports layer --- .gitignore | 1 + layers/airports/get_data.py | 40 +++++++++++++++++++++++++++++++++++++ layers/airports/index.js | 40 +++++++++++++++++++++++++++++++++++++ layers/index.js | 2 ++ 4 files changed, 83 insertions(+) create mode 100755 layers/airports/get_data.py create mode 100644 layers/airports/index.js diff --git a/.gitignore b/.gitignore index 99cdcdf..4ffd683 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ layers/survey-markers/states.js layers/tjx/data/chains.js layers/crop-history/data/counties.js layers/mn-sales-tax/data/history.js +layers/airports/data/types.js .direnv venv diff --git a/layers/airports/get_data.py b/layers/airports/get_data.py new file mode 100755 index 0000000..9a00e40 --- /dev/null +++ b/layers/airports/get_data.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +import csv +import io +import json +import urllib.request + +index_imports = [] + +resp = urllib.request.urlopen("https://davidmegginson.github.io/ourairports-data/airports.csv") +airports = list(csv.DictReader(io.TextIOWrapper(resp))) +types = set([a['type'] for a in airports]) + +for airport_type in types: + + geojson = { + "type": "FeatureCollection", + "features": [], + } + + for airport in airports: + if airport['type'] == airport_type: + geojson['features'].append({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [float(airport['longitude_deg']), float(airport['latitude_deg'])] + }, + "properties": airport, # TODO + }) + + with open(f'data/{airport_type}.geojson', 'w') as f: + f.write(json.dumps(geojson)) + +with open("data/types.js", 'w') as f: + for airport_type in types: + f.write(f"import {airport_type} from './{airport_type}.geojson?url';\n") + f.write('\nexport default {\n') + for airport_type in types: + f.write(f" {airport_type}: {airport_type},\n") + f.write("};\n") diff --git a/layers/airports/index.js b/layers/airports/index.js new file mode 100644 index 0000000..82955d8 --- /dev/null +++ b/layers/airports/index.js @@ -0,0 +1,40 @@ +import GeoJSON from 'ol/format/GeoJSON.js'; +import VectorLayer from 'ol/layer/Vector.js'; +import VectorSource from 'ol/source/Vector.js'; + +import types from './data/types.js'; + +import { Circle, Fill, Stroke, Style } from 'ol/style.js'; + +const layers = { + name: "Airports", + layers: [], +}; + +for (let [name, url] of Object.entries(types)) { + const layer = new VectorLayer({ + source: new VectorSource({ + url, + format: new GeoJSON, + }), + style: new Style({ + image: new Circle({ + radius: name == 'large_airport' ? 10 : name == "small_airport" ? 3 : 5, + fill: new Fill({ + color: 'rgba(255,255,255,0.4)', + }), + stroke: new Stroke({ + color: 'red', + width: name == 'large_airport' ? 4 : 2, + }), + }), + }), + }); + + layers.layers.push({ + name, + layer, + }); +} + +export default layers; diff --git a/layers/index.js b/layers/index.js index 7285f17..cbe48d9 100644 --- a/layers/index.js +++ b/layers/index.js @@ -26,6 +26,7 @@ import upsServiceAreas from './ups/index.js'; import fccTowersLayer from './fcc/towers/layer.js'; import mnSalesTaxLayers from './mn-sales-tax/index.js'; import versatilesLayers from './versatiles.js'; +import airports from './airports/index.js'; const layerCategories = [ { // Base maps @@ -118,6 +119,7 @@ const layerCategories = [ tjx, cropHistory, mnSalesTaxLayers, + airports, ]; export default layerCategories; From 5abb9ae18b1b1c0f5e7fade996884976a920884d Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Tue, 17 Mar 2026 18:11:52 -0500 Subject: [PATCH 2/4] Don't crash page if chandler layer can't load --- layers/chandler/layer.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/layers/chandler/layer.js b/layers/chandler/layer.js index 85ac3f1..6848f36 100644 --- a/layers/chandler/layer.js +++ b/layers/chandler/layer.js @@ -5,24 +5,15 @@ import {Style} from 'ol/style.js'; import Icon from 'ol/style/Icon.js'; import pinURL from '/layers/chandler/pin.svg?url'; // TODO: remove `?url`? -import { Feature } from 'ol'; +import { Collection, Feature } from 'ol'; import { Point } from 'ol/geom'; import { fromLonLat } from 'ol/proj'; -const res = await fetch("https://whereis.chandlerswift.com/api/0/last"); -const locs = await res.json(); -const loc = locs[0]; - -let feature = new Feature({ - geometry: new Point(fromLonLat([loc.lon, loc.lat])), - ...loc -}); +let features = new Collection(); const vectorLayer = new VectorLayer({ source: new VectorSource({ - features: [ - feature, - ] + features, }), style: new Style({ image: new Icon({ @@ -32,12 +23,23 @@ const vectorLayer = new VectorLayer({ }), }); -setInterval(async function(){ +async function refresh(){ const res = await fetch("https://whereis.chandlerswift.com/api/0/last"); const locs = await res.json(); const loc = locs[0]; - feature.setProperties(loc); // TODO: this won't remove a property if it was in a previous response but not this one - feature.getGeometry().setCoordinates(fromLonLat([loc.lon, loc.lat])); -}, 10 * 1000); + // TODO: I could probably just `features[0] = …` but I'm not sure if that causes problems with re-rendering features + if (features.getLength() == 0) { + features.push(new Feature({ + geometry: new Point(fromLonLat([loc.lon, loc.lat])), + ...loc + })); + } else { + features.item(0).setProperties(loc); // TODO: this won't remove a property if it was in a previous response but not this one + features.item(0).getGeometry().setCoordinates(fromLonLat([loc.lon, loc.lat])); + } +} + +refresh(); +setInterval(refresh, 10 * 1000); export default vectorLayer; From 4b37e0fd5585cbe44cdaad51f53ad8dab18b2914 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Tue, 17 Mar 2026 18:15:32 -0500 Subject: [PATCH 3/4] We're in Utah! (Add to states visited layer) --- layers/states/visited.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layers/states/visited.js b/layers/states/visited.js index 42fa417..917fea2 100644 --- a/layers/states/visited.js +++ b/layers/states/visited.js @@ -43,7 +43,7 @@ const visitedStatesLists = { 'SD', 'TN', 'TX', - // 'UT', + 'UT', 'VA', 'VT', 'WA', @@ -95,7 +95,7 @@ const visitedStatesLists = { 'SD', 'TN', 'TX', - // 'UT', + 'UT', 'VA', // 'VT', 'WA', From bb829d7a78d784b66efaf3fd05cbe17d08228fd0 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Tue, 17 Mar 2026 18:24:41 -0500 Subject: [PATCH 4/4] Refine airport layer data --- layers/airports/get_data.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/layers/airports/get_data.py b/layers/airports/get_data.py index 9a00e40..ec19efd 100755 --- a/layers/airports/get_data.py +++ b/layers/airports/get_data.py @@ -19,14 +19,26 @@ for airport_type in types: for airport in airports: if airport['type'] == airport_type: - geojson['features'].append({ + f = { "type": "Feature", "geometry": { "type": "Point", "coordinates": [float(airport['longitude_deg']), float(airport['latitude_deg'])] }, - "properties": airport, # TODO - }) + "properties": { + "ID": airport['ident'], + "Name": airport['name'], + "City": airport['municipality'], + "Country": airport['iso_country'], + "Region": airport['iso_region'], + "Elevation (ft)": airport['elevation_ft'], + } + } + if airport['wikipedia_link']: + f['properties']['Wikipedia'] = airport['wikipedia_link'] + if airport['home_link']: + f['properties']['Website'] = airport['home_link'] + geojson['features'].append(f) with open(f'data/{airport_type}.geojson', 'w') as f: f.write(json.dumps(geojson))