@geops/rvf-mobility-web-component 0.1.28 → 0.1.29

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 (30) hide show
  1. package/.vscode/settings.json +2 -0
  2. package/CHANGELOG.md +8 -0
  3. package/docutils.js +1 -1
  4. package/index.html +1 -1
  5. package/index.js +119 -107
  6. package/package.json +1 -1
  7. package/src/NotificationLayer/NotificationLayer.tsx +2 -2
  8. package/src/RouteIcon/RouteIcon.tsx +21 -12
  9. package/src/RvfExportMenu/RvfExportMenu.tsx +2 -0
  10. package/src/RvfFeatureDetails/RVFSellingPointDetails/RVFSellingPointDetails.tsx +47 -0
  11. package/src/RvfFeatureDetails/RVFSellingPointDetails/index.js +1 -0
  12. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +4 -1
  13. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +4 -1
  14. package/src/RvfMobilityMap/RvfMobilityMap.tsx +55 -12
  15. package/src/RvfSelectedFeatureHighlightLayer/RvfSelectedFeatureHighlightLayer.tsx +58 -32
  16. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +364 -123
  17. package/src/StationsLayer/StationsLayer.tsx +1 -1
  18. package/src/icons/NoRealtime/NoRealtime.tsx +44 -0
  19. package/src/icons/NoRealtime/index.tsx +1 -0
  20. package/src/icons/NoRealtime/norealtime.svg +6 -0
  21. package/src/utils/constants.ts +22 -1
  22. package/src/utils/exportPdf.ts +3 -3
  23. package/src/utils/fullTrajectoryStyle.ts +0 -1
  24. package/src/utils/getMainColorForVehicle.test.ts +21 -23
  25. package/src/utils/getRadius.ts +25 -24
  26. package/src/utils/hooks/useUpdatePermalink.tsx +5 -0
  27. package/src/utils/realtimeRVFStyle.ts +70 -8
  28. package/src/utils/sharingWFSUtils.ts +46 -16
  29. package/src/RvfSharedMobilityLayerGroup2/RvfSharedMobilityLayerGroup2.tsx +0 -740
  30. package/src/RvfSharedMobilityLayerGroup2/index.tsx +0 -1
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.28",
5
+ "version": "0.1.29",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
@@ -39,10 +39,10 @@ const useNotifications = () => {
39
39
  if (!baseLayer.loaded) {
40
40
  // @ts-expect-error bad type definition
41
41
  baseLayer.once("load", () => {
42
- return setStyleMetadata(baseLayer.mbMap?.getStyle()?.metadata);
42
+ return setStyleMetadata(baseLayer.mapLibreMap?.getStyle()?.metadata);
43
43
  });
44
44
  } else {
45
- setStyleMetadata(baseLayer.mbMap?.getStyle()?.metadata);
45
+ setStyleMetadata(baseLayer.mapLibreMap?.getStyle()?.metadata);
46
46
  }
47
47
  }, [baseLayer, baselayer]);
48
48
 
@@ -6,6 +6,7 @@ import {
6
6
  } from "mobility-toolbox-js/types";
7
7
  import { JSX, PreactDOMAttributes } from "preact";
8
8
 
9
+ import NoRealtime from "../icons/NoRealtime";
9
10
  import getMainColorForVehicle from "../utils/getMainColorForVehicle";
10
11
  import getTextColor from "../utils/getTextColor";
11
12
  import getTextFontForVehicle from "../utils/getTextFontForVehicle";
@@ -45,24 +46,32 @@ function RouteIcon({
45
46
 
46
47
  const fontSize = fontSizesByNbLetters[text.length] || 12;
47
48
  const font = getTextFontForVehicle(fontSize, text);
49
+ const hasRealtime = stopSequence?.has_realtime_journey;
50
+ const isCancelled = stopSequence?.stations[0]?.state === "JOURNEY_CANCELLED";
48
51
 
49
52
  if (borderColor === backgroundColor) {
50
53
  borderColor = "black";
51
54
  }
52
55
 
53
56
  return (
54
- <span
55
- className="flex h-[40px] min-w-[40px] items-center justify-center rounded-full border px-1"
56
- style={{
57
- backgroundColor,
58
- borderColor,
59
- color,
60
- font,
61
- }}
62
- {...props}
63
- >
64
- {children || text}
65
- </span>
57
+ <>
58
+ <span
59
+ className="relative flex h-[40px] min-w-[40px] items-center justify-center rounded-full border px-1"
60
+ style={{
61
+ backgroundColor,
62
+ borderColor,
63
+ color,
64
+ font,
65
+ }}
66
+ {...props}
67
+ >
68
+ {children || text}
69
+
70
+ {!isCancelled && !hasRealtime && (
71
+ <NoRealtime className={"absolute -left-2 -top-2"} />
72
+ )}
73
+ </span>
74
+ </>
66
75
  );
67
76
  }
68
77
  export default RouteIcon;
@@ -74,9 +74,11 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
74
74
  getAllLayers(layers).forEach((layer) => {
75
75
  layer.set(LAYER_PROP_IS_EXPORTING, false);
76
76
  });
77
+ map.set(LAYER_PROP_IS_EXPORTING, false);
77
78
  },
