@geops/rvf-mobility-web-component 0.1.10 → 0.1.12

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 (98) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/docutils.js +198 -0
  3. package/index.html +48 -217
  4. package/index.js +680 -87
  5. package/input.css +11 -1
  6. package/jest-setup.js +3 -2
  7. package/package.json +4 -3
  8. package/scripts/build.mjs +3 -2
  9. package/scripts/dev.mjs +2 -1
  10. package/search.html +38 -69
  11. package/src/BaseLayer/BaseLayer.tsx +20 -12
  12. package/src/FloatingMenu/FloatingMenu.tsx +42 -0
  13. package/src/FloatingMenu/index.tsx +1 -0
  14. package/src/GeolocationButton/GeolocationButton.tsx +6 -5
  15. package/src/Map/Map.tsx +1 -0
  16. package/src/MobilityMap/MobilityMap.tsx +10 -9
  17. package/src/MobilityMap/index.css +0 -13
  18. package/src/RealtimeLayer/RealtimeLayer.tsx +2 -3
  19. package/src/RvfButton/RvfButton.tsx +28 -21
  20. package/src/RvfCheckbox/RvfCheckbox.tsx +24 -0
  21. package/src/RvfCheckbox/index.tsx +1 -0
  22. package/src/RvfExportMenu/RvfExportMenu.tsx +103 -0
  23. package/src/RvfExportMenu/index.tsx +1 -0
  24. package/src/RvfExportMenuButton/RvfExportMenuButton.tsx +27 -0
  25. package/src/RvfExportMenuButton/index.tsx +1 -0
  26. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +29 -0
  27. package/src/RvfFeatureDetails/index.tsx +1 -0
  28. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +44 -0
  29. package/src/RvfFloatingMenu/index.tsx +1 -0
  30. package/src/RvfIconButton/RvfIconButton.tsx +35 -0
  31. package/src/RvfIconButton/index.tsx +1 -0
  32. package/src/RvfLayerTree/RvfLayerTree.tsx +41 -0
  33. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +120 -0
  34. package/src/RvfLayerTree/TreeItem/index.tsx +1 -0
  35. package/src/RvfLayerTree/index.tsx +1 -0
  36. package/src/RvfLayerTree/layersTreeContext.ts +4 -0
  37. package/src/RvfLayerTree/layersTreeReducer.ts +152 -0
  38. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +42 -0
  39. package/src/RvfLineNetworkPlanLayer/index.tsx +1 -0
  40. package/src/RvfMobilityMap/RvfMobilityMap.tsx +122 -83
  41. package/src/RvfMobilityMap/index.css +0 -13
  42. package/src/RvfModal/RvfModal.tsx +52 -0
  43. package/src/RvfModal/index.tsx +1 -0
  44. package/src/RvfPoisLayer/RvfPoisLayer.tsx +39 -0
  45. package/src/RvfPoisLayer/index.tsx +1 -0
  46. package/src/RvfRadioButton/RvfRadioButton.tsx +16 -0
  47. package/src/RvfRadioButton/index.tsx +1 -0
  48. package/src/RvfSelect/RvfSelect.tsx +22 -0
  49. package/src/RvfSelect/index.tsx +1 -0
  50. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +41 -0
  51. package/src/RvfSellingPointsLayer/index.tsx +1 -0
  52. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +100 -0
  53. package/src/RvfSharedMobilityLayerGroup/index.tsx +1 -0
  54. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +146 -0
  55. package/src/RvfSingleClickListener/index.tsx +1 -0
  56. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +41 -0
  57. package/src/RvfTarifZonenLayer/index.tsx +1 -0
  58. package/src/RvfTopics/RvfTopics.tsx +47 -0
  59. package/src/RvfTopics/index.tsx +1 -0
  60. package/src/RvfZoomButtons/RvfZoomButtons.tsx +36 -29
  61. package/src/Search/Search.tsx +11 -9
  62. package/src/SingleClickListener/index.tsx +1 -1
  63. package/src/StationsLayer/StationsLayer.tsx +0 -1
  64. package/src/StopsSearch/StopsSearch.tsx +38 -6
  65. package/src/icons/ArrowDown/ArrowDown.tsx +22 -0
  66. package/src/icons/ArrowDown/down-open.svg +7 -0
  67. package/src/icons/ArrowDown/index.tsx +1 -0
  68. package/src/icons/ArrowUp/ArrowUp.tsx +22 -0
  69. package/src/icons/ArrowUp/index.tsx +1 -0
  70. package/src/icons/ArrowUp/up-open.svg +7 -0
  71. package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +19 -0
  72. package/src/icons/Cancel/Cancel.tsx +21 -0
  73. package/src/icons/Cancel/cancel.svg +7 -0
  74. package/src/icons/Cancel/index.tsx +1 -0
  75. package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +14 -0
  76. package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +27 -0
  77. package/src/icons/DownOpen/DownOpen.tsx +24 -0
  78. package/src/icons/DownOpen/down-open.svg +7 -0
  79. package/src/icons/DownOpen/index.tsx +1 -0
  80. package/src/icons/Download/Download.tsx +20 -0
  81. package/src/icons/Download/download.svg +15 -0
  82. package/src/icons/Download/index.tsx +1 -0
  83. package/src/icons/Elevator/Elevator.tsx +1 -1
  84. package/src/icons/Menu/Menu.tsx +32 -0
  85. package/src/icons/Menu/index.tsx +1 -0
  86. package/src/icons/Menu/menu.svg +9 -0
  87. package/src/icons/Ok/ok-grey.svg +7 -0
  88. package/src/icons/Ok/ok.svg +4 -0
  89. package/src/icons/Scooter/scooter.svg +10 -0
  90. package/src/utils/constants.ts +9 -0
  91. package/src/utils/createMobiDataBwWfsLayer.ts +120 -0
  92. package/src/utils/createSharedMobilityLayer.ts +165 -0
  93. package/src/utils/exportPdf.ts +657 -0
  94. package/src/utils/hooks/useRvfContext.tsx +37 -0
  95. package/src/utils/hooks/useUpdatePermalink.tsx +2 -9
  96. package/tailwind.config.mjs +41 -19
  97. package/src/RvfSharedMobilityLayer/RvfSharedMobilityLayer.tsx +0 -147
  98. package/src/RvfSharedMobilityLayer/index.tsx +0 -1
