6 Commits

12 changed files with 219 additions and 178 deletions

View File

@@ -12,10 +12,9 @@ from typing import (
from aiofiles import open as async_open
from aiohttp import ClientSession
from msgspec import ValidationError
from msgspec.json import Decoder
from rich import print
from pyproj import Transformer
from shapefile import Reader as ShapeFileReader, ShapeRecord
from ..db import Database
@@ -64,6 +63,8 @@ class IdfmInterface:
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_stop_areas_decoder = Decoder(type=List[IdfmStopArea])
self._json_connection_areas_decoder = Decoder(type=List[IdfmConnectionArea])
@@ -89,19 +90,19 @@ class IdfmInterface:
(
StopShape,
self._request_stop_shapes,
IdfmInterface._format_idfm_stop_shapes,
self._format_idfm_stop_shapes,
),
(
ConnectionArea,
self._request_idfm_connection_areas,
IdfmInterface._format_idfm_connection_areas,
self._format_idfm_connection_areas,
),
(
StopArea,
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:
@@ -391,23 +392,26 @@ class IdfmInterface:
return ret
@staticmethod
def _format_idfm_stops(*stops: IdfmStop) -> Iterable[Stop]:
def _format_idfm_stops(self, *stops: IdfmStop) -> Iterable[Stop]:
for stop in stops:
fields = stop.fields
try:
created_ts = int(fields.arrcreated.timestamp()) # type: ignore
except AttributeError:
created_ts = None
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
fields.arrxepsg2154, fields.arryepsg2154
)
yield Stop(
id=int(fields.arrid),
name=fields.arrname,
latitude=fields.arrgeopoint.lat,
longitude=fields.arrgeopoint.lon,
epsg3857_x=epsg3857_point[0],
epsg3857_y=epsg3857_point[1],
town_name=fields.arrtown,
postal_region=fields.arrpostalregion,
xepsg2154=fields.arrxepsg2154,
yepsg2154=fields.arryepsg2154,
transport_mode=TransportMode(fields.arrtype.value),
version=fields.arrversion,
created_ts=created_ts,
@@ -419,53 +423,76 @@ class IdfmInterface:
record_ts=int(stop.record_timestamp.timestamp()),
)
@staticmethod
def _format_idfm_stop_areas(*stop_areas: IdfmStopArea) -> Iterable[StopArea]:
def _format_idfm_stop_areas(self, *stop_areas: IdfmStopArea) -> Iterable[StopArea]:
for stop_area in stop_areas:
fields = stop_area.fields
try:
created_ts = int(fields.zdacreated.timestamp()) # type: ignore
except AttributeError:
created_ts = None
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
fields.zdaxepsg2154, fields.zdayepsg2154
)
yield StopArea(
id=int(fields.zdaid),
name=fields.zdaname,
town_name=fields.zdatown,
postal_region=fields.zdapostalregion,
xepsg2154=fields.zdaxepsg2154,
yepsg2154=fields.zdayepsg2154,
epsg3857_x=epsg3857_point[0],
epsg3857_y=epsg3857_point[1],
type=StopAreaType(fields.zdatype.value),
version=fields.zdaversion,
created_ts=created_ts,
changed_ts=int(fields.zdachanged.timestamp()),
)
@staticmethod
def _format_idfm_connection_areas(
self,
*connection_areas: IdfmConnectionArea,
) -> Iterable[ConnectionArea]:
for connection_area in connection_areas:
epsg3857_point = self._epsg2154_epsg3857_transformer.transform(
connection_area.zdcxepsg2154, connection_area.zdcyepsg2154
)
yield ConnectionArea(
id=int(connection_area.zdcid),
name=connection_area.zdcname,
town_name=connection_area.zdctown,
postal_region=connection_area.zdcpostalregion,
xepsg2154=connection_area.zdcxepsg2154,
yepsg2154=connection_area.zdcyepsg2154,
epsg3857_x=epsg3857_point[0],
epsg3857_y=epsg3857_point[1],
transport_mode=StopAreaType(connection_area.zdctype.value),
version=connection_area.zdcversion,
created_ts=int(connection_area.zdccreated.timestamp()),
changed_ts=int(connection_area.zdcchanged.timestamp()),
)
@staticmethod
def _format_idfm_stop_shapes(*shape_records: ShapeRecord) -> Iterable[StopShape]:
def _format_idfm_stop_shapes(
self, *shape_records: ShapeRecord
) -> Iterable[StopShape]:
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(
id=shape_record.record[1],
type=shape_record.shape.shapeType,
bounding_box=list(shape_record.shape.bbox),
points=shape_record.shape.points,
epsg3857_bbox=epsg3857_bbox,
epsg3857_points=epsg3857_points,
)
async def render_line_picto(self, line: Line) -> tuple[None | str, None | str]:
@@ -508,7 +535,7 @@ class IdfmInterface:
print("---------------------------------------------------------------------")
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
params = {"MonitoringRef": f"STIF:StopPoint:Q:{stop_point_id}:"}
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)
town_name = mapped_column(String, nullable=False)
postal_region = mapped_column(String, nullable=False)
xepsg2154 = mapped_column(BigInteger, nullable=False)
yepsg2154 = mapped_column(BigInteger, nullable=False)
epsg3857_x = mapped_column(Float, nullable=False)
epsg3857_y = mapped_column(Float, nullable=False)
version = mapped_column(String, nullable=False)
created_ts = mapped_column(BigInteger)
@@ -111,8 +111,6 @@ class Stop(_Stop):
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)
accessibility = 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
type = mapped_column(Integer, nullable=False)
bounding_box = mapped_column(JSON)
points = mapped_column(JSON)
epsg3857_bbox = mapped_column(JSON)
epsg3857_points = mapped_column(JSON)
__tablename__ = "stop_shapes"
@@ -206,8 +204,8 @@ class ConnectionArea(Base):
name = mapped_column(String, nullable=False)
town_name = mapped_column(String, nullable=False)
postal_region = mapped_column(String, nullable=False)
xepsg2154 = mapped_column(BigInteger, nullable=False)
yepsg2154 = mapped_column(BigInteger, nullable=False)
epsg3857_x = mapped_column(Float, nullable=False)
epsg3857_y = mapped_column(Float, nullable=False)
transport_mode = mapped_column(Enum(StopAreaType), nullable=False)
version = mapped_column(String, nullable=False)

