@geops/rvf-mobility-web-component 0.1.83 → 0.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/index.js +206 -187
  3. package/package.json +2 -2
  4. package/src/Departure/Departure.tsx +6 -3
  5. package/src/FeatureDetails/FeatureDetails.tsx +4 -4
  6. package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +22 -0
  7. package/src/LayerTreeMenu/LayerTreeMenu.tsx +12 -4
  8. package/src/LayoutState/LayoutState.tsx +144 -63
  9. package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +2 -2
  10. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +2 -40
  11. package/src/MapLayout/MapLayout.tsx +2 -7
  12. package/src/MobilityMap/MobilityMap.tsx +1 -6
  13. package/src/MobilityMap/MobilityMapAttributes.ts +28 -7
  14. package/src/NotificationDetails/NotificationDetails.tsx +21 -22
  15. package/src/NotificationsLayer/NotificationsLayer.tsx +47 -3
  16. package/src/OverlayDetails/OverlayDetails.tsx +5 -0
  17. package/src/RealtimeLayer/RealtimeLayer.tsx +44 -50
  18. package/src/RouteIcon/RouteIcon.tsx +25 -14
  19. package/src/RouteSchedule/RouteSchedule.tsx +14 -11
  20. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +6 -3
  21. package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +8 -2
  22. package/src/RouteStop/RouteStop.tsx +14 -29
  23. package/src/RouteStopPlatform/RouteStopPlatform.tsx +1 -3
  24. package/src/RouteStopProgress/RouteStopProgress.tsx +11 -18
  25. package/src/RouteStopStation/RouteStopStation.tsx +3 -3
  26. package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +16 -3
  27. package/src/RvfSearch/RvfSearch.tsx +5 -4
  28. package/src/Search/Search.tsx +41 -20
  29. package/src/Search/SearchBase.tsx +169 -0
  30. package/src/Search/SearchHeadless.tsx +79 -0
  31. package/src/Search/index.tsx +2 -0
  32. package/src/SearchLinesResult/SearchLinesResult.tsx +43 -0
  33. package/src/SearchLinesResult/index.tsx +1 -0
  34. package/src/SearchLinesResults/SearchLinesResults.tsx +106 -0
  35. package/src/SearchLinesResults/index.tsx +1 -0
  36. package/src/SearchLnpStopsResult/SearchLnpStopsResult.tsx +42 -0
  37. package/src/SearchLnpStopsResult/index.tsx +1 -0
  38. package/src/SearchLnpStopsResults/SearchLnpStopsResults.tsx +106 -0
  39. package/src/SearchLnpStopsResults/index.tsx +1 -0
  40. package/src/SearchResult/SearchResult.tsx +25 -0
  41. package/src/SearchResult/index.tsx +1 -0
  42. package/src/SearchResults/SearchResults.tsx +81 -0
  43. package/src/SearchResults/index.tsx +1 -0
  44. package/src/SearchResultsHeader/SearchResultsHeader.tsx +36 -0
  45. package/src/SearchResultsHeader/index.tsx +1 -0
  46. package/src/SearchStopsResult/SearchStopsResult.tsx +43 -0
  47. package/src/SearchStopsResult/index.tsx +1 -0
  48. package/src/SearchStopsResults/SearchStopsResults.tsx +101 -0
  49. package/src/SearchStopsResults/index.tsx +1 -0
  50. package/src/SearchTrainsResult/SearchTrainsResult.tsx +42 -0
  51. package/src/SearchTrainsResult/index.tsx +1 -0
  52. package/src/SearchTrainsResults/SearchTrainsResults.tsx +109 -0
  53. package/src/SearchTrainsResults/index.tsx +1 -0
  54. package/src/SingleClickListener/SingleClickListener.tsx +38 -2
  55. package/src/Station/Station.tsx +23 -48
  56. package/src/StationHeader/StationHeader.tsx +3 -3
  57. package/src/StationsLayer/StationsLayer.tsx +9 -1
  58. package/src/StopsSearch/StopsSearch.tsx +2 -2
  59. package/src/ui/InputSearch/InputSearch.tsx +105 -0
  60. package/src/ui/InputSearch/index.tsx +1 -0
  61. package/src/utils/centerOnVehicle.ts +9 -2
  62. package/src/utils/constants.ts +8 -1
  63. package/src/utils/fullTrajectoryStyle.ts +4 -7
  64. package/src/utils/getBgColor.ts +4 -2
  65. package/src/utils/getDelayColorForVehicle.test.ts +21 -11
  66. package/src/utils/getDelayColorForVehicle.ts +7 -5
  67. package/src/utils/getDelayTextForVehicle.test.ts +12 -12
  68. package/src/utils/getDelayTextForVehicle.ts +4 -0
  69. package/src/utils/getMainColorForVehicle.ts +11 -4
  70. package/src/utils/getRadius.ts +9 -3
  71. package/src/utils/getTextColor.ts +1 -1
  72. package/src/utils/getTextColorForVehicle.ts +31 -0
  73. package/src/utils/getTextFontForVehicle.test.ts +1 -1
  74. package/src/utils/getTextFontForVehicle.tsx +11 -3
  75. package/src/utils/getTextForVehicle.ts +7 -1
  76. package/src/utils/hooks/useFit.tsx +69 -0
  77. package/src/utils/hooks/useFitOnFeatures.tsx +77 -0
  78. package/src/utils/hooks/useLayersConfig.tsx +3 -0
  79. package/src/utils/hooks/useLnp.tsx +39 -5
  80. package/src/utils/hooks/useMapContext.tsx +2 -5
  81. package/src/utils/hooks/useRealtimeDepartures.tsx +45 -0
  82. package/src/utils/hooks/useRealtimeRenderedTrajectory.tsx +42 -0
  83. package/src/utils/hooks/useRealtimeStation.tsx +39 -0
  84. package/src/utils/hooks/useRealtimeStopSequences.tsx +43 -0
  85. package/src/utils/hooks/useRealtimeTrainsByRouteIdentifier.tsx +71 -0
  86. package/src/utils/hooks/useRouteStop.tsx +7 -1
  87. package/src/utils/hooks/useSearchLines.tsx +34 -0
  88. package/src/utils/hooks/useSearchLnpStops.tsx +38 -0
  89. package/src/utils/hooks/useSearchStops.tsx +85 -0
  90. package/src/utils/hooks/useSearchTrains.tsx +83 -0
  91. package/src/utils/realtimeRVFStyle.ts +38 -30
  92. package/src/utils/translations.ts +17 -0
  93. package/tash +58 -0
  94. package/src/utils/centerOnStation.ts +0 -18
  95. package/src/utils/getDelayFontForVehicle.test.ts +0 -7
  96. package/src/utils/getDelayFontForVehicle.tsx +0 -8
  97. package/src/utils/hooks/useStation.tsx +0 -22