78
79
 
79
80
  onBefore: (map, layers) => {
81
+ map.set(LAYER_PROP_IS_EXPORTING, true);
80
82
  prevRealtimeLayerVisibility = realtimeLayer.getVisible();
81
83
  if (realtimeLayer.getVisible()) {
82
84
  realtimeLayer.setVisible(false);
@@ -0,0 +1,47 @@
1
+ import { Feature } from "ol";
2
+
3
+ import Automat from "../../icons/Automat";
4
+ import InPerson from "../../icons/InPerson";
5
+ import Video from "../../icons/Video";
6
+
7
+ const ICON_BY_OPERATED_BY = {
8
+ Automat: <Automat />,
9
+ Persönlich: <InPerson />,
10
+ Videozentrum: <Video />,
11
+ };
12
+
13
+ function RVFSellingPointDetails({ feature }: { feature: Feature }) {
14
+ if (!feature) {
15
+ return null;
16
+ }
17
+ const {
18
+ city,
19
+ name,
20
+ operated_by: operatedBy,
21
+ street,
22
+ telephone_info: phone,
23
+ zip_code: zip,
24
+ } = feature.getProperties();
25
+ return (
26
+ <div className="flex flex-col gap-4">
27
+ <div className="flex gap-2">
28
+ <span className={"min-w-[26px]"}>
29
+ {ICON_BY_OPERATED_BY[operatedBy]}
30
+ </span>
31
+ <span>{name}</span>
32
+ </div>
33
+ <div className="flex flex-col">
34
+ {phone && <div>{phone}</div>}
35
+ {street && <div>{street}</div>}
36
+ {(zip || city) && (
37
+ <div className="flex gap-2">
38
+ <span>{zip}</span>
39
+ <span>{city}</span>
40
+ </div>
41
+ )}
42
+ </div>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ export default RVFSellingPointDetails;
@@ -0,0 +1 @@
1
+ export { default } from "./RVFSellingPointDetails";
@@ -3,6 +3,7 @@ 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 RVFSellingPointDetails from "./RVFSellingPointDetails/RVFSellingPointDetails";
6
7
  import RvfSharedMobilityDetails from "./RvfSharedMobilityDetail";
7
8
 
8
9
  export type RvfFeatureDetailsProps = JSX.HTMLAttributes<HTMLDivElement> &
@@ -21,6 +22,7 @@ const getIsSharedMobility = (selectedFeature): boolean => {
21
22
  function RvfFeatureDetails(props: RvfFeatureDetailsProps) {
22
23
  const { selectedFeature } = useRvfContext();
23
24
  const isSharedMobility = getIsSharedMobility(selectedFeature);
25
+ const isSellingPoint = !!selectedFeature.get("tickets");
24
26
 
25
27
  const showDefaultData = () => {
26
28
  return Object.entries(selectedFeature.getProperties()).map(
@@ -39,10 +41,11 @@ function RvfFeatureDetails(props: RvfFeatureDetailsProps) {
39
41
 
40
42
  return (
41
43
  <div {...props}>
44
+ {isSellingPoint && <RVFSellingPointDetails feature={selectedFeature} />}
42
45
  {isSharedMobility && (
43
46
  <RvfSharedMobilityDetails selectedFeature={selectedFeature} />
44
47
  )}
45
- {!isSharedMobility && showDefaultData()}
48
+ {!isSharedMobility && !isSellingPoint && showDefaultData()}
46
49
  </div>
47
50
  );
48
51
  }
@@ -18,7 +18,10 @@ function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
18
18
  return new MaplibreStyleLayer({
19
19
  isQueryable: true,
20
20
  layersFilter: ({ metadata }) => {
21
- return metadata?.["rvf.filter"] === "netzplan_lines";
21
+ return (
22
+ metadata?.["rvf.filter"] === "netzplan_lines" ||
23
+ metadata?.["rvf.filter"] === "netzplan_stations"
24
+ );
22
25
  },
23
26
  maplibreLayer: baseLayer,
24
27
  minZoom: 10,
@@ -42,7 +42,7 @@ import RvfPoisLayer from "../RvfPoisLayer";
42
42
  import RvfSelectedFeatureHighlightLayer from "../RvfSelectedFeatureHighlightLayer";
43
43
  import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
44
44
  import RvfShare from "../RvfShare";
45
- import RvfSharedMobilityLayerGroup2 from "../RvfSharedMobilityLayerGroup2";
45
+ import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
46
46
  import RvfShareMenuButton from "../RvfShareMenuButton";
47
47
  import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
48
48
  import Topics from "../RvfTopics";
@@ -71,6 +71,13 @@ import realtimeRVFStyle from "../utils/realtimeRVFStyle";
71
71
  // @ts-expect-error bad type definition
72
72
  import style from "./index.css";
73
73
 
74
+ const PRIORITY_FROM_TYPE = {
75
+ bus: 25,
76
+ coach: 15,
77
+ train: 30,
78
+ tram: 20,
79
+ };
80
+
74
81
  export type RvfMobilityMapProps = {
75
82
  details: string;
76
83
  layers: string; // list of visible layers on load
@@ -100,9 +107,43 @@ const realtimeLayerProps = {
100
107
  line_tags: "RVF",
101
108
  },
102
109
  fullTrajectoryStyle: fullTrajectoryStyle,
110
+ getMotsByZoom: (z) => {
111
+ if (z < 9) {
112
+ return ["rail"];
113
+ }
114
+ return null;
115
+ },
116
+ sort: (
117
+ { properties: { delay: delayA, type: typeA } },
118
+ { properties: { delay: delayB, type: typeB } },
119
+ ) => {
120
+ // console.log(trajA, trajB);
121
+ if (typeA !== typeB) {
122
+ // console.log(trajA.properties.type, trajB.properties.type);
123
+ if (PRIORITY_FROM_TYPE[typeA] < PRIORITY_FROM_TYPE[typeB]) {
124
+ return -1;
125
+ }
126
+ return 1;
127
+ }
128
+
129
+ if (delayA === delayB) {
130
+ return 0;
131
+ }
132
+
133
+ if (delayA < delayB) {
134
+ return -1;
135
+ }
136
+ return 1;
137
+ },
103
138
  style: realtimeRVFStyle,
104
139
  styleOptions: {
105
140
  getBgColor: getBgColor,
141
+ getMaxRadiusForStrokeAndDelay: () => {
142
+ return 4;
143
+ },
144
+ getMaxRadiusForText: () => {
145
+ return 8;
146
+ },
106
147
  getRadius: getRadius,
107
148
  getTextColor: getTextColor,
108
149
  },
@@ -110,6 +151,7 @@ const realtimeLayerProps = {
110
151
 
111
152
  function RvfMobilityMap({
112
153
  apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
154
+ // baselayer = "review-geops-tgma-085vyz.de.rvf",
113
155
  baselayer = "de.rvf",
114
156
  center = null,
115
157
  details = "true",
@@ -117,7 +159,9 @@ function RvfMobilityMap({
117
159
  geolocation = "true",
118
160
  layers = null,
119
161
  layertree = "true",
120
- mapsurl = "https://maps.geops.io",
162
+ // mapsurl = "https://style-review.geops.io",
163
+ mapsurl = "https://maps.style-dev.geops.io",
164
+ // mapsurl = "https://maps.geops.io",
121
165
  maxextent = bbox,
122
166
  maxzoom = "20",
123
167
  minzoom = null,
@@ -402,7 +446,7 @@ function RvfMobilityMap({
402
446
  const stationsLayerProps = useMemo(() => {
403
447
  return {
404
448
  layersFilter: ({ metadata }) => {
405
- return metadata?.["rvf.filter"] === "netzplan_stations";
449
+ return metadata?.["general.filter"] === "stations";
406
450
  },
407
451
  };
408
452
  }, []);
@@ -492,17 +536,16 @@ function RvfMobilityMap({
492
536
  {...realtimeLayerProps}
493
537
  />
494
538
  )}
495
- {
496
- <StationsLayer
497
- minZoom={10}
498
- title={RVF_LAYERS_TITLES.haltestellen}
499
- {...stationsLayerProps}
500
- />
501
- }
539
+
540
+ <StationsLayer
541
+ minZoom={10}
542
+ title={RVF_LAYERS_TITLES.haltestellen}
543
+ {...stationsLayerProps}
544
+ />
502
545
  {hasNotification && <NotificationLayer />}
503
546
  <RvfTarifZonenLayer title={RVF_LAYERS_TITLES.tarifzonen} />
504
547
  <RvfSellingPointsLayer
505
- isQueryable={false}
548
+ isQueryable={true}
506
549
  title={RVF_LAYERS_TITLES.verkaufsstellen}
507
550
  />
508
551
  <RvfLineNetworkPlanLayer
@@ -511,7 +554,7 @@ function RvfMobilityMap({
511
554
  title={RVF_LAYERS_TITLES.liniennetz}
512
555
  />
513
556
  <RvfPoisLayer title={RVF_LAYERS_TITLES.pois} />
514
- <RvfSharedMobilityLayerGroup2
557
+ <RvfSharedMobilityLayerGroup
515
558
  title={RVF_LAYERS_TITLES.sharedMobility}
516
559
  />
517
560
 
@@ -1,62 +1,88 @@
1
- import VectorLayer from "ol/layer/Vector";
2
- import { Vector } from "ol/source";
3
- import { Circle, Stroke, Style } from "ol/style";
1
+ import { GeoJSONSource } from "maplibre-gl";
2
+ import GeoJSON from "ol/format/GeoJSON";
4
3
  import { useEffect, useMemo } from "preact/hooks";
5
4
 
5
+ import { EMPTY_FEATURE_COLLECTION, SOURCE_SELECT } from "../utils/constants";
6
6
  import useMapContext from "../utils/hooks/useMapContext";
7
7
  import useRvfContext from "../utils/hooks/useRvfContext";
8
8
 
9
+ const geojson = new GeoJSON({
10
+ dataProjection: "EPSG:4326",
11
+ featureProjection: "EPSG:3857",
12
+ });
13
+
9
14
  function SingleClickListener() {
10
- const { baseLayer, map } = useMapContext();
15
+ const { baseLayer } = useMapContext();
11
16
  const { selectedFeature } = useRvfContext();
12
17
 
13
- const layer = useMemo(() => {
14
- const layer = new VectorLayer({
15
- source: new Vector(),
16
- style: new Style({
17
- image: new Circle({
18
- fill: null,
19
- radius: 15,
20
- stroke: new Stroke({
21
- color: "red",
22
- width: 3,
23
- }),
24
- }),
25
- }),
26
- });
27
- return layer;
28
- }, []);
18
+ // const layer = useMemo(() => {
19
+ // const layer = new VectorLayer({
20
+ // source: new Vector(),
21
+ // style: new Style({
22
+ // image: new Circle({
23
+ // fill: null,
24
+ // radius: 15,
25
+ // stroke: new Stroke({
26
+ // color: "red",
27
+ // width: 3,
28
+ // }),
29
+ // }),
30
+ // }),
31
+ // });
32
+ // return layer;
33
+ // }, []);
34
+
35
+ const mbMap = useMemo(() => {
36
+ return baseLayer?.mapLibreMap;
37
+ }, [baseLayer?.mapLibreMap]);
29
38
 
30
39
  useEffect(() => {
31
40
  const vectorTileFeature = selectedFeature?.get("vectorTileFeature");
32
41
  selectedFeature?.set("selected", true);
42
+ const source = mbMap?.getSource(SOURCE_SELECT) as GeoJSONSource;
33
43
  if (selectedFeature) {
34
44
  const feature = selectedFeature.clone();
35
45
  feature.setStyle(null);
36
- layer.getSource().addFeature(feature);
37
- if (vectorTileFeature) {
38
- baseLayer?.mapLibreMap.setFeatureState(vectorTileFeature, {
46
+
47
+ // Special highlight for the sharing mobility layer
48
+ if (
49
+ vectorTileFeature ||
50
+ // feature.get("station_id") ||
51
+ // feature.get("vehicle_id") ||
52
+ // feature.get("cluster_id") ||
53
+ feature.get("cluster_id")
54
+ ) {
55
+ mbMap?.setFeatureState(vectorTileFeature, {
39
56
  hover: true,
40
57
  });
58
+ if (feature.get("cluster_id")) {
59
+ feature.set("mot", feature.get("features")[0].get("form_factor"));
60
+ feature.set("provider_name", "");
61
+ feature.set("num_vehicles_available", feature.get("point_count"));
62
+ }
63
+
64
+ source?.setData(geojson.writeFeatureObject(feature));
41
65
  }
42
66
  }
67
+
43
68
  return () => {
44
- layer?.getSource().clear();
69
+ source?.setData(EMPTY_FEATURE_COLLECTION);
70
+ // layer?.getSource().clear();
45
71
  selectedFeature?.set("selected", false);
46
72
  if (vectorTileFeature) {
47
- baseLayer?.mapLibreMap?.setFeatureState(vectorTileFeature, {
73
+ mbMap?.setFeatureState(vectorTileFeature, {
48
74
  hover: false,
49
75
  });
50
76
  }
51
77
  };
52
- }, [baseLayer?.mapLibreMap, layer, selectedFeature]);
78
+ }, [mbMap, selectedFeature]);
53
79
 
54
- useEffect(() => {
55
- layer?.setMap(map);
56
- return () => {
57
- layer?.setMap(null);
58
- };
59
- }, [layer, map]);
80
+ // useEffect(() => {
81
+ // layer?.setMap(map);
82
+ // return () => {
83
+ // layer?.setMap(null);
84
+ // };
85
+ // }, [layer, map]);
60
86
 
61
87
  return null;
62
88
  }