@@ -9,10 +9,9 @@ import {
9
9
  RealtimeStopSequence,
10
10
  RealtimeTrainId,
11
11
  } from "mobility-toolbox-js/types";
12
- import { Map as OlMap } from "ol";
13
- import { transformExtent } from "ol/proj";
12
+ import { Feature, Map as OlMap } from "ol";
14
13
  import { memo } from "preact/compat";
15
- import { useEffect, useMemo, useState } from "preact/hooks";
14
+ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
16
15
 
17
16
  import BaseLayer from "../BaseLayer";
18
17
  import Copyright from "../Copyright";
@@ -23,33 +22,46 @@ import NotificationLayer from "../NotificationLayer";
23
22
  import Overlay from "../Overlay";
24
23
  import RealtimeLayer from "../RealtimeLayer";
25
24
  import RouteSchedule from "../RouteSchedule";
26
- import RvfSharedMobilityLayer from "../RvfSharedMobilityLayer";
25
+ import RvfExportMenu from "../RvfExportMenu";
26
+ import RvfExportMenuButton from "../RvfExportMenuButton";
27
+ import RvfFeatureDetails from "../RvfFeatureDetails";
28
+ import RvfFloatingMenu from "../RvfFloatingMenu";
29
+ // 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
30
+ import RvfLineNetworkPlanLayer from "../RvfLineNetworkPlanLayer";
31
+ import Modal from "../RvfModal";
32
+ import RvfPoisLayer from "../RvfPoisLayer";
33
+ import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
34
+ import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
35
+ import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
36
+ import Topics from "../RvfTopics";
27
37
  import RvfZoomButtons from "../RvfZoomButtons";
