From 4aa7e32273a114650a0c2f0b173587032c482dc8 Mon Sep 17 00:00:00 2001 From: Liam Steckler Date: Sun, 25 Aug 2024 11:00:39 -0700 Subject: [PATCH] Switch to py-staticmaps --- requirements.txt | 10 ++++- scl.py | 98 +++++++++++++++--------------------------------- 2 files changed, 40 insertions(+), 68 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9eb502d..eaaf539 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,28 @@ +appdirs==1.4.4 blurhash==1.1.4 certifi==2024.7.4 charset-normalizer==3.3.2 decorator==5.1.1 +future==1.0.0 +geographiclib==2.0 greenlet==3.0.3 idna==3.8 -pip-install==1.3.5 Mastodon.py==1.8.1 numpy==2.1.0 pillow==10.4.0 +pip-install==1.3.5 +py-staticmaps==0.4.0 python-dateutil==2.9.0.post0 python-magic==0.4.27 +python-slugify==8.0.4 PyYAML==6.0.2 requests==2.32.3 +s2sphere==0.2.5 shapely==2.0.6 six==1.16.0 SQLAlchemy==2.0.32 staticmap==0.5.7 +svgwrite==1.4.3 +text-unidecode==1.3 typing_extensions==4.12.2 urllib3==2.2.2 diff --git a/scl.py b/scl.py index 0934fa8..1c1ec64 100644 --- a/scl.py +++ b/scl.py @@ -5,13 +5,13 @@ from typing import Optional import mastodon import requests import shapely +import staticmaps import yaml from mastodon import Mastodon -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw from sqlalchemy import create_engine, select from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column -from staticmap import CircleMarker, Polygon, StaticMap from geospatial import convert_outage_geometry @@ -36,47 +36,6 @@ mastodon_client = Mastodon( ) -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) - - def classify_event_size(num_people: int) -> dict[str, str | bool]: if num_people < 250: return { @@ -98,6 +57,15 @@ def classify_event_size(num_people: int) -> dict[str, str | bool]: } +# Only needed for workaround for https://github.com/flopp/py-staticmaps/issues/39 +def textsize(self: ImageDraw.ImageDraw, *args, **kwargs): + x, y, w, h = self.textbbox((0, 0), *args, **kwargs) + return w, h + + +ImageDraw.ImageDraw.textsize = textsize + + def get_hashtag_string(event) -> str: city = str() try: @@ -131,22 +99,28 @@ def do_initial_post( # Fallback location from the SCL API in case one couldn't be reverse geocoded area_text = event["city"] try: - map = AttribStaticMap( - 512, - 512, - url_template="https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}.png?api_key=" - + stadiamaps_api_key, + context = staticmaps.Context() + tile_provider = staticmaps.TileProvider( + "stadia-outdoors", + "https://tiles.stadiamaps.com/tiles/outdoors/$z/$x/$y.png?api_key=$k", + shards=["a", "b", "c", "d"], + attribution="© Stadia Maps © OpenMapTiles © OpenStreetMap", + api_key=stadiamaps_api_key, ) + context.set_tile_provider(tile_provider) + assert event["polygons"]["type"] == "polygon" for ring in event["polygons"]["rings"]: - polygon = Polygon( - ring, - # Appending 7F to the fill_color makes it 50% transparent - fill_color="{}7F".format(event_class["outage_color"]), - outline_color=event_class["outage_color"], - simplify=True, + context.add_object( + staticmaps.Area( + [staticmaps.create_latlng(lat, lon) for lon, lat in ring], + fill_color=staticmaps.parse_color( + "{}7F".format(event_class["outage_color"]) + ), + width=2, + color=staticmaps.parse_color(event_class["outage_color"]), + ) ) - map.add_polygon(polygon) try: outage_center: shapely.Point = outage_geometries.centroid @@ -158,16 +132,6 @@ def do_initial_post( # SE Corner assert outage_center.y > 47.2 and outage_center.x < -122 - marker_outline = CircleMarker( - (outage_center.x, outage_center.y), "white", 18 - ) - marker = CircleMarker( - (outage_center.x, outage_center.y), event_class["outage_color"], 12 - ) - - map.add_marker(marker_outline) - map.add_marker(marker) - # Zoom level 17 ensures that we won't get any building/POI names, just street names geocode_url = "{nominatim_url}/reverse?lat={lat}&lon={lon}&format=geocodejson&zoom=17".format( nominatim_url=nominatim_url, @@ -227,10 +191,10 @@ def do_initial_post( except Exception: alt_text = "A map showing the location of the outage." - map_image = map.render() + map_image: Image = context.render_pillow(512, 512) with io.BytesIO() as map_image_file: - map_image.save(map_image_file, format="PNG", optimize=True) + map_image.save(map_image_file, format="PNG") map_media_post = mastodon_client.media_post( map_image_file.getvalue(), mime_type="image/png",