esri-gl 1.0.6 → 2.0.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.
- package/README.md +52 -2
- package/dist/{index-DczUYC4z.d.ts → index-Doq93o-G.d.ts} +252 -75
- package/dist/{IdentifyImage-DmyKcbAv.js → index-DsY1_0df.js} +825 -678
- package/dist/index-DsY1_0df.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +7181 -1017
- package/dist/index.umd.js.map +1 -1
- package/dist/package.json +5 -1
- package/dist/react-map-gl.d.ts +42 -13
- package/dist/react-map-gl.js +292 -304
- package/dist/react-map-gl.js.map +1 -1
- package/dist/react.d.ts +23 -28
- package/dist/react.js +202 -178
- package/dist/react.js.map +1 -1
- package/dist/{useFeatureService-E9PiUOLP.js → useFeatureService-BRY6PHUs.js} +50 -12
- package/dist/useFeatureService-BRY6PHUs.js.map +1 -0
- package/dist/{useFeatureService-QUqwJcet.d.ts → useFeatureService-CG70gjQy.d.ts} +29 -41
- package/package.json +8 -5
- package/dist/IdentifyImage-DmyKcbAv.js.map +0 -1
- package/dist/useFeatureService-E9PiUOLP.js.map +0 -1
package/dist/react-map-gl.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { a as Find, I as IdentifyFeatures, b as IdentifyImage, Q as Query, S as Service, T as Task, f as cleanTrailingSlash, g as find, h as getServiceDetails, i as identifyImage, q as query, u as updateAttribution } from './IdentifyImage-DmyKcbAv.js';
|
|
1
|
+
import { D as DynamicMapService, d as TiledMapService, c as ImageService, e as VectorTileService, g as esriRequest, V as VectorBasemapStyle, F as FeatureService, k as serviceFromPortalItem } from './index-DsY1_0df.js';
|
|
2
|
+
export { a as Find, I as IdentifyFeatures, b as IdentifyImage, Q as Query, S as Service, T as Task, f as cleanTrailingSlash, h as find, i as getServiceDetails, j as identifyImage, q as query, r as resolveAuthentication, s as searchPortalItems, l as servicesFromWebMap, u as updateAttribution } from './index-DsY1_0df.js';
|
|
4
3
|
import { useMap } from 'react-map-gl/mapbox';
|
|
5
4
|
import { useMap as useMap$1 } from 'react-map-gl/maplibre';
|
|
6
|
-
import {
|
|
5
|
+
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
6
|
+
import { b as useFeatureService, e as useVectorTileService, c as useImageService, d as useTiledMapService, u as useDynamicMapService } from './useFeatureService-BRY6PHUs.js';
|
|
7
|
+
export { BasemapStyleSession } from '@esri/arcgis-rest-basemap-sessions';
|
|
8
|
+
export { ApiKeyManager, ApplicationCredentialsManager, ArcGISAuthError, ArcGISIdentityManager, ArcGISRequestError } from '@esri/arcgis-rest-request';
|
|
9
|
+
export { SearchQueryBuilder } from '@esri/arcgis-rest-portal';
|
|
10
|
+
import '@esri/arcgis-rest-feature-service';
|
|
7
11
|
import '@mapbox/tilebelt';
|
|
8
12
|
import 'arcgis-pbf-parser';
|
|
9
13
|
|
|
@@ -49,6 +53,7 @@ function applyAuthOptions(options, props) {
|
|
|
49
53
|
const target = options;
|
|
50
54
|
if (props.token !== undefined) target.token = props.token;
|
|
51
55
|
if (props.apiKey !== undefined) target.apiKey = props.apiKey;
|
|
56
|
+
if (props.authentication !== undefined) target.authentication = props.authentication;
|
|
52
57
|
if (props.proxy !== undefined) target.proxy = props.proxy;
|
|
53
58
|
if (props.getAttributionFromService !== undefined) target.getAttributionFromService = props.getAttributionFromService;
|
|
54
59
|
if (props.requestParams !== undefined) target.requestParams = props.requestParams;
|
|
@@ -57,84 +62,137 @@ function applyAuthOptions(options, props) {
|
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/**
|
|
60
|
-
*
|
|
65
|
+
* Returns true once the underlying map's style has finished loading.
|
|
66
|
+
* Returns false when there is no map yet, the map does not expose the
|
|
67
|
+
* expected lifecycle API, or the style has not finished loading.
|
|
61
68
|
*/
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
current: map
|
|
65
|
-
} = useReactMapGL();
|
|
66
|
-
const sourceId = props.sourceId || `esri-dynamic-${props.id}`;
|
|
67
|
-
const [isMapLoaded, setIsMapLoaded] = useState(false);
|
|
68
|
-
// Wait for map to be loaded before creating service
|
|
69
|
+
function useMapLoaded(map) {
|
|
70
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
69
71
|
useEffect(() => {
|
|
70
|
-
if (!map)
|
|
72
|
+
if (!map) {
|
|
73
|
+
setIsLoaded(false);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
71
76
|
const mapInstance = map.getMap?.();
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
if (!mapInstance || typeof mapInstance.isStyleLoaded !== 'function') {
|
|
78
|
+
setIsLoaded(false);
|
|
74
79
|
return;
|
|
75
80
|
}
|
|
76
|
-
if (
|
|
77
|
-
|
|
81
|
+
if (mapInstance.isStyleLoaded()) {
|
|
82
|
+
setIsLoaded(true);
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
|
-
const handleLoad = () =>
|
|
81
|
-
|
|
85
|
+
const handleLoad = () => setIsLoaded(true);
|
|
86
|
+
mapInstance.once?.('load', handleLoad);
|
|
82
87
|
return () => {
|
|
83
|
-
|
|
88
|
+
mapInstance.off?.('load', handleLoad);
|
|
84
89
|
};
|
|
85
90
|
}, [map]);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
91
|
+
return isLoaded;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Shared lifecycle for react-map-gl components that wrap an Esri raster
|
|
96
|
+
* service. Waits for the map style to load, then (re)builds the service and its
|
|
97
|
+
* raster layer in a single effect so that when `serviceDeps` change the old
|
|
98
|
+
* source is torn down *before* the new service recreates it — otherwise the
|
|
99
|
+
* service constructor would see the stale source and skip re-adding it.
|
|
100
|
+
*/
|
|
101
|
+
function useRasterLayer(options) {
|
|
102
|
+
const {
|
|
103
|
+
map,
|
|
104
|
+
layerId,
|
|
105
|
+
sourceId,
|
|
106
|
+
beforeId,
|
|
107
|
+
visible,
|
|
108
|
+
serviceDeps,
|
|
109
|
+
createService
|
|
110
|
+
} = options;
|
|
111
|
+
const isMapLoaded = useMapLoaded(map);
|
|
112
|
+
const [service, setService] = useState(null);
|
|
113
|
+
// Keep the latest createService without making it a dependency (callers pass
|
|
114
|
+
// a fresh closure each render; serviceDeps captures what actually matters).
|
|
115
|
+
const createServiceRef = useRef(createService);
|
|
116
|
+
createServiceRef.current = createService;
|
|
104
117
|
useEffect(() => {
|
|
105
|
-
if (!map || !
|
|
118
|
+
if (!map || !isMapLoaded) {
|
|
119
|
+
setService(null);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
106
122
|
const mapInstance = map.getMap?.();
|
|
107
|
-
if (!mapInstance || typeof mapInstance.
|
|
108
|
-
return
|
|
109
|
-
service.remove();
|
|
110
|
-
};
|
|
123
|
+
if (!mapInstance || typeof mapInstance.addLayer !== 'function') {
|
|
124
|
+
return;
|
|
111
125
|
}
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
const svc = createServiceRef.current(mapInstance, sourceId);
|
|
127
|
+
setService(svc);
|
|
128
|
+
// Add the raster layer once its source exists.
|
|
129
|
+
const sourceReady = typeof mapInstance.getSource !== 'function' || Boolean(mapInstance.getSource(sourceId));
|
|
130
|
+
if (!mapInstance.getLayer?.(layerId) && sourceReady) {
|
|
114
131
|
const layerConfig = {
|
|
115
|
-
id:
|
|
132
|
+
id: layerId,
|
|
116
133
|
type: 'raster',
|
|
117
134
|
source: sourceId,
|
|
118
135
|
layout: {
|
|
119
|
-
visibility:
|
|
136
|
+
visibility: visible !== false ? 'visible' : 'none'
|
|
120
137
|
}
|
|
121
138
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
try {
|
|
140
|
+
if (beforeId) {
|
|
141
|
+
mapInstance.addLayer?.(layerConfig, beforeId);
|
|
142
|
+
} else {
|
|
143
|
+
mapInstance.addLayer?.(layerConfig);
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (process.env?.NODE_ENV !== 'test') {
|
|
147
|
+
console.warn(`useRasterLayer: skipped adding layer "${layerId}"`, err);
|
|
148
|
+
}
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
|
-
// Cleanup function
|
|
129
151
|
return () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
152
|
+
// Remove the layer first, then the service (which removes its source), so
|
|
153
|
+
// a subsequent rebuild gets a clean slate for the same sourceId.
|
|
154
|
+
try {
|
|
155
|
+
if (mapInstance.getStyle?.() && mapInstance.getLayer?.(layerId)) {
|
|
156
|
+
mapInstance.removeLayer?.(layerId);
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// layer may already be gone
|
|
135
160
|
}
|
|
161
|
+
svc.remove();
|
|
136
162
|
};
|
|
137
|
-
|
|
163
|
+
// serviceDeps is spread so any change rebuilds the layer + source.
|
|
164
|
+
}, [map, isMapLoaded, sourceId, layerId, beforeId, visible, ...serviceDeps]);
|
|
165
|
+
return service;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* React Map GL component for Esri Dynamic Map Service
|
|
170
|
+
*/
|
|
171
|
+
function EsriDynamicLayer(props) {
|
|
172
|
+
const {
|
|
173
|
+
current: map
|
|
174
|
+
} = useReactMapGL();
|
|
175
|
+
const sourceId = props.sourceId || `esri-dynamic-${props.id}`;
|
|
176
|
+
useRasterLayer({
|
|
177
|
+
map,
|
|
178
|
+
layerId: props.id,
|
|
179
|
+
sourceId,
|
|
180
|
+
beforeId: props.beforeId,
|
|
181
|
+
visible: props.visible,
|
|
182
|
+
serviceDeps: [props.url, props.layers, props.layerDefs, props.format, props.dpi, props.transparent, props.token, props.apiKey, props.authentication, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions],
|
|
183
|
+
createService: (mapInstance, resolvedSourceId) => {
|
|
184
|
+
const options = {
|
|
185
|
+
url: props.url
|
|
186
|
+
};
|
|
187
|
+
if (props.layers !== undefined) options.layers = props.layers;
|
|
188
|
+
if (props.layerDefs !== undefined) options.layerDefs = props.layerDefs;
|
|
189
|
+
if (props.format !== undefined) options.format = props.format;
|
|
190
|
+
if (props.dpi !== undefined) options.dpi = props.dpi;
|
|
191
|
+
if (props.transparent !== undefined) options.transparent = props.transparent;
|
|
192
|
+
applyAuthOptions(options, props);
|
|
193
|
+
return new DynamicMapService(resolvedSourceId, mapInstance, options);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
138
196
|
return null;
|
|
139
197
|
}
|
|
140
198
|
|
|
@@ -146,69 +204,21 @@ function EsriTiledLayer(props) {
|
|
|
146
204
|
current: map
|
|
147
205
|
} = useReactMapGL();
|
|
148
206
|
const sourceId = props.sourceId || `esri-tiled-${props.id}`;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
setIsMapLoaded(true);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const handleLoad = () => setIsMapLoaded(true);
|
|
163
|
-
mi?.once?.('load', handleLoad);
|
|
164
|
-
return () => {
|
|
165
|
-
mi?.off?.('load', handleLoad);
|
|
166
|
-
};
|
|
167
|
-
}, [map]);
|
|
168
|
-
const service = useMemo(() => {
|
|
169
|
-
if (!map || !isMapLoaded) return null;
|
|
170
|
-
const mapInstance = map.getMap?.();
|
|
171
|
-
if (!mapInstance) return null;
|
|
172
|
-
const options = {
|
|
173
|
-
url: props.url
|
|
174
|
-
};
|
|
175
|
-
applyAuthOptions(options, props);
|
|
176
|
-
return new TiledMapService(sourceId, mapInstance, options);
|
|
177
|
-
}, [map, isMapLoaded, sourceId, props.url, props.token, props.apiKey, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
if (!map || !service) return;
|
|
180
|
-
const mapInstance = map.getMap?.();
|
|
181
|
-
if (!mapInstance || typeof mapInstance.getLayer !== 'function' || typeof mapInstance.addLayer !== 'function') {
|
|
182
|
-
return () => {
|
|
183
|
-
service.remove();
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
// Add raster layer
|
|
187
|
-
if (!mapInstance.getLayer(props.id)) {
|
|
188
|
-
const layerConfig = {
|
|
189
|
-
id: props.id,
|
|
190
|
-
type: 'raster',
|
|
191
|
-
source: sourceId,
|
|
192
|
-
layout: {
|
|
193
|
-
visibility: props.visible !== false ? 'visible' : 'none'
|
|
194
|
-
}
|
|
207
|
+
useRasterLayer({
|
|
208
|
+
map,
|
|
209
|
+
layerId: props.id,
|
|
210
|
+
sourceId,
|
|
211
|
+
beforeId: props.beforeId,
|
|
212
|
+
visible: props.visible,
|
|
213
|
+
serviceDeps: [props.url, props.token, props.apiKey, props.authentication, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions],
|
|
214
|
+
createService: (mapInstance, resolvedSourceId) => {
|
|
215
|
+
const options = {
|
|
216
|
+
url: props.url
|
|
195
217
|
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} else {
|
|
199
|
-
mapInstance.addLayer(layerConfig);
|
|
200
|
-
}
|
|
218
|
+
applyAuthOptions(options, props);
|
|
219
|
+
return new TiledMapService(resolvedSourceId, mapInstance, options);
|
|
201
220
|
}
|
|
202
|
-
|
|
203
|
-
return () => {
|
|
204
|
-
if (mapInstance.getStyle?.() && mapInstance.getLayer?.(props.id)) {
|
|
205
|
-
mapInstance.removeLayer(props.id);
|
|
206
|
-
}
|
|
207
|
-
if (service) {
|
|
208
|
-
service.remove();
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}, [map, service, props.id, props.beforeId, props.visible, sourceId]);
|
|
221
|
+
});
|
|
212
222
|
return null;
|
|
213
223
|
}
|
|
214
224
|
|
|
@@ -220,73 +230,24 @@ function EsriImageLayer(props) {
|
|
|
220
230
|
current: map
|
|
221
231
|
} = useReactMapGL();
|
|
222
232
|
const sourceId = props.sourceId || `esri-image-${props.id}`;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
setIsMapLoaded(true);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const handleLoad = () => setIsMapLoaded(true);
|
|
237
|
-
mi?.once?.('load', handleLoad);
|
|
238
|
-
return () => {
|
|
239
|
-
mi?.off?.('load', handleLoad);
|
|
240
|
-
};
|
|
241
|
-
}, [map]);
|
|
242
|
-
const service = useMemo(() => {
|
|
243
|
-
if (!map || !isMapLoaded) return null;
|
|
244
|
-
const mapInstance = map.getMap?.();
|
|
245
|
-
if (!mapInstance) return null;
|
|
246
|
-
// Only include defined properties to avoid overriding defaults with undefined
|
|
247
|
-
const options = {
|
|
248
|
-
url: props.url
|
|
249
|
-
};
|
|
250
|
-
if (props.renderingRule !== undefined) options.renderingRule = props.renderingRule;
|
|
251
|
-
if (props.mosaicRule !== undefined) options.mosaicRule = props.mosaicRule;
|
|
252
|
-
if (props.format !== undefined) options.format = props.format;
|
|
253
|
-
applyAuthOptions(options, props);
|
|
254
|
-
return new ImageService(sourceId, mapInstance, options);
|
|
255
|
-
}, [map, isMapLoaded, sourceId, props.url, props.renderingRule, props.mosaicRule, props.format, props.token, props.apiKey, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
if (!map || !service) return;
|
|
258
|
-
const mapInstance = map.getMap?.();
|
|
259
|
-
if (!mapInstance || typeof mapInstance.getLayer !== 'function' || typeof mapInstance.addLayer !== 'function') {
|
|
260
|
-
return () => {
|
|
261
|
-
service.remove();
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
// Add raster layer
|
|
265
|
-
if (!mapInstance.getLayer(props.id)) {
|
|
266
|
-
const layerConfig = {
|
|
267
|
-
id: props.id,
|
|
268
|
-
type: 'raster',
|
|
269
|
-
source: sourceId,
|
|
270
|
-
layout: {
|
|
271
|
-
visibility: props.visible !== false ? 'visible' : 'none'
|
|
272
|
-
}
|
|
233
|
+
useRasterLayer({
|
|
234
|
+
map,
|
|
235
|
+
layerId: props.id,
|
|
236
|
+
sourceId,
|
|
237
|
+
beforeId: props.beforeId,
|
|
238
|
+
visible: props.visible,
|
|
239
|
+
serviceDeps: [props.url, props.renderingRule, props.mosaicRule, props.format, props.token, props.apiKey, props.authentication, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions],
|
|
240
|
+
createService: (mapInstance, resolvedSourceId) => {
|
|
241
|
+
const options = {
|
|
242
|
+
url: props.url
|
|
273
243
|
};
|
|
274
|
-
if (props.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
244
|
+
if (props.renderingRule !== undefined) options.renderingRule = props.renderingRule;
|
|
245
|
+
if (props.mosaicRule !== undefined) options.mosaicRule = props.mosaicRule;
|
|
246
|
+
if (props.format !== undefined) options.format = props.format;
|
|
247
|
+
applyAuthOptions(options, props);
|
|
248
|
+
return new ImageService(resolvedSourceId, mapInstance, options);
|
|
279
249
|
}
|
|
280
|
-
|
|
281
|
-
return () => {
|
|
282
|
-
if (mapInstance.getStyle?.() && mapInstance.getLayer?.(props.id)) {
|
|
283
|
-
mapInstance.removeLayer(props.id);
|
|
284
|
-
}
|
|
285
|
-
if (service) {
|
|
286
|
-
service.remove();
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
}, [map, service, props.id, props.beforeId, props.visible, sourceId]);
|
|
250
|
+
});
|
|
290
251
|
return null;
|
|
291
252
|
}
|
|
292
253
|
|
|
@@ -298,31 +259,14 @@ function EsriVectorTileLayer(props) {
|
|
|
298
259
|
current: map
|
|
299
260
|
} = useReactMapGL();
|
|
300
261
|
const sourceId = props.sourceId || `esri-vector-tile-${props.id}`;
|
|
301
|
-
const
|
|
262
|
+
const isMapLoaded = useMapLoaded(map);
|
|
302
263
|
const serviceRef = useRef(null);
|
|
303
264
|
const layerIdsRef = useRef([]);
|
|
304
|
-
// Wait for map to be loaded before creating service
|
|
305
|
-
useEffect(() => {
|
|
306
|
-
if (!map) return;
|
|
307
|
-
const mapInstance = map.getMap?.();
|
|
308
|
-
const mi = mapInstance;
|
|
309
|
-
if (!mi || typeof mi.isStyleLoaded !== 'function') return;
|
|
310
|
-
if (mi.isStyleLoaded()) {
|
|
311
|
-
setIsMapLoaded(true);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const handleLoad = () => setIsMapLoaded(true);
|
|
315
|
-
mi?.once?.('load', handleLoad);
|
|
316
|
-
return () => {
|
|
317
|
-
mi?.off?.('load', handleLoad);
|
|
318
|
-
};
|
|
319
|
-
}, [map]);
|
|
320
|
-
// Create VectorTileService, fetch style, add layers, and clean up on unmount
|
|
321
265
|
useEffect(() => {
|
|
322
266
|
if (!map || !isMapLoaded) return;
|
|
323
267
|
const mapInstance = map.getMap?.();
|
|
324
268
|
if (!mapInstance) return;
|
|
325
|
-
const
|
|
269
|
+
const layerApi = mapInstance;
|
|
326
270
|
let cancelled = false;
|
|
327
271
|
const options = {
|
|
328
272
|
url: props.url
|
|
@@ -330,35 +274,23 @@ function EsriVectorTileLayer(props) {
|
|
|
330
274
|
applyAuthOptions(options, props);
|
|
331
275
|
const service = new VectorTileService(sourceId, mapInstance, options);
|
|
332
276
|
serviceRef.current = service;
|
|
333
|
-
// Fetch the full style and add all layers from it
|
|
334
277
|
service.getStyle().then(() => {
|
|
335
|
-
if (cancelled) return;
|
|
336
|
-
// Fetch the full style document
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
344
|
-
if (props.apiKey) {
|
|
345
|
-
fetchInit.headers = {
|
|
346
|
-
...(fetchInit.headers || {}),
|
|
347
|
-
'X-Esri-Authorization': `Bearer ${props.apiKey}`
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
return fetch(styleUrl, fetchInit);
|
|
351
|
-
}).then(response => {
|
|
352
|
-
if (cancelled || !response) return;
|
|
353
|
-
if (!response.ok) throw new Error(`Failed to fetch style: ${response.status}`);
|
|
354
|
-
return response.json();
|
|
278
|
+
if (cancelled) return undefined;
|
|
279
|
+
// Fetch the full style document through the shared request layer so
|
|
280
|
+
// auth (token / apiKey / authentication) is handled consistently.
|
|
281
|
+
return esriRequest(`${props.url}/resources/styles/root.json`, {
|
|
282
|
+
httpMethod: 'GET',
|
|
283
|
+
token: props.token,
|
|
284
|
+
apiKey: props.apiKey,
|
|
285
|
+
authentication: props.authentication
|
|
286
|
+
});
|
|
355
287
|
}).then(data => {
|
|
356
288
|
if (cancelled || !data?.layers) return;
|
|
357
289
|
const addedIds = [];
|
|
358
290
|
for (const layer of data.layers) {
|
|
359
291
|
if (!layer['source-layer']) continue;
|
|
360
292
|
const layerId = `${props.id}-${layer.id}`;
|
|
361
|
-
if (
|
|
293
|
+
if (layerApi.getLayer?.(layerId)) continue;
|
|
362
294
|
const layerConfig = {
|
|
363
295
|
id: layerId,
|
|
364
296
|
type: layer.type,
|
|
@@ -370,17 +302,16 @@ function EsriVectorTileLayer(props) {
|
|
|
370
302
|
if (layer.filter) layerConfig.filter = layer.filter;
|
|
371
303
|
if (layer.minzoom !== undefined) layerConfig.minzoom = layer.minzoom;
|
|
372
304
|
if (layer.maxzoom !== undefined) layerConfig.maxzoom = layer.maxzoom;
|
|
373
|
-
// Apply visibility from props
|
|
374
305
|
if (props.visible === false) {
|
|
375
306
|
layerConfig.layout = {
|
|
376
|
-
...layerConfig.layout,
|
|
307
|
+
...(layerConfig.layout || {}),
|
|
377
308
|
visibility: 'none'
|
|
378
309
|
};
|
|
379
310
|
}
|
|
380
311
|
if (props.beforeId) {
|
|
381
|
-
|
|
312
|
+
layerApi.addLayer?.(layerConfig, props.beforeId);
|
|
382
313
|
} else {
|
|
383
|
-
|
|
314
|
+
layerApi.addLayer?.(layerConfig);
|
|
384
315
|
}
|
|
385
316
|
addedIds.push(layerId);
|
|
386
317
|
}
|
|
@@ -390,11 +321,10 @@ function EsriVectorTileLayer(props) {
|
|
|
390
321
|
});
|
|
391
322
|
return () => {
|
|
392
323
|
cancelled = true;
|
|
393
|
-
|
|
394
|
-
if (mi.getStyle?.()) {
|
|
324
|
+
if (layerApi.getStyle?.()) {
|
|
395
325
|
for (const id of layerIdsRef.current) {
|
|
396
326
|
try {
|
|
397
|
-
if (
|
|
327
|
+
if (layerApi.getLayer?.(id)) layerApi.removeLayer?.(id);
|
|
398
328
|
} catch {
|
|
399
329
|
// layer may already be gone
|
|
400
330
|
}
|
|
@@ -404,7 +334,7 @@ function EsriVectorTileLayer(props) {
|
|
|
404
334
|
service.remove();
|
|
405
335
|
serviceRef.current = null;
|
|
406
336
|
};
|
|
407
|
-
}, [map, isMapLoaded, sourceId, props.url, props.id, props.beforeId, props.visible, props.token, props.apiKey, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
337
|
+
}, [map, isMapLoaded, sourceId, props.url, props.id, props.beforeId, props.visible, props.token, props.apiKey, props.authentication, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
408
338
|
return null;
|
|
409
339
|
}
|
|
410
340
|
|
|
@@ -416,10 +346,15 @@ function EsriVectorBasemapLayer(props) {
|
|
|
416
346
|
current: map
|
|
417
347
|
} = useReactMapGL();
|
|
418
348
|
const service = useMemo(() => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
349
|
+
// Forward all the auth/locale options VectorBasemapStyle supports (token or
|
|
350
|
+
// apiKey, plus language/worldview), only including the ones provided.
|
|
351
|
+
const auth = {};
|
|
352
|
+
if (props.token !== undefined) auth.token = props.token;
|
|
353
|
+
if (props.apiKey !== undefined) auth.apiKey = props.apiKey;
|
|
354
|
+
if (props.language !== undefined) auth.language = props.language;
|
|
355
|
+
if (props.worldview !== undefined) auth.worldview = props.worldview;
|
|
356
|
+
return new VectorBasemapStyle(props.basemapEnum, auth);
|
|
357
|
+
}, [props.basemapEnum, props.token, props.apiKey, props.language, props.worldview]);
|
|
423
358
|
useEffect(() => {
|
|
424
359
|
if (!map || !service) return;
|
|
425
360
|
// Vector basemap styles replace the entire map style
|
|
@@ -443,38 +378,18 @@ function EsriFeatureLayer(props) {
|
|
|
443
378
|
current: map
|
|
444
379
|
} = useReactMapGL();
|
|
445
380
|
const sourceId = props.sourceId || `esri-feature-${props.id}`;
|
|
446
|
-
const
|
|
381
|
+
const isMapLoaded = useMapLoaded(map);
|
|
447
382
|
const serviceRef = useRef(null);
|
|
448
383
|
// Keep stable refs for object props to avoid effect re-runs on every render
|
|
449
384
|
const paintRef = useRef(props.paint);
|
|
450
385
|
paintRef.current = props.paint;
|
|
451
386
|
const layoutRef = useRef(props.layout);
|
|
452
387
|
layoutRef.current = props.layout;
|
|
453
|
-
// Wait for map to be loaded before creating service
|
|
454
|
-
useEffect(() => {
|
|
455
|
-
if (!map) return;
|
|
456
|
-
const mapInstance = map.getMap?.();
|
|
457
|
-
const mi = mapInstance;
|
|
458
|
-
if (!mi || typeof mi.isStyleLoaded !== 'function') {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
if (mi.isStyleLoaded()) {
|
|
462
|
-
setIsMapLoaded(true);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const handleLoad = () => setIsMapLoaded(true);
|
|
466
|
-
mi?.once?.('load', handleLoad);
|
|
467
|
-
return () => {
|
|
468
|
-
mi?.off?.('load', handleLoad);
|
|
469
|
-
};
|
|
470
|
-
}, [map]);
|
|
471
|
-
// Create FeatureService, add layer, and clean up on unmount
|
|
472
388
|
useEffect(() => {
|
|
473
389
|
if (!map || !isMapLoaded) return;
|
|
474
390
|
const mapInstance = map.getMap?.();
|
|
475
391
|
if (!mapInstance) return;
|
|
476
|
-
const
|
|
477
|
-
// Only include defined properties to avoid overriding defaults with undefined
|
|
392
|
+
const layerApi = mapInstance;
|
|
478
393
|
const options = {
|
|
479
394
|
url: props.url
|
|
480
395
|
};
|
|
@@ -483,8 +398,7 @@ function EsriFeatureLayer(props) {
|
|
|
483
398
|
applyAuthOptions(options, props);
|
|
484
399
|
const service = new FeatureService(sourceId, mapInstance, options);
|
|
485
400
|
serviceRef.current = service;
|
|
486
|
-
|
|
487
|
-
if (typeof mi.getLayer === 'function' && typeof mi.addLayer === 'function' && !mi.getLayer(props.id)) {
|
|
401
|
+
if (typeof layerApi.getLayer === 'function' && typeof layerApi.addLayer === 'function' && !layerApi.getLayer(props.id)) {
|
|
488
402
|
const layerType = props.type || 'fill';
|
|
489
403
|
const defaultPaint = layerType === 'circle' ? {
|
|
490
404
|
'circle-radius': 4,
|
|
@@ -504,87 +418,161 @@ function EsriFeatureLayer(props) {
|
|
|
504
418
|
}
|
|
505
419
|
};
|
|
506
420
|
if (props.beforeId) {
|
|
507
|
-
|
|
421
|
+
layerApi.addLayer(layerConfig, props.beforeId);
|
|
508
422
|
} else {
|
|
509
|
-
|
|
423
|
+
layerApi.addLayer(layerConfig);
|
|
510
424
|
}
|
|
511
425
|
}
|
|
512
426
|
return () => {
|
|
513
|
-
if (
|
|
514
|
-
|
|
427
|
+
if (layerApi.getStyle?.() && layerApi.getLayer?.(props.id)) {
|
|
428
|
+
layerApi.removeLayer?.(props.id);
|
|
515
429
|
}
|
|
516
430
|
service.remove();
|
|
517
431
|
serviceRef.current = null;
|
|
518
432
|
};
|
|
519
|
-
}, [map, isMapLoaded, sourceId, props.url, props.where, props.outFields, props.id, props.beforeId, props.visible, props.type, props.token, props.apiKey, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
433
|
+
}, [map, isMapLoaded, sourceId, props.url, props.where, props.outFields, props.id, props.beforeId, props.visible, props.type, props.token, props.apiKey, props.authentication, props.proxy, props.getAttributionFromService, props.requestParams, props.fetchOptions]);
|
|
520
434
|
return null;
|
|
521
435
|
}
|
|
522
436
|
|
|
523
437
|
/**
|
|
524
|
-
*
|
|
438
|
+
* React Map GL component that resolves an ArcGIS portal item id to the matching
|
|
439
|
+
* esri-gl service and adds a renderer-appropriate layer for it.
|
|
525
440
|
*/
|
|
526
|
-
function
|
|
441
|
+
function EsriPortalLayer(props) {
|
|
527
442
|
const {
|
|
528
|
-
current:
|
|
529
|
-
} =
|
|
530
|
-
const
|
|
443
|
+
current: map
|
|
444
|
+
} = useReactMapGL();
|
|
445
|
+
const sourceId = props.sourceId || `esri-portal-${props.id}`;
|
|
446
|
+
const isMapLoaded = useMapLoaded(map);
|
|
447
|
+
const serviceRef = useRef(null);
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
if (!map || !isMapLoaded || !props.itemId) return;
|
|
450
|
+
const mapInstance = map.getMap?.();
|
|
451
|
+
if (!mapInstance) return;
|
|
452
|
+
const layerApi = mapInstance;
|
|
453
|
+
const layerId = props.id;
|
|
454
|
+
let cancelled = false;
|
|
455
|
+
const options = {};
|
|
456
|
+
applyAuthOptions(options, props);
|
|
457
|
+
if (props.layerId !== undefined) options.layerId = props.layerId;
|
|
458
|
+
if (props.portal !== undefined) options.portal = props.portal;
|
|
459
|
+
serviceFromPortalItem(sourceId, mapInstance, props.itemId, options).then(async result => {
|
|
460
|
+
if (cancelled) {
|
|
461
|
+
try {
|
|
462
|
+
result.service.remove();
|
|
463
|
+
} catch {
|
|
464
|
+
/* ignore */
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
serviceRef.current = result.service;
|
|
469
|
+
if (layerApi.getLayer?.(layerId)) {
|
|
470
|
+
props.onResolve?.(result);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
let layerConfig;
|
|
474
|
+
if (result.kind === 'feature' || result.kind === 'vector-tile') {
|
|
475
|
+
// These services expose a default style (source already set to sourceId).
|
|
476
|
+
const styled = result.service;
|
|
477
|
+
const style = await styled.getStyle();
|
|
478
|
+
if (cancelled || layerApi.getLayer?.(layerId)) return;
|
|
479
|
+
layerConfig = {
|
|
480
|
+
...style,
|
|
481
|
+
id: layerId,
|
|
482
|
+
source: sourceId
|
|
483
|
+
};
|
|
484
|
+
} else {
|
|
485
|
+
// dynamic / tiled / image → raster
|
|
486
|
+
layerConfig = {
|
|
487
|
+
id: layerId,
|
|
488
|
+
type: 'raster',
|
|
489
|
+
source: sourceId
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
if (props.visible === false) {
|
|
493
|
+
layerConfig.layout = {
|
|
494
|
+
...(layerConfig.layout || {}),
|
|
495
|
+
visibility: 'none'
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
layerApi.addLayer?.(layerConfig, props.beforeId);
|
|
499
|
+
props.onResolve?.(result);
|
|
500
|
+
}).catch(err => {
|
|
501
|
+
if (!cancelled) console.warn('EsriPortalLayer: failed to resolve portal item', err);
|
|
502
|
+
});
|
|
503
|
+
return () => {
|
|
504
|
+
cancelled = true;
|
|
505
|
+
try {
|
|
506
|
+
if (layerApi.getLayer?.(layerId)) layerApi.removeLayer?.(layerId);
|
|
507
|
+
} catch {
|
|
508
|
+
/* layer may already be gone */
|
|
509
|
+
}
|
|
510
|
+
if (serviceRef.current) {
|
|
511
|
+
try {
|
|
512
|
+
serviceRef.current.remove();
|
|
513
|
+
} catch {
|
|
514
|
+
/* ignore */
|
|
515
|
+
}
|
|
516
|
+
serviceRef.current = null;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
// Note: onResolve is intentionally excluded from deps to avoid re-resolving
|
|
520
|
+
// when an inline callback identity changes.
|
|
521
|
+
}, [map, isMapLoaded, sourceId, props.id, props.itemId, props.layerId, props.portal, props.beforeId, props.visible, props.token, props.apiKey, props.authentication]);
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Build the `useEsri{Mapbox,Maplibre}Layer` hook result from a map collection
|
|
527
|
+
* returned by the corresponding react-map-gl `useMap` hook. Keeps the two
|
|
528
|
+
* flavors of the hook identical except for the map source.
|
|
529
|
+
*/
|
|
530
|
+
function createEsriLayerHooks(collection) {
|
|
531
|
+
// Preserve legacy return shape: `map` is undefined when no ref, the
|
|
532
|
+
// underlying map otherwise. Child hooks type `map: Map | null`, so we
|
|
533
|
+
// coerce for the injection below.
|
|
534
|
+
const map = collection?.current?.getMap?.();
|
|
535
|
+
const injectedMap = map ?? null;
|
|
531
536
|
return {
|
|
532
537
|
map,
|
|
533
538
|
useDynamicMapService: options => useDynamicMapService({
|
|
534
539
|
...options,
|
|
535
|
-
map
|
|
540
|
+
map: injectedMap
|
|
536
541
|
}),
|
|
537
542
|
useTiledMapService: options => useTiledMapService({
|
|
538
543
|
...options,
|
|
539
|
-
map
|
|
544
|
+
map: injectedMap
|
|
540
545
|
}),
|
|
541
546
|
useImageService: options => useImageService({
|
|
542
547
|
...options,
|
|
543
|
-
map
|
|
548
|
+
map: injectedMap
|
|
544
549
|
}),
|
|
545
550
|
useVectorTileService: options => useVectorTileService({
|
|
546
551
|
...options,
|
|
547
|
-
map
|
|
552
|
+
map: injectedMap
|
|
548
553
|
}),
|
|
549
554
|
useFeatureService: options => useFeatureService({
|
|
550
555
|
...options,
|
|
551
|
-
map
|
|
556
|
+
map: injectedMap
|
|
552
557
|
})
|
|
553
558
|
};
|
|
554
559
|
}
|
|
555
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Hook for using Esri services with Mapbox GL JS (via react-map-gl)
|
|
563
|
+
*/
|
|
564
|
+
function useEsriMapboxLayer() {
|
|
565
|
+
const collection = useMap();
|
|
566
|
+
return createEsriLayerHooks(collection);
|
|
567
|
+
}
|
|
568
|
+
|
|
556
569
|
/**
|
|
557
570
|
* Hook for using Esri services with MapLibre GL JS (via react-map-gl/maplibre)
|
|
558
571
|
*/
|
|
559
572
|
function useEsriMaplibreLayer() {
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
} = useMap$1();
|
|
563
|
-
const map = mapRef?.getMap();
|
|
564
|
-
return {
|
|
565
|
-
map,
|
|
566
|
-
useDynamicMapService: options => useDynamicMapService({
|
|
567
|
-
...options,
|
|
568
|
-
map
|
|
569
|
-
}),
|
|
570
|
-
useTiledMapService: options => useTiledMapService({
|
|
571
|
-
...options,
|
|
572
|
-
map
|
|
573
|
-
}),
|
|
574
|
-
useImageService: options => useImageService({
|
|
575
|
-
...options,
|
|
576
|
-
map
|
|
577
|
-
}),
|
|
578
|
-
useVectorTileService: options => useVectorTileService({
|
|
579
|
-
...options,
|
|
580
|
-
map
|
|
581
|
-
}),
|
|
582
|
-
useFeatureService: options => useFeatureService({
|
|
583
|
-
...options,
|
|
584
|
-
map
|
|
585
|
-
})
|
|
586
|
-
};
|
|
573
|
+
const collection = useMap$1();
|
|
574
|
+
return createEsriLayerHooks(collection);
|
|
587
575
|
}
|
|
588
576
|
|
|
589
|
-
export { DynamicMapService, EsriDynamicLayer, EsriFeatureLayer, EsriImageLayer, EsriTiledLayer, EsriVectorBasemapLayer, EsriVectorTileLayer, FeatureService, ImageService, TiledMapService, VectorBasemapStyle, VectorTileService, useEsriMapboxLayer, useEsriMaplibreLayer };
|
|
577
|
+
export { DynamicMapService, EsriDynamicLayer, EsriFeatureLayer, EsriImageLayer, EsriPortalLayer, EsriTiledLayer, EsriVectorBasemapLayer, EsriVectorTileLayer, FeatureService, ImageService, TiledMapService, VectorBasemapStyle, VectorTileService, esriRequest, serviceFromPortalItem, useEsriMapboxLayer, useEsriMaplibreLayer };
|
|
590
578
|
//# sourceMappingURL=react-map-gl.js.map
|