@loaders.gl/arcgis 5.0.0-alpha.8

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.
Files changed (119) hide show
  1. package/README.md +26 -0
  2. package/dist/arcgis/arcgis-capability-graph.d.ts +50 -0
  3. package/dist/arcgis/arcgis-capability-graph.d.ts.map +1 -0
  4. package/dist/arcgis/arcgis-capability-graph.js +221 -0
  5. package/dist/arcgis/arcgis-capability-graph.js.map +1 -0
  6. package/dist/arcgis/arcgis-feature-query.d.ts +53 -0
  7. package/dist/arcgis/arcgis-feature-query.d.ts.map +1 -0
  8. package/dist/arcgis/arcgis-feature-query.js +381 -0
  9. package/dist/arcgis/arcgis-feature-query.js.map +1 -0
  10. package/dist/arcgis/arcgis-feature-server-source-loader-types.d.ts +33 -0
  11. package/dist/arcgis/arcgis-feature-server-source-loader-types.d.ts.map +1 -0
  12. package/dist/arcgis/arcgis-feature-server-source-loader-types.js +21 -0
  13. package/dist/arcgis/arcgis-feature-server-source-loader-types.js.map +1 -0
  14. package/dist/arcgis/arcgis-feature-server-source-loader.d.ts +84 -0
  15. package/dist/arcgis/arcgis-feature-server-source-loader.d.ts.map +1 -0
  16. package/dist/arcgis/arcgis-feature-server-source-loader.js +280 -0
  17. package/dist/arcgis/arcgis-feature-server-source-loader.js.map +1 -0
  18. package/dist/arcgis/arcgis-image-server-source-loader-types.d.ts +31 -0
  19. package/dist/arcgis/arcgis-image-server-source-loader-types.d.ts.map +1 -0
  20. package/dist/arcgis/arcgis-image-server-source-loader-types.js +21 -0
  21. package/dist/arcgis/arcgis-image-server-source-loader-types.js.map +1 -0
  22. package/dist/arcgis/arcgis-image-server-source-loader.d.ts +60 -0
  23. package/dist/arcgis/arcgis-image-server-source-loader.d.ts.map +1 -0
  24. package/dist/arcgis/arcgis-image-server-source-loader.js +130 -0
  25. package/dist/arcgis/arcgis-image-server-source-loader.js.map +1 -0
  26. package/dist/arcgis/arcgis-image-tile-source-loader-types.d.ts +31 -0
  27. package/dist/arcgis/arcgis-image-tile-source-loader-types.d.ts.map +1 -0
  28. package/dist/arcgis/arcgis-image-tile-source-loader-types.js +21 -0
  29. package/dist/arcgis/arcgis-image-tile-source-loader-types.js.map +1 -0
  30. package/dist/arcgis/arcgis-image-tile-source-loader.d.ts +63 -0
  31. package/dist/arcgis/arcgis-image-tile-source-loader.d.ts.map +1 -0
  32. package/dist/arcgis/arcgis-image-tile-source-loader.js +147 -0
  33. package/dist/arcgis/arcgis-image-tile-source-loader.js.map +1 -0
  34. package/dist/arcgis/arcgis-map-tile-source-loader-types.d.ts +31 -0
  35. package/dist/arcgis/arcgis-map-tile-source-loader-types.d.ts.map +1 -0
  36. package/dist/arcgis/arcgis-map-tile-source-loader-types.js +21 -0
  37. package/dist/arcgis/arcgis-map-tile-source-loader-types.js.map +1 -0
  38. package/dist/arcgis/arcgis-map-tile-source-loader.d.ts +62 -0
  39. package/dist/arcgis/arcgis-map-tile-source-loader.d.ts.map +1 -0
  40. package/dist/arcgis/arcgis-map-tile-source-loader.js +196 -0
  41. package/dist/arcgis/arcgis-map-tile-source-loader.js.map +1 -0
  42. package/dist/arcgis/arcgis-scene-aggregation.d.ts +34 -0
  43. package/dist/arcgis/arcgis-scene-aggregation.d.ts.map +1 -0
  44. package/dist/arcgis/arcgis-scene-aggregation.js +57 -0
  45. package/dist/arcgis/arcgis-scene-aggregation.js.map +1 -0
  46. package/dist/arcgis/arcgis-scene-server-source-loader-types.d.ts +31 -0
  47. package/dist/arcgis/arcgis-scene-server-source-loader-types.d.ts.map +1 -0
  48. package/dist/arcgis/arcgis-scene-server-source-loader-types.js +21 -0
  49. package/dist/arcgis/arcgis-scene-server-source-loader-types.js.map +1 -0
  50. package/dist/arcgis/arcgis-scene-server-source-loader.d.ts +64 -0
  51. package/dist/arcgis/arcgis-scene-server-source-loader.d.ts.map +1 -0
  52. package/dist/arcgis/arcgis-scene-server-source-loader.js +159 -0
  53. package/dist/arcgis/arcgis-scene-server-source-loader.js.map +1 -0
  54. package/dist/arcgis/arcgis-server.d.ts +15 -0
  55. package/dist/arcgis/arcgis-server.d.ts.map +1 -0
  56. package/dist/arcgis/arcgis-server.js +41 -0
  57. package/dist/arcgis/arcgis-server.js.map +1 -0
  58. package/dist/arcgis/arcgis-url-utils.d.ts +3 -0
  59. package/dist/arcgis/arcgis-url-utils.d.ts.map +1 -0
  60. package/dist/arcgis/arcgis-url-utils.js +23 -0
  61. package/dist/arcgis/arcgis-url-utils.js.map +1 -0
  62. package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.d.ts +31 -0
  63. package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.d.ts.map +1 -0
  64. package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.js +21 -0
  65. package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.js.map +1 -0
  66. package/dist/arcgis/arcgis-vector-tile-server-source-loader.d.ts +66 -0
  67. package/dist/arcgis/arcgis-vector-tile-server-source-loader.d.ts.map +1 -0
  68. package/dist/arcgis/arcgis-vector-tile-server-source-loader.js +150 -0
  69. package/dist/arcgis/arcgis-vector-tile-server-source-loader.js.map +1 -0
  70. package/dist/arcgis-feature-query-types.d.ts +77 -0
  71. package/dist/arcgis-feature-query-types.d.ts.map +1 -0
  72. package/dist/arcgis-feature-query-types.js +5 -0
  73. package/dist/arcgis-feature-query-types.js.map +1 -0
  74. package/dist/arcgis-source-types.d.ts +372 -0
  75. package/dist/arcgis-source-types.d.ts.map +1 -0
  76. package/dist/arcgis-source-types.js +108 -0
  77. package/dist/arcgis-source-types.js.map +1 -0
  78. package/dist/authentication.d.ts +19 -0
  79. package/dist/authentication.d.ts.map +1 -0
  80. package/dist/authentication.js +24 -0
  81. package/dist/authentication.js.map +1 -0
  82. package/dist/bundled.d.ts +151 -0
  83. package/dist/bundled.d.ts.map +1 -0
  84. package/dist/bundled.js +33 -0
  85. package/dist/bundled.js.map +1 -0
  86. package/dist/index.cjs +24932 -0
  87. package/dist/index.cjs.map +6 -0
  88. package/dist/index.d.ts +23 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +11 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/service-registry.d.ts +18 -0
  93. package/dist/service-registry.d.ts.map +1 -0
  94. package/dist/service-registry.js +29 -0
  95. package/dist/service-registry.js.map +1 -0
  96. package/package.json +91 -0
  97. package/src/arcgis/arcgis-capability-graph.ts +297 -0
  98. package/src/arcgis/arcgis-feature-query.ts +442 -0
  99. package/src/arcgis/arcgis-feature-server-source-loader-types.ts +34 -0
  100. package/src/arcgis/arcgis-feature-server-source-loader.ts +373 -0
  101. package/src/arcgis/arcgis-image-server-source-loader-types.ts +34 -0
  102. package/src/arcgis/arcgis-image-server-source-loader.ts +186 -0
  103. package/src/arcgis/arcgis-image-tile-source-loader-types.ts +34 -0
  104. package/src/arcgis/arcgis-image-tile-source-loader.ts +192 -0
  105. package/src/arcgis/arcgis-map-tile-source-loader-types.ts +34 -0
  106. package/src/arcgis/arcgis-map-tile-source-loader.ts +248 -0
  107. package/src/arcgis/arcgis-scene-aggregation.ts +90 -0
  108. package/src/arcgis/arcgis-scene-server-source-loader-types.ts +36 -0
  109. package/src/arcgis/arcgis-scene-server-source-loader.ts +216 -0
  110. package/src/arcgis/arcgis-server.ts +61 -0
  111. package/src/arcgis/arcgis-url-utils.ts +26 -0
  112. package/src/arcgis/arcgis-vector-tile-server-source-loader-types.ts +36 -0
  113. package/src/arcgis/arcgis-vector-tile-server-source-loader.ts +196 -0
  114. package/src/arcgis-feature-query-types.ts +89 -0
  115. package/src/arcgis-source-types.ts +372 -0
  116. package/src/authentication.ts +35 -0
  117. package/src/bundled.ts +66 -0
  118. package/src/index.ts +63 -0
  119. package/src/service-registry.ts +43 -0
