@geops/rvf-mobility-web-component 0.1.22 → 0.1.24-beta.0

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.
@@ -0,0 +1 @@
1
+ export { default } from "./RvfSharedMobilityLayerGroup2";
@@ -1,17 +1,21 @@
1
+ import { GeoJSONSource } from "maplibre-gl";
1
2
  import { Feature, MapBrowserEvent } from "ol";
2
3
  import { GeoJSON } from "ol/format";
3
4
  import { unByKey } from "ol/Observable";
4
5
  import { toLonLat } from "ol/proj";
5
6
  import { useCallback, useEffect } from "preact/hooks";
6
7
 
8
+ import { PROVIDER_LOGOS_BY_FEED_ID } from "../utils/constants";
7
9
  import useMapContext from "../utils/hooks/useMapContext";
8
10
  import useRvfContext from "../utils/hooks/useRvfContext";
9
11
  import MobilityEvent from "../utils/MobilityEvent";
12
+ import { fetchSharingStation } from "../utils/sharingGraphqlUtils";
10
13
 
11
14
  const geojson = new GeoJSON();
12
15
 
13
16
  function SingleClickListener() {
14
17
  const {
18
+ baseLayer,
15
19
  map,
16
20
  realtimeLayer,
17
21
  setStationId,
@@ -21,7 +25,23 @@ function SingleClickListener() {
21
25
  tenant,
22
26
  trainId,
23
27
  } = useMapContext();
24
- const { setSelectedFeature, setSelectedFeatures } = useRvfContext();
28
+ const { selectedFeature, setSelectedFeature, setSelectedFeatures } =
29
+ useRvfContext();
30
+
31
+ // Send the selctedFEature to the parent window
32
+ useEffect(() => {
33
+ if (!map) {
34
+ return;
35
+ }
36
+
37
+ map.getTargetElement().dispatchEvent(
38
+ new MobilityEvent("selectedfeature", {
39
+ feature: selectedFeature
40
+ ? geojson.writeFeatureObject(selectedFeature)
41
+ : null,
42
+ }),
43
+ );
44
+ }, [map, selectedFeature]);
25
45
 
26
46
  const onPointerMove = useCallback(
27
47
  async (evt: MapBrowserEvent<PointerEvent>) => {
@@ -59,6 +79,9 @@ function SingleClickListener() {
59
79
 
60
80
  const onSingleClick = useCallback(
61
81
  async (evt: MapBrowserEvent<PointerEvent>) => {
82
+ if (!baseLayer.mapLibreMap) {
83
+ return;
84
+ }
62
85
  const [realtimeFeature] = evt.map.getFeaturesAtPixel(evt.pixel, {
63
86
  layerFilter: (l) => {
64
87
  return l === realtimeLayer;
@@ -95,26 +118,59 @@ function SingleClickListener() {
95
118
  return l.get("isQueryable");
96
119
  },
97
120
  }) as Feature[];
98
- evt.map.getTargetElement().dispatchEvent(
99
- new MobilityEvent("singleclick", {
100
- ...evt,
101
- features: geojson.writeFeaturesObject(features),
102
- lonlat: toLonLat(evt.coordinate),
103
- }),
104
- );
105
- // feature.get("form_factor"); //free float
106
- // feature.get("num_vehicles_available"); // wfs livedata
107
- // feature.get("provider_name"); // station sharing
108
-
109
- if (newStationId || newTrainId || !features.length) {
110
- setSelectedFeature(null);
111
- setSelectedFeatures([]);
112
- } else {
113
- setSelectedFeatures(features);
114
- setSelectedFeature(features[0]);
115
- }
121
+
122
+ // Append more infos about the features
123
+ const addMoreData = async (features) => {
124
+ for (const feature of features) {
125
+ const clusterId = feature.get("cluster_id");
126
+ if (clusterId) {
127
+ const vtFeat = feature.get("vectorTileFeature");
128
+ const sourceId = vtFeat.layer.source;
129
+ const leaves =
130
+ (await (
131
+ baseLayer.mapLibreMap.getSource(sourceId) as GeoJSONSource
132
+ )?.getClusterLeaves(clusterId, 1000, 0)) || [];
133
+
134
+ feature.set(
135
+ "features",
136
+ leaves.map((l) => {
137
+ return geojson.readFeature(l);
138
+ }),
139
+ );
140
+ }
141
+
142
+ // Sharing station
143
+ const sharingStationId = selectedFeature?.get("station_id");
144
+ if (sharingStationId) {
145
+ const sharingStationInfo =
146
+ await fetchSharingStation(sharingStationId);
147
+ selectedFeature.setProperties(sharingStationInfo);
148
+ selectedFeature.set(
149
+ "provider_logo",
150
+ PROVIDER_LOGOS_BY_FEED_ID[selectedFeature?.get("feed_id")],
151
+ );
152
+ }
153
+ }
154
+ evt.map.getTargetElement().dispatchEvent(
155
+ new MobilityEvent("singleclick", {
156
+ ...evt,
157
+ features: geojson.writeFeaturesObject(features),
158
+ lonlat: toLonLat(evt.coordinate),
159
+ }),
160
+ );
161
+
162
+ if (newStationId || newTrainId || !features.length) {
163
+ setSelectedFeature(null);
164
+ setSelectedFeatures([]);
165
+ } else {
166
+ setSelectedFeatures(features);
167
+ setSelectedFeature(features[0]);
168
+ }
169
+ };
170
+ addMoreData(features);
116
171
  },
117
172
  [
173
+ baseLayer?.mapLibreMap,
118
174
  stationId,
119
175
  trainId,
120
176
  realtimeLayer,
@@ -122,6 +178,7 @@ function SingleClickListener() {
122
178
  tenant,
123
179
  setStationId,
124
180
  setTrainId,
181
+ selectedFeature,
125
182
  setSelectedFeature,
126
183
  setSelectedFeatures,
127
184
  ],
package/src/index.tsx CHANGED
@@ -13,6 +13,7 @@ register(
13
13
  "extent",
14
14
  "maxextent",
15
15
  "geolocation",
16
+ "details",
16
17
  "layers",
17
18
  "layertree",
18
19
  "mapsurl",
@@ -23,7 +24,7 @@ register(
23
24
  "notificationbeforelayerid",
24
25
  "print",
25
26
  "realtime",
26
- "realtimeUrl",
27
+ "realtimeurl",
27
28
  "search",
28
29
  "share",
29
30
  "tenant",
@@ -1,4 +1,13 @@
1
- import { transformExtent } from "ol/proj";
1
+ import { fromLonLat, toLonLat, transformExtent } from "ol/proj";
2
+
3
+ import callBike from "../logos/callabike_logo.png";
4
+ import flinkster from "../logos/flinkster_logo.png";
5
+ import grueneFlotteLogo from "../logos/gruene_flotte_logo.png";
6
+ import lastenVeloLogo from "../logos/lasten_velo_freiburg.png";
7
+ import freloLogo from "../logos/logo_frelo_web_rgb.png";
8
+ import naturEnergieLogo from "../logos/natur_energie_logo.png";
9
+ import yoioLogo from "../logos/yoio_logo.png";
10
+ import zeusLogo from "../logos/zeus_logo.png";
2
11
 
3
12
  export const RVF_EXTENT_4326 = [7.5, 47.7, 8.45, 48.4];
4
13
 
@@ -7,7 +16,12 @@ export const RVF_EXTENT_3857 = transformExtent(
7
16
  "EPSG:4326",
8
17
  "EPSG:3857",
9
18
  );
19
+ console.log(fromLonLat([7.844388, 47.991351]));
10
20
 
21
+ // @ts-expect-error - testsdsd sd
22
+ window.fromLonLat = fromLonLat;
23
+ // @ts-expect-error - testsdsd sd
24
+ window.toLonLat = toLonLat;
11
25
  export const LAYER_PROP_IS_EXPORTING = "isExporting";
12
26
 
13
27
  export const RVF_LAYERS_TITLES = {
@@ -27,7 +41,14 @@ export const RVF_LAYERS_TITLES = {
27
41
 
28
42
  export const RVF_LAYERS_NAMES = {
29
43
  auto: "auto",
44
+ bikeFrelo: "frelo",
45
+ bikeOthers: "bikeothers",
30
46
  cargobike: "cargobike",
47
+ cargobikeFrelo: "cargobikefrelo",
48
+ cargobikeOthers: "cargobikeothers",
49
+ carGrf: "grueneflotte",
50
+ carNatur: "naturenergie",
51
+ carOthers: "carothers",
31
52
  echtzeit: "echtzeit",
32
53
  eroller: "e-roller",
33
54
  fahrrad: "fahrrad",
@@ -91,3 +112,46 @@ export const PROVIDER_BY_FEED_ID = {
91
112
  // "carvelo",
92
113
  // "CarSharing"
93
114
  };
115
+
116
+ export const BIKE_FORM_FACTOR = "bicycle";
117
+ export const CAR_FORM_FACTOR = "car";
118
+ export const CARGOBIKE_FORM_FACTOR = "cargo_bicycle";
119
+ export const SCOOTER_FORM_FACTOR = "scooter";
120
+
121
+ // Station system ids
122
+ // "pickebike_basel",
123
+ // "dott_basel",
124
+ // "gruene-flotte_freiburg",
125
+ // "voi_ch",
126
+ // "nextbike_df",
127
+ // "carvelo2go_ch",
128
+ // "naturenergie_sharing",
129
+ // "lastenvelo_fr",
130
+ // "donkey_neuchatel",
131
+ // "flinkster_carsharing"
132
+ export const GRUNFLOTTE_FEED_ID = "gruene-flotte_freiburg";
133
+ export const NATURENERGIE_FEED_ID = "naturenergie_sharing";
134
+ export const CAR_OTHERS_FEED_IDS = ["flinkster_carsharing"];
135
+
136
+ export const FRELO_FEED_ID = "nextbike_df";
137
+ export const BIKE_OTHERS_FEED_IDS = [
138
+ "callabike_ice", // don't exist in graphql
139
+ "pickebike_basel",
140
+ "donkey_neuchatel",
141
+ ];
142
+
143
+ export const CARGOBIKE_FRELO_ID = "nextbike_df";
144
+ export const CARGOBIKE_OTHERS_FEED_IDS = ["lastenvelo_fr", "carvelo2go_ch"];
145
+
146
+ export const SCOOTER_OTHERS_FEED_IDS = ["voi_ch"];
147
+
148
+ export const PROVIDER_LOGOS_BY_FEED_ID = {
149
+ callabike_ice: callBike,
150
+ flinkster_carsharing: flinkster,
151
+ "gruene-flotte_freiburg": grueneFlotteLogo,
152
+ lastenvelo_fr: lastenVeloLogo,
153
+ naturenergie_sharing: naturEnergieLogo,
154
+ nextbike_df: freloLogo,
155
+ yoio_freiburg: yoioLogo,
156
+ zeus_freiburg: zeusLogo,
157
+ };
@@ -57,7 +57,7 @@ function createFreeFloatMobilityLayer(
57
57
  "MobiData-BW:" +
58
58
  name +
59
59
  "&" +
60
- "outputFormat=application/json&srsname=EPSG:3857&" +
60
+ "outputFormat=application/json" +
61
61
  "bbox=" +
62
62
  extent.join(",") +
63
63
  ",EPSG:3857";
@@ -72,7 +72,10 @@ function createFreeFloatMobilityLayer(
72
72
  if (xhr.status == 200) {
73
73
  const features = source
74
74
  .getFormat()
75
- .readFeatures(xhr.responseText)
75
+ .readFeatures(xhr.responseText, {
76
+ dataProjection: "EPSG:4326",
77
+ featureProjection: "EPSG:3857",
78
+ })
76
79
  ?.filter((feature) => {
77
80
  if (formFactor) {
78
81
  return feature.get("form_factor") === formFactor;
@@ -78,11 +78,14 @@ function createMobiDataBwWfsLayer(
78
78
  name: string,
79
79
  color: string,
80
80
  layerOptions: Options = {
81
- minZoom: 18,
81
+ minZoom: 0,
82
82
  },
83
83
  ): VectorLayer<Vector<Feature<Point>>> {
84
84
  const source = new Vector({
85
- format: new GeoJSON(),
85
+ format: new GeoJSON({
86
+ dataProjection: "EPSG:4326",
87
+ featureProjection: "EPSG:3857",
88
+ }),
86
89
  strategy: bboxStrategy,
87
90
  url: function (extent) {
88
91
  return (
@@ -94,7 +97,7 @@ function createMobiDataBwWfsLayer(
94
97
  "MobiData-BW:" +
95
98
  name +
96
99
  "&" +
97
- "outputFormat=application/json&srsname=EPSG:3857&" +
100
+ "outputFormat=application/json&" +
98
101
  "bbox=" +
99
102
  extent.join(",") +
100
103
  ",EPSG:3857"
@@ -0,0 +1,313 @@
1
+ import { FeatureCollection, Point } from "geojson";
2
+ import { gql, GraphQLClient } from "graphql-request";
3
+ import { Extent } from "ol/extent";
4
+
5
+ import { RVF_EXTENT_4326 } from "./constants";
6
+
7
+ const GQL_URL = "https://api.mobidata-bw.de/sharing/graphql";
8
+
9
+ export interface SharingStation {
10
+ id: string;
11
+ lat: number;
12
+ lon: number;
13
+ name: {
14
+ translation: {
15
+ language: string;
16
+ value: string;
17
+ };
18
+ };
19
+ numVehiclesAvailable: number;
20
+ system: {
21
+ id: string;
22
+ name: {
23
+ translation: {
24
+ language: string;
25
+ value: string;
26
+ };
27
+ };
28
+ operator: {
29
+ id: string;
30
+ name: {
31
+ translation: {
32
+ language: string;
33
+ value: string;
34
+ };
35
+ };
36
+ };
37
+ shortName: {
38
+ translation: {
39
+ language: string;
40
+ value: string;
41
+ };
42
+ };
43
+ };
44
+ }
45
+
46
+ const queryStation = gql`
47
+ query station($id: String!) {
48
+ station(id: $id) {
49
+ name {
50
+ translation {
51
+ language
52
+ value
53
+ }
54
+ }
55
+ shortName {
56
+ translation {
57
+ language
58
+ value
59
+ }
60
+ }
61
+ lat
62
+ lon
63
+ region {
64
+ id
65
+ }
66
+ rentalUris {
67
+ android
68
+ ios
69
+ web
70
+ }
71
+ isVirtualStation
72
+ rentalMethods
73
+ parkingHoop
74
+ parkingType
75
+ contactPhone
76
+ address
77
+ rentalMethods
78
+ capacity
79
+ vehicleTypesAvailable {
80
+ count
81
+ vehicleType {
82
+ id
83
+ formFactor
84
+ riderCapacity
85
+ cargoVolumeCapacity
86
+ cargoLoadCapacity
87
+ propulsionType
88
+ ecoLabels {
89
+ countryCode
90
+ ecoSticker
91
+ }
92
+ maxRangeMeters
93
+ name {
94
+ translation {
95
+ language
96
+ value
97
+ }
98
+ }
99
+ description {
100
+ translation {
101
+ language
102
+ value
103
+ }
104
+ }
105
+ vehicleAccessories
106
+ gCO2km
107
+ vehicleImage
108
+ make
109
+ model
110
+ color
111
+ wheelCount
112
+ maxPermittedSpeed
113
+ ratedPower
114
+ defaultReserveTime
115
+ returnConstraint
116
+ vehicleAssets {
117
+ iconUrl
118
+ iconUrlDark
119
+ iconLastModified
120
+ }
121
+ defaultReserveTime
122
+ defaultPricingPlan {
123
+ id
124
+ url
125
+ currency
126
+ isTaxable
127
+ description {
128
+ translation {
129
+ language
130
+ value
131
+ }
132
+ }
133
+ perKmPricing {
134
+ start
135
+ rate
136
+ interval
137
+ end
138
+ }
139
+ perMinPricing {
140
+ start
141
+ rate
142
+ interval
143
+ end
144
+ }
145
+ surgePricing
146
+ }
147
+ }
148
+ count
149
+ }
150
+ vehicleDocksCapacity {
151
+ vehicleTypes {
152
+ id
153
+ }
154
+ count
155
+ }
156
+ numVehiclesAvailable
157
+ numDocksDisabled
158
+ numVehiclesDisabled
159
+ numDocksAvailable
160
+ isInstalled
161
+ isRenting
162
+ isReturning
163
+ pricingPlans {
164
+ id
165
+ url
166
+ currency
167
+ isTaxable
168
+ description {
169
+ translation {
170
+ language
171
+ value
172
+ }
173
+ }
174
+ perKmPricing {
175
+ start
176
+ rate
177
+ interval
178
+ end
179
+ }
180
+ perMinPricing {
181
+ start
182
+ rate
183
+ interval
184
+ end
185
+ }
186
+ surgePricing
187
+ }
188
+ }
189
+ }
190
+ `;
191
+ const queryStations = gql`
192
+ query stations(
193
+ $ids: [String!]
194
+ $minimumLatitude: Float
195
+ $minimumLongitude: Float
196
+ $maximumLatitude: Float
197
+ $maximumLongitude: Float
198
+ $systems: [System!]
199
+ $operators: [Operator!]
200
+ $availableFormFactors: [FormFactor!]
201
+ ) {
202
+ stations(
203
+ ids: $ids
204
+ minimumLatitude: $minimumLatitude
205
+ minimumLongitude: $minimumLongitude
206
+ maximumLatitude: $maximumLatitude
207
+ maximumLongitude: $maximumLongitude
208
+ operators: $operators
209
+ availableFormFactors: $availableFormFactors
210
+ ) {
211
+ lon
212
+ id
213
+ lat
214
+ name {
215
+ translation {
216
+ language
217
+ value
218
+ }
219
+ }
220
+ numVehiclesAvailable
221
+ system {
222
+ id
223
+ name {
224
+ translation {
225
+ language
226
+ value
227
+ }
228
+ }
229
+ shortName {
230
+ translation {
231
+ language
232
+ value
233
+ }
234
+ }
235
+ operator {
236
+ id
237
+ name {
238
+ translation {
239
+ language
240
+ value
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ `;
248
+
249
+ export const fetchSharingStation = async (id: string) => {
250
+ const client = new GraphQLClient(GQL_URL, {
251
+ method: "GET",
252
+ });
253
+ const { station }: { station: unknown } = await client.request(queryStation, {
254
+ __operation: "station",
255
+ id: id,
256
+ });
257
+ return station;
258
+ };
259
+
260
+ export const fetchSharingStations = async (
261
+ ids: null | string[],
262
+ extent: Extent = RVF_EXTENT_4326,
263
+ operators: null | string[] = null,
264
+ abortController: AbortController,
265
+ ) => {
266
+ const client = new GraphQLClient(
267
+ "https://api.mobidata-bw.de/sharing/graphql",
268
+ {
269
+ method: "GET",
270
+ signal: abortController.signal,
271
+ },
272
+ );
273
+ const { stations }: { stations: SharingStation[] } = await client.request(
274
+ queryStations,
275
+ {
276
+ // availableFormFactors: ["BICYCLE"],
277
+ // id: "NBK:Station:33269707",
278
+ ids: ids,
279
+ maximumLatitude: extent?.[3],
280
+ maximumLongitude: extent?.[2],
281
+ minimumLatitude: extent?.[1],
282
+ minimumLongitude: extent?.[0],
283
+ operators: operators, //["BK:Operator:nextbike"],
284
+ },
285
+ );
286
+ const featureCollection: FeatureCollection<Point, SharingStation> = {
287
+ features: stations.map((station: SharingStation) => {
288
+ console.log("station", station);
289
+ return {
290
+ geometry: {
291
+ coordinates: [station.lon, station.lat],
292
+ type: "Point",
293
+ },
294
+ properties: {
295
+ ...station,
296
+ num_vehicles_available: station.numVehiclesAvailable,
297
+ provider_name: station.system.name.translation[0].value.replace(
298
+ "Freiburg",
299
+ "",
300
+ ),
301
+ station_id: station.id,
302
+ },
303
+ type: "Feature",
304
+ };
305
+ }),
306
+ type: "FeatureCollection",
307
+ };
308
+ return featureCollection;
309
+ };
310
+
311
+ export default {
312
+ fetchSharingStations,
313
+ };