@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.
- package/README.md +26 -0
- package/dist/arcgis/arcgis-capability-graph.d.ts +50 -0
- package/dist/arcgis/arcgis-capability-graph.d.ts.map +1 -0
- package/dist/arcgis/arcgis-capability-graph.js +221 -0
- package/dist/arcgis/arcgis-capability-graph.js.map +1 -0
- package/dist/arcgis/arcgis-feature-query.d.ts +53 -0
- package/dist/arcgis/arcgis-feature-query.d.ts.map +1 -0
- package/dist/arcgis/arcgis-feature-query.js +381 -0
- package/dist/arcgis/arcgis-feature-query.js.map +1 -0
- package/dist/arcgis/arcgis-feature-server-source-loader-types.d.ts +33 -0
- package/dist/arcgis/arcgis-feature-server-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-feature-server-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-feature-server-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-feature-server-source-loader.d.ts +84 -0
- package/dist/arcgis/arcgis-feature-server-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-feature-server-source-loader.js +280 -0
- package/dist/arcgis/arcgis-feature-server-source-loader.js.map +1 -0
- package/dist/arcgis/arcgis-image-server-source-loader-types.d.ts +31 -0
- package/dist/arcgis/arcgis-image-server-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-image-server-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-image-server-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-image-server-source-loader.d.ts +60 -0
- package/dist/arcgis/arcgis-image-server-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-image-server-source-loader.js +130 -0
- package/dist/arcgis/arcgis-image-server-source-loader.js.map +1 -0
- package/dist/arcgis/arcgis-image-tile-source-loader-types.d.ts +31 -0
- package/dist/arcgis/arcgis-image-tile-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-image-tile-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-image-tile-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-image-tile-source-loader.d.ts +63 -0
- package/dist/arcgis/arcgis-image-tile-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-image-tile-source-loader.js +147 -0
- package/dist/arcgis/arcgis-image-tile-source-loader.js.map +1 -0
- package/dist/arcgis/arcgis-map-tile-source-loader-types.d.ts +31 -0
- package/dist/arcgis/arcgis-map-tile-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-map-tile-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-map-tile-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-map-tile-source-loader.d.ts +62 -0
- package/dist/arcgis/arcgis-map-tile-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-map-tile-source-loader.js +196 -0
- package/dist/arcgis/arcgis-map-tile-source-loader.js.map +1 -0
- package/dist/arcgis/arcgis-scene-aggregation.d.ts +34 -0
- package/dist/arcgis/arcgis-scene-aggregation.d.ts.map +1 -0
- package/dist/arcgis/arcgis-scene-aggregation.js +57 -0
- package/dist/arcgis/arcgis-scene-aggregation.js.map +1 -0
- package/dist/arcgis/arcgis-scene-server-source-loader-types.d.ts +31 -0
- package/dist/arcgis/arcgis-scene-server-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-scene-server-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-scene-server-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-scene-server-source-loader.d.ts +64 -0
- package/dist/arcgis/arcgis-scene-server-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-scene-server-source-loader.js +159 -0
- package/dist/arcgis/arcgis-scene-server-source-loader.js.map +1 -0
- package/dist/arcgis/arcgis-server.d.ts +15 -0
- package/dist/arcgis/arcgis-server.d.ts.map +1 -0
- package/dist/arcgis/arcgis-server.js +41 -0
- package/dist/arcgis/arcgis-server.js.map +1 -0
- package/dist/arcgis/arcgis-url-utils.d.ts +3 -0
- package/dist/arcgis/arcgis-url-utils.d.ts.map +1 -0
- package/dist/arcgis/arcgis-url-utils.js +23 -0
- package/dist/arcgis/arcgis-url-utils.js.map +1 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.d.ts +31 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.d.ts.map +1 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.js +21 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader-types.js.map +1 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader.d.ts +66 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader.d.ts.map +1 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader.js +150 -0
- package/dist/arcgis/arcgis-vector-tile-server-source-loader.js.map +1 -0
- package/dist/arcgis-feature-query-types.d.ts +77 -0
- package/dist/arcgis-feature-query-types.d.ts.map +1 -0
- package/dist/arcgis-feature-query-types.js +5 -0
- package/dist/arcgis-feature-query-types.js.map +1 -0
- package/dist/arcgis-source-types.d.ts +372 -0
- package/dist/arcgis-source-types.d.ts.map +1 -0
- package/dist/arcgis-source-types.js +108 -0
- package/dist/arcgis-source-types.js.map +1 -0
- package/dist/authentication.d.ts +19 -0
- package/dist/authentication.d.ts.map +1 -0
- package/dist/authentication.js +24 -0
- package/dist/authentication.js.map +1 -0
- package/dist/bundled.d.ts +151 -0
- package/dist/bundled.d.ts.map +1 -0
- package/dist/bundled.js +33 -0
- package/dist/bundled.js.map +1 -0
- package/dist/index.cjs +24932 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/service-registry.d.ts +18 -0
- package/dist/service-registry.d.ts.map +1 -0
- package/dist/service-registry.js +29 -0
- package/dist/service-registry.js.map +1 -0
- package/package.json +91 -0
- package/src/arcgis/arcgis-capability-graph.ts +297 -0
- package/src/arcgis/arcgis-feature-query.ts +442 -0
- package/src/arcgis/arcgis-feature-server-source-loader-types.ts +34 -0
- package/src/arcgis/arcgis-feature-server-source-loader.ts +373 -0
- package/src/arcgis/arcgis-image-server-source-loader-types.ts +34 -0
- package/src/arcgis/arcgis-image-server-source-loader.ts +186 -0
- package/src/arcgis/arcgis-image-tile-source-loader-types.ts +34 -0
- package/src/arcgis/arcgis-image-tile-source-loader.ts +192 -0
- package/src/arcgis/arcgis-map-tile-source-loader-types.ts +34 -0
- package/src/arcgis/arcgis-map-tile-source-loader.ts +248 -0
- package/src/arcgis/arcgis-scene-aggregation.ts +90 -0
- package/src/arcgis/arcgis-scene-server-source-loader-types.ts +36 -0
- package/src/arcgis/arcgis-scene-server-source-loader.ts +216 -0
- package/src/arcgis/arcgis-server.ts +61 -0
- package/src/arcgis/arcgis-url-utils.ts +26 -0
- package/src/arcgis/arcgis-vector-tile-server-source-loader-types.ts +36 -0
- package/src/arcgis/arcgis-vector-tile-server-source-loader.ts +196 -0
- package/src/arcgis-feature-query-types.ts +89 -0
- package/src/arcgis-source-types.ts +372 -0
- package/src/authentication.ts +35 -0
- package/src/bundled.ts +66 -0
- package/src/index.ts +63 -0
- package/src/service-registry.ts +43 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {ArcGISAuthentication} from '../authentication';
|
|
6
|
+
import {ARCGIS_IMAGE_TILE_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
|
|
7
|
+
import type {ArcGISImageTileSourceLoaderOptions} from '../arcgis-source-types';
|
|
8
|
+
|
|
9
|
+
import type {ImageType} from '@loaders.gl/images';
|
|
10
|
+
import {ImageLoader} from '@loaders.gl/images';
|
|
11
|
+
import type {
|
|
12
|
+
CoreAPI,
|
|
13
|
+
GetTileDataParameters,
|
|
14
|
+
GetTileParameters,
|
|
15
|
+
SourceLoader,
|
|
16
|
+
TileSource,
|
|
17
|
+
TileSourceMetadata
|
|
18
|
+
} from '@loaders.gl/loader-utils';
|
|
19
|
+
import {DataSource} from '@loaders.gl/loader-utils';
|
|
20
|
+
import type {LERCData} from '@loaders.gl/lerc';
|
|
21
|
+
import {LERCLoader} from '@loaders.gl/lerc';
|
|
22
|
+
|
|
23
|
+
/** A tile source that renders ArcGIS ImageServer exports as deck.gl tiles. */
|
|
24
|
+
export class ArcGISImageTileSource
|
|
25
|
+
extends DataSource<string, ArcGISImageTileSourceLoaderOptions>
|
|
26
|
+
implements TileSource
|
|
27
|
+
{
|
|
28
|
+
/** MIME type represented by the tile response. */
|
|
29
|
+
readonly mimeType: string;
|
|
30
|
+
|
|
31
|
+
/** Cached ImageServer metadata. */
|
|
32
|
+
private _metadata: any | null = null;
|
|
33
|
+
/** In-flight metadata request shared by concurrent callers. */
|
|
34
|
+
private _metadataPromise: Promise<any> | null = null;
|
|
35
|
+
/** Parameters applied to subsequent export requests. */
|
|
36
|
+
private _runtimeParameters: Record<string, string | number | boolean> = {};
|
|
37
|
+
|
|
38
|
+
/** Creates an ArcGIS ImageServer tile source. */
|
|
39
|
+
constructor(url: string, options: ArcGISImageTileSourceLoaderOptions = {}, coreApi?: CoreAPI) {
|
|
40
|
+
super(
|
|
41
|
+
url.replace(/\/$/, ''),
|
|
42
|
+
options,
|
|
43
|
+
ARCGIS_IMAGE_TILE_SOURCE_LOADER_METADATA.defaultOptions,
|
|
44
|
+
coreApi
|
|
45
|
+
);
|
|
46
|
+
this.mimeType = this._getResponseFormat() === 'lerc' ? 'application/octet-stream' : 'image/png';
|
|
47
|
+
this.getTileData = this.getTileData.bind(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Returns normalized ImageServer metadata. */
|
|
51
|
+
async getMetadata(): Promise<TileSourceMetadata> {
|
|
52
|
+
const metadata = await this._getMetadata();
|
|
53
|
+
const extent = metadata.fullExtent || metadata.extent;
|
|
54
|
+
const spatialReference = metadata.spatialReference || extent?.spatialReference;
|
|
55
|
+
return {
|
|
56
|
+
name: metadata.name || metadata.serviceDescription || '',
|
|
57
|
+
title: metadata.name || metadata.serviceDescription || '',
|
|
58
|
+
abstract: metadata.description || metadata.serviceDescription || '',
|
|
59
|
+
attributions: metadata.copyrightText ? [metadata.copyrightText] : undefined,
|
|
60
|
+
boundingBox: extent
|
|
61
|
+
? [
|
|
62
|
+
[extent.xmin, extent.ymin],
|
|
63
|
+
[extent.xmax, extent.ymax]
|
|
64
|
+
]
|
|
65
|
+
: undefined,
|
|
66
|
+
layer: {
|
|
67
|
+
name: metadata.name || '',
|
|
68
|
+
srs: spatialReference?.wkid ? [`EPSG:${spatialReference.wkid}`] : [],
|
|
69
|
+
layers: []
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fetches one ImageServer export tile. */
|
|
75
|
+
async getTile(
|
|
76
|
+
parameters: GetTileParameters,
|
|
77
|
+
signal?: AbortSignal
|
|
78
|
+
): Promise<ImageType | LERCData | null> {
|
|
79
|
+
const response = await this.fetch(this.getTileURL(parameters), signal ? {signal} : undefined);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`ArcGIS ImageServer tile request failed: ${response.status} ${response.statusText}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const loader = this._getResponseFormat() === 'lerc' ? LERCLoader : ImageLoader;
|
|
86
|
+
return (await this.coreApi.parse(
|
|
87
|
+
await response.arrayBuffer(),
|
|
88
|
+
loader,
|
|
89
|
+
this.loadOptions
|
|
90
|
+
)) as ImageType;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Fetches a tile using the deck.gl-compatible request shape. */
|
|
94
|
+
async getTileData(parameters: GetTileDataParameters): Promise<ImageType | LERCData | null> {
|
|
95
|
+
return this.getTile(parameters.index, parameters.signal);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Updates parameters applied to subsequent ImageServer export requests. */
|
|
99
|
+
updateParameters(parameters: Record<string, string | number | boolean>): void {
|
|
100
|
+
this._runtimeParameters = {...this._runtimeParameters, ...parameters};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Builds an ImageServer exportImage URL for one web-mercator tile. */
|
|
104
|
+
getTileURL(parameters: GetTileParameters, tileSize?: number): string {
|
|
105
|
+
const options = this.options['arcgis-image-server-tiles'] || {};
|
|
106
|
+
const resolvedTileSize = tileSize || options.tileSize || 256;
|
|
107
|
+
const [west, south, east, north] = getWebMercatorTileBounds(parameters);
|
|
108
|
+
const url = new URL(this.getServiceURL(parameters));
|
|
109
|
+
url.pathname = `${url.pathname.replace(/\/$/, '')}/exportImage`;
|
|
110
|
+
const requestParameters = {
|
|
111
|
+
f: 'image',
|
|
112
|
+
bbox: `${west},${south},${east},${north}`,
|
|
113
|
+
bboxSR: 3857,
|
|
114
|
+
imageSR: 3857,
|
|
115
|
+
size: `${resolvedTileSize},${resolvedTileSize}`,
|
|
116
|
+
format: this._getResponseFormat(),
|
|
117
|
+
transparent: true,
|
|
118
|
+
...options.parameters,
|
|
119
|
+
...this._runtimeParameters
|
|
120
|
+
};
|
|
121
|
+
for (const [key, value] of Object.entries(requestParameters)) {
|
|
122
|
+
url.searchParams.set(key, String(value));
|
|
123
|
+
}
|
|
124
|
+
return url.toString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Fetches the ImageServer metadata document. */
|
|
128
|
+
private async _loadMetadata(): Promise<any> {
|
|
129
|
+
const url = new URL(this.url);
|
|
130
|
+
url.searchParams.set('f', 'pjson');
|
|
131
|
+
const response = await this.fetch(url.toString());
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`ArcGIS ImageServer metadata request failed: ${response.status}`);
|
|
134
|
+
}
|
|
135
|
+
return response.json();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Returns cached metadata and shares a single request among concurrent callers. */
|
|
139
|
+
private async _getMetadata(): Promise<any> {
|
|
140
|
+
if (this._metadata) return this._metadata;
|
|
141
|
+
if (!this._metadataPromise) {
|
|
142
|
+
this._metadataPromise = this._loadMetadata();
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const metadata = await this._metadataPromise;
|
|
146
|
+
this._metadata = metadata;
|
|
147
|
+
return metadata;
|
|
148
|
+
} finally {
|
|
149
|
+
this._metadataPromise = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Selects a service endpoint for a tile using a stable URL-pool mapping. */
|
|
154
|
+
private getServiceURL(parameters: GetTileParameters): string {
|
|
155
|
+
const urls = this.options['arcgis-image-server-tiles']?.urls;
|
|
156
|
+
return urls?.length ? urls[(parameters.x + parameters.y) % urls.length] : this.url;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Returns the response format after applying configured and runtime overrides. */
|
|
160
|
+
private _getResponseFormat(): 'png32' | 'lerc' {
|
|
161
|
+
const options = this.options['arcgis-image-server-tiles'] || {};
|
|
162
|
+
const format = this._runtimeParameters.format || options.parameters?.format || options.format;
|
|
163
|
+
return format === 'lerc' ? 'lerc' : 'png32';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Runtime service loader for synchronous construction. */
|
|
168
|
+
export const ArcGISImageTileSourceLoaderWithParser = {
|
|
169
|
+
...ARCGIS_IMAGE_TILE_SOURCE_LOADER_METADATA,
|
|
170
|
+
dataType: null as unknown as ArcGISImageTileSource,
|
|
171
|
+
batchType: null as never,
|
|
172
|
+
preload: undefined,
|
|
173
|
+
/** Supplies ArcGIS credentials after loading the selected implementation. */
|
|
174
|
+
getAuthentications: () => [ArcGISAuthentication],
|
|
175
|
+
createDataSource: (
|
|
176
|
+
url: string,
|
|
177
|
+
options: ArcGISImageTileSourceLoaderOptions = {},
|
|
178
|
+
coreApi?: CoreAPI
|
|
179
|
+
) => new ArcGISImageTileSource(url, options, coreApi)
|
|
180
|
+
} as const satisfies SourceLoader<ArcGISImageTileSource>;
|
|
181
|
+
|
|
182
|
+
/** Calculates the Web Mercator extent represented by an XYZ tile. */
|
|
183
|
+
function getWebMercatorTileBounds(parameters: GetTileParameters): [number, number, number, number] {
|
|
184
|
+
const worldSize = 20037508.342789244;
|
|
185
|
+
const tileCount = 2 ** parameters.z;
|
|
186
|
+
const tileSize = (worldSize * 2) / tileCount;
|
|
187
|
+
const west = -worldSize + parameters.x * tileSize;
|
|
188
|
+
const east = west + tileSize;
|
|
189
|
+
const north = worldSize - parameters.y * tileSize;
|
|
190
|
+
const south = north - tileSize;
|
|
191
|
+
return [west, south, east, north];
|
|
192
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {ARCGIS_MAP_TILE_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
|
|
6
|
+
import type {CoreAPI, SourceLoader} from '@loaders.gl/loader-utils';
|
|
7
|
+
import type {ArcGISMapTileSource} from './arcgis-map-tile-source-loader';
|
|
8
|
+
import type {ArcGISMapTileSourceLoaderOptions} from '../arcgis-source-types';
|
|
9
|
+
|
|
10
|
+
/** Loads the ArcGISMapTileSourceLoader implementation on first asynchronous use. */
|
|
11
|
+
async function preloadArcGISMapTileSourceLoader(): Promise<SourceLoader<ArcGISMapTileSource>> {
|
|
12
|
+
const {ArcGISMapTileSourceLoaderWithParser} = await import(
|
|
13
|
+
'@loaders.gl/arcgis/arcgis-map-tile-source-loader'
|
|
14
|
+
);
|
|
15
|
+
return ArcGISMapTileSourceLoaderWithParser;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Lightweight service descriptor; use load() or the synchronous /bundled entrypoint. */
|
|
19
|
+
export const ArcGISMapTileSourceLoader = {
|
|
20
|
+
...ARCGIS_MAP_TILE_SOURCE_LOADER_METADATA,
|
|
21
|
+
dataType: null as unknown as ArcGISMapTileSource,
|
|
22
|
+
batchType: null as never,
|
|
23
|
+
preload: preloadArcGISMapTileSourceLoader,
|
|
24
|
+
/** Requires the runtime loader when constructing a source synchronously. */
|
|
25
|
+
createDataSource(
|
|
26
|
+
_url: string,
|
|
27
|
+
_options: ArcGISMapTileSourceLoaderOptions,
|
|
28
|
+
_coreApi?: CoreAPI
|
|
29
|
+
): ArcGISMapTileSource {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'ArcGISMapTileSourceLoader requires async load() or an @loaders.gl/arcgis/bundled import'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
} as const satisfies SourceLoader<ArcGISMapTileSource>;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {ArcGISAuthentication} from '../authentication';
|
|
6
|
+
import {ARCGIS_MAP_TILE_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
|
|
7
|
+
import type {
|
|
8
|
+
ArcGISMapTileSourceLoaderOptions,
|
|
9
|
+
ArcGISMapServerMetadata,
|
|
10
|
+
ArcGISMapTileParameters
|
|
11
|
+
} from '../arcgis-source-types';
|
|
12
|
+
|
|
13
|
+
import type {ImageType} from '@loaders.gl/images';
|
|
14
|
+
import {ImageLoader} from '@loaders.gl/images';
|
|
15
|
+
import type {
|
|
16
|
+
CoreAPI,
|
|
17
|
+
GetTileDataParameters,
|
|
18
|
+
GetTileParameters,
|
|
19
|
+
SourceLoader,
|
|
20
|
+
TileSource,
|
|
21
|
+
TileSourceMetadata
|
|
22
|
+
} from '@loaders.gl/loader-utils';
|
|
23
|
+
import {DataSource} from '@loaders.gl/loader-utils';
|
|
24
|
+
|
|
25
|
+
/** ArcGIS MapServer source for cached `/tile/{z}/{y}/{x}` image tiles. */
|
|
26
|
+
export class ArcGISMapTileSource
|
|
27
|
+
extends DataSource<string, ArcGISMapTileSourceLoaderOptions>
|
|
28
|
+
implements TileSource
|
|
29
|
+
{
|
|
30
|
+
/** MIME type rendered by the generic deck.gl tile adapter. */
|
|
31
|
+
readonly mimeType = 'image/png';
|
|
32
|
+
|
|
33
|
+
/** Cached service metadata. */
|
|
34
|
+
private _metadata: ArcGISMapServerMetadata | null = null;
|
|
35
|
+
/** In-flight metadata request shared by concurrent callers. */
|
|
36
|
+
private _metadataPromise: Promise<ArcGISMapServerMetadata> | null = null;
|
|
37
|
+
/** Parameters applied to subsequent dynamic exports. */
|
|
38
|
+
private _runtimeParameters: ArcGISMapTileParameters = {};
|
|
39
|
+
|
|
40
|
+
/** Creates an ArcGIS MapServer tile source. */
|
|
41
|
+
constructor(url: string, options: ArcGISMapTileSourceLoaderOptions = {}, coreApi?: CoreAPI) {
|
|
42
|
+
super(
|
|
43
|
+
url.replace(/\/$/, ''),
|
|
44
|
+
options,
|
|
45
|
+
ARCGIS_MAP_TILE_SOURCE_LOADER_METADATA.defaultOptions,
|
|
46
|
+
coreApi
|
|
47
|
+
);
|
|
48
|
+
this.getTileData = this.getTileData.bind(this);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Loads and normalizes ArcGIS service metadata. */
|
|
52
|
+
async getMetadata(): Promise<TileSourceMetadata> {
|
|
53
|
+
const metadata = await this._getMetadata();
|
|
54
|
+
const extent = metadata.fullExtent;
|
|
55
|
+
const spatialReference = metadata.spatialReference || extent?.spatialReference;
|
|
56
|
+
const spatialReferenceWkid = (spatialReference as {wkid?: number} | undefined)?.wkid;
|
|
57
|
+
return {
|
|
58
|
+
name: metadata.name || '',
|
|
59
|
+
title: metadata.name || '',
|
|
60
|
+
abstract: metadata.description || metadata.serviceDescription || '',
|
|
61
|
+
attributions: metadata.copyrightText ? [metadata.copyrightText] : undefined,
|
|
62
|
+
minZoom: 0,
|
|
63
|
+
maxZoom: metadata.tileInfo?.lods?.length
|
|
64
|
+
? Math.max(...metadata.tileInfo.lods.map(lod => lod.level))
|
|
65
|
+
: undefined,
|
|
66
|
+
boundingBox: extent
|
|
67
|
+
? [
|
|
68
|
+
[extent.xmin, extent.ymin],
|
|
69
|
+
[extent.xmax, extent.ymax]
|
|
70
|
+
]
|
|
71
|
+
: undefined,
|
|
72
|
+
layer: {
|
|
73
|
+
name: metadata.name || '',
|
|
74
|
+
srs: spatialReferenceWkid ? [`EPSG:${spatialReferenceWkid}`] : [],
|
|
75
|
+
layers: []
|
|
76
|
+
},
|
|
77
|
+
tileGrid: metadata.tileInfo
|
|
78
|
+
? {
|
|
79
|
+
crs: (metadata.tileInfo.spatialReference as {wkid?: number} | undefined)?.wkid
|
|
80
|
+
? `EPSG:${(metadata.tileInfo.spatialReference as {wkid: number}).wkid}`
|
|
81
|
+
: undefined,
|
|
82
|
+
tileSize:
|
|
83
|
+
metadata.tileInfo.rows && metadata.tileInfo.cols
|
|
84
|
+
? [metadata.tileInfo.cols, metadata.tileInfo.rows]
|
|
85
|
+
: undefined,
|
|
86
|
+
origin: metadata.tileInfo.origin
|
|
87
|
+
? [metadata.tileInfo.origin.x, metadata.tileInfo.origin.y]
|
|
88
|
+
: undefined,
|
|
89
|
+
matrixIds: metadata.tileInfo.lods?.map(lod => String(lod.level))
|
|
90
|
+
}
|
|
91
|
+
: undefined
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Fetches and decodes one cached ArcGIS tile. */
|
|
96
|
+
async getTile(parameters: GetTileParameters, signal?: AbortSignal): Promise<ImageType | null> {
|
|
97
|
+
const options = this.options['arcgis-map-server'] || {};
|
|
98
|
+
const mode = options.mode || 'auto';
|
|
99
|
+
const metadata = mode === 'auto' ? await this._getMetadata() : null;
|
|
100
|
+
const useCachedTiles = mode === 'cached' || (mode === 'auto' && isCompatibleCache(metadata));
|
|
101
|
+
const tileURL = useCachedTiles
|
|
102
|
+
? this.getTileURL(parameters)
|
|
103
|
+
: this.getExportTileURL(parameters, options.tileSize || 256);
|
|
104
|
+
const response = await this.fetch(tileURL, signal ? {signal} : undefined);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`ArcGIS MapServer tile request failed: ${response.status} ${response.statusText}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (await this.coreApi.parse(
|
|
111
|
+
await response.arrayBuffer(),
|
|
112
|
+
ImageLoader,
|
|
113
|
+
this.loadOptions
|
|
114
|
+
)) as ImageType;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Fetches a tile using the deck.gl-compatible request shape. */
|
|
118
|
+
async getTileData(parameters: GetTileDataParameters): Promise<ImageType | null> {
|
|
119
|
+
return this.getTile(parameters.index, parameters.signal);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Builds the standard ArcGIS cached tile URL. */
|
|
123
|
+
getTileURL(parameters: GetTileParameters): string {
|
|
124
|
+
const template = this.options['arcgis-map-server']?.urlTemplate;
|
|
125
|
+
if (template) {
|
|
126
|
+
const templateURL = new URL(
|
|
127
|
+
template
|
|
128
|
+
.replaceAll('{z}', String(parameters.z))
|
|
129
|
+
.replaceAll('{y}', String(parameters.y))
|
|
130
|
+
.replaceAll('{x}', String(parameters.x))
|
|
131
|
+
);
|
|
132
|
+
return templateURL.toString();
|
|
133
|
+
}
|
|
134
|
+
const url = new URL(this.getServiceURL(parameters));
|
|
135
|
+
url.pathname = `${url.pathname.replace(/\/$/, '')}/tile/${parameters.z}/${parameters.y}/${parameters.x}`;
|
|
136
|
+
return url.toString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Updates parameters applied to subsequent dynamic MapServer export requests. */
|
|
140
|
+
updateParameters(parameters: ArcGISMapTileParameters): void {
|
|
141
|
+
this._runtimeParameters = {...this._runtimeParameters, ...parameters};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Builds a dynamic MapServer `export` request for one web-mercator tile. */
|
|
145
|
+
getExportTileURL(parameters: GetTileParameters, tileSize?: number): string {
|
|
146
|
+
const options = this.options['arcgis-map-server'] || {};
|
|
147
|
+
const resolvedTileSize = tileSize || options.tileSize || 256;
|
|
148
|
+
const [west, south, east, north] = getWebMercatorTileBounds(parameters);
|
|
149
|
+
const url = new URL(this.getServiceURL(parameters));
|
|
150
|
+
url.pathname = `${url.pathname.replace(/\/$/, '')}/export`;
|
|
151
|
+
const searchParameters: ArcGISMapTileParameters = {
|
|
152
|
+
f: 'image',
|
|
153
|
+
bbox: `${west},${south},${east},${north}`,
|
|
154
|
+
bboxSR: 3857,
|
|
155
|
+
imageSR: 3857,
|
|
156
|
+
size: `${resolvedTileSize},${resolvedTileSize}`,
|
|
157
|
+
format: 'png32',
|
|
158
|
+
transparent: true,
|
|
159
|
+
...options.exportParameters,
|
|
160
|
+
...this._runtimeParameters
|
|
161
|
+
};
|
|
162
|
+
for (const [key, value] of Object.entries(searchParameters)) {
|
|
163
|
+
url.searchParams.set(key, String(value));
|
|
164
|
+
}
|
|
165
|
+
return url.toString();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Fetches the MapServer metadata document and applies configured parameters. */
|
|
169
|
+
private async _loadMetadata(): Promise<ArcGISMapServerMetadata> {
|
|
170
|
+
const configuredMetadata = this.options['arcgis-map-server']?.metadata;
|
|
171
|
+
if (configuredMetadata) return configuredMetadata;
|
|
172
|
+
const url = new URL(this.url);
|
|
173
|
+
url.searchParams.set('f', 'pjson');
|
|
174
|
+
for (const [key, value] of Object.entries(
|
|
175
|
+
this.options['arcgis-map-server']?.parameters || {}
|
|
176
|
+
)) {
|
|
177
|
+
url.searchParams.set(key, value);
|
|
178
|
+
}
|
|
179
|
+
const response = await this.fetch(url.toString());
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`ArcGIS MapServer metadata request failed: ${response.status} ${response.statusText}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return response.json();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Returns cached metadata and shares a single request among concurrent callers. */
|
|
189
|
+
private async _getMetadata(): Promise<ArcGISMapServerMetadata> {
|
|
190
|
+
if (this._metadata) return this._metadata;
|
|
191
|
+
if (!this._metadataPromise) {
|
|
192
|
+
this._metadataPromise = this._loadMetadata();
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const metadata = await this._metadataPromise;
|
|
196
|
+
this._metadata = metadata;
|
|
197
|
+
return metadata;
|
|
198
|
+
} finally {
|
|
199
|
+
this._metadataPromise = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Selects a service endpoint for a tile using a stable URL-pool mapping. */
|
|
204
|
+
private getServiceURL(parameters: GetTileParameters): string {
|
|
205
|
+
const urls = this.options['arcgis-map-server']?.urls;
|
|
206
|
+
return urls?.length ? urls[(parameters.x + parameters.y) % urls.length] : this.url;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isCompatibleCache(metadata: ArcGISMapServerMetadata | null): boolean {
|
|
211
|
+
const tileInfo = metadata?.tileInfo;
|
|
212
|
+
if (!tileInfo || tileInfo.rows !== 256 || tileInfo.cols !== 256) return false;
|
|
213
|
+
const spatialReference = tileInfo.spatialReference || metadata?.spatialReference;
|
|
214
|
+
const wkid = (spatialReference as {wkid?: number; latestWkid?: number} | undefined)?.wkid;
|
|
215
|
+
if (wkid !== 3857 && wkid !== 102100 && wkid !== 102113) return false;
|
|
216
|
+
const origin = tileInfo.origin;
|
|
217
|
+
return (
|
|
218
|
+
!origin ||
|
|
219
|
+
(Math.abs(origin.x + 20037508.342789244) < 1 && Math.abs(origin.y - 20037508.342789244) < 1)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Calculates the Web Mercator extent represented by an XYZ tile. */
|
|
224
|
+
function getWebMercatorTileBounds(parameters: GetTileParameters): [number, number, number, number] {
|
|
225
|
+
const worldSize = 20037508.342789244;
|
|
226
|
+
const tileCount = 2 ** parameters.z;
|
|
227
|
+
const tileSize = (worldSize * 2) / tileCount;
|
|
228
|
+
const west = -worldSize + parameters.x * tileSize;
|
|
229
|
+
const east = west + tileSize;
|
|
230
|
+
const north = worldSize - parameters.y * tileSize;
|
|
231
|
+
const south = north - tileSize;
|
|
232
|
+
return [west, south, east, north];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Runtime service loader for synchronous construction. */
|
|
236
|
+
export const ArcGISMapTileSourceLoaderWithParser = {
|
|
237
|
+
...ARCGIS_MAP_TILE_SOURCE_LOADER_METADATA,
|
|
238
|
+
dataType: null as unknown as ArcGISMapTileSource,
|
|
239
|
+
batchType: null as never,
|
|
240
|
+
preload: undefined,
|
|
241
|
+
/** Supplies ArcGIS credentials after loading the selected implementation. */
|
|
242
|
+
getAuthentications: () => [ArcGISAuthentication],
|
|
243
|
+
createDataSource: (
|
|
244
|
+
url: string,
|
|
245
|
+
options: ArcGISMapTileSourceLoaderOptions = {},
|
|
246
|
+
coreApi?: CoreAPI
|
|
247
|
+
) => new ArcGISMapTileSource(url, options, coreApi)
|
|
248
|
+
} as const satisfies SourceLoader<ArcGISMapTileSource>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
/** Aggregation operation supported by the SceneServer client helper. */
|
|
6
|
+
export type ArcGISSceneAggregationOperation = 'count' | 'sum' | 'min' | 'max' | 'average';
|
|
7
|
+
|
|
8
|
+
/** A single aggregation specification. */
|
|
9
|
+
export type ArcGISSceneAggregationSpec = {
|
|
10
|
+
/** Output field name. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Input property used by numeric operations. */
|
|
13
|
+
field?: string;
|
|
14
|
+
/** Operation to apply. */
|
|
15
|
+
operation: ArcGISSceneAggregationOperation;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Options for deterministic client-side feature aggregation. */
|
|
19
|
+
export type ArcGISSceneAggregationOptions = {
|
|
20
|
+
/** Features represented as ArcGIS feature objects or plain attribute records. */
|
|
21
|
+
features: unknown[];
|
|
22
|
+
/** Optional property used to create groups. */
|
|
23
|
+
groupBy?: string;
|
|
24
|
+
/** Aggregations to compute for each group. */
|
|
25
|
+
aggregations: ArcGISSceneAggregationSpec[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Result of one group in a client-side aggregation. */
|
|
29
|
+
export type ArcGISSceneAggregationGroup = {
|
|
30
|
+
/** Group key, or `undefined` when no grouping was requested. */
|
|
31
|
+
group: unknown;
|
|
32
|
+
/** Aggregated values keyed by specification name. */
|
|
33
|
+
values: Record<string, number>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Aggregates SceneServer features without evaluating renderer or Arcade expressions.
|
|
38
|
+
* Missing and non-numeric values are ignored by numeric operations; `count` counts
|
|
39
|
+
* records regardless of field presence.
|
|
40
|
+
*/
|
|
41
|
+
export function aggregateArcGISSceneFeatures(
|
|
42
|
+
options: ArcGISSceneAggregationOptions
|
|
43
|
+
): ArcGISSceneAggregationGroup[] {
|
|
44
|
+
const groups = new Map<unknown, unknown[]>();
|
|
45
|
+
for (const feature of options.features) {
|
|
46
|
+
const attributes = getFeatureAttributes(feature);
|
|
47
|
+
const key = options.groupBy ? attributes[options.groupBy] : undefined;
|
|
48
|
+
const group = groups.get(key);
|
|
49
|
+
if (group) group.push(feature);
|
|
50
|
+
else groups.set(key, [feature]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [...groups.entries()].map(([group, features]) => ({
|
|
54
|
+
group,
|
|
55
|
+
values: Object.fromEntries(
|
|
56
|
+
options.aggregations.map(spec => [spec.name, aggregateValues(features, spec)])
|
|
57
|
+
)
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function aggregateValues(features: unknown[], specification: ArcGISSceneAggregationSpec): number {
|
|
62
|
+
if (specification.operation === 'count') return features.length;
|
|
63
|
+
const values = features
|
|
64
|
+
.map(feature => getFeatureAttributes(feature)[specification.field || ''])
|
|
65
|
+
.filter(value => value !== null && value !== undefined && value !== '')
|
|
66
|
+
.map(value => (typeof value === 'number' ? value : Number(value)))
|
|
67
|
+
.filter(Number.isFinite);
|
|
68
|
+
if (!values.length) return 0;
|
|
69
|
+
switch (specification.operation) {
|
|
70
|
+
case 'sum':
|
|
71
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
72
|
+
case 'min':
|
|
73
|
+
return Math.min(...values);
|
|
74
|
+
case 'max':
|
|
75
|
+
return Math.max(...values);
|
|
76
|
+
case 'average':
|
|
77
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
78
|
+
default:
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getFeatureAttributes(feature: unknown): Record<string, unknown> {
|
|
84
|
+
if (!feature || typeof feature !== 'object') return {};
|
|
85
|
+
const candidate = feature as {attributes?: unknown};
|
|
86
|
+
if (candidate.attributes && typeof candidate.attributes === 'object') {
|
|
87
|
+
return candidate.attributes as Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
return feature as Record<string, unknown>;
|
|
90
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {ARCGIS_SCENE_SERVER_SOURCE_LOADER_METADATA} from '../arcgis-source-types';
|
|
6
|
+
import type {CoreAPI, SourceLoader} from '@loaders.gl/loader-utils';
|
|
7
|
+
import type {ArcGISSceneServerSource} from './arcgis-scene-server-source-loader';
|
|
8
|
+
import type {ArcGISSceneServerSourceOptions} from '../arcgis-source-types';
|
|
9
|
+
|
|
10
|
+
/** Loads the ArcGISSceneServerSourceLoader implementation on first asynchronous use. */
|
|
11
|
+
async function preloadArcGISSceneServerSourceLoader(): Promise<
|
|
12
|
+
SourceLoader<ArcGISSceneServerSource>
|
|
13
|
+
> {
|
|
14
|
+
const {ArcGISSceneServerSourceLoaderWithParser} = await import(
|
|
15
|
+
'@loaders.gl/arcgis/arcgis-scene-server-source-loader'
|
|
16
|
+
);
|
|
17
|
+
return ArcGISSceneServerSourceLoaderWithParser;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Lightweight service descriptor; use load() or the synchronous /bundled entrypoint. */
|
|
21
|
+
export const ArcGISSceneServerSourceLoader = {
|
|
22
|
+
...ARCGIS_SCENE_SERVER_SOURCE_LOADER_METADATA,
|
|
23
|
+
dataType: null as unknown as ArcGISSceneServerSource,
|
|
24
|
+
batchType: null as never,
|
|
25
|
+
preload: preloadArcGISSceneServerSourceLoader,
|
|
26
|
+
/** Requires the runtime loader when constructing a source synchronously. */
|
|
27
|
+
createDataSource(
|
|
28
|
+
_url: string,
|
|
29
|
+
_options: ArcGISSceneServerSourceOptions,
|
|
30
|
+
_coreApi?: CoreAPI
|
|
31
|
+
): ArcGISSceneServerSource {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'ArcGISSceneServerSourceLoader requires async load() or an @loaders.gl/arcgis/bundled import'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
} as const satisfies SourceLoader<ArcGISSceneServerSource>;
|