28
38
  import ScaleLine from "../ScaleLine";
29
39
  import Search from "../Search";
30
- import SingleClickListener from "../SingleClickListener/SingleClickListener";
40
+ import SingleClickListener from "../SingleClickListener";
31
41
  import Station from "../Station";
32
42
  import StationsLayer from "../StationsLayer";
33
43
  // @ts-expect-error bad type definition
34
44
  import tailwind from "../style.css";
45
+ import { RVF_EXTENT_3857 } from "../utils/constants";
35
46
  import { I18nContext } from "../utils/hooks/useI18n";
36
47
  import { MapContext } from "../utils/hooks/useMapContext";
48
+ import { RvfContext } from "../utils/hooks/useRvfContext";
37
49
  import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
38
50
  import i18n from "../utils/i18n";
39
51
  import MobilityEvent from "../utils/MobilityEvent";
40
52
  // @ts-expect-error bad type definition
41
53
  import style from "./index.css";
42
- // 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
43
54
 
44
55
  export type RvfMobilityMapProps = {} & MobilityMapProps;
45
56
 
46
- const rvfExtent = [7.5, 47.7, 8.45, 48.4];
47
- const rvfExtentTransformed = transformExtent(
48
- rvfExtent,
49
- "EPSG:4326",
50
- "EPSG:3857",
51
- );
52
- const bbox = rvfExtentTransformed.join(",");
57
+ const bbox = RVF_EXTENT_3857.join(",");
58
+
59
+ const baseLayerProps = {
60
+ mapLibreOptions: {
61
+ maxCanvasSize: [20000, 20000], // remove 4096 limitations
62
+ preserveDrawingBuffer: true,
63
+ },
64
+ };
53
65
 