View File

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

View File

@@ -58,8 +58,6 @@ async def shutdown():
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/"
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:
# print(stop.__dict__)
return StopSchema(
id=stop.id,
name=stop.name,
town=stop.town_name,
# xepsg2154=stop.xepsg2154,
# yepsg2154=stop.yepsg2154,
lat=stop.latitude,
lon=stop.longitude,
epsg3857_x=stop.epsg3857_x,
epsg3857_y=stop.epsg3857_y,
lines=[line.id for line in stop.lines],
)
# châtelet
@app.get("/stop/")
async def get_stop(
name: str = "", limit: int = 10
@@ -142,8 +134,6 @@ async def get_stop(
id=stop_area.id,
name=stop_area.name,
town=stop_area.town_name,
# xepsg2154=stop_area.xepsg2154,
# yepsg2154=stop_area.yepsg2154,
type=stop_area.type,
lines=[line.id for line in stop_area.lines],
stops=formatted_stops,
@@ -187,8 +177,9 @@ async def get_next_passages(stop_id: str) -> NextPassagesSchema | None:
dst_names = call.DestinationDisplay
dsts = [dst.value for dst in dst_names] if dst_names else []
print(f"{call.ArrivalPlatformName = }")
arrivalPlatformName = (
call.ArrivalPlatformName.value if call.ArrivalPlatformName else None
)
next_passage = NextPassageSchema(
line=line_id,
@@ -197,9 +188,7 @@ async def get_next_passages(stop_id: str) -> NextPassagesSchema | None:
atStop=call.VehicleAtStop,
aimedArrivalTs=optional_datetime_to_ts(call.AimedArrivalTime),
expectedArrivalTs=optional_datetime_to_ts(call.ExpectedArrivalTime),
arrivalPlatformName=call.ArrivalPlatformName.value
if call.ArrivalPlatformName
else None,
arrivalPlatformName=arrivalPlatformName,
aimedDepartTs=optional_datetime_to_ts(call.AimedDepartureTime),
expectedDepartTs=optional_datetime_to_ts(call.ExpectedDepartureTime),
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
):
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}"

View File

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

View File

@@ -1,38 +1,37 @@
{
"name": "vite-template-solid",
"version": "0.0.0",
"engine": "19.3.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite --debug",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"@types/leaflet": "^1.9.0",
"@types/proj4": "^2.5.2",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"eslint": "^8.32.0",
"eslint-plugin-solid": "^0.9.3",
"sass": "^1.62.0",
"typescript": "^4.9.4",
"typescript-eslint-language-service": "^5.0.0",
"vite": "^4.0.3",
"vite-plugin-solid": "^2.5.0"
},
"dependencies": {
"@hope-ui/solid": "^0.6.7",
"@motionone/solid": "^10.15.5",
"@solid-primitives/date": "^2.0.5",
"@solid-primitives/scroll": "^2.0.10",
"@stitches/core": "^1.2.8",
"date-fns": "^2.29.3",
"matrix-widget-api": "^1.1.1",
"ol": "^7.3.0",
"proj4": "^2.9.0",
"solid-js": "^1.6.6",
"solid-transition-group": "^0.0.10"
}
"name": "vite-template-solid",
"version": "0.0.0",
"engine": "19.3.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite --debug",
"build": "vite build",
"serve": "vite preview",
"bundle-visualizer": "npx vite-bundle-visualizer"
},
"license": "MIT",
"devDependencies": {
"@types/leaflet": "^1.9.0",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"eslint": "^8.32.0",
"eslint-plugin-solid": "^0.9.3",
"sass": "^1.62.0",
"typescript": "^4.9.4",
"typescript-eslint-language-service": "^5.0.0",
"vite": "^4.0.3",
"vite-bundle-visualizer": "^0.6.0",
"vite-plugin-solid": "^2.5.0"
},
"dependencies": {
"@motionone/solid": "^10.15.5",
"@solid-primitives/date": "^2.0.5",
"@solid-primitives/scroll": "^2.0.10",
"@stitches/core": "^1.2.8",
"date-fns": "^2.29.3",
"matrix-widget-api": "^1.1.1",
"ol": "^7.3.0",
"solid-js": "^1.6.6",
"solid-transition-group": "^0.0.10"
}
}

