@geops/rvf-mobility-web-component 0.1.33 → 0.1.35
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 +22 -0
- package/index.html +5 -3
- package/index.js +216 -195
- package/package.json +1 -1
- package/src/GeolocationButton/GeolocationButton.tsx +32 -4
- package/src/RouteIcon/RouteIcon.tsx +6 -3
- package/src/RouteStopProgress/RouteStopProgress.tsx +12 -7
- package/src/RvfEmbedNavigation/DragPanWarning.ts +124 -0
- package/src/RvfEmbedNavigation/RvfEmbedNavigation.tsx +50 -0
- package/src/RvfEmbedNavigation/index.js +1 -0
- package/src/RvfFeatureDetails/RvfLineNetworkDetails/RvfLineNetworkDetails.tsx +158 -23
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +11 -0
- package/src/RvfMobilityMap/index.css +13 -0
- package/src/icons/Geolocation/location.svg +7 -0
- package/src/index.tsx +1 -0
- package/src/utils/getFeatureInformationTitle.ts +3 -2
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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.35",
|
|
6
6
|
"homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.js",
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
2
|
|
|
3
|
-
import { Geolocation } from "ol";
|
|
3
|
+
import { Feature, Geolocation } from "ol";
|
|
4
|
+
import { Point } from "ol/geom";
|
|
5
|
+
import VectorLayer from "ol/layer/Vector";
|
|
4
6
|
import { unByKey } from "ol/Observable";
|
|
5
7
|
import { fromLonLat } from "ol/proj";
|
|
8
|
+
import VectorSource from "ol/source/Vector";
|
|
9
|
+
import { Icon, Style } from "ol/style";
|
|
6
10
|
import { useEffect, useMemo } from "preact/hooks";
|
|
7
11
|
|
|
8
12
|
import GeolocationIcon from "../icons/Geolocation";
|
|
13
|
+
// @ts-ecpect-error - svg file not handled by vite
|
|
14
|
+
import locationSvg from "../icons/Geolocation/location.svg";
|
|
9
15
|
import RvfIconButton from "../RvfIconButton";
|
|
10
16
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
11
17
|
|
|
18
|
+
const point = new Point([0, 0]);
|
|
19
|
+
const feature = new Feature(point);
|
|
20
|
+
const layer = new VectorLayer({
|
|
21
|
+
source: new VectorSource({ features: [feature] }),
|
|
22
|
+
style: new Style({
|
|
23
|
+
image: new Icon({
|
|
24
|
+
anchor: [0.5, 1],
|
|
25
|
+
src: locationSvg,
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
12
30
|
export type GeolocationButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
|
|
13
31
|
PreactDOMAttributes;
|
|
14
32
|
|
|
@@ -32,15 +50,19 @@ function GeolocationButton({ ...props }: GeolocationButtonProps) {
|
|
|
32
50
|
geolocation.once("change:position", (evt) => {
|
|
33
51
|
const position = evt.target.getPosition();
|
|
34
52
|
if (evt.target.getPosition()) {
|
|
53
|
+
const coord = fromLonLat(position, "EPSG:3857");
|
|
35
54
|
map.getView().setZoom(TRACKING_ZOOM);
|
|
36
|
-
map.getView().setCenter(
|
|
55
|
+
map.getView().setCenter(coord);
|
|
56
|
+
point.setCoordinates(coord);
|
|
37
57
|
}
|
|
38
58
|
}),
|
|
39
59
|
// then we only center the map.
|
|
40
60
|
geolocation.on("change:position", (evt) => {
|
|
41
61
|
const position = evt.target.getPosition();
|
|
42
62
|
if (evt.target.getPosition()) {
|
|
43
|
-
|
|
63
|
+
const coord = fromLonLat(position, "EPSG:3857");
|
|
64
|
+
map.getView().setCenter(coord);
|
|
65
|
+
point.setCoordinates(coord);
|
|
44
66
|
}
|
|
45
67
|
}),
|
|
46
68
|
];
|
|
@@ -52,7 +74,13 @@ function GeolocationButton({ ...props }: GeolocationButtonProps) {
|
|
|
52
74
|
|
|
53
75
|
useEffect(() => {
|
|
54
76
|
geolocation.setTracking(isTracking);
|
|
55
|
-
|
|
77
|
+
if (isTracking) {
|
|
78
|
+
layer.setMap(map);
|
|
79
|
+
}
|
|
80
|
+
return () => {
|
|
81
|
+
layer.setMap(null);
|
|
82
|
+
};
|
|
83
|
+
}, [map, geolocation, isTracking]);
|
|
56
84
|
|
|
57
85
|
return (
|
|
58
86
|
<RvfIconButton
|
|
@@ -34,7 +34,7 @@ function RouteIcon({
|
|
|
34
34
|
departure?.line ||
|
|
35
35
|
stopSequence?.line ||
|
|
36
36
|
trajectory?.properties?.line;
|
|
37
|
-
const type = stopSequence?.type || trajectory?.type;
|
|
37
|
+
const type = lineToUse?.type || stopSequence?.type || trajectory?.type;
|
|
38
38
|
const backgroundColor = getMainColorForVehicle(
|
|
39
39
|
line || departure || stopSequence || trajectory,
|
|
40
40
|
);
|
|
@@ -46,7 +46,10 @@ function RouteIcon({
|
|
|
46
46
|
|
|
47
47
|
const fontSize = fontSizesByNbLetters[text.length] || 12;
|
|
48
48
|
const font = getTextFontForVehicle(fontSize, text);
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
// RealtimeIcon only for stopsequence for now
|
|
51
|
+
const hasRealtime = stopSequence?.has_realtime_journey === true;
|
|
52
|
+
const showNoRealtimeIcon = !!stopSequence;
|
|
50
53
|
const isCancelled = stopSequence?.stations[0]?.state === "JOURNEY_CANCELLED";
|
|
51
54
|
|
|
52
55
|
if (borderColor === backgroundColor) {
|
|
@@ -67,7 +70,7 @@ function RouteIcon({
|
|
|
67
70
|
>
|
|
68
71
|
{children || text}
|
|
69
72
|
|
|
70
|
-
{!isCancelled && !hasRealtime && (
|
|
73
|
+
{showNoRealtimeIcon && !isCancelled && !hasRealtime && (
|
|
71
74
|
<NoRealtime className={"absolute -left-2 -top-2"} />
|
|
72
75
|
)}
|
|
73
76
|
</span>
|
|
@@ -6,11 +6,16 @@ import useMapContext from "../utils/hooks/useMapContext";
|
|
|
6
6
|
import useRouteStop from "../utils/hooks/useRouteStop";
|
|
7
7
|
|
|
8
8
|
export type RouteStopProgressProps = {
|
|
9
|
+
lineColor?: string;
|
|
9
10
|
svgProps?: JSX.HTMLAttributes<SVGElement> & PreactDOMAttributes;
|
|
10
11
|
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
11
12
|
PreactDOMAttributes;
|
|
12
13
|
|
|
13
|
-
function RouteStopProgress({
|
|
14
|
+
function RouteStopProgress({
|
|
15
|
+
lineColor,
|
|
16
|
+
svgProps,
|
|
17
|
+
...props
|
|
18
|
+
}: RouteStopProgressProps) {
|
|
14
19
|
const { stopSequence } = useMapContext();
|
|
15
20
|
const { invertColor, status } = useRouteStop();
|
|
16
21
|
const { isBoarding, isFirst, isLast, isLeft, isPassed, progress } = status;
|
|
@@ -19,16 +24,16 @@ function RouteStopProgress({ svgProps, ...props }: RouteStopProgressProps) {
|
|
|
19
24
|
const yDone = `${progress}%`;
|
|
20
25
|
|
|
21
26
|
const greyColor = "rgb(156, 163, 175)";
|
|
22
|
-
const
|
|
27
|
+
const lineColorr = lineColor || getMainColorForVehicle(stopSequence);
|
|
23
28
|
|
|
24
|
-
let colorScheme = isPassed ? greyColor :
|
|
25
|
-
let invertColorScheme = isPassed ?
|
|
29
|
+
let colorScheme = isPassed ? greyColor : lineColorr;
|
|
30
|
+
let invertColorScheme = isPassed ? lineColorr : greyColor;
|
|
26
31
|
let progressDoneColor = greyColor;
|
|
27
32
|
|
|
28
33
|
if (invertColor) {
|
|
29
|
-
colorScheme = isPassed ?
|
|
30
|
-
invertColorScheme = isPassed ? greyColor :
|
|
31
|
-
progressDoneColor =
|
|
34
|
+
colorScheme = isPassed ? lineColorr : greyColor;
|
|
35
|
+
invertColorScheme = isPassed ? greyColor : lineColorr;
|
|
36
|
+
progressDoneColor = lineColorr;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
const circleColor =
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Map, MapBrowserEvent } from "ol";
|
|
2
|
+
import type { Options } from "ol/control/Control";
|
|
3
|
+
|
|
4
|
+
import { Control } from "ol/control";
|
|
5
|
+
import { EventsKey } from "ol/events";
|
|
6
|
+
import { DragPan } from "ol/interaction";
|
|
7
|
+
import { unByKey } from "ol/Observable";
|
|
8
|
+
|
|
9
|
+
type DragPanWarningOptions = {
|
|
10
|
+
className?: string;
|
|
11
|
+
} & Options;
|
|
12
|
+
|
|
13
|
+
class DragPanWarning extends Control {
|
|
14
|
+
dragPan?: DragPan;
|
|
15
|
+
|
|
16
|
+
icon: HTMLDivElement;
|
|
17
|
+
|
|
18
|
+
onPointerDragRef?: EventsKey;
|
|
19
|
+
|
|
20
|
+
text: HTMLParagraphElement;
|
|
21
|
+
|
|
22
|
+
constructor(options: DragPanWarningOptions = {}) {
|
|
23
|
+
const element = document.createElement("div");
|
|
24
|
+
element.className =
|
|
25
|
+
options.className !== undefined
|
|
26
|
+
? options.className
|
|
27
|
+
: "ol-drag-pan-warning";
|
|
28
|
+
element.style.display = "none";
|
|
29
|
+
element.style.pointerEvents = "auto";
|
|
30
|
+
|
|
31
|
+
super({
|
|
32
|
+
element,
|
|
33
|
+
render: options.render,
|
|
34
|
+
target: options.target,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.icon = document.createElement("div");
|
|
38
|
+
this.icon.className = `${element.className}-icon`;
|
|
39
|
+
|
|
40
|
+
// sbb icons: sbb/two-finger-tap-large.svg';
|
|
41
|
+
this.icon.innerHTML = `
|
|
42
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
|
43
|
+
<path fill="none" stroke="currentColor" stroke-width="2" d="M22.999,25 L22.997,13.002 C22.997,11.896 23.893,11 24.999,11 C26.105,11 27.001,11.896 27.001,13.002 L26.999,26.002 M31,25.003 C31,23.897 31.896,23.001 33.002,23.001 C34.11,23.001 35.006,23.897 35.006,25.003 C35.006,25.003 35,26.125 35,32.001 C35,37.875 33.2,41.001 33.2,41.001 L19,41.001 C19,41.001 12.21,29.483 11.586,28.419 C10.962,27.355 10.804,26.369 11.586,25.587 C12.37,24.805 13.636,24.805 14.418,25.587 L18.998,30.169 L19,30.169 L19,25.001 L18.996,15.003 C18.996,13.897 19.894,13.001 21,13.001 C22.106,13.001 23.002,13.897 23.002,15.003 L23,26.015 M26.9942,22.997 C26.9942,21.891 27.8902,20.995 28.9962,20.995 C30.1042,20.995 31.0002,21.891 31.0002,22.997 L31.0002,26.001 M30,16.3046 C30.632,15.3606 31,14.2246 31,13.0006 C31,9.6846 28.314,7.0006 25,7.0006 C23.208,7.0006 21.616,7.8026 20.518,9.0486 C17.432,9.2986 15,11.8506 15,15.0006 C15,16.2166 15.368,17.3426 15.988,18.2866"/>
|
|
44
|
+
</svg>
|
|
45
|
+
`;
|
|
46
|
+
this.element.appendChild(this.icon);
|
|
47
|
+
|
|
48
|
+
this.text = document.createElement("p");
|
|
49
|
+
this.text.className = `${element.className}-text`;
|
|
50
|
+
this.text.innerHTML = "Benutzen Sie 2 Finger um die Karte zu bedienen.";
|
|
51
|
+
this.element.appendChild(this.text);
|
|
52
|
+
|
|
53
|
+
this.dragPan = undefined;
|
|
54
|
+
this.onPointerDragRef = undefined;
|
|
55
|
+
this.onPointerDrag = this.onPointerDrag.bind(this);
|
|
56
|
+
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onPointerDrag(event: MapBrowserEvent<PointerEvent>) {
|
|
60
|
+
// Show the warning on next pointerdrag events.
|
|
61
|
+
if (event.activePointers?.length !== 1) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
this.element.style.display = "flex";
|
|
65
|
+
document.addEventListener("touchend", this.onTouchEnd, { once: true });
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onTouchEnd() {
|
|
70
|
+
unByKey(this.onPointerDragRef);
|
|
71
|
+
this.element.style.display = "none";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setMap(map: Map) {
|
|
75
|
+
super.setMap(map);
|
|
76
|
+
document.removeEventListener("touchend", this.onTouchEnd);
|
|
77
|
+
unByKey(this.onPointerDragRef);
|
|
78
|
+
|
|
79
|
+
if (map) {
|
|
80
|
+
const viewport = map.getViewport();
|
|
81
|
+
|
|
82
|
+
// We allow default scroll behavior for touch events.
|
|
83
|
+
viewport.style.touchAction = "pan-x pan-y";
|
|
84
|
+
|
|
85
|
+
// Deactivate drag pan only on touch events.
|
|
86
|
+
this.dragPan = map
|
|
87
|
+
.getInteractions()
|
|
88
|
+
.getArray()
|
|
89
|
+
.find((interaction) => {
|
|
90
|
+
return interaction instanceof DragPan;
|
|
91
|
+
}) as DragPan;
|
|
92
|
+
|
|
93
|
+
if (!this.dragPan) {
|
|
94
|
+
console.error(
|
|
95
|
+
"Impossible to find the DragPan interaction, DragPanWarning will not work as expected.",
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.listenerKeys.push(
|
|
101
|
+
// @ts-expect-error - we need to listen to pointerdown events
|
|
102
|
+
map.on("pointerdown", (evt: MapBrowserEvent<PointerEvent>) => {
|
|
103
|
+
document.removeEventListener("touchend", this.onTouchEnd);
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
evt.originalEvent.pointerType !== "touch" ||
|
|
107
|
+
// @ts-expect-error - condition_ is a private method
|
|
108
|
+
!this.dragPan?.condition_(evt)
|
|
109
|
+
) {
|
|
110
|
+
this.element.style.display = "none";
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.onPointerDragRef = map.on("pointerdrag", this.onPointerDrag);
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
this.element.addEventListener("click", this.onTouchEnd);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default DragPanWarning;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MouseWheelZoom } from "ol/interaction";
|
|
2
|
+
import { useEffect, useState } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
5
|
+
import DragPanWarning from "./DragPanWarning";
|
|
6
|
+
|
|
7
|
+
function RvfEmbedNavigation() {
|
|
8
|
+
const { map } = useMapContext();
|
|
9
|
+
const [target, setTarget] = useState(null);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
let dragPanWarningControl = null;
|
|
13
|
+
|
|
14
|
+
if (!map || !target) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Deactivates mouse wheel zoom
|
|
18
|
+
map.getInteractions().forEach((interaction) => {
|
|
19
|
+
if (interaction instanceof MouseWheelZoom) {
|
|
20
|
+
interaction.setActive(false);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
dragPanWarningControl = new DragPanWarning({ target: target });
|
|
25
|
+
map.addControl(dragPanWarningControl);
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
if (dragPanWarningControl) {
|
|
29
|
+
map.removeControl(dragPanWarningControl);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Deactivates mouse wheel zoom
|
|
33
|
+
map.getInteractions().forEach((interaction) => {
|
|
34
|
+
if (interaction instanceof MouseWheelZoom) {
|
|
35
|
+
interaction.setActive(true);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}, [map, target]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
ref={(node) => {
|
|
44
|
+
return setTarget(node);
|
|
45
|
+
}}
|
|
46
|
+
></div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default RvfEmbedNavigation;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfEmbedNavigation";
|
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
import { RealtimeLine } from "mobility-toolbox-js/types";
|
|
1
2
|
import { Feature } from "ol";
|
|
2
3
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
3
4
|
|
|
5
|
+
import ArrowDown from "../../icons/ArrowDown";
|
|
6
|
+
import ArrowUp from "../../icons/ArrowUp";
|
|
7
|
+
import RouteIcon from "../../RouteIcon";
|
|
8
|
+
import RouteStopProgress from "../../RouteStopProgress";
|
|
4
9
|
import useMapContext from "../../utils/hooks/useMapContext";
|
|
10
|
+
import { RouteStopContext } from "../../utils/hooks/useRouteStop";
|
|
5
11
|
|
|
6
12
|
let cacheLineInfosById = null;
|
|
13
|
+
let cacheStopInfosById = null;
|
|
14
|
+
|
|
15
|
+
interface LineInfo {
|
|
16
|
+
color: string;
|
|
17
|
+
external_id: string;
|
|
18
|
+
id: string;
|
|
19
|
+
long_name: string;
|
|
20
|
+
mot: string;
|
|
21
|
+
operator_name: string;
|
|
22
|
+
short_name: string;
|
|
23
|
+
text_color: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface StopInfo {
|
|
27
|
+
external_id: string;
|
|
28
|
+
importance: number;
|
|
29
|
+
long_name: string;
|
|
30
|
+
short_name: string;
|
|
31
|
+
visibility_level: number;
|
|
32
|
+
}
|
|
7
33
|
|
|
8
34
|
function RvfLineNetworkDetails({
|
|
9
35
|
feature,
|
|
@@ -13,7 +39,11 @@ function RvfLineNetworkDetails({
|
|
|
13
39
|
features: Feature[];
|
|
14
40
|
}) {
|
|
15
41
|
const { apikey, mapsurl } = useMapContext();
|
|
16
|
-
const [lineInfos, setLineInfos] = useState(null);
|
|
42
|
+
const [lineInfos, setLineInfos] = useState<LineInfo[]>(null);
|
|
43
|
+
const [stopInfos, setStopInfos] = useState<StopInfo[]>(null);
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
const [stopInfosOpenId, setStopInfosOpenId] = useState<string>(null);
|
|
46
|
+
|
|
17
47
|
useEffect(() => {
|
|
18
48
|
const fetchInfos = async () => {
|
|
19
49
|
if (!cacheLineInfosById) {
|
|
@@ -22,15 +52,16 @@ function RvfLineNetworkDetails({
|
|
|
22
52
|
);
|
|
23
53
|
const data = await response.json();
|
|
24
54
|
cacheLineInfosById = data["geops.lnp.lines"];
|
|
55
|
+
cacheStopInfosById = data["geops.lnp.stops"];
|
|
25
56
|
}
|
|
26
57
|
setLineInfos(cacheLineInfosById);
|
|
58
|
+
setStopInfos(cacheStopInfosById);
|
|
27
59
|
};
|
|
28
60
|
fetchInfos();
|
|
29
61
|
}, [apikey, mapsurl]);
|
|
30
62
|
|
|
31
|
-
const
|
|
63
|
+
const lineInfosByOperator: Record<string, LineInfo[]> = useMemo(() => {
|
|
32
64
|
const byOperators = {};
|
|
33
|
-
|
|
34
65
|
[
|
|
35
66
|
...new Set(
|
|
36
67
|
features.map((f) => {
|
|
@@ -46,51 +77,155 @@ function RvfLineNetworkDetails({
|
|
|
46
77
|
if (!byOperators[operatorName]) {
|
|
47
78
|
byOperators[operatorName] = [];
|
|
48
79
|
}
|
|
80
|
+
lineInfos[id].id = id;
|
|
49
81
|
byOperators[operatorName].push(lineInfos[id]);
|
|
50
82
|
});
|
|
51
83
|
|
|
52
84
|
return byOperators;
|
|
53
85
|
}, [features, lineInfos]);
|
|
54
86
|
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
88
|
+
const stopInfoIdsByLineId: Record<string, string[]> = useMemo(() => {
|
|
89
|
+
const byLineId = {};
|
|
90
|
+
features.forEach((f) => {
|
|
91
|
+
const lineId = f.get("original_line_id");
|
|
92
|
+
if (lineId && !byLineId[lineId]) {
|
|
93
|
+
try {
|
|
94
|
+
byLineId[lineId] = JSON.parse(f.get("stop_ids"));
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.log(e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return byLineId;
|
|
102
|
+
}, [features]);
|
|
103
|
+
|
|
55
104
|
if (!feature || !lineInfos) {
|
|
56
105
|
return null;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
108
|
return (
|
|
60
109
|
<div className="flex flex-col gap-4">
|
|
61
|
-
{Object.entries(
|
|
62
|
-
(
|
|
63
|
-
|
|
64
|
-
<div
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
{Object.entries(lineInfosByOperator).map(([operatorName, linesInfos]) => {
|
|
111
|
+
return (
|
|
112
|
+
<div className={"flex flex-col gap-2"} key={operatorName}>
|
|
113
|
+
<div>{operatorName}</div>
|
|
114
|
+
{linesInfos
|
|
115
|
+
.sort((a, b) => {
|
|
116
|
+
return a.short_name?.localeCompare(b.short_name);
|
|
117
|
+
})
|
|
118
|
+
.map((lineInfo) => {
|
|
67
119
|
const {
|
|
120
|
+
color: backgroundColor,
|
|
121
|
+
id,
|
|
68
122
|
// color,
|
|
69
123
|
// external_id,
|
|
70
|
-
long_name
|
|
124
|
+
long_name,
|
|
125
|
+
mot,
|
|
71
126
|
short_name: shortName,
|
|
72
|
-
|
|
127
|
+
text_color: textColor,
|
|
73
128
|
} = lineInfo;
|
|
129
|
+
let longName = long_name;
|
|
130
|
+
|
|
131
|
+
let stops = null;
|
|
132
|
+
//stopInfoIdsByLineId?.[id] || null;
|
|
133
|
+
if (!stops?.length) {
|
|
134
|
+
stops = null;
|
|
135
|
+
}
|
|
136
|
+
console.log("stops", stops, longName);
|
|
137
|
+
if (!longName && stops) {
|
|
138
|
+
const names = stops.map((stopId) => {
|
|
139
|
+
return stopInfos[stopId].short_name;
|
|
140
|
+
});
|
|
141
|
+
console.log("stops", names);
|
|
142
|
+
|
|
143
|
+
longName = [
|
|
144
|
+
...new Set([names[0], names[names.length - 1]]),
|
|
145
|
+
].join(" - ");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Build a line object
|
|
149
|
+
const line: { type: string } & RealtimeLine = {
|
|
150
|
+
color: null,
|
|
151
|
+
id: null,
|
|
152
|
+
name: shortName,
|
|
153
|
+
stroke: null,
|
|
154
|
+
text_color: null,
|
|
155
|
+
type: mot,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (textColor) {
|
|
159
|
+
line.text_color = "#" + textColor;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (backgroundColor) {
|
|
163
|
+
line.color = "#" + backgroundColor;
|
|
164
|
+
}
|
|
74
165
|
|
|
75
166
|
return (
|
|
76
|
-
<div
|
|
77
|
-
<div
|
|
167
|
+
<div key={shortName}>
|
|
168
|
+
<div
|
|
169
|
+
className={
|
|
170
|
+
"flex w-full items-center justify-between gap-2"
|
|
171
|
+
}
|
|
172
|
+
// onClick={() => {
|
|
173
|
+
// setStopInfosOpenId(stopInfosOpenId === id ? null : id);
|
|
174
|
+
// }}
|
|
175
|
+
>
|
|
176
|
+
<div>
|
|
177
|
+
<RouteIcon line={line}></RouteIcon>
|
|
178
|
+
</div>
|
|
179
|
+
{!!longName && (
|
|
180
|
+
<div className={"flex-1 text-left"}>{longName}</div>
|
|
181
|
+
)}
|
|
182
|
+
{!!stops && (
|
|
183
|
+
<button className={"shrink-0"}>
|
|
184
|
+
{stopInfosOpenId === id ? <ArrowUp /> : <ArrowDown />}
|
|
185
|
+
</button>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
{!!stops && (
|
|
78
189
|
<div
|
|
79
|
-
className={
|
|
80
|
-
"rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
|
|
81
|
-
}
|
|
190
|
+
className={`${stopInfosOpenId === id ? "" : "hidden"}`}
|
|
82
191
|
>
|
|
83
|
-
{
|
|
192
|
+
{stops?.map((stopId, index, arr) => {
|
|
193
|
+
const stop = stopInfos[stopId];
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
className={"flex items-center gap-2"}
|
|
197
|
+
key={stopId}
|
|
198
|
+
>
|
|
199
|
+
<RouteStopContext.Provider
|
|
200
|
+
value={{
|
|
201
|
+
index,
|
|
202
|
+
status: {
|
|
203
|
+
isFirst: !index,
|
|
204
|
+
isLast: index === arr.length - 1,
|
|
205
|
+
isLeft: false,
|
|
206
|
+
isPassed: false,
|
|
207
|
+
progress: !index ? 50 : 0,
|
|
208
|
+
},
|
|
209
|
+
stop,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
<RouteStopProgress
|
|
213
|
+
className="relative flex size-8 shrink-0 items-center justify-center"
|
|
214
|
+
lineColor={line.color}
|
|
215
|
+
/>
|
|
216
|
+
<div>{stop.short_name}</div>
|
|
217
|
+
</RouteStopContext.Provider>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
})}
|
|
84
221
|
</div>
|
|
85
|
-
|
|
86
|
-
<div>{longName}</div>
|
|
222
|
+
)}
|
|
87
223
|
</div>
|
|
88
224
|
);
|
|
89
225
|
})}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
})}
|
|
94
229
|
</div>
|
|
95
230
|
);
|
|
96
231
|
}
|
|
@@ -29,6 +29,7 @@ import NotificationLayer from "../NotificationLayer";
|
|
|
29
29
|
import Overlay from "../Overlay";
|
|
30
30
|
import RealtimeLayer from "../RealtimeLayer";
|
|
31
31
|
import RouteSchedule from "../RouteSchedule";
|
|
32
|
+
import RvfEmbedNavigation from "../RvfEmbedNavigation/RvfEmbedNavigation";
|
|
32
33
|
import RvfExportMenu from "../RvfExportMenu";
|
|
33
34
|
import RvfExportMenuButton from "../RvfExportMenuButton";
|
|
34
35
|
import RvfFeatureDetails from "../RvfFeatureDetails";
|
|
@@ -80,6 +81,7 @@ const PRIORITY_FROM_TYPE = {
|
|
|
80
81
|
|
|
81
82
|
export type RvfMobilityMapProps = {
|
|
82
83
|
details: string;
|
|
84
|
+
embed: string;
|
|
83
85
|
layers: string; // list of visible layers on load
|
|
84
86
|
layertree: string;
|
|
85
87
|
print: string;
|
|
@@ -153,6 +155,7 @@ function RvfMobilityMap({
|
|
|
153
155
|
baselayer = "de.rvf",
|
|
154
156
|
center = null,
|
|
155
157
|
details = "true",
|
|
158
|
+
embed = "false",
|
|
156
159
|
extent = bbox,
|
|
157
160
|
geolocation = "true",
|
|
158
161
|
layers = null,
|
|
@@ -241,6 +244,10 @@ function RvfMobilityMap({
|
|
|
241
244
|
return details === "true";
|
|
242
245
|
}, [details]);
|
|
243
246
|
|
|
247
|
+
const isEmbed = useMemo(() => {
|
|
248
|
+
return embed === "true";
|
|
249
|
+
}, [embed]);
|
|
250
|
+
|
|
244
251
|
// Apply initial visibility of layers
|
|
245
252
|
useInitialLayersVisiblity(map, layers);
|
|
246
253
|
|
|
@@ -331,6 +338,7 @@ function RvfMobilityMap({
|
|
|
331
338
|
baselayer,
|
|
332
339
|
center,
|
|
333
340
|
details,
|
|
341
|
+
embed,
|
|
334
342
|
extent,
|
|
335
343
|
geolocation,
|
|
336
344
|
layers,
|
|
@@ -356,6 +364,7 @@ function RvfMobilityMap({
|
|
|
356
364
|
);
|
|
357
365
|
}, [
|
|
358
366
|
baselayer,
|
|
367
|
+
embed,
|
|
359
368
|
layers,
|
|
360
369
|
center,
|
|
361
370
|
geolocation,
|
|
@@ -526,6 +535,8 @@ function RvfMobilityMap({
|
|
|
526
535
|
<Map className="relative flex-1 overflow-visible ">
|
|
527
536
|
<BaseLayer {...baseLayerProps} />
|
|
528
537
|
<SingleClickListener />
|
|
538
|
+
{isEmbed && <RvfEmbedNavigation />}
|
|
539
|
+
|
|
529
540
|
<RvfSelectedFeatureHighlightLayer />
|
|
530
541
|
|
|
531
542
|
{hasRealtime && (
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
3
|
+
<title>iconfont/location</title>
|
|
4
|
+
<g id="iconfont/location" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
5
|
+
<path d="M12,1.42108547e-14 C17.5228474,1.42108547e-14 22,4.47715241 22,10 C22,13.3716045 20.1975984,16.6760073 17.2259527,19.8127445 C16.2070021,20.8883035 15.118253,21.8681776 14.0291768,22.735405 C13.6473986,23.0394136 13.2921737,23.3081093 12.9722093,23.5391947 C12.777224,23.6800174 12.6351454,23.77842 12.5547002,23.8320502 C12.2188008,24.0559831 11.7811992,24.0559831 11.4452998,23.8320502 C11.3648546,23.77842 11.222776,23.6800174 11.0277907,23.5391947 C10.7078263,23.3081093 10.3526014,23.0394136 9.97082322,22.735405 C8.88174698,21.8681776 7.7929979,20.8883035 6.77404732,19.8127445 C3.80240157,16.6760073 2,13.3716045 2,10 C2,4.47715241 6.47715256,1.42108547e-14 12,1.42108547e-14 Z M12,2 C7.58172205,2 4,5.58172193 4,10 C4,12.7533953 5.57259843,15.6364924 8.22595268,18.4372552 C9.1757521,19.4398213 10.196378,20.3583846 11.2166768,21.1708447 C11.4951711,21.3926087 11.7576885,21.5936102 12,21.7726167 C12.2423115,21.5936102 12.5048289,21.3926087 12.7833232,21.1708447 C13.803622,20.3583846 14.8242479,19.4398213 15.7740473,18.4372552 C18.4274016,15.6364924 20,12.7533953 20,10 C20,5.58172193 16.418278,2 12,2 Z M12,5.99999987 C14.209139,5.99999987 16,7.79086087 16,9.99999987 C16,12.2091389 14.209139,13.9999999 12,13.9999999 C9.790861,13.9999999 8,12.2091389 8,9.99999987 C8,7.79086087 9.790861,5.99999987 12,5.99999987 Z M12,7.99999987 C10.8954305,7.99999987 10,8.89543037 10,9.99999987 C10,11.1045694 10.8954305,11.9999999 12,11.9999999 C13.1045695,11.9999999 14,11.1045694 14,9.99999987 C14,8.89543037 13.1045695,7.99999987 12,7.99999987 Z" id="Combined-Shape" fill="#E3000B" fill-rule="nonzero"></path>
|
|
6
|
+
</g>
|
|
7
|
+
</svg>
|