@mapcomponents/react-maplibre 0.1.13 → 0.1.17

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 (100) hide show
  1. package/.github/workflows/storybook.yml +4 -2
  2. package/CHANGELOG.md +33 -0
  3. package/README.md +22 -6
  4. package/coverage/clover.xml +447 -460
  5. package/coverage/coverage-final.json +14 -14
  6. package/coverage/lcov-report/index.html +77 -78
  7. package/coverage/lcov-report/src/components/MapLibreMap/MapLibreMap.js.html +2 -3
  8. package/coverage/lcov-report/src/components/MapLibreMap/index.html +2 -3
  9. package/coverage/lcov-report/src/components/MlCreatePdfButton/MlCreatePdfButton.js.html +2 -3
  10. package/coverage/lcov-report/src/components/MlCreatePdfButton/index.html +2 -3
  11. package/coverage/lcov-report/src/components/MlFeatureEditor/MlFeatureEditor.js.html +2 -3
  12. package/coverage/lcov-report/src/components/MlFeatureEditor/index.html +2 -3
  13. package/coverage/lcov-report/src/components/MlFillExtrusionLayer/MlFillExtrusionLayer.js.html +2 -3
  14. package/coverage/lcov-report/src/components/MlFillExtrusionLayer/index.html +2 -3
  15. package/coverage/lcov-report/src/components/MlFollowGps/MlFollowGps.js.html +112 -107
  16. package/coverage/lcov-report/src/components/MlFollowGps/index.html +16 -17
  17. package/coverage/lcov-report/src/components/MlGPXViewer/MlGPXViewer.js.html +2 -3
  18. package/coverage/lcov-report/src/components/MlGPXViewer/gpxConverter.js.html +2 -3
  19. package/coverage/lcov-report/src/components/MlGPXViewer/index.html +2 -3
  20. package/coverage/lcov-report/src/components/MlGeoJsonLayer/MlGeoJsonLayer.js.html +142 -134
  21. package/coverage/lcov-report/src/components/MlGeoJsonLayer/index.html +20 -21
  22. package/coverage/lcov-report/src/components/MlImageMarkerLayer/MlImageMarkerLayer.js.html +46 -152
  23. package/coverage/lcov-report/src/components/MlImageMarkerLayer/index.html +20 -21
  24. package/coverage/lcov-report/src/components/MlLayer/MlLayer.js.html +92 -30
  25. package/coverage/lcov-report/src/components/MlLayer/index.html +20 -21
  26. package/coverage/lcov-report/src/components/MlLayerMagnify/MlLayerMagnify.js.html +2 -3
  27. package/coverage/lcov-report/src/components/MlLayerMagnify/index.html +2 -3
  28. package/coverage/lcov-report/src/components/MlLayerSwipe/MlLayerSwipe.js.html +2 -3
  29. package/coverage/lcov-report/src/components/MlLayerSwipe/index.html +2 -3
  30. package/coverage/lcov-report/src/components/MlLayerSwitcher/MlLayerSwitcher.js.html +3 -10
  31. package/coverage/lcov-report/src/components/MlLayerSwitcher/components/LayerBox.js.html +9 -82
  32. package/coverage/lcov-report/src/components/MlLayerSwitcher/components/index.html +10 -11
  33. package/coverage/lcov-report/src/components/MlLayerSwitcher/index.html +2 -3
  34. package/coverage/lcov-report/src/components/MlMarker/MlMarker.js.html +6 -7
  35. package/coverage/lcov-report/src/components/MlMarker/index.html +6 -7
  36. package/coverage/lcov-report/src/components/MlNavigationCompass/MlNavigationCompass.js.html +2 -3
  37. package/coverage/lcov-report/src/components/MlNavigationCompass/index.html +2 -3
  38. package/coverage/lcov-report/src/components/MlNavigationTools/MlNavigationTools.js.html +11 -15
  39. package/coverage/lcov-report/src/components/MlNavigationTools/index.html +8 -9
  40. package/coverage/lcov-report/src/components/MlOsmLayer/MlOsmLayer.js.html +2 -3
  41. package/coverage/lcov-report/src/components/MlOsmLayer/index.html +2 -3
  42. package/coverage/lcov-report/src/components/MlScaleReference/MlScaleReference.js.html +2 -3
  43. package/coverage/lcov-report/src/components/MlScaleReference/index.html +2 -3
  44. package/coverage/lcov-report/src/components/MlShareMapState/MlShareMapState.js.html +209 -18
  45. package/coverage/lcov-report/src/components/MlShareMapState/index.html +10 -11
  46. package/coverage/lcov-report/src/components/MlSpatialElevationProfile/MlSpatialElevationProfile.js.html +2 -3
  47. package/coverage/lcov-report/src/components/MlSpatialElevationProfile/index.html +2 -3
  48. package/coverage/lcov-report/src/components/MlThreeJsLayer/MlThreeJsLayer.js.html +2 -3
  49. package/coverage/lcov-report/src/components/MlThreeJsLayer/index.html +2 -3
  50. package/coverage/lcov-report/src/components/MlUseMapDebugger/MlUseMapDebugger.js.html +6 -25
  51. package/coverage/lcov-report/src/components/MlUseMapDebugger/index.html +6 -7
  52. package/coverage/lcov-report/src/components/MlVectorTileLayer/MlVectorTileLayer.js.html +2 -3
  53. package/coverage/lcov-report/src/components/MlVectorTileLayer/index.html +2 -3
  54. package/coverage/lcov-report/src/components/MlWmsFeatureInfoPopup/MlWmsFeatureInfoPopup.js.html +2 -3
  55. package/coverage/lcov-report/src/components/MlWmsFeatureInfoPopup/index.html +2 -3
  56. package/coverage/lcov-report/src/components/MlWmsLayer/MlWmsLayer.js.html +2 -3
  57. package/coverage/lcov-report/src/components/MlWmsLayer/index.html +2 -3
  58. package/coverage/lcov-report/src/components/MlWmsLoader/MlWmsLoader.js.html +6 -19
  59. package/coverage/lcov-report/src/components/MlWmsLoader/index.html +8 -9
  60. package/coverage/lcov-report/src/hooks/index.html +35 -36
  61. package/coverage/lcov-report/src/hooks/useMap.js.html +81 -169
  62. package/coverage/lcov-report/src/hooks/useMapState.js.html +82 -125
  63. package/coverage/lcov-report/src/hooks/useWms.js.html +9 -22
  64. package/coverage/lcov-report/src/i18n.js.html +2 -3
  65. package/coverage/lcov-report/src/index.html +2 -3
  66. package/coverage/lcov-report/src/translations/english.js.html +2 -3
  67. package/coverage/lcov-report/src/translations/german.js.html +2 -3
  68. package/coverage/lcov-report/src/translations/index.html +2 -3
  69. package/coverage/lcov.info +891 -896
  70. package/dist/index.esm.js +941 -717
  71. package/dist/index.esm.js.map +1 -1
  72. package/package.json +2 -2
  73. package/src/components/MapLibreMap/lib/MapLibreGlWrapper.js +16 -14
  74. package/src/components/MlComponentTemplate/MlComponentTemplate.js +7 -32
  75. package/src/components/MlFollowGps/MlFollowGps.js +67 -65
  76. package/src/components/MlFollowGps/MlFollowGps.test.js +3 -5
  77. package/src/components/MlGeoJsonLayer/MlGeoJsonLayer.js +91 -88
  78. package/src/components/MlGeoJsonLayer/MlGeoJsonLayer.stories.js +20 -6
  79. package/src/components/MlGeoJsonLayer/assets/sample_polygon_1.json +33 -0
  80. package/src/components/MlGeoJsonLayer/util/getDefaultLayerTypeByGeometry.js +25 -0
  81. package/src/components/MlGeoJsonLayer/util/getDefaultPaintPropsByType.js +22 -0
  82. package/src/components/MlImageMarkerLayer/MlImageMarkerLayer.js +21 -56
  83. package/src/components/MlLayer/MlLayer.js +26 -5
  84. package/src/components/MlLayerSwitcher/MlLayerSwitcher.js +0 -2
  85. package/src/components/MlLayerSwitcher/MlLayerSwitcher.stories.js +3 -6
  86. package/src/components/MlLayerSwitcher/components/LayerBox.js +2 -26
  87. package/src/components/MlMarker/MlMarker.js +1 -1
  88. package/src/components/MlNavigationTools/MlNavigationTools.js +4 -5
  89. package/src/components/MlShareMapState/MlShareMapState.js +73 -9
  90. package/src/components/MlShareMapState/MlShareMapState.stories.js +22 -2
  91. package/src/components/MlSpatialElevationProfile/MlSpatialElevationProfile.stories.js +1 -3
  92. package/src/components/MlUseMapDebugger/MlUseMapDebugger.js +1 -7
  93. package/src/components/MlWmsLoader/MlWmsLoader.js +0 -4
  94. package/src/hooks/useMap.js +33 -62
  95. package/src/hooks/useMapState.js +3 -17
  96. package/src/hooks/useWms.js +2 -7
  97. package/src/index.js +3 -0
  98. package/src/ui_components/ImageLoader.js +8 -3
  99. package/src/ui_components/Sidebar.js +1 -1
  100. package/src/ui_components/TopToolbar.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapcomponents/react-maplibre",
