@geops/rvf-mobility-web-component 0.1.15 → 0.1.16
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 +29 -0
- package/docutils.js +8 -2
- package/index.html +14 -2
- package/index.js +162 -75
- package/package.json +2 -1
- package/src/RealtimeLayer/RealtimeLayer.tsx +2 -0
- package/src/RvfExportMenu/RvfExportMenu.tsx +12 -1
- package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +25 -4
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/FloatingVehiclesDetails/FloatingVehiclesDetails.tsx +53 -0
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/FloatingVehiclesDetails/index.tsx +1 -0
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/RvfSharedMobilityDetails.tsx +129 -0
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/StationDetails/StationDetails.tsx +24 -0
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/StationDetails/index.tsx +1 -0
- package/src/RvfFeatureDetails/RvfSharedMobilityDetail/index.tsx +1 -0
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +2 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +107 -26
- package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +1 -1
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +2 -0
- package/src/RvfSelectedFeatureHighlightLayer/RvfSelectedFeatureHighlightLayer.tsx +64 -0
- package/src/RvfSelectedFeatureHighlightLayer/index.tsx +1 -0
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +2 -0
- package/src/RvfShare/RvfShare.tsx +1 -1
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +50 -18
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +2 -10
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +2 -0
- package/src/RvfTopics/RvfTopics.tsx +40 -8
- package/src/StationsLayer/StationsLayer.tsx +2 -0
- package/src/icons/Bike/rvf_shared_bike.svg +2 -2
- package/src/icons/Car/rvf_shared_car.svg +3 -3
- package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +3 -3
- package/src/icons/Scooter/rvf_shared_scooter.svg +2 -2
- package/src/index.tsx +4 -0
- package/src/logos/callabike_logo.png +0 -0
- package/src/logos/flinkster_logo.png +0 -0
- package/src/logos/gruene_flotte_logo.png +0 -0
- package/src/logos/logo_frelo_web_rgb.png +0 -0
- package/src/logos/natur_energie_logo.png +0 -0
- package/src/logos/yoio_logo.png +0 -0
- package/src/logos/zeus_logo.png +0 -0
- package/src/utils/applyInitialLayerVisibility.ts +41 -0
- package/src/utils/constants.ts +30 -0
- package/src/utils/{createSharedMobilityLayer.ts → createFreeFloatMobilityLayer.ts} +41 -69
- package/src/utils/createMobiDataBwWfsLayer.ts +108 -67
- package/src/utils/exportPdf.ts +1 -4
- package/src/utils/getLayersAsFlatArray.ts +22 -0
- package/src/utils/getLinkByDevice.ts +28 -0
- package/src/utils/getPermalinkParameters.ts +27 -0
- package/src/utils/hooks/useInitialLayersVisiblity.tsx +28 -0
- package/src/utils/hooks/useMapContext.tsx +2 -0
- 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.
|
|
5
|
+
"version": "0.1.16",
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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,129 @@
|
|
|
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
|
+
const categories = {
|
|
32
|
+
callabike_ice: "Bike Sharing",
|
|
33
|
+
flinkster_carsharing: "Car Sharing",
|
|
34
|
+
"gruene-flotte_freiburg": "Car Sharing",
|
|
35
|
+
lastenvelo_fr: "Cargo Bike Sharing",
|
|
36
|
+
naturenergie_sharing: "Car Sharing",
|
|
37
|
+
nextbike_df: "Bike Sharing",
|
|
38
|
+
yoio_freiburg: "E-Scooter",
|
|
39
|
+
zeus_freiburg: "E-Scooter",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function RvfSharedMobilityDetails({
|
|
43
|
+
selectedFeature,
|
|
44
|
+
}: RvfSharedMobilityDetailsProps) {
|
|
45
|
+
const [features, setFeatures] = useState([]);
|
|
46
|
+
const isStationDetails =
|
|
47
|
+
!!selectedFeature.get("categories") ||
|
|
48
|
+
!selectedFeature.get("features")?.length;
|
|
49
|
+
const isFloatingVehicle = !!selectedFeature.get("features")?.length;
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const extent = selectedFeature.getGeometry().getExtent();
|
|
53
|
+
extent[0] = extent[0] - 2;
|
|
54
|
+
extent[1] = extent[1] - 2;
|
|
55
|
+
extent[2] = extent[2] + 2;
|
|
56
|
+
extent[3] = extent[3] + 2;
|
|
57
|
+
const categoryName = selectedFeature.get("category")?.toLowerCase();
|
|
58
|
+
let featureType;
|
|
59
|
+
|
|
60
|
+
if (categoryName) {
|
|
61
|
+
if (categoryName === "bike sharing") {
|
|
62
|
+
featureType = API_REQUEST_FEATURE_TYPE.stations.bike;
|
|
63
|
+
} else if (categoryName === "car sharing") {
|
|
64
|
+
featureType = API_REQUEST_FEATURE_TYPE.stations.car;
|
|
65
|
+
} else if (categoryName === "cargo bike sharing") {
|
|
66
|
+
featureType = API_REQUEST_FEATURE_TYPE.stations.cargoBike;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fetch(createRequestLink(featureType, extent))
|
|
70
|
+
.then((res) => {
|
|
71
|
+
return res.json();
|
|
72
|
+
})
|
|
73
|
+
.then((data) => {
|
|
74
|
+
selectedFeature.setProperties(data.features[0].properties);
|
|
75
|
+
setFeatures([selectedFeature]);
|
|
76
|
+
})
|
|
77
|
+
.catch((err) => {
|
|
78
|
+
console.error(err);
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
if (isFloatingVehicle) {
|
|
82
|
+
setFeatures(selectedFeature.get("features"));
|
|
83
|
+
} else {
|
|
84
|
+
setFeatures([selectedFeature]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, [selectedFeature, isFloatingVehicle]);
|
|
88
|
+
|
|
89
|
+
const createRequestLink = (name, extent) => {
|
|
90
|
+
return (
|
|
91
|
+
"https://api.mobidata-bw.de/geoserver/MobiData-BW/" +
|
|
92
|
+
name +
|
|
93
|
+
"/ows" +
|
|
94
|
+
"?service=WFS&" +
|
|
95
|
+
"version=1.1.0&request=GetFeature&typename=" +
|
|
96
|
+
"MobiData-BW:" +
|
|
97
|
+
name +
|
|
98
|
+
"&" +
|
|
99
|
+
"outputFormat=application/json&srsname=EPSG:3857&" +
|
|
100
|
+
"bbox=" +
|
|
101
|
+
extent.join(",") +
|
|
102
|
+
",EPSG:3857"
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="flex flex-col overflow-scroll px-2 pt-2">
|
|
108
|
+
<div className="flex items-center text-xl text-grey">
|
|
109
|
+
<img
|
|
110
|
+
alt="logo"
|
|
111
|
+
className="max-w-24"
|
|
112
|
+
src={logos[features[0]?.get("feed_id")]}
|
|
113
|
+
/>
|
|
114
|
+
<span className="ml-4">
|
|
115
|
+
{selectedFeature.get("category") ||
|
|
116
|
+
categories[features[0]?.get("feed_id")]}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
{features.length && isStationDetails && (
|
|
120
|
+
<StationDetails feature={features[0]} />
|
|
121
|
+
)}
|
|
122
|
+
{!!features[0]?.get("form_factor") && isFloatingVehicle && (
|
|
123
|
+
<FloatingVehiclesDetails features={features} />
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default RvfSharedMobilityDetails;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Feature } from "ol";
|
|
2
|
+
|
|
3
|
+
import getLinkByDevice from "../../../utils/getLinkByDevice";
|
|
4
|
+
export interface StationDetailsProps {
|
|
5
|
+
feature: Feature;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function StationDetails({ feature }: StationDetailsProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex flex-col">
|
|
11
|
+
{feature.get("num_vehicles_available")} Fahrzeuge
|
|
12
|
+
<a
|
|
13
|
+
className="mx-auto mt-[40%] rounded-xl border border-grey px-2 py-1 text-xl text-grey hover:border-red hover:text-red"
|
|
14
|
+
href={getLinkByDevice(feature)}
|
|
15
|
+
rel="noreferrer"
|
|
16
|
+
target="_blank"
|
|
17
|
+
>
|
|
18
|
+
Jetzt buchen
|
|
19
|
+
</a>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
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]);
|
|
@@ -39,6 +39,7 @@ import RvfLineNetworkPlanLayer from "../RvfLineNetworkPlanLayer";
|
|
|
39
39
|
import Modal from "../RvfModal";
|
|
40
40
|
import RvfOverlayHeader from "../RvfOverlayHeader";
|
|
41
41
|
import RvfPoisLayer from "../RvfPoisLayer";
|
|
42
|
+
import RvfSelectedFeatureHighlightLayer from "../RvfSelectedFeatureHighlightLayer";
|
|
42
43
|
import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
|
|
43
44
|
import RvfShare from "../RvfShare";
|
|
44
45
|
import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
|
|
@@ -55,6 +56,7 @@ import StationsLayer from "../StationsLayer";
|
|
|
55
56
|
import tailwind from "../style.css";
|
|
56
57
|
import { RVF_EXTENT_3857 } from "../utils/constants";
|
|
57
58
|
import { I18nContext } from "../utils/hooks/useI18n";
|
|
59
|
+
import useInitialLayersVisiblity from "../utils/hooks/useInitialLayersVisiblity";
|
|
58
60
|
import { MapContext } from "../utils/hooks/useMapContext";
|
|
59
61
|
import { RvfContext } from "../utils/hooks/useRvfContext";
|
|
60
62
|
import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
|
|
@@ -63,7 +65,13 @@ import MobilityEvent from "../utils/MobilityEvent";
|
|
|
63
65
|
// @ts-expect-error bad type definition
|
|
64
66
|
import style from "./index.css";
|
|
65
67
|
|
|
66
|
-
export type RvfMobilityMapProps = {
|
|
68
|
+
export type RvfMobilityMapProps = {
|
|
69
|
+
layers: string; // list of visible layers on load
|
|
70
|
+
layertree: string;
|
|
71
|
+
print: string;
|
|
72
|
+
share: string;
|
|
73
|
+
toolbar: string;
|
|
74
|
+
} & MobilityMapProps;
|
|
67
75
|
|
|
68
76
|
const bbox = RVF_EXTENT_3857.join(",");
|
|
69
77
|
|
|
@@ -87,6 +95,8 @@ function RvfMobilityMap({
|
|
|
87
95
|
center = null,
|
|
88
96
|
extent = bbox,
|
|
89
97
|
geolocation = "true",
|
|
98
|
+
layers = null,
|
|
99
|
+
layertree = "true",
|
|
90
100
|
mapsurl = "https://maps.geops.io",
|
|
91
101
|
maxextent = bbox,
|
|
92
102
|
maxzoom = "20",
|
|
@@ -97,9 +107,11 @@ function RvfMobilityMap({
|
|
|
97
107
|
notificationbeforelayerid = null,
|
|
98
108
|
notificationurl = null,
|
|
99
109
|
permalink = "false",
|
|
110
|
+
print = "true",
|
|
100
111
|
realtime = "true",
|
|
101
112
|
realtimeurl = "wss://api.geops.io/tracker-ws/v1/ws",
|
|
102
113
|
search = "false",
|
|
114
|
+
share = "true",
|
|
103
115
|
stopsurl = "https://api.geops.io/stops/v1/",
|
|
104
116
|
tenant = null,
|
|
105
117
|
toolbar = "true",
|
|
@@ -129,7 +141,42 @@ function RvfMobilityMap({
|
|
|
129
141
|
useState<MaplibreStyleLayer>();
|
|
130
142
|
const [sharedMobilityLayerGroup, setSharedMobilityLayerGroup] =
|
|
131
143
|
useState<Group>();
|
|
132
|
-
|
|
144
|
+
|
|
145
|
+
// Convert string boolean to boolean
|
|
146
|
+
const hasToolbar = useMemo(() => {
|
|
147
|
+
return toolbar === "true";
|
|
148
|
+
}, [toolbar]);
|
|
149
|
+
|
|
150
|
+
const hasLayerTree = useMemo(() => {
|
|
151
|
+
return layertree === "true";
|
|
152
|
+
}, [layertree]);
|
|
153
|
+
|
|
154
|
+
const hasRealtime = useMemo(() => {
|
|
155
|
+
return realtime === "true";
|
|
156
|
+
}, [realtime]);
|
|
157
|
+
|
|
158
|
+
const hasNotification = useMemo(() => {
|
|
159
|
+
return notification === "true";
|
|
160
|
+
}, [notification]);
|
|
161
|
+
|
|
162
|
+
const hasGeolocation = useMemo(() => {
|
|
163
|
+
return geolocation === "true";
|
|
164
|
+
}, [geolocation]);
|
|
165
|
+
|
|
166
|
+
const hasSearch = useMemo(() => {
|
|
167
|
+
return search === "true";
|
|
168
|
+
}, [search]);
|
|
169
|
+
|
|
170
|
+
const hasShare = useMemo(() => {
|
|
171
|
+
return share === "true";
|
|
172
|
+
}, [share]);
|
|
173
|
+
|
|
174
|
+
const hasPrint = useMemo(() => {
|
|
175
|
+
return print === "true";
|
|
176
|
+
}, [print]);
|
|
177
|
+
|
|
178
|
+
// Apply initial visibility of layers
|
|
179
|
+
useInitialLayersVisiblity(map, layers);
|
|
133
180
|
|
|
134
181
|
// TODO: this should be removed. The parent application should be responsible to do this
|
|
135
182
|
// or we should find something that fit more usecases
|
|
@@ -146,6 +193,7 @@ function RvfMobilityMap({
|
|
|
146
193
|
geolocation,
|
|
147
194
|
isFollowing,
|
|
148
195
|
isTracking,
|
|
196
|
+
layers,
|
|
149
197
|
map,
|
|
150
198
|
mapsurl,
|
|
151
199
|
maxextent,
|
|
@@ -190,6 +238,7 @@ function RvfMobilityMap({
|
|
|
190
238
|
map,
|
|
191
239
|
mapsurl,
|
|
192
240
|
maxextent,
|
|
241
|
+
layers,
|
|
193
242
|
maxzoom,
|
|
194
243
|
minzoom,
|
|
195
244
|
mots,
|
|
@@ -217,6 +266,8 @@ function RvfMobilityMap({
|
|
|
217
266
|
center,
|
|
218
267
|
extent,
|
|
219
268
|
geolocation,
|
|
269
|
+
layers,
|
|
270
|
+
layertree,
|
|
220
271
|
mapsurl,
|
|
221
272
|
maxextent,
|
|
222
273
|
maxzoom,
|
|
@@ -226,9 +277,11 @@ function RvfMobilityMap({
|
|
|
226
277
|
notificationat,
|
|
227
278
|
notificationbeforelayerid,
|
|
228
279
|
notificationurl,
|
|
280
|
+
print,
|
|
229
281
|
realtime,
|
|
230
282
|
realtimeurl,
|
|
231
283
|
search,
|
|
284
|
+
share,
|
|
232
285
|
tenant,
|
|
233
286
|
toolbar,
|
|
234
287
|
zoom,
|
|
@@ -236,8 +289,10 @@ function RvfMobilityMap({
|
|
|
236
289
|
);
|
|
237
290
|
}, [
|
|
238
291
|
baselayer,
|
|
292
|
+
layers,
|
|
239
293
|
center,
|
|
240
294
|
geolocation,
|
|
295
|
+
layertree,
|
|
241
296
|
toolbar,
|
|
242
297
|
mapsurl,
|
|
243
298
|
maxzoom,
|
|
@@ -254,6 +309,8 @@ function RvfMobilityMap({
|
|
|
254
309
|
zoom,
|
|
255
310
|
extent,
|
|
256
311
|
maxextent,
|
|
312
|
+
print,
|
|
313
|
+
share,
|
|
257
314
|
]);
|
|
258
315
|
|
|
259
316
|
const rvfContextValue = useMemo(() => {
|
|
@@ -261,6 +318,7 @@ function RvfMobilityMap({
|
|
|
261
318
|
isExportMenuOpen,
|
|
262
319
|
isLayerTreeOpen,
|
|
263
320
|
isShareMenuOpen,
|
|
321
|
+
layertree,
|
|
264
322
|
lineNetworkPlanLayer,
|
|
265
323
|
poisLayer,
|
|
266
324
|
selectedFeature,
|
|
@@ -281,6 +339,7 @@ function RvfMobilityMap({
|
|
|
281
339
|
};
|
|
282
340
|
}, [
|
|
283
341
|
isExportMenuOpen,
|
|
342
|
+
layertree,
|
|
284
343
|
isLayerTreeOpen,
|
|
285
344
|
isShareMenuOpen,
|
|
286
345
|
lineNetworkPlanLayer,
|
|
@@ -399,8 +458,9 @@ function RvfMobilityMap({
|
|
|
399
458
|
<Map className="relative flex-1 overflow-visible ">
|
|
400
459
|
<BaseLayer {...baseLayerProps} />
|
|
401
460
|
<SingleClickListener />
|
|
461
|
+
<RvfSelectedFeatureHighlightLayer />
|
|
402
462
|
|
|
403
|
-
{
|
|
463
|
+
{hasRealtime && <RealtimeLayer title="Echtzeit" />}
|
|
404
464
|
{
|
|
405
465
|
<StationsLayer
|
|
406
466
|
minZoom={10}
|
|
@@ -408,14 +468,14 @@ function RvfMobilityMap({
|
|
|
408
468
|
{...stationsLayerProps}
|
|
409
469
|
/>
|
|
410
470
|
}
|
|
411
|
-
{
|
|
471
|
+
{hasNotification && <NotificationLayer />}
|
|
472
|
+
<RvfTarifZonenLayer title="Tarifzonen" />
|
|
412
473
|
<RvfSellingPointsLayer isQueryable title="Verkaufsstellen" />
|
|
413
474
|
<RvfLineNetworkPlanLayer
|
|
414
475
|
isQueryable
|
|
415
476
|
minZoom={10}
|
|
416
477
|
title="Liniennetz"
|
|
417
478
|
/>
|
|
418
|
-
<RvfTarifZonenLayer isQueryable title="Tarifzonen" />
|
|
419
479
|
<RvfPoisLayer title="POIs" />
|
|
420
480
|
<RvfSharedMobilityLayerGroup title="Shared Mobility" />
|
|
421
481
|
|
|
@@ -428,11 +488,11 @@ function RvfMobilityMap({
|
|
|
428
488
|
</div>
|
|
429
489
|
|
|
430
490
|
<div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
|
|
431
|
-
{
|
|
432
|
-
{!hasToolbar && <RvfExportMenuButton />}
|
|
491
|
+
{hasGeolocation && <GeolocationButton />}
|
|
492
|
+
{!hasToolbar && hasPrint && <RvfExportMenuButton />}
|
|
433
493
|
</div>
|
|
434
494
|
|
|
435
|
-
{
|
|
495
|
+
{hasSearch && (
|
|
436
496
|
<div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
|
|
437
497
|
<Search />
|
|
438
498
|
</div>
|
|
@@ -442,7 +502,7 @@ function RvfMobilityMap({
|
|
|
442
502
|
<RvfZoomButtons />
|
|
443
503
|
</div>
|
|
444
504
|
|
|
445
|
-
{!hasToolbar && (
|
|
505
|
+
{!hasToolbar && hasLayerTree && (
|
|
446
506
|
<RvfFloatingMenu
|
|
447
507
|
isOpen={isLayerTreeOpen}
|
|
448
508
|
onClick={onLayerTreeMenuClick}
|
|
@@ -453,10 +513,10 @@ function RvfMobilityMap({
|
|
|
453
513
|
)}
|
|
454
514
|
</Map>
|
|
455
515
|
<Overlay
|
|
456
|
-
className={"z-50"}
|
|
516
|
+
className={"z-50 bg-white"}
|
|
457
517
|
ScrollableHandlerProps={scrollableHandlerProps}
|
|
458
518
|
>
|
|
459
|
-
{
|
|
519
|
+
{hasRealtime && trainId && (
|
|
460
520
|
<>
|
|
461
521
|
<RvfOverlayHeader
|
|
462
522
|
onClose={() => {
|
|
@@ -486,21 +546,21 @@ function RvfMobilityMap({
|
|
|
486
546
|
}}
|
|
487
547
|
title="Informations"
|
|
488
548
|
></RvfOverlayHeader>
|
|
489
|
-
<RvfFeatureDetails className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden
|
|
549
|
+
<RvfFeatureDetails className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden px-2 pt-2" />
|
|
490
550
|
</>
|
|
491
551
|
)}
|
|
492
|
-
{hasToolbar && isExportMenuOpen && (
|
|
552
|
+
{hasToolbar && hasPrint && isExportMenuOpen && (
|
|
493
553
|
<>
|
|
494
554
|
<RvfOverlayHeader
|
|
495
555
|
onClose={() => {
|
|
496
556
|
setIsExportMenuOpen(false);
|
|
497
557
|
}}
|
|
498
|
-
title="
|
|
558
|
+
title="Drücken"
|
|
499
559
|
></RvfOverlayHeader>
|
|
500
|
-
<RvfExportMenu className="relative flex flex-col gap-
|
|
560
|
+
<RvfExportMenu className="relative flex flex-col gap-8 overflow-y-auto overflow-x-hidden px-4 py-6" />
|
|
501
561
|
</>
|
|
502
562
|
)}
|
|
503
|
-
{hasToolbar && isLayerTreeOpen && (
|
|
563
|
+
{hasToolbar && hasLayerTree && isLayerTreeOpen && (
|
|
504
564
|
<>
|
|
505
565
|
<RvfOverlayHeader
|
|
506
566
|
onClose={() => {
|
|
@@ -508,33 +568,54 @@ function RvfMobilityMap({
|
|
|
508
568
|
}}
|
|
509
569
|
title="Layers"
|
|
510
570
|
></RvfOverlayHeader>
|
|
511
|
-
<Topics className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
571
|
+
<Topics className=" relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
512
572
|
</>
|
|
513
573
|
)}
|
|
514
|
-
{hasToolbar && isShareMenuOpen && (
|
|
574
|
+
{hasToolbar && hasShare && isShareMenuOpen && (
|
|
515
575
|
<>
|
|
516
576
|
<RvfOverlayHeader
|
|
517
577
|
onClose={() => {
|
|
518
|
-
|
|
578
|
+
setIsShareMenuOpen(false);
|
|
519
579
|
}}
|
|
520
580
|
title="Share"
|
|
521
581
|
></RvfOverlayHeader>
|
|
522
|
-
<RvfShare className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden
|
|
582
|
+
<RvfShare className="relative flex h-full flex-col gap-8 overflow-y-auto overflow-x-hidden px-4 py-6" />
|
|
523
583
|
</>
|
|
524
584
|
)}
|
|
525
585
|
</Overlay>
|
|
526
586
|
|
|
527
587
|
{hasToolbar && (
|
|
528
|
-
<div
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
588
|
+
<div
|
|
589
|
+
className={
|
|
590
|
+
"z-[100] flex justify-around overflow-x-hidden border-t bg-white p-1 @lg/main:block @lg/main:border-r @lg/main:p-0 "
|
|
591
|
+
}
|
|
592
|
+
>
|
|
593
|
+
{hasLayerTree && (
|
|
594
|
+
<RvfLayerTreeButton
|
|
595
|
+
className={"border-none"}
|
|
596
|
+
title="Layers"
|
|
597
|
+
/>
|
|
598
|
+
)}
|
|
599
|
+
|
|
600
|
+
{hasPrint && (
|
|
601
|
+
<RvfExportMenuButton
|
|
602
|
+
className={"border-none"}
|
|
603
|
+
title="Drücken"
|
|
604
|
+
/>
|
|
605
|
+
)}
|
|
606
|
+
|
|
607
|
+
{hasShare && (
|
|
608
|
+
<RvfShareMenuButton
|
|
609
|
+
className={"border-none"}
|
|
610
|
+
title="Share"
|
|
611
|
+
/>
|
|
612
|
+
)}
|
|
532
613
|
</div>
|
|
533
614
|
)}
|
|
534
615
|
|
|
535
|
-
{!hasToolbar && isExportMenuOpen && (
|
|
616
|
+
{!hasToolbar && hasPrint && isExportMenuOpen && (
|
|
536
617
|
<Modal onClose={onExportMenuClose}>
|
|
537
|
-
<RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
|
|
618
|
+
<RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
538
619
|
</Modal>
|
|
539
620
|
)}
|
|
540
621
|
</div>
|