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