💥 All location points provided by backend are in EPSG:3857

This commit is contained in:
2023-04-23 11:14:11 +02:00
parent 8fafdb3dde
commit d94027da9a
5 changed files with 75 additions and 58 deletions

View File

@@ -12,10 +12,9 @@ from typing import (
from aiofiles import open as async_open from aiofiles import open as async_open
from aiohttp import ClientSession from aiohttp import ClientSession
from msgspec import ValidationError from msgspec import ValidationError
from msgspec.json import Decoder from msgspec.json import Decoder
from rich import print from pyproj import Transformer
from shapefile import Reader as ShapeFileReader, ShapeRecord from shapefile import Reader as ShapeFileReader, ShapeRecord
from ..db import Database from ..db import Database
@@ -64,6 +63,8 @@ class IdfmInterface:
self._http_headers = {"Accept": "application/json", "apikey": self._api_key} self._http_headers = {"Accept": "application/json", "apikey": self._api_key}
self._epsg2154_epsg3857_transformer = Transformer.from_crs(2154, 3857)
self._json_stops_decoder = Decoder(type=List[IdfmStop]) self._json_stops_decoder = Decoder(type=List[IdfmStop])
self._json_stop_areas_decoder = Decoder(type=List[IdfmStopArea]) self._json_stop_areas_decoder = Decoder(type=List[IdfmStopArea])
self._json_connection_areas_decoder = Decoder(type=List[IdfmConnectionArea]) self._json_connection_areas_decoder = Decoder(type=List[IdfmConnectionArea])
@@ -89,19 +90,19 @@ class IdfmInterface:
( (
StopShape, StopShape,
self._request_stop_shapes, self._request_stop_shapes,
IdfmInterface._format_idfm_stop_shapes, self._format_idfm_stop_shapes,
), ),
( (
ConnectionArea, ConnectionArea,
self._request_idfm_connection_areas, self._request_idfm_connection_areas,
IdfmInterface._format_idfm_connection_areas, self._format_idfm_connection_areas,
), ),
( (
StopArea, StopArea,
self._request_idfm_stop_areas, self._request_idfm_stop_areas,
IdfmInterface._format_idfm_stop_areas, self._format_idfm_stop_areas,
), ),
(Stop, self._request_idfm_stops, IdfmInterface._format_idfm_stops), (Stop, self._request_idfm_stops, self._format_idfm_stops),
) )
for model, get_method, format_method in STEPS: for model, get_method, format_method in STEPS:
@@ -391,23 +392,26 @@ class IdfmInterface:
return ret return ret
@staticmethod def _format_idfm_stops(self, *stops: IdfmStop) -> Iterable[Stop]:
def _format_idfm_stops(*stops: IdfmStop) -> Iterable[Stop]:
for stop in stops: for stop in stops:
fields = stop.fields fields = stop.fields
try: try:
created_ts = int(fields.arrcreated.timestamp()) # type: ignore created_ts = int(fields.arrcreated.timestamp()) # type: ignore
except AttributeError: except AttributeError:
created_ts = None created_ts = None
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
fields.arrxepsg2154, fields.arryepsg2154
)
yield Stop( yield Stop(
id=int(fields.arrid), id=int(fields.arrid),
name=fields.arrname, name=fields.arrname,
latitude=fields.arrgeopoint.lat, epsg3857_x=epsg3857_point[0],
longitude=fields.arrgeopoint.lon, epsg3857_y=epsg3857_point[1],
town_name=fields.arrtown, town_name=fields.arrtown,
postal_region=fields.arrpostalregion, postal_region=fields.arrpostalregion,
xepsg2154=fields.arrxepsg2154,
yepsg2154=fields.arryepsg2154,
transport_mode=TransportMode(fields.arrtype.value), transport_mode=TransportMode(fields.arrtype.value),
version=fields.arrversion, version=fields.arrversion,
created_ts=created_ts, created_ts=created_ts,
@@ -419,53 +423,76 @@ class IdfmInterface:
record_ts=int(stop.record_timestamp.timestamp()), record_ts=int(stop.record_timestamp.timestamp()),
) )
@staticmethod def _format_idfm_stop_areas(self, *stop_areas: IdfmStopArea) -> Iterable[StopArea]:
def _format_idfm_stop_areas(*stop_areas: IdfmStopArea) -> Iterable[StopArea]:
for stop_area in stop_areas: for stop_area in stop_areas:
fields = stop_area.fields fields = stop_area.fields
try: try:
created_ts = int(fields.zdacreated.timestamp()) # type: ignore created_ts = int(fields.zdacreated.timestamp()) # type: ignore
except AttributeError: except AttributeError:
created_ts = None created_ts = None
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
fields.zdaxepsg2154, fields.zdayepsg2154
)
yield StopArea( yield StopArea(
id=int(fields.zdaid), id=int(fields.zdaid),
name=fields.zdaname, name=fields.zdaname,
town_name=fields.zdatown, town_name=fields.zdatown,
postal_region=fields.zdapostalregion, postal_region=fields.zdapostalregion,
xepsg2154=fields.zdaxepsg2154, epsg3857_x=epsg3857_point[0],
yepsg2154=fields.zdayepsg2154, epsg3857_y=epsg3857_point[1],
type=StopAreaType(fields.zdatype.value), type=StopAreaType(fields.zdatype.value),
version=fields.zdaversion, version=fields.zdaversion,
created_ts=created_ts, created_ts=created_ts,
changed_ts=int(fields.zdachanged.timestamp()), changed_ts=int(fields.zdachanged.timestamp()),
) )
@staticmethod
def _format_idfm_connection_areas( def _format_idfm_connection_areas(
self,
*connection_areas: IdfmConnectionArea, *connection_areas: IdfmConnectionArea,
) -> Iterable[ConnectionArea]: ) -> Iterable[ConnectionArea]:
for connection_area in connection_areas: for connection_area in connection_areas:
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
connection_area.zdcxepsg2154, connection_area.zdcyepsg2154
)
yield ConnectionArea( yield ConnectionArea(
id=int(connection_area.zdcid), id=int(connection_area.zdcid),
name=connection_area.zdcname, name=connection_area.zdcname,
town_name=connection_area.zdctown, town_name=connection_area.zdctown,
postal_region=connection_area.zdcpostalregion, postal_region=connection_area.zdcpostalregion,
xepsg2154=connection_area.zdcxepsg2154, epsg3857_x=epsg3857_point[0],
yepsg2154=connection_area.zdcyepsg2154, epsg3857_y=epsg3857_point[1],
transport_mode=StopAreaType(connection_area.zdctype.value), transport_mode=StopAreaType(connection_area.zdctype.value),
version=connection_area.zdcversion, version=connection_area.zdcversion,
created_ts=int(connection_area.zdccreated.timestamp()), created_ts=int(connection_area.zdccreated.timestamp()),
changed_ts=int(connection_area.zdcchanged.timestamp()), changed_ts=int(connection_area.zdcchanged.timestamp()),
) )
@staticmethod def _format_idfm_stop_shapes(
def _format_idfm_stop_shapes(*shape_records: ShapeRecord) -> Iterable[StopShape]: self, *shape_records: ShapeRecord
) -> Iterable[StopShape]:
for shape_record in shape_records: for shape_record in shape_records:
epsg3857_points = [
self._epsg2154_epsg3857_transformer.transform(*point)
for point in shape_record.shape.points
]
bbox_it = iter(shape_record.shape.bbox)
epsg3857_bbox = [
self._epsg2154_epsg3857_transformer.transform(*point)
for point in zip(bbox_it, bbox_it)
]
yield StopShape( yield StopShape(
id=shape_record.record[1], id=shape_record.record[1],
type=shape_record.shape.shapeType, type=shape_record.shape.shapeType,
bounding_box=list(shape_record.shape.bbox), epsg3857_bbox=epsg3857_bbox,
points=shape_record.shape.points, epsg3857_points=epsg3857_points,
) )
async def render_line_picto(self, line: Line) -> tuple[None | str, None | str]: async def render_line_picto(self, line: Line) -> tuple[None | str, None | str]:
@@ -508,7 +535,7 @@ class IdfmInterface:
print("---------------------------------------------------------------------") print("---------------------------------------------------------------------")
return data return data
async def get_next_passages(self, stop_point_id: str) -> IdfmResponse | None: async def get_next_passages(self, stop_point_id: int) -> IdfmResponse | None:
ret = None ret = None
params = {"MonitoringRef": f"STIF:StopPoint:Q:{stop_point_id}:"} params = {"MonitoringRef": f"STIF:StopPoint:Q:{stop_point_id}:"}
async with ClientSession(headers=self._http_headers) as session: async with ClientSession(headers=self._http_headers) as session:

