From a0a5b5bf42cf8fbb13d1539eb8af2f2b9be7a548 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Mon, 3 Jul 2023 17:13:20 -0500 Subject: [PATCH] Add NHL arenas layer --- layers/nhl-arenas/README.md | 7 +++ layers/nhl-arenas/get_data.py | 97 +++++++++++++++++++++++++++++++++++ layers/nhl-arenas/layer.js | 30 +++++++++++ layers/nhl-arenas/visited.js | 10 ++++ main.js | 6 ++- 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 layers/nhl-arenas/README.md create mode 100755 layers/nhl-arenas/get_data.py create mode 100644 layers/nhl-arenas/layer.js create mode 100644 layers/nhl-arenas/visited.js diff --git a/layers/nhl-arenas/README.md b/layers/nhl-arenas/README.md new file mode 100644 index 0000000..685f982 --- /dev/null +++ b/layers/nhl-arenas/README.md @@ -0,0 +1,7 @@ +https://en.wikipedia.org/wiki/Template:NHL_arenas + +https://en.wikipedia.org/wiki/Template:NHL_arenas_map + +https://en.wikipedia.org/wiki/List_of_National_Hockey_League_arenas + +https://statsapi.web.nhl.com/api/v1/venues diff --git a/layers/nhl-arenas/get_data.py b/layers/nhl-arenas/get_data.py new file mode 100755 index 0000000..e83d365 --- /dev/null +++ b/layers/nhl-arenas/get_data.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +import re +from typing import Tuple +import requests +import json + +BASE_URL="https://en.wikipedia.org/w/api.php" + +# A previous attempt at this script used the NHL api...except for some reason +# they don't include all the arenas! Only 16 are included on this page: +# +# venueData = requests.get("https://statsapi.web.nhl.com/api/v1/venues").json() +# +# venues = [] for venue in venueData["venues"]: # Special-case a few entries if +# venue["name"] == "NASSAU LIVE CENTER": # As of 2021 the islanders now play out +# of UBS Arena. Not sure why this # is still in the list. continue if +# venue["name"] == "Prudential Center Map & Info": # not sure why they call +# it that venue["name"] = "Prudential Center" ... + +def wikipedia_request(page_title: str) -> str: + params = { + "action": "parse", + "page": f"{page_title}", + "prop": "wikitext", + "formatversion": 2, + "format": "json", + } + return requests.get(url=BASE_URL, params=params).json()['parse']['wikitext'] + +def get_wikipedia_coords_for_arena(arena: str) -> Tuple[float, float]: + raw_arena_page = wikipedia_request(arena) + # print(raw_arena_page) + + # e.g. `coordinates = {{coord|40.712094|N|73.727157|W|...}}` + match = re.search(r"[Cc]oord\|([0-9.]*)\|N\|([0-9.]*)\|W\|", raw_arena_page) + if match: + return (float(match[1]), -float(match[2])) + + # e.g. `coordinates = {{Coord|47.622|-122.354|...}}` + match = re.search(r"[Cc]oord\|([0-9.]*)\|(-[0-9.]*)\|[^\d]", raw_arena_page) + if match: + return (float(match[1]), float(match[2])) + + # e.g. `coordinates = {{coord|44|56|41|N|93|6|4|W|...}}` + match = re.search(r"[Cc]oord\|([0-9.]*)\|([0-9.]*)\|([0-9.]*)\|N\|([0-9.]*)\|([0-9.]*)\|([0-9.]*)\|W\|", raw_arena_page) # Assuming northern and western hemispheres; currently safe + lat_deg = match[1] + lat_min = match[2] + lat_sec = match[3] + lon_deg = match[4] + lon_min = match[5] + lon_sec = match[6] + lat = float(lat_deg) + float(lat_min) / 60 + float(lat_sec) / 3600 + lon = float(lon_deg) + float(lon_min) / 60 + float(lon_sec) / 3600 + return (lat, -lon) + + +print("Retrieving arena list...", flush=True) +raw_arenas_list = wikipedia_request("Template:NHL arenas") +arena_names = re.findall(r"\* +\[\[ ?(.*?)(?:\|.*)? ?\]\]", raw_arenas_list) +arenas = [] +for arena in arena_names: + print(f"Retrieving data for {arena}...", flush=True) + nominatim_params = { + 'q': arena, + 'format': "json", + 'addressdetails': 1, + } + if arena == "SAP Center": + nominatim_params['q'] = "SAP Center at San Jose" # https://en.wikipedia.org/w/index.php?title=SAP_Center&oldid=690907747 + nominatim_result = requests.get(url="https://nominatim.openstreetmap.org/search", params=nominatim_params).json()[0] + + # confirm it matches what wikipedia claims + wiki_lat, wiki_lon = get_wikipedia_coords_for_arena(arena) + if wiki_lat - float(nominatim_result["lat"]) > 0.1 or wiki_lon - float(nominatim_result["lon"]) > 0.1: + raise Exception(f"Data mismatch for {arena}: {wiki_lat} vs {nominatim_result['lat']}; {wiki_lon} vs {nominatim_result['lon']}") + + arenas.append({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [float(nominatim_result["lon"]), float(nominatim_result["lat"])], # yes, [lon, lat] since it's [x, y] + }, + "properties": { + "name": arena, + "osm_id": nominatim_result["osm_id"], + "address": nominatim_result["address"], # requires &addressdetails=1 (https://nominatim.org/release-docs/latest/api/Search/#output-details) + }, + }) + +geojson = { + "type": "FeatureCollection", + "features": arenas, +} + +with open("nhl-arenas-data.geojson", "w") as f: + f.write(json.dumps(geojson)) diff --git a/layers/nhl-arenas/layer.js b/layers/nhl-arenas/layer.js new file mode 100644 index 0000000..434ac9c --- /dev/null +++ b/layers/nhl-arenas/layer.js @@ -0,0 +1,30 @@ +import VectorLayer from 'ol/layer/Vector'; +import {Vector as VectorSource} from 'ol/source.js'; +import GeoJSON from 'ol/format/GeoJSON.js'; + +import {Style, Stroke, Circle, Fill} from 'ol/style.js'; + +import arenaURL from '/data/nhl-arenas-data.geojson?url'; // TODO: remove `?url`? + +import visitedArenas from './visited.js' + +const vectorLayer = new VectorLayer({ + source: new VectorSource({ + url: arenaURL, + format: new GeoJSON, + }), + // TODO: use '✓' and '✗' (or maybe '✔' and '✘') (from https://en.wikipedia.org/wiki/List_of_Unicode_characters#Dingbats) + // TODO: popups with Arena information (name, photo, date visited, score from that day) + style: function(feature, resolution) { + const base_color = visitedArenas.some(a => a.name == feature.get('name')) ? '#008800' : '#FF0000'; + return new Style({ + image: new Circle({ + radius: 50/Math.pow(resolution, 1/4), + fill: new Fill({color: base_color + '33'}), + stroke: new Stroke({color: base_color, width: 1}), + }), + }); + } +}); + +export default vectorLayer; diff --git a/layers/nhl-arenas/visited.js b/layers/nhl-arenas/visited.js new file mode 100644 index 0000000..eb0bde9 --- /dev/null +++ b/layers/nhl-arenas/visited.js @@ -0,0 +1,10 @@ +export default [ + { + name: "Xcel Energy Center", + date: new Date('2022-04-28'), + }, + { + name: "Enterprise Center", + date: new Date('2023-03-15'), + }, +] diff --git a/main.js b/main.js index d1e15c9..3f9c993 100644 --- a/main.js +++ b/main.js @@ -5,6 +5,7 @@ import OSM from 'ol/source/OSM'; import {fromLonLat} from 'ol/proj.js'; import amtrakLayer from './layers/amtrak/layer.js'; +import arenasLayer from './layers/nhl-arenas/layer.js'; const map = new Map({ target: 'map', @@ -19,4 +20,7 @@ const map = new Map({ }) }); -map.addLayer(amtrakLayer); +map.getLayers().extend([ + amtrakLayer, + arenasLayer, +]);