3
- "version": "0.1.13",
3
+ "version": "0.1.17",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "module": "dist/index.esm.js",
@@ -12,7 +12,7 @@
12
12
  "@fontsource/roboto": "^4.5.1",
13
13
  "@mapbox/mapbox-gl-draw": "^1.2.0",
14
14
  "@mapbox/mapbox-gl-sync-move": "^0.3.0",
15
- "@mapcomponents/react-core": "^0.1.1",
15
+ "@mapcomponents/react-core": "^0.1.7",
16
16
  "@mui/icons-material": "^5.0.1",
17
17
  "@mui/material": "5.0.0",
18
18
  "@mui/styles": "^5.0.1",
@@ -1,3 +1,4 @@
1
+ // @ts-ignore: TS export Problem to be fixed upstream
1
2
  import maplibregl from "maplibre-gl/dist/maplibre-gl";
2
3
 
3
4
  /**
@@ -68,6 +69,7 @@ const MapLibreGlWrapper = function (props) {
68
69
  if (!Object.is(item.handler, handler)) {
69
70
  return item;
70
71
  }
72
+ return false;
71
73
  });
72
74
  },
73
75
  /**
@@ -91,7 +93,7 @@ const MapLibreGlWrapper = function (props) {
91
93
  /**
92
94
  * Array containing an object for each layer in the MapLibre instance providing information on visibility, loading state, order, paint & layout properties
93
95
  */