View File

@@ -48,8 +48,8 @@ class _Stop(Base):
name = mapped_column(String, nullable=False, index=True) name = mapped_column(String, nullable=False, index=True)
town_name = mapped_column(String, nullable=False) town_name = mapped_column(String, nullable=False)
postal_region = mapped_column(String, nullable=False) postal_region = mapped_column(String, nullable=False)
xepsg2154 = mapped_column(BigInteger, nullable=False) epsg3857_x = mapped_column(Float, nullable=False)
yepsg2154 = mapped_column(BigInteger, nullable=False) epsg3857_y = mapped_column(Float, nullable=False)
version = mapped_column(String, nullable=False) version = mapped_column(String, nullable=False)
created_ts = mapped_column(BigInteger) created_ts = mapped_column(BigInteger)
@@ -111,8 +111,6 @@ class Stop(_Stop):
id = mapped_column(BigInteger, ForeignKey("_stops.id"), primary_key=True) id = mapped_column(BigInteger, ForeignKey("_stops.id"), primary_key=True)
latitude = mapped_column(Float, nullable=False)
longitude = mapped_column(Float, nullable=False)
transport_mode = mapped_column(Enum(TransportMode), nullable=False) transport_mode = mapped_column(Enum(TransportMode), nullable=False)
accessibility = mapped_column(Enum(IdfmState), nullable=False) accessibility = mapped_column(Enum(IdfmState), nullable=False)
visual_signs_available = mapped_column(Enum(IdfmState), nullable=False) visual_signs_available = mapped_column(Enum(IdfmState), nullable=False)
@@ -191,8 +189,8 @@ class StopShape(Base):
id = mapped_column(BigInteger, primary_key=True) # Same id than ConnectionArea id = mapped_column(BigInteger, primary_key=True) # Same id than ConnectionArea
type = mapped_column(Integer, nullable=False) type = mapped_column(Integer, nullable=False)
bounding_box = mapped_column(JSON) epsg3857_bbox = mapped_column(JSON)
points = mapped_column(JSON) epsg3857_points = mapped_column(JSON)
__tablename__ = "stop_shapes" __tablename__ = "stop_shapes"
@@ -206,8 +204,8 @@ class ConnectionArea(Base):
name = mapped_column(String, nullable=False) name = mapped_column(String, nullable=False)
town_name = mapped_column(String, nullable=False) town_name = mapped_column(String, nullable=False)
postal_region = mapped_column(String, nullable=False) postal_region = mapped_column(String, nullable=False)
xepsg2154 = mapped_column(BigInteger, nullable=False) epsg3857_x = mapped_column(Float, nullable=False)
yepsg2154 = mapped_column(BigInteger, nullable=False) epsg3857_y = mapped_column(Float, nullable=False)
transport_mode = mapped_column(Enum(StopAreaType), nullable=False) transport_mode = mapped_column(Enum(StopAreaType), nullable=False)
version = mapped_column(String, nullable=False) version = mapped_column(String, nullable=False)

