@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -201
- package/bin/cli.js +157 -173
- package/contexts/4.34/{claude → skills}/arcgis-3d-advanced/SKILL.md +586 -586
- package/contexts/4.34/{claude → skills}/arcgis-advanced-layers/SKILL.md +431 -431
- package/contexts/4.34/{claude → skills}/arcgis-analysis-services/SKILL.md +607 -607
- package/contexts/4.34/{claude → skills}/arcgis-authentication/SKILL.md +301 -301
- package/contexts/4.34/{claude → skills}/arcgis-cim-symbols/SKILL.md +486 -486
- package/contexts/4.34/{claude → skills}/arcgis-coordinates-projection/SKILL.md +406 -406
- package/contexts/4.34/{claude → skills}/arcgis-core-maps/SKILL.md +739 -739
- package/contexts/4.34/{claude → skills}/arcgis-core-utilities/SKILL.md +732 -732
- package/contexts/4.34/{claude → skills}/arcgis-custom-rendering/SKILL.md +445 -445
- package/contexts/4.34/{claude → skills}/arcgis-editing-advanced/SKILL.md +702 -702
- package/contexts/4.34/{claude → skills}/arcgis-feature-effects/SKILL.md +393 -393
- package/contexts/4.34/{claude → skills}/arcgis-geometry-operations/SKILL.md +489 -489
- package/contexts/4.34/{claude → skills}/arcgis-imagery/SKILL.md +307 -307
- package/contexts/4.34/{claude → skills}/arcgis-interaction/SKILL.md +572 -572
- package/contexts/4.34/{claude → skills}/arcgis-knowledge-graphs/SKILL.md +582 -582
- package/contexts/4.34/{claude → skills}/arcgis-layers/SKILL.md +601 -601
- package/contexts/4.34/{claude → skills}/arcgis-map-tools/SKILL.md +668 -668
- package/contexts/4.34/{claude → skills}/arcgis-media-layers/SKILL.md +290 -290
- package/contexts/4.34/{claude → skills}/arcgis-portal-content/SKILL.md +679 -679
- package/contexts/4.34/{claude → skills}/arcgis-scene-effects/SKILL.md +512 -512
- package/contexts/4.34/{claude → skills}/arcgis-smart-mapping/SKILL.md +686 -686
- package/contexts/4.34/skills/arcgis-starter-app/SKILL.md +273 -0
- package/contexts/4.34/skills/arcgis-starter-app-extended/SKILL.md +649 -0
- package/contexts/4.34/{claude → skills}/arcgis-tables-forms/SKILL.md +877 -877
- package/contexts/4.34/{claude → skills}/arcgis-time-animation/SKILL.md +722 -722
- package/contexts/4.34/{claude → skills}/arcgis-utility-networks/SKILL.md +301 -301
- package/contexts/4.34/{claude → skills}/arcgis-visualization/SKILL.md +580 -580
- package/contexts/4.34/{claude → skills}/arcgis-widgets-ui/SKILL.md +574 -574
- package/lib/installer.js +294 -379
- package/package.json +45 -45
- package/contexts/4.34/copilot/arcgis-3d.instructions.md +0 -267
- package/contexts/4.34/copilot/arcgis-analysis.instructions.md +0 -294
- package/contexts/4.34/copilot/arcgis-arcade.instructions.md +0 -234
- package/contexts/4.34/copilot/arcgis-authentication.instructions.md +0 -187
- package/contexts/4.34/copilot/arcgis-cim-symbols.instructions.md +0 -177
- package/contexts/4.34/copilot/arcgis-core-maps.instructions.md +0 -246
- package/contexts/4.34/copilot/arcgis-core-utilities.instructions.md +0 -247
- package/contexts/4.34/copilot/arcgis-editing.instructions.md +0 -262
- package/contexts/4.34/copilot/arcgis-geometry.instructions.md +0 -225
- package/contexts/4.34/copilot/arcgis-layers.instructions.md +0 -278
- package/contexts/4.34/copilot/arcgis-popup-templates.instructions.md +0 -266
- package/contexts/4.34/copilot/arcgis-portal-advanced.instructions.md +0 -275
- package/contexts/4.34/copilot/arcgis-smart-mapping.instructions.md +0 -184
- package/contexts/4.34/copilot/arcgis-time-animation.instructions.md +0 -112
- package/contexts/4.34/copilot/arcgis-visualization.instructions.md +0 -321
- package/contexts/4.34/copilot/arcgis-widgets-ui.instructions.md +0 -277
- /package/contexts/4.34/{claude → skills}/arcgis-arcade/SKILL.md +0 -0
- /package/contexts/4.34/{claude → skills}/arcgis-popup-templates/SKILL.md +0 -0
|
@@ -1,445 +1,445 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: arcgis-custom-rendering
|
|
3
|
-
description: Create custom layer types with WebGL rendering, custom tile layers, and blend layers. Use for advanced visualizations and custom data sources.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# ArcGIS Custom Rendering
|
|
7
|
-
|
|
8
|
-
Use this skill for creating custom layers, WebGL rendering, and advanced visualization techniques.
|
|
9
|
-
|
|
10
|
-
## Custom TileLayer
|
|
11
|
-
|
|
12
|
-
### Basic Custom TileLayer
|
|
13
|
-
```javascript
|
|
14
|
-
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
15
|
-
import esriRequest from "@arcgis/core/request.js";
|
|
16
|
-
import Color from "@arcgis/core/Color.js";
|
|
17
|
-
|
|
18
|
-
const TintLayer = BaseTileLayer.createSubclass({
|
|
19
|
-
properties: {
|
|
20
|
-
urlTemplate: null,
|
|
21
|
-
tint: {
|
|
22
|
-
value: null,
|
|
23
|
-
type: Color
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
// Generate tile URL
|
|
28
|
-
getTileUrl(level, row, col) {
|
|
29
|
-
return this.urlTemplate
|
|
30
|
-
.replace("{z}", level)
|
|
31
|
-
.replace("{x}", col)
|
|
32
|
-
.replace("{y}", row);
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
// Fetch and process tiles
|
|
36
|
-
fetchTile(level, row, col, options) {
|
|
37
|
-
const url = this.getTileUrl(level, row, col);
|
|
38
|
-
|
|
39
|
-
return esriRequest(url, {
|
|
40
|
-
responseType: "image",
|
|
41
|
-
signal: options?.signal
|
|
42
|
-
}).then((response) => {
|
|
43
|
-
const image = response.data;
|
|
44
|
-
const width = this.tileInfo.size[0];
|
|
45
|
-
const height = this.tileInfo.size[0];
|
|
46
|
-
|
|
47
|
-
// Create canvas for processing
|
|
48
|
-
const canvas = document.createElement("canvas");
|
|
49
|
-
const context = canvas.getContext("2d");
|
|
50
|
-
canvas.width = width;
|
|
51
|
-
canvas.height = height;
|
|
52
|
-
|
|
53
|
-
// Apply tint
|
|
54
|
-
if (this.tint) {
|
|
55
|
-
context.fillStyle = this.tint.toCss();
|
|
56
|
-
context.fillRect(0, 0, width, height);
|
|
57
|
-
context.globalCompositeOperation = "difference";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
context.drawImage(image, 0, 0, width, height);
|
|
61
|
-
return canvas;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Use the custom layer
|
|
67
|
-
const customLayer = new TintLayer({
|
|
68
|
-
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
|
|
69
|
-
tint: new Color("#132178"),
|
|
70
|
-
title: "Custom Tinted Layer"
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
map.add(customLayer);
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Watch Property Changes
|
|
77
|
-
```javascript
|
|
78
|
-
const CustomLayer = BaseTileLayer.createSubclass({
|
|
79
|
-
properties: {
|
|
80
|
-
customProperty: null
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
initialize() {
|
|
84
|
-
reactiveUtils.watch(
|
|
85
|
-
() => this.customProperty,
|
|
86
|
-
() => {
|
|
87
|
-
this.refresh(); // Refresh tiles when property changes
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Custom DynamicLayer
|
|
95
|
-
|
|
96
|
-
### Dynamic Layer from Canvas
|
|
97
|
-
```javascript
|
|
98
|
-
import BaseDynamicLayer from "@arcgis/core/layers/BaseDynamicLayer.js";
|
|
99
|
-
|
|
100
|
-
const CustomDynamicLayer = BaseDynamicLayer.createSubclass({
|
|
101
|
-
properties: {
|
|
102
|
-
data: null
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
// Called when layer needs to render
|
|
106
|
-
getImageUrl(extent, width, height) {
|
|
107
|
-
// Create canvas with specified dimensions
|
|
108
|
-
const canvas = document.createElement("canvas");
|
|
109
|
-
canvas.width = width;
|
|
110
|
-
canvas.height = height;
|
|
111
|
-
const context = canvas.getContext("2d");
|
|
112
|
-
|
|
113
|
-
// Draw your custom visualization
|
|
114
|
-
this.drawVisualization(context, extent, width, height);
|
|
115
|
-
|
|
116
|
-
return canvas.toDataURL("image/png");
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
drawVisualization(ctx, extent, width, height) {
|
|
120
|
-
// Convert geographic coordinates to pixel coordinates
|
|
121
|
-
const xScale = width / (extent.xmax - extent.xmin);
|
|
122
|
-
const yScale = height / (extent.ymax - extent.ymin);
|
|
123
|
-
|
|
124
|
-
// Draw data points
|
|
125
|
-
this.data.forEach(point => {
|
|
126
|
-
const x = (point.x - extent.xmin) * xScale;
|
|
127
|
-
const y = height - (point.y - extent.ymin) * yScale;
|
|
128
|
-
|
|
129
|
-
ctx.beginPath();
|
|
130
|
-
ctx.arc(x, y, 5, 0, Math.PI * 2);
|
|
131
|
-
ctx.fillStyle = "red";
|
|
132
|
-
ctx.fill();
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Custom Elevation Layer
|
|
139
|
-
|
|
140
|
-
### Exaggerated Elevation
|
|
141
|
-
```javascript
|
|
142
|
-
import BaseElevationLayer from "@arcgis/core/layers/BaseElevationLayer.js";
|
|
143
|
-
|
|
144
|
-
const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
|
|
145
|
-
properties: {
|
|
146
|
-
exaggeration: 2
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
load() {
|
|
150
|
-
this._elevation = new ElevationLayer({
|
|
151
|
-
url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
|
|
152
|
-
});
|
|
153
|
-
this.addResolvingPromise(this._elevation.load());
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
fetchTile(level, row, col, options) {
|
|
157
|
-
return this._elevation.fetchTile(level, row, col, options)
|
|
158
|
-
.then((data) => {
|
|
159
|
-
const exaggeration = this.exaggeration;
|
|
160
|
-
for (let i = 0; i < data.values.length; i++) {
|
|
161
|
-
data.values[i] *= exaggeration;
|
|
162
|
-
}
|
|
163
|
-
return data;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Custom Blend Layer
|
|
170
|
-
|
|
171
|
-
### Blending Multiple Layers
|
|
172
|
-
```javascript
|
|
173
|
-
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
|
|
174
|
-
|
|
175
|
-
const BlendLayer = Layer.createSubclass({
|
|
176
|
-
properties: {
|
|
177
|
-
layers: null,
|
|
178
|
-
blendMode: "multiply"
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
createLayerView(view) {
|
|
182
|
-
if (view.type === "2d") {
|
|
183
|
-
return new BlendLayerView2D({
|
|
184
|
-
view: view,
|
|
185
|
-
layer: this
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const BlendLayerView2D = BaseLayerViewGL2D.createSubclass({
|
|
192
|
-
render(renderParameters) {
|
|
193
|
-
// Custom WebGL rendering
|
|
194
|
-
const gl = renderParameters.context;
|
|
195
|
-
// ... WebGL operations
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## WebGL Custom Rendering
|
|
201
|
-
|
|
202
|
-
### Custom LayerView with WebGL
|
|
203
|
-
```javascript
|
|
204
|
-
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
|
|
205
|
-
|
|
206
|
-
const CustomLayerView2D = BaseLayerViewGL2D.createSubclass({
|
|
207
|
-
constructor() {
|
|
208
|
-
this.bindAttach = this.attach.bind(this);
|
|
209
|
-
this.bindDetach = this.detach.bind(this);
|
|
210
|
-
this.bindRender = this.render.bind(this);
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
attach() {
|
|
214
|
-
const gl = this.context;
|
|
215
|
-
|
|
216
|
-
// Create shaders
|
|
217
|
-
const vertexSource = `
|
|
218
|
-
attribute vec2 a_position;
|
|
219
|
-
uniform mat3 u_transform;
|
|
220
|
-
void main() {
|
|
221
|
-
gl_Position = vec4((u_transform * vec3(a_position, 1)).xy, 0, 1);
|
|
222
|
-
}
|
|
223
|
-
`;
|
|
224
|
-
|
|
225
|
-
const fragmentSource = `
|
|
226
|
-
precision mediump float;
|
|
227
|
-
uniform vec4 u_color;
|
|
228
|
-
void main() {
|
|
229
|
-
gl_FragColor = u_color;
|
|
230
|
-
}
|
|
231
|
-
`;
|
|
232
|
-
|
|
233
|
-
// Compile shaders and create program
|
|
234
|
-
this.program = this.createProgram(gl, vertexSource, fragmentSource);
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
render(renderParameters) {
|
|
238
|
-
const gl = renderParameters.context;
|
|
239
|
-
const state = renderParameters.state;
|
|
240
|
-
|
|
241
|
-
// Set up transformation matrix
|
|
242
|
-
const transform = mat3.create();
|
|
243
|
-
mat3.translate(transform, transform, [state.size[0] / 2, state.size[1] / 2]);
|
|
244
|
-
|
|
245
|
-
gl.useProgram(this.program);
|
|
246
|
-
// ... draw operations
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
detach() {
|
|
250
|
-
const gl = this.context;
|
|
251
|
-
gl.deleteProgram(this.program);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## LERC Decoding
|
|
257
|
-
|
|
258
|
-
### Custom LERC Layer
|
|
259
|
-
```javascript
|
|
260
|
-
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
261
|
-
import esriRequest from "@arcgis/core/request.js";
|
|
262
|
-
|
|
263
|
-
// Import LERC decoder
|
|
264
|
-
import * as Lerc from "https://cdn.jsdelivr.net/npm/lerc@4.0.4/+esm";
|
|
265
|
-
|
|
266
|
-
const LercLayer = BaseTileLayer.createSubclass({
|
|
267
|
-
properties: {
|
|
268
|
-
urlTemplate: null,
|
|
269
|
-
minValue: 0,
|
|
270
|
-
maxValue: 1000
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
fetchTile(level, row, col, options) {
|
|
274
|
-
const url = this.urlTemplate
|
|
275
|
-
.replace("{z}", level)
|
|
276
|
-
.replace("{x}", col)
|
|
277
|
-
.replace("{y}", row);
|
|
278
|
-
|
|
279
|
-
return esriRequest(url, {
|
|
280
|
-
responseType: "array-buffer",
|
|
281
|
-
signal: options?.signal
|
|
282
|
-
}).then((response) => {
|
|
283
|
-
// Decode LERC data
|
|
284
|
-
const decodedPixels = Lerc.decode(response.data);
|
|
285
|
-
const { width, height, pixels } = decodedPixels;
|
|
286
|
-
|
|
287
|
-
// Convert to canvas
|
|
288
|
-
const canvas = document.createElement("canvas");
|
|
289
|
-
canvas.width = width;
|
|
290
|
-
canvas.height = height;
|
|
291
|
-
const ctx = canvas.getContext("2d");
|
|
292
|
-
const imageData = ctx.createImageData(width, height);
|
|
293
|
-
|
|
294
|
-
// Map values to colors
|
|
295
|
-
for (let i = 0; i < pixels[0].length; i++) {
|
|
296
|
-
const value = pixels[0][i];
|
|
297
|
-
const normalized = (value - this.minValue) / (this.maxValue - this.minValue);
|
|
298
|
-
const color = this.valueToColor(normalized);
|
|
299
|
-
|
|
300
|
-
imageData.data[i * 4] = color.r;
|
|
301
|
-
imageData.data[i * 4 + 1] = color.g;
|
|
302
|
-
imageData.data[i * 4 + 2] = color.b;
|
|
303
|
-
imageData.data[i * 4 + 3] = 255;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
ctx.putImageData(imageData, 0, 0);
|
|
307
|
-
return canvas;
|
|
308
|
-
});
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
valueToColor(value) {
|
|
312
|
-
// Simple blue-to-red color ramp
|
|
313
|
-
return {
|
|
314
|
-
r: Math.round(value * 255),
|
|
315
|
-
g: 0,
|
|
316
|
-
b: Math.round((1 - value) * 255)
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
## Custom Graphics Rendering
|
|
323
|
-
|
|
324
|
-
### Animated Lines with WebGL
|
|
325
|
-
```javascript
|
|
326
|
-
// Animated lines require custom render nodes in 3D
|
|
327
|
-
// or custom LayerView in 2D
|
|
328
|
-
|
|
329
|
-
const view = new SceneView({
|
|
330
|
-
container: "viewDiv",
|
|
331
|
-
map: map
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Use external render node for animations
|
|
335
|
-
view.when(() => {
|
|
336
|
-
// Add custom render node
|
|
337
|
-
const renderNode = new CustomRenderNode({ view });
|
|
338
|
-
view.environment.lighting = {
|
|
339
|
-
type: "virtual"
|
|
340
|
-
};
|
|
341
|
-
});
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
## Tessellation Helpers
|
|
345
|
-
|
|
346
|
-
### Using Tessellation for Complex Geometries
|
|
347
|
-
```javascript
|
|
348
|
-
import tessellate from "@arcgis/core/geometry/support/tessellate.js";
|
|
349
|
-
|
|
350
|
-
// Tessellate a polygon for WebGL rendering
|
|
351
|
-
const polygon = new Polygon({
|
|
352
|
-
rings: [[/* coordinates */]]
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
const tessellated = tessellate(polygon);
|
|
356
|
-
// Use tessellated.vertices and tessellated.indices for WebGL
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
## Complete Example
|
|
360
|
-
|
|
361
|
-
```html
|
|
362
|
-
<!DOCTYPE html>
|
|
363
|
-
<html>
|
|
364
|
-
<head>
|
|
365
|
-
<link rel="stylesheet" href="https://js.arcgis.com/4.34/esri/themes/light/main.css" />
|
|
366
|
-
<script src="https://js.arcgis.com/4.34/"></script>
|
|
367
|
-
<style>
|
|
368
|
-
html, body, #viewDiv { height: 100%; margin: 0; }
|
|
369
|
-
</style>
|
|
370
|
-
<script type="module">
|
|
371
|
-
import Map from "@arcgis/core/Map.js";
|
|
372
|
-
import SceneView from "@arcgis/core/views/SceneView.js";
|
|
373
|
-
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
374
|
-
import esriRequest from "@arcgis/core/request.js";
|
|
375
|
-
import Color from "@arcgis/core/Color.js";
|
|
376
|
-
|
|
377
|
-
// Create custom tinted tile layer
|
|
378
|
-
const TintLayer = BaseTileLayer.createSubclass({
|
|
379
|
-
properties: {
|
|
380
|
-
urlTemplate: null,
|
|
381
|
-
tint: { value: null, type: Color }
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
getTileUrl(level, row, col) {
|
|
385
|
-
return this.urlTemplate
|
|
386
|
-
.replace("{z}", level)
|
|
387
|
-
.replace("{x}", col)
|
|
388
|
-
.replace("{y}", row);
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
fetchTile(level, row, col, options) {
|
|
392
|
-
return esriRequest(this.getTileUrl(level, row, col), {
|
|
393
|
-
responseType: "image",
|
|
394
|
-
signal: options?.signal
|
|
395
|
-
}).then((response) => {
|
|
396
|
-
const canvas = document.createElement("canvas");
|
|
397
|
-
const ctx = canvas.getContext("2d");
|
|
398
|
-
canvas.width = canvas.height = this.tileInfo.size[0];
|
|
399
|
-
|
|
400
|
-
if (this.tint) {
|
|
401
|
-
ctx.fillStyle = this.tint.toCss();
|
|
402
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
403
|
-
ctx.globalCompositeOperation = "difference";
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
ctx.drawImage(response.data, 0, 0, canvas.width, canvas.height);
|
|
407
|
-
return canvas;
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const customLayer = new TintLayer({
|
|
413
|
-
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
|
|
414
|
-
tint: new Color("#4488ff"),
|
|
415
|
-
title: "Custom Layer"
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
const map = new Map({ layers: [customLayer] });
|
|
419
|
-
|
|
420
|
-
const view = new SceneView({
|
|
421
|
-
container: "viewDiv",
|
|
422
|
-
map: map,
|
|
423
|
-
center: [8.5, 46],
|
|
424
|
-
zoom: 8
|
|
425
|
-
});
|
|
426
|
-
</script>
|
|
427
|
-
</head>
|
|
428
|
-
<body>
|
|
429
|
-
<div id="viewDiv"></div>
|
|
430
|
-
</body>
|
|
431
|
-
</html>
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
## Common Pitfalls
|
|
435
|
-
|
|
436
|
-
1. **CORS**: External tile servers must support CORS
|
|
437
|
-
|
|
438
|
-
2. **Canvas size**: Match tile size exactly with tileInfo
|
|
439
|
-
|
|
440
|
-
3. **Memory management**: Clean up WebGL resources in detach()
|
|
441
|
-
|
|
442
|
-
4. **Async operations**: Handle abort signals properly
|
|
443
|
-
|
|
444
|
-
5. **Coordinate systems**: Transform coordinates correctly for rendering
|
|
445
|
-
|
|
1
|
+
---
|
|
2
|
+
name: arcgis-custom-rendering
|
|
3
|
+
description: Create custom layer types with WebGL rendering, custom tile layers, and blend layers. Use for advanced visualizations and custom data sources.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ArcGIS Custom Rendering
|
|
7
|
+
|
|
8
|
+
Use this skill for creating custom layers, WebGL rendering, and advanced visualization techniques.
|
|
9
|
+
|
|
10
|
+
## Custom TileLayer
|
|
11
|
+
|
|
12
|
+
### Basic Custom TileLayer
|
|
13
|
+
```javascript
|
|
14
|
+
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
15
|
+
import esriRequest from "@arcgis/core/request.js";
|
|
16
|
+
import Color from "@arcgis/core/Color.js";
|
|
17
|
+
|
|
18
|
+
const TintLayer = BaseTileLayer.createSubclass({
|
|
19
|
+
properties: {
|
|
20
|
+
urlTemplate: null,
|
|
21
|
+
tint: {
|
|
22
|
+
value: null,
|
|
23
|
+
type: Color
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Generate tile URL
|
|
28
|
+
getTileUrl(level, row, col) {
|
|
29
|
+
return this.urlTemplate
|
|
30
|
+
.replace("{z}", level)
|
|
31
|
+
.replace("{x}", col)
|
|
32
|
+
.replace("{y}", row);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Fetch and process tiles
|
|
36
|
+
fetchTile(level, row, col, options) {
|
|
37
|
+
const url = this.getTileUrl(level, row, col);
|
|
38
|
+
|
|
39
|
+
return esriRequest(url, {
|
|
40
|
+
responseType: "image",
|
|
41
|
+
signal: options?.signal
|
|
42
|
+
}).then((response) => {
|
|
43
|
+
const image = response.data;
|
|
44
|
+
const width = this.tileInfo.size[0];
|
|
45
|
+
const height = this.tileInfo.size[0];
|
|
46
|
+
|
|
47
|
+
// Create canvas for processing
|
|
48
|
+
const canvas = document.createElement("canvas");
|
|
49
|
+
const context = canvas.getContext("2d");
|
|
50
|
+
canvas.width = width;
|
|
51
|
+
canvas.height = height;
|
|
52
|
+
|
|
53
|
+
// Apply tint
|
|
54
|
+
if (this.tint) {
|
|
55
|
+
context.fillStyle = this.tint.toCss();
|
|
56
|
+
context.fillRect(0, 0, width, height);
|
|
57
|
+
context.globalCompositeOperation = "difference";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
context.drawImage(image, 0, 0, width, height);
|
|
61
|
+
return canvas;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Use the custom layer
|
|
67
|
+
const customLayer = new TintLayer({
|
|
68
|
+
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
|
|
69
|
+
tint: new Color("#132178"),
|
|
70
|
+
title: "Custom Tinted Layer"
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
map.add(customLayer);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Watch Property Changes
|
|
77
|
+
```javascript
|
|
78
|
+
const CustomLayer = BaseTileLayer.createSubclass({
|
|
79
|
+
properties: {
|
|
80
|
+
customProperty: null
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
initialize() {
|
|
84
|
+
reactiveUtils.watch(
|
|
85
|
+
() => this.customProperty,
|
|
86
|
+
() => {
|
|
87
|
+
this.refresh(); // Refresh tiles when property changes
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Custom DynamicLayer
|
|
95
|
+
|
|
96
|
+
### Dynamic Layer from Canvas
|
|
97
|
+
```javascript
|
|
98
|
+
import BaseDynamicLayer from "@arcgis/core/layers/BaseDynamicLayer.js";
|
|
99
|
+
|
|
100
|
+
const CustomDynamicLayer = BaseDynamicLayer.createSubclass({
|
|
101
|
+
properties: {
|
|
102
|
+
data: null
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Called when layer needs to render
|
|
106
|
+
getImageUrl(extent, width, height) {
|
|
107
|
+
// Create canvas with specified dimensions
|
|
108
|
+
const canvas = document.createElement("canvas");
|
|
109
|
+
canvas.width = width;
|
|
110
|
+
canvas.height = height;
|
|
111
|
+
const context = canvas.getContext("2d");
|
|
112
|
+
|
|
113
|
+
// Draw your custom visualization
|
|
114
|
+
this.drawVisualization(context, extent, width, height);
|
|
115
|
+
|
|
116
|
+
return canvas.toDataURL("image/png");
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
drawVisualization(ctx, extent, width, height) {
|
|
120
|
+
// Convert geographic coordinates to pixel coordinates
|
|
121
|
+
const xScale = width / (extent.xmax - extent.xmin);
|
|
122
|
+
const yScale = height / (extent.ymax - extent.ymin);
|
|
123
|
+
|
|
124
|
+
// Draw data points
|
|
125
|
+
this.data.forEach(point => {
|
|
126
|
+
const x = (point.x - extent.xmin) * xScale;
|
|
127
|
+
const y = height - (point.y - extent.ymin) * yScale;
|
|
128
|
+
|
|
129
|
+
ctx.beginPath();
|
|
130
|
+
ctx.arc(x, y, 5, 0, Math.PI * 2);
|
|
131
|
+
ctx.fillStyle = "red";
|
|
132
|
+
ctx.fill();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Custom Elevation Layer
|
|
139
|
+
|
|
140
|
+
### Exaggerated Elevation
|
|
141
|
+
```javascript
|
|
142
|
+
import BaseElevationLayer from "@arcgis/core/layers/BaseElevationLayer.js";
|
|
143
|
+
|
|
144
|
+
const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
|
|
145
|
+
properties: {
|
|
146
|
+
exaggeration: 2
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
load() {
|
|
150
|
+
this._elevation = new ElevationLayer({
|
|
151
|
+
url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
|
|
152
|
+
});
|
|
153
|
+
this.addResolvingPromise(this._elevation.load());
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
fetchTile(level, row, col, options) {
|
|
157
|
+
return this._elevation.fetchTile(level, row, col, options)
|
|
158
|
+
.then((data) => {
|
|
159
|
+
const exaggeration = this.exaggeration;
|
|
160
|
+
for (let i = 0; i < data.values.length; i++) {
|
|
161
|
+
data.values[i] *= exaggeration;
|
|
162
|
+
}
|
|
163
|
+
return data;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Custom Blend Layer
|
|
170
|
+
|
|
171
|
+
### Blending Multiple Layers
|
|
172
|
+
```javascript
|
|
173
|
+
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
|
|
174
|
+
|
|
175
|
+
const BlendLayer = Layer.createSubclass({
|
|
176
|
+
properties: {
|
|
177
|
+
layers: null,
|
|
178
|
+
blendMode: "multiply"
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
createLayerView(view) {
|
|
182
|
+
if (view.type === "2d") {
|
|
183
|
+
return new BlendLayerView2D({
|
|
184
|
+
view: view,
|
|
185
|
+
layer: this
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const BlendLayerView2D = BaseLayerViewGL2D.createSubclass({
|
|
192
|
+
render(renderParameters) {
|
|
193
|
+
// Custom WebGL rendering
|
|
194
|
+
const gl = renderParameters.context;
|
|
195
|
+
// ... WebGL operations
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## WebGL Custom Rendering
|
|
201
|
+
|
|
202
|
+
### Custom LayerView with WebGL
|
|
203
|
+
```javascript
|
|
204
|
+
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
|
|
205
|
+
|
|
206
|
+
const CustomLayerView2D = BaseLayerViewGL2D.createSubclass({
|
|
207
|
+
constructor() {
|
|
208
|
+
this.bindAttach = this.attach.bind(this);
|
|
209
|
+
this.bindDetach = this.detach.bind(this);
|
|
210
|
+
this.bindRender = this.render.bind(this);
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
attach() {
|
|
214
|
+
const gl = this.context;
|
|
215
|
+
|
|
216
|
+
// Create shaders
|
|
217
|
+
const vertexSource = `
|
|
218
|
+
attribute vec2 a_position;
|
|
219
|
+
uniform mat3 u_transform;
|
|
220
|
+
void main() {
|
|
221
|
+
gl_Position = vec4((u_transform * vec3(a_position, 1)).xy, 0, 1);
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
const fragmentSource = `
|
|
226
|
+
precision mediump float;
|
|
227
|
+
uniform vec4 u_color;
|
|
228
|
+
void main() {
|
|
229
|
+
gl_FragColor = u_color;
|
|
230
|
+
}
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
// Compile shaders and create program
|
|
234
|
+
this.program = this.createProgram(gl, vertexSource, fragmentSource);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
render(renderParameters) {
|
|
238
|
+
const gl = renderParameters.context;
|
|
239
|
+
const state = renderParameters.state;
|
|
240
|
+
|
|
241
|
+
// Set up transformation matrix
|
|
242
|
+
const transform = mat3.create();
|
|
243
|
+
mat3.translate(transform, transform, [state.size[0] / 2, state.size[1] / 2]);
|
|
244
|
+
|
|
245
|
+
gl.useProgram(this.program);
|
|
246
|
+
// ... draw operations
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
detach() {
|
|
250
|
+
const gl = this.context;
|
|
251
|
+
gl.deleteProgram(this.program);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## LERC Decoding
|
|
257
|
+
|
|
258
|
+
### Custom LERC Layer
|
|
259
|
+
```javascript
|
|
260
|
+
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
261
|
+
import esriRequest from "@arcgis/core/request.js";
|
|
262
|
+
|
|
263
|
+
// Import LERC decoder
|
|
264
|
+
import * as Lerc from "https://cdn.jsdelivr.net/npm/lerc@4.0.4/+esm";
|
|
265
|
+
|
|
266
|
+
const LercLayer = BaseTileLayer.createSubclass({
|
|
267
|
+
properties: {
|
|
268
|
+
urlTemplate: null,
|
|
269
|
+
minValue: 0,
|
|
270
|
+
maxValue: 1000
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
fetchTile(level, row, col, options) {
|
|
274
|
+
const url = this.urlTemplate
|
|
275
|
+
.replace("{z}", level)
|
|
276
|
+
.replace("{x}", col)
|
|
277
|
+
.replace("{y}", row);
|
|
278
|
+
|
|
279
|
+
return esriRequest(url, {
|
|
280
|
+
responseType: "array-buffer",
|
|
281
|
+
signal: options?.signal
|
|
282
|
+
}).then((response) => {
|
|
283
|
+
// Decode LERC data
|
|
284
|
+
const decodedPixels = Lerc.decode(response.data);
|
|
285
|
+
const { width, height, pixels } = decodedPixels;
|
|
286
|
+
|
|
287
|
+
// Convert to canvas
|
|
288
|
+
const canvas = document.createElement("canvas");
|
|
289
|
+
canvas.width = width;
|
|
290
|
+
canvas.height = height;
|
|
291
|
+
const ctx = canvas.getContext("2d");
|
|
292
|
+
const imageData = ctx.createImageData(width, height);
|
|
293
|
+
|
|
294
|
+
// Map values to colors
|
|
295
|
+
for (let i = 0; i < pixels[0].length; i++) {
|
|
296
|
+
const value = pixels[0][i];
|
|
297
|
+
const normalized = (value - this.minValue) / (this.maxValue - this.minValue);
|
|
298
|
+
const color = this.valueToColor(normalized);
|
|
299
|
+
|
|
300
|
+
imageData.data[i * 4] = color.r;
|
|
301
|
+
imageData.data[i * 4 + 1] = color.g;
|
|
302
|
+
imageData.data[i * 4 + 2] = color.b;
|
|
303
|
+
imageData.data[i * 4 + 3] = 255;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ctx.putImageData(imageData, 0, 0);
|
|
307
|
+
return canvas;
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
valueToColor(value) {
|
|
312
|
+
// Simple blue-to-red color ramp
|
|
313
|
+
return {
|
|
314
|
+
r: Math.round(value * 255),
|
|
315
|
+
g: 0,
|
|
316
|
+
b: Math.round((1 - value) * 255)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Custom Graphics Rendering
|
|
323
|
+
|
|
324
|
+
### Animated Lines with WebGL
|
|
325
|
+
```javascript
|
|
326
|
+
// Animated lines require custom render nodes in 3D
|
|
327
|
+
// or custom LayerView in 2D
|
|
328
|
+
|
|
329
|
+
const view = new SceneView({
|
|
330
|
+
container: "viewDiv",
|
|
331
|
+
map: map
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Use external render node for animations
|
|
335
|
+
view.when(() => {
|
|
336
|
+
// Add custom render node
|
|
337
|
+
const renderNode = new CustomRenderNode({ view });
|
|
338
|
+
view.environment.lighting = {
|
|
339
|
+
type: "virtual"
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Tessellation Helpers
|
|
345
|
+
|
|
346
|
+
### Using Tessellation for Complex Geometries
|
|
347
|
+
```javascript
|
|
348
|
+
import tessellate from "@arcgis/core/geometry/support/tessellate.js";
|
|
349
|
+
|
|
350
|
+
// Tessellate a polygon for WebGL rendering
|
|
351
|
+
const polygon = new Polygon({
|
|
352
|
+
rings: [[/* coordinates */]]
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const tessellated = tessellate(polygon);
|
|
356
|
+
// Use tessellated.vertices and tessellated.indices for WebGL
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Complete Example
|
|
360
|
+
|
|
361
|
+
```html
|
|
362
|
+
<!DOCTYPE html>
|
|
363
|
+
<html>
|
|
364
|
+
<head>
|
|
365
|
+
<link rel="stylesheet" href="https://js.arcgis.com/4.34/esri/themes/light/main.css" />
|
|
366
|
+
<script src="https://js.arcgis.com/4.34/"></script>
|
|
367
|
+
<style>
|
|
368
|
+
html, body, #viewDiv { height: 100%; margin: 0; }
|
|
369
|
+
</style>
|
|
370
|
+
<script type="module">
|
|
371
|
+
import Map from "@arcgis/core/Map.js";
|
|
372
|
+
import SceneView from "@arcgis/core/views/SceneView.js";
|
|
373
|
+
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
|
|
374
|
+
import esriRequest from "@arcgis/core/request.js";
|
|
375
|
+
import Color from "@arcgis/core/Color.js";
|
|
376
|
+
|
|
377
|
+
// Create custom tinted tile layer
|
|
378
|
+
const TintLayer = BaseTileLayer.createSubclass({
|
|
379
|
+
properties: {
|
|
380
|
+
urlTemplate: null,
|
|
381
|
+
tint: { value: null, type: Color }
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
getTileUrl(level, row, col) {
|
|
385
|
+
return this.urlTemplate
|
|
386
|
+
.replace("{z}", level)
|
|
387
|
+
.replace("{x}", col)
|
|
388
|
+
.replace("{y}", row);
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
fetchTile(level, row, col, options) {
|
|
392
|
+
return esriRequest(this.getTileUrl(level, row, col), {
|
|
393
|
+
responseType: "image",
|
|
394
|
+
signal: options?.signal
|
|
395
|
+
}).then((response) => {
|
|
396
|
+
const canvas = document.createElement("canvas");
|
|
397
|
+
const ctx = canvas.getContext("2d");
|
|
398
|
+
canvas.width = canvas.height = this.tileInfo.size[0];
|
|
399
|
+
|
|
400
|
+
if (this.tint) {
|
|
401
|
+
ctx.fillStyle = this.tint.toCss();
|
|
402
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
403
|
+
ctx.globalCompositeOperation = "difference";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
ctx.drawImage(response.data, 0, 0, canvas.width, canvas.height);
|
|
407
|
+
return canvas;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const customLayer = new TintLayer({
|
|
413
|
+
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
|
|
414
|
+
tint: new Color("#4488ff"),
|
|
415
|
+
title: "Custom Layer"
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const map = new Map({ layers: [customLayer] });
|
|
419
|
+
|
|
420
|
+
const view = new SceneView({
|
|
421
|
+
container: "viewDiv",
|
|
422
|
+
map: map,
|
|
423
|
+
center: [8.5, 46],
|
|
424
|
+
zoom: 8
|
|
425
|
+
});
|
|
426
|
+
</script>
|
|
427
|
+
</head>
|
|
428
|
+
<body>
|
|
429
|
+
<div id="viewDiv"></div>
|
|
430
|
+
</body>
|
|
431
|
+
</html>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Common Pitfalls
|
|
435
|
+
|
|
436
|
+
1. **CORS**: External tile servers must support CORS
|
|
437
|
+
|
|
438
|
+
2. **Canvas size**: Match tile size exactly with tileInfo
|
|
439
|
+
|
|
440
|
+
3. **Memory management**: Clean up WebGL resources in detach()
|
|
441
|
+
|
|
442
|
+
4. **Async operations**: Handle abort signals properly
|
|
443
|
+
|
|
444
|
+
5. **Coordinate systems**: Transform coordinates correctly for rendering
|
|
445
|
+
|