@@ 1,3 1,7 @@
+import time
+from collections import defaultdict
+
+from lxml import html
import requests
import simdjson
from PIL import Image, ImageDraw
@@ 11,6 15,7 @@ MAP = [
]
VESSELS = "https://www.wsdot.com/ferries/vesselwatch/Vessels.ashx"
+SCHEDULE = "https://www.wsdot.com/ferries/schedule/scheduledetailbyroute.aspx?route=ana-sj"
OVERPASS = "https://overpass-api.de/api/interpreter"
MIN_LON = min(MAP, key=lambda coord: coord[0])[0]
@@ 83,19 88,7 @@ def paste(src: Image, dest_x, dest_y):
DRAW.point((dest_x + x - center_x, dest_y + y - center_y))
-def main():
- filename = f"coastlines-{hash((MIN_LAT, MIN_LON, MAX_LAT, MAX_LON))}.json"
- try:
- with open(filename) as f:
- coastlines = simdjson.load(f)
- except FileNotFoundError:
- print("Downloading coastlines")
- coastlines = requests.get(OVERPASS, {"data": OVERPASS_QUERY}).json()
- with open(filename, "w") as f:
- simdjson.dump(coastlines, f)
-
- draw_coastlines(coastlines)
-
+def draw_boats():
boats = requests.get(VESSELS).json()["vessellist"]
boats = filter(lambda boat: boat["route"] == "ANA-SJ", boats)
boats = filter(lambda boat: boat["aterm_id"] != "", boats)
@@ 116,6 109,81 @@ def main():
DRAW.point((x, y))
DRAW.text((x, y), label, anchor="mm")
+
+def draw_schedule():
+ response = requests.get(SCHEDULE)
+ if not response.ok:
+ return
+
+ root = html.fromstring(response.content)
+ schedule_lines = defaultdict(list)
+ current_label = None
+ table = root.xpath('//*[@class="schedgridbyroute"]')[0]
+
+ current_time = time.localtime()
+
+ for row in table.xpath('tr'):
+ header = row.xpath('td/h4[@id="sailingDescription"]/text()')
+ # Section header
+ if len(header) > 0:
+ current_label = header[0]
+ continue
+
+ # Terminal names
+ headers = row.xpath('td/h4[@id="sailingTerminal"]')
+ if len(headers) > 0:
+ continue
+
+ # Filter out non-body rows
+ if 'HighlightOnHover' not in row.attrib['class']:
+ continue
+
+ line = []
+ times = []
+ for col in row.xpath('td'):
+ cell_contents = ''.join(col.xpath('descendant::*/text()')).replace('\xa0', '').strip()
+ if '-' in cell_contents:
+ cell_contents = ''
+ if len(col.xpath('div/div[@class="pm"]')) > 0 and ':' in cell_contents:
+ hour, minute = map(int, cell_contents.split(':'))
+ times.append(60 * (hour + 12) + minute)
+ cell_contents += 'p'
+ elif ':' in cell_contents:
+ hour, minute = map(int, cell_contents.split(':'))
+ times.append(60 * hour + minute)
+ elif 'Noon' in cell_contents:
+ times.append(60 * 12)
+ elif 'Midnight' in cell_contents:
+ times.append(60 * 24)
+ line.append(cell_contents)
+ if current_time.tm_hour * 60 + current_time.tm_min < max(times):
+ schedule_lines[current_label].append('{} {:6} {:6} {:6} {:6} {:6}'.format(*line))
+
+ mf_or_satsun = 'Saturday and Sunday' if time.localtime().tm_wday > 4 else 'Monday through Friday'
+ left_col = f'Leave Westbound ({mf_or_satsun})'
+ right_col = f'Leave Eastbound ({mf_or_satsun})'
+
+ left_lines = ' Anac. Lopez Shaw Orcas Friday\n' + '\n'.join(schedule_lines[left_col])
+ right_lines = ' Friday Orcas Shaw Lopez Anac.\n' + '\n'.join(schedule_lines[right_col])
+
+ DRAW.multiline_text((2, MAP_HEIGHT + 2), left_lines)
+ DRAW.multiline_text((WIDTH / 2 + 2, MAP_HEIGHT + 2), right_lines)
+
+def main():
+ filename = f"coastlines-{hash((MIN_LAT, MIN_LON, MAX_LAT, MAX_LON))}.json"
+ try:
+ with open(filename) as f:
+ coastlines = simdjson.load(f)
+ except FileNotFoundError:
+ print("Downloading coastlines")
+ coastlines = requests.get(OVERPASS, {"data": OVERPASS_QUERY}).json()
+ with open(filename, "w") as f:
+ simdjson.dump(coastlines, f)
+
+ draw_coastlines(coastlines)
+ draw_boats()
+ draw_schedule()
+
SCREEN.save("map.bmp", "BMP")