View File

@@ -7,10 +7,8 @@ class Stop(BaseModel):
id: int id: int
name: str name: str
town: str town: str
lat: float epsg3857_x: float
lon: float epsg3857_y: float
# xepsg2154: int
# yepsg2154: int
lines: list[str] lines: list[str]
@@ -18,15 +16,16 @@ class StopArea(BaseModel):
id: int id: int
name: str name: str
town: str town: str
# xepsg2154: int
# yepsg2154: int
type: StopAreaType type: StopAreaType
lines: list[str] # SNCF lines are linked to stop areas and not stops. lines: list[str] # SNCF lines are linked to stop areas and not stops.
stops: list[Stop] stops: list[Stop]
Point = tuple[float, float]
class StopShape(BaseModel): class StopShape(BaseModel):
id: int id: int
type: int type: int
bbox: list[float] epsg3857_bbox: list[Point]
points: list[tuple[float, float]] epsg3857_points: list[Point]

View File

@@ -58,8 +58,6 @@ async def shutdown():
await db.disconnect() await db.disconnect()
# /addwidget https://localhost:4443/static/#?widgetId=$matrix_widget_id&userId=$matrix_user_id
# /addwidget https://localhost:3000/widget?widgetId=$matrix_widget_id&userId=$matrix_user_id
STATIC_ROOT = "../frontend/" STATIC_ROOT = "../frontend/"
app.mount("/widget", StaticFiles(directory=STATIC_ROOT, html=True), name="widget") app.mount("/widget", StaticFiles(directory=STATIC_ROOT, html=True), name="widget")
@@ -94,22 +92,16 @@ async def get_line(line_id: str) -> LineSchema:
def _format_stop(stop: Stop) -> StopSchema: def _format_stop(stop: Stop) -> StopSchema:
# print(stop.__dict__)
return StopSchema( return StopSchema(
id=stop.id, id=stop.id,
name=stop.name, name=stop.name,
town=stop.town_name, town=stop.town_name,
# xepsg2154=stop.xepsg2154, epsg3857_x=stop.epsg3857_x,
# yepsg2154=stop.yepsg2154, epsg3857_y=stop.epsg3857_y,
lat=stop.latitude,
lon=stop.longitude,
lines=[line.id for line in stop.lines], lines=[line.id for line in stop.lines],
) )
# châtelet
@app.get("/stop/") @app.get("/stop/")
async def get_stop( async def get_stop(
name: str = "", limit: int = 10 name: str = "", limit: int = 10
@@ -142,8 +134,6 @@ async def get_stop(
id=stop_area.id, id=stop_area.id,
name=stop_area.name, name=stop_area.name,
town=stop_area.town_name, town=stop_area.town_name,
# xepsg2154=stop_area.xepsg2154,
# yepsg2154=stop_area.yepsg2154,
type=stop_area.type, type=stop_area.type,
lines=[line.id for line in stop_area.lines], lines=[line.id for line in stop_area.lines],
stops=formatted_stops, stops=formatted_stops,
@@ -187,8 +177,9 @@ async def get_next_passages(stop_id: str) -> NextPassagesSchema | None:
dst_names = call.DestinationDisplay dst_names = call.DestinationDisplay
dsts = [dst.value for dst in dst_names] if dst_names else [] dsts = [dst.value for dst in dst_names] if dst_names else []
arrivalPlatformName = (
print(f"{call.ArrivalPlatformName = }") call.ArrivalPlatformName.value if call.ArrivalPlatformName else None
)
next_passage = NextPassageSchema( next_passage = NextPassageSchema(
line=line_id, line=line_id,
@@ -197,9 +188,7 @@ async def get_next_passages(stop_id: str) -> NextPassagesSchema | None:
atStop=call.VehicleAtStop, atStop=call.VehicleAtStop,
aimedArrivalTs=optional_datetime_to_ts(call.AimedArrivalTime), aimedArrivalTs=optional_datetime_to_ts(call.AimedArrivalTime),
expectedArrivalTs=optional_datetime_to_ts(call.ExpectedArrivalTime), expectedArrivalTs=optional_datetime_to_ts(call.ExpectedArrivalTime),
arrivalPlatformName=call.ArrivalPlatformName.value arrivalPlatformName=arrivalPlatformName,
if call.ArrivalPlatformName
else None,
aimedDepartTs=optional_datetime_to_ts(call.AimedDepartureTime), aimedDepartTs=optional_datetime_to_ts(call.AimedDepartureTime),
expectedDepartTs=optional_datetime_to_ts(call.ExpectedDepartureTime), expectedDepartTs=optional_datetime_to_ts(call.ExpectedDepartureTime),
arrivalStatus=call.ArrivalStatus.value, arrivalStatus=call.ArrivalStatus.value,
@@ -250,7 +239,10 @@ async def get_stop_shape(stop_id: int) -> StopShapeSchema | None:
and (shape := await StopShape.get_by_id(connection_area.id)) is not None and (shape := await StopShape.get_by_id(connection_area.id)) is not None
): ):
return StopShapeSchema( return StopShapeSchema(
id=shape.id, type=shape.type, bbox=shape.bounding_box, points=shape.points id=shape.id,
type=shape.type,
epsg3857_bbox=shape.epsg3857_bbox,
epsg3857_points=shape.epsg3857_points,
) )
msg = f"No shape found for stop {stop_id}" msg = f"No shape found for stop {stop_id}"

View File

@@ -17,6 +17,7 @@ uvicorn = "^0.20.0"
asyncpg = "^0.27.0" asyncpg = "^0.27.0"
msgspec = "^0.12.0" msgspec = "^0.12.0"
pyshp = "^2.3.1" pyshp = "^2.3.1"
pyproj = "^3.5.0"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]