Compare commits

..

6 commits

Author SHA1 Message Date
5c27528247 Fix MIME type for image
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline was successful
2024-11-23 22:13:46 -08:00
e6722876bd Log outage geometries to database (#68)
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline failed
To enable us to detect changes to to the geometry for future updating of the map (#67)

Reviewed-on: #68
2024-11-23 17:14:30 -08:00
fbdc85bc29 Merge pull request 'Update dependency numpy to v2.1.3' (#66) from renovate/numpy-2.x into main
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline failed
Reviewed-on: #66
2024-11-20 11:14:22 -08:00
7f4e8232dc Update dependency numpy to v2.1.3
Some checks failed
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/vulnerability-scan Pipeline failed
ci/woodpecker/pull_request_closed/lint Pipeline was successful
ci/woodpecker/pull_request_closed/vulnerability-scan Pipeline was successful
2024-11-06 19:16:31 +00:00
14f217a91e Update dependency SQLAlchemy to v2.0.36
All checks were successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/vulnerability-scan Pipeline was successful
ci/woodpecker/pull_request_closed/lint Pipeline was successful
ci/woodpecker/pull_request_closed/vulnerability-scan Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline was successful
2024-11-02 03:10:47 +00:00
ab112abeed quieter-restoration-time (#64)
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/vulnerability-scan Pipeline was successful
Closes #62

Reviewed-on: #64
2024-10-27 12:30:44 -07:00
3 changed files with 41 additions and 10 deletions

View file

@ -1,4 +1,5 @@
from shapely import MultiPolygon, Polygon
from shapely import MultiPolygon, Polygon, Geometry, to_wkb, from_wkb
from sqlalchemy.types import TypeDecorator, LargeBinary
def convert_outage_geometry(event) -> MultiPolygon:
@ -9,3 +10,21 @@ def convert_outage_geometry(event) -> MultiPolygon:
for ring in event["polygons"]["rings"]:
polygon_list.append(Polygon(ring))
return MultiPolygon(polygon_list)
class DBGeometry(TypeDecorator):
impl = LargeBinary
cache_ok = True
def process_bind_param(self, value, dialect):
if isinstance(value, Geometry):
value = to_wkb(value)
return value
def process_result_value(self, value, dialect):
if value is None:
return value
else:
if not isinstance(value, Geometry):
value = from_wkb(value)
return value

View file

@ -6,7 +6,7 @@ greenlet==3.1.1
idna==3.10
pip-install==1.3.5
Mastodon.py==1.8.1
numpy==2.1.2
numpy==2.1.3
pillow==11.0.0
python-dateutil==2.9.0.post0
python-magic==0.4.27
@ -14,7 +14,7 @@ PyYAML==6.0.2
requests==2.32.3
shapely==2.0.6
six==1.16.0
SQLAlchemy==2.0.35
SQLAlchemy==2.0.36
staticmap==0.5.7
typing_extensions==4.12.2
urllib3==2.2.3

26
scl.py
View file

@ -5,15 +5,16 @@ from typing import Optional
import mastodon
import requests
import shapely
import staticmap
import yaml
from mastodon import Mastodon
from PIL import Image, ImageDraw, ImageFont
from shapely import Geometry
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 geospatial import convert_outage_geometry
from geospatial import DBGeometry, convert_outage_geometry
post_datetime_format = "%b %e %l:%M %p"
@ -36,7 +37,7 @@ mastodon_client = Mastodon(
)
class AttribStaticMap(StaticMap, object):
class AttribStaticMap(staticmap.StaticMap, object):
def __init__(self, *args, **kwargs):
self.attribution = "© Stadia Maps © OpenMapTiles © OpenStreetMap"
super(AttribStaticMap, self).__init__(*args, **kwargs)
@ -137,7 +138,7 @@ def do_initial_post(
)
assert event["polygons"]["type"] == "polygon"
for ring in event["polygons"]["rings"]:
polygon = Polygon(
polygon = staticmap.Polygon(
ring,
# Appending 7F to the fill_color makes it 50% transparent
fill_color="{}7F".format(event_class["outage_color"]),
@ -221,7 +222,7 @@ def do_initial_post(
map_image.save(map_image_file, format="WebP", method=6)
map_media_post = mastodon_client.media_post(
map_image_file.getvalue(),
mime_type="image/png",
mime_type="image/webp",
description=alt_text,
)
map_media_post_id = map_media_post["id"]
@ -272,7 +273,9 @@ Cause: {}
class Base(DeclarativeBase):
pass
type_annotation_map = {
Geometry: DBGeometry,
}
class SclOutage(Base):
@ -292,9 +295,11 @@ class SclOutage(Base):
max_num_people: Mapped[int] = mapped_column()
neighborhood: Mapped[Optional[str]] = mapped_column()
city: Mapped[Optional[str]] = mapped_column()
outage_geometries: Mapped[Optional[Geometry]] = mapped_column()
geometries_modified: Mapped[Optional[bool]] = mapped_column()
def __repr__(self) -> str:
return f"SclOutage(scl_outage_id={self.scl_outage_id!r}, most_recent_post_id={self.most_recent_post_id!r}, initial_post_id={self.initial_post_id!r}, map_media_post_id={self.map_media_post_id!r}, last_updated_time={self.last_updated_time!r}, no_longer_in_response_time={self.no_longer_in_response_time!r}, start_time={self.start_time!r}, num_people={self.num_people!r}, max_num_people={self.max_num_people!r}, neighborhood={self.neighborhood!r}, city={self.city!r})"
return f"SclOutage(scl_outage_id={self.scl_outage_id!r}, most_recent_post_id={self.most_recent_post_id!r}, initial_post_id={self.initial_post_id!r}, map_media_post_id={self.map_media_post_id!r}, last_updated_time={self.last_updated_time!r}, no_longer_in_response_time={self.no_longer_in_response_time!r}, start_time={self.start_time!r}, num_people={self.num_people!r}, max_num_people={self.max_num_people!r}, neighborhood={self.neighborhood!r}, city={self.city!r}, outage_geometries={self.outage_geometries!r}, geometries_modified={self.geometries_modified!r})"
engine = create_engine("sqlite:///scl.db")
@ -366,6 +371,10 @@ with Session(engine) as session:
# Used to determine the maximum number of people affected by this outage, to determine if it's worth posting about
existing_record.max_num_people = event["numPeople"]
max_event_class = classify_event_size(existing_record.max_num_people)
if existing_record.outage_geometries != outage_geometries:
print("Geometries modified")
existing_record.outage_geometries = outage_geometries
existing_record.geometries_modified = True
if updated_properties:
updated_properties.sort()
@ -426,6 +435,8 @@ with Session(engine) as session:
existing_record.map_media_post_id = initial_post_result[
"map_media_post_id"
]
else:
print("Existing record was found, and no properties were updated.")
session.commit()
except NoResultFound:
@ -476,6 +487,7 @@ with Session(engine) as session:
max_num_people=event["numPeople"],
neighborhood=neighborhood,
city=city,
outage_geometries=outage_geometries,
)
session.add(new_outage_record)
session.commit()