@geops/rvf-mobility-web-component 0.1.15 → 0.1.17

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 (51) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/docutils.js +8 -2
  3. package/index.html +14 -2
  4. package/index.js +152 -78
  5. package/package.json +2 -1
  6. package/src/RealtimeLayer/RealtimeLayer.tsx +2 -0
  7. package/src/RvfExportMenu/RvfExportMenu.tsx +12 -1
  8. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +25 -4
  9. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/FloatingVehiclesDetails/FloatingVehiclesDetails.tsx +53 -0
  10. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/FloatingVehiclesDetails/index.tsx +1 -0
  11. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/RvfSharedMobilityDetails.tsx +123 -0
  12. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/StationDetails/StationDetails.tsx +32 -0
  13. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/StationDetails/index.tsx +1 -0
  14. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/index.tsx +1 -0
  15. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +2 -0
  16. package/src/RvfMobilityMap/RvfMobilityMap.tsx +123 -34
  17. package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +1 -1
  18. package/src/RvfPoisLayer/RvfPoisLayer.tsx +2 -0
  19. package/src/RvfSelectedFeatureHighlightLayer/RvfSelectedFeatureHighlightLayer.tsx +64 -0
  20. package/src/RvfSelectedFeatureHighlightLayer/index.tsx +1 -0
  21. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +2 -0
  22. package/src/RvfShare/RvfShare.tsx +1 -1
  23. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +59 -23
  24. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +2 -10
  25. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +2 -0
  26. package/src/RvfTopics/RvfTopics.tsx +40 -8
  27. package/src/StationsLayer/StationsLayer.tsx +2 -0
  28. package/src/icons/Bike/rvf_shared_bike.svg +2 -2
  29. package/src/icons/Car/rvf_shared_car.svg +3 -3
  30. package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +3 -3
  31. package/src/icons/Scooter/rvf_shared_scooter.svg +2 -2
  32. package/src/index.tsx +4 -0
  33. package/src/logos/callabike_logo.png +0 -0
  34. package/src/logos/flinkster_logo.png +0 -0
  35. package/src/logos/gruene_flotte_logo.png +0 -0
  36. package/src/logos/logo_frelo_web_rgb.png +0 -0
  37. package/src/logos/natur_energie_logo.png +0 -0
  38. package/src/logos/yoio_logo.png +0 -0
  39. package/src/logos/zeus_logo.png +0 -0
  40. package/src/utils/applyInitialLayerVisibility.ts +41 -0
  41. package/src/utils/constants.ts +63 -0
  42. package/src/utils/{createSharedMobilityLayer.ts → createFreeFloatMobilityLayer.ts} +41 -69
  43. package/src/utils/createMobiDataBwWfsLayer.ts +108 -67
  44. package/src/utils/exportPdf.ts +1 -4
  45. package/src/utils/getFeatureInformationTitle.ts +37 -0
  46. package/src/utils/getLayersAsFlatArray.ts +22 -0
  47. package/src/utils/getLinkByDevice.ts +23 -0
  48. package/src/utils/getPermalinkParameters.ts +27 -0
  49. package/src/utils/hooks/useInitialLayersVisiblity.tsx +28 -0
  50. package/src/utils/hooks/useMapContext.tsx +2 -0
  51. package/src/utils/hooks/useUpdatePermalink.tsx +44 -11
package/package.json CHANGED
@@ -2,12 +2,13 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.15",
5
+ "version": "0.1.17",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "jspdf": "^2.5.2",
11
+ "lodash.debounce": "^4.0.8",
11
12
  "maplibre-gl": "^4.7.1",
12
13
  "mobility-toolbox-js": "3.1.1-beta.0",
13
14
  "ol": "^10.3.1",
@@ -7,6 +7,7 @@ import { memo } from "preact/compat";
7
7
  import { useEffect, useMemo } from "preact/hooks";
8
8
 
9
9
  import centerOnVehicle from "../utils/centerOnVehicle";
10
+ import { RVF_LAYERS_NAMES } from "../utils/constants";
10
11
  import getDelayColorForVehicle from "../utils/getDelayColorForVehicle";
11
12
  import getDelayFontForVehicle from "../utils/getDelayFontForVehicle";
12
13
  import getDelayTextForVehicle from "../utils/getDelayTextForVehicle";
@@ -48,6 +49,7 @@ function RealtimeLayer(props: RealtimeLayerProps) {
48
49
  return mots.split(",") as RealtimeMot[];
49
50
  }