@@ -0,0 +1,373 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {ArcGISAuthentication} from '../authentication';
6
+ import {ARCGIS_FEATURE_SERVER_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
7
+ import type {
8
+ ArcGISFeatureServiceQueryOptions,
9
+ ArcGISFeatureServerSourceLoaderOptions
10
+ } from '../arcgis-source-types';
11
+
12
+ import type {DataType, Schema} from '@loaders.gl/schema';
13
+ import {
14
+ convertFeaturesToWKBArrowTable,
15
+ convertGeojsonToBinaryFeatureCollection
16
+ } from '@loaders.gl/gis';
17
+ import type {
18
+ CoreAPI,
19
+ VectorSourceMetadata,
20
+ GetFeaturesParameters,
21
+ VectorSource,
22
+ VectorSourceData
23
+ } from '@loaders.gl/loader-utils';
24
+ import type {SourceLoader} from '@loaders.gl/loader-utils';
25
+ import {DataSource} from '@loaders.gl/loader-utils';
26
+ import {buildArcGISResourceURL} from './arcgis-url-utils';
27
+ import {ArcGISFeatureQueryClient} from './arcgis-feature-query';
28
+ import type {
29
+ ArcGISFeatureQueryParameters,
30
+ ArcGISFeatureQueryOptions,
31
+ ArcGISFeatureQueryResult,
32
+ ArcGISFeatureObjectIds,
33
+ ArcGISFeatureExtent
34
+ } from '../arcgis-feature-query-types';
35
+ export {ArcGISFeatureQueryError} from './arcgis-feature-query';
36
+
37
+ /** Runtime service loader for synchronous construction. */
38
+ export const ArcGISFeatureServerSourceLoaderWithParser = {
39
+ ...ARCGIS_FEATURE_SERVER_SOURCE_LOADER_METADATA,
40
+ dataType: null as unknown as ArcGISVectorSource,
41
+ batchType: null as never,
42
+ preload: undefined,
43
+ /** Supplies ArcGIS credentials after loading the selected implementation. */
44
+ getAuthentications: () => [ArcGISAuthentication],
45
+ createDataSource: (
46
+ url: string,
47
+ options: ArcGISFeatureServerSourceLoaderOptions,
48
+ coreApi?: CoreAPI
49
+ ): ArcGISVectorSource => new ArcGISVectorSource(url, options, coreApi)
50
+ } as const satisfies SourceLoader<ArcGISVectorSource>;
51
+
52
+ /**
53
+ * ArcGIS FeatureServer
54
+ * Note - exports a big API, that could be exposed here if there is a use case
55
+ * @see https://developers.arcgis.com/rest/services-reference/enterprise/feature-service.htm
56
+ */
57
+ export class ArcGISVectorSource
58
+ extends DataSource<string, ArcGISFeatureServerSourceLoaderOptions>
59
+ implements VectorSource
60
+ {
61
+ /** Cached ArcGIS FeatureServer metadata request. */
62
+ protected formatSpecificMetadata: Promise<any> | null = null;
63
+
64
+ /** Creates a query source for a FeatureServer or queryable MapServer layer. */
65
+ constructor(url: string, options: ArcGISFeatureServerSourceLoaderOptions, coreApi?: CoreAPI) {
66
+ super(url, options, ARCGIS_FEATURE_SERVER_SOURCE_LOADER_METADATA.defaultOptions, coreApi);
67
+ }
68
+
69
+ /** Returns a schema inferred from ArcGIS FeatureServer metadata fields. */
70
+ async getSchema(): Promise<Schema> {
71
+ const metadata = await this.getFormatSpecificMetadata();
72
+ return parseArcGISFeatureServerSchema(metadata);
73
+ }
74
+
75
+ /** Returns normalized VectorSource metadata. */
76
+ async getMetadata(
77
+ options: {formatSpecificMetadata?: boolean} = {}
78
+ ): Promise<VectorSourceMetadata> {
79
+ // Wait for raw metadata to load
80
+ const formatSpecificMetadata = await this.getFormatSpecificMetadata();
81
+
82
+ const metadata = parseArcGISFeatureServerMetadata(formatSpecificMetadata);
83
+
84
+ // Only add the big blob of source metadata if explicitly requested
85
+ if (options.formatSpecificMetadata) {
86
+ metadata.formatSpecificMetadata = formatSpecificMetadata;
87
+ }
88
+ return metadata;
89
+ }
90
+
91
+ /** Retrieves the complete filtered query, rejecting partial results before format conversion. */
92
+ async getFeatures(parameters: GetFeaturesParameters): Promise<VectorSourceData> {
93
+ const result = await this.queryFeatures(parameters);
94
+ if (!result.complete) {
95
+ throw new Error(
96
+ `Incomplete ArcGIS query (${result.reason}): ${result.loaded}/${result.expectedCount}. Use queryFeatures() to inspect partial results.`
97
+ );
98
+ }
99
+ const features = result.data.features;
100
+ switch (parameters.format || 'arrow') {
101
+ case 'binary':
102
+ if (features.some(feature => !feature.geometry)) {
103
+ throw new Error('Binary output cannot retain null geometries; use geojson or arrow');
104
+ }
105
+ return convertGeojsonToBinaryFeatureCollection(features);
106
+ case 'geojson':
107
+ return result.data;
108
+ default:
109
+ return convertFeaturesToWKBArrowTable(features, {
110
+ encodingPreference: parameters.geoarrow?.encodingPreference
111
+ });
112
+ }
113
+ }
114
+
115
+ /** Collects a bounded query and returns explicit completeness evidence alongside its data. */
116
+ async queryFeatures(
117
+ parameters: ArcGISFeatureQueryOptions = {}
118
+ ): Promise<ArcGISFeatureQueryResult> {
119
+ let result: ArcGISFeatureQueryResult | undefined;
120
+ const features: ArcGISFeatureQueryResult['data']['features'] = [];
121
+ for await (const page of this.queryFeaturePages(parameters)) {
122
+ for (const feature of page.data.features) features.push(feature);
123
+ result = page;
124
+ }
125
+ return {...result!, data: {shape: 'geojson-table', type: 'FeatureCollection', features}};
126
+ }
127
+
128
+ /** Streams unique records with cumulative progress; breaking iteration stops subsequent work. */
129
+ queryFeaturePages(
130
+ parameters: ArcGISFeatureQueryOptions = {}
131
+ ): AsyncGenerator<ArcGISFeatureQueryResult> {
132
+ return this.createQueryClient(parameters).getPages(parameters);
133
+ }
134
+
135
+ /** Requests one page, explicitly marked incomplete regardless of the server transfer flag. */
136
+ queryFeaturePage(parameters: ArcGISFeatureQueryOptions = {}): Promise<ArcGISFeatureQueryResult> {
137
+ return this.createQueryClient(parameters).getPage(parameters);
138
+ }
139
+
140
+ /** Returns the current count for the selected query filters. */
141
+ queryCount(parameters: ArcGISFeatureQueryParameters = {}): Promise<number> {
142
+ return this.createQueryClient(parameters).getCount();
143
+ }
144
+
145
+ /** Returns object IDs with truncation information; use queryFeatures() for complete retrieval. */
146
+ queryObjectIds(parameters: ArcGISFeatureQueryParameters = {}): Promise<ArcGISFeatureObjectIds> {
147
+ return this.createQueryClient(parameters).getObjectIds();
148
+ }
149
+
150
+ /** Returns query bounds and their explicit spatial reference. */
151
+ queryExtent(parameters: ArcGISFeatureQueryParameters = {}): Promise<ArcGISFeatureExtent> {
152
+ return this.createQueryClient(parameters).getExtent();
153
+ }
154
+
155
+ /** Shares transport and query filters across summary and data operations. */
156
+ private createQueryClient(parameters: ArcGISFeatureQueryParameters): ArcGISFeatureQueryClient {
157
+ return new ArcGISFeatureQueryClient(
158
+ this.getLayerURL(parameters),
159
+ this.fetch.bind(this),
160
+ this.getQueryParameters(parameters),
161
+ parameters.signal
162
+ );
163
+ }
164
+
165
+ /** Requests the raw ArcGIS FeatureServer metadata document. */
166
+ protected async _getFormatSpecificMetadata() {
167
+ // PJSON is formatted by a bit slower than JSON
168
+ const url = this.metadataURL();
169
+ return await new ArcGISFeatureQueryClient(url, this.fetch.bind(this), {}).request('');
170
+ }
171
+
172
+ /** Loads and caches the raw ArcGIS FeatureServer metadata. */
173
+ protected async getFormatSpecificMetadata(): Promise<any> {
174
+ this.formatSpecificMetadata ||= this._getFormatSpecificMetadata().catch(error => {
175
+ this.formatSpecificMetadata = null;
176
+ throw error;
177
+ });
178
+ return await this.formatSpecificMetadata;
179
+ }
180
+
181
+ /** Builds a metadata URL for the ArcGIS FeatureServer endpoint. */
182
+ metadataURL(options?: {parameters?: Record<string, unknown>}): string {
183
+ return this.getUrl('', {f: 'pjson', ...options?.parameters});
184
+ }
185
+
186
+ /** Builds a query URL from generic vector source parameters. */
187
+ getFeaturesURL(parameters: ArcGISFeatureQueryParameters): string {
188
+ return buildArcGISResourceURL(
189
+ this.getLayerURL(parameters),
190
+ 'query',
191
+ this.getQueryParameters(parameters)
192
+ );
193
+ }
194
+
195
+ /** Selects exactly one layer; service roots must not silently query an unspecified layer. */
196
+ private getLayerURL(parameters: ArcGISFeatureQueryParameters): string {
197
+ const url = new URL(this.url);
198
+ const layerIdentifiers = Array.isArray(parameters.layers)
199
+ ? parameters.layers
200
+ : parameters.layers === undefined
201
+ ? []
202
+ : [parameters.layers];
203
+ if (layerIdentifiers.length > 1)
204
+ throw new Error('ArcGIS feature queries require exactly one layer');
205
+ const match = /\/(?:FeatureServer|MapServer)(?:\/(\d+))?\/?$/i.exec(url.pathname);
206
+ if (!match) throw new Error('Expected an ArcGIS FeatureServer or MapServer layer URL');
207
+ const layer = layerIdentifiers[0];
208
+ if (match[1]) {
209
+ if (layer !== undefined && String(layer) !== match[1])
210
+ throw new Error('Layer selection conflicts with the endpoint URL');
211
+ return url.toString();
212
+ }
213
+ if (layer === undefined || !/^\d+$/.test(String(layer)))
214
+ throw new Error('Select a layer ID when querying an ArcGIS service root');
215
+ return buildArcGISResourceURL(this.url, String(layer), {});
216
+ }
217
+
218
+ /** Applies source defaults and per-request filters consistently across all query operations. */
219
+ private getQueryParameters(
220
+ parameters: ArcGISFeatureQueryParameters
221
+ ): ArcGISFeatureServiceQueryOptions {
222
+ const defaultParameters = this.options['arcgis-feature-server']?.queryParameters || {};
223
+ const outputSpatialReference = normalizeArcGISSpatialReference(parameters.crs) || 4326;
224
+ const requestSpatialReference =
225
+ normalizeArcGISSpatialReference(parameters.requestCrs) || outputSpatialReference;
226
+ const queryParameters: ArcGISFeatureServiceQueryOptions = {
227
+ returnGeometry: true,
228
+ where: '1=1',
229
+ outFields: '*',
230
+ outSR: outputSpatialReference,
231
+ inSR: requestSpatialReference,
232
+ f: 'geojson',
233
+ ...defaultParameters,
234
+ ...parameters.query
235
+ };
236
+ if (parameters.crs !== undefined) queryParameters.outSR = outputSpatialReference;
237
+ if (parameters.requestCrs !== undefined) queryParameters.inSR = requestSpatialReference;
238
+ if (parameters.boundingBox) {
239
+ const coordinates = parameters.boundingBox.flat();
240
+ if (
241
+ !coordinates.every(Number.isFinite) ||
242
+ coordinates[0] > coordinates[2] ||
243
+ coordinates[1] > coordinates[3]
244
+ ) {
245
+ throw new Error(
246
+ 'ArcGIS boundingBox must contain finite ordered coordinates; split antimeridian queries'
247
+ );
248
+ }
249
+ queryParameters.geometry = coordinates.join(',');
250
+ queryParameters.geometryType = 'esriGeometryEnvelope';
251
+ queryParameters.spatialRel ||= 'esriSpatialRelIntersects';
252
+ }
253
+ return queryParameters;
254
+ }
255
+
256
+ /** Builds an ArcGIS FeatureServer URL. */
257
+ protected getUrl(
258
+ path: string,
259
+ options: Record<string, unknown>,
260
+ extra?: Record<string, unknown>
261
+ ): string {
262
+ return buildArcGISResourceURL(this.url, path, {...options, ...extra});
263
+ }
264
+ }
265
+
266
+ /** Normalizes layer and nonspatial table metadata. */
267
+ function parseArcGISFeatureServerMetadata(json: any): VectorSourceMetadata {
268
+ const layers: VectorSourceMetadata['layers'] = [];
269
+ for (const layer of [...(json.layers || []), ...(json.tables || [])]) {
270
+ const extent = layer.extent;
271
+ const spatialReference = layer.spatialReference || extent?.spatialReference;
272
+ layers.push({
273
+ name: layer.id === undefined ? layer.name : String(layer.id),
274
+ title: layer.name,
275
+ crs: getArcGISCoordinateReferenceSystems(spatialReference),
276
+ boundingBox: normalizeArcGISExtent(extent)
277
+ });
278
+ }
279
+
280
+ if (!layers.length && (json.id !== undefined || json.name)) {
281
+ const extent = json.extent;
282
+ layers.push({
283
+ name: json.id === undefined ? json.name : String(json.id),
284
+ title: json.name,
285
+ crs: getArcGISCoordinateReferenceSystems(json.spatialReference || extent?.spatialReference),
286
+ boundingBox: normalizeArcGISExtent(extent)
287
+ });
288
+ }
289
+
290
+ return {
291
+ // version: json.currentVersion || '',
292
+ title: json.serviceDescription || '',
293
+ name: json.serviceDescription || '',
294
+ abstract: json.description || '',
295
+ keywords: [],
296
+ // attrribution: json.copyrightText || ''.
297
+ // crs: 'EPSG:4326',
298
+ layers
299
+ };
300
+ }
301
+
302
+ /** Normalizes an ArcGIS spatial reference to a one-element CRS list. */
303
+ function getArcGISCoordinateReferenceSystems(
304
+ spatialReference: {wkid?: number; latestWkid?: number} | undefined
305
+ ): string[] | undefined {
306
+ const wellKnownIdentifier = spatialReference?.latestWkid || spatialReference?.wkid;
307
+ return wellKnownIdentifier ? [`EPSG:${wellKnownIdentifier}`] : undefined;
308
+ }
309
+
310
+ /** Normalizes an ArcGIS extent to the loaders.gl two-corner shape. */
311
+ function normalizeArcGISExtent(
312
+ extent: {xmin?: number; ymin?: number; xmax?: number; ymax?: number} | undefined
313
+ ): [[number, number], [number, number]] | undefined {
314
+ return extent && [extent.xmin, extent.ymin, extent.xmax, extent.ymax].every(Number.isFinite)
315
+ ? [
316
+ [extent.xmin!, extent.ymin!],
317
+ [extent.xmax!, extent.ymax!]
318
+ ]
319
+ : undefined;
320
+ }
321
+
322
+ /** Normalizes EPSG-prefixed CRS strings to ArcGIS WKID values. */
323
+ function normalizeArcGISSpatialReference(
324
+ spatialReference: string | number | undefined
325
+ ): string | number | undefined {
326
+ if (typeof spatialReference === 'string') {
327
+ const match = /^EPSG:(\d+)$/i.exec(spatialReference);
328
+ if (match) {
329
+ return match[1];
330
+ }
331
+ }
332
+ if (spatialReference !== undefined && !/^\d+$/.test(String(spatialReference))) {
333
+ throw new Error('ArcGIS query CRS must be a numeric WKID or EPSG identifier');
334
+ }
335
+ return spatialReference;
336
+ }
337
+
338
+ /** Builds a schema from ArcGIS FeatureServer metadata fields. */
339
+ function parseArcGISFeatureServerSchema(json: any): Schema {
340
+ const fields = Array.isArray(json.fields)
341
+ ? json.fields.map((field: any) => ({
342
+ name: field.name,
343
+ type:
344
+ field.type === 'esriFieldTypeOID' && field.length === 8
345
+ ? 'int64'
346
+ : getSchemaTypeFromArcGISFieldType(field.type),
347
+ nullable: field.nullable
348
+ }))
349
+ : [];
350
+
351
+ return {metadata: {}, fields};
352
+ }
353
+
354
+ /** Maps common ArcGIS field types to loaders.gl schema type strings. */
355
+ function getSchemaTypeFromArcGISFieldType(type: string): DataType {
356
+ switch (type) {
357
+ case 'esriFieldTypeDouble':
358
+ return 'float64';
359
+ case 'esriFieldTypeSingle':
360
+ return 'float32';
361
+ case 'esriFieldTypeSmallInteger':
362
+ return 'int16';
363
+ case 'esriFieldTypeBigInteger':
364
+ return 'int64';
365
+ case 'esriFieldTypeInteger':
366
+ case 'esriFieldTypeOID':
367
+ return 'int32';
368
+ case 'esriFieldTypeDate':
369
+ return 'timestamp-millisecond';
370
+ default:
371
+ return 'utf8';
372
+ }
373
+ }
@@ -0,0 +1,34 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {ARCGIS_IMAGE_SERVER_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
6
+ import type {CoreAPI, SourceLoader} from '@loaders.gl/loader-utils';
7
+ import type {ArcGISImageSource} from './arcgis-image-server-source-loader';
8
+ import type {ArcGISImageSourceLoaderProps} from '../arcgis-source-types';
9
+
10
+ /** Loads the ArcGISImageServerSourceLoader implementation on first asynchronous use. */
11
+ async function preloadArcGISImageServerSourceLoader(): Promise<SourceLoader<ArcGISImageSource>> {
12
+ const {ArcGISImageServerSourceLoaderWithParser} = await import(
13
+ '@loaders.gl/arcgis/arcgis-image-server-source-loader'
14
+ );
15
+ return ArcGISImageServerSourceLoaderWithParser;
16
+ }
17
+
18
+ /** Lightweight service descriptor; use load() or the synchronous /bundled entrypoint. */
19
+ export const ArcGISImageServerSourceLoader = {
20
+ ...ARCGIS_IMAGE_SERVER_SOURCE_LOADER_METADATA,
21
+ dataType: null as unknown as ArcGISImageSource,
22
+ batchType: null as never,
23
+ preload: preloadArcGISImageServerSourceLoader,
24
+ /** Requires the runtime loader when constructing a source synchronously. */
25
+ createDataSource(
26
+ _url: string,
27
+ _options: ArcGISImageSourceLoaderProps,
28
+ _coreApi?: CoreAPI
29
+ ): ArcGISImageSource {
30
+ throw new Error(
31
+ 'ArcGISImageServerSourceLoader requires async load() or an @loaders.gl/arcgis/bundled import'
32
+ );
33
+ }
34
+ } as const satisfies SourceLoader<ArcGISImageSource>;
@@ -0,0 +1,186 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {ArcGISAuthentication} from '../authentication';
6
+ import {ARCGIS_IMAGE_SERVER_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
7
+ import type {
8
+ ArcGISImageSourceLoaderProps,
9
+ ArcGISExportImageParameters
10
+ } from '../arcgis-source-types';
11
+
12
+ import type {ImageType} from '@loaders.gl/images';
13
+ import {ImageLoader} from '@loaders.gl/images';
14
+ import type {
15
+ CoreAPI,
16
+ SourceLoader,
17
+ ImageSource,
18
+ ImageSourceMetadata,
19
+ GetImageParameters
20
+ } from '@loaders.gl/loader-utils';
21
+ import {DataSource} from '@loaders.gl/loader-utils';
22
+ import type {LERCData} from '@loaders.gl/lerc';
23
+ import {LERCLoader} from '@loaders.gl/lerc';
24
+ import {buildArcGISResourceURL} from './arcgis-url-utils';
25
+
26
+ /** Runtime service loader for synchronous construction. */
27
+ export const ArcGISImageServerSourceLoaderWithParser = {
28
+ ...ARCGIS_IMAGE_SERVER_SOURCE_LOADER_METADATA,
29
+ dataType: null as unknown as ArcGISImageSource,
30
+ batchType: null as never,
31
+ preload: undefined,
32
+ /** Supplies ArcGIS credentials after loading the selected implementation. */
33
+ getAuthentications: () => [ArcGISAuthentication],
34
+ createDataSource: (
35
+ url: string,
36
+ props: ArcGISImageSourceLoaderProps,
37
+ coreApi?: CoreAPI
38
+ ): ArcGISImageSource => new ArcGISImageSource(url, props, coreApi)
39
+ } as const satisfies SourceLoader<ArcGISImageSource>;
40
+
41
+ /**
42
+ * ArcGIS ImageServer
43
+ * Note - exports a big API, that could be exposed here if there is a use case
44
+ * @see https://developers.arcgis.com/rest/services-reference/enterprise/image-service.htm
45
+ */
46
+ export class ArcGISImageSource
47
+ extends DataSource<string, ArcGISImageSourceLoaderProps>
48
+ implements ImageSource
49
+ {
50
+ constructor(url: string, props: ArcGISImageSourceLoaderProps, coreApi?: CoreAPI) {
51
+ super(url, props, ARCGIS_IMAGE_SERVER_SOURCE_LOADER_METADATA.defaultOptions, coreApi);
52
+ }
53
+
54
+ /** Returns normalized ImageSource metadata. */
55
+ async getMetadata(): Promise<ImageSourceMetadata> {
56
+ return normalizeArcGISImageServerMetadata(await this.metadata());
57
+ }
58
+
59
+ /** Requests an image from generic ImageSource parameters. */
60
+ async getImage(parameters: GetImageParameters): Promise<ImageType> {
61
+ const {boundingBox, width, height, crs, format, signal} = parameters;
62
+ const spatialReference = normalizeArcGISSpatialReference(crs) || '4326';
63
+ const imageParameters: ArcGISExportImageParameters = {
64
+ bbox: [...boundingBox[0], ...boundingBox[1]],
65
+ bboxSR: spatialReference,
66
+ imageSR: spatialReference,
67
+ width,
68
+ height,
69
+ format: format === 'image/png' ? 'png' : undefined
70
+ };
71
+ return await this.exportImage(imageParameters, signal);
72
+ }
73
+
74
+ /** Requests the ArcGIS ImageServer metadata document. */
75
+ async metadata(): Promise<unknown> {
76
+ const response = await this.fetch(this.metadataURL());
77
+ await this.checkResponse(response);
78
+ return await response.json();
79
+ }
80
+
81
+ /** Requests an exported image from the ArcGIS ImageServer endpoint. */
82
+ async exportImage(
83
+ options: ArcGISExportImageParameters,
84
+ signal?: AbortSignal
85
+ ): Promise<ImageType> {
86
+ const response = await this.fetch(this.exportImageURL(options), signal ? {signal} : undefined);
87
+ await this.checkResponse(response);
88
+ const arrayBuffer = await response.arrayBuffer();
89
+ return (await this.coreApi.parse(arrayBuffer, ImageLoader, this.loadOptions)) as ImageType;
90
+ }
91
+
92
+ /** Requests an analytical LERC raster from the ArcGIS ImageServer endpoint. */
93
+ async exportRaster(
94
+ options: ArcGISExportImageParameters,
95
+ signal?: AbortSignal
96
+ ): Promise<LERCData> {
97
+ const response = await this.fetch(
98
+ this.exportImageURL({...options, format: 'lerc'}),
99
+ signal ? {signal} : undefined
100
+ );
101
+ await this.checkResponse(response);
102
+ return (await this.coreApi.parse(
103
+ await response.arrayBuffer(),
104
+ LERCLoader,
105
+ this.loadOptions
106
+ )) as LERCData;
107
+ }
108
+
109
+ /** Builds a metadata URL for the ArcGIS ImageServer endpoint. */
110
+ metadataURL(options?: {parameters?: Record<string, unknown>}): string {
111
+ return this.getUrl('', {f: 'pjson', ...options?.parameters});
112
+ }
113
+
114
+ /** Builds an exportImage URL for the ArcGIS ImageServer endpoint. */
115
+ exportImageURL(options: ArcGISExportImageParameters): string {
116
+ const defaultParameters = this.options['arcgis-image-server']?.exportImageParameters || {};
117
+ const {width, height, ...parameters} = {...defaultParameters, ...options};
118
+ return this.getUrl('exportImage', {
119
+ ...parameters,
120
+ bbox: parameters.bbox,
121
+ size: [width, height],
122
+ f: parameters.f || 'image'
123
+ });
124
+ }
125
+
126
+ /** Builds an ArcGIS ImageServer URL. */
127
+ protected getUrl(
128
+ path: string,
129
+ options: Record<string, unknown>,
130
+ extra?: Record<string, unknown>
131
+ ): string {
132
+ return buildArcGISResourceURL(this.url, path, {...options, ...extra});
133
+ }
134
+
135
+ /** Checks an ArcGIS ImageServer response. */
136
+ protected async checkResponse(response: Response): Promise<void> {
137
+ if (!response.ok) {
138
+ throw new Error(
139
+ response.statusText || `ArcGIS ImageServer request failed: ${response.status}`
140
+ );
141
+ }
142
+ }
143
+ }
144
+
145
+ /** Normalizes EPSG-prefixed CRS strings to ArcGIS WKID values. */
146
+ function normalizeArcGISSpatialReference(
147
+ spatialReference: string | number | undefined
148
+ ): string | number | undefined {
149
+ if (typeof spatialReference === 'string') {
150
+ const match = /^EPSG:(\d+)$/i.exec(spatialReference);
151
+ if (match) {
152
+ return match[1];
153
+ }
154
+ }
155
+ return spatialReference;
156
+ }
157
+
158
+ /** Normalizes ArcGIS ImageServer metadata to the generic ImageSource metadata shape. */
159
+ function normalizeArcGISImageServerMetadata(metadata: unknown): ImageSourceMetadata {
160
+ const arcgisMetadata = metadata as any;
161
+ const extent = arcgisMetadata.fullExtent || arcgisMetadata.extent;
162
+ const spatialReference = arcgisMetadata.spatialReference || extent?.spatialReference;
163
+ const wellKnownIdentifier = spatialReference?.latestWkid || spatialReference?.wkid;
164
+ const boundingBox =
165
+ extent && [extent.xmin, extent.ymin, extent.xmax, extent.ymax].every(Number.isFinite)
166
+ ? ([
167
+ [extent.xmin, extent.ymin],
168
+ [extent.xmax, extent.ymax]
169
+ ] as [[number, number], [number, number]])
170
+ : undefined;
171
+ const name = arcgisMetadata.name || arcgisMetadata.serviceDescription || '';
172
+ return {
173
+ name,
174
+ title: name,
175
+ abstract: arcgisMetadata.description || arcgisMetadata.serviceDescription || '',
176
+ keywords: Array.isArray(arcgisMetadata.keywords) ? arcgisMetadata.keywords : [],
177
+ layers: [
178
+ {
179
+ name,
180
+ title: name,
181
+ crs: wellKnownIdentifier ? [`EPSG:${wellKnownIdentifier}`] : undefined,
182
+ boundingBox
183
+ }
184
+ ]
185
+ };
186
+ }
@@ -0,0 +1,34 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {ARCGIS_IMAGE_TILE_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
6
+ import type {CoreAPI, SourceLoader} from '@loaders.gl/loader-utils';
7
+ import type {ArcGISImageTileSource} from './arcgis-image-tile-source-loader';
8
+ import type {ArcGISImageTileSourceLoaderOptions} from '../arcgis-source-types';
9
+
10
+ /** Loads the ArcGISImageTileSourceLoader implementation on first asynchronous use. */
11
+ async function preloadArcGISImageTileSourceLoader(): Promise<SourceLoader<ArcGISImageTileSource>> {
12
+ const {ArcGISImageTileSourceLoaderWithParser} = await import(
13
+ '@loaders.gl/arcgis/arcgis-image-tile-source-loader'
14
+ );
15
+ return ArcGISImageTileSourceLoaderWithParser;
16
+ }
17
+
18
+ /** Lightweight service descriptor; use load() or the synchronous /bundled entrypoint. */
19
+ export const ArcGISImageTileSourceLoader = {
20
+ ...ARCGIS_IMAGE_TILE_SOURCE_LOADER_METADATA,
21
+ dataType: null as unknown as ArcGISImageTileSource,
22
+ batchType: null as never,
23
+ preload: preloadArcGISImageTileSourceLoader,
24
+ /** Requires the runtime loader when constructing a source synchronously. */
25
+ createDataSource(
26
+ _url: string,
27
+ _options: ArcGISImageTileSourceLoaderOptions,
28
+ _coreApi?: CoreAPI
29
+ ): ArcGISImageTileSource {
30
+ throw new Error(
31
+ 'ArcGISImageTileSourceLoader requires async load() or an @loaders.gl/arcgis/bundled import'
32
+ );
33
+ }
34
+ } as const satisfies SourceLoader<ArcGISImageTileSource>;