@@ -35,8 +35,8 @@ export type MobilityMapAttributeName =
35
35
  | "layers"
36
36
  | "layersconfig"
37
37
  | "layertree"
38
+ | "lineid"
38
39
  | "lnp"
39
- | "lnpid"
40
40
  | "mainlink"
41
41
  | "mainlinktitle"
42
42
  | "mapset"
@@ -52,6 +52,7 @@ export type MobilityMapAttributeName =
52
52
  | "mots"
53
53
  | "notification"
54
54
  | "notificationat"
55
+ | "notificationid"
55
56
  | "notificationtenant"
56
57
  | "notificationurl"
57
58
  | "permalink"
@@ -60,14 +61,17 @@ export type MobilityMapAttributeName =
60
61
  | "queryablelayers"
61
62
  | "realtime"
62
63
  | "realtimebboxparameters"
64
+ | "realtimeresturl"
63
65
  | "realtimetenant"
64
66
  | "realtimeurl"
65
67
  | "runs"
66
68
  | "search"
67
69
  | "share"
70
+ | "stationid"
68
71
  | "stopsurl"
69
72
  | "tenant"
70
73
  | "toolbar"
74
+ | "trainid"
71
75
  | "zoom";
72
76
 
73
77
  export type MobilityMapAttributes = Record<
@@ -165,16 +169,16 @@ where:
165
169
  public: true,
166
170
  type: "boolean",
167
171
  },
172
+ lineid: {
173
+ description: `An id or a short/long name of a line to highlight. <br/>Ex: S1`,
174
+ public: false,
175
+ },
168
176
  lnp: {
169
177
  defaultValue: "true",
170
178
  description: `Add the linesnetworkplans layer to the map. This layer will display lines network plans on the map.`,
171
179
  public: false,
172
180
  type: "boolean",
173
181
  },
174
- lnpid: {
175
- description: `An id or a short/long name of a line to highlight. <br/>Ex: S1`,
176
- public: false,
177
- },
178
182
  mainlink: {
179
183
  description:
180
184
  "A link displayed on bottom left of the map. The link can be a template, for example you can use {{x}} {{y}} {{z}} to insert the current position of the map in the url.<br/>Ex: http://mywebsite/mypage#map/{{x}}/{{y}}/{{z}}.",
@@ -192,7 +196,7 @@ where:
192
196
  },