50
51
  : undefined,
52
+ name: RVF_LAYERS_NAMES.echtzeit,
51
53
  tenant,
52
54
  url: realtimeurl,
53
55
  zIndex: 1,
@@ -15,8 +15,10 @@ export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
15
15
 
16
16
  const formats = ["A4", "A3", "A1", "A0"];
17
17
 
18
+ let prevRealtimeLayerVisibility = false;
19
+
18
20
  function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
19
- const { map } = useMapContext();
21
+ const { map, realtimeLayer } = useMapContext();
20
22
  const [useMaxExtent, setUseMaxExtent] = useState(false);
21
23
  const [format, setFormat] = useState<string>(formats[0]);
22
24
  const checkboxId = useId();
@@ -64,12 +66,21 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
64
66
  { format },
65
67
  {
66
68
  onAfter: (map, layers) => {
69
+ if (
70
+ prevRealtimeLayerVisibility !== realtimeLayer.getVisible()
71
+ ) {
72
+ realtimeLayer.setVisible(prevRealtimeLayerVisibility);
73
+ }
67
74
  getAllLayers(layers).forEach((layer) => {
68
75
  layer.set(LAYER_PROP_IS_EXPORTING, false);
69
76
  });
70
77
  },
71
78
 
72
79
  onBefore: (map, layers) => {
80
+ prevRealtimeLayerVisibility = realtimeLayer.getVisible();
81
+ if (realtimeLayer.getVisible()) {
82
+ realtimeLayer.setVisible(false);
83
+ }
73
84
  getAllLayers(layers).forEach((layer) => {
74
85
  layer.set(LAYER_PROP_IS_EXPORTING, true);
75
86
  });
@@ -3,16 +3,28 @@ import type { JSX, PreactDOMAttributes } from "preact";
3
3
  import { memo } from "preact/compat";
4
4
 
5
5
  import useRvfContext from "../utils/hooks/useRvfContext";
6
+ import RvfSharedMobilityDetails from "./RvfSharedMobilityDetail";
6
7
 
7
8
  export type RvfFeatureDetailsProps = JSX.HTMLAttributes<HTMLDivElement> &
8
9
  PreactDOMAttributes;
9
10
 
11
+ const getIsSharedMobility = (selectedFeature): boolean => {
12
+ const id =
13
+ selectedFeature.getId() || selectedFeature.get("features")[0].getId();
14
+ if (typeof id === "number") {
15
+ return !!selectedFeature.get("category");
16
+ } else if (typeof id === "string") {
17
+ return id.includes("sharing_stations") || id.includes("Vehicle");
18
+ }
19
+ };
20
+
10
21
  function RvfFeatureDetails(props: RvfFeatureDetailsProps) {
11
22
  const { selectedFeature } = useRvfContext();
23
+ const isSharedMobility = getIsSharedMobility(selectedFeature);
12
24
 
13
- return (
14
- <div {...props}>
15
- {Object.entries(selectedFeature.getProperties()).map(([key, value]) => {
25
+ const showDefaultData = () => {
26
+ return Object.entries(selectedFeature.getProperties()).map(
27
+ ([key, value]) => {
16
28
  return (
17
29
  <div className="flex gap-2" key={key}>
18
30
  <span>
@@ -21,7 +33,16 @@ function RvfFeatureDetails(props: RvfFeatureDetailsProps) {
21
33
  <div>{value}</div>
22
34
  </div>
23
35
  );
24
- })}
36
+ },
37
+ );
38
+ };
39
+
40
+ return (
41
+ <div {...props}>
42
+ {isSharedMobility && (
43
+ <RvfSharedMobilityDetails selectedFeature={selectedFeature} />
44
+ )}
45
+ {!isSharedMobility && showDefaultData()}
25
46
  </div>
26
47
  );
27
48
  }
@@ -0,0 +1,53 @@
1
+ import { Feature } from "ol";
2
+
3
+ import getLinkByDevice from "../../../utils/getLinkByDevice";
4
+
5
+ export interface FloatingVehiclesDetailsProps {
6
+ features: Feature[];
7
+ }
8
+
9
+ function FloatingVehiclesDetails({ features }: FloatingVehiclesDetailsProps) {
10
+ const renderedDetails = features.map((feature, idx) => {
11
+ let vehicleNumber;
12
+ if (feature.get("feed_id") === "yoio_freiburg") {
13
+ vehicleNumber = feature.get("rental_uris_android").split("/").pop();
14
+ } else if (feature.get("feed_id") === "zeus_freiburg") {
15
+ const url = new URL(feature.get("rental_uris_web"));
16
+ vehicleNumber = new URLSearchParams(url.search).get("vehicle");
17
+ }
18
+
19
+ const scooterLink = getLinkByDevice(feature);
20
+ const availableDistanceString = feature
21
+ .get("current_range_meters")
22
+ .toLocaleString()
23
+ .replace(",", "'");
24
+
25
+ return (
26
+ <li className="p-1" key={idx}>
27
+ <a href={scooterLink} key={idx} rel="noreferrer" target="_blank">
28
+ {`${vehicleNumber} (${availableDistanceString} m)`}
29
+ </a>
30
+ </li>
31
+ );
32
+ });
33
+
34
+ return (
35
+ <div className="flex flex-col overflow-scroll">
36
+ {features.length} Fahrzeuge
37
+ <span className="text-lg text-grey">Fahrzeug mieten</span>
38
+ <ul className="h-screen list-disc overflow-scroll pl-4 underline underline-offset-2">
39
+ {renderedDetails}
40
+ </ul>
41
+ <a
42
+ className="mx-auto text-lg text-grey hover:text-red"
43
+ href={getLinkByDevice(features[0])}
44
+ rel="noreferrer"
45
+ target="_blank"
46
+ >
47
+ Über {features[0].get("feed_id").split("_")[0].toUpperCase()}
48
+ </a>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default FloatingVehiclesDetails;
@@ -0,0 +1 @@
1
+ export { default } from "./FloatingVehiclesDetails";
@@ -0,0 +1,123 @@
1
+ import { Feature } from "ol";
2
+ import { useEffect, useState } from "preact/hooks";
3
+
4
+ import callBike from "../../logos/callabike_logo.png";
5
+ import flinkster from "../../logos/flinkster_logo.png";
6
+ import grueneFlotteLogo from "../../logos/gruene_flotte_logo.png";
7
+ import lastenVeloLogo from "../../logos/lasten_velo_freiburg.png";
8
+ import freloLogo from "../../logos/logo_frelo_web_rgb.png";
9
+ import naturEnergieLogo from "../../logos/natur_energie_logo.png";
10
+ import yoioLogo from "../../logos/yoio_logo.png";
11
+ import zeusLogo from "../../logos/zeus_logo.png";
12
+ import { API_REQUEST_FEATURE_TYPE } from "../../utils/constants";
13
+ import FloatingVehiclesDetails from "./FloatingVehiclesDetails";
14
+ import StationDetails from "./StationDetails";
15
+
16
+ export interface RvfSharedMobilityDetailsProps {
17
+ selectedFeature: Feature;
18
+ }
19
+
20
+ const logos = {
21
+ callabike_ice: callBike,
22
+ flinkster_carsharing: flinkster,
23
+ "gruene-flotte_freiburg": grueneFlotteLogo,
24
+ lastenvelo_fr: lastenVeloLogo,
25
+ naturenergie_sharing: naturEnergieLogo,
26
+ nextbike_df: freloLogo,
27
+ yoio_freiburg: yoioLogo,
28
+ zeus_freiburg: zeusLogo,
29
+ };
30
+
31
+ function RvfSharedMobilityDetails({
32
+ selectedFeature,
33
+ }: RvfSharedMobilityDetailsProps) {
34
+ const [features, setFeatures] = useState([]);
35
+ const isStationDetails =
36
+ !!selectedFeature.get("categories") ||
37
+ !selectedFeature.get("features")?.length;
38
+ const isFloatingVehicle = !!selectedFeature.get("features")?.length;
39
+
40
+ useEffect(() => {
41
+ const extent = selectedFeature.getGeometry().getExtent();
42
+ extent[0] = extent[0] - 2;
43
+ extent[1] = extent[1] - 2;
44
+ extent[2] = extent[2] + 2;
45
+ extent[3] = extent[3] + 2;
46
+ const categoryName = selectedFeature.get("category")?.toLowerCase();
47
+ let featureType;
48
+ let abortController;
49
+
50
+ if (categoryName) {
51
+ if (categoryName === "bike sharing") {
52
+ featureType = API_REQUEST_FEATURE_TYPE.stations.bike;
53
+ } else if (categoryName === "car sharing") {
54
+ featureType = API_REQUEST_FEATURE_TYPE.stations.car;
55
+ } else if (categoryName === "cargo bike sharing") {
56
+ featureType = API_REQUEST_FEATURE_TYPE.stations.cargoBike;
57
+ }
58
+ abortController = new AbortController();
59
+
60
+ fetch(createRequestLink(featureType, extent), {
61
+ signal: new AbortController().signal,
62
+ })
63
+ .then((res) => {
64
+ return res.json();
65
+ })
66
+ .then((data) => {
67
+ if (data.features?.[0].properties) {
68
+ selectedFeature.setProperties(data.features[0].properties);
69
+ }
70
+ setFeatures([selectedFeature]);
71
+ })
72
+ .catch((err) => {
73
+ console.error(err);
74
+ });
75
+ } else {
76
+ if (isFloatingVehicle) {
77
+ setFeatures(selectedFeature.get("features"));
78
+ } else {
79
+ setFeatures([selectedFeature]);
80
+ }
81
+ }
82
+ return () => {
83
+ abortController?.abort();
84
+ };
85
+ }, [selectedFeature, isFloatingVehicle]);
86
+
87
+ const createRequestLink = (name, extent) => {
88
+ return (
89
+ "https://api.mobidata-bw.de/geoserver/MobiData-BW/" +
90
+ name +
91
+ "/ows" +
92
+ "?service=WFS&" +
93
+ "version=1.1.0&request=GetFeature&typename=" +
94
+ "MobiData-BW:" +
95
+ name +
96
+ "&" +
97
+ "outputFormat=application/json&srsname=EPSG:3857&" +
98
+ "bbox=" +
99
+ extent.join(",") +
100
+ ",EPSG:3857"
101
+ );
102
+ };
103
+
104
+ return (
105
+ <div className="flex flex-col overflow-scroll px-2 pt-2">
106
+ <div className="flex items-center text-xl text-grey">
107
+ <img
108
+ alt="logo"
109
+ className="max-w-24"
110
+ src={logos[features[0]?.get("feed_id")]}
111
+ />
112
+ </div>
113
+ {features.length && isStationDetails && (
114
+ <StationDetails feature={features[0]} />
115
+ )}
116
+ {!!features[0]?.get("form_factor") && isFloatingVehicle && (
117
+ <FloatingVehiclesDetails features={features} />
118
+ )}
119
+ </div>
120
+ );
121
+ }
122
+
123
+ export default RvfSharedMobilityDetails;
@@ -0,0 +1,32 @@
1
+ import { Feature } from "ol";
2
+ import { useMemo } from "preact/hooks";
3
+
4
+ import RvfButton from "../../../RvfButton";
5
+ import getLinkByDevice from "../../../utils/getLinkByDevice";
6
+ export interface StationDetailsProps {
7
+ feature: Feature;
8
+ }
9
+
10
+ function StationDetails({ feature }: StationDetailsProps) {
11
+ const link = useMemo(() => {
12
+ return getLinkByDevice(feature);
13
+ }, [feature]);
14
+
15
+ return (
16
+ <div className="flex flex-col">
17
+ {feature.get("num_vehicles_available")} Fahrzeuge
18
+ {link && (
19
+ <a
20
+ className={"my-12 flex items-center justify-center"}
21
+ href={link}
22
+ rel="noreferrer"
23
+ target="_blank"
24
+ >
25
+ <RvfButton>Jetzt buchen</RvfButton>
26
+ </a>
27
+ )}
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export default StationDetails;
@@ -0,0 +1 @@
1
+ export { default } from "./StationDetails";
@@ -0,0 +1 @@
1
+ export { default } from "./RvfSharedMobilityDetails";
@@ -3,6 +3,7 @@ import { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/Maplibr
3
3
  import { memo } from "preact/compat";
4
4
  import { useEffect, useMemo } from "preact/hooks";
5
5
 
6
+ import { RVF_LAYERS_NAMES } from "../utils/constants";
6
7
  import useMapContext from "../utils/hooks/useMapContext";
7
8
  import useRvfContext from "../utils/hooks/useRvfContext";
8
9
 
@@ -21,6 +22,7 @@ function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
21
22
  },
22
23
  maplibreLayer: baseLayer,
23
24
  minZoom: 10,
25
+ name: RVF_LAYERS_NAMES.liniennetz,
24
26
  ...(props || {}),
25
27
  });
26
28
  }, [baseLayer, props]);