View File

@@ -2,6 +2,10 @@
--idfm-black: #2c2e35;
--idfm-white: #ffffff;
--neutral-color: #d7dbdf;
--border-radius: calc(15/1920*100%);
height: inherit;
width: inherit;

View File

@@ -1,19 +1,16 @@
import { createIcon } from "@hope-ui/solid";
import { VoidComponent } from "solid-js";
// From https://github.com/hope-ui/hope-ui/blob/main/apps/docs/src/icons/IconHamburgerMenu.tsx
export const IconHamburgerMenu = createIcon({
viewBox: "0 0 15 15",
path: () => (
<path
d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386
13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5
8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761
13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
// Inspired by https://github.com/hope-ui/hope-ui/blob/main/apps/docs/src/icons/IconHamburgerMenu.tsx
export const IconHamburgerMenu: VoidComponent<{}> = () => {
return (
<svg class="iconHamburgerMenu" viewBox="0 0 15 15">
<path d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386
13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5
8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761
13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z"
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
/>
),
});
</svg>);
}

View File

@@ -21,18 +21,24 @@
}
.menu {
aspect-ratio: 0.75;
height: $header-element-height;
aspect-ratio: 1;
margin-right: calc(30/1920*100%);
margin-left: auto;
border: $component-border;
border-radius: $component-border-radius;
button {
height: 100%;
aspect-ratio: 1;
background-color: var(--idfm-black);
border: $component-border;
border-radius: $component-border-radius;
border: 0;
color: var(--idfm-white);
background-color: transparent;
.iconHamburgerMenu {
width: 75%;
}
}
}

