@geops/rvf-mobility-web-component 0.1.11 → 0.1.13
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 +36 -0
- package/docutils.js +9 -9
- package/index.html +1 -1
- package/index.js +332 -263
- package/input.css +2 -8
- package/package.json +15 -15
- package/scripts/build.mjs +3 -3
- package/scripts/dev.mjs +3 -1
- package/src/BaseLayer/BaseLayer.tsx +20 -12
- package/src/RealtimeLayer/RealtimeLayer.tsx +1 -2
- package/src/RouteSchedule/RouteSchedule.tsx +3 -1
- package/src/RouteStop/RouteStop.tsx +1 -1
- package/src/RvfButton/RvfButton.tsx +2 -2
- package/src/RvfCheckbox/RvfCheckbox.tsx +25 -0
- package/src/RvfCheckbox/index.tsx +1 -0
- package/src/RvfExportMenu/RvfExportMenu.tsx +31 -11
- package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +44 -0
- package/src/RvfFloatingMenu/index.tsx +1 -0
- package/src/RvfIconButton/RvfIconButton.tsx +1 -1
- package/src/{LayerTree/LayerTree.tsx → RvfLayerTree/RvfLayerTree.tsx} +11 -18
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +130 -0
- package/src/RvfLayerTree/index.tsx +1 -0
- package/src/{LayerTree → RvfLayerTree}/layersTreeReducer.ts +1 -4
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +49 -0
- package/src/RvfLineNetworkPlanLayer/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +100 -35
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
- package/src/RvfRadioButton/RvfRadioButton.tsx +16 -0
- package/src/RvfRadioButton/index.tsx +1 -0
- package/src/RvfSelect/RvfSelect.tsx +22 -0
- package/src/RvfSelect/index.tsx +1 -0
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +45 -0
- package/src/RvfSellingPointsLayer/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +93 -44
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +20 -3
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +48 -0
- package/src/RvfTarifZonenLayer/index.tsx +1 -0
- package/src/RvfTopics/RvfTopics.tsx +44 -0
- package/src/RvfTopics/index.tsx +1 -0
- package/src/icons/ArrowDown/ArrowDown.tsx +22 -0
- package/src/icons/ArrowDown/down-open.svg +7 -0
- package/src/icons/ArrowDown/index.tsx +1 -0
- package/src/icons/ArrowUp/ArrowUp.tsx +22 -0
- package/src/icons/ArrowUp/index.tsx +1 -0
- package/src/icons/ArrowUp/up-open.svg +7 -0
- package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +19 -0
- package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +14 -0
- package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +27 -0
- package/src/icons/DownOpen/DownOpen.tsx +24 -0
- package/src/icons/DownOpen/down-open.svg +7 -0
- package/src/icons/DownOpen/index.tsx +1 -0
- package/src/icons/Ok/ok-grey.svg +7 -0
- package/src/icons/Ok/ok.svg +4 -0
- package/src/icons/Scooter/scooter.svg +10 -0
- package/src/utils/constants.ts +2 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +31 -23
- package/src/utils/createSharedMobilityLayer.ts +176 -0
- package/src/utils/exportPdf.ts +20 -29
- package/src/utils/getAllLayers.ts +25 -0
- package/src/utils/hooks/useRvfContext.tsx +33 -0
- package/tailwind.config.mjs +31 -50
- package/src/LayerTree/TreeItem/TreeItem.tsx +0 -145
- package/src/LayerTree/TreeItemContainer/TreeItemContainer.tsx +0 -16
- package/src/LayerTree/TreeItemContainer/index.tsx +0 -1
- package/src/LayerTree/index.tsx +0 -1
- package/src/TopicMenu/TopicMenu.tsx +0 -143
- package/src/TopicMenu/index.tsx +0 -1
- /package/src/{LayerTree → RvfLayerTree}/TreeItem/index.tsx +0 -0
- /package/src/{LayerTree → RvfLayerTree}/layersTreeContext.ts +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
8
|
+
|
|
9
|
+
function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
|
|
10
|
+
const { baseLayer, map } = useMapContext();
|
|
11
|
+
const { setLineNetworkPlanLayer } = useRvfContext();
|
|
12
|
+
|
|
13
|
+
const layer = useMemo(() => {
|
|
14
|
+
if (!baseLayer) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return new MaplibreStyleLayer({
|
|
18
|
+
isQueryable: true,
|
|
19
|
+
layersFilter: ({ metadata, source }) => {
|
|
20
|
+
return (
|
|
21
|
+
metadata?.["rvf.filter"] === "netowrk_plans" ||
|
|
22
|
+
source === "network_plans"
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
maplibreLayer: baseLayer,
|
|
26
|
+
minZoom: 10,
|
|
27
|
+
...(props || {}),
|
|
28
|
+
});
|
|
29
|
+
}, [baseLayer, props]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setLineNetworkPlanLayer(layer);
|
|
33
|
+
}, [layer, setLineNetworkPlanLayer]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!map || !layer) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
map.addLayer(layer);
|
|
41
|
+
return () => {
|
|
42
|
+
map.removeLayer(layer);
|
|
43
|
+
};
|
|
44
|
+
}, [map, layer]);
|
|
45
|
+
|
|
46
|
+
return null; // <RegisterForSelectFeaturesOnClick />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default memo(RvfLineNetworkPlanLayer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfLineNetworkPlanLayer";
|
|
@@ -10,14 +10,19 @@ import {
|
|
|
10
10
|
RealtimeTrainId,
|
|
11
11
|
} from "mobility-toolbox-js/types";
|
|
12
12
|
import { Feature, Map as OlMap } from "ol";
|
|
13
|
+
import { Group } from "ol/layer";
|
|
13
14
|
import { memo } from "preact/compat";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useCallback,
|
|
17
|
+
useEffect,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
} from "preact/hooks";
|
|
15
22
|
|
|
16
23
|
import BaseLayer from "../BaseLayer";
|
|
17
24
|
import Copyright from "../Copyright";
|
|
18
25
|
import GeolocationButton from "../GeolocationButton";
|
|
19
|
-
import Cancel from "../icons/Cancel";
|
|
20
|
-
import Menu from "../icons/Menu";
|
|
21
26
|
import Map from "../Map";
|
|
22
27
|
import { MobilityMapProps } from "../MobilityMap/MobilityMap";
|
|
23
28
|
import NotificationLayer from "../NotificationLayer";
|
|
@@ -27,11 +32,15 @@ import RouteSchedule from "../RouteSchedule";
|
|
|
27
32
|
import RvfExportMenu from "../RvfExportMenu";
|
|
28
33
|
import RvfExportMenuButton from "../RvfExportMenuButton";
|
|
29
34
|
import RvfFeatureDetails from "../RvfFeatureDetails";
|
|
35
|
+
import RvfFloatingMenu from "../RvfFloatingMenu";
|
|
30
36
|
// 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
|
|
37
|
+
import RvfLineNetworkPlanLayer from "../RvfLineNetworkPlanLayer";
|
|
32
38
|
import Modal from "../RvfModal";
|
|
33
39
|
import RvfPoisLayer from "../RvfPoisLayer";
|
|
40
|
+
import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
|
|
34
41
|
import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
|
|
42
|
+
import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
|
|
43
|
+
import Topics from "../RvfTopics";
|
|
35
44
|
import RvfZoomButtons from "../RvfZoomButtons";
|
|
36
45
|
import ScaleLine from "../ScaleLine";
|
|
37
46
|
import Search from "../Search";
|
|
@@ -40,7 +49,6 @@ import Station from "../Station";
|
|
|
40
49
|
import StationsLayer from "../StationsLayer";
|
|
41
50
|
// @ts-expect-error bad type definition
|
|
42
51
|
import tailwind from "../style.css";
|
|
43
|
-
import TopicMenu from "../TopicMenu";
|
|
44
52
|
import { RVF_EXTENT_3857 } from "../utils/constants";
|
|
45
53
|
import { I18nContext } from "../utils/hooks/useI18n";
|
|
46
54
|
import { MapContext } from "../utils/hooks/useMapContext";
|
|
@@ -57,10 +65,18 @@ const bbox = RVF_EXTENT_3857.join(",");
|
|
|
57
65
|
|
|
58
66
|
const baseLayerProps = {
|
|
59
67
|
mapLibreOptions: {
|
|
68
|
+
maxCanvasSize: [20000, 20000] as [number, number], // remove 4096 limitations
|
|
60
69
|
preserveDrawingBuffer: true,
|
|
61
70
|
},
|
|
62
71
|
};
|
|
63
72
|
|
|
73
|
+
const styleProps = {
|
|
74
|
+
//fontSize: 16
|
|
75
|
+
};
|
|
76
|
+
const scrollableHandlerProps = {
|
|
77
|
+
style: { width: "calc(100% - 60px)" },
|
|
78
|
+
};
|
|
79
|
+
|
|
64
80
|
function RvfMobilityMap({
|
|
65
81
|
apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
|
|
66
82
|
baselayer = "de.rvf",
|
|
@@ -98,7 +114,15 @@ function RvfMobilityMap({
|
|
|
98
114
|
const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
|
|
99
115
|
const [selectedFeature, setSelectedFeature] = useState<Feature>();
|
|
100
116
|
const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
|
|
101
|
-
const [isLayerTreeOpen, setIsLayerTreeOpen] = useState(false);
|
|
117
|
+
const [isLayerTreeOpen, setIsLayerTreeOpen] = useState<boolean>(false);
|
|
118
|
+
const [sellingPointsLayer, setSellingPointsLayer] =
|
|
119
|
+
useState<MaplibreStyleLayer>();
|
|
120
|
+
const [tarifZonenLayer, setTarifZonenLayer] = useState<MaplibreStyleLayer>();
|
|
121
|
+
const [poisLayer, setPoisLayer] = useState<MaplibreStyleLayer>();
|
|
122
|
+
const [lineNetworkPlanLayer, setLineNetworkPlanLayer] =
|
|
123
|
+
useState<MaplibreStyleLayer>();
|
|
124
|
+
const [sharedMobilityLayerGroup, setSharedMobilityLayerGroup] =
|
|
125
|
+
useState<Group>();
|
|
102
126
|
|
|
103
127
|
// TODO: this should be removed. The parent application should be responsible to do this
|
|
104
128
|
// or we should find something that fit more usecases
|
|
@@ -226,13 +250,57 @@ function RvfMobilityMap({
|
|
|
226
250
|
const rvfContextValue = useMemo(() => {
|
|
227
251
|
return {
|
|
228
252
|
isExportMenuOpen,
|
|
253
|
+
isLayerTreeOpen,
|
|
254
|
+
lineNetworkPlanLayer,
|
|
255
|
+
poisLayer,
|
|
229
256
|
selectedFeature,
|
|
230
257
|
selectedFeatures,
|
|
258
|
+
sellingPointsLayer,
|
|
231
259
|
setIsExportMenuOpen,
|
|
260
|
+
setIsLayerTreeOpen,
|
|
261
|
+
setLineNetworkPlanLayer,
|
|
262
|
+
setPoisLayer,
|
|
232
263
|
setSelectedFeature,
|
|
233
264
|
setSelectedFeatures,
|
|
265
|
+
setSellingPointsLayer,
|
|
266
|
+
setSharedMobilityLayerGroup,
|
|
267
|
+
setTarifZonenLayer,
|
|
268
|
+
sharedMobilityLayerGroup,
|
|
269
|
+
tarifZonenLayer,
|
|
270
|
+
};
|
|
271
|
+
}, [
|
|
272
|
+
isExportMenuOpen,
|
|
273
|
+
isLayerTreeOpen,
|
|
274
|
+
lineNetworkPlanLayer,
|
|
275
|
+
poisLayer,
|
|
276
|
+
selectedFeature,
|
|
277
|
+
selectedFeatures,
|
|
278
|
+
sellingPointsLayer,
|
|
279
|
+
sharedMobilityLayerGroup,
|
|
280
|
+
tarifZonenLayer,
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
const onLayerTreeMenuClick = useCallback(() => {
|
|
284
|
+
setIsLayerTreeOpen(!isLayerTreeOpen);
|
|
285
|
+
}, [isLayerTreeOpen]);
|
|
286
|
+
|
|
287
|
+
const onExportMenuClose = useCallback(() => {
|
|
288
|
+
setIsExportMenuOpen(false);
|
|
289
|
+
}, []);
|
|
290
|
+
|
|
291
|
+
const copyrightOptions = useMemo(() => {
|
|
292
|
+
return {
|
|
293
|
+
format: (copyrights) => {
|
|
294
|
+
const newCopyrights = [];
|
|
295
|
+
copyrights.forEach((copyright) => {
|
|
296
|
+
if (!/(sbb|rvf)/i.test(copyright)) {
|
|
297
|
+
newCopyrights.push(copyright);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return newCopyrights.join(" | ");
|
|
301
|
+
},
|
|
234
302
|
};
|
|
235
|
-
}, [
|
|
303
|
+
}, []);
|
|
236
304
|
|
|
237
305
|
return (
|
|
238
306
|
<I18nContext.Provider value={i18n}>
|
|
@@ -243,56 +311,57 @@ function RvfMobilityMap({
|
|
|
243
311
|
<div
|
|
244
312
|
className="relative size-full border font-sans @container/main"
|
|
245
313
|
ref={eventNodeRef}
|
|
314
|
+
style={styleProps}
|
|
246
315
|
>
|
|
247
|
-
<div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
|
|
316
|
+
<div className="relative flex size-full flex-col text-base @lg/main:flex-row-reverse">
|
|
248
317
|
<Map className="relative flex-1 overflow-visible ">
|
|
249
|
-
<BaseLayer {...baseLayerProps}
|
|
318
|
+
<BaseLayer {...baseLayerProps} />
|
|
250
319
|
<SingleClickListener />
|
|
251
320
|
|
|
252
|
-
{realtime === "true" && <RealtimeLayer title="
|
|
321
|
+
{realtime === "true" && <RealtimeLayer title="Echtzeit" />}
|
|
253
322
|
{tenant && <StationsLayer />}
|
|
254
323
|
{notification === "true" && <NotificationLayer />}
|
|
255
|
-
|
|
256
|
-
<
|
|
257
|
-
<RvfTarifZonenLayer />
|
|
324
|
+
<RvfSellingPointsLayer title="Verkaufsstellen" />
|
|
325
|
+
<RvfLineNetworkPlanLayer title="Liniennetz" />
|
|
326
|
+
<RvfTarifZonenLayer title="Tarifzonen" />
|
|
258
327
|
<RvfPoisLayer title="POIs" />
|
|
259
|
-
|
|
260
328
|
<RvfSharedMobilityLayerGroup title="Shared Mobility" />
|
|
261
329
|
|
|
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} />}
|
|
272
|
-
</div>
|
|
273
330
|
<div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
|
|
274
331
|
<ScaleLine className="bg-slate-50/70" />
|
|
275
|
-
<Copyright
|
|
332
|
+
<Copyright
|
|
333
|
+
className="bg-slate-50/70"
|
|
334
|
+
options={copyrightOptions}
|
|
335
|
+
/>
|
|
276
336
|
</div>
|
|
337
|
+
|
|
277
338
|
<div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
|
|
278
339
|
{geolocation === "true" && <GeolocationButton />}
|
|
340
|
+
<RvfExportMenuButton />
|
|
279
341
|
</div>
|
|
342
|
+
|
|
280
343
|
{search === "true" && (
|
|
281
344
|
<div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
|
|
282
345
|
<Search />
|
|
283
346
|
</div>
|
|
284
347
|
)}
|
|
348
|
+
|
|
285
349
|
<div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
|
|
286
|
-
<RvfExportMenuButton />
|
|
287
350
|
<RvfZoomButtons />
|
|
288
351
|
</div>
|
|
352
|
+
|
|
353
|
+
<RvfFloatingMenu
|
|
354
|
+
isOpen={isLayerTreeOpen}
|
|
355
|
+
onClick={onLayerTreeMenuClick}
|
|
356
|
+
title="Kartendaten"
|
|
357
|
+
>
|
|
358
|
+
<Topics className={"w-full px-2"} />
|
|
359
|
+
</RvfFloatingMenu>
|
|
289
360
|
</Map>
|
|
290
361
|
|
|
291
362
|
<Overlay
|
|
292
363
|
className={"z-50"}
|
|
293
|
-
ScrollableHandlerProps={
|
|
294
|
-
style: { width: "calc(100% - 60px)" },
|
|
295
|
-
}}
|
|
364
|
+
ScrollableHandlerProps={scrollableHandlerProps}
|
|
296
365
|
>
|
|
297
366
|
{realtime === "true" && trainId && (
|
|
298
367
|
<RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
|
|
@@ -306,11 +375,7 @@ function RvfMobilityMap({
|
|
|
306
375
|
</Overlay>
|
|
307
376
|
|
|
308
377
|
{isExportMenuOpen && (
|
|
309
|
-
<Modal
|
|
310
|
-
onClose={() => {
|
|
311
|
-
setIsExportMenuOpen(false);
|
|
312
|
-
}}
|
|
313
|
-
>
|
|
378
|
+
<Modal onClose={onExportMenuClose}>
|
|
314
379
|
<RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
|
|
315
380
|
</Modal>
|
|
316
381
|
)}
|
|
@@ -4,9 +4,11 @@ import { memo } from "preact/compat";
|
|
|
4
4
|
import { useEffect, useMemo } from "preact/hooks";
|
|
5
5
|
|
|
6
6
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
7
8
|
|
|
8
9
|
function RvfPoisLayer(props: MaplibreStyleLayerOptions) {
|
|
9
10
|
const { baseLayer, map } = useMapContext();
|
|
11
|
+
const { setPoisLayer } = useRvfContext();
|
|
10
12
|
|
|
11
13
|
const layer = useMemo(() => {
|
|
12
14
|
if (!baseLayer) {
|
|
@@ -22,6 +24,10 @@ function RvfPoisLayer(props: MaplibreStyleLayerOptions) {
|
|
|
22
24
|
});
|
|
23
25
|
}, [baseLayer, props]);
|
|
24
26
|
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setPoisLayer(layer);
|
|
29
|
+
}, [layer, setPoisLayer]);
|
|
30
|
+
|
|
25
31
|
useEffect(() => {
|
|
26
32
|
if (!map || !layer) {
|
|
27
33
|
return;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JSX } from "preact";
|
|
2
|
+
|
|
3
|
+
export type RvfRadioButtonProps =
|
|
4
|
+
{} & JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
5
|
+
|
|
6
|
+
function RvfRadioButton(props: RvfRadioButtonProps) {
|
|
7
|
+
return (
|
|
8
|
+
<input
|
|
9
|
+
className="peer mr-2 size-[20px] rounded-full border-2 border-grey accent-red"
|
|
10
|
+
{...props}
|
|
11
|
+
type="radio"
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default RvfRadioButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfRadioButton";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import ArrowDown from "../icons/ArrowDown";
|
|
4
|
+
|
|
5
|
+
export type RvfSelectProps = {} & JSX.HTMLAttributes<HTMLSelectElement> &
|
|
6
|
+
PreactDOMAttributes;
|
|
7
|
+
|
|
8
|
+
function RvfSelect({ children, className, onChange }: RvfSelectProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="relative flex items-center text-grey">
|
|
11
|
+
<select
|
|
12
|
+
className={`min-w-12 appearance-none rounded-xl border border-grey p-2 text-base focus:outline-none ${className}`}
|
|
13
|
+
onChange={onChange}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</select>
|
|
17
|
+
<ArrowDown className="pointer-events-none absolute right-1" />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default RvfSelect;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfSelect";
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
8
|
+
|
|
9
|
+
function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
|
|
10
|
+
const { baseLayer, map } = useMapContext();
|
|
11
|
+
const { setSellingPointsLayer } = useRvfContext();
|
|
12
|
+
|
|
13
|
+
const layer = useMemo(() => {
|
|
14
|
+
if (!baseLayer) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return new MaplibreStyleLayer({
|
|
18
|
+
isQueryable: true,
|
|
19
|
+
layersFilter: ({ metadata }) => {
|
|
20
|
+
return metadata?.["general.filter"] === "selling_points";
|
|
21
|
+
},
|
|
22
|
+
maplibreLayer: baseLayer,
|
|
23
|
+
...(props || {}),
|
|
24
|
+
});
|
|
25
|
+
}, [baseLayer, props]);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setSellingPointsLayer(layer);
|
|
29
|
+
}, [layer, setSellingPointsLayer]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!map || !layer) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
map.addLayer(layer);
|
|
37
|
+
return () => {
|
|
38
|
+
map.removeLayer(layer);
|
|
39
|
+
};
|
|
40
|
+
}, [map, layer]);
|
|
41
|
+
|
|
42
|
+
return null; // <RegisterForSelectFeaturesOnClick />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default memo(RvfSellingPointsLayer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfSellingPointsLayer";
|
|
@@ -1,66 +1,111 @@
|
|
|
1
1
|
import type { Options } from "ol/layer/Group";
|
|
2
2
|
|
|
3
|
+
import { MaplibreStyleLayer } from "mobility-toolbox-js/ol";
|
|
4
|
+
import { Feature } from "ol";
|
|
5
|
+
import { Point } from "ol/geom";
|
|
3
6
|
import { Group, Vector } from "ol/layer";
|
|
7
|
+
import { Cluster } from "ol/source";
|
|
8
|
+
import VectorSource from "ol/source/Vector";
|
|
4
9
|
import { memo } from "preact/compat";
|
|
5
10
|
import { useEffect, useMemo } from "preact/hooks";
|
|
6
11
|
|
|
7
12
|
import createMobiDataBwWfsLayer from "../utils/createMobiDataBwWfsLayer";
|
|
13
|
+
import createFreeFloatMobilityLayer from "../utils/createSharedMobilityLayer";
|
|
8
14
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
15
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
export type GroupOptions = Options & Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
function RvfSharedMobilityLayerGroup(props: GroupOptions) {
|
|
20
|
+
const { baseLayer, map } = useMapContext();
|
|
21
|
+
const { setSharedMobilityLayerGroup } = useRvfContext();
|
|
12
22
|
|
|
13
23
|
const group = useMemo(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
24
|
+
if (!baseLayer) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sharingBicycle = new Group({
|
|
29
|
+
layers: [
|
|
30
|
+
new MaplibreStyleLayer({
|
|
31
|
+
isQueryable: true,
|
|
32
|
+
layersFilter: ({ metadata }) => {
|
|
33
|
+
return metadata?.["rvf.filter"] === "bike";
|
|
34
|
+
},
|
|
35
|
+
maplibreLayer: baseLayer,
|
|
36
|
+
}),
|
|
37
|
+
createMobiDataBwWfsLayer("sharing_stations_bicycle", "green"),
|
|
38
|
+
createFreeFloatMobilityLayer("sharing_vehicles", "green", "bike"),
|
|
39
|
+
],
|
|
40
|
+
title: "Fahrrad",
|
|
41
|
+
} as GroupOptions);
|
|
42
|
+
|
|
43
|
+
const sharingCar = new Group({
|
|
44
|
+
layers: [
|
|
45
|
+
new MaplibreStyleLayer({
|
|
46
|
+
isQueryable: true,
|
|
47
|
+
layersFilter: ({ metadata }) => {
|
|
48
|
+
return metadata?.["rvf.filter"] === "car";
|
|
49
|
+
},
|
|
50
|
+
maplibreLayer: baseLayer,
|
|
51
|
+
}),
|
|
52
|
+
createMobiDataBwWfsLayer("sharing_stations_car", "green"),
|
|
53
|
+
createFreeFloatMobilityLayer("sharing_vehicles", "green", "car"),
|
|
54
|
+
],
|
|
55
|
+
title: "Auto",
|
|
56
|
+
} as GroupOptions);
|
|
57
|
+
|
|
58
|
+
const sharingCargoBicycle = new Group({
|
|
45
59
|
layers: [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
new MaplibreStyleLayer({
|
|
61
|
+
isQueryable: true,
|
|
62
|
+
layersFilter: ({ metadata }) => {
|
|
63
|
+
return metadata?.["rvf.filter"] === "cargo_bike";
|
|
64
|
+
},
|
|
65
|
+
maplibreLayer: baseLayer,
|
|
66
|
+
}),
|
|
67
|
+
createMobiDataBwWfsLayer("sharing_stations_cargo_bicycle", "green"),
|
|
68
|
+
createFreeFloatMobilityLayer("sharing_vehicles", "green", "cargo_bike"),
|
|
51
69
|
],
|
|
70
|
+
title: "Cargobike",
|
|
71
|
+
} as GroupOptions);
|
|
72
|
+
|
|
73
|
+
const sharingScooter = new Group({
|
|
74
|
+
layers: [
|
|
75
|
+
new MaplibreStyleLayer({
|
|
76
|
+
isQueryable: true,
|
|
77
|
+
layersFilter: ({ metadata }) => {
|
|
78
|
+
return metadata?.["rvf.filter"] === "hitchhiking";
|
|
79
|
+
},
|
|
80
|
+
maplibreLayer: baseLayer,
|
|
81
|
+
}),
|
|
82
|
+
createMobiDataBwWfsLayer("sharing_stations_scooters_standing", "green"),
|
|
83
|
+
createFreeFloatMobilityLayer("sharing_vehicles", "green", "scooter"),
|
|
84
|
+
],
|
|
85
|
+
title: "E-Roller",
|
|
86
|
+
} as GroupOptions);
|
|
87
|
+
|
|
88
|
+
return new Group({
|
|
89
|
+
layers: [sharingBicycle, sharingCar, sharingCargoBicycle, sharingScooter],
|
|
52
90
|
...props,
|
|
53
91
|
});
|
|
54
|
-
}, [props]);
|
|
92
|
+
}, [baseLayer, props]);
|
|
55
93
|
|
|
56
94
|
// Reload features every minute
|
|
57
95
|
useEffect(() => {
|
|
58
96
|
const interval = window.setInterval(() => {
|
|
59
|
-
group.getLayers().forEach((layer:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
group.getLayers().forEach((layer: Group) => {
|
|
98
|
+
layer.getLayers().forEach((layer: MaplibreStyleLayer | Vector) => {
|
|
99
|
+
const source = layer.getSource();
|
|
100
|
+
source?.refresh();
|
|
101
|
+
|
|
102
|
+
// For cluster source
|
|
103
|
+
(
|
|
104
|
+
(source as Cluster<Feature<Point>>)?.getSource?.() as VectorSource<
|
|
105
|
+
Feature<Point>
|
|
106
|
+
>
|
|
107
|
+
)?.refresh();
|
|
108
|
+
});
|
|
64
109
|
});
|
|
65
110
|
}, 60000);
|
|
66
111
|
return () => {
|
|
@@ -68,6 +113,10 @@ function RvfSharedMobilityLayerGroup(props: Options & Record<string, unknown>) {
|
|
|
68
113
|
};
|
|
69
114
|
}, [group]);
|
|
70
115
|
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
setSharedMobilityLayerGroup(group);
|
|
118
|
+
}, [group, setSharedMobilityLayerGroup]);
|
|
119
|
+
|
|
71
120
|
useEffect(() => {
|
|
72
121
|
if (!map || !group) {
|
|
73
122
|
return;
|
|
@@ -42,8 +42,17 @@ function SingleClickListener() {
|
|
|
42
42
|
return feat.get("tralis_network")?.includes(tenant);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// Send all the features under the cursor
|
|
46
|
+
const features = evt.map.getFeaturesAtPixel(evt.pixel, {
|
|
47
|
+
layerFilter: (l) => {
|
|
48
|
+
return l.get("isQueryable");
|
|
49
|
+
},
|
|
50
|
+
}) as Feature[];
|
|
51
|
+
|
|
45
52
|
evt.map.getTargetElement().style.cursor =
|
|
46
|
-
realtimeFeature || stationFeature
|
|
53
|
+
realtimeFeature || stationFeature || features?.length
|
|
54
|
+
? "pointer"
|
|
55
|
+
: "default";
|
|
47
56
|
},
|
|
48
57
|
[realtimeLayer, stationsLayer, tenant],
|
|
49
58
|
);
|
|
@@ -101,8 +110,16 @@ function SingleClickListener() {
|
|
|
101
110
|
setSelectedFeature(null);
|
|
102
111
|
setSelectedFeatures([]);
|
|
103
112
|
} else {
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
const feats = [];
|
|
114
|
+
features.forEach((feature) => {
|
|
115
|
+
if (feature.get("features")) {
|
|
116
|
+
feats.push(...feature.get("features"));
|
|
117
|
+
} else {
|
|
118
|
+
feats.push(feature);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
setSelectedFeatures(feats);
|
|
122
|
+
setSelectedFeature(feats[0]);
|
|
106
123
|
}
|
|
107
124
|
},
|
|
108
125
|
[
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
8
|
+
|
|
9
|
+
function RvfTarifZonenLayer(props: MaplibreStyleLayerOptions) {
|
|
10
|
+
const { baseLayer, map } = useMapContext();
|
|
11
|
+
const { setTarifZonenLayer } = useRvfContext();
|
|
12
|
+
|
|
13
|
+
const layer = useMemo(() => {
|
|
14
|
+
if (!baseLayer) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return new MaplibreStyleLayer({
|
|
18
|
+
isQueryable: false,
|
|
19
|
+
layersFilter: ({ metadata, source, "source-layer": sourceLayer }) => {
|
|
20
|
+
return (
|
|
21
|
+
metadata?.["rvf.filter"] === "tarifzonen" ||
|
|
22
|
+
(source === "rvf" && sourceLayer === "tarifzonen")
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
maplibreLayer: baseLayer,
|
|
26
|
+
...(props || {}),
|
|
27
|
+
});
|
|
28
|
+
}, [baseLayer, props]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setTarifZonenLayer(layer);
|
|
32
|
+
}, [layer, setTarifZonenLayer]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!map || !layer) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
map.addLayer(layer);
|
|
40
|
+
return () => {
|
|
41
|
+
map.removeLayer(layer);
|
|
42
|
+
};
|
|
43
|
+
}, [map, layer]);
|
|
44
|
+
|
|
45
|
+
return null; // <RegisterForSelectFeaturesOnClick />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default memo(RvfTarifZonenLayer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfTarifZonenLayer";
|