193
197
  mapsetplanid: {
194
198
  description:
195
- "The id of the mapset plan to display. Mostly for debugging purposes.",
199
+ "An id of the mapset plan to display. Mostly for debugging purposes.",
196
200
  public: false,
197
201
  },
198
202
  mapsettags: {
@@ -206,7 +210,7 @@ where:
206
210
  },
207
211
  mapsettimestamp: {
208
212
  description: `The ${geopsMapsetApiLink} timestamp used to load valid standard plan. If not defined it will use the current time.`,
209
- public: false,
213
+ public: true,
210
214
  },
211
215
  mapseturl: {
212
216
  defaultValue: "https://editor.mapset.io/api/v1/",
@@ -250,6 +254,10 @@ where:
250
254
  "An ISO date string used to display active notification at this date in the notification layer. If not defined the current date will be used.<br/>Ex: 2025-08-01T00:00:00Z .",
251
255
  public: false,
252
256
  },
257
+ notificationid: {
258
+ description: `An id of a notification to show details of.`,
259
+ public: false,
260
+ },
253
261
  notificationtenant: {
254
262
  defaultValue: "rvf",
255
263
  description: `The ${geopsMocoApiLink} tenant to get the notification from.`,
@@ -298,6 +306,11 @@ where:
298
306
  "A space separated list of parameters to add to the realtime BBOX request to define custom behavior.<br/>Ex: graph=XXX line_tags=XXX.",
299
307
  public: false,
300
308
  },
309
+ realtimeresturl: {
310
+ defaultValue: "https://api.geops.io/tracker-http/v1/",
311
+ description: `The ${geopsRealtimeApiLink} REST API url to use for fetching infos about realtime data.`,
312
+ public: false,
313
+ },
301
314
  realtimetenant: {
302
315
  description: `The ${geopsRealtimeApiLink} tenant to get the realtime data from.`,
303
316
  public: false,
@@ -326,6 +339,10 @@ where:
326
339
  public: true,
327
340
  type: "boolean",
328
341
  },
342
+ stationid: {
343
+ description: `An id or a short/long name of a station to show details of.`,
344
+ public: false,
345
+ },
329
346
  stopsurl: {
330
347
  defaultValue: "https://api.geops.io/stops/v1/",
331
348
  description: `The ${geopsStopsApiLink} to use.`,
@@ -341,6 +358,10 @@ where:
341
358
  public: true,
342
359
  type: "boolean",
343
360
  },
