@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/src/utils/constants.ts
CHANGED
|
@@ -9,3 +9,33 @@ export const RVF_EXTENT_3857 = transformExtent(
|
|
|
9
9
|
);
|
|
10
10
|
|
|
11
11
|
export const LAYER_PROP_IS_EXPORTING = "isExporting";
|
|
12
|
+
|
|
13
|
+
export const RVF_LAYERS_NAMES = {
|
|
14
|
+
auto: "auto",
|
|
15
|
+
cargobike: "cargobike",
|
|
16
|
+
echtzeit: "echtzeit",
|
|
17
|
+
eroller: "e-roller",
|
|
18
|
+
fahrrad: "fahrrad",
|
|
19
|
+
haltestellen: "haltestellen",
|
|
20
|
+
liniennetz: "liniennetz",
|
|
21
|
+
mitfahrpunkte: "mitfahrpunkte",
|
|
22
|
+
pois: "pois",
|
|
23
|
+
sharedMobility: "shared mobility",
|
|
24
|
+
tarifzonen: "tarifzonen",
|
|
25
|
+
verkaufsstellen: "verkaufsstellen",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const API_REQUEST_FEATURE_TYPE = {
|
|
29
|
+
freeFloat: {
|
|
30
|
+
bike: "sharing_vehicles",
|
|
31
|
+
car: "sharing_vehicles",
|
|
32
|
+
cargoBike: "sharing_vehicles",
|
|
33
|
+
scooter: "sharing_vehicles",
|
|
34
|
+
},
|
|
35
|
+
stations: {
|
|
36
|
+
bike: "sharing_stations_bicycle",
|
|
37
|
+
car: "sharing_stations_car",
|
|
38
|
+
cargoBike: "sharing_stations_cargo_bicycle",
|
|
39
|
+
scooter: "sharing_stations_scooters_standing",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -4,26 +4,38 @@ import { Point } from "ol/geom";
|
|
|
4
4
|
import VectorLayer, { Options } from "ol/layer/Vector";
|
|
5
5
|
import { bbox as bboxStrategy } from "ol/loadingstrategy.js";
|
|
6
6
|
import { Cluster, Vector } from "ol/source";
|
|
7
|
-
import { Circle, Fill,
|
|
7
|
+
import { Circle, Fill, Stroke, Style, Text } from "ol/style";
|
|
8
8
|
|
|
9
|
-
// @ts-expect-error - load svg as data url
|
|
10
|
-
import bicycle from "../icons/Bike/rvf_shared_bike.svg";
|
|
11
|
-
// @ts-expect-error - load svg as data url
|
|
12
|
-
import car from "../icons/Car/rvf_shared_car.svg";
|
|
13
|
-
// @ts-expect-error - load svg as data url
|
|
14
|
-
import cargoBicycle from "../icons/CargoBike/rvf_shared_cargo_bike.svg";
|
|
15
|
-
// @ts-expect-error - load svg as data url
|
|
16
|
-
import scooter from "../icons/Scooter/rvf_shared_scooter.svg";
|
|
17
9
|
import { LAYER_PROP_IS_EXPORTING } from "./constants";
|
|
10
|
+
import { iconStyleByFormFactor } from "./createMobiDataBwWfsLayer";
|
|
18
11
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
export const getCircleStyle = (color: string) => {
|
|
13
|
+
return new Style({
|
|
14
|
+
image: new Circle({
|
|
15
|
+
declutterMode: "declutter",
|
|
16
|
+
displacement: [-12, 12],
|
|
17
|
+
fill: new Fill({
|
|
18
|
+
color: "white",
|
|
19
|
+
}),
|
|
20
|
+
radius: 9,
|
|
21
|
+
stroke: new Stroke({
|
|
22
|
+
color: color,
|
|
23
|
+
width: 1,
|
|
24
|
+
}),
|
|
25
|
+
}),
|
|
26
|
+
text: new Text({
|
|
27
|
+
declutterMode: "declutter",
|
|
28
|
+
fill: new Fill({
|
|
29
|
+
color: color,
|
|
30
|
+
}),
|
|
31
|
+
font: "bolder 10px arial",
|
|
32
|
+
offsetX: -12,
|
|
33
|
+
offsetY: -11,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
24
36
|
};
|
|
25
37
|
|
|
26
|
-
function
|
|
38
|
+
function createFreeFloatMobilityLayer(
|
|
27
39
|
name: string,
|
|
28
40
|
color: string,
|
|
29
41
|
formFactor: string,
|
|
@@ -31,6 +43,8 @@ function createSharedMobilityLayer(
|
|
|
31
43
|
minZoom: 17.99,
|
|
32
44
|
},
|
|
33
45
|
): VectorLayer<Vector<Feature<Point>>> {
|
|
46
|
+
// Exemple how to get a specific form factor
|
|
47
|
+
//api.mobidata-bw.de/geoserver/MobiData-BW/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=MobiData-BW%3Asharing_vehicles&CQL_FILTER=form_factor%20%3D%20%27scooter%27&maxFeatures=20000&outputFormat=csv
|
|
34
48
|
const source = new Vector({
|
|
35
49
|
format: new GeoJSON(),
|
|
36
50
|
loader: function (extent, resolution, projection, success, failure) {
|
|
@@ -88,54 +102,7 @@ function createSharedMobilityLayer(
|
|
|
88
102
|
strategy: bboxStrategy,
|
|
89
103
|
});
|
|
90
104
|
|
|
91
|
-
const style =
|
|
92
|
-
image: new Circle({
|
|
93
|
-
declutterMode: "declutter",
|
|
94
|
-
displacement: [-12, 10],
|
|
95
|
-
fill: new Fill({
|
|
96
|
-
color: "white",
|
|
97
|
-
}),
|
|
98
|
-
radius: 10,
|
|
99
|
-
stroke: new Stroke({
|
|
100
|
-
color: color,
|
|
101
|
-
width: 2,
|
|
102
|
-
}),
|
|
103
|
-
}),
|
|
104
|
-
text: new Text({
|
|
105
|
-
declutterMode: "declutter",
|
|
106
|
-
fill: new Fill({
|
|
107
|
-
color: color,
|
|
108
|
-
}),
|
|
109
|
-
font: "bold 12px inherit",
|
|
110
|
-
offsetX: -12,
|
|
111
|
-
offsetY: -10,
|
|
112
|
-
}),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const iconStyleByFormFactor = {};
|
|
116
|
-
Object.entries(iconByFormFactor).forEach(([key, value]) => {
|
|
117
|
-
iconStyleByFormFactor[key] = new Style({
|
|
118
|
-
image: new Icon({
|
|
119
|
-
src: value || iconByFormFactor["scooter"],
|
|
120
|
-
width: 25,
|
|
121
|
-
}),
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// const backgroundStyle = new Style({
|
|
126
|
-
// image: new Circle({
|
|
127
|
-
// declutterMode: "declutter",
|
|
128
|
-
// displacement: [0, 0],
|
|
129
|
-
// fill: new Fill({
|
|
130
|
-
// color: "#00973c",
|
|
131
|
-
// }),
|
|
132
|
-
// radius: 12,
|
|
133
|
-
// }),
|
|
134
|
-
// stroke: new Stroke({
|
|
135
|
-
// color: "white",
|
|
136
|
-
// width: 2,
|
|
137
|
-
// }),
|
|
138
|
-
// });
|
|
105
|
+
const style = getCircleStyle(color);
|
|
139
106
|
|
|
140
107
|
const clusterSource = new Cluster({
|
|
141
108
|
distance: 40,
|
|
@@ -146,21 +113,26 @@ function createSharedMobilityLayer(
|
|
|
146
113
|
if (layer.get(LAYER_PROP_IS_EXPORTING)) {
|
|
147
114
|
return null;
|
|
148
115
|
}
|
|
149
|
-
const
|
|
116
|
+
const circleStyle = style.clone();
|
|
150
117
|
|
|
151
|
-
|
|
152
|
-
|
|
118
|
+
const featuresCount = feature.get("features").length;
|
|
119
|
+
circleStyle.getText().setText(featuresCount);
|
|
153
120
|
|
|
154
121
|
const formFactor =
|
|
155
122
|
feature.get("features")?.[0].get?.("form_factor") || "scooter";
|
|
156
123
|
|
|
157
|
-
|
|
124
|
+
const styles = [iconStyleByFormFactor[formFactor], circleStyle];
|
|
125
|
+
|
|
126
|
+
styles.forEach((style) => {
|
|
127
|
+
style.setZIndex(parseInt(getUid(feature), 10));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return styles;
|
|
158
131
|
};
|
|
159
132
|
|
|
160
133
|
const layer = new VectorLayer({
|
|
161
134
|
// @ts-expect-error - custom properties
|
|
162
135
|
isQueryable: true,
|
|
163
|
-
name,
|
|
164
136
|
source: clusterSource,
|
|
165
137
|
style: styleFunction,
|
|
166
138
|
...layerOptions,
|
|
@@ -173,4 +145,4 @@ function createSharedMobilityLayer(
|
|
|
173
145
|
return layer;
|
|
174
146
|
}
|
|
175
147
|
|
|
176
|
-
export default
|
|
148
|
+
export default createFreeFloatMobilityLayer;
|
|
@@ -4,15 +4,98 @@ import { Point } from "ol/geom";
|
|
|
4
4
|
import VectorLayer, { Options } from "ol/layer/Vector";
|
|
5
5
|
import { bbox as bboxStrategy } from "ol/loadingstrategy.js";
|
|
6
6
|
import { Vector } from "ol/source";
|
|
7
|
-
import { Circle, Fill, Stroke, Style, Text } from "ol/style";
|
|
7
|
+
import { Circle, Fill, Icon, Stroke, Style, Text } from "ol/style";
|
|
8
8
|
|
|
9
|
+
// @ts-expect-error - load svg as data url
|
|
10
|
+
import bicycle from "../icons/Bike/rvf_shared_bike.svg";
|
|
11
|
+
// @ts-expect-error - load svg as data url
|
|
12
|
+
import car from "../icons/Car/rvf_shared_car.svg";
|
|
13
|
+
// @ts-expect-error - load svg as data url
|
|
14
|
+
import cargo_bicycle from "../icons/CargoBike/rvf_shared_cargo_bike.svg";
|
|
15
|
+
// @ts-expect-error - load svg as data url
|
|
16
|
+
import scooter from "../icons/Scooter/rvf_shared_scooter.svg";
|
|
9
17
|
import { LAYER_PROP_IS_EXPORTING } from "./constants";
|
|
10
18
|
|
|
19
|
+
const iconByFormFactor = {
|
|
20
|
+
bicycle,
|
|
21
|
+
car,
|
|
22
|
+
cargo_bicycle,
|
|
23
|
+
scooter,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const iconStyleByFormFactor = {};
|
|
27
|
+
Object.entries(iconByFormFactor).forEach(([key, value]) => {
|
|
28
|
+
iconStyleByFormFactor[key] = new Style({
|
|
29
|
+
image: new Icon({
|
|
30
|
+
src: value,
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const getCircleStyle = (color: string) => {
|
|
36
|
+
return new Style({
|
|
37
|
+
image: new Circle({
|
|
38
|
+
displacement: [12, 12],
|
|
39
|
+
fill: new Fill({
|
|
40
|
+
color: "white",
|
|
41
|
+
}),
|
|
42
|
+
radius: 9,
|
|
43
|
+
stroke: new Stroke({
|
|
44
|
+
color: color,
|
|
45
|
+
width: 1,
|
|
46
|
+
}),
|
|
47
|
+
}),
|
|
48
|
+
text: new Text({
|
|
49
|
+
fill: new Fill({
|
|
50
|
+
color: color,
|
|
51
|
+
}),
|
|
52
|
+
font: "bolder 10px arial",
|
|
53
|
+
offsetX: 12,
|
|
54
|
+
offsetY: -11,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getCompanyNameTextStyle = (feedId: string) => {
|
|
60
|
+
const offsetY =
|
|
61
|
+
feedId === "naturenergie_sharing" || feedId === "gruene_flotte_freiburg"
|
|
62
|
+
? 25
|
|
63
|
+
: 20;
|
|
64
|
+
return new Style({
|
|
65
|
+
text: new Text({
|
|
66
|
+
declutterMode: "declutter",
|
|
67
|
+
fill: new Fill({ color: "rgba(6, 76, 10, 1)" }),
|
|
68
|
+
font: "bold 12px/1.1 source-sans-pro",
|
|
69
|
+
offsetY,
|
|
70
|
+
stroke: new Stroke({ color: "white", width: 4 }),
|
|
71
|
+
text: companyTextByFeedId[feedId] || feedId,
|
|
72
|
+
// textBaseline: "middle",
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const companyTextByFeedId = {
|
|
78
|
+
callabike_ice: "Call a Bike",
|
|
79
|
+
flinkster_carsharing: "Flinkster",
|
|
80
|
+
gruene_flotte_freiburg: "Die Grüne\nFlotte",
|
|
81
|
+
lastenvelo_fr: "LastenVelo",
|
|
82
|
+
naturenergie_sharing: "naturenergie\nsharing",
|
|
83
|
+
nextbike_df: "Frelo",
|
|
84
|
+
// "nextbike",
|
|
85
|
+
// "EinfachMobil",
|
|
86
|
+
// "Donkey Republic"
|
|
87
|
+
// "PubliBike",
|
|
88
|
+
// "Voi",
|
|
89
|
+
// "Velospot",
|
|
90
|
+
// "carvelo",
|
|
91
|
+
// "CarSharing"
|
|
92
|
+
};
|
|
93
|
+
|
|
11
94
|
function createMobiDataBwWfsLayer(
|
|
12
95
|
name: string,
|
|
13
96
|
color: string,
|
|
14
97
|
layerOptions: Options = {
|
|
15
|
-
minZoom:
|
|
98
|
+
minZoom: 18,
|
|
16
99
|
},
|
|
17
100
|
): VectorLayer<Vector<Feature<Point>>> {
|
|
18
101
|
const source = new Vector({
|
|
@@ -36,86 +119,44 @@ function createMobiDataBwWfsLayer(
|
|
|
36
119
|
},
|
|
37
120
|
});
|
|
38
121
|
|
|
39
|
-
const style =
|
|
40
|
-
image: new Circle({
|
|
41
|
-
declutterMode: "declutter",
|
|
42
|
-
displacement: [11, 12],
|
|
43
|
-
fill: new Fill({
|
|
44
|
-
color: "white",
|
|
45
|
-
}),
|
|
46
|
-
radius: 10,
|
|
47
|
-
stroke: new Stroke({
|
|
48
|
-
color: color,
|
|
49
|
-
width: 2,
|
|
50
|
-
}),
|
|
51
|
-
}),
|
|
52
|
-
text: new Text({
|
|
53
|
-
declutterMode: "declutter",
|
|
54
|
-
fill: new Fill({
|
|
55
|
-
color: color,
|
|
56
|
-
}),
|
|
57
|
-
font: "bold 12px inherit",
|
|
58
|
-
offsetX: 11,
|
|
59
|
-
offsetY: -12,
|
|
60
|
-
// stroke: new Stroke({ color: "black", width: 1 }),
|
|
61
|
-
// text: ["to-string", ["get", "num_vehicles_available"]],
|
|
62
|
-
}),
|
|
63
|
-
});
|
|
122
|
+
const style = getCircleStyle(color);
|
|
64
123
|
|
|
65
124
|
const layer = new VectorLayer({
|
|
66
125
|
// @ts-expect-error - custom properties
|
|
67
126
|
isQueryable: true,
|
|
68
|
-
name,
|
|
69
127
|
// declutter: true,
|
|
70
128
|
source: source,
|
|
71
129
|
...layerOptions,
|
|
72
|
-
// style: (feature) => {
|
|
73
|
-
// console.log("feature", feature);
|
|
74
|
-
// style
|
|
75
|
-
// .getText()
|
|
76
|
-
// .setText(feature.get("num_vehicles_available").toString());
|
|
77
|
-
// return style;
|
|
78
|
-
// },
|
|
79
|
-
|
|
80
|
-
// style: {
|
|
81
|
-
// "circle-fill-color": "white",
|
|
82
|
-
// "circle-radius": 15,
|
|
83
|
-
// "circle-stroke-color": color,
|
|
84
|
-
// "circle-stroke-width": 2,
|
|
85
|
-
// // "fill-color": "rgba(100,100,100,0.25)",
|
|
86
|
-
// // "stroke-color": "white",
|
|
87
|
-
// // "stroke-width": 0.75,
|
|
88
|
-
// // "text-background-fill-color": "black",
|
|
89
|
-
// "text-fill-color": color,
|
|
90
|
-
// "text-font": "bold 12px sans-serif",
|
|
91
|
-
// "text-stroke-color": color,
|
|
92
|
-
// "text-value": ["to-string", ["get", "num_vehicles_available"]],
|
|
93
|
-
// },
|
|
94
130
|
});
|
|
95
131
|
|
|
96
132
|
const styleFunction = (feature: Feature) => {
|
|
97
133
|
if (layer.get(LAYER_PROP_IS_EXPORTING)) {
|
|
98
134
|
return null;
|
|
99
135
|
}
|
|
100
|
-
const
|
|
136
|
+
const circleStyle = style.clone();
|
|
101
137
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// }),
|
|
138
|
+
const numVehiclesAvailable = feature
|
|
139
|
+
.get("num_vehicles_available")
|
|
140
|
+
?.toString();
|
|
141
|
+
circleStyle.getText().setText(numVehiclesAvailable);
|
|
142
|
+
|
|
143
|
+
const formFactor = (feature.getId() as string)
|
|
144
|
+
.split(".")[0]
|
|
145
|
+
.replace("sharing_stations_", "");
|
|
146
|
+
|
|
147
|
+
const feedId = feature.get("feed_id").replace("-", "_"); // to handle id's like gruene-flotte_freiburg
|
|
148
|
+
|
|
149
|
+
const styles = [
|
|
150
|
+
iconStyleByFormFactor[formFactor],
|
|
151
|
+
getCompanyNameTextStyle(feedId),
|
|
152
|
+
circleStyle,
|
|
118
153
|
];
|
|
154
|
+
|
|
155
|
+
styles.forEach((style) => {
|
|
156
|
+
style.setZIndex(parseInt(getUid(feature), 10));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return styles;
|
|
119
160
|
};
|
|
120
161
|
|
|
121
162
|
source.on("addfeature", function (event) {
|
package/src/utils/exportPdf.ts
CHANGED
|
@@ -554,9 +554,7 @@ async function exportPdf(
|
|
|
554
554
|
const size = sizePt.map((n) => {
|
|
555
555
|
return (n * 96) / 72;
|
|
556
556
|
});
|
|
557
|
-
const extent = useMaxExtent
|
|
558
|
-
? RVF_EXTENT_3857
|
|
559
|
-
: map.getView().calculateExtent();
|
|
557
|
+
const extent = useMaxExtent ? RVF_EXTENT_3857 : null; //map.getView().calculateExtent();
|
|
560
558
|
|
|
561
559
|
// Save current pixel ratio
|
|
562
560
|
const actualPixelRatio = window.devicePixelRatio;
|
|
@@ -602,7 +600,6 @@ async function exportPdf(
|
|
|
602
600
|
const mapToExport = await createMapToExport(map, layers, extent, size, {
|
|
603
601
|
pixelRatio: pixelRatio,
|
|
604
602
|
});
|
|
605
|
-
|
|
606
603
|
Object.defineProperty(window, "devicePixelRatio", {
|
|
607
604
|
get() {
|
|
608
605
|
return actualPixelRatio;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Group } from "ol/layer";
|
|
2
|
+
import BaseLayer from "ol/layer/Base";
|
|
3
|
+
|
|
4
|
+
const getLayersAsFlatArray = (
|
|
5
|
+
layersOrLayer: BaseLayer | BaseLayer[],
|
|
6
|
+
): BaseLayer[] => {
|
|
7
|
+
let layers = layersOrLayer;
|
|
8
|
+
if (!Array.isArray(layers)) {
|
|
9
|
+
layers = [layersOrLayer as BaseLayer];
|
|
10
|
+
}
|
|
11
|
+
let flatLayers: BaseLayer[] = [];
|
|
12
|
+
layers.forEach((layer: BaseLayer) => {
|
|
13
|
+
flatLayers.push(layer);
|
|
14
|
+
// Handle children property and ol.layer.Group
|
|
15
|
+
const children =
|
|
16
|
+
layer.get("children") || (layer as Group).getLayers?.()?.getArray();
|
|
17
|
+
flatLayers = flatLayers.concat(getLayersAsFlatArray(children || []));
|
|
18
|
+
});
|
|
19
|
+
return flatLayers;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default getLayersAsFlatArray;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Feature } from "ol";
|
|
2
|
+
|
|
3
|
+
function getLinkByDevice(feature: Feature): string {
|
|
4
|
+
const iosLink = feature.get("rental_uris_ios");
|
|
5
|
+
const androidLink = feature.get("rental_uris_android");
|
|
6
|
+
const webLink = feature.get("rental_uris_web");
|
|
7
|
+
if (
|
|
8
|
+
navigator.userAgent.includes("iPhone") ||
|
|
9
|
+
navigator.userAgent.includes("iPad") ||
|
|
10
|
+
navigator.userAgent.includes("iPod")
|
|
11
|
+
) {
|
|
12
|
+
if (iosLink) {
|
|
13
|
+
return iosLink;
|
|
14
|
+
} else {
|
|
15
|
+
return webLink;
|
|
16
|
+
}
|
|
17
|
+
} else if (navigator.userAgent.includes("Android")) {
|
|
18
|
+
if (androidLink) {
|
|
19
|
+
return androidLink;
|
|
20
|
+
} else {
|
|
21
|
+
return webLink;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return webLink;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default getLinkByDevice;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Map } from "ol";
|
|
2
|
+
|
|
3
|
+
import getLayersAsFlatArray from "./getLayersAsFlatArray";
|
|
4
|
+
|
|
5
|
+
// This function return URL parameters representing a map.
|
|
6
|
+
const getPermalinkParameters = (
|
|
7
|
+
map: Map,
|
|
8
|
+
urlParams: URLSearchParams = new URLSearchParams(),
|
|
9
|
+
): URLSearchParams => {
|
|
10
|
+
const z = map.getView().getZoom();
|
|
11
|
+
const [x, y] = map.getView().getCenter();
|
|
12
|
+
const layers = getLayersAsFlatArray(map.getLayers().getArray())
|
|
13
|
+
.filter((layer) => {
|
|
14
|
+
return layer.get("name") && layer.getVisible();
|
|
15
|
+
})
|
|
16
|
+
.map((layer) => {
|
|
17
|
+
return layer.get("name");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
urlParams.set("layers", layers.join(","));
|
|
21
|
+
urlParams.set("x", x.toFixed(2));
|
|
22
|
+
urlParams.set("y", y.toFixed(2));
|
|
23
|
+
urlParams.set("z", z.toFixed(1));
|
|
24
|
+
return urlParams;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default getPermalinkParameters;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Map } from "ol";
|
|
2
|
+
import { unByKey } from "ol/Observable";
|
|
3
|
+
import { useEffect } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import applyInitialLayerVisibility from "../applyInitialLayerVisibility";
|
|
6
|
+
import getLayersAsFlatArray from "../getLayersAsFlatArray";
|
|
7
|
+
|
|
8
|
+
const useInitialLayersVisiblity = (map: Map, layers: string) => {
|
|
9
|
+
// Apply initial visibility of layers from layers attribute
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!map) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
getLayersAsFlatArray(map.getLayers().getArray()).forEach((layer) => {
|
|
15
|
+
applyInitialLayerVisibility(layers, layer);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const key = map.getLayers().on("add", (event) => {
|
|
19
|
+
applyInitialLayerVisibility(layers, event.element);
|
|
20
|
+
});
|
|
21
|
+
return () => {
|
|
22
|
+
unByKey(key);
|
|
23
|
+
};
|
|
24
|
+
}, [map, layers]);
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default useInitialLayersVisiblity;
|
|
@@ -21,6 +21,7 @@ export type MapContextType = {
|
|
|
21
21
|
baseLayer: MaplibreLayer;
|
|
22
22
|
isFollowing: boolean;
|
|
23
23
|
isTracking: boolean;
|
|
24
|
+
layers: string;
|
|
24
25
|
map: Map;
|
|
25
26
|
realtimeLayer: RealtimeLayer;
|
|
26
27
|
setBaseLayer: (baseLayer: MaplibreLayer) => void;
|
|
@@ -44,6 +45,7 @@ export const MapContext = createContext<MapContextType>({
|
|
|
44
45
|
baseLayer: null,
|
|
45
46
|
isFollowing: false,
|
|
46
47
|
isTracking: false,
|
|
48
|
+
layers: null,
|
|
47
49
|
map: null,
|
|
48
50
|
realtimeLayer: null,
|
|
49
51
|
setBaseLayer: (baseLayer?: MaplibreLayer) => {},
|
|
@@ -1,26 +1,59 @@
|
|
|
1
|
+
import debounce from "lodash.debounce";
|
|
1
2
|
import { Map } from "ol";
|
|
3
|
+
import { EventsKey } from "ol/events";
|
|
2
4
|
import { unByKey } from "ol/Observable";
|
|
3
5
|
import { useEffect } from "preact/hooks";
|
|
4
6
|
|
|
7
|
+
import getLayersAsFlatArray from "../getLayersAsFlatArray";
|
|
8
|
+
import getPermalinkParameters from "../getPermalinkParameters";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This hook only update parameters in the url, it does not apply the url parameters.
|
|
12
|
+
*/
|
|
13
|
+
|
|
5
14
|
const useUpdatePermalink = (map: Map, permalink: boolean) => {
|
|
6
15
|
useEffect(() => {
|
|
7
|
-
let
|
|
16
|
+
let moveEndKey: EventsKey;
|
|
17
|
+
let loadEndKey: EventsKey;
|
|
18
|
+
let changeVisibleKeys: EventsKey[];
|
|
19
|
+
|
|
8
20
|
if (map && permalink) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
updatePermalinkDebounced(map);
|
|
22
|
+
|
|
23
|
+
// Update x,y,z in URL on moveend
|
|
24
|
+
moveEndKey = map?.on("moveend", (evt) => {
|
|
25
|
+
updatePermalinkDebounced(evt.map);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Update layers in URL on change:visible event
|
|
29
|
+
loadEndKey = map.once("loadend", (evt) => {
|
|
30
|
+
updatePermalinkDebounced(evt.map);
|
|
31
|
+
changeVisibleKeys = getLayersAsFlatArray(
|
|
32
|
+
evt.map.getLayers().getArray(),
|
|
33
|
+
).map((layer) => {
|
|
34
|
+
return layer.on("change:visible", () => {
|
|
35
|
+
updatePermalinkDebounced(evt.map);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
18
38
|
});
|
|
19
39
|
}
|
|
40
|
+
|
|
20
41
|
return () => {
|
|
21
|
-
unByKey(
|
|
42
|
+
unByKey(moveEndKey);
|
|
43
|
+
unByKey(loadEndKey);
|
|
44
|
+
unByKey(changeVisibleKeys);
|
|
22
45
|
};
|
|
23
46
|
}, [map, permalink]);
|
|
24
47
|
return null;
|
|
25
48
|
};
|
|
49
|
+
|
|
50
|
+
const updatePermalink = (map: Map) => {
|
|
51
|
+
const currentUrlParams = new URLSearchParams(window.location.search);
|
|
52
|
+
const urlParams = getPermalinkParameters(map, currentUrlParams);
|
|
53
|
+
urlParams.set("permalink", "true");
|
|
54
|
+
window.history.replaceState(null, null, `?${urlParams.toString()}`);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const updatePermalinkDebounced = debounce(updatePermalink, 1000);
|
|
58
|
+
|
|
26
59
|
export default useUpdatePermalink;
|