94
- layerState: {},
96
+ layerState: [],
95
97
  /**
96
98
  * Maps layerIds to layerState in JSON string form for quick deep comparisons
97
99
  */
@@ -110,7 +112,7 @@ const MapLibreGlWrapper = function (props) {
110
112
  //if (self.baseLayers.indexOf(layer.id) === -1) {
111
113
  let paint = {};
112
114
  let values = layer.paint?._values;
113
- Object.keys(values || {}).map((propName) => {
115
+ Object.keys(values || {}).forEach((propName) => {
114
116
  paint[propName] =
115
117
  typeof values[propName].value !== "undefined"
116
118
  ? values[propName].value.value
@@ -118,7 +120,7 @@ const MapLibreGlWrapper = function (props) {
118
120
  });
119
121
  let layout = {};
120
122
  values = layer.layout?._values;
121
- Object.keys(values || {}).map((propName) => {
123
+ Object.keys(values || {}).forEach((propName) => {
122
124
  layout[propName] =
123
125
  typeof values[propName].value !== "undefined"
124
126
  ? values[propName].value.value
@@ -499,17 +501,17 @@ const MapLibreGlWrapper = function (props) {
499
501
  self.wrapper.refreshViewport();
500
502
  self.wrapper.fire("viewportchange");
501
503
 
502
- self.map.on("move", () => {
503
- self.wrapper.viewportState = self.wrapper.getViewport();
504
- self.wrapper.fire("viewportchange");
505
- });
506
- self.map.on("data", () => {
507
- self.wrapper.refreshLayerState();
508
- self.wrapper.fire("layerchange");
509
- });
510
- if (typeof props.onReady === "function") {
511
- props.onReady(self.map, self);
512
- }
504
+ self.map.on("move", () => {
505
+ self.wrapper.viewportState = self.wrapper.getViewport();
506
+ self.wrapper.fire("viewportchange");
507
+ });
508
+ self.map.on("data", () => {
509
+ self.wrapper.refreshLayerState();
510
+ self.wrapper.fire("layerchange");
511
+ });
512
+ if (typeof props.onReady === "function") {
513
+ props.onReady(self.map, self);
514
+ }
513
515
  };
514
516
  initializeMapLibre();
515
517
  };
@@ -1,8 +1,6 @@
1
- import React, { useRef, useEffect, useContext } from "react";
1
+ import React, { useRef, useEffect } from "react";
2
2
  import PropTypes from "prop-types";
3
-
4
- import { MapContext } from "@mapcomponents/react-core";
5
- import { v4 as uuidv4 } from "uuid";
3
+ import useMap from "../../hooks/useMap";
6
4
 
7
5
  /**
8
6
  * TODO: Add short & useful description
@@ -13,40 +11,17 @@ import { v4 as uuidv4 } from "uuid";
13
11
  * @component
14
12
  */
15
13
  const MlComponentTemplate = (props) => {
16
- // Use a useRef hook to reference the layer object to be able to access it later inside useEffect hooks
17
- const mapContext = useContext(MapContext);
18
-
14
+ const mapHook = useMap({ mapId: props.mapId, waitForLayer: props.insertBeforeLayer });
19
15
  const initializedRef = useRef(false);
20
- const mapRef = useRef(undefined);
21
- const componentId = useRef((props.idPrefix ? props.idPrefix : "MlComponentTemplate-") + uuidv4());
22
-
23
- useEffect(() => {
24
- let _componentId = componentId.current;
25
-
26
- return () => {
27
- // This is the cleanup function, it is called when this react component is removed from react-dom
28
- // try to remove anything this component has added to the MapLibre-gl instance
29
- // e.g.: remove the layer
30
- // mapContext.getMap(props.mapId).removeLayer(layerRef.current);
31
- // check for the existence of map.style before calling getLayer or getSource
32
-
33
- if (mapRef.current) {
34
- mapRef.current.cleanup(_componentId);
35
- mapRef.current = undefined;
36
- }
37
- initializedRef.current = false;
38
- };
39
- }, []);
40
16
 
41
17
  useEffect(() => {
42
- if (!mapContext.mapExists(props.mapId) || initializedRef.current) return;
43
- // the MapLibre-gl instance (mapContext.getMap(props.mapId)) is accessible here
18
+ if (!mapHook.mapIsReady || initializedRef.current) return;
19
+ // the MapLibre-gl instance (mapHook.map) is accessible here
44
20
  // initialize the layer and add it to the MapLibre-gl instance or do something else with it
45
21
  initializedRef.current = true;
46
- mapRef.current = mapContext.getMap(props.mapId);
47
22
 
48
- mapRef.current.setCenter([7.132122000552613, 50.716405378037706]);
49
- }, [mapContext.mapIds, mapContext, props.mapId]);
23
+ mapHook.map.setCenter([7.132122000552613, 50.716405378037706]);
24
+ }, [mapHook.map, mapHook.mapIsReady, props.mapId]);
50
25
 
51
26
  return <></>;
52
27
  };
@@ -1,11 +1,10 @@
1
- import React, {useRef, useEffect, useContext, useState} from "react";
1
+ import React, { useRef, useEffect, useState, useCallback } from "react";
2
2
  import PropTypes from "prop-types";
3
+ import useMap from "../../hooks/useMap";
3
4
 
4
- import {MapContext} from "@mapcomponents/react-core";
5
- import {v4 as uuidv4} from "uuid";
6
5
  import Button from "@mui/material/Button";
7
6
  import RoomIcon from "@mui/icons-material/Room";
8
- import {point} from "@turf/turf"
7
+ import { point, circle } from "@turf/turf";
9
8
  import MlGeoJsonLayer from "../MlGeoJsonLayer/MlGeoJsonLayer";
10
9
  import MlImageMarkerLayer from "../MlImageMarkerLayer/MlImageMarkerLayer";
11
10
 
@@ -21,88 +20,72 @@ import marker from "./assets/marker.png";
21
20
  * @component
22
21
  */
23
22
  const MlFollowGps = (props) => {
24
- // Use a useRef hook to reference the layer object to be able to access it later inside useEffect hooks
25
- const mapContext = useContext(MapContext);
23
+ const mapHook = useMap({ mapId: props.mapId, waitForLayer: props.insertBeforeLayer });
24
+
26
25
  const [isFollowed, setIsFollowed] = useState(false);
27
26
  const [geoJson, setGeoJson] = useState(undefined);
28
27
  const watchIdRef = useRef(undefined);
29
28
  const [locationAccessDenied, setLocationAccessDenied] = useState(false);
30
29
 
31
- const initializedRef = useRef(false);
32
- const mapRef = useRef(undefined);
33
- const componentId = useRef((props.idPrefix ? props.idPrefix : "MlFollowGps-") + uuidv4());
34
- const [accuracyRadius, setAccuracyRadius] = useState(30);
30
+ const [accuracyGeoJson, setAccuracyGeoJson] = useState();
35
31
 
36
32
  useEffect(() => {
37
- let _componentId = componentId.current;
38
-
39
33
  return () => {
40
- // This is the cleanup function, it is called when this react component is removed from react-dom
41
- // try to remove anything this component has added to the MapLibre-gl instance
42
- // e.g.: remove the layer
43
- // mapContext.getMap(props.mapId).removeLayer(layerRef.current);
44
- // check for the existence of map.style before calling getLayer or getSource
45
-
46
- if (mapRef.current) {
47
- mapRef.current.cleanup(_componentId);
48
- mapRef.current = undefined;
49
- }
50
34
  if (watchIdRef.current) {
51
- initializedRef.current = false;
52
35
  navigator.geolocation.clearWatch(watchIdRef.current);
53
36
  watchIdRef.current = undefined;
54
37
  }
55
38
  };
56
39
  }, []);
57
40
 
58
- useEffect(() => {
59
- if (!mapContext.mapExists(props.mapId) || initializedRef.current) return;
60
- // the MapLibre-gl instance (mapContext.getMap(props.mapId)) is accessible here
61
- // initialize the layer and add it to the MapLibre-gl instance or do something else with it
62
- initializedRef.current = true;
63
- mapRef.current = mapContext.getMap(props.mapId);
64
- mapRef.current.setCenter([7.132122000552613, 50.716405378037706]);
65
- }, [mapContext.mapIds, mapContext, props.mapId]);
66
-
67
- const getLocationSuccess = (pos) => {
68
- if (!mapRef.current) return;
69
- mapRef.current.setCenter([pos.coords.longitude, pos.coords.latitude]);
70
- setAccuracyRadius(pos.coords.accuracy);
71
- setGeoJson(point([pos.coords.longitude, pos.coords.latitude]));
72
- };
41
+ const getLocationSuccess = useCallback(
42
+ (pos) => {
43
+ if (!mapHook.map) return;
44
+
45
+ mapHook.map.setCenter([pos.coords.longitude, pos.coords.latitude]);
46
+ const geoJsonPoint = point([pos.coords.longitude, pos.coords.latitude]);
47
+ setGeoJson(geoJsonPoint);
48
+ setAccuracyGeoJson(circle(geoJsonPoint, pos.coords.accuracy / 1000));
49
+ },
50
+ [mapHook.map]
51
+ );
73
52
 
74
53
  const getLocationError = (err) => {
75
54
  console.log("Access of user location denied");
76
55
  setLocationAccessDenied(true);
77
56
  };
78
57
 
58
+ useEffect(() => {
59
+ if (!mapHook.map) return;
60
+
61
+ if (isFollowed) {
62
+ watchIdRef.current = navigator.geolocation.watchPosition(
63
+ getLocationSuccess,
64
+ getLocationError
65
+ );
66
+ } else {
67
+ navigator.geolocation.clearWatch(watchIdRef.current);
68
+ }
69
+ }, [isFollowed, getLocationSuccess]);
70
+
79
71
  return (
80
72
  <>
81
73
  {isFollowed && geoJson && (
82
74
  <MlGeoJsonLayer
83
- geojson={geoJson}
84
- type={"circle"}
75
+ geojson={accuracyGeoJson}
76
+ type={"fill"}
85
77
  paint={{
86
- "circle-radius": {
87
- stops: [
88
- [0, 0],
89
- [
90
- 20,
91
- accuracyRadius /
92
- 0.075 /
93
- Math.cos((geoJson.geometry.coordinates[1] * Math.PI) / 180),
94
- ],
95
- ],
96
- base: 2,
97
- },
98
- "circle-color": "#ee7700",
99
- "circle-opacity": 0.5,
78
+ "fill-color": "#ee7700",
79
+ "fill-opacity": 0.5,
80
+ ...props.accuracyPaint,
100
81
  }}
82
+ insertBeforeLayer={"MlFollowGpsMarker"}
101
83
  />
102
84
  )}
103
85
 
104
86
  {isFollowed && geoJson && (
105
87
  <MlImageMarkerLayer
88
+ layerId={"MlFollowGpsMarker"}
106
89
  options={{
107
90
  type: "symbol",
108
91
  source: {
@@ -112,29 +95,22 @@ const MlFollowGps = (props) => {
112
95
  layout: {
113
96
  "icon-size": 0.1,
114
97
  "icon-offset": [0, -340],
98
+ ...props.markerLayout,
115
99
  },
116
100
  }}
117
- imgSrc={marker}
101
+ imgSrc={props.markerImage || marker}
118
102
  />
119
103
  )}
120
104
 
121
105
  <Button
122
- sx={{ zIndex: 1002, color: isFollowed ? "#bbb" : "#666", ...props.style }}
106
+ sx={{ zIndex: 1002, color: isFollowed ? props.onColor : props.offColor, ...props.style }}
123
107
  disabled={locationAccessDenied}
124
108
  onClick={() => {
125
- if (isFollowed) {
126
- navigator.geolocation.clearWatch(watchIdRef.current);
127
- } else {
128
- watchIdRef.current = navigator.geolocation.watchPosition(
129
- getLocationSuccess,
130
- getLocationError
131
- );
132
- }
133
109
  setIsFollowed(!isFollowed);
134
110
  }}
135
111
  >
136
112
  {" "}
137
- <RoomIcon sx={{fontSize: props.style.fontSize}}/>{" "}
113
+ <RoomIcon sx={{ fontSize: props.style.fontSize }} />{" "}
138
114
  </Button>
139
115
  </>
140
116
  );
@@ -156,6 +132,8 @@ MlFollowGps.defaultProps = {
156
132
  color: "#ececec",
157
133
  },
158
134
  },
135
+ onColor: "#ececec",
136
+ offColor: "#666",
159
137
  };
160
138
 
161
139
  MlFollowGps.propTypes = {
@@ -167,5 +145,29 @@ MlFollowGps.propTypes = {
167
145
  * CSS style object that is applied to the button component
168
146
  */
169
147
  style: PropTypes.object,
148
+ /**
149
+ * Active button font color
150
+ */
151
+ onColor: PropTypes.string,
152
+ /**
153
+ * Inactive button font color
154
+ */
155
+ offColor: PropTypes.string,
156
+ /**
157
+ * Accuracy paint property object, that is passed to the MlGeoJsonLayer responsible for drawing the accuracy circle.
158
+ * Use any available paint prop from layer type "fill".
159
+ * https://maplibre.org/maplibre-gl-js-docs/style-spec/layers/#fill
160
+ */
161
+ accuracyPaint: PropTypes.object,
162
+ /**
163
+ * Marker layout property object, that is passed to the MlImageMarkerLayer responsible for drawing the position marker.
164
+ * Use any available layout property from layer type "symbol".
165
+ * https://maplibre.org/maplibre-gl-js-docs/style-spec/layers/#symbol
166
+ */
167
+ markerLayout: PropTypes.object,
168
+ /**
169
+ * Replace the default marker image with a custom one.
170
+ */
171
+ markerImage: PropTypes.string,
170
172
  };
171
173
  export default MlFollowGps;
@@ -6,14 +6,12 @@ import MlFollowGps from "./MlFollowGps";
6
6
  import MapLibreMap from "./../MapLibreMap/MapLibreMap";
7
7
 
8
8
  const mockGeolocation = {
9
- watchPosition: jest.fn(),
9
+ watchPosition: jest.fn(() => 1),
10
10
  clearWatch: jest.fn(),
11
- }
11
+ };
12
12
 
13
13
  global.navigator.geolocation = mockGeolocation;
14
14
 
15
- global.navigator.geolocation.watchPosition.mockReturnValue(1);
16
-
17
15
  const MlFollowGPSTestComponent = (props) => {
18
16
  const [componentVisible, setComponentVisible] = useState(true);
19
17
 
@@ -60,6 +58,6 @@ describe("<MlFollowGps>", () => {
60
58
  wrapper.find("MlFollowGps button").simulate("click");
61
59
  //wrapper.find(".toggle_layer_visible").simulate("click");
62
60
 
63
- await waitFor(() => expect(mockGeolocation.clearWatch).toHaveBeenCalledTimes(1));
61
+ await waitFor(() => expect(mockGeolocation.clearWatch).toHaveBeenCalledTimes(2));
64
62
  });
65
63
  });
@@ -1,13 +1,16 @@
1
- import React, { useRef, useEffect, useContext, useCallback } from "react";
1
+ import React, { useRef, useEffect, useCallback } from "react";
2
2
  import PropTypes from "prop-types";
3
3
 
4
- import { v4 as uuidv4 } from "uuid";
5
4
  import * as turf from "@turf/turf";
6
- import { MapContext } from "@mapcomponents/react-core";
5
+
6
+ import useMap from "../../hooks/useMap";
7
7
 
8
8
  import { _transitionToGeojson } from "./util/transitionFunctions";
9
+ import getDefaultPaintPropsByType from "./util/getDefaultPaintPropsByType";
10
+ import getDefaulLayerTypeByGeometry from "./util/getDefaultLayerTypeByGeometry";
9
11
 
10
12
  const msPerStep = 50;
13
+ const legalLayerTypes = ["circle", "fill", "line"];
11
14
 
12
15
  /**
13
16
  * Adds source and layer of types "line", "fill" or "circle" to display GeoJSON data on the map.
@@ -16,50 +19,43 @@ const msPerStep = 50;
16
19
  */
17
20
  const MlGeoJsonLayer = (props) => {
18
21
  // Use a useRef hook to reference the layer object to be able to access it later inside useEffect hooks
19
- const mapContext = useContext(MapContext);
20
- const oldGeojsonRef = useRef(null);
21
- const mapRef = useRef(null);
22
+ const mapHook = useMap({ mapId: props.mapId, waitForLayer: props.insertBeforeLayer });
22
23
  const initializedRef = useRef(false);
24
+ const layerId = useRef(props.layerId || "MlGeoJsonLayer-" + mapHook.componentId);
25
+ const layerTypeRef = useRef(undefined);
26
+
27
+ // transition effect variables
28
+ const oldGeojsonRef = useRef(null);
23
29
  const transitionInProgressRef = useRef(false);
24
30
  const transitionTimeoutRef = useRef(undefined);
25
31
  const currentTransitionStepRef = useRef(false);
26
32
  const transitionGeojsonDataRef = useRef([]);
27
33
  const transitionGeojsonCommonDataRef = useRef([]);
28
- const componentId = useRef(
29
- (props.layerId ? props.layerId : "MlGeoJsonLayer-") + (props.idSuffix || uuidv4())
30
- );
31
- const layerId = useRef(props.layerId || componentId.current);
32
34
 
33
35
  useEffect(() => {
34
- let _componentId = componentId.current;
35
36
  return () => {
36
37
  // This is the cleanup function, it is called when this react component is removed from react-dom
37
38
  if (transitionTimeoutRef.current) {
38
39
  clearTimeout(transitionTimeoutRef.current);
39
40
  }
40
- if (mapRef.current) {
41
- mapRef.current.cleanup(_componentId);
42
-
43
- mapRef.current = null;
44
- }
45
41
  };
46
42
  }, []);
47
43
 
48
44
  useEffect(() => {
49
- if (!mapRef.current || !initializedRef.current) return;
45
+ if (!mapHook.map || !initializedRef.current) return;
50
46
 
51
47
  for (var key in props.layout) {
52
- mapContext.getMap(props.mapId).setLayoutProperty(layerId.current, key, props.layout[key]);
48
+ mapHook.map.setLayoutProperty(layerId.current, key, props.layout[key]);
53
49
  }
54
- }, [props.layout, mapContext, props.mapId]);
50
+ }, [props.layout, mapHook.map, props.mapId]);
55
51
 
56
52
  useEffect(() => {
57
- if (!mapRef.current || !initializedRef.current) return;
53
+ if (!mapHook.map || !initializedRef.current) return;
58
54
 
59
55
  for (var key in props.paint) {
60
- mapContext.getMap(props.mapId).setPaintProperty(layerId.current, key, props.paint[key]);
56
+ mapHook.map.setPaintProperty(layerId.current, key, props.paint[key]);
61
57
  }
62
- }, [props.paint, mapContext, props.mapId]);
58
+ }, [props.paint, mapHook.map, props.mapId]);
63
59
 
64
60
  const transitionToGeojson = useCallback(
65
61
  (newGeojson) => {
@@ -72,18 +68,16 @@ const MlGeoJsonLayer = (props) => {
72
68
  oldGeojsonRef,
73
69
  msPerStep,
74
70
  currentTransitionStepRef,
75
- mapRef.current,
76
- componentId.current,
71
+ mapHook.map,
72
+ layerId.current,
77
73
  transitionTimeoutRef
78
74
  );
79
75
  },
80
- [props]
76
+ [props, mapHook.map]
81
77
  );
82
78
 
83
79
  useEffect(() => {
84
- if (!mapRef.current?.getSource?.(componentId.current) || !initializedRef.current) return;
85
- // the MapLibre-gl instance (mapContext.map) is accessible here
86
- // initialize the layer and add it to the MapLibre-gl instance or do something else with it
80
+ if (!mapHook?.map?.getSource(layerId.current) || !initializedRef.current) return;
87
81
 
88
82
  if (
89
83
  typeof props.transitionTime !== "undefined" &&
@@ -96,79 +90,93 @@ const MlGeoJsonLayer = (props) => {
96
90
  transitionGeojsonCommonDataRef.current = [];
97
91
  transitionToGeojson(props.geojson);
98
92
  } else {
99
- mapRef.current.getSource(componentId.current).setData(props.geojson);
93
+ mapHook.map.getSource(layerId.current).setData(props.geojson);
100
94
  }
101
95
  oldGeojsonRef.current = props.geojson;
102
96
  }, [
103
97
  props.geojson,
104
98
  props.mapId,
105
- mapContext,
99
+ mapHook.map,
106
100
  props.type,
107
101
  transitionToGeojson,
108
102
  props.transitionTime,
109
103
  ]);
110
104
 
111
- useEffect(() => {
112
- if (!mapContext.mapExists(props.mapId) || initializedRef.current) return;
113
- // the MapLibre-gl instance (mapContext.map) is accessible here
114
- // initialize the layer and add it to the MapLibre-gl instance or do something else with it
105
+ const createLayer = useCallback(() => {
106
+ let geojson = props.geojson;
115
107
 
116
- if (props.geojson) {
117
- initializedRef.current = true;
118
- let geojson = props.geojson;
119
-
120
- if (
121
- props.type === "line" &&
122
- typeof props.transitionTime !== "undefined" &&
123
- props.transitionTime &&
124
- typeof props.geojson.geometry !== "undefined"
125
- ) {
126
- var tmpChunks = turf.lineChunk(props.geojson, 0.01);
127
- geojson = tmpChunks.features[0];
128
- }
108
+ if (
109
+ props.type === "line" &&
110
+ typeof props.transitionTime !== "undefined" &&
111
+ props.transitionTime &&
112
+ typeof props.geojson.geometry !== "undefined"
113
+ ) {
114
+ var tmpChunks = turf.lineChunk(props.geojson, 0.01);
115
+ geojson = tmpChunks.features[0];
116
+ }
129
117
 
130
- mapRef.current = mapContext.getMap(props.mapId);
131
-
132
- mapRef.current.addLayer(
133
- {
134
- id: layerId.current,
135
- source: {
136
- type: "geojson",
137
- data: geojson,
138
- },
139
- type: props.type || "line",
140
- paint: props.paint || {
141
- "line-color": "rgb(100,200,100)",
142
- "line-width": 10,
143
- },
144
- layout: props.layout || {},
118
+ layerTypeRef.current = props.type || getDefaulLayerTypeByGeometry(props.geojson);
119
+
120
+ mapHook.map.addLayer(
121
+ {
122
+ id: layerId.current,
123
+ source: {
124
+ type: "geojson",
125
+ data: geojson,
145
126
  },
146
- props.insertBeforeLayer,
147
- componentId.current
148
- );
127
+ type: layerTypeRef.current,
128
+ paint: props.paint || getDefaultPaintPropsByType(layerTypeRef.current),
129
+ layout: props.layout || {},
130
+ },
131
+ props.insertBeforeLayer,
132
+ mapHook.componentId
133
+ );
134
+
135
+ if (typeof props.onHover !== "undefined") {
136
+ mapHook.map.on("mousemove", mapHook.componentId, props.onHover, mapHook.componentId);
137
+ }
149
138
 
150
- if (typeof props.onHover !== "undefined") {
151
- mapRef.current.on("mousemove", componentId.current, props.onHover, componentId.current);
152
- }
139
+ if (typeof props.onClick !== "undefined") {
140
+ mapHook.map.on("click", mapHook.componentId, props.onClick, mapHook.componentId);
141
+ }
153
142
 
154
- if (typeof props.onClick !== "undefined") {
155
- mapRef.current.on("click", componentId.current, props.onClick, componentId.current);
156
- }
143
+ if (typeof props.onLeave !== "undefined") {
144
+ mapHook.map.on("mouseleave", mapHook.componentId, props.onLeave, mapHook.componentId);
145
+ }
157
146
 
158
- if (typeof props.onLeave !== "undefined") {
159
- mapRef.current.on("mouseleave", componentId.current, props.onLeave, componentId.current);
160
- }
147
+ if (
148
+ props.type === "line" &&
149
+ typeof props.transitionTime !== "undefined" &&
150
+ typeof props.geojson.geometry !== "undefined"
151
+ ) {
152
+ transitionToGeojson(props.geojson);
153
+ oldGeojsonRef.current = props.geojson;
154
+ }
155
+ }, [mapHook.map, props, transitionToGeojson]);
161
156
 
162
- if (
163
- props.type === "line" &&
164
- typeof props.transitionTime !== "undefined" &&
165
- typeof props.geojson.geometry !== "undefined"
166
- ) {
167
- transitionToGeojson(props.geojson);
168
- oldGeojsonRef.current = props.geojson;
169
- }
157
+ useEffect(() => {
158
+ if (!mapHook.mapIsReady || !props.geojson) return;
159
+
160
+ if (
161
+ initializedRef.current &&
162
+ legalLayerTypes.indexOf(props.type) !== -1 &&
163
+ layerTypeRef.current &&
164
+ props.type !== layerTypeRef.current
165
+ ) {
166
+ mapHook.map.cleanup(mapHook.componentId);
167
+ } else if (
168
+ initializedRef.current &&
169
+ (legalLayerTypes.indexOf(props.type) === -1 ||
170
+ (legalLayerTypes.indexOf(props.type) !== -1 && props.type === layerTypeRef.current))
171
+ ) {
172
+ return;
170
173
  }
171
- }, [mapContext.mapIds, mapContext, props, transitionToGeojson]);
174
+
175
+ // initialize the layer and add it to the MapLibre-gl instance or do something else with it
176
+ initializedRef.current = true;
177
+
178
+ createLayer();
179
+ }, [mapHook.mapIsReady, createLayer, props]);
172
180
 
173
181
  return <></>;
174
182
  };
@@ -230,11 +238,6 @@ MlGeoJsonLayer.propTypes = {
230
238
  * Only works with layer type "line" and LineString GeoJSON data.
231
239
  */
232
240
  transitionTime: PropTypes.number,
233
- /**
234
- * Id suffix string that is appended to the componentId.
235
- * Probably removed soon.
236
- */
237
- idSuffix: PropTypes.string,
238
241
  };
239
242
 
240
243
  export default MlGeoJsonLayer;