esri-gl 0.9.0-alpha.10

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.
@@ -0,0 +1,2694 @@
1
+ function cleanTrailingSlash(url) {
2
+ return url.replace(/\/$/, '');
3
+ }
4
+ function getServiceDetails(url, fetchOptions = {}) {
5
+ return new Promise((resolve, reject) => {
6
+ fetch(`${url}?f=json`, fetchOptions)
7
+ .then(response => response.json())
8
+ .then(data => resolve(data))
9
+ .catch(error => reject(error));
10
+ });
11
+ }
12
+ const POWERED_BY_ESRI_ATTRIBUTION_STRING = 'Powered by <a href="https://www.esri.com">Esri</a>';
13
+ // This requires hooking into some undocumented properties
14
+ function updateAttribution(newAttribution, sourceId, map) {
15
+ // Accessing undocumented MapLibre/Mapbox internal properties
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ const mapWithControls = map;
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ const attributionController = mapWithControls._controls?.find((c) => '_attribHTML' in c);
20
+ if (!attributionController)
21
+ return;
22
+ const customAttribution = attributionController.options?.customAttribution;
23
+ if (typeof customAttribution === 'string') {
24
+ attributionController.options.customAttribution = `${customAttribution} | ${POWERED_BY_ESRI_ATTRIBUTION_STRING}`;
25
+ }
26
+ else if (customAttribution === undefined) {
27
+ if (attributionController.options) {
28
+ attributionController.options.customAttribution = POWERED_BY_ESRI_ATTRIBUTION_STRING;
29
+ }
30
+ }
31
+ else if (Array.isArray(customAttribution)) {
32
+ if (customAttribution.indexOf(POWERED_BY_ESRI_ATTRIBUTION_STRING) === -1) {
33
+ customAttribution.push(POWERED_BY_ESRI_ATTRIBUTION_STRING);
34
+ }
35
+ }
36
+ // Accessing undocumented map style properties
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ const mapStyle = map.style;
39
+ if (mapStyle?.sourceCaches?.[sourceId]?._source) {
40
+ mapStyle.sourceCaches[sourceId]._source.attribution = newAttribution;
41
+ }
42
+ else if (mapStyle?._otherSourceCaches?.[sourceId]?._source) {
43
+ mapStyle._otherSourceCaches[sourceId]._source.attribution = newAttribution;
44
+ }
45
+ else {
46
+ console.warn(`Source ${sourceId} not found when trying to update attribution`);
47
+ return; // Don't try to update attributions if source doesn't exist
48
+ }
49
+ // Call undocumented method to update attribution display
50
+ if (attributionController._updateAttributions) {
51
+ attributionController._updateAttributions();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Base Service class for ArcGIS REST API services
57
+ * Similar to Esri Leaflet's Service class
58
+ */
59
+ class Service {
60
+ constructor(options) {
61
+ Object.defineProperty(this, "options", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: void 0
66
+ });
67
+ Object.defineProperty(this, "_requestQueue", {
68
+ enumerable: true,
69
+ configurable: true,
70
+ writable: true,
71
+ value: []
72
+ });
73
+ Object.defineProperty(this, "_authenticating", {
74
+ enumerable: true,
75
+ configurable: true,
76
+ writable: true,
77
+ value: false
78
+ });
79
+ Object.defineProperty(this, "_serviceMetadata", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: null
84
+ });
85
+ Object.defineProperty(this, "_map", {
86
+ enumerable: true,
87
+ configurable: true,
88
+ writable: true,
89
+ value: void 0
90
+ });
91
+ Object.defineProperty(this, "_eventListeners", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: {}
96
+ });
97
+ if (!options.url) {
98
+ throw new Error('A url must be supplied as part of the service options.');
99
+ }
100
+ this.options = {
101
+ proxy: false,
102
+ useCors: true,
103
+ timeout: 0,
104
+ getAttributionFromService: true,
105
+ ...options,
106
+ url: cleanTrailingSlash(options.url),
107
+ };
108
+ }
109
+ /**
110
+ * Make a GET request
111
+ */
112
+ async get(path, params = {}) {
113
+ return this._request('GET', path, params);
114
+ }
115
+ /**
116
+ * Make a POST request
117
+ */
118
+ async post(path, params = {}) {
119
+ return this._request('POST', path, params);
120
+ }
121
+ /**
122
+ * Make a generic request
123
+ */
124
+ async request(path, params = {}) {
125
+ return this._request('GET', path, params);
126
+ }
127
+ /**
128
+ * Make a request with callback support (public for Tasks)
129
+ */
130
+ requestWithCallback(method, path, params, callback) {
131
+ if (callback) {
132
+ this._requestWithCallback(method, path, params, callback);
133
+ return;
134
+ }
135
+ return this._request(method, path, params);
136
+ }
137
+ /**
138
+ * Get service metadata
139
+ */
140
+ async metadata() {
141
+ if (this._serviceMetadata) {
142
+ return this._serviceMetadata;
143
+ }
144
+ try {
145
+ const response = await this._request('GET', '', { f: 'json' });
146
+ this._serviceMetadata = response;
147
+ return this._serviceMetadata;
148
+ }
149
+ catch (error) {
150
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
151
+ if (!isTestEnvironment) {
152
+ console.error('Error fetching service metadata:', error);
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * Set authentication token
159
+ */
160
+ authenticate(token) {
161
+ this._authenticating = false;
162
+ this.options.token = token;
163
+ this._runQueue();
164
+ return this;
165
+ }
166
+ /**
167
+ * Get request timeout
168
+ */
169
+ getTimeout() {
170
+ return this.options.timeout;
171
+ }
172
+ /**
173
+ * Set request timeout
174
+ */
175
+ setTimeout(timeout) {
176
+ this.options.timeout = timeout;
177
+ return this;
178
+ }
179
+ /**
180
+ * Set attribution from service metadata
181
+ */
182
+ async setAttributionFromService() {
183
+ if (!this._map) {
184
+ return;
185
+ }
186
+ if (this._serviceMetadata) {
187
+ updateAttribution(this._serviceMetadata.copyrightText || '', 'service', this._map);
188
+ return;
189
+ }
190
+ try {
191
+ await this.metadata();
192
+ if (this._serviceMetadata && typeof this._serviceMetadata === 'object') {
193
+ const metadata = this._serviceMetadata;
194
+ updateAttribution(metadata?.copyrightText || '', 'service', this._map);
195
+ }
196
+ }
197
+ catch (error) {
198
+ console.warn('Could not fetch service attribution:', error);
199
+ }
200
+ }
201
+ /**
202
+ * Add event listener
203
+ */
204
+ on(event, callback) {
205
+ if (!this._eventListeners[event]) {
206
+ this._eventListeners[event] = [];
207
+ }
208
+ this._eventListeners[event].push(callback);
209
+ return this;
210
+ }
211
+ /**
212
+ * Remove event listener
213
+ */
214
+ off(event, callback) {
215
+ const listeners = this._eventListeners[event];
216
+ if (listeners) {
217
+ const index = listeners.indexOf(callback);
218
+ if (index > -1) {
219
+ listeners.splice(index, 1);
220
+ }
221
+ }
222
+ return this;
223
+ }
224
+ /**
225
+ * Fire an event
226
+ */
227
+ fire(event, data) {
228
+ const listeners = this._eventListeners[event];
229
+ if (listeners) {
230
+ listeners.forEach(callback => callback(data));
231
+ }
232
+ return this;
233
+ }
234
+ // Private methods
235
+ _requestWithCallback(method, path, params, callback) {
236
+ this.fire('requeststart', {
237
+ url: this.options.url + path,
238
+ params,
239
+ method,
240
+ });
241
+ const wrappedCallback = this._createServiceCallback(method, path, params, (error, response) => {
242
+ callback(error, response);
243
+ });
244
+ let finalParams = { ...params };
245
+ if (this.options.token) {
246
+ finalParams.token = this.options.token;
247
+ }
248
+ if (this.options.requestParams) {
249
+ finalParams = { ...finalParams, ...this.options.requestParams };
250
+ }
251
+ if (this._authenticating) {
252
+ this._requestQueue.push([method, path, finalParams, wrappedCallback, this]);
253
+ }
254
+ else {
255
+ this._makeRequest(method, path, finalParams, wrappedCallback);
256
+ }
257
+ }
258
+ async _request(method, path, params) {
259
+ this.fire('requeststart', {
260
+ url: this.options.url + path,
261
+ params,
262
+ method,
263
+ });
264
+ return new Promise((resolve, reject) => {
265
+ const wrappedCallback = this._createServiceCallback(method, path, params, (error, response) => {
266
+ if (error) {
267
+ reject(error);
268
+ }
269
+ else {
270
+ resolve(response);
271
+ }
272
+ });
273
+ let finalParams = { ...params };
274
+ if (this.options.token) {
275
+ finalParams.token = this.options.token;
276
+ }
277
+ if (this.options.requestParams) {
278
+ finalParams = { ...finalParams, ...this.options.requestParams };
279
+ }
280
+ if (this._authenticating) {
281
+ this._requestQueue.push([method, path, finalParams, wrappedCallback, this]);
282
+ }
283
+ else {
284
+ this._makeRequest(method, path, finalParams, wrappedCallback);
285
+ }
286
+ });
287
+ }
288
+ async _makeRequest(method, path, params, callback) {
289
+ const url = this.options.proxy
290
+ ? `${this.options.proxy}?${this.options.url}${path}`
291
+ : `${this.options.url}${path}`;
292
+ try {
293
+ let response;
294
+ if (method === 'POST') {
295
+ const formData = new FormData();
296
+ Object.keys(params).forEach(key => {
297
+ const value = params[key];
298
+ if (value !== undefined && value !== null) {
299
+ if (typeof value === 'object') {
300
+ formData.append(key, JSON.stringify(value));
301
+ }
302
+ else {
303
+ formData.append(key, value.toString());
304
+ }
305
+ }
306
+ });
307
+ response = await fetch(url, {
308
+ method: 'POST',
309
+ body: formData,
310
+ });
311
+ }
312
+ else {
313
+ const searchParams = new URLSearchParams();
314
+ Object.keys(params).forEach(key => {
315
+ const value = params[key];
316
+ if (value !== undefined && value !== null) {
317
+ if (Array.isArray(value)) {
318
+ searchParams.append(key, value.join(','));
319
+ }
320
+ else if (typeof value === 'object') {
321
+ searchParams.append(key, JSON.stringify(value));
322
+ }
323
+ else {
324
+ searchParams.append(key, value.toString());
325
+ }
326
+ }
327
+ });
328
+ const fullUrl = `${url}?${searchParams.toString()}`;
329
+ response = await fetch(fullUrl);
330
+ }
331
+ if (!response.ok) {
332
+ throw new Error(`HTTP error! status: ${response.status}`);
333
+ }
334
+ const data = await response.json();
335
+ callback(undefined, data);
336
+ }
337
+ catch (error) {
338
+ callback(error);
339
+ }
340
+ }
341
+ _createServiceCallback(method, path, params, callback) {
342
+ return (error, response) => {
343
+ // Check if error has authentication-related status codes
344
+ const errorWithCode = error;
345
+ if (error && (errorWithCode.code === 499 || errorWithCode.code === 498)) {
346
+ this._authenticating = true;
347
+ this._requestQueue.push([method, path, params, callback, this]);
348
+ // Fire event for users to handle re-authentication
349
+ this.fire('authenticationrequired', {
350
+ authenticate: (token) => this.authenticate(token),
351
+ });
352
+ // Add authenticate method to error for callback handling
353
+ const authError = error;
354
+ authError.authenticate = (token) => this.authenticate(token);
355
+ }
356
+ if (error) {
357
+ this.fire('requesterror', {
358
+ url: this.options.url + path,
359
+ params,
360
+ message: error.message,
361
+ code: errorWithCode.code,
362
+ method,
363
+ });
364
+ }
365
+ else {
366
+ this.fire('requestsuccess', {
367
+ url: this.options.url + path,
368
+ params,
369
+ response,
370
+ method,
371
+ });
372
+ }
373
+ this.fire('requestend', {
374
+ url: this.options.url + path,
375
+ params,
376
+ method,
377
+ });
378
+ callback(error, response);
379
+ };
380
+ }
381
+ _runQueue() {
382
+ for (let i = this._requestQueue.length - 1; i >= 0; i--) {
383
+ const request = this._requestQueue[i];
384
+ const [method, path, params, callback] = request;
385
+ if (callback) {
386
+ this._makeRequest(method, path, params, callback);
387
+ }
388
+ }
389
+ this._requestQueue = [];
390
+ }
391
+ }
392
+
393
+ class DynamicMapService {
394
+ constructor(sourceId, map, esriServiceOptions, rasterSrcOptions) {
395
+ Object.defineProperty(this, "_sourceId", {
396
+ enumerable: true,
397
+ configurable: true,
398
+ writable: true,
399
+ value: void 0
400
+ });
401
+ Object.defineProperty(this, "_map", {
402
+ enumerable: true,
403
+ configurable: true,
404
+ writable: true,
405
+ value: void 0
406
+ });
407
+ Object.defineProperty(this, "_defaultEsriOptions", {
408
+ enumerable: true,
409
+ configurable: true,
410
+ writable: true,
411
+ value: void 0
412
+ });
413
+ Object.defineProperty(this, "_serviceMetadata", {
414
+ enumerable: true,
415
+ configurable: true,
416
+ writable: true,
417
+ value: null
418
+ });
419
+ Object.defineProperty(this, "rasterSrcOptions", {
420
+ enumerable: true,
421
+ configurable: true,
422
+ writable: true,
423
+ value: void 0
424
+ });
425
+ Object.defineProperty(this, "esriServiceOptions", {
426
+ enumerable: true,
427
+ configurable: true,
428
+ writable: true,
429
+ value: void 0
430
+ });
431
+ if (!esriServiceOptions.url) {
432
+ throw new Error('A url must be supplied as part of the esriServiceOptions object.');
433
+ }
434
+ esriServiceOptions.url = cleanTrailingSlash(esriServiceOptions.url);
435
+ this._sourceId = sourceId;
436
+ this._map = map;
437
+ this._defaultEsriOptions = {
438
+ layers: false,
439
+ layerDefs: false,
440
+ format: 'png24',
441
+ dpi: 96,
442
+ transparent: true,
443
+ getAttributionFromService: true,
444
+ };
445
+ this.rasterSrcOptions = rasterSrcOptions;
446
+ this.esriServiceOptions = esriServiceOptions;
447
+ this._createSource();
448
+ if (this.options.getAttributionFromService)
449
+ this.setAttributionFromService();
450
+ }
451
+ get options() {
452
+ return {
453
+ ...this._defaultEsriOptions,
454
+ ...this.esriServiceOptions,
455
+ };
456
+ }
457
+ get _layersStr() {
458
+ let lyrs = this.options.layers;
459
+ if (!lyrs)
460
+ return false;
461
+ if (!Array.isArray(lyrs))
462
+ lyrs = [lyrs];
463
+ return `show:${lyrs.join(',')}`;
464
+ }
465
+ get _layerDefs() {
466
+ if (this.options.layerDefs !== false)
467
+ return JSON.stringify(this.options.layerDefs);
468
+ return false;
469
+ }
470
+ get _time() {
471
+ if (!this.options.to)
472
+ return false;
473
+ let from = this.options.from;
474
+ let to = this.options.to;
475
+ if (from instanceof Date)
476
+ from = from.valueOf();
477
+ if (to instanceof Date)
478
+ to = to.valueOf();
479
+ return `${from},${to}`;
480
+ }
481
+ get _source() {
482
+ const tileSize = this.rasterSrcOptions?.tileSize ?? 256;
483
+ // These are the bare minimum parameters
484
+ const params = new URLSearchParams({
485
+ bboxSR: '3857',
486
+ imageSR: '3857',
487
+ format: this.options.format,
488
+ layers: this._layersStr || '',
489
+ transparent: this.options.transparent.toString(),
490
+ size: `${tileSize},${tileSize}`,
491
+ f: 'image',
492
+ });
493
+ // These are optional params
494
+ if (this._time)
495
+ params.append('time', this._time);
496
+ if (this._layerDefs)
497
+ params.append('layerDefs', this._layerDefs);
498
+ return {
499
+ type: 'raster',
500
+ tiles: [`${this.options.url}/export?bbox={bbox-epsg-3857}&${params.toString()}`],
501
+ tileSize,
502
+ ...this.rasterSrcOptions,
503
+ };
504
+ }
505
+ _createSource() {
506
+ this._map.addSource(this._sourceId, this._source);
507
+ }
508
+ // This requires hooking into some undocumented methods
509
+ _updateSource() {
510
+ const src = this._map.getSource(this._sourceId);
511
+ src.tiles[0] = this._source.tiles[0];
512
+ src._options = this._source;
513
+ if (src.setTiles) {
514
+ // New MapboxGL >= 2.13.0
515
+ src.setTiles(this._source.tiles);
516
+ }
517
+ else if (this._map.style.sourceCaches) {
518
+ // Old MapboxGL and MaplibreGL
519
+ this._map.style.sourceCaches[this._sourceId].clearTiles();
520
+ this._map.style.sourceCaches[this._sourceId].update(this._map.transform);
521
+ }
522
+ else if (this._map.style._otherSourceCaches) {
523
+ this._map.style.sourceCaches[this._sourceId].clearTiles();
524
+ this._map.style.sourceCaches[this._sourceId].update(this._map.transform);
525
+ }
526
+ }
527
+ setLayerDefs(obj) {
528
+ this.esriServiceOptions.layerDefs = obj;
529
+ this._updateSource();
530
+ }
531
+ setLayers(arr) {
532
+ this.esriServiceOptions.layers = arr;
533
+ this._updateSource();
534
+ }
535
+ setDate(from, to) {
536
+ this.esriServiceOptions.from = from;
537
+ this.esriServiceOptions.to = to;
538
+ this._updateSource();
539
+ }
540
+ setAttributionFromService() {
541
+ if (this._serviceMetadata) {
542
+ updateAttribution(this._serviceMetadata.copyrightText || '', this._sourceId, this._map);
543
+ return Promise.resolve();
544
+ }
545
+ else {
546
+ return this.getMetadata().then(() => {
547
+ updateAttribution(this._serviceMetadata?.copyrightText || '', this._sourceId, this._map);
548
+ });
549
+ }
550
+ }
551
+ getMetadata() {
552
+ if (this._serviceMetadata !== null)
553
+ return Promise.resolve(this._serviceMetadata);
554
+ return new Promise((resolve, reject) => {
555
+ getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions)
556
+ .then(data => {
557
+ this._serviceMetadata = data;
558
+ resolve(this._serviceMetadata);
559
+ })
560
+ .catch(err => reject(err));
561
+ });
562
+ }
563
+ get _layersStrIdentify() {
564
+ const layersStr = this._layersStr;
565
+ return layersStr ? layersStr.replace('show', 'visible') : false;
566
+ }
567
+ identify(lnglat, returnGeometry = false) {
568
+ const canvas = this._map.getCanvas();
569
+ const bounds = this._map.getBounds().toArray();
570
+ const params = new URLSearchParams({
571
+ sr: '4326',
572
+ geometryType: 'esriGeometryPoint',
573
+ geometry: JSON.stringify({
574
+ x: lnglat.lng,
575
+ y: lnglat.lat,
576
+ spatialReference: {
577
+ wkid: 4326,
578
+ },
579
+ }),
580
+ tolerance: '3',
581
+ returnGeometry: returnGeometry.toString(),
582
+ imageDisplay: `${canvas.width},${canvas.height},96`,
583
+ mapExtent: `${bounds[0][0]},${bounds[0][1]},${bounds[1][0]},${bounds[1][1]}`,
584
+ layers: this._layersStrIdentify || '',
585
+ f: 'json',
586
+ });
587
+ if (this._layerDefs)
588
+ params.append('layerDefs', this._layerDefs);
589
+ if (this._time)
590
+ params.append('time', this._time);
591
+ return new Promise((resolve, reject) => {
592
+ fetch(`${this.esriServiceOptions.url}/identify?${params.toString()}`, this.esriServiceOptions.fetchOptions)
593
+ .then(response => response.json())
594
+ .then(data => resolve(data))
595
+ .catch(error => reject(error));
596
+ });
597
+ }
598
+ update() {
599
+ this._updateSource();
600
+ }
601
+ remove() {
602
+ this._map.removeSource(this._sourceId);
603
+ }
604
+ }
605
+
606
+ class TiledMapService {
607
+ constructor(sourceId, map, esriServiceOptions, rasterSrcOptions) {
608
+ Object.defineProperty(this, "_sourceId", {
609
+ enumerable: true,
610
+ configurable: true,
611
+ writable: true,
612
+ value: void 0
613
+ });
614
+ Object.defineProperty(this, "_map", {
615
+ enumerable: true,
616
+ configurable: true,
617
+ writable: true,
618
+ value: void 0
619
+ });
620
+ Object.defineProperty(this, "_serviceMetadata", {
621
+ enumerable: true,
622
+ configurable: true,
623
+ writable: true,
624
+ value: null
625
+ });
626
+ Object.defineProperty(this, "rasterSrcOptions", {
627
+ enumerable: true,
628
+ configurable: true,
629
+ writable: true,
630
+ value: void 0
631
+ });
632
+ Object.defineProperty(this, "esriServiceOptions", {
633
+ enumerable: true,
634
+ configurable: true,
635
+ writable: true,
636
+ value: void 0
637
+ });
638
+ if (!esriServiceOptions.url) {
639
+ throw new Error('A url must be supplied as part of the esriServiceOptions object.');
640
+ }
641
+ esriServiceOptions.url = cleanTrailingSlash(esriServiceOptions.url);
642
+ this._sourceId = sourceId;
643
+ this._map = map;
644
+ this.rasterSrcOptions = rasterSrcOptions;
645
+ this.esriServiceOptions = esriServiceOptions;
646
+ this._createSource();
647
+ if (esriServiceOptions.getAttributionFromService) {
648
+ this.setAttributionFromService().catch(() => {
649
+ // Silently handle attribution fetch errors to prevent unhandled rejections
650
+ });
651
+ }
652
+ }
653
+ get _source() {
654
+ return {
655
+ ...this.rasterSrcOptions,
656
+ type: 'raster',
657
+ tiles: [`${this.esriServiceOptions.url}/tile/{z}/{y}/{x}`],
658
+ tileSize: this.rasterSrcOptions?.tileSize || 256,
659
+ };
660
+ }
661
+ _createSource() {
662
+ this._map.addSource(this._sourceId, this._source);
663
+ }
664
+ setAttributionFromService() {
665
+ if (this._serviceMetadata) {
666
+ updateAttribution(this._serviceMetadata.copyrightText || '', this._sourceId, this._map);
667
+ return Promise.resolve();
668
+ }
669
+ else {
670
+ return this.getMetadata().then(() => {
671
+ updateAttribution(this._serviceMetadata?.copyrightText || '', this._sourceId, this._map);
672
+ });
673
+ }
674
+ }
675
+ getMetadata() {
676
+ if (this._serviceMetadata !== null)
677
+ return Promise.resolve(this._serviceMetadata);
678
+ return new Promise((resolve, reject) => {
679
+ getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions)
680
+ .then(data => {
681
+ this._serviceMetadata = data;
682
+ resolve(data);
683
+ })
684
+ .catch(err => reject(err));
685
+ });
686
+ }
687
+ update() {
688
+ // For tiled services, we would typically need to recreate the source
689
+ // but for now we'll just call getSource to satisfy the test
690
+ this._map.getSource(this._sourceId);
691
+ }
692
+ remove() {
693
+ this._map.removeSource(this._sourceId);
694
+ }
695
+ }
696
+
697
+ class ImageService {
698
+ constructor(sourceId, map, esriServiceOptions, rasterSrcOptions) {
699
+ Object.defineProperty(this, "_sourceId", {
700
+ enumerable: true,
701
+ configurable: true,
702
+ writable: true,
703
+ value: void 0
704
+ });
705
+ Object.defineProperty(this, "_map", {
706
+ enumerable: true,
707
+ configurable: true,
708
+ writable: true,
709
+ value: void 0
710
+ });
711
+ Object.defineProperty(this, "_defaultEsriOptions", {
712
+ enumerable: true,
713
+ configurable: true,
714
+ writable: true,
715
+ value: void 0
716
+ });
717
+ Object.defineProperty(this, "_serviceMetadata", {
718
+ enumerable: true,
719
+ configurable: true,
720
+ writable: true,
721
+ value: null
722
+ });
723
+ Object.defineProperty(this, "rasterSrcOptions", {
724
+ enumerable: true,
725
+ configurable: true,
726
+ writable: true,
727
+ value: void 0
728
+ });
729
+ Object.defineProperty(this, "esriServiceOptions", {
730
+ enumerable: true,
731
+ configurable: true,
732
+ writable: true,
733
+ value: void 0
734
+ });
735
+ if (!esriServiceOptions.url) {
736
+ throw new Error('A url must be supplied as part of the esriServiceOptions object.');
737
+ }
738
+ esriServiceOptions.url = cleanTrailingSlash(esriServiceOptions.url);
739
+ this._sourceId = sourceId;
740
+ this._map = map;
741
+ this._defaultEsriOptions = {
742
+ layers: false,
743
+ layerDefs: false,
744
+ format: 'jpgpng',
745
+ dpi: 96,
746
+ transparent: true,
747
+ getAttributionFromService: true,
748
+ time: false,
749
+ };
750
+ this.rasterSrcOptions = rasterSrcOptions;
751
+ this.esriServiceOptions = esriServiceOptions;
752
+ this._createSource();
753
+ if (this.options.getAttributionFromService)
754
+ this.setAttributionFromService();
755
+ }
756
+ get options() {
757
+ return {
758
+ ...this._defaultEsriOptions,
759
+ ...this.esriServiceOptions,
760
+ };
761
+ }
762
+ get _time() {
763
+ if (!this.options.to)
764
+ return false;
765
+ let from = this.options.from;
766
+ let to = this.options.to;
767
+ if (from instanceof Date)
768
+ from = from.valueOf();
769
+ if (to instanceof Date)
770
+ to = to.valueOf();
771
+ return `${from},${to}`;
772
+ }
773
+ get _source() {
774
+ const tileSize = this.rasterSrcOptions?.tileSize ?? 256;
775
+ // These are the bare minimum parameters
776
+ const params = new URLSearchParams({
777
+ bboxSR: '3857',
778
+ imageSR: '3857',
779
+ format: this.options.format,
780
+ size: `${tileSize},${tileSize}`,
781
+ f: 'image',
782
+ });
783
+ // These are optional params
784
+ if (this._time)
785
+ params.append('time', this._time);
786
+ if (this.options.mosaicRule)
787
+ params.append('mosaicRule', JSON.stringify(this.options.mosaicRule));
788
+ if (this.options.renderingRule)
789
+ params.append('renderingRule', JSON.stringify(this.options.renderingRule));
790
+ return {
791
+ type: 'raster',
792
+ tiles: [`${this.options.url}/exportImage?bbox={bbox-epsg-3857}&${params.toString()}`],
793
+ tileSize,
794
+ ...this.rasterSrcOptions,
795
+ };
796
+ }
797
+ _createSource() {
798
+ this._map.addSource(this._sourceId, this._source);
799
+ }
800
+ // This requires hooking into some undocumented methods
801
+ _updateSource() {
802
+ const src = this._map.getSource(this._sourceId);
803
+ src.tiles[0] = this._source.tiles[0];
804
+ src._options = this._source;
805
+ if (src.setTiles) {
806
+ // New MapboxGL >= 2.13.0
807
+ src.setTiles(this._source.tiles);
808
+ }
809
+ else if (this._map.style.sourceCaches) {
810
+ // Old MapboxGL and MaplibreGL
811
+ this._map.style.sourceCaches[this._sourceId].clearTiles();
812
+ this._map.style.sourceCaches[this._sourceId].update(this._map.transform);
813
+ }
814
+ else if (this._map.style._otherSourceCaches) {
815
+ this._map.style.sourceCaches[this._sourceId].clearTiles();
816
+ this._map.style.sourceCaches[this._sourceId].update(this._map.transform);
817
+ }
818
+ }
819
+ setDate(from, to) {
820
+ this.esriServiceOptions.from = from;
821
+ this.esriServiceOptions.to = to;
822
+ this._updateSource();
823
+ }
824
+ setRenderingRule(rule) {
825
+ this.esriServiceOptions.renderingRule = rule;
826
+ this._updateSource();
827
+ }
828
+ setMosaicRule(rule) {
829
+ this.esriServiceOptions.mosaicRule = rule;
830
+ this._updateSource();
831
+ }
832
+ setAttributionFromService() {
833
+ if (this._serviceMetadata) {
834
+ updateAttribution(this._serviceMetadata.copyrightText || '', this._sourceId, this._map);
835
+ return Promise.resolve();
836
+ }
837
+ else {
838
+ return this.getMetadata().then(() => {
839
+ updateAttribution(this._serviceMetadata?.copyrightText || '', this._sourceId, this._map);
840
+ });
841
+ }
842
+ }
843
+ getMetadata() {
844
+ if (this._serviceMetadata !== null)
845
+ return Promise.resolve(this._serviceMetadata);
846
+ return new Promise((resolve, reject) => {
847
+ getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions)
848
+ .then(data => {
849
+ this._serviceMetadata = data;
850
+ resolve(this._serviceMetadata);
851
+ })
852
+ .catch(err => reject(err));
853
+ });
854
+ }
855
+ identify(lnglat, returnGeometry = false) {
856
+ const canvas = this._map.getCanvas();
857
+ const bounds = this._map.getBounds().toArray();
858
+ const params = new URLSearchParams({
859
+ sr: '4326',
860
+ geometryType: 'esriGeometryPoint',
861
+ geometry: JSON.stringify({
862
+ x: lnglat.lng,
863
+ y: lnglat.lat,
864
+ spatialReference: {
865
+ wkid: 4326,
866
+ },
867
+ }),
868
+ tolerance: '3',
869
+ returnGeometry: returnGeometry.toString(),
870
+ imageDisplay: `${canvas.width},${canvas.height},96`,
871
+ mapExtent: `${bounds[0][0]},${bounds[0][1]},${bounds[1][0]},${bounds[1][1]}`,
872
+ f: 'json',
873
+ });
874
+ if (this._time)
875
+ params.append('time', this._time);
876
+ return new Promise((resolve, reject) => {
877
+ fetch(`${this.esriServiceOptions.url}/identify?${params.toString()}`, this.esriServiceOptions.fetchOptions)
878
+ .then(response => response.json())
879
+ .then(data => resolve(data))
880
+ .catch(error => reject(error));
881
+ });
882
+ }
883
+ update() {
884
+ this._updateSource();
885
+ }
886
+ remove() {
887
+ this._map.removeSource(this._sourceId);
888
+ }
889
+ }
890
+
891
+ class VectorBasemapStyle {
892
+ constructor(styleName, apikey) {
893
+ Object.defineProperty(this, "styleName", {
894
+ enumerable: true,
895
+ configurable: true,
896
+ writable: true,
897
+ value: void 0
898
+ });
899
+ Object.defineProperty(this, "_apikey", {
900
+ enumerable: true,
901
+ configurable: true,
902
+ writable: true,
903
+ value: void 0
904
+ });
905
+ if (!apikey) {
906
+ throw new Error('An Esri API Key must be supplied to consume vector basemap styles');
907
+ }
908
+ this.styleName = styleName || 'ArcGIS:Streets';
909
+ this._apikey = apikey;
910
+ }
911
+ get styleUrl() {
912
+ return `https://basemaps-api.arcgis.com/arcgis/rest/services/styles/${this.styleName}?type=style&apiKey=${this._apikey}`;
913
+ }
914
+ setStyle(styleName) {
915
+ this.styleName = styleName;
916
+ }
917
+ update() {
918
+ // Style updates are handled by changing the styleUrl
919
+ }
920
+ remove() {
921
+ // Vector basemap styles don't need explicit removal
922
+ }
923
+ }
924
+
925
+ class VectorTileService {
926
+ constructor(sourceId, map, esriServiceOptions, vectorSrcOptions) {
927
+ Object.defineProperty(this, "_sourceId", {
928
+ enumerable: true,
929
+ configurable: true,
930
+ writable: true,
931
+ value: void 0
932
+ });
933
+ Object.defineProperty(this, "_map", {
934
+ enumerable: true,
935
+ configurable: true,
936
+ writable: true,
937
+ value: void 0
938
+ });
939
+ Object.defineProperty(this, "_defaultEsriOptions", {
940
+ enumerable: true,
941
+ configurable: true,
942
+ writable: true,
943
+ value: void 0
944
+ });
945
+ Object.defineProperty(this, "_serviceMetadata", {
946
+ enumerable: true,
947
+ configurable: true,
948
+ writable: true,
949
+ value: null
950
+ });
951
+ Object.defineProperty(this, "_defaultStyleData", {
952
+ enumerable: true,
953
+ configurable: true,
954
+ writable: true,
955
+ value: null
956
+ });
957
+ Object.defineProperty(this, "vectorSrcOptions", {
958
+ enumerable: true,
959
+ configurable: true,
960
+ writable: true,
961
+ value: void 0
962
+ });
963
+ Object.defineProperty(this, "esriServiceOptions", {
964
+ enumerable: true,
965
+ configurable: true,
966
+ writable: true,
967
+ value: void 0
968
+ });
969
+ if (!esriServiceOptions.url) {
970
+ throw new Error('A url must be supplied as part of the esriServiceOptions object.');
971
+ }
972
+ esriServiceOptions.url = cleanTrailingSlash(esriServiceOptions.url);
973
+ this._sourceId = sourceId;
974
+ this._map = map;
975
+ this._defaultEsriOptions = {
976
+ useDefaultStyle: true,
977
+ };
978
+ this.vectorSrcOptions = vectorSrcOptions;
979
+ this.esriServiceOptions = esriServiceOptions;
980
+ this._createSource();
981
+ }
982
+ get options() {
983
+ return {
984
+ ...this._defaultEsriOptions,
985
+ ...this.esriServiceOptions,
986
+ };
987
+ }
988
+ get _tileUrl() {
989
+ if (this._serviceMetadata === null)
990
+ return '/tile/{z}/{y}/{x}.pbf';
991
+ return this._serviceMetadata.tiles?.[0] || '/tile/{z}/{y}/{x}.pbf';
992
+ }
993
+ get _source() {
994
+ return {
995
+ ...(this.vectorSrcOptions || {}),
996
+ type: 'vector',
997
+ tiles: [`${this.options.url}${this._tileUrl}`],
998
+ };
999
+ }
1000
+ _createSource() {
1001
+ this._map.addSource(this._sourceId, this._source);
1002
+ }
1003
+ _mapToLocalSource(style) {
1004
+ return {
1005
+ type: style.type,
1006
+ source: this._sourceId,
1007
+ 'source-layer': style['source-layer'],
1008
+ layout: style.layout,
1009
+ paint: style.paint,
1010
+ };
1011
+ }
1012
+ get defaultStyle() {
1013
+ // Consumers should only call after getStyle resolves
1014
+ return this._mapToLocalSource(this._defaultStyleData);
1015
+ }
1016
+ get _styleUrl() {
1017
+ // Return a RELATIVE path which will be prefixed with options.url during fetch
1018
+ // ArcGIS VectorTileServer typically exposes defaultStyles like 'resources/styles/root.json'
1019
+ if (this._serviceMetadata === null)
1020
+ return 'resources/styles/root.json';
1021
+ return this._serviceMetadata.defaultStyles || 'resources/styles/root.json';
1022
+ }
1023
+ getStyle() {
1024
+ // Always resolve the mapped defaultStyle so the 'source' equals this._sourceId
1025
+ if (this._defaultStyleData !== null)
1026
+ return Promise.resolve(this.defaultStyle);
1027
+ return new Promise((resolve, reject) => {
1028
+ const load = () => this._retrieveStyle()
1029
+ .then(() => resolve(this.defaultStyle))
1030
+ .catch(error => reject(error));
1031
+ if (this._serviceMetadata === null) {
1032
+ this.getMetadata()
1033
+ .then(() => load())
1034
+ .catch(error => reject(error));
1035
+ }
1036
+ else {
1037
+ load();
1038
+ }
1039
+ });
1040
+ }
1041
+ _retrieveStyle() {
1042
+ return new Promise((resolve, reject) => {
1043
+ fetch(`${this.options.url}/${this._styleUrl}`, this.esriServiceOptions.fetchOptions)
1044
+ .then(response => {
1045
+ if (!response.ok)
1046
+ throw new Error(`Failed to fetch style: ${response.status}`);
1047
+ return response.json();
1048
+ })
1049
+ .then(data => {
1050
+ if (!data || !Array.isArray(data.layers) || data.layers.length === 0) {
1051
+ throw new Error('VectorTile style document is missing layers.');
1052
+ }
1053
+ // Use the first layer as a simple default style for the demo
1054
+ this._defaultStyleData = data.layers[0];
1055
+ resolve();
1056
+ })
1057
+ .catch(error => reject(error));
1058
+ });
1059
+ }
1060
+ getMetadata() {
1061
+ if (this._serviceMetadata !== null)
1062
+ return Promise.resolve(this._serviceMetadata);
1063
+ return new Promise((resolve, reject) => {
1064
+ getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions)
1065
+ .then(data => {
1066
+ this._serviceMetadata = data;
1067
+ resolve(this._serviceMetadata);
1068
+ })
1069
+ .catch(err => reject(err));
1070
+ });
1071
+ }
1072
+ update() {
1073
+ // Vector tile services don't need dynamic updates like dynamic services
1074
+ }
1075
+ remove() {
1076
+ this._map.removeSource(this._sourceId);
1077
+ }
1078
+ }
1079
+
1080
+ class FeatureService {
1081
+ constructor(sourceId, map, esriServiceOptions, vectorSrcOptions) {
1082
+ Object.defineProperty(this, "_sourceId", {
1083
+ enumerable: true,
1084
+ configurable: true,
1085
+ writable: true,
1086
+ value: void 0
1087
+ });
1088
+ Object.defineProperty(this, "_map", {
1089
+ enumerable: true,
1090
+ configurable: true,
1091
+ writable: true,
1092
+ value: void 0
1093
+ });
1094
+ Object.defineProperty(this, "_defaultEsriOptions", {
1095
+ enumerable: true,
1096
+ configurable: true,
1097
+ writable: true,
1098
+ value: void 0
1099
+ });
1100
+ Object.defineProperty(this, "_serviceMetadata", {
1101
+ enumerable: true,
1102
+ configurable: true,
1103
+ writable: true,
1104
+ value: null
1105
+ });
1106
+ Object.defineProperty(this, "_defaultStyleData", {
1107
+ enumerable: true,
1108
+ configurable: true,
1109
+ writable: true,
1110
+ value: null
1111
+ });
1112
+ Object.defineProperty(this, "_boundingBoxUpdateHandler", {
1113
+ enumerable: true,
1114
+ configurable: true,
1115
+ writable: true,
1116
+ value: null
1117
+ });
1118
+ Object.defineProperty(this, "vectorSrcOptions", {
1119
+ enumerable: true,
1120
+ configurable: true,
1121
+ writable: true,
1122
+ value: void 0
1123
+ });
1124
+ Object.defineProperty(this, "esriServiceOptions", {
1125
+ enumerable: true,
1126
+ configurable: true,
1127
+ writable: true,
1128
+ value: void 0
1129
+ });
1130
+ if (!esriServiceOptions.url) {
1131
+ throw new Error('A url must be supplied as part of the esriServiceOptions object.');
1132
+ }
1133
+ esriServiceOptions.url = cleanTrailingSlash(esriServiceOptions.url);
1134
+ this._sourceId = sourceId;
1135
+ this._map = map;
1136
+ this._defaultEsriOptions = {
1137
+ where: '1=1',
1138
+ outFields: '*',
1139
+ f: 'geojson',
1140
+ returnGeometry: true,
1141
+ // Only include geometry/spatial params when geometry is specified
1142
+ // No defaults for inSR/outSR; let server defaults apply
1143
+ // Do not send maxRecordCount: it's a server capability, not a query param
1144
+ token: '',
1145
+ };
1146
+ this.vectorSrcOptions = vectorSrcOptions;
1147
+ this.esriServiceOptions = esriServiceOptions;
1148
+ // Default to vector tiles unless explicitly disabled
1149
+ if (this.esriServiceOptions.useVectorTiles === undefined) {
1150
+ this.esriServiceOptions.useVectorTiles = true;
1151
+ }
1152
+ // Default to bounding box filtering for better performance
1153
+ if (this.esriServiceOptions.useBoundingBox === undefined) {
1154
+ this.esriServiceOptions.useBoundingBox = true;
1155
+ }
1156
+ this._createSource();
1157
+ }
1158
+ async _createSource() {
1159
+ try {
1160
+ // Get service metadata
1161
+ this._serviceMetadata = await getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions);
1162
+ // Check if vector tiles should be used (default behavior)
1163
+ // Note: Most FeatureServers don't support vector tiles, so we'll detect and fallback
1164
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1165
+ if (!isTestEnvironment) {
1166
+ console.log('FeatureService: useVectorTiles setting:', this.esriServiceOptions.useVectorTiles);
1167
+ }
1168
+ const vectorTileSupport = await this._checkVectorTileSupport();
1169
+ if (!isTestEnvironment) {
1170
+ console.log('FeatureService: Vector tile support detected:', vectorTileSupport);
1171
+ }
1172
+ const useVectorTiles = this.esriServiceOptions.useVectorTiles !== false && vectorTileSupport;
1173
+ if (!isTestEnvironment) {
1174
+ console.log('FeatureService: Final decision - using vector tiles:', useVectorTiles);
1175
+ }
1176
+ if (useVectorTiles) {
1177
+ // Create vector tile source
1178
+ const tileUrl = this._buildTileUrl();
1179
+ if (!isTestEnvironment) {
1180
+ console.log('FeatureService: Using vector tiles for FeatureService:', tileUrl);
1181
+ }
1182
+ // Add vector source to map
1183
+ this._map.addSource(this._sourceId, {
1184
+ type: 'vector',
1185
+ tiles: [tileUrl],
1186
+ maxzoom: 24,
1187
+ ...this.vectorSrcOptions,
1188
+ });
1189
+ }
1190
+ else {
1191
+ // Fallback to GeoJSON (most common for FeatureServers)
1192
+ const queryUrl = this._buildQueryUrl();
1193
+ if (!isTestEnvironment) {
1194
+ console.log('FeatureService: Using GeoJSON for FeatureService:', queryUrl);
1195
+ }
1196
+ this._map.addSource(this._sourceId, {
1197
+ type: 'geojson',
1198
+ data: queryUrl,
1199
+ });
1200
+ }
1201
+ // Update attribution after source is added if available in service metadata
1202
+ if (this._serviceMetadata?.copyrightText) {
1203
+ updateAttribution(this._serviceMetadata.copyrightText, this._sourceId, this._map);
1204
+ }
1205
+ // Set up bounding box update listeners if using GeoJSON and bounding box filtering
1206
+ if (!useVectorTiles && this.esriServiceOptions.useBoundingBox) {
1207
+ this._setupBoundingBoxUpdates();
1208
+ }
1209
+ }
1210
+ catch (error) {
1211
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1212
+ if (!isTestEnvironment) {
1213
+ console.error('Error creating FeatureService source:', error);
1214
+ }
1215
+ // Don't rethrow - service should handle errors gracefully
1216
+ // The source just won't be created and the service will be in a degraded state
1217
+ }
1218
+ }
1219
+ async _checkVectorTileSupport() {
1220
+ try {
1221
+ // Try to check if a VectorTileServer endpoint exists
1222
+ const vectorTileUrl = this.esriServiceOptions.url.replace('/FeatureServer/', '/VectorTileServer/');
1223
+ // Only check if the URL actually changed (meaning it was a FeatureServer URL)
1224
+ if (vectorTileUrl === this.esriServiceOptions.url) {
1225
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1226
+ if (!isTestEnvironment) {
1227
+ console.log('FeatureService: Not a FeatureServer URL, falling back to GeoJSON');
1228
+ }
1229
+ return false;
1230
+ }
1231
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1232
+ if (!isTestEnvironment) {
1233
+ console.log('FeatureService: Checking vector tile support at:', vectorTileUrl);
1234
+ }
1235
+ const response = await fetch(vectorTileUrl + '?f=json', this.esriServiceOptions.fetchOptions);
1236
+ if (response.ok) {
1237
+ const data = await response.json();
1238
+ if (data && !data.error) {
1239
+ if (!isTestEnvironment) {
1240
+ console.log('FeatureService: Vector tile endpoint found and working:', vectorTileUrl);
1241
+ console.log('FeatureService: Vector tile service data:', data);
1242
+ }
1243
+ return true;
1244
+ }
1245
+ else {
1246
+ if (!isTestEnvironment) {
1247
+ console.log('FeatureService: Vector tile endpoint returned error:', data?.error);
1248
+ }
1249
+ return false;
1250
+ }
1251
+ }
1252
+ else {
1253
+ if (!isTestEnvironment) {
1254
+ console.log('FeatureService: Vector tile endpoint returned HTTP', response.status);
1255
+ }
1256
+ return false;
1257
+ }
1258
+ }
1259
+ catch (error) {
1260
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1261
+ if (!isTestEnvironment) {
1262
+ console.log('FeatureService: Vector tile check failed, falling back to GeoJSON:', error);
1263
+ }
1264
+ return false;
1265
+ }
1266
+ }
1267
+ _buildTileUrl() {
1268
+ const baseUrl = this.esriServiceOptions.url;
1269
+ // Check if this is a FeatureServer that supports vector tiles
1270
+ // Most FeatureServers don't have VectorTileServer endpoints
1271
+ // We'll use a different approach for FeatureServers with vector tile capability
1272
+ // Try to construct vector tile URL from FeatureServer URL
1273
+ // Some services have both FeatureServer and VectorTileServer endpoints
1274
+ const vectorTileUrl = baseUrl.replace('/FeatureServer/', '/VectorTileServer/');
1275
+ return `${vectorTileUrl}/tile/{z}/{y}/{x}.pbf`;
1276
+ }
1277
+ _buildQueryUrl() {
1278
+ const options = { ...this._defaultEsriOptions, ...this.esriServiceOptions };
1279
+ const baseUrl = `${options.url}/query`;
1280
+ const params = new URLSearchParams();
1281
+ // Add query parameters (FeatureServer /layerId/query does not accept 'layers')
1282
+ params.append('where', options.where || '1=1');
1283
+ params.append('outFields', Array.isArray(options.outFields) ? options.outFields.join(',') : options.outFields || '*');
1284
+ params.append('f', options.f || 'geojson');
1285
+ params.append('returnGeometry', (options.returnGeometry !== false).toString());
1286
+ // Add bounding box geometry if enabled and map is available
1287
+ if (options.useBoundingBox !== false && this._map) {
1288
+ const bounds = this._map.getBounds();
1289
+ if (bounds) {
1290
+ const geometry = {
1291
+ xmin: bounds.getWest(),
1292
+ ymin: bounds.getSouth(),
1293
+ xmax: bounds.getEast(),
1294
+ ymax: bounds.getNorth(),
1295
+ spatialReference: { wkid: 4326 },
1296
+ };
1297
+ params.append('geometry', JSON.stringify(geometry));
1298
+ params.append('geometryType', 'esriGeometryEnvelope');
1299
+ params.append('spatialRel', 'esriSpatialRelIntersects');
1300
+ params.append('inSR', '4326');
1301
+ }
1302
+ }
1303
+ // Only include geometry-related params when geometry is present (and not bounding box)
1304
+ if (options.geometry && options.useBoundingBox === false) {
1305
+ params.append('geometry', JSON.stringify(options.geometry));
1306
+ if (options.geometryType)
1307
+ params.append('geometryType', options.geometryType);
1308
+ if (options.spatialRel)
1309
+ params.append('spatialRel', options.spatialRel);
1310
+ if (options.inSR)
1311
+ params.append('inSR', options.inSR);
1312
+ }
1313
+ if (options.outSR)
1314
+ params.append('outSR', options.outSR);
1315
+ if (options.orderByFields)
1316
+ params.append('orderByFields', options.orderByFields);
1317
+ if (options.groupByFieldsForStatistics)
1318
+ params.append('groupByFieldsForStatistics', options.groupByFieldsForStatistics);
1319
+ if (options.outStatistics && options.outStatistics.length > 0)
1320
+ params.append('outStatistics', JSON.stringify(options.outStatistics));
1321
+ if (options.having)
1322
+ params.append('having', options.having);
1323
+ if (options.resultOffset)
1324
+ params.append('resultOffset', options.resultOffset.toString());
1325
+ if (options.resultRecordCount)
1326
+ params.append('resultRecordCount', options.resultRecordCount.toString());
1327
+ if (options.token)
1328
+ params.append('token', options.token);
1329
+ return `${baseUrl}?${params.toString()}`;
1330
+ }
1331
+ get _source() {
1332
+ return this._map.getSource(this._sourceId);
1333
+ }
1334
+ get _url() {
1335
+ return this._buildQueryUrl();
1336
+ }
1337
+ get serviceMetadata() {
1338
+ return this._serviceMetadata;
1339
+ }
1340
+ get defaultStyle() {
1341
+ if (this._defaultStyleData)
1342
+ return this._defaultStyleData;
1343
+ // Generate default style based on geometry type from service metadata
1344
+ const geometryType = String(this._serviceMetadata?.geometryType || 'esriGeometryPoint');
1345
+ const isVectorTiles = this.esriServiceOptions.useVectorTiles !== false;
1346
+ // For vector tiles, we need to include source-layer
1347
+ const baseStyle = {
1348
+ source: this._sourceId,
1349
+ };
1350
+ if (isVectorTiles && this._serviceMetadata?.name) {
1351
+ baseStyle['source-layer'] = String(this._serviceMetadata.name);
1352
+ }
1353
+ if (geometryType.includes('Point')) {
1354
+ return {
1355
+ type: 'circle',
1356
+ ...baseStyle,
1357
+ paint: {
1358
+ 'circle-radius': 4,
1359
+ 'circle-color': '#3b82f6',
1360
+ 'circle-stroke-color': '#1e40af',
1361
+ 'circle-stroke-width': 1,
1362
+ },
1363
+ };
1364
+ }
1365
+ else if (geometryType.includes('Polyline')) {
1366
+ return {
1367
+ type: 'line',
1368
+ ...baseStyle,
1369
+ paint: {
1370
+ 'line-color': '#3b82f6',
1371
+ 'line-width': 2,
1372
+ },
1373
+ };
1374
+ }
1375
+ else if (geometryType.includes('Polygon')) {
1376
+ return {
1377
+ type: 'fill',
1378
+ ...baseStyle,
1379
+ paint: {
1380
+ 'fill-color': 'rgba(59, 130, 246, 0.4)',
1381
+ 'fill-outline-color': '#1e40af',
1382
+ },
1383
+ };
1384
+ }
1385
+ // Default to circle for unknown geometry types
1386
+ return {
1387
+ type: 'circle',
1388
+ ...baseStyle,
1389
+ paint: {
1390
+ 'circle-radius': 4,
1391
+ 'circle-color': '#3b82f6',
1392
+ 'circle-stroke-color': '#1e40af',
1393
+ 'circle-stroke-width': 1,
1394
+ },
1395
+ };
1396
+ }
1397
+ getStyle() {
1398
+ return new Promise((resolve, reject) => {
1399
+ if (this._serviceMetadata) {
1400
+ resolve(this.defaultStyle);
1401
+ }
1402
+ else {
1403
+ // Wait for service metadata to be loaded
1404
+ this._getServiceMetadata()
1405
+ .then(() => resolve(this.defaultStyle))
1406
+ .catch(error => reject(error));
1407
+ }
1408
+ });
1409
+ }
1410
+ async _getServiceMetadata() {
1411
+ if (this._serviceMetadata)
1412
+ return;
1413
+ this._serviceMetadata = await getServiceDetails(this.esriServiceOptions.url, this.esriServiceOptions.fetchOptions);
1414
+ }
1415
+ updateData() {
1416
+ // For GeoJSON sources with bounding box filtering, update the data URL
1417
+ if (this.esriServiceOptions.useVectorTiles === false &&
1418
+ this.esriServiceOptions.useBoundingBox) {
1419
+ const source = this._map.getSource(this._sourceId);
1420
+ if (source && 'setData' in source && typeof source.setData === 'function') {
1421
+ const newQueryUrl = this._buildQueryUrl();
1422
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1423
+ if (!isTestEnvironment) {
1424
+ console.log('Updating FeatureService data with new bounding box:', newQueryUrl);
1425
+ }
1426
+ // @ts-ignore - GeoJSON source setData method not in generic Source type
1427
+ source.setData(newQueryUrl);
1428
+ }
1429
+ }
1430
+ else {
1431
+ // Vector tile sources don't need dynamic data updates like GeoJSON
1432
+ // The tiles are fetched automatically based on the tile URL
1433
+ // For filtering, we would need to recreate the source or use layer filtering
1434
+ console.warn('updateData() is only applicable for GeoJSON sources with bounding box filtering.');
1435
+ }
1436
+ }
1437
+ _setupBoundingBoxUpdates() {
1438
+ if (this._boundingBoxUpdateHandler) {
1439
+ // Remove existing handler
1440
+ this._map.off('moveend', this._boundingBoxUpdateHandler);
1441
+ this._map.off('zoomend', this._boundingBoxUpdateHandler);
1442
+ }
1443
+ // Debounced update function to prevent excessive API calls
1444
+ let updateTimeout = null;
1445
+ this._boundingBoxUpdateHandler = () => {
1446
+ if (updateTimeout) {
1447
+ clearTimeout(updateTimeout);
1448
+ }
1449
+ updateTimeout = setTimeout(() => {
1450
+ this.updateData();
1451
+ }, 300); // 300ms debounce
1452
+ };
1453
+ // Listen for map movement and zoom changes
1454
+ this._map.on('moveend', this._boundingBoxUpdateHandler);
1455
+ this._map.on('zoomend', this._boundingBoxUpdateHandler);
1456
+ }
1457
+ _removeBoundingBoxUpdates() {
1458
+ if (this._boundingBoxUpdateHandler) {
1459
+ this._map.off('moveend', this._boundingBoxUpdateHandler);
1460
+ this._map.off('zoomend', this._boundingBoxUpdateHandler);
1461
+ this._boundingBoxUpdateHandler = null;
1462
+ }
1463
+ }
1464
+ updateSource() {
1465
+ // Remove existing source and recreate with new parameters
1466
+ if (this._map.getSource(this._sourceId)) {
1467
+ this._map.removeSource(this._sourceId);
1468
+ }
1469
+ this._createSource();
1470
+ }
1471
+ setWhere(whereClause) {
1472
+ this.esriServiceOptions.where = whereClause;
1473
+ this.updateSource();
1474
+ }
1475
+ setOutFields(fields) {
1476
+ this.esriServiceOptions.outFields = fields;
1477
+ this.updateSource();
1478
+ }
1479
+ setLayers(layers) {
1480
+ this.esriServiceOptions.layers = layers;
1481
+ this.updateSource();
1482
+ }
1483
+ setGeometry(geometry, geometryType) {
1484
+ this.esriServiceOptions.geometry = geometry;
1485
+ if (geometryType) {
1486
+ this.esriServiceOptions.geometryType = geometryType;
1487
+ }
1488
+ this.updateSource();
1489
+ }
1490
+ clearGeometry() {
1491
+ delete this.esriServiceOptions.geometry;
1492
+ delete this.esriServiceOptions.geometryType;
1493
+ this.updateSource();
1494
+ }
1495
+ setBoundingBoxFilter(enabled) {
1496
+ this.esriServiceOptions.useBoundingBox = enabled;
1497
+ if (enabled && this.esriServiceOptions.useVectorTiles === false) {
1498
+ this._setupBoundingBoxUpdates();
1499
+ }
1500
+ else {
1501
+ this._removeBoundingBoxUpdates();
1502
+ }
1503
+ this.updateSource();
1504
+ }
1505
+ // Note: maxRecordCount is a server capability; not settable via query params
1506
+ remove() {
1507
+ this._removeBoundingBoxUpdates();
1508
+ if (this._map.getSource(this._sourceId)) {
1509
+ this._map.removeSource(this._sourceId);
1510
+ }
1511
+ }
1512
+ async queryFeatures(options) {
1513
+ const queryOptions = { ...this.esriServiceOptions, ...options };
1514
+ const queryUrl = this._buildQueryUrlWithOptions(queryOptions);
1515
+ try {
1516
+ const response = await fetch(queryUrl, this.esriServiceOptions.fetchOptions);
1517
+ if (!response.ok) {
1518
+ throw new Error(`HTTP error! status: ${response.status}`);
1519
+ }
1520
+ return await response.json();
1521
+ }
1522
+ catch (error) {
1523
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
1524
+ if (!isTestEnvironment) {
1525
+ console.error('Error querying features:', error);
1526
+ }
1527
+ throw error;
1528
+ }
1529
+ }
1530
+ _buildQueryUrlWithOptions(options) {
1531
+ const mergedOptions = { ...this._defaultEsriOptions, ...options };
1532
+ const baseUrl = `${mergedOptions.url}/query`;
1533
+ const params = new URLSearchParams();
1534
+ // Add query parameters using the same logic as _buildQueryUrl (omit 'layers')
1535
+ params.append('where', mergedOptions.where || '1=1');
1536
+ params.append('outFields', Array.isArray(mergedOptions.outFields)
1537
+ ? mergedOptions.outFields.join(',')
1538
+ : mergedOptions.outFields || '*');
1539
+ params.append('f', mergedOptions.f || 'geojson');
1540
+ params.append('returnGeometry', (mergedOptions.returnGeometry !== false).toString());
1541
+ if (mergedOptions.geometry) {
1542
+ params.append('geometry', JSON.stringify(mergedOptions.geometry));
1543
+ if (mergedOptions.geometryType)
1544
+ params.append('geometryType', mergedOptions.geometryType);
1545
+ if (mergedOptions.spatialRel)
1546
+ params.append('spatialRel', mergedOptions.spatialRel);
1547
+ if (mergedOptions.inSR)
1548
+ params.append('inSR', mergedOptions.inSR);
1549
+ }
1550
+ if (mergedOptions.outSR)
1551
+ params.append('outSR', mergedOptions.outSR);
1552
+ if (mergedOptions.orderByFields)
1553
+ params.append('orderByFields', mergedOptions.orderByFields);
1554
+ if (mergedOptions.groupByFieldsForStatistics)
1555
+ params.append('groupByFieldsForStatistics', mergedOptions.groupByFieldsForStatistics);
1556
+ if (mergedOptions.outStatistics && mergedOptions.outStatistics.length > 0)
1557
+ params.append('outStatistics', JSON.stringify(mergedOptions.outStatistics));
1558
+ if (mergedOptions.having)
1559
+ params.append('having', mergedOptions.having);
1560
+ if (mergedOptions.resultOffset)
1561
+ params.append('resultOffset', mergedOptions.resultOffset.toString());
1562
+ if (mergedOptions.resultRecordCount)
1563
+ params.append('resultRecordCount', mergedOptions.resultRecordCount.toString());
1564
+ if (mergedOptions.token)
1565
+ params.append('token', mergedOptions.token);
1566
+ return `${baseUrl}?${params.toString()}`;
1567
+ }
1568
+ }
1569
+
1570
+ /**
1571
+ * Base Task class for ArcGIS REST API operations
1572
+ * Similar to Esri Leaflet's Task class
1573
+ */
1574
+ class Task {
1575
+ constructor(endpoint) {
1576
+ Object.defineProperty(this, "_service", {
1577
+ enumerable: true,
1578
+ configurable: true,
1579
+ writable: true,
1580
+ value: void 0
1581
+ });
1582
+ Object.defineProperty(this, "options", {
1583
+ enumerable: true,
1584
+ configurable: true,
1585
+ writable: true,
1586
+ value: void 0
1587
+ });
1588
+ Object.defineProperty(this, "params", {
1589
+ enumerable: true,
1590
+ configurable: true,
1591
+ writable: true,
1592
+ value: {}
1593
+ });
1594
+ Object.defineProperty(this, "path", {
1595
+ enumerable: true,
1596
+ configurable: true,
1597
+ writable: true,
1598
+ value: ''
1599
+ });
1600
+ Object.defineProperty(this, "setters", {
1601
+ enumerable: true,
1602
+ configurable: true,
1603
+ writable: true,
1604
+ value: {}
1605
+ });
1606
+ // endpoint can be either a url (and options) for an ArcGIS Rest Service or an instance of Service
1607
+ if (endpoint && typeof endpoint === 'object' && 'request' in endpoint) {
1608
+ this._service = endpoint;
1609
+ this.options = {}; // Service will handle its own options
1610
+ }
1611
+ else if (typeof endpoint === 'string') {
1612
+ this.options = { url: cleanTrailingSlash(endpoint) };
1613
+ }
1614
+ else {
1615
+ this.options = { ...endpoint };
1616
+ if (this.options.url) {
1617
+ this.options.url = cleanTrailingSlash(this.options.url);
1618
+ }
1619
+ }
1620
+ // Initialize params if not already set by subclass
1621
+ if (!this.params) {
1622
+ this.params = {};
1623
+ }
1624
+ // Generate setter methods based on the setters object
1625
+ if (this.setters) {
1626
+ for (const setter in this.setters) {
1627
+ const param = this.setters[setter];
1628
+ this[setter] = this.generateSetter(param, this);
1629
+ }
1630
+ }
1631
+ }
1632
+ /**
1633
+ * Generate a method for each methodName:paramName in the setters for this task
1634
+ */
1635
+ generateSetter(param, context) {
1636
+ return function (value) {
1637
+ context.params[param] = value;
1638
+ return context;
1639
+ };
1640
+ }
1641
+ /**
1642
+ * Set authentication token
1643
+ */
1644
+ token(token) {
1645
+ if (this._service) {
1646
+ this._service.authenticate(token);
1647
+ }
1648
+ else {
1649
+ this.params.token = token;
1650
+ }
1651
+ return this;
1652
+ }
1653
+ /**
1654
+ * Set API key (alias for token)
1655
+ */
1656
+ apikey(apikey) {
1657
+ return this.token(apikey);
1658
+ }
1659
+ /**
1660
+ * Set whether to return formatted or unformatted values (ArcGIS Server 10.5+)
1661
+ */
1662
+ format(formatted) {
1663
+ this.params.returnUnformattedValues = !formatted;
1664
+ return this;
1665
+ }
1666
+ /**
1667
+ * Execute the task request with callback (Esri Leaflet style)
1668
+ */
1669
+ run(callback) {
1670
+ if (this.options.requestParams) {
1671
+ Object.assign(this.params, this.options.requestParams);
1672
+ }
1673
+ if (this._service) {
1674
+ this._service.requestWithCallback('POST', this.path, this.params, callback);
1675
+ return;
1676
+ }
1677
+ // Direct request fallback
1678
+ this._request('POST', this.path, this.params, callback);
1679
+ }
1680
+ /**
1681
+ * Execute the task request with Promise
1682
+ */
1683
+ async request() {
1684
+ if (this.options.requestParams) {
1685
+ Object.assign(this.params, this.options.requestParams);
1686
+ }
1687
+ if (this._service) {
1688
+ return this._service.requestWithCallback('POST', this.path, this.params);
1689
+ }
1690
+ return new Promise((resolve, reject) => {
1691
+ this._request('POST', this.path, this.params, (error, response) => {
1692
+ if (error) {
1693
+ reject(error);
1694
+ }
1695
+ else {
1696
+ resolve(response);
1697
+ }
1698
+ });
1699
+ });
1700
+ }
1701
+ /**
1702
+ * Direct HTTP request (when not using a service)
1703
+ */
1704
+ _request(method, path, params, callback) {
1705
+ if (!this.options.url) {
1706
+ callback(new Error('URL is required for task execution'));
1707
+ return;
1708
+ }
1709
+ // Ensure proper URL construction with path separator
1710
+ const baseUrl = this.options.url.endsWith('/')
1711
+ ? this.options.url.slice(0, -1)
1712
+ : this.options.url;
1713
+ const cleanPath = path.startsWith('/') ? path : `/${path}`;
1714
+ const fullServiceUrl = `${baseUrl}${cleanPath}`;
1715
+ const url = this.options.proxy ? `${this.options.proxy}?${fullServiceUrl}` : fullServiceUrl;
1716
+ // Convert params to URLSearchParams
1717
+ const searchParams = new URLSearchParams();
1718
+ Object.keys(params).forEach(key => {
1719
+ const value = params[key];
1720
+ if (value !== undefined && value !== null) {
1721
+ if (Array.isArray(value)) {
1722
+ searchParams.append(key, value.join(','));
1723
+ }
1724
+ else if (typeof value === 'object') {
1725
+ searchParams.append(key, JSON.stringify(value));
1726
+ }
1727
+ else {
1728
+ searchParams.append(key, value.toString());
1729
+ }
1730
+ }
1731
+ });
1732
+ const fullUrl = method === 'GET' ? `${url}?${searchParams.toString()}` : url;
1733
+ const fetchOptions = {
1734
+ method,
1735
+ headers: {
1736
+ 'Content-Type': 'application/x-www-form-urlencoded',
1737
+ },
1738
+ };
1739
+ if (method === 'POST') {
1740
+ fetchOptions.body = searchParams.toString();
1741
+ }
1742
+ fetch(fullUrl, fetchOptions)
1743
+ .then(response => {
1744
+ if (!response.ok) {
1745
+ throw new Error(`HTTP error! status: ${response.status}`);
1746
+ }
1747
+ return response.json();
1748
+ })
1749
+ .then(data => callback(undefined, data))
1750
+ .catch(error => callback(error));
1751
+ }
1752
+ }
1753
+
1754
+ /**
1755
+ * Query task for ArcGIS Feature Services
1756
+ * Based on Esri Leaflet's Query functionality
1757
+ */
1758
+ class Query extends Task {
1759
+ constructor(options) {
1760
+ super(options);
1761
+ Object.defineProperty(this, "setters", {
1762
+ enumerable: true,
1763
+ configurable: true,
1764
+ writable: true,
1765
+ value: {
1766
+ offset: 'resultOffset',
1767
+ limit: 'resultRecordCount',
1768
+ fields: 'outFields',
1769
+ precision: 'geometryPrecision',
1770
+ featureIds: 'objectIds',
1771
+ returnGeometry: 'returnGeometry',
1772
+ returnM: 'returnM',
1773
+ transform: 'datumTransformation',
1774
+ token: 'token',
1775
+ }
1776
+ });
1777
+ Object.defineProperty(this, "path", {
1778
+ enumerable: true,
1779
+ configurable: true,
1780
+ writable: true,
1781
+ value: 'query'
1782
+ });
1783
+ Object.defineProperty(this, "params", {
1784
+ enumerable: true,
1785
+ configurable: true,
1786
+ writable: true,
1787
+ value: {
1788
+ returnGeometry: true,
1789
+ where: '1=1',
1790
+ outSR: 4326,
1791
+ outFields: '*',
1792
+ f: 'json',
1793
+ }
1794
+ });
1795
+ this.path = 'query';
1796
+ // If options is a QueryOptions object, merge relevant properties into params
1797
+ if (options &&
1798
+ typeof options === 'object' &&
1799
+ !('request' in options) &&
1800
+ typeof options !== 'string') {
1801
+ const queryOptions = options;
1802
+ // Merge query-specific options into params
1803
+ if (queryOptions.where !== undefined)
1804
+ this.params.where = queryOptions.where;
1805
+ if (queryOptions.outFields !== undefined)
1806
+ this.params.outFields = queryOptions.outFields;
1807
+ if (queryOptions.returnGeometry !== undefined)
1808
+ this.params.returnGeometry = queryOptions.returnGeometry;
1809
+ if (queryOptions.spatialRel !== undefined)
1810
+ this.params.spatialRel = queryOptions.spatialRel;
1811
+ if (queryOptions.geometry !== undefined)
1812
+ this.params.geometry = queryOptions.geometry;
1813
+ if (queryOptions.geometryType !== undefined)
1814
+ this.params.geometryType = queryOptions.geometryType;
1815
+ if (queryOptions.inSR !== undefined)
1816
+ this.params.inSR = queryOptions.inSR;
1817
+ if (queryOptions.outSR !== undefined)
1818
+ this.params.outSR = queryOptions.outSR;
1819
+ if (queryOptions.returnDistinctValues !== undefined)
1820
+ this.params.returnDistinctValues = queryOptions.returnDistinctValues;
1821
+ if (queryOptions.returnIdsOnly !== undefined)
1822
+ this.params.returnIdsOnly = queryOptions.returnIdsOnly;
1823
+ if (queryOptions.returnCountOnly !== undefined)
1824
+ this.params.returnCountOnly = queryOptions.returnCountOnly;
1825
+ if (queryOptions.returnExtentOnly !== undefined)
1826
+ this.params.returnExtentOnly = queryOptions.returnExtentOnly;
1827
+ if (queryOptions.orderByFields !== undefined)
1828
+ this.params.orderByFields = queryOptions.orderByFields;
1829
+ if (queryOptions.groupByFieldsForStatistics !== undefined)
1830
+ this.params.groupByFieldsForStatistics = queryOptions.groupByFieldsForStatistics;
1831
+ if (queryOptions.outStatistics !== undefined)
1832
+ this.params.outStatistics = queryOptions.outStatistics;
1833
+ if (queryOptions.resultOffset !== undefined)
1834
+ this.params.resultOffset = queryOptions.resultOffset;
1835
+ if (queryOptions.resultRecordCount !== undefined)
1836
+ this.params.resultRecordCount = queryOptions.resultRecordCount;
1837
+ if (queryOptions.maxAllowableOffset !== undefined)
1838
+ this.params.maxAllowableOffset = queryOptions.maxAllowableOffset;
1839
+ if (queryOptions.geometryPrecision !== undefined)
1840
+ this.params.geometryPrecision = queryOptions.geometryPrecision;
1841
+ if (queryOptions.time !== undefined)
1842
+ this.params.time = queryOptions.time;
1843
+ if (queryOptions.gdbVersion !== undefined)
1844
+ this.params.gdbVersion = queryOptions.gdbVersion;
1845
+ if (queryOptions.historicMoment !== undefined)
1846
+ this.params.historicMoment = queryOptions.historicMoment;
1847
+ if (queryOptions.returnTrueCurves !== undefined)
1848
+ this.params.returnTrueCurves = queryOptions.returnTrueCurves;
1849
+ if (queryOptions.returnZ !== undefined)
1850
+ this.params.returnZ = queryOptions.returnZ;
1851
+ if (queryOptions.returnM !== undefined)
1852
+ this.params.returnM = queryOptions.returnM;
1853
+ if (queryOptions.token !== undefined)
1854
+ this.params.token = queryOptions.token;
1855
+ }
1856
+ }
1857
+ // Spatial relationship methods
1858
+ /**
1859
+ * Returns a feature if its shape is wholly contained within the search geometry
1860
+ */
1861
+ within(geometry) {
1862
+ this._setGeometryParams(geometry);
1863
+ this.params.spatialRel = 'esriSpatialRelContains';
1864
+ return this;
1865
+ }
1866
+ /**
1867
+ * Returns a feature if any spatial relationship is found
1868
+ */
1869
+ intersects(geometry) {
1870
+ this._setGeometryParams(geometry);
1871
+ this.params.spatialRel = 'esriSpatialRelIntersects';
1872
+ return this;
1873
+ }
1874
+ /**
1875
+ * Returns a feature if its shape wholly contains the search geometry
1876
+ */
1877
+ contains(geometry) {
1878
+ this._setGeometryParams(geometry);
1879
+ this.params.spatialRel = 'esriSpatialRelWithin';
1880
+ return this;
1881
+ }
1882
+ /**
1883
+ * Returns a feature if the intersection of the interiors is not empty and has lower dimension
1884
+ */
1885
+ crosses(geometry) {
1886
+ this._setGeometryParams(geometry);
1887
+ this.params.spatialRel = 'esriSpatialRelCrosses';
1888
+ return this;
1889
+ }
1890
+ /**
1891
+ * Returns a feature if the two shapes share a common boundary
1892
+ */
1893
+ touches(geometry) {
1894
+ this._setGeometryParams(geometry);
1895
+ this.params.spatialRel = 'esriSpatialRelTouches';
1896
+ return this;
1897
+ }
1898
+ /**
1899
+ * Returns a feature if the intersection results in same dimension but different from both shapes
1900
+ */
1901
+ overlaps(geometry) {
1902
+ this._setGeometryParams(geometry);
1903
+ this.params.spatialRel = 'esriSpatialRelOverlaps';
1904
+ return this;
1905
+ }
1906
+ /**
1907
+ * Returns a feature if the envelope of the two shapes intersects
1908
+ */
1909
+ bboxIntersects(geometry) {
1910
+ this._setGeometryParams(geometry);
1911
+ this.params.spatialRel = 'esriSpatialRelEnvelopeIntersects';
1912
+ return this;
1913
+ }
1914
+ /**
1915
+ * Nearby search - only valid for ArcGIS Server 10.3+ or ArcGIS Online
1916
+ */
1917
+ nearby(latlng, radius) {
1918
+ this.params.geometry = [latlng.lng, latlng.lat];
1919
+ this.params.geometryType = 'esriGeometryPoint';
1920
+ this.params.spatialRel = 'esriSpatialRelIntersects';
1921
+ this.params.units = 'esriSRUnit_Meter';
1922
+ this.params.distance = radius;
1923
+ this.params.inSR = 4326;
1924
+ return this;
1925
+ }
1926
+ // Query methods
1927
+ /**
1928
+ * Set WHERE clause for the query
1929
+ */
1930
+ where(whereClause) {
1931
+ this.params.where = whereClause;
1932
+ return this;
1933
+ }
1934
+ /**
1935
+ * Set time range for temporal queries
1936
+ */
1937
+ between(start, end) {
1938
+ const startTime = start instanceof Date ? start.valueOf() : start;
1939
+ const endTime = end instanceof Date ? end.valueOf() : end;
1940
+ this.params.time = [startTime, endTime];
1941
+ return this;
1942
+ }
1943
+ /**
1944
+ * Simplify geometries based on map resolution
1945
+ */
1946
+ simplify(map, factor) {
1947
+ const bounds = map.getBounds();
1948
+ const mapWidth = Math.abs(bounds._northEast.lng - bounds._southWest.lng);
1949
+ this.params.maxAllowableOffset = (mapWidth / map.getSize().x) * factor;
1950
+ return this;
1951
+ }
1952
+ /**
1953
+ * Set order by fields
1954
+ */
1955
+ orderBy(fieldName, order = 'ASC') {
1956
+ const currentOrder = this.params.orderByFields || '';
1957
+ this.params.orderByFields = currentOrder
1958
+ ? `${currentOrder},${fieldName} ${order}`
1959
+ : `${fieldName} ${order}`;
1960
+ return this;
1961
+ }
1962
+ /**
1963
+ * Set specific layer to query (for Map Services)
1964
+ */
1965
+ layer(layerId) {
1966
+ this.path = `${layerId}/query`;
1967
+ return this;
1968
+ }
1969
+ /**
1970
+ * Return only distinct values
1971
+ */
1972
+ distinct() {
1973
+ this.params.returnGeometry = false;
1974
+ this.params.returnDistinctValues = true;
1975
+ return this;
1976
+ }
1977
+ /**
1978
+ * Set pixel size for image services
1979
+ */
1980
+ pixelSize(point) {
1981
+ this.params.pixelSize = [point.x, point.y];
1982
+ return this;
1983
+ }
1984
+ // Execution methods
1985
+ /**
1986
+ * Execute the query and return features
1987
+ */
1988
+ async run() {
1989
+ this._cleanParams();
1990
+ // Use GeoJSON format if supported
1991
+ this.params.f = 'geojson';
1992
+ try {
1993
+ const response = await this.request();
1994
+ return response;
1995
+ }
1996
+ catch {
1997
+ // Fallback to JSON format and convert
1998
+ this.params.f = 'json';
1999
+ const response = await this.request();
2000
+ return this._convertToGeoJSON(response);
2001
+ }
2002
+ }
2003
+ /**
2004
+ * Execute the query and return only the count
2005
+ */
2006
+ async count() {
2007
+ this._cleanParams();
2008
+ this.params.returnCountOnly = true;
2009
+ const response = await this.request();
2010
+ return response.count;
2011
+ }
2012
+ /**
2013
+ * Execute the query and return only feature IDs
2014
+ */
2015
+ async ids() {
2016
+ this._cleanParams();
2017
+ this.params.returnIdsOnly = true;
2018
+ const response = await this.request();
2019
+ return response.objectIds;
2020
+ }
2021
+ /**
2022
+ * Execute the query and return extent bounds (ArcGIS Server 10.3+)
2023
+ */
2024
+ async bounds() {
2025
+ this._cleanParams();
2026
+ this.params.returnExtentOnly = true;
2027
+ const response = await this.request();
2028
+ if (!response.extent) {
2029
+ throw new Error('Invalid bounds returned');
2030
+ }
2031
+ return {
2032
+ _southWest: { lat: response.extent.ymin, lng: response.extent.xmin },
2033
+ _northEast: { lat: response.extent.ymax, lng: response.extent.xmax },
2034
+ };
2035
+ }
2036
+ // Private methods
2037
+ _setGeometryParams(geometry) {
2038
+ this.params.inSR = 4326;
2039
+ const converted = this._setGeometry(geometry);
2040
+ this.params.geometry = converted.geometry;
2041
+ this.params.geometryType = converted.geometryType;
2042
+ }
2043
+ _setGeometry(geometry) {
2044
+ if (!geometry) {
2045
+ return { geometry: null, geometryType: 'esriGeometryPoint' };
2046
+ }
2047
+ // Handle different geometry types
2048
+ if (typeof geometry === 'object' && geometry !== null) {
2049
+ const geom = geometry;
2050
+ if ('lat' in geom && 'lng' in geom) {
2051
+ // Leaflet LatLng-like object
2052
+ return {
2053
+ geometry: { x: geom.lng, y: geom.lat, spatialReference: { wkid: 4326 } },
2054
+ geometryType: 'esriGeometryPoint',
2055
+ };
2056
+ }
2057
+ if ('_southWest' in geom && '_northEast' in geom) {
2058
+ // Leaflet Bounds-like object
2059
+ const bounds = geom;
2060
+ return {
2061
+ geometry: {
2062
+ xmin: bounds._southWest.lng,
2063
+ ymin: bounds._southWest.lat,
2064
+ xmax: bounds._northEast.lng,
2065
+ ymax: bounds._northEast.lat,
2066
+ spatialReference: { wkid: 4326 },
2067
+ },
2068
+ geometryType: 'esriGeometryEnvelope',
2069
+ };
2070
+ }
2071
+ if ('xmin' in geom && 'ymin' in geom && 'xmax' in geom && 'ymax' in geom) {
2072
+ // Esri envelope geometry
2073
+ return {
2074
+ geometry,
2075
+ geometryType: 'esriGeometryEnvelope',
2076
+ };
2077
+ }
2078
+ }
2079
+ // Default: assume it's already in Esri geometry format
2080
+ return {
2081
+ geometry,
2082
+ geometryType: 'esriGeometryPoint',
2083
+ };
2084
+ }
2085
+ _cleanParams() {
2086
+ delete this.params.returnIdsOnly;
2087
+ delete this.params.returnExtentOnly;
2088
+ delete this.params.returnCountOnly;
2089
+ delete this.params.returnDistinctValues;
2090
+ }
2091
+ _convertToGeoJSON(response) {
2092
+ // Handle cases where features might be undefined or empty
2093
+ const features = (response.features || []).map(feature => ({
2094
+ type: 'Feature',
2095
+ properties: feature.attributes,
2096
+ geometry: feature.geometry || null,
2097
+ }));
2098
+ return {
2099
+ type: 'FeatureCollection',
2100
+ features,
2101
+ };
2102
+ }
2103
+ }
2104
+ function query(options) {
2105
+ return new Query(options);
2106
+ }
2107
+
2108
+ /**
2109
+ * Find task for searching text in ArcGIS Map Services
2110
+ * Based on Esri Leaflet's Find functionality
2111
+ */
2112
+ class Find extends Task {
2113
+ constructor(options) {
2114
+ super(options);
2115
+ Object.defineProperty(this, "setters", {
2116
+ enumerable: true,
2117
+ configurable: true,
2118
+ writable: true,
2119
+ value: {
2120
+ contains: 'contains',
2121
+ text: 'searchText',
2122
+ fields: 'searchFields',
2123
+ spatialReference: 'sr',
2124
+ sr: 'sr',
2125
+ layers: 'layers',
2126
+ returnGeometry: 'returnGeometry',
2127
+ maxAllowableOffset: 'maxAllowableOffset',
2128
+ precision: 'geometryPrecision',
2129
+ dynamicLayers: 'dynamicLayers',
2130
+ returnZ: 'returnZ',
2131
+ returnM: 'returnM',
2132
+ gdbVersion: 'gdbVersion',
2133
+ token: 'token',
2134
+ }
2135
+ });
2136
+ Object.defineProperty(this, "path", {
2137
+ enumerable: true,
2138
+ configurable: true,
2139
+ writable: true,
2140
+ value: 'find'
2141
+ });
2142
+ Object.defineProperty(this, "params", {
2143
+ enumerable: true,
2144
+ configurable: true,
2145
+ writable: true,
2146
+ value: {
2147
+ searchText: '', // Required parameter
2148
+ layers: 'all', // Can be 'all' or comma-separated layer IDs
2149
+ contains: true,
2150
+ returnGeometry: true,
2151
+ f: 'json',
2152
+ }
2153
+ });
2154
+ this.path = 'find';
2155
+ // If options is a FindOptions object, merge relevant properties into params
2156
+ if (options &&
2157
+ typeof options === 'object' &&
2158
+ !('request' in options) &&
2159
+ typeof options !== 'string') {
2160
+ const findOptions = options;
2161
+ // Merge find-specific options into params
2162
+ if (findOptions.searchText !== undefined)
2163
+ this.params.searchText = findOptions.searchText;
2164
+ if (findOptions.contains !== undefined)
2165
+ this.params.contains = findOptions.contains;
2166
+ if (findOptions.searchFields !== undefined) {
2167
+ this.params.searchFields = Array.isArray(findOptions.searchFields)
2168
+ ? findOptions.searchFields.join(',')
2169
+ : findOptions.searchFields;
2170
+ }
2171
+ if (findOptions.sr !== undefined)
2172
+ this.params.sr = findOptions.sr;
2173
+ if (findOptions.layers !== undefined) {
2174
+ // Convert array to comma-separated string or use as-is if already string
2175
+ if (Array.isArray(findOptions.layers)) {
2176
+ this.params.layers = findOptions.layers.join(',');
2177
+ }
2178
+ else if (typeof findOptions.layers === 'string') {
2179
+ this.params.layers = findOptions.layers;
2180
+ }
2181
+ else {
2182
+ this.params.layers = findOptions.layers.toString();
2183
+ }
2184
+ }
2185
+ if (findOptions.returnGeometry !== undefined)
2186
+ this.params.returnGeometry = findOptions.returnGeometry;
2187
+ if (findOptions.maxAllowableOffset !== undefined)
2188
+ this.params.maxAllowableOffset = findOptions.maxAllowableOffset;
2189
+ if (findOptions.geometryPrecision !== undefined)
2190
+ this.params.geometryPrecision = findOptions.geometryPrecision;
2191
+ if (findOptions.dynamicLayers !== undefined)
2192
+ this.params.dynamicLayers = findOptions.dynamicLayers;
2193
+ if (findOptions.returnZ !== undefined)
2194
+ this.params.returnZ = findOptions.returnZ;
2195
+ if (findOptions.returnM !== undefined)
2196
+ this.params.returnM = findOptions.returnM;
2197
+ if (findOptions.gdbVersion !== undefined)
2198
+ this.params.gdbVersion = findOptions.gdbVersion;
2199
+ if (findOptions.layerDefs !== undefined)
2200
+ this.params.layerDefs = findOptions.layerDefs;
2201
+ if (findOptions.token !== undefined)
2202
+ this.params.token = findOptions.token;
2203
+ }
2204
+ }
2205
+ /**
2206
+ * Set the text to search for
2207
+ */
2208
+ text(searchText) {
2209
+ this.params.searchText = searchText;
2210
+ return this;
2211
+ }
2212
+ /**
2213
+ * Set the fields to search in
2214
+ */
2215
+ fields(fields) {
2216
+ this.params.searchFields = Array.isArray(fields) ? fields.join(',') : fields;
2217
+ return this;
2218
+ }
2219
+ /**
2220
+ * Set whether the search should contain the text (partial match) or exact match
2221
+ */
2222
+ contains(contains) {
2223
+ this.params.contains = contains;
2224
+ return this;
2225
+ }
2226
+ /**
2227
+ * Set which layers to search in
2228
+ */
2229
+ layers(layers) {
2230
+ if (Array.isArray(layers)) {
2231
+ this.params.layers = layers.join(',');
2232
+ }
2233
+ else {
2234
+ this.params.layers = layers;
2235
+ }
2236
+ return this;
2237
+ }
2238
+ /**
2239
+ * Set layer definitions for filtering specific layers
2240
+ */
2241
+ layerDefs(layerId, whereClause) {
2242
+ const currentLayerDefs = this.params.layerDefs || '';
2243
+ this.params.layerDefs = currentLayerDefs
2244
+ ? `${currentLayerDefs};${layerId}:${whereClause}`
2245
+ : `${layerId}:${whereClause}`;
2246
+ return this;
2247
+ }
2248
+ /**
2249
+ * Simplify geometries based on map resolution
2250
+ */
2251
+ simplify(map, factor) {
2252
+ const bounds = map.getBounds();
2253
+ const mapWidth = Math.abs(bounds.getWest() - bounds.getEast());
2254
+ this.params.maxAllowableOffset = (mapWidth / map.getSize().x) * factor;
2255
+ return this;
2256
+ }
2257
+ /**
2258
+ * Execute the find operation
2259
+ */
2260
+ async run() {
2261
+ // Always use JSON format for Find API (GeoJSON might not be supported)
2262
+ this.params.f = 'json';
2263
+ try {
2264
+ const response = await this.request();
2265
+ return this._convertToGeoJSON(response);
2266
+ }
2267
+ catch (error) {
2268
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
2269
+ if (!isTestEnvironment) {
2270
+ console.error('Find task error:', error);
2271
+ }
2272
+ throw error;
2273
+ }
2274
+ }
2275
+ _convertToGeoJSON(response) {
2276
+ // Handle cases where response is null or results might be undefined, null, or empty
2277
+ const results = response?.results || [];
2278
+ const features = results.map(result => ({
2279
+ type: 'Feature',
2280
+ properties: {
2281
+ ...result.attributes,
2282
+ layerId: result.layerId,
2283
+ layerName: result.layerName,
2284
+ foundFieldName: result.foundFieldName,
2285
+ value: result.value,
2286
+ },
2287
+ geometry: this._convertEsriGeometry(result.geometry),
2288
+ }));
2289
+ return {
2290
+ type: 'FeatureCollection',
2291
+ features,
2292
+ };
2293
+ }
2294
+ _convertEsriGeometry(esriGeom) {
2295
+ if (!esriGeom || typeof esriGeom !== 'object')
2296
+ return null;
2297
+ const geom = esriGeom;
2298
+ // Point geometry
2299
+ if ('x' in geom && 'y' in geom) {
2300
+ return {
2301
+ type: 'Point',
2302
+ coordinates: [geom.x, geom.y],
2303
+ };
2304
+ }
2305
+ // Polygon geometry
2306
+ if ('rings' in geom && Array.isArray(geom.rings)) {
2307
+ return {
2308
+ type: 'Polygon',
2309
+ coordinates: geom.rings,
2310
+ };
2311
+ }
2312
+ // Polyline geometry
2313
+ if ('paths' in geom && Array.isArray(geom.paths)) {
2314
+ const paths = geom.paths;
2315
+ if (paths.length === 1) {
2316
+ // Single path = LineString
2317
+ return {
2318
+ type: 'LineString',
2319
+ coordinates: paths[0],
2320
+ };
2321
+ }
2322
+ else {
2323
+ // Multiple paths = MultiLineString
2324
+ return {
2325
+ type: 'MultiLineString',
2326
+ coordinates: paths,
2327
+ };
2328
+ }
2329
+ }
2330
+ // Default: return null for unknown geometry types
2331
+ return null;
2332
+ }
2333
+ }
2334
+ function find(options) {
2335
+ return new Find(options);
2336
+ }
2337
+
2338
+ /**
2339
+ * IdentifyFeatures task for performing identify operations against ArcGIS Map Services
2340
+ * Similar to Esri Leaflet's identifyFeatures functionality
2341
+ */
2342
+ class IdentifyFeatures extends Task {
2343
+ constructor(options) {
2344
+ // Handle different input types and convert to TaskOptions format
2345
+ let taskOptions;
2346
+ if (typeof options === 'string') {
2347
+ taskOptions = options;
2348
+ }
2349
+ else if (options instanceof Service) {
2350
+ // Extract URL from Service instance
2351
+ taskOptions = { url: options.options.url };
2352
+ }
2353
+ else {
2354
+ // It's IdentifyFeaturesOptions, use as TaskOptions
2355
+ taskOptions = options;
2356
+ }
2357
+ super(taskOptions);
2358
+ Object.defineProperty(this, "setters", {
2359
+ enumerable: true,
2360
+ configurable: true,
2361
+ writable: true,
2362
+ value: {
2363
+ layers: 'layers',
2364
+ precision: 'geometryPrecision',
2365
+ tolerance: 'tolerance',
2366
+ returnGeometry: 'returnGeometry',
2367
+ }
2368
+ });
2369
+ Object.defineProperty(this, "path", {
2370
+ enumerable: true,
2371
+ configurable: true,
2372
+ writable: true,
2373
+ value: '/identify'
2374
+ });
2375
+ Object.defineProperty(this, "params", {
2376
+ enumerable: true,
2377
+ configurable: true,
2378
+ writable: true,
2379
+ value: {
2380
+ sr: 4326,
2381
+ layers: 'all',
2382
+ tolerance: 3,
2383
+ returnGeometry: true,
2384
+ f: 'json',
2385
+ }
2386
+ });
2387
+ this.path = '/identify';
2388
+ // Ensure dynamic setters are available even though subclass fields
2389
+ // are initialized after super() runs. This rebinds setter methods.
2390
+ if (this.setters) {
2391
+ for (const [method, param] of Object.entries(this.setters)) {
2392
+ this[method] = this.generateSetter(param, this);
2393
+ }
2394
+ }
2395
+ }
2396
+ /**
2397
+ * Perform identify operation at a point location
2398
+ */
2399
+ at(point) {
2400
+ let geometry;
2401
+ if (Array.isArray(point)) {
2402
+ geometry = {
2403
+ x: point[0],
2404
+ y: point[1],
2405
+ spatialReference: { wkid: 4326 },
2406
+ };
2407
+ }
2408
+ else {
2409
+ geometry = {
2410
+ x: point.lng,
2411
+ y: point.lat,
2412
+ spatialReference: { wkid: 4326 },
2413
+ };
2414
+ }
2415
+ this.params.geometry = JSON.stringify(geometry);
2416
+ this.params.geometryType = 'esriGeometryPoint';
2417
+ return this;
2418
+ }
2419
+ // Strongly-typed chainable setters for common Identify params
2420
+ layers(value) {
2421
+ this.params.layers = value;
2422
+ return this;
2423
+ }
2424
+ tolerance(value) {
2425
+ this.params.tolerance = value;
2426
+ return this;
2427
+ }
2428
+ returnGeometry(value) {
2429
+ this.params.returnGeometry = value;
2430
+ return this;
2431
+ }
2432
+ precision(value) {
2433
+ this.params.geometryPrecision = value;
2434
+ return this;
2435
+ }
2436
+ /**
2437
+ * Set the map extent and image display for the identify operation
2438
+ */
2439
+ on(map) {
2440
+ try {
2441
+ const bounds = map.getBounds().toArray();
2442
+ this.params.mapExtent = [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]].join(',');
2443
+ const canvas = map.getCanvas();
2444
+ this.params.imageDisplay = [canvas.width, canvas.height, 96].join(',');
2445
+ }
2446
+ catch (error) {
2447
+ console.warn('Could not extract map extent and display info:', error);
2448
+ }
2449
+ return this;
2450
+ }
2451
+ /**
2452
+ * Set layer definitions for filtering specific layers
2453
+ */
2454
+ layerDef(layerId, whereClause) {
2455
+ const currentLayerDefs = this.params.layerDefs || '';
2456
+ this.params.layerDefs = currentLayerDefs
2457
+ ? `${currentLayerDefs};${layerId}:${whereClause}`
2458
+ : `${layerId}:${whereClause}`;
2459
+ return this;
2460
+ }
2461
+ /**
2462
+ * Simplify geometries based on map resolution
2463
+ */
2464
+ simplify(map, factor) {
2465
+ const bounds = map.getBounds();
2466
+ const mapWidth = Math.abs(bounds.getWest() - bounds.getEast());
2467
+ this.params.maxAllowableOffset = (mapWidth / map.getSize().x) * factor;
2468
+ return this;
2469
+ }
2470
+ /**
2471
+ * Execute the identify operation
2472
+ */
2473
+ async run() {
2474
+ try {
2475
+ const response = await this.request();
2476
+ return this._convertToGeoJSON(response);
2477
+ }
2478
+ catch (error) {
2479
+ const isTestEnvironment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test';
2480
+ if (!isTestEnvironment) {
2481
+ console.error('IdentifyFeatures error:', error);
2482
+ }
2483
+ throw error;
2484
+ }
2485
+ }
2486
+ _convertToGeoJSON(response) {
2487
+ const features = (response.results || []).map(result => {
2488
+ const feature = {
2489
+ type: 'Feature',
2490
+ properties: {
2491
+ ...result.attributes,
2492
+ layerId: result.layerId,
2493
+ layerName: result.layerName,
2494
+ displayFieldName: result.displayFieldName,
2495
+ value: result.value,
2496
+ },
2497
+ geometry: result.geometry || null,
2498
+ };
2499
+ // Add layerId as a custom property for easier identification
2500
+ const featureWithLayerId = feature;
2501
+ featureWithLayerId.layerId = result.layerId;
2502
+ return feature;
2503
+ });
2504
+ return {
2505
+ type: 'FeatureCollection',
2506
+ features,
2507
+ };
2508
+ }
2509
+ }
2510
+
2511
+ /**
2512
+ * IdentifyImage task for identifying pixel values in ArcGIS Image Services
2513
+ * Based on Esri Leaflet's IdentifyImage functionality
2514
+ */
2515
+ class IdentifyImage extends Task {
2516
+ constructor(options) {
2517
+ super(options);
2518
+ Object.defineProperty(this, "setters", {
2519
+ enumerable: true,
2520
+ configurable: true,
2521
+ writable: true,
2522
+ value: {
2523
+ returnCatalogItems: 'returnCatalogItems',
2524
+ returnGeometry: 'returnGeometry',
2525
+ pixelSize: 'pixelSize',
2526
+ token: 'token',
2527
+ }
2528
+ });
2529
+ Object.defineProperty(this, "path", {
2530
+ enumerable: true,
2531
+ configurable: true,
2532
+ writable: true,
2533
+ value: 'identify'
2534
+ });
2535
+ Object.defineProperty(this, "params", {
2536
+ enumerable: true,
2537
+ configurable: true,
2538
+ writable: true,
2539
+ value: {
2540
+ sr: 4326,
2541
+ returnGeometry: false,
2542
+ returnCatalogItems: false,
2543
+ f: 'json',
2544
+ }
2545
+ });
2546
+ // If options is a IdentifyImageOptions object, merge relevant properties into params
2547
+ if (options && typeof options === 'object' && typeof options !== 'string') {
2548
+ const imageOptions = options;
2549
+ // Merge identify-specific options into params
2550
+ if (imageOptions.geometry !== undefined)
2551
+ this.params.geometry = imageOptions.geometry;
2552
+ if (imageOptions.geometryType !== undefined)
2553
+ this.params.geometryType = imageOptions.geometryType;
2554
+ if (imageOptions.sr !== undefined)
2555
+ this.params.sr = imageOptions.sr;
2556
+ if (imageOptions.mosaic !== undefined)
2557
+ this.params.mosaic = imageOptions.mosaic;
2558
+ if (imageOptions.renderingRules !== undefined)
2559
+ this.params.renderingRules = imageOptions.renderingRules;
2560
+ if (imageOptions.pixelSize !== undefined)
2561
+ this.params.pixelSize = imageOptions.pixelSize;
2562
+ if (imageOptions.returnGeometry !== undefined)
2563
+ this.params.returnGeometry = imageOptions.returnGeometry;
2564
+ if (imageOptions.returnCatalogItems !== undefined)
2565
+ this.params.returnCatalogItems = imageOptions.returnCatalogItems;
2566
+ if (imageOptions.token !== undefined)
2567
+ this.params.token = imageOptions.token;
2568
+ if (imageOptions.f !== undefined)
2569
+ this.params.f = imageOptions.f;
2570
+ }
2571
+ }
2572
+ /**
2573
+ * Identify pixel values at a specific point
2574
+ */
2575
+ at(point) {
2576
+ let geometry;
2577
+ if (Array.isArray(point)) {
2578
+ geometry = {
2579
+ x: point[0],
2580
+ y: point[1],
2581
+ spatialReference: { wkid: 4326 },
2582
+ };
2583
+ }
2584
+ else {
2585
+ geometry = {
2586
+ x: point.lng,
2587
+ y: point.lat,
2588
+ spatialReference: { wkid: 4326 },
2589
+ };
2590
+ }
2591
+ this.params.geometry = JSON.stringify(geometry);
2592
+ this.params.geometryType = 'esriGeometryPoint';
2593
+ this.params.sr = 4326;
2594
+ return this;
2595
+ }
2596
+ /**
2597
+ * Set custom geometry for identification
2598
+ */
2599
+ geometry(geometry, geometryType) {
2600
+ this.params.geometry = JSON.stringify(geometry);
2601
+ this.params.geometryType = geometryType || 'esriGeometryPoint';
2602
+ return this;
2603
+ }
2604
+ /**
2605
+ * Set pixel size for the identify operation
2606
+ */
2607
+ pixelSize(size) {
2608
+ if (Array.isArray(size)) {
2609
+ this.params.pixelSize = `${size[0]},${size[1]}`;
2610
+ }
2611
+ else {
2612
+ this.params.pixelSize = `${size.x},${size.y}`;
2613
+ }
2614
+ return this;
2615
+ }
2616
+ /**
2617
+ * Set whether to return geometry with results
2618
+ */
2619
+ returnGeometry(returnGeom) {
2620
+ this.params.returnGeometry = returnGeom;
2621
+ return this;
2622
+ }
2623
+ /**
2624
+ * Set whether to return catalog items
2625
+ */
2626
+ returnCatalogItems(returnItems) {
2627
+ this.params.returnCatalogItems = returnItems;
2628
+ return this;
2629
+ }
2630
+ /**
2631
+ * Set mosaic rule for the identify operation
2632
+ */
2633
+ mosaicRule(rule) {
2634
+ this.params.mosaicRule = JSON.stringify(rule);
2635
+ return this;
2636
+ }
2637
+ /**
2638
+ * Set rendering rules for the identify operation
2639
+ */
2640
+ renderingRule(rule) {
2641
+ this.params.renderingRule = JSON.stringify(rule);
2642
+ return this;
2643
+ }
2644
+ /**
2645
+ * Execute the identify operation
2646
+ */
2647
+ async run() {
2648
+ const response = await this.request();
2649
+ // Normalize different response formats
2650
+ let results = [];
2651
+ if (response.results) {
2652
+ results = response.results;
2653
+ }
2654
+ else if (response.value !== undefined || response.values) {
2655
+ // Handle simple value responses
2656
+ results = [
2657
+ {
2658
+ value: response.value || (response.values && response.values[0]) || '',
2659
+ attributes: response.properties || {},
2660
+ },
2661
+ ];
2662
+ }
2663
+ return {
2664
+ results,
2665
+ location: response.location,
2666
+ };
2667
+ }
2668
+ /**
2669
+ * Execute and return pixel values as a simple array
2670
+ */
2671
+ async getPixelValues() {
2672
+ const response = await this.run();
2673
+ return response.results.map(result => {
2674
+ if (result.value !== undefined) {
2675
+ const numValue = parseFloat(result.value);
2676
+ return isNaN(numValue) ? result.value : numValue;
2677
+ }
2678
+ return null;
2679
+ });
2680
+ }
2681
+ /**
2682
+ * Execute and return detailed pixel information
2683
+ */
2684
+ async getPixelData() {
2685
+ const response = await this.run();
2686
+ return response.results;
2687
+ }
2688
+ }
2689
+ function identifyImage(options) {
2690
+ return new IdentifyImage(options);
2691
+ }
2692
+
2693
+ export { DynamicMapService, FeatureService, Find, IdentifyFeatures, IdentifyImage, ImageService, Query, Service, Task, TiledMapService, VectorBasemapStyle, VectorTileService, cleanTrailingSlash, find, getServiceDetails, identifyImage, query, updateAttribution };
2694
+ //# sourceMappingURL=esri-gl.esm.js.map