Compare commits
1 commit
main
...
switch-map
Author | SHA1 | Date | |
---|---|---|---|
4aa7e32273 |
2 changed files with 40 additions and 68 deletions
|
@ -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
98
scl.py
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue