Fix center of outage calculation (#24)
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline failed

Solves #23

Reviewed-on: #24
This commit is contained in:
Liam Steckler 2024-04-20 15:03:16 -07:00
parent 8b54a4b78c
commit 4accc75c06
4 changed files with 54 additions and 20 deletions

View file

@ -2,7 +2,7 @@ when:
branch: main
steps:
- name: lint
image: python:3-alpine
image: python:3-slim
commands:
- python -m pip install --upgrade pip
- python -m pip install -r requirements.txt

11
geospatial.py Normal file
View file

@ -0,0 +1,11 @@
from shapely import MultiPolygon, Polygon
def convert_outage_geometry(event) -> MultiPolygon:
assert event["polygons"]["type"] == "polygon"
assert event["polygons"]["hasZ"] is False
assert event["polygons"]["hasM"] is False
polygon_list = []
for ring in event["polygons"]["rings"]:
polygon_list.append(Polygon(ring))
return MultiPolygon(polygon_list)

View file

@ -4,12 +4,15 @@ charset-normalizer==3.3.2
decorator==5.1.1
greenlet==3.0.3
idna==3.6
install==1.3.5
Mastodon.py==1.8.1
numpy==1.26.4
pillow==10.2.0
python-dateutil==2.9.0.post0
python-magic==0.4.27
PyYAML==6.0.1
requests==2.31.0
shapely==2.0.3
six==1.16.0
SQLAlchemy==2.0.28
staticmap==0.5.7

58
scl.py
View file

@ -1,17 +1,19 @@
import io
import math
from datetime import datetime
from typing import Optional
import mastodon
import requests
import shapely
import yaml
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
from staticmap import CircleMarker, Polygon, StaticMap
from geospatial import convert_outage_geometry
post_datetime_format = "%b %e %l:%M %p"
@ -104,7 +106,11 @@ def get_hashtag_string(event) -> str:
def do_initial_post(
event, event_class, start_time: datetime, estimated_restoration_time: datetime
event,
event_class,
start_time: datetime,
estimated_restoration_time: datetime,
outage_geometries: shapely.MultiPolygon,
) -> dict[str, str | None]:
post_id = None
map_media_post_id = None
@ -127,30 +133,32 @@ def do_initial_post(
simplify=True,
)
map.add_polygon(polygon)
map_image = map.render()
try:
outage_center: shapely.Point = outage_geometries.centroid
def num2deg(xtile, ytile, zoom):
n = 1 << zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return lat_deg, lon_deg
center_lat_lon = num2deg(map.x_center, map.y_center, map.zoom)
assert outage_center.geom_type == "Point"
# Check to make sure the calculated lat and lon are sane enough
# NW Corner
assert center_lat_lon[0] < 48 and center_lat_lon[1] > -122.6
assert outage_center.y < 48 and outage_center.x > -122.6
# SE Corner
assert center_lat_lon[0] > 47.2 and center_lat_lon[1] < -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
geocode_url = "{nominatim_url}/reverse?lat={lat}&lon={lon}&format=geocodejson&zoom=17".format(
nominatim_url=nominatim_url,
lat=center_lat_lon[0],
lon=center_lat_lon[1],
lat=outage_center.y,
lon=outage_center.x,
)
geocode_headers = {"User-Agent": "seattlecitylight-mastodon-bot"}
geocode_response = requests.get(geocode_url, headers=geocode_headers)
@ -202,6 +210,8 @@ def do_initial_post(
except Exception:
alt_text = "A map showing the location of the outage."
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_client.media_post(
@ -297,6 +307,8 @@ with Session(engine) as session:
else:
status = None
outage_geometries = convert_outage_geometry(event)
try:
hashtag_string = get_hashtag_string(event)
existing_record = lookup_result.one()
@ -361,7 +373,11 @@ with Session(engine) as session:
"Posting an event that grew above the threshold required to post"
)
initial_post_result = do_initial_post(
event, event_class, start_time, estimated_restoration_time
event,
event_class,
start_time,
estimated_restoration_time,
outage_geometries,
)
existing_record.initial_post_id = initial_post_result["post_id"]
existing_record.most_recent_post_id = initial_post_result["post_id"]
@ -382,7 +398,11 @@ with Session(engine) as session:
)
else:
initial_post_result = do_initial_post(
event, event_class, start_time, estimated_restoration_time
event,
event_class,
start_time,
estimated_restoration_time,
outage_geometries,
)
post_id = initial_post_result["post_id"]
map_media_post_id = initial_post_result["map_media_post_id"]