esri-gl 0.9.0-alpha.13 → 0.9.0-alpha.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/README.md +15 -2
- package/dist/esri-gl.esm.js +298 -68
- package/dist/esri-gl.esm.js.map +1 -1
- package/dist/esri-gl.js +298 -68
- package/dist/esri-gl.js.map +1 -1
- package/dist/esri-gl.min.js +1 -1
- package/dist/esri-gl.min.js.map +1 -1
- package/dist/index-C6q9qGh6.d.ts +1272 -0
- package/dist/index.d.ts +1 -1248
- package/dist/index.js +298 -68
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +298 -68
- package/dist/index.umd.js.map +1 -1
- package/dist/package.json +65 -0
- package/dist/react-map-gl.d.ts +3 -3
- package/dist/react-map-gl.js +192 -47
- package/dist/react-map-gl.js.map +1 -1
- package/dist/react.d.ts +19 -6
- package/dist/react.js +81 -29
- package/dist/react.js.map +1 -1
- package/dist/{useFeatureService-Dsqu-yjX.js → useFeatureService-C8yzDPdU.js} +43 -26
- package/dist/useFeatureService-C8yzDPdU.js.map +1 -0
- package/dist/{useFeatureService-CLnmgBg3.d.ts → useFeatureService-Rb-V86Ks.d.ts} +8 -12
- package/package.json +17 -5
- package/dist/useFeatureService-Dsqu-yjX.js.map +0 -1
package/README.md
CHANGED
|
@@ -368,7 +368,7 @@ For the smoothest React experience with declarative layer management:
|
|
|
368
368
|
|
|
369
369
|
```typescript
|
|
370
370
|
import React, { useState } from 'react';
|
|
371
|
-
import { Map } from 'react-map-gl';
|
|
371
|
+
import { Map } from 'react-map-gl/mapbox';
|
|
372
372
|
import {
|
|
373
373
|
EsriDynamicLayer,
|
|
374
374
|
EsriFeatureLayer,
|
|
@@ -485,7 +485,7 @@ import type {
|
|
|
485
485
|
IdentifyResult,
|
|
486
486
|
EsriLayerProps
|
|
487
487
|
} from 'esri-gl';
|
|
488
|
-
import type { MapRef } from 'react-map-gl';
|
|
488
|
+
import type { MapRef } from 'react-map-gl/mapbox';
|
|
489
489
|
|
|
490
490
|
// Fully typed component props
|
|
491
491
|
interface MapComponentProps {
|
|
@@ -653,6 +653,8 @@ All type declarations are available in the `dist/` directory after building.
|
|
|
653
653
|
|
|
654
654
|
## Contributing
|
|
655
655
|
|
|
656
|
+
We welcome contributions! Please follow these steps:
|
|
657
|
+
|
|
656
658
|
1. Fork the repository
|
|
657
659
|
2. Create a feature branch: `git checkout -b feature/my-feature`
|
|
658
660
|
3. Make changes and add tests
|
|
@@ -661,6 +663,17 @@ All type declarations are available in the `dist/` directory after building.
|
|
|
661
663
|
6. Push to branch: `git push origin feature/my-feature`
|
|
662
664
|
7. Submit a pull request
|
|
663
665
|
|
|
666
|
+
### Pre-commit Hooks
|
|
667
|
+
|
|
668
|
+
This project uses [Husky](https://typicode.github.io/husky/) to automatically run quality checks before each commit:
|
|
669
|
+
|
|
670
|
+
- **Formatting & Linting**: Automatically formats and lints staged files using Prettier and ESLint
|
|
671
|
+
- **Testing**: Runs the full test suite to ensure no regressions
|
|
672
|
+
|
|
673
|
+
The hooks are automatically installed when you run `npm install`. If you need to skip them (not recommended), you can use `git commit --no-verify`.
|
|
674
|
+
|
|
675
|
+
For more details, see [.husky/README.md](.husky/README.md).
|
|
676
|
+
|
|
664
677
|
## License
|
|
665
678
|
|
|
666
679
|
MIT License - see [LICENSE](LICENSE) file for details.
|
package/dist/esri-gl.esm.js
CHANGED
|
@@ -462,7 +462,11 @@ class DynamicMapService {
|
|
|
462
462
|
this.rasterSrcOptions = rasterSrcOptions;
|
|
463
463
|
this.esriServiceOptions = esriServiceOptions;
|
|
464
464
|
this._createSource();
|
|
465
|
-
if (this.options.getAttributionFromService)
|
|
465
|
+
if (this.options.getAttributionFromService) {
|
|
466
|
+
this.setAttributionFromService().catch(() => {
|
|
467
|
+
// Silently handle attribution fetch errors to prevent unhandled rejections
|
|
468
|
+
});
|
|
469
|
+
}
|
|
466
470
|
}
|
|
467
471
|
get options() {
|
|
468
472
|
return {
|
|
@@ -543,7 +547,10 @@ class DynamicMapService {
|
|
|
543
547
|
};
|
|
544
548
|
}
|
|
545
549
|
_createSource() {
|
|
546
|
-
|
|
550
|
+
// Check if source already exists before adding
|
|
551
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
552
|
+
this._map.addSource(this._sourceId, this._source);
|
|
553
|
+
}
|
|
547
554
|
}
|
|
548
555
|
// This requires hooking into some undocumented methods
|
|
549
556
|
_updateSource() {
|
|
@@ -562,11 +569,21 @@ class DynamicMapService {
|
|
|
562
569
|
const src = this._map.getSource(this._sourceId);
|
|
563
570
|
if (!src) return;
|
|
564
571
|
try {
|
|
565
|
-
src.tiles
|
|
572
|
+
// Ensure src.tiles exists before accessing it
|
|
573
|
+
if (src.tiles && Array.isArray(src.tiles) && this._source.tiles && this._source.tiles.length > 0) {
|
|
574
|
+
src.tiles[0] = this._source.tiles[0];
|
|
575
|
+
}
|
|
566
576
|
src._options = this._source;
|
|
567
577
|
if (src.setTiles) {
|
|
568
578
|
// New MapboxGL >= 2.13.0
|
|
569
|
-
|
|
579
|
+
// setTiles may return a promise - handle rejections to prevent console errors
|
|
580
|
+
const result = src.setTiles(this._source.tiles);
|
|
581
|
+
if (result && typeof result.catch === 'function') {
|
|
582
|
+
result.catch(() => {
|
|
583
|
+
// Silently ignore - setTiles rejections are often abort errors during rapid updates
|
|
584
|
+
// The outer try/catch will handle any synchronous errors
|
|
585
|
+
});
|
|
586
|
+
}
|
|
570
587
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
571
588
|
} else if (this._map.style.sourceCaches) {
|
|
572
589
|
// Old MapboxGL and MaplibreGL
|
|
@@ -582,8 +599,21 @@ class DynamicMapService {
|
|
|
582
599
|
this._map.style.sourceCaches[this._sourceId].update(this._map.transform);
|
|
583
600
|
}
|
|
584
601
|
} catch (error) {
|
|
585
|
-
|
|
586
|
-
|
|
602
|
+
// Comprehensive abort detection - check all possible error shapes
|
|
603
|
+
// MapLibre can throw various forms of AbortError
|
|
604
|
+
const errorName = error?.name;
|
|
605
|
+
const errorMessage = error?.message;
|
|
606
|
+
const errorConstructor = error?.constructor?.name;
|
|
607
|
+
let stringified = '';
|
|
608
|
+
try {
|
|
609
|
+
stringified = String(error);
|
|
610
|
+
} catch {
|
|
611
|
+
// ignore
|
|
612
|
+
}
|
|
613
|
+
// Check every possible way an error could represent AbortError
|
|
614
|
+
const isAbortError = error instanceof DOMException && error.name === 'AbortError' || error instanceof Error && error.name === 'AbortError' || errorName === 'AbortError' || errorConstructor === 'AbortError' || errorMessage?.toLowerCase().includes('abort') || stringified.toLowerCase().includes('abort') || errorMessage?.includes('AbortError') || stringified.includes('AbortError') || stringified === 'Error: AbortError' || error === 'AbortError';
|
|
615
|
+
if (isAbortError) {
|
|
616
|
+
// Silently ignore aborted tile operations - MapLibre will retry
|
|
587
617
|
return;
|
|
588
618
|
}
|
|
589
619
|
// Swallow occasional transient errors that can happen during style reloads
|
|
@@ -1146,7 +1176,54 @@ class DynamicMapService {
|
|
|
1146
1176
|
this._updateSource();
|
|
1147
1177
|
}
|
|
1148
1178
|
remove() {
|
|
1149
|
-
this._map
|
|
1179
|
+
const map = this._map;
|
|
1180
|
+
if (!map || typeof map.removeSource !== 'function') {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
const mapWithStyle = map;
|
|
1185
|
+
const mapLayerApi = map;
|
|
1186
|
+
const mapSourceApi = map;
|
|
1187
|
+
if (typeof mapWithStyle.getStyle === 'function') {
|
|
1188
|
+
const style = mapWithStyle.getStyle();
|
|
1189
|
+
const layers = style?.layers || [];
|
|
1190
|
+
layers.forEach(layer => {
|
|
1191
|
+
if (layer.source !== this._sourceId) return;
|
|
1192
|
+
if (typeof mapLayerApi.getLayer !== 'function' || typeof mapLayerApi.removeLayer !== 'function') {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
let hasLayer = false;
|
|
1196
|
+
try {
|
|
1197
|
+
hasLayer = Boolean(mapLayerApi.getLayer(layer.id));
|
|
1198
|
+
} catch {
|
|
1199
|
+
hasLayer = false;
|
|
1200
|
+
}
|
|
1201
|
+
if (!hasLayer) return;
|
|
1202
|
+
try {
|
|
1203
|
+
mapLayerApi.removeLayer(layer.id);
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
console.warn(`Failed to remove layer ${layer.id} for source ${this._sourceId}:`, error);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
if (typeof mapSourceApi.getSource === 'function') {
|
|
1210
|
+
let hasSource = false;
|
|
1211
|
+
try {
|
|
1212
|
+
hasSource = Boolean(mapSourceApi.getSource(this._sourceId));
|
|
1213
|
+
} catch {
|
|
1214
|
+
hasSource = false;
|
|
1215
|
+
}
|
|
1216
|
+
if (hasSource) {
|
|
1217
|
+
try {
|
|
1218
|
+
map.removeSource(this._sourceId);
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1226
|
+
}
|
|
1150
1227
|
}
|
|
1151
1228
|
}
|
|
1152
1229
|
|
|
@@ -1206,7 +1283,10 @@ class TiledMapService {
|
|
|
1206
1283
|
};
|
|
1207
1284
|
}
|
|
1208
1285
|
_createSource() {
|
|
1209
|
-
|
|
1286
|
+
// Check if source already exists before adding
|
|
1287
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
1288
|
+
this._map.addSource(this._sourceId, this._source);
|
|
1289
|
+
}
|
|
1210
1290
|
}
|
|
1211
1291
|
setAttributionFromService() {
|
|
1212
1292
|
if (this._serviceMetadata) {
|
|
@@ -1233,7 +1313,39 @@ class TiledMapService {
|
|
|
1233
1313
|
this._map.getSource(this._sourceId);
|
|
1234
1314
|
}
|
|
1235
1315
|
remove() {
|
|
1236
|
-
|
|
1316
|
+
// Guard against disposed or invalid map
|
|
1317
|
+
if (!this._map || typeof this._map.removeSource !== 'function') {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
// First, remove any layers that are using this source
|
|
1322
|
+
const mapWithStyle = this._map;
|
|
1323
|
+
if (mapWithStyle.getStyle && typeof mapWithStyle.getLayer === 'function') {
|
|
1324
|
+
const style = mapWithStyle.getStyle();
|
|
1325
|
+
const layers = style?.layers || [];
|
|
1326
|
+
const getLayer = mapWithStyle.getLayer;
|
|
1327
|
+
layers.forEach(layer => {
|
|
1328
|
+
if (layer.source === this._sourceId) {
|
|
1329
|
+
try {
|
|
1330
|
+
if (getLayer(layer.id)) {
|
|
1331
|
+
this._map.removeLayer(layer.id);
|
|
1332
|
+
}
|
|
1333
|
+
} catch {
|
|
1334
|
+
// Layer may already be removed
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
// Then check if source exists before trying to remove it
|
|
1340
|
+
if (typeof this._map.getSource === 'function') {
|
|
1341
|
+
const source = this._map.getSource(this._sourceId);
|
|
1342
|
+
if (source) {
|
|
1343
|
+
this._map.removeSource(this._sourceId);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1348
|
+
}
|
|
1237
1349
|
}
|
|
1238
1350
|
}
|
|
1239
1351
|
|
|
@@ -1294,7 +1406,11 @@ class ImageService {
|
|
|
1294
1406
|
this.rasterSrcOptions = rasterSrcOptions;
|
|
1295
1407
|
this.esriServiceOptions = esriServiceOptions;
|
|
1296
1408
|
this._createSource();
|
|
1297
|
-
if (this.options.getAttributionFromService)
|
|
1409
|
+
if (this.options.getAttributionFromService) {
|
|
1410
|
+
this.setAttributionFromService().catch(() => {
|
|
1411
|
+
// Silently handle attribution fetch errors to prevent unhandled rejections
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1298
1414
|
}
|
|
1299
1415
|
get options() {
|
|
1300
1416
|
return {
|
|
@@ -1332,11 +1448,18 @@ class ImageService {
|
|
|
1332
1448
|
};
|
|
1333
1449
|
}
|
|
1334
1450
|
_createSource() {
|
|
1335
|
-
|
|
1451
|
+
// Check if source already exists before adding
|
|
1452
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
1453
|
+
this._map.addSource(this._sourceId, this._source);
|
|
1454
|
+
}
|
|
1336
1455
|
}
|
|
1337
1456
|
// This requires hooking into some undocumented methods
|
|
1338
1457
|
_updateSource() {
|
|
1339
1458
|
const src = this._map.getSource(this._sourceId);
|
|
1459
|
+
if (!src) {
|
|
1460
|
+
// Source not yet added to map, nothing to update
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1340
1463
|
src.tiles[0] = this._source.tiles[0];
|
|
1341
1464
|
src._options = this._source;
|
|
1342
1465
|
if (src.setTiles) {
|
|
@@ -1411,7 +1534,27 @@ class ImageService {
|
|
|
1411
1534
|
this._updateSource();
|
|
1412
1535
|
}
|
|
1413
1536
|
remove() {
|
|
1414
|
-
this._map.removeSource
|
|
1537
|
+
if (this._map && typeof this._map.removeSource === 'function') {
|
|
1538
|
+
try {
|
|
1539
|
+
// First, remove any layers that are using this source
|
|
1540
|
+
const mapWithStyle = this._map;
|
|
1541
|
+
if (mapWithStyle.getStyle) {
|
|
1542
|
+
const style = mapWithStyle.getStyle();
|
|
1543
|
+
const layers = style?.layers || [];
|
|
1544
|
+
layers.forEach(layer => {
|
|
1545
|
+
if (layer.source === this._sourceId && this._map.getLayer(layer.id)) {
|
|
1546
|
+
this._map.removeLayer(layer.id);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
// Then check if source exists before trying to remove it
|
|
1551
|
+
if (this._map.getSource && this._map.getSource(this._sourceId)) {
|
|
1552
|
+
this._map.removeSource(this._sourceId);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1415
1558
|
}
|
|
1416
1559
|
}
|
|
1417
1560
|
|
|
@@ -1659,7 +1802,10 @@ class VectorTileService {
|
|
|
1659
1802
|
};
|
|
1660
1803
|
}
|
|
1661
1804
|
_createSource() {
|
|
1662
|
-
|
|
1805
|
+
// Check if source already exists before adding
|
|
1806
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
1807
|
+
this._map.addSource(this._sourceId, this._source);
|
|
1808
|
+
}
|
|
1663
1809
|
}
|
|
1664
1810
|
_mapToLocalSource(style) {
|
|
1665
1811
|
return {
|
|
@@ -1720,7 +1866,54 @@ class VectorTileService {
|
|
|
1720
1866
|
// Vector tile services don't need dynamic updates like dynamic services
|
|
1721
1867
|
}
|
|
1722
1868
|
remove() {
|
|
1723
|
-
this._map
|
|
1869
|
+
const map = this._map;
|
|
1870
|
+
if (!map || typeof map.removeSource !== 'function') {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
try {
|
|
1874
|
+
const mapWithStyle = map;
|
|
1875
|
+
const mapLayerApi = map;
|
|
1876
|
+
const mapSourceApi = map;
|
|
1877
|
+
if (typeof mapWithStyle.getStyle === 'function') {
|
|
1878
|
+
const style = mapWithStyle.getStyle();
|
|
1879
|
+
const layers = style?.layers || [];
|
|
1880
|
+
layers.forEach(layer => {
|
|
1881
|
+
if (layer.source !== this._sourceId) return;
|
|
1882
|
+
if (typeof mapLayerApi.getLayer !== 'function' || typeof mapLayerApi.removeLayer !== 'function') {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
let hasLayer = false;
|
|
1886
|
+
try {
|
|
1887
|
+
hasLayer = Boolean(mapLayerApi.getLayer(layer.id));
|
|
1888
|
+
} catch {
|
|
1889
|
+
hasLayer = false;
|
|
1890
|
+
}
|
|
1891
|
+
if (!hasLayer) return;
|
|
1892
|
+
try {
|
|
1893
|
+
mapLayerApi.removeLayer(layer.id);
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
console.warn(`Failed to remove layer ${layer.id} for source ${this._sourceId}:`, error);
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
if (typeof mapSourceApi.getSource === 'function') {
|
|
1900
|
+
let hasSource = false;
|
|
1901
|
+
try {
|
|
1902
|
+
hasSource = Boolean(mapSourceApi.getSource(this._sourceId));
|
|
1903
|
+
} catch {
|
|
1904
|
+
hasSource = false;
|
|
1905
|
+
}
|
|
1906
|
+
if (hasSource) {
|
|
1907
|
+
try {
|
|
1908
|
+
map.removeSource(this._sourceId);
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
1916
|
+
}
|
|
1724
1917
|
}
|
|
1725
1918
|
}
|
|
1726
1919
|
|
|
@@ -1762,6 +1955,18 @@ class FeatureService {
|
|
|
1762
1955
|
writable: true,
|
|
1763
1956
|
value: null
|
|
1764
1957
|
});
|
|
1958
|
+
Object.defineProperty(this, "_sourceReadyResolve", {
|
|
1959
|
+
enumerable: true,
|
|
1960
|
+
configurable: true,
|
|
1961
|
+
writable: true,
|
|
1962
|
+
value: null
|
|
1963
|
+
});
|
|
1964
|
+
Object.defineProperty(this, "_sourceReadyReject", {
|
|
1965
|
+
enumerable: true,
|
|
1966
|
+
configurable: true,
|
|
1967
|
+
writable: true,
|
|
1968
|
+
value: null
|
|
1969
|
+
});
|
|
1765
1970
|
Object.defineProperty(this, "vectorSrcOptions", {
|
|
1766
1971
|
enumerable: true,
|
|
1767
1972
|
configurable: true,
|
|
@@ -1774,6 +1979,32 @@ class FeatureService {
|
|
|
1774
1979
|
writable: true,
|
|
1775
1980
|
value: void 0
|
|
1776
1981
|
});
|
|
1982
|
+
/**
|
|
1983
|
+
* Promise that resolves when the source has been successfully added to the map.
|
|
1984
|
+
* This can be used to ensure the source exists before adding layers that reference it.
|
|
1985
|
+
*
|
|
1986
|
+
* @example
|
|
1987
|
+
* ```typescript
|
|
1988
|
+
* const service = new FeatureService('my-source', map, { url: '...' });
|
|
1989
|
+
*
|
|
1990
|
+
* // Wait for source to be ready before adding a layer
|
|
1991
|
+
* await service.sourceReady;
|
|
1992
|
+
* map.addLayer({
|
|
1993
|
+
* id: 'my-layer',
|
|
1994
|
+
* type: 'circle',
|
|
1995
|
+
* source: 'my-source',
|
|
1996
|
+
* paint: { 'circle-radius': 5, 'circle-color': '#007cbf' }
|
|
1997
|
+
* });
|
|
1998
|
+
* ```
|
|
1999
|
+
*
|
|
2000
|
+
* If source creation fails, the promise will reject with an error.
|
|
2001
|
+
*/
|
|
2002
|
+
Object.defineProperty(this, "sourceReady", {
|
|
2003
|
+
enumerable: true,
|
|
2004
|
+
configurable: true,
|
|
2005
|
+
writable: true,
|
|
2006
|
+
value: void 0
|
|
2007
|
+
});
|
|
1777
2008
|
if (!esriServiceOptions.url) {
|
|
1778
2009
|
throw new Error('A url must be supplied as part of the esriServiceOptions object.');
|
|
1779
2010
|
}
|
|
@@ -1800,49 +2031,48 @@ class FeatureService {
|
|
|
1800
2031
|
if (this.esriServiceOptions.useBoundingBox === undefined) {
|
|
1801
2032
|
this.esriServiceOptions.useBoundingBox = true;
|
|
1802
2033
|
}
|
|
2034
|
+
// Create the sourceReady promise that will resolve when the source is added to the map
|
|
2035
|
+
this.sourceReady = new Promise((resolve, reject) => {
|
|
2036
|
+
this._sourceReadyResolve = resolve;
|
|
2037
|
+
this._sourceReadyReject = reject;
|
|
2038
|
+
});
|
|
1803
2039
|
this._createSource();
|
|
1804
2040
|
}
|
|
1805
2041
|
async _createSource() {
|
|
2042
|
+
if (!this._map) {
|
|
2043
|
+
if (this._sourceReadyReject) {
|
|
2044
|
+
this._sourceReadyReject(new Error('Map not available'));
|
|
2045
|
+
}
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
1806
2048
|
try {
|
|
1807
2049
|
// Get service metadata
|
|
1808
2050
|
this._serviceMetadata = await getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions);
|
|
1809
2051
|
// Check if vector tiles should be used (default behavior)
|
|
1810
2052
|
// Note: Most FeatureServers don't support vector tiles, so we'll detect and fallback
|
|
1811
|
-
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
1812
|
-
if (!isTestEnvironment) {
|
|
1813
|
-
console.log('FeatureService: useVectorTiles setting:', this.esriServiceOptions.useVectorTiles);
|
|
1814
|
-
}
|
|
1815
2053
|
const vectorTileSupport = await this._checkVectorTileSupport();
|
|
1816
|
-
if (!isTestEnvironment) {
|
|
1817
|
-
console.log('FeatureService: Vector tile support detected:', vectorTileSupport);
|
|
1818
|
-
}
|
|
1819
2054
|
const useVectorTiles = this.esriServiceOptions.useVectorTiles !== false && vectorTileSupport;
|
|
1820
|
-
if (!isTestEnvironment) {
|
|
1821
|
-
console.log('FeatureService: Final decision - using vector tiles:', useVectorTiles);
|
|
1822
|
-
}
|
|
1823
2055
|
if (useVectorTiles) {
|
|
1824
2056
|
// Create vector tile source
|
|
1825
2057
|
const tileUrl = this._buildTileUrl();
|
|
1826
|
-
if
|
|
1827
|
-
|
|
2058
|
+
// Add vector source to map if it doesn't already exist
|
|
2059
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
2060
|
+
this._map.addSource(this._sourceId, {
|
|
2061
|
+
type: 'vector',
|
|
2062
|
+
tiles: [tileUrl],
|
|
2063
|
+
maxzoom: 24,
|
|
2064
|
+
...this.vectorSrcOptions
|
|
2065
|
+
});
|
|
1828
2066
|
}
|
|
1829
|
-
// Add vector source to map
|
|
1830
|
-
this._map.addSource(this._sourceId, {
|
|
1831
|
-
type: 'vector',
|
|
1832
|
-
tiles: [tileUrl],
|
|
1833
|
-
maxzoom: 24,
|
|
1834
|
-
...this.vectorSrcOptions
|
|
1835
|
-
});
|
|
1836
2067
|
} else {
|
|
1837
2068
|
// Fallback to GeoJSON (most common for FeatureServers)
|
|
1838
2069
|
const queryUrl = this._buildQueryUrl();
|
|
1839
|
-
if (!
|
|
1840
|
-
|
|
2070
|
+
if (!this._map.getSource(this._sourceId)) {
|
|
2071
|
+
this._map.addSource(this._sourceId, {
|
|
2072
|
+
type: 'geojson',
|
|
2073
|
+
data: queryUrl
|
|
2074
|
+
});
|
|
1841
2075
|
}
|
|
1842
|
-
this._map.addSource(this._sourceId, {
|
|
1843
|
-
type: 'geojson',
|
|
1844
|
-
data: queryUrl
|
|
1845
|
-
});
|
|
1846
2076
|
}
|
|
1847
2077
|
// Update attribution after source is added if available in service metadata
|
|
1848
2078
|
if (this._serviceMetadata?.copyrightText) {
|
|
@@ -1852,11 +2082,19 @@ class FeatureService {
|
|
|
1852
2082
|
if (!useVectorTiles && this.esriServiceOptions.useBoundingBox) {
|
|
1853
2083
|
this._setupBoundingBoxUpdates();
|
|
1854
2084
|
}
|
|
2085
|
+
// Resolve the sourceReady promise now that the source is successfully added
|
|
2086
|
+
if (this._sourceReadyResolve) {
|
|
2087
|
+
this._sourceReadyResolve();
|
|
2088
|
+
}
|
|
1855
2089
|
} catch (error) {
|
|
1856
2090
|
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
1857
2091
|
if (!isTestEnvironment) {
|
|
1858
2092
|
console.error('Error creating FeatureService source:', error);
|
|
1859
2093
|
}
|
|
2094
|
+
// Reject the sourceReady promise so callers can catch the error
|
|
2095
|
+
if (this._sourceReadyReject && error instanceof Error) {
|
|
2096
|
+
this._sourceReadyReject(error);
|
|
2097
|
+
}
|
|
1860
2098
|
// Don't rethrow - service should handle errors gracefully
|
|
1861
2099
|
// The source just won't be created and the service will be in a degraded state
|
|
1862
2100
|
}
|
|
@@ -1867,42 +2105,20 @@ class FeatureService {
|
|
|
1867
2105
|
const vectorTileUrl = this.esriServiceOptions.url.replace('/FeatureServer/', '/VectorTileServer/');
|
|
1868
2106
|
// Only check if the URL actually changed (meaning it was a FeatureServer URL)
|
|
1869
2107
|
if (vectorTileUrl === this.esriServiceOptions.url) {
|
|
1870
|
-
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
1871
|
-
if (!isTestEnvironment) {
|
|
1872
|
-
console.log('FeatureService: Not a FeatureServer URL, falling back to GeoJSON');
|
|
1873
|
-
}
|
|
1874
2108
|
return false;
|
|
1875
2109
|
}
|
|
1876
|
-
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
1877
|
-
if (!isTestEnvironment) {
|
|
1878
|
-
console.log('FeatureService: Checking vector tile support at:', vectorTileUrl);
|
|
1879
|
-
}
|
|
1880
2110
|
const response = await fetch(vectorTileUrl + '?f=json', this.esriServiceOptions.fetchOptions);
|
|
1881
2111
|
if (response.ok) {
|
|
1882
2112
|
const data = await response.json();
|
|
1883
2113
|
if (data && !data.error) {
|
|
1884
|
-
if (!isTestEnvironment) {
|
|
1885
|
-
console.log('FeatureService: Vector tile endpoint found and working:', vectorTileUrl);
|
|
1886
|
-
console.log('FeatureService: Vector tile service data:', data);
|
|
1887
|
-
}
|
|
1888
2114
|
return true;
|
|
1889
2115
|
} else {
|
|
1890
|
-
if (!isTestEnvironment) {
|
|
1891
|
-
console.log('FeatureService: Vector tile endpoint returned error:', data?.error);
|
|
1892
|
-
}
|
|
1893
2116
|
return false;
|
|
1894
2117
|
}
|
|
1895
2118
|
} else {
|
|
1896
|
-
if (!isTestEnvironment) {
|
|
1897
|
-
console.log('FeatureService: Vector tile endpoint returned HTTP', response.status);
|
|
1898
|
-
}
|
|
1899
2119
|
return false;
|
|
1900
2120
|
}
|
|
1901
|
-
} catch
|
|
1902
|
-
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
1903
|
-
if (!isTestEnvironment) {
|
|
1904
|
-
console.log('FeatureService: Vector tile check failed, falling back to GeoJSON:', error);
|
|
1905
|
-
}
|
|
2121
|
+
} catch {
|
|
1906
2122
|
return false;
|
|
1907
2123
|
}
|
|
1908
2124
|
}
|
|
@@ -2047,10 +2263,6 @@ class FeatureService {
|
|
|
2047
2263
|
const source = this._map.getSource(this._sourceId);
|
|
2048
2264
|
if (source && 'setData' in source && typeof source.setData === 'function') {
|
|
2049
2265
|
const newQueryUrl = this._buildQueryUrl();
|
|
2050
|
-
const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
|
|
2051
|
-
if (!isTestEnvironment) {
|
|
2052
|
-
console.log('Updating FeatureService data with new bounding box:', newQueryUrl);
|
|
2053
|
-
}
|
|
2054
2266
|
// @ts-ignore - GeoJSON source setData method not in generic Source type
|
|
2055
2267
|
source.setData(newQueryUrl);
|
|
2056
2268
|
}
|
|
@@ -2131,8 +2343,26 @@ class FeatureService {
|
|
|
2131
2343
|
// Note: maxRecordCount is a server capability; not settable via query params
|
|
2132
2344
|
remove() {
|
|
2133
2345
|
this._removeBoundingBoxUpdates();
|
|
2134
|
-
if (this._map
|
|
2135
|
-
|
|
2346
|
+
if (this._map && typeof this._map.removeSource === 'function') {
|
|
2347
|
+
try {
|
|
2348
|
+
// First, remove any layers that are using this source
|
|
2349
|
+
const mapWithStyle = this._map;
|
|
2350
|
+
if (mapWithStyle.getStyle) {
|
|
2351
|
+
const style = mapWithStyle.getStyle();
|
|
2352
|
+
const layers = style?.layers || [];
|
|
2353
|
+
layers.forEach(layer => {
|
|
2354
|
+
if (layer.source === this._sourceId && this._map.getLayer && this._map.getLayer(layer.id)) {
|
|
2355
|
+
this._map.removeLayer(layer.id);
|
|
2356
|
+
}
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
// Then check if source exists before trying to remove it
|
|
2360
|
+
if (this._map.getSource && this._map.getSource(this._sourceId)) {
|
|
2361
|
+
this._map.removeSource(this._sourceId);
|
|
2362
|
+
}
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
console.warn(`Failed to remove source ${this._sourceId}:`, error);
|
|
2365
|
+
}
|
|
2136
2366
|
}
|
|
2137
2367
|
}
|
|
2138
2368
|
async queryFeatures(options) {
|