View File

@@ -4,17 +4,41 @@
.stopSearchMenu {
@extend %widget;
.inputGroup {
.stopNameInput {
width: 50%;
// height: 5%;
height: 60%;
display: flex;
flex-flow: row;
border: solid var(--neutral-color) calc(0.01*1vh);
border-radius: var(--border-radius);
background-color: transparent;
.leftAddon {
width: 17%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--idfm-white);
}
// TODO: Setup hop-ui to avoid to have to overrride rules.
input {
width: 83%;
padding-left: 3%;
padding-right: 3%;
color: var(--idfm-white);
font-family: IDFVoyageur-regular;
background-color: transparent;
}
}
.title {
@extend %title;

View File

@@ -2,8 +2,6 @@ import { createContext, createEffect, createResource, createSignal, For, JSX, on
import { createStore } from "solid-js/store";
import { createScrollPosition } from "@solid-primitives/scroll";
import { Input, InputLeftAddon, InputGroup } from "@hope-ui/solid";
import OlFeature from 'ol/Feature';
import OlMap from 'ol/Map';
import OlView from 'ol/View';
@@ -14,36 +12,26 @@ import OlOverlay from 'ol/Overlay';
import OlPoint from 'ol/geom/Point';
import OlPolygon from 'ol/geom/Polygon';
import OlVectorSource from 'ol/source/Vector';
import { fromLonLat, toLonLat } from 'ol/proj';
import { Tile as OlTileLayer, Vector as OlVectorLayer } from 'ol/layer';
import { Circle, Fill, Stroke, Style } from 'ol/style';
import { easeOut } from 'ol/easing';
import { getVectorContext } from 'ol/render';
import { unByKey } from 'ol/Observable';
import { register } from 'ol/proj/proj4';
import proj4 from 'proj4';
import { Stop, StopShape } from './types';
import { PositionedPanel, renderLineTransportMode, renderLinePicto, ScrollingText, TransportModeWeights } from './utils';
import { AppContextContext, AppContextStore } from "./appContext";
import { BusinessDataContext, BusinessDataStore } from "./businessData";
import "./stopsSearchMenu.scss";
proj4.defs("EPSG:2154", "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
register(proj4);
type ByStopIdMapFeatures = Record<number, OlFeature>;
interface SearchStore {
getSearchText: () => string;
setSearchText: (text: string, businessDataStore: BusinessDataStore) => Promise<void>;
isSearchInProgress: () => boolean;
getFoundStops: () => Stop[];
setFoundStops: (stops: Stop[]) => void;
@@ -65,11 +53,14 @@ interface SearchStore {
const SearchContext = createContext<SearchStore>();
function SearchProvider(props: { children: JSX.Element }) {
const searchTextDelayMs = 1500;
type Store = {
searchText: string;
searchInProgress: boolean;
searchPromise: Promise<void> | undefined;
foundStops: Stop[];
displayedPanelId: number;
panels: PositionedPanel[];
@@ -79,7 +70,7 @@ function SearchProvider(props: { children: JSX.Element }) {
const [store, setStore] = createStore<Store>({
searchText: "",
searchInProgress: false,
searchPromise: undefined,
foundStops: [],
displayedPanelId: 0,
panels: [],
@@ -91,22 +82,30 @@ function SearchProvider(props: { children: JSX.Element }) {
return store.searchText;
}
const setSearchText = async (text: string, businessDataStore: BusinessDataStore): Promise<void> => {
setStore('searchInProgress', true);
setStore('searchText', text);
const { searchStopByName } = businessDataStore;
console.log("store.searchText=", store.searchText);
const stopsById = await searchStopByName(store.searchText);
console.log("stopsById=", stopsById);
setFoundStops(Object.values(stopsById));
setStore('searchInProgress', false);
const debounce = async (fn: (...args: any[]) => Promise<void>, delayMs: number) => {
let timerId: number;
return new Promise((...args) => {
clearTimeout(timerId);
timerId = setTimeout(fn, delayMs, ...args);
});
}
const isSearchInProgress = (): boolean => {
return store.searchInProgress;
const setSearchText = async (text: string, businessDataStore: BusinessDataStore): Promise<void> => {
setStore('searchText', text);
if (store.searchPromise === undefined) {
const { searchStopByName } = businessDataStore;
const promise: Promise<void> = debounce(async (onSuccess: () => void) => {
console.log(`Fetching data for "${store.searchText}" stop name`);
const stopsById = await searchStopByName(store.searchText);
console.log("stopsById=", stopsById);
setFoundStops(Object.values(stopsById));
onSuccess();
}, searchTextDelayMs).then(() => {
setStore('searchPromise', undefined);
});
setStore('searchPromise', promise);
}
}
const getFoundStops = (): Stop[] => {
@@ -158,10 +157,9 @@ function SearchProvider(props: { children: JSX.Element }) {
setStore('mapFeatures', stopId, feature);
};
return (
<SearchContext.Provider value={{
getSearchText, setSearchText, isSearchInProgress,
getSearchText, setSearchText,
getFoundStops, setFoundStops,
getDisplayedPanelId, setDisplayedPanelId,
getPanels, setPanels,
@@ -173,6 +171,13 @@ function SearchProvider(props: { children: JSX.Element }) {
);
}
const StopNameInput: VoidComponent<{ onInput: JSX.EventHandler<HTMLInputElement, InputEvent>, leftAddon: string, placeholder: string }> = (props) => {
return (
<div class="stopNameInput">
<div class="leftAddon">{props.leftAddon}</div>
<input type="text" oninput={props.onInput} placeholder={props.placeholder} />
</div>);
};
const Header: VoidComponent<{ title: string, minCharsNb: number }> = (props) => {
@@ -182,13 +187,11 @@ const Header: VoidComponent<{ title: string, minCharsNb: number }> = (props) =>
if (businessDataStore === undefined || searchStore === undefined)
return <div />;
const { isSearchInProgress, setSearchText } = searchStore;
const { setSearchText } = searchStore;
const onStopNameInput: JSX.EventHandler<HTMLInputElement, InputEvent> = async (event): Promise<void> => {
/* TODO: Add a tempo before fetching stop for giving time to user to finish his request */
const stopName = event.currentTarget.value;
if (stopName.length >= props.minCharsNb) {
console.log(`Fetching data for "${stopName}" stop name`);
await setSearchText(stopName, businessDataStore);
}
}
@@ -202,12 +205,7 @@ const Header: VoidComponent<{ title: string, minCharsNb: number }> = (props) =>
</text>
</svg>
</div>
<div class="inputGroup">
<InputGroup >
<InputLeftAddon>🚉 🚏</InputLeftAddon>
<Input onInput={onStopNameInput} readOnly={isSearchInProgress()} placeholder="Stop name..." />
</InputGroup>
</div>
<StopNameInput onInput={onStopNameInput} leftAddon="🚉 🚏" placeholder="Stop name..." />
</div >
);
};
@@ -352,7 +350,6 @@ const StopsPanels: ParentComponent<{ maxStopsPerPanel: number }> = (props) => {
}
const { getDisplayedPanelId, getFoundStops, getPanels, setDisplayedPanelId, setPanels } = searchStore;
let stopsPanelsRef: HTMLDivElement | undefined = undefined
const stopsPanelsScroll = createScrollPosition(() => stopsPanelsRef);
const yStopsPanelsScroll = () => stopsPanelsScroll.y;
@@ -498,15 +495,13 @@ const MapStop: VoidComponent<{ stop: Stop, selected: Stop | undefined }> = (prop
let feature = undefined;
if (props.stop.lat !== undefined && props.stop.lon !== undefined) {
if (props.stop.epsg3857_x !== undefined && props.stop.epsg3857_y !== undefined) {
const selectStopStyle = () => {
return (props.selected?.id === props.stop.id ? selectedStopStyle : stopStyle);
}
feature = new OlFeature({
geometry: new OlPoint(fromLonLat([props.stop.lon, props.stop.lat])),
geometry: new OlPoint([props.stop.epsg3857_x, props.stop.epsg3857_y]),
});
feature.setStyle(selectStopStyle);
}
@@ -514,11 +509,10 @@ const MapStop: VoidComponent<{ stop: Stop, selected: Stop | undefined }> = (prop
let geometry = undefined;
const areaShape = shape();
if (areaShape !== undefined) {
const transformed = areaShape.points.map(point => fromLonLat(toLonLat(point, 'EPSG:2154')));
geometry = new OlPolygon([transformed.slice(0, -1)]);
geometry = new OlPolygon([areaShape.epsg3857_points.slice(0, -1)]);
}
else {
geometry = new OlPoint(fromLonLat([props.stop.lon, props.stop.lat]));
geometry = new OlPoint([props.stop.epsg3857_x, props.stop.epsg3857_y]);
}
feature = new OlFeature({ geometry: geometry });
feature.setStyle(stopAreaStyle);
@@ -604,11 +598,11 @@ const Map: ParentComponent<{}> = () => {
const stopId: number = feature.getId();
const stop = getStop(stopId);
// TODO: Handle StopArea (use center given by the backend)
if (stop?.lat !== undefined && stop?.lon !== undefined) {
if (stop?.epsg3857_x !== undefined && stop?.epsg3857_y !== undefined) {
setSelectedMapStop(stop);
map?.getView().animate(
{
center: fromLonLat([stop.lon, stop.lat]),
center: [stop.epsg3857_x, stop.epsg3857_y],
duration: 1000
},
// Display the popup once the animation finished

View File

@@ -42,17 +42,17 @@ export class Stop {
id: number;
name: string;
town: string;
lat: number;
lon: number;
epsg3857_x: number;
epsg3857_y: number;
stops: Stop[];
lines: string[];
constructor(id: number, name: string, town: string, lat: number, lon: number, stops: Stop[], lines: string[]) {
constructor(id: number, name: string, town: string, epsg3857_x: number, epsg3857_y: number, stops: Stop[], lines: string[]) {
this.id = id;
this.name = name;
this.town = town;
this.lat = lat;
this.lon = lon;
this.epsg3857_x = epsg3857_x;
this.epsg3857_y = epsg3857_y;
this.stops = stops;
this.lines = lines;
for (const stop of this.stops) {
@@ -68,14 +68,14 @@ export type Points = [number, number][];
export class StopShape {
stop_id: number;
type_: number;
bounding_box: number[];
points: Points;
epsg3857_bbox: number[];
epsg3857_points: Points;
constructor(stop_id: number, type_: number, bounding_box: number[], points: Points) {
constructor(stop_id: number, type_: number, epsg3857_bbox: number[], epsg3857_points: Points) {
this.stop_id = stop_id;
this.type_ = type_;
this.bounding_box = bounding_box;
this.points = points;
this.epsg3857_bbox = epsg3857_bbox;
this.epsg3857_points = epsg3857_points;
}
};