💥 All location points provided by backend are in EPSG:3857
This commit is contained in:
@@ -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:
|
||||||
|
@@ -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)
|
||||||
|
@@ -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]
|
||||||
|
@@ -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}"
|
||||||
|
@@ -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"]
|
||||||
|
Reference in New Issue
Block a user