54
66
  function RvfMobilityMap({
55
67
  apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
@@ -59,7 +71,7 @@ function RvfMobilityMap({
59
71
  geolocation = "true",
60
72
  mapsurl = "https://maps.geops.io",
61
73
  maxextent = bbox,
62
- maxzoom = null,
74
+ maxzoom = "20",
63
75
  minzoom = null,
64
76
  mots = null,
65
77
  notification = "true",
@@ -74,6 +86,7 @@ function RvfMobilityMap({
74
86
  tenant = null,
75
87
  zoom = null,
76
88
  }: RvfMobilityMapProps) {
89
+ const eventNodeRef = useRef<HTMLDivElement>();
77
90
  const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
78
91
  const [isFollowing, setIsFollowing] = useState(false);
79
92
  const [isTracking, setIsTracking] = useState(false);
@@ -84,10 +97,14 @@ function RvfMobilityMap({
84
97
  const [map, setMap] = useState<OlMap>();
85
98
  const [stationId, setStationId] = useState<RealtimeStationId>();
86
99
  const [trainId, setTrainId] = useState<RealtimeTrainId>();
100
+ const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
101
+ const [selectedFeature, setSelectedFeature] = useState<Feature>();
102
+ const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
103
+ const [isLayerTreeOpen, setIsLayerTreeOpen] = useState<boolean>(false);
87
104
 
88
105
  // TODO: this should be removed. The parent application should be responsible to do this
89
106
  // or we should find something that fit more usecases
90
- const { x, y, z } = useUpdatePermalink(map, permalink === "true");
107
+ useUpdatePermalink(map, permalink === "true");
91
108
 
92
109
  const mapContextValue = useMemo(() => {
93
110
  return {
@@ -165,10 +182,10 @@ function RvfMobilityMap({
165
182
  ]);
166
183
 
167
184
  useEffect(() => {
168
- dispatchEvent(
185
+ eventNodeRef.current?.dispatchEvent(
169
186
  new MobilityEvent<RvfMobilityMapProps>("mwc:attribute", {
170
187
  baselayer,
171
- center: x && y ? `${x},${y}` : center,
188
+ center,
172
189
  extent,
173
190
  geolocation,
174
191
  mapsurl,
@@ -184,7 +201,7 @@ function RvfMobilityMap({
184
201
  realtimeurl,
185
202
  search,
186
203
  tenant,
187
- zoom: z || zoom,
204
+ zoom,
188
205
  }),
189
206
  );
190
207
  }, [
@@ -204,85 +221,107 @@ function RvfMobilityMap({
204
221
  search,
205
222
  tenant,
206
223
  zoom,
207
- x,
208
- y,
209
- z,
210
224
  extent,
211
225
  maxextent,
212
226
  ]);
213
227
 
228
+ const rvfContextValue = useMemo(() => {
229
+ return {
230
+ isExportMenuOpen,
231
+ isLayerTreeOpen,
232
+ selectedFeature,
233
+ selectedFeatures,
234
+ setIsExportMenuOpen,
235
+ setIsLayerTreeOpen,
236
+ setSelectedFeature,
237
+ setSelectedFeatures,
238
+ };
239
+ }, [isExportMenuOpen, selectedFeature, selectedFeatures, isLayerTreeOpen]);
240
+
214
241
  return (
215
242
  <I18nContext.Provider value={i18n}>
216
243
  <style>{tailwind}</style>
217
244
  <style>{style}</style>
218
245
  <MapContext.Provider value={mapContextValue}>
219
- <div className="relative size-full border font-sans @container/main">
220
- <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
221
- <Map className="relative flex-1 overflow-visible ">
222
- <BaseLayer />
223
- <SingleClickListener />
224
- {realtime === "true" && <RealtimeLayer />}
225
- {tenant && <StationsLayer />}
226
- {notification === "true" && <NotificationLayer />}
227
- <RvfSharedMobilityLayer
228
- color="red"
229
- name="MobiData-BW:sharing_stations_bicycle"
230
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_bicycle/ows"
231
- />
232
- <RvfSharedMobilityLayer
233
- color="blue"
234
- name="MobiData-BW:sharing_stations_cargo_bicycle"
235
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_cargo_bicycle/ows"
236
- />
237
- <RvfSharedMobilityLayer
238
- color="green"
239
- name="MobiData-BW:sharing_stations_car"
240
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_car/ows"
241
- />
242
- <RvfSharedMobilityLayer
243
- color="orange"
244
- name="MobiData-BW:sharing_stations_scooters_standing"
245
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_scooters_standing/ows"
246
- />
247
- <RvfSharedMobilityLayer
248
- color="orange"
249
- minZoom={14.999}
250
- name="MobiData-BW:sharing_vehicles"
251
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_vehicles/ows"
252
- />
246
+ <RvfContext.Provider value={rvfContextValue}>
247
+ <div
248
+ className="relative size-full border font-sans @container/main"
249
+ ref={eventNodeRef}
250
+ style={{ fontSize: 16 }}
251
+ >
252
+ <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
253
+ <Map className="relative flex-1 overflow-visible ">
254
+ <RvfFloatingMenu
255
+ isOpen={isLayerTreeOpen}
256
+ onClick={() => {
257
+ setIsLayerTreeOpen(!isLayerTreeOpen);
258
+ }}
259
+ title="Kartendaten"
260
+ >
261
+ <Topics className={"w-full px-2"} />
262
+ </RvfFloatingMenu>
263
+ <BaseLayer {...baseLayerProps} isNotInLayerTree />
264
+ <SingleClickListener />
253
265
 
254
- <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
255
- <ScaleLine className="bg-slate-50/70" />
256
- <Copyright className="bg-slate-50/70" />
257
- </div>
258
- <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
259
- {geolocation === "true" && <GeolocationButton />}
260
- </div>
261
- {search === "true" && (
262
- <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
263
- <Search />
266
+ {realtime === "true" && <RealtimeLayer title="Echtzeit" />}
267
+ {tenant && <StationsLayer />}
268
+ {notification === "true" && <NotificationLayer />}
269
+ <RvfSellingPointsLayer title="Verkaufsstellen" />
270
+ <RvfLineNetworkPlanLayer title="Liniennetz" />
271
+ <RvfTarifZonenLayer title="Tarifzonen" />
272
+ <RvfPoisLayer title="POIs" />
273
+ <RvfSharedMobilityLayerGroup title="Shared Mobility" />
274
+
275
+ <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
276
+ <ScaleLine className="bg-slate-50/70" />
277
+ <Copyright className="bg-slate-50/70" />
264
278
  </div>
265
- )}
266
- <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
267
- <RvfZoomButtons />
268
- </div>
269
- </Map>
270
279
 
271
- <Overlay
272
- className={"z-50"}
273
- ScrollableHandlerProps={{
274
- style: { width: "calc(100% - 60px)" },
275
- }}
276
- >
277
- {realtime === "true" && trainId && (
278
- <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
279
- )}
280
- {tenant && stationId && (
281
- <Station className="relative overflow-y-auto overflow-x-hidden" />
280
+ <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
281
+ {geolocation === "true" && <GeolocationButton />}
282
+ <RvfExportMenuButton />
283
+ </div>
284
+
285
+ {search === "true" && (
286
+ <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
287
+ <Search />
288
+ </div>
289
+ )}
290
+
291
+ <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
292
+ <RvfZoomButtons />
293
+ </div>
294
+ </Map>
295
+
296
+ <Overlay
297
+ className={"z-50"}
298
+ ScrollableHandlerProps={{
299
+ style: { width: "calc(100% - 60px)" },
300
+ }}
301
+ >
302
+ {realtime === "true" && trainId && (
303
+ <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
304
+ )}
305
+ {tenant && stationId && (
306
+ <Station className="relative overflow-y-auto overflow-x-hidden" />
307
+ )}
308
+ {selectedFeature && (
309
+ <RvfFeatureDetails className="relative overflow-y-auto overflow-x-hidden" />
310
+ )}
311
+ </Overlay>
312
+
313
+ {isExportMenuOpen && (
314
+ <Modal
315
+ onClose={() => {
316
+ setIsExportMenuOpen(false);
317
+ }}
318
+ >
319
+ <RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
320
+ </Modal>
282
321
  )}
283
- </Overlay>
322
+ </div>
284
323
  </div>
285
- </div>
324
+ </RvfContext.Provider>
286
325
  </MapContext.Provider>
287
326
  </I18nContext.Provider>
288
327
  );
@@ -1,13 +0,0 @@
1
- ::-webkit-scrollbar {
2
- width: 3px;
3
- height: 3px;
4
- }
5
-
6
- ::-webkit-scrollbar-thumb {
7
- background: lightgray;
8
- z-index: 5;
9
- }
10
-
11
- ::-webkit-scrollbar-track {
12
- background: transparent;
13
- }
@@ -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,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 h-inputControl w-inputControl 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,41 @@
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 RvfSellingPointsLayer(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, source, "source-layer": sourceLayer }) => {
17
+ return (
18
+ metadata?.["rvf.filter"] === "selling_points" ||
19
+ (source === "rvf" && sourceLayer === "selling_points")
20
+ );
21
+ },
22
+ maplibreLayer: baseLayer,
23
+ ...(props || {}),
24
+ });
25
+ }, [baseLayer, props]);
26
+
27
+ useEffect(() => {
28
+ if (!map || !layer) {
29
+ return;
30
+ }
31
+
32
+ map.addLayer(layer);
33
+ return () => {
34
+ map.removeLayer(layer);
35
+ };
36
+ }, [map, layer]);
37
+
38
+ return null; // <RegisterForSelectFeaturesOnClick />;
39
+ }
40
+
41
+ export default memo(RvfSellingPointsLayer);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfSellingPointsLayer";
@@ -0,0 +1,100 @@
1
+ import type { Options } from "ol/layer/Group";
2
+
3
+ import { Feature } from "ol";
4
+ import { Point } from "ol/geom";
5
+ import { Group, Vector } from "ol/layer";
6
+ import { Cluster } from "ol/source";
7
+ import VectorSource from "ol/source/Vector";
8
+ import { memo } from "preact/compat";
9
+ import { useEffect, useMemo } from "preact/hooks";
10
+
11
+ import createMobiDataBwWfsLayer from "../utils/createMobiDataBwWfsLayer";
12
+ import createSharedMobilityLayer from "../utils/createSharedMobilityLayer";
13
+ import useMapContext from "../utils/hooks/useMapContext";
14
+
15
+ function RvfSharedMobilityLayerGroup(props: Options & Record<string, unknown>) {
16
+ const { map } = useMapContext();
17
+
18
+ const group = useMemo(() => {
19
+ const sharingStationsBicycle = createMobiDataBwWfsLayer(
20
+ "sharing_stations_bicycle",
21
+ "green",
22
+ );
23
+ sharingStationsBicycle.set("title", "Stations Bicycle");
24
+
25
+ const sharingStationsCar = createMobiDataBwWfsLayer(
26
+ "sharing_stations_car",
27
+ "green",
28
+ );
29
+ sharingStationsCar.set("title", "Stations Car");
30
+
31
+ const sharingStationsCargoBicycle = createMobiDataBwWfsLayer(
32
+ "sharing_stations_cargo_bicycle",
33
+ "green",
34
+ );
35
+ sharingStationsCargoBicycle.set("title", "Stations Cargo Bicycle");
36
+
37
+ const sharingStationsScooterStanding = createMobiDataBwWfsLayer(
38
+ "sharing_stations_scooters_standing",
39
+ "purple",
40
+ );
41
+ sharingStationsScooterStanding.set("title", "Stations Scooter");
42
+
43
+ const sharingVehicles = createSharedMobilityLayer(
44
+ "sharing_vehicles",
45
+ "green",
46
+ );
47
+ sharingVehicles.set("title", "Free Floating");
48
+
49
+ return new Group({
50
+ layers: [
51
+ sharingStationsBicycle,
52
+ sharingStationsCar,
53
+ sharingStationsCargoBicycle,
54
+ sharingStationsScooterStanding,
55
+ sharingVehicles,
56
+ ],
57
+ ...props,
58
+ });
59
+ }, [props]);
60
+
61
+ // Reload features every minute
62
+ useEffect(() => {
63
+ const interval = window.setInterval(() => {
64
+ group.getLayers().forEach((layer: Vector) => {
65
+ // @ts-expect-error - private property
66
+ layer.getSource().loadedExtentsRtree_.clear();
67
+ layer.getSource().clear(true);
68
+ layer.getSource().changed();
69
+
70
+ // For cluster source
71
+ (
72
+ (
73
+ layer.getSource() as Cluster<Feature<Point>>
74
+ )?.getSource?.() as VectorSource<Feature<Point>>
75
+ )?.refresh();
76
+ });
77
+ }, 60000);
78
+ return () => {
79
+ window.clearInterval(interval);
80
+ };
81
+ }, [group]);
82
+
83
+ useEffect(() => {
84
+ if (!map || !group) {
85
+ return;
86
+ }
87
+ map.on("moveend", () => {
88
+ console.log("ZOOM", map.getView().getZoom());
89
+ });
90
+
91
+ map.addLayer(group);
92
+
93
+ return () => {
94
+ map.removeLayer(group);
95
+ };
96
+ });
97
+
98
+ return null;
99
+ }
100
+ export default memo(RvfSharedMobilityLayerGroup);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfSharedMobilityLayerGroup";