@geops/rvf-mobility-web-component 0.1.9 → 0.1.11
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/.github/CODEOWNERS +37 -0
- package/.github/workflows/conventional-pr-title.yml +36 -0
- package/CHANGELOG.md +55 -0
- package/README.md +3 -1
- package/doc/package.json +5 -5
- package/doc/src/app/components/GeopsMobilityDoc.tsx +19 -0
- package/docutils.js +198 -0
- package/index.html +48 -217
- package/index.js +683 -91
- package/input.css +15 -1
- package/jest-setup.js +3 -2
- package/package.json +9 -8
- package/scripts/dev.mjs +1 -1
- package/search.html +38 -69
- package/src/GeolocationButton/GeolocationButton.tsx +6 -17
- package/src/LayerTree/LayerTree.tsx +44 -0
- package/src/LayerTree/TreeItem/TreeItem.tsx +145 -0
- package/src/LayerTree/TreeItem/index.tsx +1 -0
- package/src/LayerTree/TreeItemContainer/TreeItemContainer.tsx +16 -0
- package/src/LayerTree/TreeItemContainer/index.tsx +1 -0
- package/src/LayerTree/index.tsx +1 -0
- package/src/LayerTree/layersTreeContext.ts +4 -0
- package/src/LayerTree/layersTreeReducer.ts +156 -0
- package/src/Map/Map.tsx +57 -12
- package/src/MobilityMap/MobilityMap.tsx +22 -9
- package/src/MobilityMap/index.css +0 -13
- package/src/RealtimeLayer/RealtimeLayer.tsx +1 -1
- package/src/RvfButton/RvfButton.tsx +45 -0
- package/src/RvfButton/index.tsx +1 -0
- package/src/RvfExportMenu/RvfExportMenu.tsx +95 -0
- package/src/RvfExportMenu/index.tsx +1 -0
- package/src/RvfExportMenuButton/RvfExportMenuButton.tsx +27 -0
- package/src/RvfExportMenuButton/index.tsx +1 -0
- package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +29 -0
- package/src/RvfFeatureDetails/index.tsx +1 -0
- package/src/RvfIconButton/RvfIconButton.tsx +35 -0
- package/src/RvfIconButton/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +132 -52
- package/src/RvfMobilityMap/index.css +0 -13
- package/src/RvfModal/RvfModal.tsx +52 -0
- package/src/RvfModal/index.tsx +1 -0
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +39 -0
- package/src/RvfPoisLayer/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +88 -0
- package/src/RvfSharedMobilityLayerGroup/index.tsx +1 -0
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +137 -0
- package/src/RvfSingleClickListener/index.tsx +1 -0
- package/src/RvfZoomButtons/RvfZoomButtons.tsx +73 -0
- package/src/RvfZoomButtons/index.tsx +1 -0
- package/src/Search/Search.tsx +11 -9
- package/src/SingleClickListener/index.tsx +1 -1
- package/src/StationsLayer/StationsLayer.tsx +0 -1
- package/src/StopsSearch/StopsSearch.tsx +38 -6
- package/src/TopicMenu/TopicMenu.tsx +143 -0
- package/src/TopicMenu/index.tsx +1 -0
- package/src/icons/Cancel/Cancel.tsx +21 -0
- package/src/icons/Cancel/cancel.svg +7 -0
- package/src/icons/Cancel/index.tsx +1 -0
- package/src/icons/Download/Download.tsx +20 -0
- package/src/icons/Download/download.svg +15 -0
- package/src/icons/Download/index.tsx +1 -0
- package/src/icons/Elevator/Elevator.tsx +1 -1
- package/src/icons/Geolocation/Geolocation.tsx +21 -0
- package/src/icons/Geolocation/index.tsx +1 -0
- package/src/icons/Menu/Menu.tsx +32 -0
- package/src/icons/Menu/index.tsx +1 -0
- package/src/icons/Menu/menu.svg +9 -0
- package/src/icons/Minus/Minus.tsx +19 -0
- package/src/icons/Minus/index.tsx +1 -0
- package/src/icons/Minus/minus.svg +7 -0
- package/src/icons/Plus/Plus.tsx +19 -0
- package/src/icons/Plus/index.tsx +1 -0
- package/src/icons/Plus/plus.svg +7 -0
- package/src/index.tsx +2 -0
- package/src/utils/constants.ts +9 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +120 -0
- package/src/utils/exportPdf.ts +677 -0
- package/src/utils/hooks/useRvfContext.tsx +37 -0
- package/src/utils/hooks/useUpdatePermalink.tsx +2 -9
- package/tailwind.config.mjs +60 -8
|
@@ -9,49 +9,67 @@ import {
|
|
|
9
9
|
RealtimeStopSequence,
|
|
10
10
|
RealtimeTrainId,
|
|
11
11
|
} from "mobility-toolbox-js/types";
|
|
12
|
-
import { Map as OlMap } from "ol";
|
|
13
|
-
import { getCenter } from "ol/extent";
|
|
14
|
-
import { fromLonLat } from "ol/proj";
|
|
12
|
+
import { Feature, Map as OlMap } from "ol";
|
|
15
13
|
import { memo } from "preact/compat";
|
|
16
|
-
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
14
|
+
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
17
15
|
|
|
18
16
|
import BaseLayer from "../BaseLayer";
|
|
19
17
|
import Copyright from "../Copyright";
|
|
20
18
|
import GeolocationButton from "../GeolocationButton";
|
|
19
|
+
import Cancel from "../icons/Cancel";
|
|
20
|
+
import Menu from "../icons/Menu";
|
|
21
21
|
import Map from "../Map";
|
|
22
22
|
import { MobilityMapProps } from "../MobilityMap/MobilityMap";
|
|
23
23
|
import NotificationLayer from "../NotificationLayer";
|
|
24
24
|
import Overlay from "../Overlay";
|
|
25
25
|
import RealtimeLayer from "../RealtimeLayer";
|
|
26
26
|
import RouteSchedule from "../RouteSchedule";
|
|
27
|
+
import RvfExportMenu from "../RvfExportMenu";
|
|
28
|
+
import RvfExportMenuButton from "../RvfExportMenuButton";
|
|
29
|
+
import RvfFeatureDetails from "../RvfFeatureDetails";
|
|
30
|
+
// Notificationurl example: https://mobility-web-component-tmp.vercel.app/geops-mobility?notificationurl=https%3A%2F%2Fmoco.geops.io%2Fapi%2Fv1%2Fexport%2Fnotification%2F%3Fsso_config%3Dsob&geolocation=false&realtime=false&search=false¬ificationat=2024-01-25T22%3A59%3A00Z
|
|
31
|
+
import RvfIconButton from "../RvfIconButton";
|
|
32
|
+
import Modal from "../RvfModal";
|
|
33
|
+
import RvfPoisLayer from "../RvfPoisLayer";
|
|
34
|
+
import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
|
|
35
|
+
import RvfZoomButtons from "../RvfZoomButtons";
|
|
27
36
|
import ScaleLine from "../ScaleLine";
|
|
28
37
|
import Search from "../Search";
|
|
29
|
-
import SingleClickListener from "../SingleClickListener
|
|
38
|
+
import SingleClickListener from "../SingleClickListener";
|
|
30
39
|
import Station from "../Station";
|
|
31
40
|
import StationsLayer from "../StationsLayer";
|
|
32
41
|
// @ts-expect-error bad type definition
|
|
33
42
|
import tailwind from "../style.css";
|
|
43
|
+
import TopicMenu from "../TopicMenu";
|
|
44
|
+
import { RVF_EXTENT_3857 } from "../utils/constants";
|
|
34
45
|
import { I18nContext } from "../utils/hooks/useI18n";
|
|
35
46
|
import { MapContext } from "../utils/hooks/useMapContext";
|
|
47
|
+
import { RvfContext } from "../utils/hooks/useRvfContext";
|
|
36
48
|
import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
|
|
37
49
|
import i18n from "../utils/i18n";
|
|
38
50
|
import MobilityEvent from "../utils/MobilityEvent";
|
|
39
51
|
// @ts-expect-error bad type definition
|
|
40
52
|
import style from "./index.css";
|
|
41
|
-
// Notificationurl example: https://mobility-web-component-tmp.vercel.app/geops-mobility?notificationurl=https%3A%2F%2Fmoco.geops.io%2Fapi%2Fv1%2Fexport%2Fnotification%2F%3Fsso_config%3Dsob&geolocation=false&realtime=false&search=false¬ificationat=2024-01-25T22%3A59%3A00Z
|
|
42
53
|
|
|
43
54
|
export type RvfMobilityMapProps = {} & MobilityMapProps;
|
|
44
55
|
|
|
45
|
-
const
|
|
46
|
-
|
|
56
|
+
const bbox = RVF_EXTENT_3857.join(",");
|
|
57
|
+
|
|
58
|
+
const baseLayerProps = {
|
|
59
|
+
mapLibreOptions: {
|
|
60
|
+
preserveDrawingBuffer: true,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
47
63
|
|
|
48
64
|
function RvfMobilityMap({
|
|
49
65
|
apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
|
|
50
66
|
baselayer = "de.rvf",
|
|
51
|
-
center =
|
|
67
|
+
center = null,
|
|
68
|
+
extent = bbox,
|
|
52
69
|
geolocation = "true",
|
|
53
70
|
mapsurl = "https://maps.geops.io",
|
|
54
|
-
|
|
71
|
+
maxextent = bbox,
|
|
72
|
+
maxzoom = "20",
|
|
55
73
|
minzoom = null,
|
|
56
74
|
mots = null,
|
|
57
75
|
notification = "true",
|
|
@@ -64,8 +82,9 @@ function RvfMobilityMap({
|
|
|
64
82
|
search = "false",
|
|
65
83
|
stopsurl = "https://api.geops.io/stops/v1/",
|
|
66
84
|
tenant = null,
|
|
67
|
-
zoom =
|
|
85
|
+
zoom = null,
|
|
68
86
|
}: RvfMobilityMapProps) {
|
|
87
|
+
const eventNodeRef = useRef<HTMLDivElement>();
|
|
69
88
|
const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
|
|
70
89
|
const [isFollowing, setIsFollowing] = useState(false);
|
|
71
90
|
const [isTracking, setIsTracking] = useState(false);
|
|
@@ -76,10 +95,14 @@ function RvfMobilityMap({
|
|
|
76
95
|
const [map, setMap] = useState<OlMap>();
|
|
77
96
|
const [stationId, setStationId] = useState<RealtimeStationId>();
|
|
78
97
|
const [trainId, setTrainId] = useState<RealtimeTrainId>();
|
|
98
|
+
const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
|
|
99
|
+
const [selectedFeature, setSelectedFeature] = useState<Feature>();
|
|
100
|
+
const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
|
|
101
|
+
const [isLayerTreeOpen, setIsLayerTreeOpen] = useState(false);
|
|
79
102
|
|
|
80
103
|
// TODO: this should be removed. The parent application should be responsible to do this
|
|
81
104
|
// or we should find something that fit more usecases
|
|
82
|
-
|
|
105
|
+
useUpdatePermalink(map, permalink === "true");
|
|
83
106
|
|
|
84
107
|
const mapContextValue = useMemo(() => {
|
|
85
108
|
return {
|
|
@@ -88,11 +111,13 @@ function RvfMobilityMap({
|
|
|
88
111
|
baselayer,
|
|
89
112
|
baseLayer,
|
|
90
113
|
center,
|
|
114
|
+
extent,
|
|
91
115
|
geolocation,
|
|
92
116
|
isFollowing,
|
|
93
117
|
isTracking,
|
|
94
118
|
map,
|
|
95
119
|
mapsurl,
|
|
120
|
+
maxextent,
|
|
96
121
|
maxzoom,
|
|
97
122
|
minzoom,
|
|
98
123
|
mots,
|
|
@@ -127,11 +152,13 @@ function RvfMobilityMap({
|
|
|
127
152
|
baselayer,
|
|
128
153
|
baseLayer,
|
|
129
154
|
center,
|
|
155
|
+
extent,
|
|
130
156
|
geolocation,
|
|
131
157
|
isFollowing,
|
|
132
158
|
isTracking,
|
|
133
159
|
map,
|
|
134
160
|
mapsurl,
|
|
161
|
+
maxextent,
|
|
135
162
|
maxzoom,
|
|
136
163
|
minzoom,
|
|
137
164
|
mots,
|
|
@@ -153,12 +180,14 @@ function RvfMobilityMap({
|
|
|
153
180
|
]);
|
|
154
181
|
|
|
155
182
|
useEffect(() => {
|
|
156
|
-
dispatchEvent(
|
|
183
|
+
eventNodeRef.current?.dispatchEvent(
|
|
157
184
|
new MobilityEvent<RvfMobilityMapProps>("mwc:attribute", {
|
|
158
185
|
baselayer,
|
|
159
|
-
center
|
|
186
|
+
center,
|
|
187
|
+
extent,
|
|
160
188
|
geolocation,
|
|
161
189
|
mapsurl,
|
|
190
|
+
maxextent,
|
|
162
191
|
maxzoom,
|
|
163
192
|
minzoom,
|
|
164
193
|
mots,
|
|
@@ -170,7 +199,7 @@ function RvfMobilityMap({
|
|
|
170
199
|
realtimeurl,
|
|
171
200
|
search,
|
|
172
201
|
tenant,
|
|
173
|
-
zoom
|
|
202
|
+
zoom,
|
|
174
203
|
}),
|
|
175
204
|
);
|
|
176
205
|
}, [
|
|
@@ -190,53 +219,104 @@ function RvfMobilityMap({
|
|
|
190
219
|
search,
|
|
191
220
|
tenant,
|
|
192
221
|
zoom,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
z,
|
|
222
|
+
extent,
|
|
223
|
+
maxextent,
|
|
196
224
|
]);
|
|
197
225
|
|
|
226
|
+
const rvfContextValue = useMemo(() => {
|
|
227
|
+
return {
|
|
228
|
+
isExportMenuOpen,
|
|
229
|
+
selectedFeature,
|
|
230
|
+
selectedFeatures,
|
|
231
|
+
setIsExportMenuOpen,
|
|
232
|
+
setSelectedFeature,
|
|
233
|
+
setSelectedFeatures,
|
|
234
|
+
};
|
|
235
|
+
}, [isExportMenuOpen, selectedFeature, selectedFeatures]);
|
|
236
|
+
|
|
198
237
|
return (
|
|
199
238
|
<I18nContext.Provider value={i18n}>
|
|
200
239
|
<style>{tailwind}</style>
|
|
201
240
|
<style>{style}</style>
|
|
202
241
|
<MapContext.Provider value={mapContextValue}>
|
|
203
|
-
<
|
|
204
|
-
<div
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<
|
|
220
|
-
|
|
242
|
+
<RvfContext.Provider value={rvfContextValue}>
|
|
243
|
+
<div
|
|
244
|
+
className="relative size-full border font-sans @container/main"
|
|
245
|
+
ref={eventNodeRef}
|
|
246
|
+
>
|
|
247
|
+
<div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
|
|
248
|
+
<Map className="relative flex-1 overflow-visible ">
|
|
249
|
+
<BaseLayer {...baseLayerProps} isNotInLayerTree />
|
|
250
|
+
<SingleClickListener />
|
|
251
|
+
|
|
252
|
+
{realtime === "true" && <RealtimeLayer title="Realtime data" />}
|
|
253
|
+
{tenant && <StationsLayer />}
|
|
254
|
+
{notification === "true" && <NotificationLayer />}
|
|
255
|
+
{/* <RvfLnpLayer />
|
|
256
|
+
<RvfVerkaufStellenLayer />
|
|
257
|
+
<RvfTarifZonenLayer /> */}
|
|
258
|
+
<RvfPoisLayer title="POIs" />
|
|
259
|
+
|
|
260
|
+
<RvfSharedMobilityLayerGroup title="Shared Mobility" />
|
|
261
|
+
|
|
262
|
+
<div className="absolute left-2 top-2 z-10">
|
|
263
|
+
<RvfIconButton
|
|
264
|
+
onClick={() => {
|
|
265
|
+
return setIsLayerTreeOpen(!isLayerTreeOpen);
|
|
266
|
+
}}
|
|
267
|
+
selected={isLayerTreeOpen}
|
|
268
|
+
>
|
|
269
|
+
{isLayerTreeOpen ? <Cancel /> : <Menu />}
|
|
270
|
+
</RvfIconButton>
|
|
271
|
+
{isLayerTreeOpen && <TopicMenu map={map} />}
|
|
221
272
|
</div>
|
|
273
|
+
<div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
|
|
274
|
+
<ScaleLine className="bg-slate-50/70" />
|
|
275
|
+
<Copyright className="bg-slate-50/70" />
|
|
276
|
+
</div>
|
|
277
|
+
<div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
|
|
278
|
+
{geolocation === "true" && <GeolocationButton />}
|
|
279
|
+
</div>
|
|
280
|
+
{search === "true" && (
|
|
281
|
+
<div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
|
|
282
|
+
<Search />
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
<div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
|
|
286
|
+
<RvfExportMenuButton />
|
|
287
|
+
<RvfZoomButtons />
|
|
288
|
+
</div>
|
|
289
|
+
</Map>
|
|
290
|
+
|
|
291
|
+
<Overlay
|
|
292
|
+
className={"z-50"}
|
|
293
|
+
ScrollableHandlerProps={{
|
|
294
|
+
style: { width: "calc(100% - 60px)" },
|
|
295
|
+
}}
|
|
296
|
+
>
|
|
297
|
+
{realtime === "true" && trainId && (
|
|
298
|
+
<RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
|
|
299
|
+
)}
|
|
300
|
+
{tenant && stationId && (
|
|
301
|
+
<Station className="relative overflow-y-auto overflow-x-hidden" />
|
|
302
|
+
)}
|
|
303
|
+
{selectedFeature && (
|
|
304
|
+
<RvfFeatureDetails className="relative overflow-y-auto overflow-x-hidden" />
|
|
305
|
+
)}
|
|
306
|
+
</Overlay>
|
|
307
|
+
|
|
308
|
+
{isExportMenuOpen && (
|
|
309
|
+
<Modal
|
|
310
|
+
onClose={() => {
|
|
311
|
+
setIsExportMenuOpen(false);
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
|
|
315
|
+
</Modal>
|
|
222
316
|
)}
|
|
223
|
-
</
|
|
224
|
-
|
|
225
|
-
<Overlay
|
|
226
|
-
className={"z-50"}
|
|
227
|
-
ScrollableHandlerProps={{
|
|
228
|
-
style: { width: "calc(100% - 60px)" },
|
|
229
|
-
}}
|
|
230
|
-
>
|
|
231
|
-
{realtime === "true" && trainId && (
|
|
232
|
-
<RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
|
|
233
|
-
)}
|
|
234
|
-
{tenant && stationId && (
|
|
235
|
-
<Station className="relative overflow-y-auto overflow-x-hidden" />
|
|
236
|
-
)}
|
|
237
|
-
</Overlay>
|
|
317
|
+
</div>
|
|
238
318
|
</div>
|
|
239
|
-
</
|
|
319
|
+
</RvfContext.Provider>
|
|
240
320
|
</MapContext.Provider>
|
|
241
321
|
</I18nContext.Provider>
|
|
242
322
|
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
import { memo } from "preact/compat";
|
|
3
|
+
import { useEffect, useState } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
export type ModalProps = {
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
} & JSX.HTMLAttributes<HTMLDialogElement> &
|
|
8
|
+
PreactDOMAttributes;
|
|
9
|
+
|
|
10
|
+
function RvfModal({ children, onClose, ...props }: ModalProps) {
|
|
11
|
+
const [node, setNode] = useState<HTMLDialogElement>();
|
|
12
|
+
let hasChildren = !!children;
|
|
13
|
+
if (Array.isArray(children)) {
|
|
14
|
+
hasChildren =
|
|
15
|
+
children?.length &&
|
|
16
|
+
(children || []).find((c) => {
|
|
17
|
+
return !!c;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
node?.addEventListener("close", onClose);
|
|
23
|
+
return () => {
|
|
24
|
+
node?.removeEventListener("close", onClose);
|
|
25
|
+
};
|
|
26
|
+
}, [node, onClose]);
|
|
27
|
+
|
|
28
|
+
if (!hasChildren) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<dialog
|
|
34
|
+
className={
|
|
35
|
+
"absolute inset-0 z-50 flex size-full items-center justify-center bg-transparent"
|
|
36
|
+
}
|
|
37
|
+
ref={(n) => {
|
|
38
|
+
setNode(n);
|
|
39
|
+
n?.focus();
|
|
40
|
+
}}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<button
|
|
44
|
+
className="absolute inset-0 z-0 size-full bg-black/60"
|
|
45
|
+
onClick={onClose}
|
|
46
|
+
></button>
|
|
47
|
+
<div className="z-10 h-3/4 w-1/2 bg-white">{children}</div>
|
|
48
|
+
</dialog>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default memo(RvfModal);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfModal";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MaplibreStyleLayer } from "mobility-toolbox-js/ol";
|
|
2
|
+
import { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/MaplibreStyleLayer";
|
|
3
|
+
import { memo } from "preact/compat";
|
|
4
|
+
import { useEffect, useMemo } from "preact/hooks";
|
|
5
|
+
|
|
6
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
|
+
|
|
8
|
+
function RvfPoisLayer(props: MaplibreStyleLayerOptions) {
|
|
9
|
+
const { baseLayer, map } = useMapContext();
|
|
10
|
+
|
|
11
|
+
const layer = useMemo(() => {
|
|
12
|
+
if (!baseLayer) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return new MaplibreStyleLayer({
|
|
16
|
+
layersFilter: ({ metadata }) => {
|
|
17
|
+
return metadata?.["mapset.filter"] === "mapset_poi";
|
|
18
|
+
},
|
|
19
|
+
maplibreLayer: baseLayer,
|
|
20
|
+
visible: false,
|
|
21
|
+
...(props || {}),
|
|
22
|
+
});
|
|
23
|
+
}, [baseLayer, props]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!map || !layer) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
map.addLayer(layer);
|
|
31
|
+
return () => {
|
|
32
|
+
map.removeLayer(layer);
|
|
33
|
+
};
|
|
34
|
+
}, [map, layer]);
|
|
35
|
+
|
|
36
|
+
return null; // <RegisterForSelectFeaturesOnClick />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default memo(RvfPoisLayer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfPoisLayer";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Options } from "ol/layer/Group";
|
|
2
|
+
|
|
3
|
+
import { Group, Vector } from "ol/layer";
|
|
4
|
+
import { memo } from "preact/compat";
|
|
5
|
+
import { useEffect, useMemo } from "preact/hooks";
|
|
6
|
+
|
|
7
|
+
import createMobiDataBwWfsLayer from "../utils/createMobiDataBwWfsLayer";
|
|
8
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
9
|
+
|
|
10
|
+
function RvfSharedMobilityLayerGroup(props: Options & Record<string, unknown>) {
|
|
11
|
+
const { map } = useMapContext();
|
|
12
|
+
|
|
13
|
+
const group = useMemo(() => {
|
|
14
|
+
const sharingStationsBicycle = createMobiDataBwWfsLayer(
|
|
15
|
+
"sharing_stations_bicycle",
|
|
16
|
+
"red",
|
|
17
|
+
);
|
|
18
|
+
sharingStationsBicycle.set("title", "Stations Bicycle");
|
|
19
|
+
|
|
20
|
+
const sharingStationsCar = createMobiDataBwWfsLayer(
|
|
21
|
+
"sharing_stations_car",
|
|
22
|
+
"blue",
|
|
23
|
+
);
|
|
24
|
+
sharingStationsCar.set("title", "Stations Car");
|
|
25
|
+
|
|
26
|
+
const sharingStationsCargoBicycle = createMobiDataBwWfsLayer(
|
|
27
|
+
"sharing_stations_cargo_bicycle",
|
|
28
|
+
"green",
|
|
29
|
+
);
|
|
30
|
+
sharingStationsCargoBicycle.set("title", "Stations Cargo Bicycle");
|
|
31
|
+
|
|
32
|
+
const sharingStationsScooterStanding = createMobiDataBwWfsLayer(
|
|
33
|
+
"sharing_stations_scooters_standing",
|
|
34
|
+
"purple",
|
|
35
|
+
);
|
|
36
|
+
sharingStationsScooterStanding.set("title", "Stations Scooter");
|
|
37
|
+
|
|
38
|
+
const sharingVehicles = createMobiDataBwWfsLayer(
|
|
39
|
+
"sharing_vehicles",
|
|
40
|
+
"orange",
|
|
41
|
+
);
|
|
42
|
+
sharingVehicles.set("title", "Free Floating");
|
|
43
|
+
console.log("ici");
|
|
44
|
+
return new Group({
|
|
45
|
+
layers: [
|
|
46
|
+
sharingStationsBicycle,
|
|
47
|
+
sharingStationsCar,
|
|
48
|
+
sharingStationsCargoBicycle,
|
|
49
|
+
sharingStationsScooterStanding,
|
|
50
|
+
sharingVehicles,
|
|
51
|
+
],
|
|
52
|
+
...props,
|
|
53
|
+
});
|
|
54
|
+
}, [props]);
|
|
55
|
+
|
|
56
|
+
// Reload features every minute
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const interval = window.setInterval(() => {
|
|
59
|
+
group.getLayers().forEach((layer: Vector) => {
|
|
60
|
+
// @ts-expect-error - private property
|
|
61
|
+
layer.getSource().loadedExtentsRtree_.clear();
|
|
62
|
+
layer.getSource().clear(true);
|
|
63
|
+
layer.getSource().changed();
|
|
64
|
+
});
|
|
65
|
+
}, 60000);
|
|
66
|
+
return () => {
|
|
67
|
+
window.clearInterval(interval);
|
|
68
|
+
};
|
|
69
|
+
}, [group]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!map || !group) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
map.on("moveend", () => {
|
|
76
|
+
console.log("ZOOM", map.getView().getZoom());
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
map.addLayer(group);
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
map.removeLayer(group);
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
export default memo(RvfSharedMobilityLayerGroup);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfSharedMobilityLayerGroup";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Feature, MapBrowserEvent } from "ol";
|
|
2
|
+
import { GeoJSON } from "ol/format";
|
|
3
|
+
import { unByKey } from "ol/Observable";
|
|
4
|
+
import { toLonLat } from "ol/proj";
|
|
5
|
+
import { useCallback, useEffect } from "preact/hooks";
|
|
6
|
+
|
|
7
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
8
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
9
|
+
import MobilityEvent from "../utils/MobilityEvent";
|
|
10
|
+
|
|
11
|
+
const geojson = new GeoJSON();
|
|
12
|
+
|
|
13
|
+
function SingleClickListener() {
|
|
14
|
+
const {
|
|
15
|
+
map,
|
|
16
|
+
realtimeLayer,
|
|
17
|
+
setStationId,
|
|
18
|
+
setTrainId,
|
|
19
|
+
stationId,
|
|
20
|
+
stationsLayer,
|
|
21
|
+
tenant,
|
|
22
|
+
trainId,
|
|
23
|
+
} = useMapContext();
|
|
24
|
+
const { setSelectedFeature, setSelectedFeatures } = useRvfContext();
|
|
25
|
+
|
|
26
|
+
const onPointerMove = useCallback(
|
|
27
|
+
async (evt: MapBrowserEvent<PointerEvent>) => {
|
|
28
|
+
const [realtimeFeature] = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
29
|
+
layerFilter: (l) => {
|
|
30
|
+
return l === realtimeLayer;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
realtimeLayer?.highlight(realtimeFeature as Feature);
|
|
34
|
+
|
|
35
|
+
const stationsFeatures = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
36
|
+
layerFilter: (l) => {
|
|
37
|
+
return l === stationsLayer;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const [stationFeature] = stationsFeatures.filter((feat) => {
|
|
42
|
+
return feat.get("tralis_network")?.includes(tenant);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
evt.map.getTargetElement().style.cursor =
|
|
46
|
+
realtimeFeature || stationFeature ? "pointer" : "default";
|
|
47
|
+
},
|
|
48
|
+
[realtimeLayer, stationsLayer, tenant],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const onSingleClick = useCallback(
|
|
52
|
+
async (evt: MapBrowserEvent<PointerEvent>) => {
|
|
53
|
+
const [realtimeFeature] = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
54
|
+
layerFilter: (l) => {
|
|
55
|
+
return l === realtimeLayer;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const stationsFeatures = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
60
|
+
layerFilter: (l) => {
|
|
61
|
+
return l === stationsLayer;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const [stationFeature] = stationsFeatures.filter((feat) => {
|
|
65
|
+
return feat.get("tralis_network")?.includes(tenant);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const newStationId = stationFeature?.get("uid");
|
|
69
|
+
|
|
70
|
+
const newTrainId = realtimeFeature?.get("train_id");
|
|
71
|
+
|
|
72
|
+
if (newStationId && stationId !== newStationId) {
|
|
73
|
+
setStationId(newStationId);
|
|
74
|
+
setTrainId(null);
|
|
75
|
+
} else if (newTrainId && newTrainId !== trainId) {
|
|
76
|
+
setTrainId(realtimeFeature.get("train_id"));
|
|
77
|
+
setStationId(null);
|
|
78
|
+
} else {
|
|
79
|
+
setTrainId(null);
|
|
80
|
+
setStationId(null);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Send all the features under the cursor
|
|
84
|
+
const features = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
85
|
+
layerFilter: (l) => {
|
|
86
|
+
return l.get("isQueryable");
|
|
87
|
+
},
|
|
88
|
+
}) as Feature[];
|
|
89
|
+
evt.map.getTargetElement().dispatchEvent(
|
|
90
|
+
new MobilityEvent("singleclick", {
|
|
91
|
+
...evt,
|
|
92
|
+
features: geojson.writeFeaturesObject(features),
|
|
93
|
+
lonlat: toLonLat(evt.coordinate),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
// feature.get("form_factor"); //free float
|
|
97
|
+
// feature.get("num_vehicles_available"); // wfs livedata
|
|
98
|
+
// feature.get("provider_name"); // station sharing
|
|
99
|
+
|
|
100
|
+
if (newStationId || newTrainId || !features.length) {
|
|
101
|
+
setSelectedFeature(null);
|
|
102
|
+
setSelectedFeatures([]);
|
|
103
|
+
} else {
|
|
104
|
+
setSelectedFeatures(features);
|
|
105
|
+
setSelectedFeature(features[0]);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[
|
|
109
|
+
stationId,
|
|
110
|
+
trainId,
|
|
111
|
+
realtimeLayer,
|
|
112
|
+
stationsLayer,
|
|
113
|
+
tenant,
|
|
114
|
+
setStationId,
|
|
115
|
+
setTrainId,
|
|
116
|
+
setSelectedFeature,
|
|
117
|
+
setSelectedFeatures,
|
|
118
|
+
],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const key = map?.on("singleclick", onSingleClick);
|
|
123
|
+
return () => {
|
|
124
|
+
unByKey(key);
|
|
125
|
+
};
|
|
126
|
+
}, [map, onSingleClick]);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const key = map?.on("pointermove", onPointerMove);
|
|
130
|
+
return () => {
|
|
131
|
+
unByKey(key);
|
|
132
|
+
};
|
|
133
|
+
}, [map, onPointerMove]);
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
export default SingleClickListener;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfSingleClickListener";
|