Introduction to layer extensibility
The ArcGIS Maps SDK for JavaScript provides a number of predefined layers. These layers retrieve images or data from servers and display them in the view.
In addition to this sample, the following samples demonstrate the basic fundamentals of creating custom layers.
- Custom BlendLayer
- Custom LERC Layer
- Custom DynamicLayer
- Custom ElevationLayer - Exaggerating elevation
- Custom ElevationLayer - Thematic data as elevation
Creating custom layers is useful when you need to:
- display data from a source not exclusively supported in the ArcGIS Maps SDK for JavaScript
- preprocess data before it is displayed in the view (this could be because the service returns binary data, which needs to be processed to generate an image)
- create custom visualizations not explicitly supported in the API
- display synthesized data such as an exaggerated elevation
Writing a custom layer enables you to support the drawing of new data formats. Before you get started on creating custom layers, it is helpful to have some familiarity with the following topics:
Create a custom tile layer
This sample demonstrates how to create a custom TileLayer from OpenTopoMap's style. Tile layers are composed of images, such as satellite imagery, which are square tiles stitched together in columns and rows, giving the layer the appearance that it is one continuous image. These layers have several levels of detail (LOD) that permit users to zoom to any region of the map and load more tiles that depict features in higher resolution at larger map scales.
To create a custom tile layer, you must call the createSubclass() method on the
BaseTileLayer class. We'll name the custom layer Tint
.
Since this layer needs to know where to access predefined tiles, we will create a url
property. The application will provide the url
value for the layer,
and the layer will fetch tiles from the generated URL. The difference
blending operation is then applied to the OpenTopoMap tiles prior to displaying each image. We will create a tint
property on this layer so that the application can specify a color,
which will be used in the blending operation.
const TintLayer = BaseTileLayer.createSubclass({
// set up the properties specific to this layer
properties: {
// url to the tiles, provided by the application
urlTemplate: null,
// tint color that will be used to change
// OpenTopoMap's tiles
// value provided by the application
tint: {
value: null,
type: Color
}
}
// override necessary methods here
});
Users will be able to change the tint
color at runtime by picking a color from the Calcite color picker component.
The reactiveUtils.watch method watches the tint layer's tint
property and will respond to a change by calling the refresh() method.
// watch the tint property and refresh the layer if the tint values
// change at runtime.
const TintLayer = BaseTileLayer.createSubclass({
...
initialize() {
reactiveUtils.watch(
()=> this.tint,
()=>{
this.refresh();
});
},
...
});
You extend BaseTileLayer two different ways:
- Request tiles as they are predefined from a data source
- Images or data need to be pre-processed prior to display in the view
Request tiles as they are predefined from a data source
To request images as they are predefined from a data source, overwrite the getTileUrl() method so it returns the URL for the requested tile at a given level, row, and column.
const MyCustomTileLayer = BaseTileLayer.createSubclass({
// properties of the custom tile layer
properties: {
urlTemplate: null
},
// override getTileUrl()
// generate the tile url for a given level, row and column
getTileUrl: function(level, row, col) {
return this.urlTemplate
.replace("{z}", level)
.replace("{x}", col)
.replace("{y}", row);
}
});
Images or data need preprocessing prior to display in the view
If data or tiles need to be preprocessed prior to display, then override the fetchTile() method. This sample uses this approach by fetching OpenTopoMap's tiles and applying the difference blending operation with a given color to the canvas. Once the image and color are blended, the final result will display in the view.
// This method fetches tiles for the specified level and size.
// Override this method to process the data returned from the server.
fetchTile: function (level, row, col, options) {
// call getTileUrl() method to construct the URL to tiles
// for a given level, row and col provided by the LayerView
const url = this.getTileUrl(level, row, col);
// request for tiles based on the generated url
// the signal option ensures that obsolete requests are aborted
return esriRequest(url, {
responseType: "image",
signal: options && options.signal
})
.then(function (response) {
// when esri request resolves successfully
// get the image from the response
const image = response.data;
const width = this.tileInfo.size[0];
const height = this.tileInfo.size[0];
// create a canvas with 2D rendering context
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// Apply the tint color provided by
// by the application to the canvas
if (this.tint) {
// Get a CSS color string in rgba form
// representing the tint Color instance.
context.fillStyle = this.tint.toCss();
context.fillRect(0, 0, width, height);
// Applies "difference" blending operation between canvas
// and OpenTopoMap tiles. Difference blending operation subtracts
// the bottom layer (canvas) from the top layer (tiles) or the
// other way round to always get a positive value.
context.globalCompositeOperation = "difference";
}
// Draw the blended image onto the canvas.
context.drawImage(image, 0, 0, width, height);
return canvas;
}.bind(this));
}
Using the custom tile layer in JavaScript application
Once the custom layer is created, you can add it to the layers of the Map and add the map to a MapView or SceneView instance.
// Create a new instance of the TintLayer and set its properties.
const openTopoMapTileLayer = new TintLayer({
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
tint: "#132178", // blue color
title: "OpenTopoMap"
});
const map = new Map({
layers: [openTopoMapTileLayer]
});
const view = new SceneView({
container: "viewDiv",
map: map,
center: [0, 30],
zoom: 3
});