361
+ trainid: {
362
+ description: `An id of a route to highlight on the map and to show details of.`,
363
+ public: false,
364
+ },
344
365
  zoom: {
345
366
  description: "The zoom level of the map.",
346
367
  public: true,
@@ -9,6 +9,7 @@ import Warning from "../icons/Warning";
9
9
  import ShadowOverflow from "../ShadowOverflow";
10
10
  import Link from "../ui/Link";
11
11
  import useI18n from "../utils/hooks/useI18n";
12
+ import useMapContext from "../utils/hooks/useMapContext";
12
13
  import useMocoSituation from "../utils/hooks/useMocoSituation";
13
14
 
14
15
  import type {
@@ -36,18 +37,6 @@ const toShortDate = (date: Date, showTime, showYear?: boolean) => {
36
37
  .replace(/\.$/, "");
37
38
  };
38
39
 
39
- // const getLine = (name: string, lines: NotificationLine[]): NotificationLine => {
40
- // if (lines?.length) {
41
- // const line = lines.find((linee) => {
42
- // return linee.name === name;
43
- // });
44
- // if (line) {
45
- // return line;
46
- // }
47
- // }
48
- // return { mot: "bus", name } as NotificationLine;
49
- // };
50
-
51
40
  export type NotificationLine = {
52
41
  mot?: RealtimeMot;
53
42
  operator_name?: string;
@@ -57,15 +46,14 @@ export type NotificationLine = {
57
46
 
58
47
  function NotificationDetails({
59
48
  className,
60
- feature,
61
49
  ...props
62
50
  }: {
63
51
  className?: string;
64
52
  feature: Feature;
65
53
  }) {
66
54
  const { locale, t } = useI18n();
67
- const { situationId } = feature.getProperties();
68
- const situationParsed = useMocoSituation(situationId);
55
+ const { notificationId } = useMapContext();
56
+ const situationParsed = useMocoSituation(notificationId);
69
57
 
70
58
  // moco export v2
71
59
  let textualContentMultilingual: Partial<MultilingualTextualContentType> = {};
@@ -81,6 +69,17 @@ function NotificationDetails({
81
69
  // Find the current publication(s) at the current date
82
70
  publicationsToDisplay =
83
71
  publicationsArr?.filter(({ publicationWindows }) => {
72
+ // In some cases publicationWindows can be undefined here but defined at the
73
+ // root of the object so we apply the root publicationWindows to all publications with empty one
74
+ if (
75
+ !publicationWindows?.length &&
76
+ situationParsed?.publicationWindows?.length
77
+ ) {
78
+ // @ts-expect-error we should not set this value directly
79
+ // eslint-disable-next-line no-param-reassign
80
+ publicationWindows = situationParsed.publicationWindows;
81
+ }
82
+
84
83
  return publicationWindows.find(({ endTime, startTime }) => {
85
84
  const now = new Date();
86
85
  const startT = new Date(startTime);
@@ -209,13 +208,13 @@ function NotificationDetails({
209
208
  },
210
209
  )}
211
210
  <div className={"my-4 flex flex-col gap-4"}>
212
- <div
213
- dangerouslySetInnerHTML={{
214
- __html:
215
- textualContent?.description ||
216
- t("no_details_available"),
217
- }}
218
- />
211
+ {textualContent?.description && (
212
+ <div
213
+ dangerouslySetInnerHTML={{
214
+ __html: textualContent?.description,
215
+ }}
216
+ />
217
+ )}
219
218
  {!!textualContentMultilingual?.images?.length && (
220
219
  <div className="flex flex-wrap gap-2">
221
220
  {textualContentMultilingual.images.map(
@@ -1,6 +1,7 @@
1
1
  import { MocoLayer, type MocoLayerOptions } from "mobility-toolbox-js/ol";
2
+ import { unByKey } from "ol/Observable";
2
3
  import { memo } from "preact/compat";
3
- import { useEffect, useMemo } from "preact/hooks";
4
+ import { useEffect, useMemo, useState } from "preact/hooks";
4
5
 
5
6
  import { LAYER_NAME_NOTIFICATIONS } from "../utils/constants";
6
7
  import useMapContext from "../utils/hooks/useMapContext";
@@ -9,6 +10,7 @@ function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
9
10
  const {
10
11
  apikey,
11
12
  baseLayer,
13
+ linesNetworkPlanLayer,
12
14
  map,
13
15
  notificationat,
14
16
  notificationtenant,
@@ -16,6 +18,9 @@ function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
16
18
  previewNotifications,
17
19
  setNotificationsLayer,
18
20
  } = useMapContext();
21
+ const [isLnpVisible, setIsLnpVisible] = useState(
22
+ !!linesNetworkPlanLayer?.getVisible(),
23
+ );
19
24
 
20
25
  const layer = useMemo(() => {
21
26
  if (!baseLayer) {
@@ -42,12 +47,12 @@ function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
42
47
  }
43
48
  return mocoLayer;
44
49
  }, [
45
- apikey,
46
50
  baseLayer,
51
+ apikey,
47
52
  notificationat,
53
+ previewNotifications,
48
54
  notificationtenant,
49
55
  notificationurl,
50
- previewNotifications,
51
56
  props,
52
57
  ]);
53
58
 
@@ -66,6 +71,45 @@ function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
66
71
  };
67
72
  }, [map, layer]);
68
73
 
74
+ // Watch lnp visibility
75
+ useEffect(() => {
76
+ const key = linesNetworkPlanLayer?.on("change:visible", () => {
77
+ const visible = linesNetworkPlanLayer.getVisible();
78
+ setIsLnpVisible(visible);
79
+ });
80
+ setIsLnpVisible(linesNetworkPlanLayer?.getVisible() || false);
81
+ return () => {
82
+ unByKey(key);
83
+ };
84
+ }, [linesNetworkPlanLayer]);
85
+
86
+ // Change the layersFilter based on lnp visibility
87
+ // Moco layers must de hidden by default otherwise, on load, everything is displayed
88
+ useEffect(() => {
89
+ if (!layer) {
90
+ return;
91
+ }
92
+ const visible = layer.getVisible();
93
+ if (visible) {
94
+ layer.setVisible(false);
95
+ }
96
+
97
+ if (isLnpVisible) {
98
+ layer.layersFilter = (layerSpec) => {
99
+ return layerSpec.metadata?.["general.filter"] === "moco.lnp";
100
+ };
101
+ } else {
102
+ layer.layersFilter = (layerSpec) => {
103
+ return layerSpec.metadata?.["general.filter"] === "moco";
104
+ };
105
+ }
106
+
107
+ layer.setVisible(visible);
108
+ return () => {
109
+ layer.applyLayoutVisibility();
110
+ };
111
+ }, [isLnpVisible, layer, linesNetworkPlanLayer]);
112
+
69
113
  return null;
70
114
  }
71
115
 
@@ -39,15 +39,19 @@ function OverlayDetails() {
39
39
  if (featuresInfo?.layer) {
40
40
  return featuresInfo.layer;
41
41
  }
42
+
42
43
  if (trainId) {
43
44
  return realtimeLayer;
44
45
  }
46
+
45
47
  if (stationId) {
46
48
  return stationsLayer;
47
49
  }
50
+
48
51
  if (linesIds) {
49
52
  return linesNetworkPlanLayer;
50
53
  }
54
+
51
55
  if (notificationId) {
52
56
  return notificationsLayer;
53
57
  }
@@ -63,6 +67,7 @@ function OverlayDetails() {
63
67
  linesNetworkPlanLayer,
64
68
  notificationsLayer,
65
69
  ]);
70
+
66
71
  return (
67
72
  <>
68
73
  <OverlayDetailsHeader
@@ -2,15 +2,18 @@ import {
2
2
  getGraphByZoom,
3
3
  RealtimeLayer as MtbRealtimeLayer,
4
4
  } from "mobility-toolbox-js/ol";
5
+ import GeoJSON from "ol/format/GeoJSON";
5
6
  import { unByKey } from "ol/Observable";
7
+ import { Vector } from "ol/source";
6
8
  import { memo } from "preact/compat";
7
9
  import { useEffect, useMemo, useState } from "preact/hooks";
8
10
 
9
11
  import centerOnVehicle from "../utils/centerOnVehicle";
10
12
  import { LAYER_NAME_REALTIME } from "../utils/constants";
11
13
  import getDelayColorForVehicle from "../utils/getDelayColorForVehicle";
12
- import getDelayFontForVehicle from "../utils/getDelayFontForVehicle";
13
14
  import getDelayTextForVehicle from "../utils/getDelayTextForVehicle";
15
+ import getMainColorForVehicle from "../utils/getMainColorForVehicle";
16
+ import getTextColorForVehicle from "../utils/getTextColorForVehicle";
14
17
  import getTextFontForVehicle from "../utils/getTextFontForVehicle";
15
18
  import getTextForVehicle from "../utils/getTextForVehicle";
16
19
  import useMapContext from "../utils/hooks/useMapContext";
@@ -18,7 +21,6 @@ import useMapContext from "../utils/hooks/useMapContext";
18
21
  import type { RealtimeLayerOptions } from "mobility-toolbox-js/ol/layers/RealtimeLayer";
19
22
  import type {
20
23
  RealtimeMot,
21
- RealtimeStation,
22
24
  RealtimeTrainId,
23
25
  StyleMetadataGraphs,
24
26
  } from "mobility-toolbox-js/types";
@@ -26,6 +28,7 @@ import type {
26
28
  const TRACKING_ZOOM = 16;
27
29
 
28
30
  const useGraphs = true; //;window.location?.href?.includes("graphs=true");
31
+ const geojson = new GeoJSON();
29
32
 
30
33
  function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
31
34
  const {
@@ -41,10 +44,6 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
41
44
  setIsFollowing,
42
45
  setIsTracking,
43
46
  setRealtimeLayer,
44
- setStation,
45
- setStopSequence,
46
- stationId,
47
- stopSequence,
48
47
  tenant,
49
48
  trainId,
50
49
  } = useMapContext();
@@ -74,16 +73,18 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
74
73
  }
75
74
  : undefined,
76
75
  isQueryable: true,
76
+ minZoom: 5, // It depends fo the radius mapping in realtimeStyleUtils
77
77
  name: LAYER_NAME_REALTIME,
78
78
  tenant,
79
79
  url: realtimeurl,
80
80
  zIndex: 1,
81
81
  ...props,
82
82
  styleOptions: {
83
+ getColor: getMainColorForVehicle,
83
84
  getDelayColor: getDelayColorForVehicle,
84
- getDelayFont: getDelayFontForVehicle,
85
85
  getDelayText: getDelayTextForVehicle,
86
86
  getText: getTextForVehicle,
87
+ getTextColor: getTextColorForVehicle,
87
88
  getTextFont: getTextFontForVehicle,
88
89
  ...(props?.styleOptions || {}),
89
90
  },
@@ -114,10 +115,10 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
114
115
 
115
116
  // Behavior when vehicle is selected or not.
116
117
  useEffect(() => {
117
- if (!stopSequence) {
118
+ if (!trainId) {
118
119
  setIsFollowing(false);
119
120
  }
120
- }, [stopSequence, setIsFollowing]);
121
+ }, [trainId, setIsFollowing]);
121
122
 
122
123
  // Behavior when user tracking is activated or not.
123
124
  useEffect(() => {
@@ -152,11 +153,13 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
152
153
 
153
154
  if (layer) {
154
155
  layer.engine.useThrottle = !isFollowing;
156
+ // We deactivate the bbox on moveend when following to have smoother experience.
157
+ // The correct bbox will be set later by getting the full trajectory.
155
158
  layer.engine.isUpdateBboxOnMoveEnd = !isFollowing;
156
159
  // layer.useRequestAnimationFrame = isFollowing;
157
160
  layer.allowRenderWhenAnimating = !!isFollowing;
158
161
  }
159
- if (!isFollowing || !stopSequence || !map || !layer) {
162
+ if (!isFollowing || !trainId || !map || !layer) {
160
163
  return;
161
164
  }
162
165
 
@@ -166,11 +169,29 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
166
169
  let vehicle = id && layer?.trajectories?.[id];
167
170
 
168
171
  if (!vehicle) {
169
- const message = await layer.api.getTrajectory(
170
- stopSequence.id,
172
+ const message = await layer.api.getTrajectory(trainId, layer.mode);
173
+ vehicle = message?.content;
174
+
175
+ // We get the fulltrajectory to set the api bbox correctly.
176
+ const fullMessage = await layer.api.getFullTrajectory(
177
+ trainId,
171
178
  layer.mode,
179
+ undefined,
172
180
  );
173
- vehicle = message?.content;
181
+ if (fullMessage?.content) {
182
+ try {
183
+ const features = geojson.readFeatures(fullMessage.content);
184
+ const extent = new Vector({ features }).getExtent();
185
+ layer.api.bbox = [...extent, ...layer.api.bbox.slice(4)];
186
+ } catch (err) {
187
+ // eslint-disable-next-line no-console
188
+ console.warn(
189
+ "Error parsing full trajectory feature:",
190
+ err,
191
+ fullMessage?.content,
192
+ );
193
+ }
194
+ }
174
195
  }
175
196
 
176
197
  const success = await centerOnVehicle(vehicle, map, TRACKING_ZOOM);
@@ -178,16 +199,19 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
178
199
  // Once the map is zoomed on the vehicle we follow him, only recenter , no zoom changes.
179
200
  if (success === true) {
180
201
  interval = setInterval(() => {
181
- void centerOnVehicle(layer?.trajectories?.[stopSequence.id], map);
202
+ void centerOnVehicle(
203
+ layer?.trajectories?.[trainId],
204
+ map,
205
+ TRACKING_ZOOM,
206
+ );
182
207
  }, 1000);
183
208
  }
184
209
  };
185
- void followVehicle(stopSequence.id);
186
-
210
+ void followVehicle(trainId);
187
211
  return () => {
188
212
  clearInterval(interval);
189
213
  };
190
- }, [isFollowing, map, layer, stopSequence, setIsTracking]);
214
+ }, [isFollowing, map, layer, trainId, setIsTracking]);
191
215
 
192
216
  // DO NOT CENTER ON THE TRAIN
193
217
  // useEffect(() => {
@@ -200,26 +224,7 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
200
224
  // }
201
225
  // }, [map, trainId, layer]);
202
226
 
203
- // Ask the station using the stationId to the Realtime API.
204
- useEffect(() => {
205
- if (!stationId || !layer?.api) {
206
- return;
207
- }
208
- layer?.api?.subscribe(`station ${stationId}`, ({ content }) => {
209
- if (content) {
210
- setStation(content as RealtimeStation);
211
- }
212
- });
213
-
214
- return () => {
215
- setStation(null);
216
- if (stationId) {
217
- layer?.api?.unsubscribe(`station ${stationId}`);
218
- }
219
- };
220
- }, [stationId, layer?.api, setStation]);
221
-
222
- // Subscribe to the stop sequence of the selected vehicle.
227
+ // Subscribe to the full trajectory of the selected vehicle.
223
228
  useEffect(() => {
224
229
  if (!trainId || !layer?.api) {
225
230
  return;
@@ -230,25 +235,14 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
230
235
  console.error("Error highlighting trajectory:", err);
231
236
  });
232
237
 
233
- layer?.api?.subscribeStopSequence(trainId, ({ content }) => {
234
- if (content) {
235
- const [firstStopSequence] = content;
236
- if (firstStopSequence) {
237
- setStopSequence(firstStopSequence);
238
- }
239
- }
240
- });
241
-
242
238
  return () => {
243
- setStopSequence(null);
244
- if (trainId && layer) {
245
- layer.api?.unsubscribeStopSequence(trainId);
239
+ if (layer?.selectedVehicleId) {
246
240
  layer.api?.unsubscribeFullTrajectory(layer.selectedVehicleId);
247
241
  layer.selectedVehicleId = null;
248
242
  layer.vectorLayer.getSource().clear();
249
243
  }
250
244
  };
251
- }, [trainId, layer, layer?.api, setStopSequence]);
245
+ }, [trainId, layer, layer?.api]);
252
246
 
253
247
  // Get graphs value
254
248
  useEffect(() => {
@@ -2,9 +2,10 @@ import { twMerge } from "tailwind-merge";
2
2
 
3
3
  import NoRealtime from "../icons/NoRealtime";
4
4
  import getMainColorForVehicle from "../utils/getMainColorForVehicle";
5
- import getTextColor from "../utils/getTextColor";
5
+ import getTextColorForVehicle from "../utils/getTextColorForVehicle";
6
6
  import getTextFontForVehicle from "../utils/getTextFontForVehicle";
7
7
  import getTextForVehicle from "../utils/getTextForVehicle";
8
+ import useMapContext from "../utils/hooks/useMapContext";
8
9
 
9
10
  import type {
10
11
  RealtimeDeparture,
@@ -14,11 +15,14 @@ import type {
14
15
  } from "mobility-toolbox-js/types";
15
16
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
16
17
 
18
+ import type { LnpLineInfo } from "../utils/hooks/useLnp";
19
+
17
20
  export type RouteIconProps = {
18
21
  className?: string;
19
22
  departure?: RealtimeDeparture;
20
23
  displayNoRealtimeIcon?: boolean;
21
24
  line?: RealtimeLine;
25
+ lineInfo?: LnpLineInfo;
22
26
  stopSequence?: RealtimeStopSequence;
23
27
  trajectory?: RealtimeTrajectory;
24
28
  } & HTMLAttributes<HTMLSpanElement> &
@@ -31,33 +35,40 @@ function RouteIcon({
31
35
  departure,
32
36
  displayNoRealtimeIcon = false,
33
37
  line,
38
+ lineInfo,
34
39
  stopSequence,
35
40
  trajectory,
36
41
  ...props
37
42
  }: RouteIconProps) {
43
+ const { realtimeLayer } = useMapContext();
38
44
  const lineToUse =
39
45
  line ||
46
+ lineInfo ||
40
47
  departure?.line ||
41
48
  stopSequence?.line ||
42
49
  trajectory?.properties?.line;
43
- const type = lineToUse?.type || stopSequence?.type || trajectory?.type;
44
- const backgroundColor = getMainColorForVehicle(
45
- line || departure || stopSequence || trajectory,
46
- );
47
- const color = lineToUse?.text_color || getTextColor(type);
48
- let borderColor = lineToUse?.stroke || "black";
49
- const text = getTextForVehicle(
50
- line || departure || stopSequence || trajectory,
51
- );
50
+ const trainId = stopSequence?.id || departure?.train_id;
51
+ const trajectoryToUse = trajectory || realtimeLayer?.trajectories[trainId];
52
+ const objectToUse =
53
+ line || lineInfo || departure || stopSequence || trajectory;
52
54
 
55
+ const backgroundColor = getMainColorForVehicle(objectToUse);
56
+ const color = getTextColorForVehicle(objectToUse);
57
+ const text = getTextForVehicle(objectToUse);
53
58
  const fontSize = fontSizesByNbLetters[text.length] || 12;
54
- const font = getTextFontForVehicle(fontSize, text);
59
+ const font = getTextFontForVehicle(objectToUse, null, fontSize, text);
55
60
 
56
61
  // RealtimeIcon only for stopsequence for now
57
- const hasRealtime = stopSequence?.has_realtime_journey === true;
58
- const showNoRealtimeIcon = !!stopSequence;
62
+ const hasRealtime =
63
+ stopSequence?.has_realtime_journey ||
64
+ departure?.has_realtime_journey ||
65
+ trajectoryToUse?.properties?.has_realtime_journey ||
66
+ (stopSequence?.stations?.[0]?.state &&
67
+ stopSequence?.stations?.[0]?.state !== "TIME_BASED");
68
+ const showNoRealtimeIcon = !!stopSequence || !!departure || !!trajectoryToUse;
59
69
  const isCancelled = stopSequence?.stations[0]?.state === "JOURNEY_CANCELLED";
60
70
 
71
+ let borderColor = lineToUse?.stroke || "black";
61
72
  if (borderColor === backgroundColor) {
62
73
  borderColor = "black";
63
74
  }
@@ -81,7 +92,7 @@ function RouteIcon({
81
92
  {displayNoRealtimeIcon &&
82
93
  showNoRealtimeIcon &&
83
94
  !isCancelled &&
84
- !hasRealtime && <NoRealtime className={"absolute -top-2 -left-2"} />}
95
+ !hasRealtime && <NoRealtime className={"absolute -top-3 -right-3"} />}
85
96
  </span>
86
97
  );
87
98
  }
@@ -7,15 +7,19 @@ import RouteScheduleHeader from "../RouteScheduleHeader";
7
7
  import RouteStop from "../RouteStop";
8
8
  import ShadowOverflow from "../ShadowOverflow";
9
9
  import useMapContext from "../utils/hooks/useMapContext";
10
+ import useRealtimeStopSequences from "../utils/hooks/useRealtimeStopSequences";
10
11
 
11
12
  import type { RealtimeStop } from "mobility-toolbox-js/types";
12
- import type { JSX, PreactDOMAttributes } from "preact";
13
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
13
14
 
14
- export type RouteScheduleProps = JSX.HTMLAttributes<HTMLDivElement> &
15
+ export type RouteScheduleProps = {
16
+ className?: string;
17
+ } & HTMLAttributes<HTMLDivElement> &
15
18
  PreactDOMAttributes;
16
19
 
17
- function RouteSchedule(props: RouteScheduleProps) {
18
- const { stopSequence } = useMapContext();
20
+ function RouteSchedule({ className }: RouteScheduleProps) {
21
+ const { trainId } = useMapContext();
22
+ const stopSequences = useRealtimeStopSequences(trainId);
19
23
  const ref = useRef();
20
24
 
21
25
  useEffect(() => {
@@ -38,17 +42,15 @@ function RouteSchedule(props: RouteScheduleProps) {
38
42
  clearTimeout(interval);
39
43
  };
40
44
  // Scroll automatically when a new scroll infos is set.
41
- }, [stopSequence]);
45
+ }, [stopSequences]);
42
46
 
43
- if (!stopSequence) {
47
+ if (!stopSequences?.[0]) {
44
48
  return null;
45
49
  }
46
-
47
- const { className } = props;
48
-
50
+ const stopSequence = stopSequences[0];
49
51
  return (
50
52
  <>
51
- <RouteScheduleHeader />
53
+ <RouteScheduleHeader stopSequence={stopSequence} />
52
54
  <ShadowOverflow>
53
55
  <div className={twMerge("text-base", className)} ref={ref}>
54
56
  {stopSequence.stations.map((stop: RealtimeStop, index: number) => {
@@ -62,10 +64,11 @@ function RouteSchedule(props: RouteScheduleProps) {
62
64
  (`${stationId}` || stationName) + arrivalTime + departureTime
63
65
  }
64
66
  stop={stop}
67
+ stopSequence={stopSequence}
65
68
  />
66
69
  );
67
70
  })}
68
- <RouteScheduleFooter />
71
+ <RouteScheduleFooter stopSequence={stopSequence} />
69
72
  </div>
70
73
  </ShadowOverflow>
71
74
  </>
@@ -1,6 +1,6 @@
1
1
  import { memo } from "preact/compat";
2
2
 
3
- import useMapContext from "../utils/hooks/useMapContext";
3
+ import type { RealtimeStopSequence } from "mobility-toolbox-js/types";
4
4
 
5
5
  const defaultRenderLink = (text: string, url: string) => {
6
6
  return url ? (
@@ -17,8 +17,11 @@ const defaultRenderLink = (text: string, url: string) => {
17
17
  );
18
18
  };
19
19
 
20
- function RouteScheduleFooter() {
21
- const { stopSequence } = useMapContext();
20
+ function RouteScheduleFooter({
21
+ stopSequence,
22
+ }: {
23
+ stopSequence: RealtimeStopSequence;
24
+ }) {
22
25
  if (!stopSequence.operator && !stopSequence.publisher) {
23
26
  return null;
24
27
  }