@geops/rvf-mobility-web-component 0.1.77 → 0.1.79

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/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.77",
5
+ "version": "0.1.79",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
@@ -11,8 +11,8 @@
11
11
  "graphql-request": "^7.1.2",
12
12
  "jspdf": "^3.0.3",
13
13
  "lodash.debounce": "^4.0.8",
14
- "maplibre-gl": "^5.9.0",
15
- "mobility-toolbox-js": "3.4.6-beta.6",
14
+ "maplibre-gl": "^5.10.0",
15
+ "mobility-toolbox-js": "3.4.7",
16
16
  "ol": "^10.6.1",
17
17
  "preact": "^10.27.2",
18
18
  "preact-custom-element": "^4.5.1",
@@ -25,9 +25,9 @@
25
25
  "devDependencies": {
26
26
  "@commitlint/cli": "^20.1.0",
27
27
  "@commitlint/config-conventional": "^20.0.0",
28
- "@eslint/js": "^9.37.0",
28
+ "@eslint/js": "^9.38.0",
29
29
  "@geops/eslint-config-react": "^1.6.0-beta.1",
30
- "@tailwindcss/cli": "^4.1.14",
30
+ "@tailwindcss/cli": "^4.1.16",
31
31
  "@tailwindcss/container-queries": "^0.1.1",
32
32
  "@testing-library/preact": "^3.2.4",
33
33
  "@types/geojson": "^7946.0.16",
@@ -35,9 +35,9 @@
35
35
  "@types/lodash": "^4.17.20",
36
36
  "@types/preact-custom-element": "^4.0.4",
37
37
  "concurrently": "^9.2.1",
38
- "esbuild": "^0.25.10",
38
+ "esbuild": "^0.25.11",
39
39
  "esbuild-sass-plugin": "^3.3.1",
40
- "eslint": "^9.37.0",
40
+ "eslint": "^9.38.0",
41
41
  "eslint-plugin-tailwindcss": "^4.0.0-beta.0",
42
42
  "fixpack": "^4.0.0",
43
43
  "generact": "^0.4.0",
@@ -47,15 +47,15 @@
47
47
  "jest-environment-jsdom": "^30.2.0",
48
48
  "jest-preset-preact": "^4.1.1",
49
49
  "next": "15.5.5",
50
- "preact-render-to-string": "^6.6.2",
50
+ "preact-render-to-string": "^6.6.3",
51
51
  "prettier": "^3.6.2",
52
- "prettier-plugin-tailwindcss": "^0.6.14",
52
+ "prettier-plugin-tailwindcss": "^0.7.1",
53
53
  "standard-version": "^9.5.0",
54
54
  "tailwind-merge": "^3.3.1",
55
- "tailwindcss": "^4.1.14",
55
+ "tailwindcss": "^4.1.16",
56
56
  "ts-jest": "^29.4.5",
57
57
  "typescript": "^5.9.3",
58
- "typescript-eslint": "^8.46.1"
58
+ "typescript-eslint": "^8.46.2"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "yarn build:css && yarn build:js && cp index*.js* doc/public/",
@@ -1,6 +1,5 @@
1
1
  import { MapsetLayer as MtbMapsetLayer } from "mobility-toolbox-js/ol";
2
2
  import { unByKey } from "ol/Observable";
3
- import { transformExtent } from "ol/proj";
4
3
  import { memo } from "preact/compat";
5
4
  import { useEffect, useMemo } from "preact/hooks";
6
5
 
@@ -8,8 +7,8 @@ import { LAYER_NAME_MAPSET } from "../utils/constants";
8
7
  import useMapContext from "../utils/hooks/useMapContext";
9
8
 
10
9
  import type { MapsetLayerOptions } from "mobility-toolbox-js/ol";
11
-
12
- let moveEndTimeout: ReturnType<typeof setTimeout>;
10
+ import type { Feature } from "ol";
11
+ import type { EventTypes } from "ol/Observable";
13
12
 
14
13
  export const isFeatureOutsideZoomLimit = (feature, map) => {
15
14
  const zoom = map?.getView()?.getZoom();
@@ -23,7 +22,6 @@ function MapsetLayer(props?: Partial<MapsetLayerOptions>) {
23
22
  apikey,
24
23
  baseLayer,
25
24
  map,
26
- mapsetbbox,
27
25
  mapsetplanid,
28
26
  mapsettags,
29
27
  mapsettenants,
@@ -37,38 +35,8 @@ function MapsetLayer(props?: Partial<MapsetLayerOptions>) {
37
35
  return null;
38
36
  }
39
37
 
40
- let bbox = undefined;
41
- if (mapsetbbox) {
42
- bbox = mapsetbbox?.split(",").map((coord) => {
43
- return Number(coord.trim());
44
- });
45
- if (
46
- bbox.length === 4 &&
47
- !bbox.some((coord) => {
48
- return Number.isNaN(coord);
49
- })
50
- ) {
51
- bbox = transformExtent(bbox, "EPSG:3857", "EPSG:4326");
52
- }
53
- } else {
54
- // we have to wait that the map is well sized to get the extent
55
- // It triggers an erro in FF without this
56
- if (
57
- map.getView()?.getCenter() &&
58
- map.getSize()[0] > 0 &&
59
- map.getSize()[1] > 0
60
- ) {
61
- bbox = transformExtent(
62
- map.getView()?.calculateExtent(),
63
- "EPSG:3857",
64
- "EPSG:4326",
65
- );
66
- }
67
- }
68
38
  return new MtbMapsetLayer({
69
39
  apiKey: apikey,
70
- bbox,
71
- mapseturl: mapseturl || undefined,
72
40
  name: LAYER_NAME_MAPSET,
73
41
  planId: mapsetplanid ?? undefined,
74
42
  tags: mapsettags?.split(",").map((t) => {
@@ -77,14 +45,13 @@ function MapsetLayer(props?: Partial<MapsetLayerOptions>) {
77
45
  tenants: mapsettenants?.split(",").map((t) => {
78
46
  return t.trim();
79
47
  }),
80
- timestamp: mapsettimestamp || new Date().toISOString(), // Load only standard plan
81
- zoom: map.getView().getZoom(),
48
+ timestamp: mapsettimestamp, // Load only standard plan
49
+ url: mapseturl,
82
50
  ...(props || {}),
83
51
  });
84
52
  }, [
85
53
  baseLayer,
86
54
  map,
87
- mapsetbbox,
88
55
  apikey,
89
56
  mapseturl,
90
57
  mapsetplanid,
@@ -109,49 +76,12 @@ function MapsetLayer(props?: Partial<MapsetLayerOptions>) {
109
76
  };
110
77
  }, [map, layer]);
111
78
 
112
- useEffect(() => {
113
- const view = map?.getView();
114
- if (!view || !layer) {
115
- return;
116
- }
117
- const handleMoveEnd = () => {
118
- if (mapsetplanid || !layer.get("visible")) {
119
- return;
120
- }
121
- clearTimeout(moveEndTimeout);
122
- moveEndTimeout = setTimeout(() => {
123
- const currentBbox = transformExtent(
124
- view.calculateExtent(map.getSize()),
125
- "EPSG:3857",
126
- "EPSG:4326",
127
- );
128
- layer.bbox = currentBbox;
129
- layer.zoom = view.getZoom();
130
- }, 100);
131
- };
132
-
133
- const listeners = [
134
- layer.on("change:visible", () => {
135
- if (layer.get("visible")) {
136
- handleMoveEnd();
137
- }
138
- }),
139
- view.on("change:center", handleMoveEnd),
140
- view.on("change:resolution", handleMoveEnd),
141
- ];
142
-
143
- return () => {
144
- clearTimeout(moveEndTimeout);
145
- unByKey(listeners);
146
- };
147
- }, [map, layer, mapsetplanid]);
148
-
149
- // Apply fetaure's minzoom and maxzoom to its style
79
+ // Apply feature's minzoom and maxzoom to its style
150
80
  // TODO should be done by the mapset layer itself
151
81
  useEffect(() => {
152
82
  let key = null;
153
83
  if (layer) {
154
- key = layer.on("updatefeatures", () => {
84
+ key = layer.on("updatefeatures" as EventTypes, () => {
155
85
  const features = layer.getSource()?.getFeatures();
156
86
  if (!features?.length) {
157
87
  return;
@@ -40,7 +40,6 @@ export type MobilityMapAttributeName =
40
40
  | "mainlink"
41
41
  | "mainlinktitle"
42
42
  | "mapset"
43
- | "mapsetbbox"
44
43
  | "mapsetplanid"
45
44
  | "mapsettags"
46
45
  | "mapsettenants"
@@ -191,12 +190,6 @@ where:
191
190
  public: true,
192
191
  type: "boolean",
193
192
  },
194
- mapsetbbox: {
195
- defaultValue: MAX_EXTENT.join(","),
196
- description:
197
- "The BBOX to constrain the boundary of the mapset layer in EPSG:3857 coordinates. Mandatory for mapset layer. <br/>Ex: 831634,5933959,940649,6173660 .",
198
- public: false,
199
- },
200
193
  mapsetplanid: {
201
194
  description:
202
195
  "The id of the mapset plan to display. Mostly for debugging purposes.",
@@ -216,7 +209,7 @@ where:
216
209
  public: false,
217
210
  },
218
211
  mapseturl: {
219
- defaultValue: "https://editor.mapset.io/api/v1",
212
+ defaultValue: "https://editor.mapset.io/api/v1/",
220
213
  description: `The ${geopsMapsetApiLink} url to use.`,
221
214
  public: true,
222
215
  },
@@ -1,4 +1,5 @@
1
1
  import { MocoAPI } from "mobility-toolbox-js/ol";
2
+ import { SeverityEnumeration } from "mobility-toolbox-js/types";
2
3
  import { memo, useEffect, useState } from "preact/compat";
3
4
 
4
5
  import SituationDetails from "../SituationDetails";
@@ -19,6 +20,17 @@ export type MobilityNotificationsProps = Record<
19
20
  null | string | undefined
20
21
  >;
21
22
 
23
+ const severityOrder = {
24
+ [SeverityEnumeration.NoImpact]: 2,
25
+ [SeverityEnumeration.Normal]: 5,
26
+ [SeverityEnumeration.Severe]: 6,
27
+ [SeverityEnumeration.Slight]: 4,
28
+ [SeverityEnumeration.Undefined]: 1,
29
+ [SeverityEnumeration.Unknown]: 0,
30
+ [SeverityEnumeration.VerySevere]: 7,
31
+ [SeverityEnumeration.VerySlight]: 3,
32
+ };
33
+
22
34
  function MobilityNotifications({
23
35
  apikey,
24
36
  lang,
@@ -59,20 +71,44 @@ function MobilityNotifications({
59
71
  <I18nContext.Provider value={i18n}>
60
72
  <style>{tailwind}</style>
61
73
  <div className="flex w-full flex-col gap-6">
62
- {situations?.map((situation) => {
63
- return (
64
- <SituationDetails
65
- canToggle={true}
66
- headerClassName="text-rvf-h3 font-semibold"
67
- iconClassName="w-8 h-8 text-red"
68
- key={situation.id}
69
- reasonClassName="hidden"
70
- situation={situation}
71
- timeIntervalClassName="pl-1 text-base py-3 font-semibold text-balance"
72
- useShortMonth={false}
73
- ></SituationDetails>
74
- );
75
- })}
74
+ {situations
75
+ ?.sort((a, b) => {
76
+ // sort by startDate
77
+ const startDateA = a.affectedTimeIntervalsStart;
78
+ const startDateB = b.affectedTimeIntervalsStart;
79
+ if (startDateA && startDateB) {
80
+ if (startDateA === startDateB) {
81
+ // Sort by severity;
82
+ const severityA =
83
+ a.publications.find((pub) => {
84
+ return !!pub.severity;
85
+ })?.severity || 0;
86
+ const severityB =
87
+ b.publications.find((pub) => {
88
+ return !!pub.severity;
89
+ })?.severity || 0;
90
+ return severityOrder[severityA] > severityOrder[severityB]
91
+ ? -1
92
+ : 1;
93
+ }
94
+ return startDateA < startDateB ? -1 : 1;
95
+ }
96
+ })
97
+ .map((situation) => {
98
+ console.log("Rendering situation", situation.id, situation.title);
99
+ return (
100
+ <SituationDetails
101
+ canToggle={true}
102
+ headerClassName="text-rvf-h3 font-semibold"
103
+ iconClassName="w-8 h-8 text-red"
104
+ key={situation.id}
105
+ reasonClassName="hidden"
106
+ situation={situation}
107
+ timeIntervalClassName="pl-1 text-base py-3 font-semibold text-balance"
108
+ useShortMonth={false}
109
+ ></SituationDetails>
110
+ );
111
+ })}
76
112
  </div>
77
113
  </I18nContext.Provider>
78
114
  );
@@ -0,0 +1,17 @@
1
+
2
+ - creation app/repo/deploy (lint) -> 4h
3
+ - creation map with ArcGIS Maps SDK with SBB base layer(one or more?) -> 4h
4
+ - add Betriebspunkte and Linien layers on the map -> 4h
5
+ - using URL parameter "?betriebspunkte=BN":
6
+ - ask the ArcGIS FeatureServer the Betriebpunkte corresponding to URL paramter -> 4h
7
+ - zoom on it with a buffer if 5km -> 2h
8
+ - ask the ArcGIS FeatureServer the Gemeinde, the Kanton of the Betriebspunkte -> 4h
9
+ - ask the ArcGIS FeatureServer all Linien available in this 5km extent -> 3h
10
+ - show a dialog with Kanton, Gemiende and the list of selectable lines -> 4h
11
+ - define the default "KM von" and "KM bis" for each Linie using the 5km extent -> 4h
12
+ - display "KM von" and "KM bis" in the dialog as input text -> 2h
13
+
14
+ - add a tool to select an area by bbox -> 1d
15
+ - add a tool to select an area by "Frei Skizze" -> 1d
16
+ - use the area drawn to recalculate the "KM von" and "KM bis" of all Linien -> 3h
17
+ -
@@ -1,91 +0,0 @@
1
- import {
2
- getFeatureCollectionToRenderFromSituation,
3
- MocoLayer,
4
- } from "mobility-toolbox-js/ol";
5
- import { unByKey } from "ol/Observable";
6
-
7
- import type { GeoJSONSource } from "maplibre-gl";
8
- import type { MocoNotificationFeatureCollectionToRender } from "mobility-toolbox-js/ol";
9
- import type { Map } from "ol";
10
-
11
- export const MOCO_SOURCE_ID = "moco";
12
- export const MOCO_MD_LAYER_FILTER = "moco";
13
- class MocoLayer2 extends MocoLayer {
14
- constructor(options) {
15
- super(options);
16
- }
17
-
18
- attachToMap(map: Map): void {
19
- super.attachToMap(map);
20
- unByKey(this.olEventsKeys);
21
- }
22
-
23
- getDataByGraph(
24
- data: MocoNotificationFeatureCollectionToRender,
25
- ): MocoNotificationFeatureCollectionToRender {
26
- // const zoom = this.getMapInternal()?.getView()?.getZoom();
27
- // const graphs = (
28
- // this.maplibreLayer?.mapLibreMap?.getStyle() as MapsStyleSpecification
29
- // ).metadata?.graphs;
30
-
31
- // const graph = getGraphByZoom(zoom, graphs);
32
- const newData: MocoNotificationFeatureCollectionToRender = {
33
- features: (data?.features || []).filter((feature) => {
34
- delete feature.properties?.publicationStops;
35
- delete feature.properties?.publicationLines;
36
- delete feature.properties?.publication;
37
- delete feature.properties?.situation;
38
- return (
39
- feature.properties?.graph === "osm" ||
40
- // feature.properties?.graph === "np_topo4" ||
41
- // feature.properties?.graph === "np_topo5" ||
42
- // feature.properties?.graph === "np_topo6" //||
43
- feature.properties?.graph === "np_topo7" ||
44
- feature.properties?.graph === "np_topo8" ||
45
- feature.properties?.graph === "np_topo9" ||
46
- feature.properties?.graph === "np_topo10" ||
47
- feature.properties?.graph === "np_topo11" ||
48
- feature.properties?.graph === "np_topo12" ||
49
- feature.properties?.graph === "np_topo13" ||
50
- feature.properties?.graph === "np_topo14" ||
51
- feature.properties?.graph === "np_topo15"
52
- );
53
- }),
54
- type: "FeatureCollection",
55
- };
56
- return newData;
57
- }
58
- /**
59
- * This function updates the GeoJSON source data, with the current situations available in this.situations.
60
- * @returns
61
- */
62
- async updateData(): Promise<boolean | undefined> {
63
- if (this.loadAll) {
64
- const situations = await this.loadData();
65
- // We don't use the setter here to avoid infinite loop
66
- this.set("situations", situations ?? []);
67
- }
68
- const source = this.maplibreLayer?.mapLibreMap?.getSource(MOCO_SOURCE_ID);
69
- if (!source) {
70
- // eslint-disable-next-line no-console
71
- console.warn("MocoLayer: No source found for id : ", MOCO_SOURCE_ID);
72
- return Promise.reject(new Error("No source found"));
73
- }
74
-
75
- const data = {
76
- features: (this.situations ?? []).flatMap((situation) => {
77
- return getFeatureCollectionToRenderFromSituation(situation).features;
78
- }),
79
- type: "FeatureCollection",
80
- } as MocoNotificationFeatureCollectionToRender;
81
- // this.#dataInternal = data;
82
-
83
- // Apply new data to the source
84
- console.log(this.getDataByGraph(data));
85
- // console.log(JSON.stringify(this.getDataByGraph(data)));
86
- (source as GeoJSONSource).setData(this.getDataByGraph(data));
87
- return Promise.resolve(true);
88
- }
89
- }
90
-
91
- export default MocoLayer2;
@@ -1,130 +0,0 @@
1
- import sbbConstructionBanner from "../icons/sbb_construction_banner.png";
2
- // import sbbAddStop from "../icons/sbb_add_stop.png";
3
- // import sbbAlternative from "../icons/sbb_alternative.png";
4
- // import sbbCancellation from "../icons/sbb_cancellation.png";
5
- import sbbConstruction from "../icons/sbb_construction.png";
6
- // import sbbDelay from "../icons/sbb_delay.png";
7
- import sbbDisruptionBanner from "../icons/sbb_disruption_banner.png";
8
- import sbbDisruption from "../icons/sbb_disruption.png";
9
- // import sbbInfo from "../icons/sbb_info.png";
10
- import sbbMissedConnectionBanner from "../icons/sbb_missed_connection_banner.png";
11
- import sbbMissedConnection from "../icons/sbb_missed_connection.png";
12
- import sbbReplacementBusBanner from "../icons/sbb_replacementbus_banner.png";
13
- // import sbbPlatformChange from "../icons/sbb_platform_change.png";
14
- import sbbReplacementBus from "../icons/sbb_replacementbus.png";
15
- // import sbbReRoute from "../icons/sbb_reroute.png";
16
- // import warningbanner from "../icons/warning-banner.png";
17
- // import warning from "../icons/warning.png";
18
-
19
- // export const AffectedLineStyleCategoryChoices = {
20
- // Construction: "CONSTRUCTION",
21
- // Disruption: "DISRUPTION",
22
- // IndustrialAction: "INDUSTRIAL_ACTION",
23
- // LiftFailure: "LIFT_FAILURE",
24
- // Other: "OTHER",
25
- // RailReplacement: "RAIL_REPLACEMENT",
26
- // SpecialEvent: "SPECIAL_EVENT",
27
- // VehicleFaulure: "VEHICLE_FAULURE",
28
- // Warning: "WARNING",
29
- // } as const;
30
- const defaultIcon = sbbMissedConnection;
31
- const defaultIconBanner = sbbMissedConnectionBanner;
32
- export const icons = {
33
- CONSTRUCTION: sbbConstruction,
34
- CONSTRUCTION_BANNER: sbbConstructionBanner,
35
- DISRUPTION: sbbDisruption,
36
- DISRUPTION_BANNER: sbbDisruptionBanner,
37
- INDUSTRIAL_ACTION: defaultIcon,
38
- INDUSTRIAL_ACTION_BANNER: defaultIconBanner,
39
- LIFT_FAILURE: defaultIcon,
40
- LIFT_FAILURE_BANNER: defaultIconBanner,
41
- OTHER: defaultIcon,
42
- OTHER_BANNER: defaultIconBanner,
43
- RAIL_REPLACEMENT: sbbReplacementBus,
44
-
45
- RAIL_REPLACEMENT_BANNER: sbbReplacementBusBanner,
46
- SPECIAL_EVENT: defaultIcon,
47
- SPECIAL_EVENT_BANNER: defaultIconBanner,
48
- VEHICLE_FAULURE: defaultIcon,
49
- VEHICLE_FAULURE_BANNER: defaultIconBanner,
50
- warning: defaultIcon,
51
- WARNING: defaultIcon,
52
- warningBanner: defaultIconBanner,
53
- };
54
-
55
- // Add a source and styleLayer using the id in parameter.
56
- const addSourceAndLayers = (
57
- mapboxLayer,
58
- sourceId,
59
- sourceData,
60
- styleLayer,
61
- beforeLayerId,
62
- ) => {
63
- if (!mapboxLayer.loaded) {
64
- mapboxLayer.once("load", () => {
65
- addSourceAndLayers(
66
- mapboxLayer,
67
- sourceId,
68
- sourceData,
69
- styleLayer,
70
- beforeLayerId,
71
- );
72
- });
73
- return;
74
- }
75
- const { mbMap } = mapboxLayer;
76
-
77
- Object.keys(icons).forEach((icon) => {
78
- if (!mbMap.getImage(icon)) {
79
- mbMap.loadImage(icons[icon]).then((image) => {
80
- if (!mbMap.getImage(icon)) {
81
- mbMap.addImage(icon, image.data);
82
- }
83
- });
84
- }
85
- });
86
-
87
- // Update source
88
- if (sourceId && sourceData) {
89
- const source = mbMap.getSource(sourceId);
90
- if (source) {
91
- source.setData(sourceData);
92
- } else {
93
- mbMap.addSource(sourceId, {
94
- data: sourceData,
95
- generateId: true,
96
- type: "geojson",
97
- });
98
- }
99
- }
100
-
101
- // Update layer
102
- if (styleLayer) {
103
- let layer = mbMap.getLayer(sourceId);
104
- if (layer) {
105
- mbMap.removeLayer(layer.id);
106
- }
107
-
108
- // styleLayer could be an array of styles to add.
109
- let styleLayers = styleLayer;
110
- if (!Array.isArray(styleLayer)) {
111
- styleLayers = [styleLayer];
112
- }
113
- styleLayers.forEach((style) => {
114
- if (mbMap.getSource(style.source)) {
115
- layer = mbMap.getLayer(style.id);
116
- if (layer) {
117
- mbMap.removeLayer(layer.id);
118
- }
119
- mbMap.addLayer(style, beforeLayerId);
120
- } else {
121
- console.warn(
122
- `The source ${style.source} doesn't exist. This layer can't be added`,
123
- style,
124
- );
125
- }
126
- });
127
- }
128
- };
129
-
130
- export default addSourceAndLayers;