Switch to py-staticmaps

This commit is contained in:
Liam Steckler 2024-08-25 11:00:39 -07:00
parent aae40e22ad
commit 4aa7e32273
2 changed files with 40 additions and 68 deletions

View file

@ -1,20 +1,28 @@
appdirs==1.4.4
blurhash==1.1.4 blurhash==1.1.4
certifi==2024.7.4 certifi==2024.7.4
charset-normalizer==3.3.2 charset-normalizer==3.3.2
decorator==5.1.1 decorator==5.1.1
future==1.0.0
geographiclib==2.0
greenlet==3.0.3 greenlet==3.0.3
idna==3.8 idna==3.8
pip-install==1.3.5
Mastodon.py==1.8.1 Mastodon.py==1.8.1
numpy==2.1.0 numpy==2.1.0
pillow==10.4.0 pillow==10.4.0
pip-install==1.3.5
py-staticmaps==0.4.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-magic==0.4.27 python-magic==0.4.27
python-slugify==8.0.4
PyYAML==6.0.2 PyYAML==6.0.2
requests==2.32.3 requests==2.32.3
s2sphere==0.2.5
shapely==2.0.6 shapely==2.0.6
six==1.16.0 six==1.16.0
SQLAlchemy==2.0.32 SQLAlchemy==2.0.32
staticmap==0.5.7 staticmap==0.5.7
svgwrite==1.4.3
text-unidecode==1.3
typing_extensions==4.12.2 typing_extensions==4.12.2
urllib3==2.2.2 urllib3==2.2.2

98
scl.py
View file

@ -5,13 +5,13 @@ from typing import Optional
import mastodon import mastodon
import requests import requests
import shapely import shapely
import staticmaps
import yaml import yaml
from mastodon import Mastodon from mastodon import Mastodon
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw
from sqlalchemy import create_engine, select from sqlalchemy import create_engine, select
from sqlalchemy.exc import NoResultFound from sqlalchemy.exc import NoResultFound
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
from staticmap import CircleMarker, Polygon, StaticMap
from geospatial import convert_outage_geometry 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]: def classify_event_size(num_people: int) -> dict[str, str | bool]:
if num_people < 250: if num_people < 250:
return { 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: def get_hashtag_string(event) -> str:
city = str() city = str()
try: try:
@ -131,22 +99,28 @@ def do_initial_post(
# Fallback location from the SCL API in case one couldn't be reverse geocoded # Fallback location from the SCL API in case one couldn't be reverse geocoded
area_text = event["city"] area_text = event["city"]
try: try:
map = AttribStaticMap( context = staticmaps.Context()
512, tile_provider = staticmaps.TileProvider(
512, "stadia-outdoors",
url_template="https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}.png?api_key=" "https://tiles.stadiamaps.com/tiles/outdoors/$z/$x/$y.png?api_key=$k",
+ stadiamaps_api_key, 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" assert event["polygons"]["type"] == "polygon"
for ring in event["polygons"]["rings"]: for ring in event["polygons"]["rings"]:
polygon = Polygon( context.add_object(
ring, staticmaps.Area(
# Appending 7F to the fill_color makes it 50% transparent [staticmaps.create_latlng(lat, lon) for lon, lat in ring],
fill_color="{}7F".format(event_class["outage_color"]), fill_color=staticmaps.parse_color(
outline_color=event_class["outage_color"], "{}7F".format(event_class["outage_color"])
simplify=True, ),
width=2,
color=staticmaps.parse_color(event_class["outage_color"]),
)
) )
map.add_polygon(polygon)
try: try:
outage_center: shapely.Point = outage_geometries.centroid outage_center: shapely.Point = outage_geometries.centroid
@ -158,16 +132,6 @@ def do_initial_post(
# SE Corner # SE Corner
assert outage_center.y > 47.2 and outage_center.x < -122 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 # 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( geocode_url = "{nominatim_url}/reverse?lat={lat}&lon={lon}&format=geocodejson&zoom=17".format(
nominatim_url=nominatim_url, nominatim_url=nominatim_url,
@ -227,10 +191,10 @@ def do_initial_post(
except Exception: except Exception:
alt_text = "A map showing the location of the outage." 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: 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_media_post = mastodon_client.media_post(
map_image_file.getvalue(), map_image_file.getvalue(),
mime_type="image/png", mime_type="image/png",