esri-gl 0.9.1 → 0.10.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.
@@ -55,7 +55,9 @@ function updateAttribution(newAttribution, sourceId, map) {
55
55
  if (!attributionController) return;
56
56
  const customAttribution = attributionController.options?.customAttribution;
57
57
  if (typeof customAttribution === 'string') {
58
- attributionController.options.customAttribution = `${customAttribution} | ${POWERED_BY_ESRI_ATTRIBUTION_STRING}`;
58
+ if (!customAttribution.includes(POWERED_BY_ESRI_ATTRIBUTION_STRING)) {
59
+ attributionController.options.customAttribution = `${customAttribution} | ${POWERED_BY_ESRI_ATTRIBUTION_STRING}`;
60
+ }
59
61
  } else if (customAttribution === undefined) {
60
62
  if (attributionController.options) {
61
63
  attributionController.options.customAttribution = POWERED_BY_ESRI_ATTRIBUTION_STRING;
@@ -73,8 +75,9 @@ function updateAttribution(newAttribution, sourceId, map) {
73
75
  } else if (mapStyle?._otherSourceCaches?.[sourceId]?._source) {
74
76
  mapStyle._otherSourceCaches[sourceId]._source.attribution = newAttribution;
75
77
  } else {
76
- console.warn(`Source ${sourceId} not found when trying to update attribution`);
77
- return; // Don't try to update attributions if source doesn't exist
78
+ // Source may not be registered in style caches yet (async attribution fetch race).
79
+ // Silently skip attribution will be set when the source is ready.
80
+ return;
78
81
  }
79
82
  // Call undocumented method to update attribution display
80
83
  if (attributionController._updateAttributions) {
@@ -1284,6 +1287,8 @@ class DynamicMapService {
1284
1287
  return;
1285
1288
  }
1286
1289
  try {
1290
+ // Guard against map whose style has already been destroyed
1291
+ if (!map.style) return;
1287
1292
  const mapWithStyle = map;
1288
1293
  const mapLayerApi = map;
1289
1294
  const mapSourceApi = map;
@@ -1428,6 +1433,8 @@ class TiledMapService {
1428
1433
  return;
1429
1434
  }
1430
1435
  try {
1436
+ // Guard against map whose style has already been destroyed
1437
+ if (!this._map.style) return;
1431
1438
  // First, remove any layers that are using this source
1432
1439
  const mapWithStyle = this._map;
1433
1440
  if (mapWithStyle.getStyle && typeof mapWithStyle.getLayer === 'function') {
@@ -1664,8 +1671,10 @@ class ImageService {
1664
1671
  remove() {
1665
1672
  if (this._map && typeof this._map.removeSource === 'function') {
1666
1673
  try {
1667
- // First, remove any layers that are using this source
1674
+ // Guard against map whose style has already been destroyed
1668
1675
  const mapWithStyle = this._map;
1676
+ if (!mapWithStyle.style) return;
1677
+ // First, remove any layers that are using this source
1669
1678
  if (mapWithStyle.getStyle) {
1670
1679
  const style = mapWithStyle.getStyle();
1671
1680
  const layers = style?.layers || [];
@@ -2015,6 +2024,8 @@ class VectorTileService {
2015
2024
  return;
2016
2025
  }
2017
2026
  try {
2027
+ // Guard against map whose style has already been destroyed
2028
+ if (!map.style) return;
2018
2029
  const mapWithStyle = map;
2019
2030
  const mapLayerApi = map;
2020
2031
  const mapSourceApi = map;
@@ -2282,8 +2293,10 @@ class FeatureService {
2282
2293
  this.disableRequests();
2283
2294
  if (this._map && typeof this._map.removeSource === 'function') {
2284
2295
  try {
2285
- // First, remove any layers that are using this source
2296
+ // Guard against map whose style has already been destroyed
2286
2297
  const mapWithStyle = this._map;
2298
+ if (!mapWithStyle.style) return;
2299
+ // First, remove any layers that are using this source
2287
2300
  if (mapWithStyle.getStyle) {
2288
2301
  const style = mapWithStyle.getStyle();
2289
2302
  const layers = style?.layers || [];
@@ -3831,6 +3844,8 @@ function find(options) {
3831
3844
  * IdentifyFeatures task for performing identify operations against ArcGIS Map Services
3832
3845
  * Similar to Esri Leaflet's identifyFeatures functionality
3833
3846
  */
3847
+ // Scale constant: at zoom 0 and 96 DPI, the Web Mercator scale denominator
3848
+ const SCALE_AT_ZOOM_0 = 559082264.028;
3834
3849
  class IdentifyFeatures extends Task {
3835
3850
  constructor(options) {
3836
3851
  // Handle different input types and convert to TaskOptions format
@@ -3876,6 +3891,18 @@ class IdentifyFeatures extends Task {
3876
3891
  f: 'json'
3877
3892
  }
3878
3893
  });
3894
+ Object.defineProperty(this, "_map", {
3895
+ enumerable: true,
3896
+ configurable: true,
3897
+ writable: true,
3898
+ value: null
3899
+ });
3900
+ Object.defineProperty(this, "_layerScaleRanges", {
3901
+ enumerable: true,
3902
+ configurable: true,
3903
+ writable: true,
3904
+ value: {}
3905
+ });
3879
3906
  this.path = '/identify';
3880
3907
  // Ensure dynamic setters are available even though subclass fields
3881
3908
  // are initialized after super() runs. This rebinds setter methods.
@@ -3929,9 +3956,11 @@ class IdentifyFeatures extends Task {
3929
3956
  return this;
3930
3957
  }
3931
3958
  /**
3932
- * Set the map extent and image display for the identify operation
3959
+ * Set the map extent and image display for the identify operation.
3960
+ * Also stores the map reference for zoom-based scale filtering.
3933
3961
  */
3934
3962
  on(map) {
3963
+ this._map = map;
3935
3964
  try {
3936
3965
  const bounds = map.getBounds().toArray();
3937
3966
  this.params.mapExtent = [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]].join(',');
@@ -3942,6 +3971,22 @@ class IdentifyFeatures extends Task {
3942
3971
  }
3943
3972
  return this;
3944
3973
  }
3974
+ /**
3975
+ * Set scale ranges for layers so the identify can skip layers not visible at the current zoom.
3976
+ * ArcGIS uses minScale (zoomed out limit) and maxScale (zoomed in limit).
3977
+ * A value of 0 means no limit.
3978
+ *
3979
+ * @example
3980
+ * identify.layerScales({
3981
+ * 0: { minScale: 1000000, maxScale: 0 }, // Cities: visible below 1:1M
3982
+ * 1: { minScale: 5000000, maxScale: 500000 }, // Highways: visible between 1:5M and 1:500K
3983
+ * 2: { minScale: 0, maxScale: 0 }, // States: always visible
3984
+ * });
3985
+ */
3986
+ layerScales(scales) {
3987
+ this._layerScaleRanges = scales;
3988
+ return this;
3989
+ }
3945
3990
  /**
3946
3991
  * Set layer definitions for filtering specific layers
3947
3992
  */
@@ -3960,9 +4005,67 @@ class IdentifyFeatures extends Task {
3960
4005
  return this;
3961
4006
  }
3962
4007
  /**
3963
- * Execute the identify operation
4008
+ * Get the current map scale from zoom level.
4009
+ * Uses the standard Web Mercator scale formula at 96 DPI.
4010
+ */
4011
+ _getMapScale() {
4012
+ if (!this._map || typeof this._map.getZoom !== 'function') return null;
4013
+ const zoom = this._map.getZoom();
4014
+ return SCALE_AT_ZOOM_0 / Math.pow(2, zoom);
4015
+ }
4016
+ /**
4017
+ * Check if a layer is visible at the given scale.
4018
+ * ArcGIS convention: minScale = most zoomed out (largest number), maxScale = most zoomed in (smallest number).
4019
+ * A value of 0 means no limit in that direction.
4020
+ */
4021
+ _isLayerVisibleAtScale(layerId, scale) {
4022
+ const range = this._layerScaleRanges[layerId];
4023
+ if (!range) return true; // No scale info means always visible
4024
+ if (range.minScale && scale > range.minScale) return false; // Too zoomed out
4025
+ if (range.maxScale && scale < range.maxScale) return false; // Too zoomed in
4026
+ return true;
4027
+ }
4028
+ /**
4029
+ * Filter the layers parameter to only include layers visible at the current scale.
4030
+ * Returns false if no layers are visible (request should be skipped).
4031
+ */
4032
+ _filterLayersByScale() {
4033
+ const scale = this._getMapScale();
4034
+ if (scale === null || Object.keys(this._layerScaleRanges).length === 0) return true;
4035
+ const layersParam = this.params.layers;
4036
+ if (typeof layersParam !== 'string') return true;
4037
+ // Parse layers string: "all", "visible:0,1,2", "top", "visible", "0,1,2"
4038
+ const match = layersParam.match(/^(all|top|visible)(?::(.+))?$/);
4039
+ if (!match) return true;
4040
+ const prefix = match[1];
4041
+ const idsPart = match[2];
4042
+ // If no specific IDs, filter all known layer IDs from scale ranges
4043
+ let layerIds;
4044
+ if (idsPart) {
4045
+ layerIds = idsPart.split(',').map(Number).filter(n => !isNaN(n));
4046
+ } else {
4047
+ layerIds = Object.keys(this._layerScaleRanges).map(Number);
4048
+ }
4049
+ const visibleIds = layerIds.filter(id => this._isLayerVisibleAtScale(id, scale));
4050
+ if (visibleIds.length === 0) return false;
4051
+ // Update layers param with only visible IDs
4052
+ this.params.layers = `${prefix}:${visibleIds.join(',')}`;
4053
+ return true;
4054
+ }
4055
+ /**
4056
+ * Execute the identify operation.
4057
+ * If layer scale ranges are set via `layerScales()` and a map is provided via `on()`,
4058
+ * layers not visible at the current zoom level will be excluded. If no layers are visible,
4059
+ * an empty FeatureCollection is returned without making a network request.
3964
4060
  */
3965
4061
  async run() {
4062
+ // Skip request entirely if no layers are visible at the current scale
4063
+ if (!this._filterLayersByScale()) {
4064
+ return {
4065
+ type: 'FeatureCollection',
4066
+ features: []
4067
+ };
4068
+ }
3966
4069
  try {
3967
4070
  const response = await this.request();
3968
4071
  return this._convertToGeoJSON(response);
@@ -4171,4 +4274,4 @@ function identifyImage(options) {
4171
4274
  }
4172
4275
 
4173
4276
  export { DynamicMapService as D, FeatureService as F, IdentifyFeatures as I, Query as Q, Service as S, Task as T, VectorBasemapStyle as V, Find as a, IdentifyImage as b, ImageService as c, TiledMapService as d, VectorTileService as e, cleanTrailingSlash as f, find as g, getServiceDetails as h, identifyImage as i, query as q, updateAttribution as u };
4174
- //# sourceMappingURL=IdentifyImage-B1oeOjpm.js.map
4277
+ //# sourceMappingURL=IdentifyImage-DWBF8K_a.js.map