From 53a5cc8aa0b426934762c72a53dfa8f4fddfac49 Mon Sep 17 00:00:00 2001 From: Liam Steckler Date: Sat, 13 Jan 2024 17:13:38 -0800 Subject: [PATCH] Initial map support --- scl.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/scl.py b/scl.py index 2b5223a..0547ece 100644 --- a/scl.py +++ b/scl.py @@ -1,11 +1,14 @@ +import io from datetime import datetime from typing import Optional import requests from mastodon import Mastodon +from PIL import Image, ImageDraw, ImageFont from sqlalchemy import create_engine, select from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column +from staticmap import Polygon, StaticMap post_datetime_format = "%b %e %l:%M %p" @@ -19,6 +22,51 @@ except requests.JSONDecodeError: mastodon = Mastodon(access_token="scl_bot_mastodon.secret") +with open("stadiamaps_api_key.secret", "r+") as stadiamaps_api_key_file: + # Reading from a file + stadiamaps_api_key = stadiamaps_api_key_file.read() + + +class AttribStaticMap(StaticMap, object): + def __init__(self, *args, **kwargs): + self.attribution = "© Stadia Maps © OpenMapTiles © OpenStreetMap" + super(AttribStaticMap, self).__init__(*args, **kwargs) + + def _draw_features(self, image): + super(AttribStaticMap, self)._draw_features(image) + + txt = Image.new("RGBA", image.size, (255, 255, 255, 0)) + # get a font + # fnt = ImageFont.truetype('FreeMono.ttf', 12) + fnt = ImageFont.load_default() + # get a drawing context + d = ImageDraw.Draw(txt) + + textSize = fnt.getbbox(self.attribution) + textPosition = (image.size[0] - textSize[2], image.size[1] - textSize[3]) + offset = 2 + options = {"fill": (255, 255, 255, 180)} + d.rectangle( + [ + (textPosition[0] - (2 * offset), textPosition[1] - (2 * offset)), + ( + textSize[2] + textPosition[0] + (2 * offset), + textSize[3] + textPosition[1] + (2 * offset), + ), + ], + **options, + ) + + # draw text, full opacity + d.text( + (textPosition[0] - offset, textPosition[1] - offset), + self.attribution, + font=fnt, + fill="black", + ) + + image.paste(txt, (0, 0), txt) + class Base(DeclarativeBase): pass @@ -56,10 +104,13 @@ with Session(engine) as session: lookup_result = session.scalars(lookup_statement) if event["numPeople"] < 250: outage_size = "Small" + outage_color = "#F97316" elif event["numPeople"] < 1000: outage_size = "Medium" + outage_color = "#EF4444" else: outage_size = "Large" + outage_color = "991B1B" if "status" in event: status = event["status"] @@ -142,9 +193,35 @@ Cause: {} hashtag_string, ) + try: + map = AttribStaticMap( + 512, + 512, + url_template="https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}.png?api_key=" + + stadiamaps_api_key, + ) + assert event["polygons"]["type"] == "polygon" + for ring in event["polygons"]["rings"]: + polygon = Polygon( + ring, "{}7F".format(outage_color), outage_color, simplify=True + ) + map.add_polygon(polygon) + map_image = map.render() + with io.BytesIO() as map_image_file: + map_image.save(map_image_file, format="PNG", optimize=True) + map_media_post = mastodon.media_post( + map_image_file.getvalue(), mime_type="image/png" + ) + + except Exception as e: + print(e) + print( + "Ran into an issue with generating/uploading the map. Will post without it." + ) + map_media_post = None + mastodon_post_result = mastodon.status_post( - status=post_text, - visibility="public", + status=post_text, media_ids=map_media_post, visibility="public" ) new_outage_record = SclOutage(