@geops/rvf-mobility-web-component 0.1.75 → 0.1.77
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.
- package/CHANGELOG.md +15 -0
- package/index.js +99 -99
- package/package.json +2 -2
- package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +10 -2
- package/src/MobilityMap/MobilityMap.tsx +4 -0
- package/src/NotificationDetails/NotificationDetails.tsx +30 -57
- package/src/NotificationsLayer/MocoLayer2.ts +91 -0
- package/src/NotificationsLayer/NotificationsLayer.tsx +2 -4
- package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +3 -12
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +95 -84
- package/src/SingleClickListener/SingleClickListener.tsx +1 -1
- package/src/utils/hooks/useMocoSituation.tsx +54 -0
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@geops/rvf-mobility-web-component",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
4
|
"description": "Web components for rvf in the domains of mobility and logistics.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.77",
|
|
6
6
|
"homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"jspdf": "^3.0.3",
|
|
13
13
|
"lodash.debounce": "^4.0.8",
|
|
14
14
|
"maplibre-gl": "^5.9.0",
|
|
15
|
-
"mobility-toolbox-js": "3.4.
|
|
15
|
+
"mobility-toolbox-js": "3.4.6-beta.6",
|
|
16
16
|
"ol": "^10.6.1",
|
|
17
17
|
"preact": "^10.27.2",
|
|
18
18
|
"preact-custom-element": "^4.5.1",
|
|
@@ -15,7 +15,6 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) {
|
|
|
15
15
|
const {
|
|
16
16
|
baseLayer,
|
|
17
17
|
featuresInfos,
|
|
18
|
-
lines,
|
|
19
18
|
linesIds,
|
|
20
19
|
linesNetworkPlanLayer,
|
|
21
20
|
map,
|
|
@@ -58,6 +57,11 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) {
|
|
|
58
57
|
return featuresInfo.layer === linesNetworkPlanLayer;
|
|
59
58
|
})?.features || [];
|
|
60
59
|
|
|
60
|
+
if (!features.length) {
|
|
61
|
+
setLinesIds(null);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
const ids = [
|
|
62
66
|
...new Set(
|
|
63
67
|
(features || []).map((f) => {
|
|
@@ -65,6 +69,10 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) {
|
|
|
65
69
|
}),
|
|
66
70
|
),
|
|
67
71
|
];
|
|
72
|
+
if (!ids.length) {
|
|
73
|
+
setLinesIds(null);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
68
76
|
setLinesIds(ids?.length ? ids : null);
|
|
69
77
|
}, [featuresInfos, layer, linesNetworkPlanLayer, setLinesIds]);
|
|
70
78
|
|
|
@@ -81,7 +89,7 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) {
|
|
|
81
89
|
// Reset the filter
|
|
82
90
|
highlightLinesNetworkPlan(undefined, baseLayer);
|
|
83
91
|
};
|
|
84
|
-
}, [baseLayer, baseLayer?.loaded, layer,
|
|
92
|
+
}, [baseLayer, baseLayer?.loaded, layer, linesIds]);
|
|
85
93
|
|
|
86
94
|
return null;
|
|
87
95
|
}
|
|
@@ -87,6 +87,7 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
87
87
|
const [stationId, setStationId] = useState<RealtimeStationId>();
|
|
88
88
|
const [trainId, setTrainId] = useState<RealtimeTrainId>();
|
|
89
89
|
const [linesIds, setLinesIds] = useState<string[]>();
|
|
90
|
+
const [notificationId, setNotificationId] = useState<string>();
|
|
90
91
|
|
|
91
92
|
const [featuresInfos, setFeaturesInfos] = useState<
|
|
92
93
|
LayerGetFeatureInfoResponse[]
|
|
@@ -141,6 +142,7 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
141
142
|
linesNetworkPlanLayer,
|
|
142
143
|
map,
|
|
143
144
|
mapsetLayer,
|
|
145
|
+
notificationId,
|
|
144
146
|
notificationsLayer,
|
|
145
147
|
permalinkUrlSearchParams,
|
|
146
148
|
previewNotifications,
|
|
@@ -175,6 +177,7 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
175
177
|
setLinesNetworkPlanLayer,
|
|
176
178
|
setMap,
|
|
177
179
|
setMapsetLayer,
|
|
180
|
+
setNotificationId,
|
|
178
181
|
setNotificationsLayer,
|
|
179
182
|
setPermalinkUrlSearchParams,
|
|
180
183
|
setPreviewNotifications,
|
|
@@ -222,6 +225,7 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
222
225
|
linesNetworkPlanLayer,
|
|
223
226
|
map,
|
|
224
227
|
mapsetLayer,
|
|
228
|
+
notificationId,
|
|
225
229
|
notificationsLayer,
|
|
226
230
|
permalinkUrlSearchParams,
|
|
227
231
|
previewNotifications,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AffectedTimeIntervalType,
|
|
3
3
|
type PublicationType,
|
|
4
|
-
type SituationType,
|
|
5
4
|
type TextualContentType,
|
|
6
5
|
} from "mobility-toolbox-js/types";
|
|
7
6
|
import { twMerge } from "tailwind-merge";
|
|
@@ -10,6 +9,7 @@ import Warning from "../icons/Warning";
|
|
|
10
9
|
import ShadowOverflow from "../ShadowOverflow";
|
|
11
10
|
import Link from "../ui/Link";
|
|
12
11
|
import useI18n from "../utils/hooks/useI18n";
|
|
12
|
+
import useMocoSituation from "../utils/hooks/useMocoSituation";
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
15
|
MultilingualTextualContentType,
|
|
@@ -64,30 +64,8 @@ function NotificationDetails({
|
|
|
64
64
|
feature: Feature;
|
|
65
65
|
}) {
|
|
66
66
|
const { locale, t } = useI18n();
|
|
67
|
-
const {
|
|
68
|
-
|
|
69
|
-
// useEffect(() => {
|
|
70
|
-
// const abortController = new AbortController();
|
|
71
|
-
// if (!notificationtenant) {
|
|
72
|
-
// setLines([]);
|
|
73
|
-
// }
|
|
74
|
-
// fetch(
|
|
75
|
-
// `https://tralis-tracker-api.geops.io/api/lines/${notificationtenant}/`,
|
|
76
|
-
// )
|
|
77
|
-
// .then((res) => {
|
|
78
|
-
// return res.json();
|
|
79
|
-
// })
|
|
80
|
-
// .then((data) => {
|
|
81
|
-
// setLines(data);
|
|
82
|
-
// })
|
|
83
|
-
// .catch((err) => {
|
|
84
|
-
// // eslint-disable-next-line no-console
|
|
85
|
-
// console.error("Failed to fetch lines", err);
|
|
86
|
-
// });
|
|
87
|
-
// return () => {
|
|
88
|
-
// abortController?.abort();
|
|
89
|
-
// };
|
|
90
|
-
// }, [notificationtenant]);
|
|
67
|
+
const { situationId } = feature.getProperties();
|
|
68
|
+
const situationParsed = useMocoSituation(situationId);
|
|
91
69
|
|
|
92
70
|
// moco export v2
|
|
93
71
|
let textualContentMultilingual: Partial<MultilingualTextualContentType> = {};
|
|
@@ -96,41 +74,36 @@ function NotificationDetails({
|
|
|
96
74
|
let publicationsToDisplay: PublicationType[] = [];
|
|
97
75
|
let reasonsToDisplay: string[] = [];
|
|
98
76
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
situationParsed?.publications || [];
|
|
77
|
+
// const situationParsed: SituationType = JSON.parse(situation) || {};
|
|
78
|
+
const publicationsArr: PublicationType[] =
|
|
79
|
+
situationParsed?.publications || [];
|
|
103
80
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
81
|
+
// Find the current publication(s) at the current date
|
|
82
|
+
publicationsToDisplay =
|
|
83
|
+
publicationsArr?.filter(({ publicationWindows }) => {
|
|
84
|
+
return publicationWindows.find(({ endTime, startTime }) => {
|
|
85
|
+
const now = new Date();
|
|
86
|
+
const startT = new Date(startTime);
|
|
87
|
+
const endT = new Date(endTime);
|
|
88
|
+
return startT <= now && now <= endT;
|
|
89
|
+
});
|
|
90
|
+
}) || [];
|
|
114
91
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
92
|
+
// Display the current and next affected time intervals not the one in the past
|
|
93
|
+
timeIntervalsToDisplay =
|
|
94
|
+
(situationParsed?.affectedTimeIntervals || []).filter(
|
|
95
|
+
({ endTime, startTime }) => {
|
|
96
|
+
const now = new Date();
|
|
97
|
+
const startT = new Date(startTime);
|
|
98
|
+
const endT = new Date(endTime);
|
|
99
|
+
return (startT <= now && now <= endT) || now < startT;
|
|
100
|
+
},
|
|
101
|
+
) || [];
|
|
125
102
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
} catch (e) {
|
|
131
|
-
// eslint-disable-next-line no-console
|
|
132
|
-
console.error("Failed to parse publications", e);
|
|
133
|
-
}
|
|
103
|
+
// Display the reasons
|
|
104
|
+
reasonsToDisplay = (situationParsed?.reasons || []).map(({ name }) => {
|
|
105
|
+
return name;
|
|
106
|
+
});
|
|
134
107
|
|
|
135
108
|
return (
|
|
136
109
|
<ShadowOverflow {...props} className={twMerge("px-4 text-base", className)}>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getFeatureCollectionToRenderFromSituation,
|
|
3
|
+
MocoLayer,
|
|
4
|
+
} from "mobility-toolbox-js/ol";
|
|
5
|
+
import { unByKey } from "ol/Observable";
|
|
6
|
+
|
|
7
|
+
import type { GeoJSONSource } from "maplibre-gl";
|
|
8
|
+
import type { MocoNotificationFeatureCollectionToRender } from "mobility-toolbox-js/ol";
|
|
9
|
+
import type { Map } from "ol";
|
|
10
|
+
|
|
11
|
+
export const MOCO_SOURCE_ID = "moco";
|
|
12
|
+
export const MOCO_MD_LAYER_FILTER = "moco";
|
|
13
|
+
class MocoLayer2 extends MocoLayer {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
super(options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
attachToMap(map: Map): void {
|
|
19
|
+
super.attachToMap(map);
|
|
20
|
+
unByKey(this.olEventsKeys);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getDataByGraph(
|
|
24
|
+
data: MocoNotificationFeatureCollectionToRender,
|
|
25
|
+
): MocoNotificationFeatureCollectionToRender {
|
|
26
|
+
// const zoom = this.getMapInternal()?.getView()?.getZoom();
|
|
27
|
+
// const graphs = (
|
|
28
|
+
// this.maplibreLayer?.mapLibreMap?.getStyle() as MapsStyleSpecification
|
|
29
|
+
// ).metadata?.graphs;
|
|
30
|
+
|
|
31
|
+
// const graph = getGraphByZoom(zoom, graphs);
|
|
32
|
+
const newData: MocoNotificationFeatureCollectionToRender = {
|
|
33
|
+
features: (data?.features || []).filter((feature) => {
|
|
34
|
+
delete feature.properties?.publicationStops;
|
|
35
|
+
delete feature.properties?.publicationLines;
|
|
36
|
+
delete feature.properties?.publication;
|
|
37
|
+
delete feature.properties?.situation;
|
|
38
|
+
return (
|
|
39
|
+
feature.properties?.graph === "osm" ||
|
|
40
|
+
// feature.properties?.graph === "np_topo4" ||
|
|
41
|
+
// feature.properties?.graph === "np_topo5" ||
|
|
42
|
+
// feature.properties?.graph === "np_topo6" //||
|
|
43
|
+
feature.properties?.graph === "np_topo7" ||
|
|
44
|
+
feature.properties?.graph === "np_topo8" ||
|
|
45
|
+
feature.properties?.graph === "np_topo9" ||
|
|
46
|
+
feature.properties?.graph === "np_topo10" ||
|
|
47
|
+
feature.properties?.graph === "np_topo11" ||
|
|
48
|
+
feature.properties?.graph === "np_topo12" ||
|
|
49
|
+
feature.properties?.graph === "np_topo13" ||
|
|
50
|
+
feature.properties?.graph === "np_topo14" ||
|
|
51
|
+
feature.properties?.graph === "np_topo15"
|
|
52
|
+
);
|
|
53
|
+
}),
|
|
54
|
+
type: "FeatureCollection",
|
|
55
|
+
};
|
|
56
|
+
return newData;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* This function updates the GeoJSON source data, with the current situations available in this.situations.
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
async updateData(): Promise<boolean | undefined> {
|
|
63
|
+
if (this.loadAll) {
|
|
64
|
+
const situations = await this.loadData();
|
|
65
|
+
// We don't use the setter here to avoid infinite loop
|
|
66
|
+
this.set("situations", situations ?? []);
|
|
67
|
+
}
|
|
68
|
+
const source = this.maplibreLayer?.mapLibreMap?.getSource(MOCO_SOURCE_ID);
|
|
69
|
+
if (!source) {
|
|
70
|
+
// eslint-disable-next-line no-console
|
|
71
|
+
console.warn("MocoLayer: No source found for id : ", MOCO_SOURCE_ID);
|
|
72
|
+
return Promise.reject(new Error("No source found"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const data = {
|
|
76
|
+
features: (this.situations ?? []).flatMap((situation) => {
|
|
77
|
+
return getFeatureCollectionToRenderFromSituation(situation).features;
|
|
78
|
+
}),
|
|
79
|
+
type: "FeatureCollection",
|
|
80
|
+
} as MocoNotificationFeatureCollectionToRender;
|
|
81
|
+
// this.#dataInternal = data;
|
|
82
|
+
|
|
83
|
+
// Apply new data to the source
|
|
84
|
+
console.log(this.getDataByGraph(data));
|
|
85
|
+
// console.log(JSON.stringify(this.getDataByGraph(data)));
|
|
86
|
+
(source as GeoJSONSource).setData(this.getDataByGraph(data));
|
|
87
|
+
return Promise.resolve(true);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default MocoLayer2;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { MocoLayer } from "mobility-toolbox-js/ol";
|
|
1
|
+
import { MocoLayer, type MocoLayerOptions } from "mobility-toolbox-js/ol";
|
|
2
2
|
import { memo } from "preact/compat";
|
|
3
3
|
import { useEffect, useMemo } from "preact/hooks";
|
|
4
4
|
|
|
5
5
|
import { LAYER_NAME_NOTIFICATIONS } from "../utils/constants";
|
|
6
6
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
7
|
|
|
8
|
-
import type { MocoLayerOptions } from "mobility-toolbox-js/ol";
|
|
9
|
-
|
|
10
8
|
function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
|
|
11
9
|
const {
|
|
12
10
|
apikey,
|
|
@@ -28,9 +26,9 @@ function NotificationsLayer(props?: Partial<MocoLayerOptions>) {
|
|
|
28
26
|
apiParameters: {
|
|
29
27
|
contentMedium: true,
|
|
30
28
|
},
|
|
31
|
-
date: notificationat ? new Date(notificationat) : undefined,
|
|
32
29
|
maplibreLayer: baseLayer,
|
|
33
30
|
name: LAYER_NAME_NOTIFICATIONS,
|
|
31
|
+
publicAt: notificationat ? new Date(notificationat) : undefined,
|
|
34
32
|
situations: previewNotifications,
|
|
35
33
|
tenant: notificationtenant,
|
|
36
34
|
url: notificationurl,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type SituationType } from "mobility-toolbox-js/types";
|
|
2
1
|
import { twMerge } from "tailwind-merge";
|
|
3
2
|
|
|
4
3
|
import ShadowOverflow from "../../ShadowOverflow";
|
|
5
4
|
import SituationDetails from "../../SituationDetails";
|
|
5
|
+
import useMocoSituation from "../../utils/hooks/useMocoSituation";
|
|
6
6
|
|
|
7
7
|
import type { Feature } from "ol";
|
|
8
8
|
|
|
@@ -20,17 +20,8 @@ function NotificationDetails({
|
|
|
20
20
|
timeIntervalClassName?: string;
|
|
21
21
|
toggleable?: boolean;
|
|
22
22
|
}) {
|
|
23
|
-
const {
|
|
24
|
-
|
|
25
|
-
// moco export v2
|
|
26
|
-
let situationParsed: Partial<SituationType> = {};
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
situationParsed = JSON.parse(situation) || {};
|
|
30
|
-
} catch (e) {
|
|
31
|
-
// eslint-disable-next-line no-console
|
|
32
|
-
console.error("Failed to parse publications", e);
|
|
33
|
-
}
|
|
23
|
+
const { situationId } = feature.getProperties();
|
|
24
|
+
const situationParsed = useMocoSituation(situationId);
|
|
34
25
|
|
|
35
26
|
return (
|
|
36
27
|
<ShadowOverflow {...props} className={twMerge("px-4 text-base", className)}>
|
|
@@ -95,90 +95,6 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
|
|
|
95
95
|
return baseLayer?.mapLibreMap;
|
|
96
96
|
}, [baseLayer?.mapLibreMap]);
|
|
97
97
|
|
|
98
|
-
const updateData = useCallback(() => {
|
|
99
|
-
const abortController = new AbortController();
|
|
100
|
-
const extent = transformExtent(
|
|
101
|
-
map.getView().calculateExtent(),
|
|
102
|
-
"EPSG:3857",
|
|
103
|
-
"EPSG:4326",
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const fetchData = async () => {
|
|
107
|
-
// Fill stations data
|
|
108
|
-
// The stations are also loaded in the style to have them displayed during export
|
|
109
|
-
const stations = await fetchStations(
|
|
110
|
-
WFS_STATIONS_TYPE,
|
|
111
|
-
abortController,
|
|
112
|
-
extent,
|
|
113
|
-
);
|
|
114
|
-
setStationsData(stations);
|
|
115
|
-
|
|
116
|
-
// Fill free float data
|
|
117
|
-
const freeFloatData = (await fetchSharingWFS(
|
|
118
|
-
WFS_FREE_FLOAT_TYPE,
|
|
119
|
-
abortController,
|
|
120
|
-
extent,
|
|
121
|
-
)) as FeatureCollection<Point, SharingVehicleWFS>;
|
|
122
|
-
|
|
123
|
-
const bikes: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
124
|
-
features: [],
|
|
125
|
-
type: "FeatureCollection",
|
|
126
|
-
};
|
|
127
|
-
const cargoBikes: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
128
|
-
features: [],
|
|
129
|
-
type: "FeatureCollection",
|
|
130
|
-
};
|
|
131
|
-
const cars: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
132
|
-
features: [],
|
|
133
|
-
type: "FeatureCollection",
|
|
134
|
-
};
|
|
135
|
-
const scooter: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
136
|
-
features: [],
|
|
137
|
-
type: "FeatureCollection",
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
freeFloatData.features.forEach((feature) => {
|
|
141
|
-
if (feature.properties.form_factor === BIKE_FORM_FACTOR) {
|
|
142
|
-
bikes.features.push(feature);
|
|
143
|
-
} else if (feature.properties.form_factor === CARGOBIKE_FORM_FACTOR) {
|
|
144
|
-
cargoBikes.features.push(feature);
|
|
145
|
-
} else if (feature.properties.form_factor === CAR_FORM_FACTOR) {
|
|
146
|
-
cars.features.push(feature);
|
|
147
|
-
} else if (feature.properties.form_factor === SCOOTER_FORM_FACTOR) {
|
|
148
|
-
scooter.features.push(feature);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
setBikeFreeFloatData(bikes);
|
|
153
|
-
setCargoBikeFreeFloatData(cargoBikes);
|
|
154
|
-
setCarFreeFloatData(cars);
|
|
155
|
-
setScooterFreeFloatData(scooter);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
void fetchData();
|
|
159
|
-
|
|
160
|
-
return () => {
|
|
161
|
-
abortController.abort();
|
|
162
|
-
};
|
|
163
|
-
}, [map]);
|
|
164
|
-
|
|
165
|
-
// Request all stations and vehicleson each moveend event
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
const key = map?.on("moveend", updateData);
|
|
168
|
-
|
|
169
|
-
// @ts-expect-error - change property can have custom values
|
|
170
|
-
const key2 = map?.on(`change:${LAYER_PROP_IS_EXPORTING}`, (evt) => {
|
|
171
|
-
// Reupdate the data after finishing eporting the map
|
|
172
|
-
if (!evt.target.get(LAYER_PROP_IS_EXPORTING)) {
|
|
173
|
-
updateData();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
const key3 = map?.once("rendercomplete", updateData);
|
|
177
|
-
return () => {
|
|
178
|
-
unByKey([key, key2, key3]);
|
|
179
|
-
};
|
|
180
|
-
}, [map, updateData, mbMap]);
|
|
181
|
-
|
|
182
98
|
useEffect(() => {
|
|
183
99
|
if (!mbMap?.style || !stationsData) {
|
|
184
100
|
return;
|
|
@@ -402,6 +318,101 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
|
|
|
402
318
|
} as GroupOptions);
|
|
403
319
|
}, [baseLayer, props, t]);
|
|
404
320
|
|
|
321
|
+
const updateData = useCallback(() => {
|
|
322
|
+
// Only update when layers are visible
|
|
323
|
+
if (map?.getView()?.getZoom() < 15.5 || !group.getVisible()) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const abortController = new AbortController();
|
|
327
|
+
const extent = transformExtent(
|
|
328
|
+
map.getView().calculateExtent(),
|
|
329
|
+
"EPSG:3857",
|
|
330
|
+
"EPSG:4326",
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const fetchData = async () => {
|
|
334
|
+
// Fill stations data
|
|
335
|
+
// The stations are also loaded in the style to have them displayed during export
|
|
336
|
+
const stations = await fetchStations(
|
|
337
|
+
WFS_STATIONS_TYPE,
|
|
338
|
+
abortController,
|
|
339
|
+
extent,
|
|
340
|
+
);
|
|
341
|
+
setStationsData(stations);
|
|
342
|
+
|
|
343
|
+
// Fill free float data
|
|
344
|
+
const freeFloatData = (await fetchSharingWFS(
|
|
345
|
+
WFS_FREE_FLOAT_TYPE,
|
|
346
|
+
abortController,
|
|
347
|
+
extent,
|
|
348
|
+
)) as FeatureCollection<Point, SharingVehicleWFS>;
|
|
349
|
+
|
|
350
|
+
const bikes: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
351
|
+
features: [],
|
|
352
|
+
type: "FeatureCollection",
|
|
353
|
+
};
|
|
354
|
+
const cargoBikes: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
355
|
+
features: [],
|
|
356
|
+
type: "FeatureCollection",
|
|
357
|
+
};
|
|
358
|
+
const cars: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
359
|
+
features: [],
|
|
360
|
+
type: "FeatureCollection",
|
|
361
|
+
};
|
|
362
|
+
const scooter: FeatureCollection<Point, SharingVehicleWFS> = {
|
|
363
|
+
features: [],
|
|
364
|
+
type: "FeatureCollection",
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
freeFloatData.features.forEach((feature) => {
|
|
368
|
+
if (feature.properties.form_factor === BIKE_FORM_FACTOR) {
|
|
369
|
+
bikes.features.push(feature);
|
|
370
|
+
} else if (feature.properties.form_factor === CARGOBIKE_FORM_FACTOR) {
|
|
371
|
+
cargoBikes.features.push(feature);
|
|
372
|
+
} else if (feature.properties.form_factor === CAR_FORM_FACTOR) {
|
|
373
|
+
cars.features.push(feature);
|
|
374
|
+
} else if (feature.properties.form_factor === SCOOTER_FORM_FACTOR) {
|
|
375
|
+
scooter.features.push(feature);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
setBikeFreeFloatData(bikes);
|
|
380
|
+
setCargoBikeFreeFloatData(cargoBikes);
|
|
381
|
+
setCarFreeFloatData(cars);
|
|
382
|
+
setScooterFreeFloatData(scooter);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
void fetchData();
|
|
386
|
+
|
|
387
|
+
return () => {
|
|
388
|
+
abortController.abort();
|
|
389
|
+
};
|
|
390
|
+
}, [map, group]);
|
|
391
|
+
|
|
392
|
+
// Request all stations and vehicleson each moveend event
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
const key = map?.on("moveend", () => {
|
|
395
|
+
return updateData();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// @ts-expect-error - change property can have custom values
|
|
399
|
+
const key2 = map?.on(`change:${LAYER_PROP_IS_EXPORTING}`, (evt) => {
|
|
400
|
+
// Reupdate the data after finishing eporting the map
|
|
401
|
+
if (!evt.target.get(LAYER_PROP_IS_EXPORTING)) {
|
|
402
|
+
updateData();
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
const key3 = map?.once("rendercomplete", updateData);
|
|
406
|
+
const key4 = group?.on("change:visible", (evt) => {
|
|
407
|
+
if (evt.target.getVisible()) {
|
|
408
|
+
updateData();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return () => {
|
|
412
|
+
unByKey([key, key2, key3, key4]);
|
|
413
|
+
};
|
|
414
|
+
}, [map, updateData, mbMap, group]);
|
|
415
|
+
|
|
405
416
|
// Reload features every minute
|
|
406
417
|
useEffect(() => {
|
|
407
418
|
const interval = window.setInterval(() => {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
import useMapContext from "./useMapContext";
|
|
4
|
+
|
|
5
|
+
import type { MocoAPI } from "mobility-toolbox-js/maplibre";
|
|
6
|
+
import type {
|
|
7
|
+
MocoExportParameters,
|
|
8
|
+
SituationType,
|
|
9
|
+
} from "mobility-toolbox-js/types";
|
|
10
|
+
|
|
11
|
+
function useMocoSituation(situationId?: string, params?: MocoExportParameters) {
|
|
12
|
+
const { lang, notificationsLayer } = useMapContext();
|
|
13
|
+
const [situation, setSituation] = useState<SituationType>();
|
|
14
|
+
|
|
15
|
+
const api: MocoAPI | undefined = useMemo(() => {
|
|
16
|
+
return notificationsLayer?.api;
|
|
17
|
+
}, [notificationsLayer]);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const abortController = new AbortController();
|
|
21
|
+
api
|
|
22
|
+
?.exportById(situationId, {
|
|
23
|
+
contentLarge: true,
|
|
24
|
+
contentMedium: true,
|
|
25
|
+
contentSmall: true,
|
|
26
|
+
includeGeoms: false,
|
|
27
|
+
includeLines: true,
|
|
28
|
+
includeStops: true,
|
|
29
|
+
[lang]: true,
|
|
30
|
+
limit: 1,
|
|
31
|
+
...(params || {}),
|
|
32
|
+
})
|
|
33
|
+
.then((response) => {
|
|
34
|
+
setSituation(response);
|
|
35
|
+
})
|
|
36
|
+
.catch((err) => {
|
|
37
|
+
// 20: AbortError
|
|
38
|
+
if (err.code !== 20) {
|
|
39
|
+
setSituation(undefined);
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.error("Failed to fetch situation", err);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
abortController.abort();
|
|
48
|
+
};
|
|
49
|
+
}, [api, situationId, params, lang]);
|
|
50
|
+
|
|
51
|
+
return situation;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default useMocoSituation;
|