@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/docutils.js +9 -9
  3. package/index.html +1 -1
  4. package/index.js +332 -263
  5. package/input.css +2 -8
  6. package/package.json +15 -15
  7. package/scripts/build.mjs +3 -3
  8. package/scripts/dev.mjs +3 -1
  9. package/src/BaseLayer/BaseLayer.tsx +20 -12
  10. package/src/RealtimeLayer/RealtimeLayer.tsx +1 -2
  11. package/src/RouteSchedule/RouteSchedule.tsx +3 -1
  12. package/src/RouteStop/RouteStop.tsx +1 -1
  13. package/src/RvfButton/RvfButton.tsx +2 -2
  14. package/src/RvfCheckbox/RvfCheckbox.tsx +25 -0
  15. package/src/RvfCheckbox/index.tsx +1 -0
  16. package/src/RvfExportMenu/RvfExportMenu.tsx +31 -11
  17. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +44 -0
  18. package/src/RvfFloatingMenu/index.tsx +1 -0
  19. package/src/RvfIconButton/RvfIconButton.tsx +1 -1
  20. package/src/{LayerTree/LayerTree.tsx → RvfLayerTree/RvfLayerTree.tsx} +11 -18
  21. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +130 -0
  22. package/src/RvfLayerTree/index.tsx +1 -0
  23. package/src/{LayerTree → RvfLayerTree}/layersTreeReducer.ts +1 -4
  24. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +49 -0
  25. package/src/RvfLineNetworkPlanLayer/index.tsx +1 -0
  26. package/src/RvfMobilityMap/RvfMobilityMap.tsx +100 -35
  27. package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
  28. package/src/RvfRadioButton/RvfRadioButton.tsx +16 -0
  29. package/src/RvfRadioButton/index.tsx +1 -0
  30. package/src/RvfSelect/RvfSelect.tsx +22 -0
  31. package/src/RvfSelect/index.tsx +1 -0
  32. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +45 -0
  33. package/src/RvfSellingPointsLayer/index.tsx +1 -0
  34. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +93 -44
  35. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +20 -3
  36. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +48 -0
  37. package/src/RvfTarifZonenLayer/index.tsx +1 -0
  38. package/src/RvfTopics/RvfTopics.tsx +44 -0
  39. package/src/RvfTopics/index.tsx +1 -0
  40. package/src/icons/ArrowDown/ArrowDown.tsx +22 -0
  41. package/src/icons/ArrowDown/down-open.svg +7 -0
  42. package/src/icons/ArrowDown/index.tsx +1 -0
  43. package/src/icons/ArrowUp/ArrowUp.tsx +22 -0
  44. package/src/icons/ArrowUp/index.tsx +1 -0
  45. package/src/icons/ArrowUp/up-open.svg +7 -0
  46. package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +19 -0
  47. package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +14 -0
  48. package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +27 -0
  49. package/src/icons/DownOpen/DownOpen.tsx +24 -0
  50. package/src/icons/DownOpen/down-open.svg +7 -0
  51. package/src/icons/DownOpen/index.tsx +1 -0
  52. package/src/icons/Ok/ok-grey.svg +7 -0
  53. package/src/icons/Ok/ok.svg +4 -0
  54. package/src/icons/Scooter/scooter.svg +10 -0
  55. package/src/utils/constants.ts +2 -0
  56. package/src/utils/createMobiDataBwWfsLayer.ts +31 -23
  57. package/src/utils/createSharedMobilityLayer.ts +176 -0
  58. package/src/utils/exportPdf.ts +20 -29
  59. package/src/utils/getAllLayers.ts +25 -0
  60. package/src/utils/hooks/useRvfContext.tsx +33 -0
  61. package/tailwind.config.mjs +31 -50
  62. package/src/LayerTree/TreeItem/TreeItem.tsx +0 -145
  63. package/src/LayerTree/TreeItemContainer/TreeItemContainer.tsx +0 -16
  64. package/src/LayerTree/TreeItemContainer/index.tsx +0 -1
  65. package/src/LayerTree/index.tsx +0 -1
  66. package/src/TopicMenu/TopicMenu.tsx +0 -143
  67. package/src/TopicMenu/index.tsx +0 -1
  68. /package/src/{LayerTree → RvfLayerTree}/TreeItem/index.tsx +0 -0
  69. /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 { useEffect, useMemo, useRef, useState } from "preact/hooks";
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&notificationat=2024-01-25T22%3A59%3A00Z
31
- import RvfIconButton from "../RvfIconButton";
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("&nbsp;|&nbsp;");
301
+ },
234
302
  };
235
- }, [isExportMenuOpen, selectedFeature, selectedFeatures]);
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} isNotInLayerTree />
318
+ <BaseLayer {...baseLayerProps} />
250
319
  <SingleClickListener />
251
320
 
252
- {realtime === "true" && <RealtimeLayer title="Realtime data" />}
321
+ {realtime === "true" && <RealtimeLayer title="Echtzeit" />}
253
322
  {tenant && <StationsLayer />}
254
323
  {notification === "true" && <NotificationLayer />}
255
- {/* <RvfLnpLayer />
256
- <RvfVerkaufStellenLayer />
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 className="bg-slate-50/70" />
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
- function RvfSharedMobilityLayerGroup(props: Options & Record<string, unknown>) {
11
- const { map } = useMapContext();
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
- 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({
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
- sharingStationsBicycle,
47
- sharingStationsCar,
48
- sharingStationsCargoBicycle,
49
- sharingStationsScooterStanding,
50
- sharingVehicles,
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: Vector) => {
60
- // @ts-expect-error - private property
61
- layer.getSource().loadedExtentsRtree_.clear();
62
- layer.getSource().clear(true);
63
- layer.getSource().changed();
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 ? "pointer" : "default";
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
- setSelectedFeatures(features);
105
- setSelectedFeature(features[0]);
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";