This sample demonstrates how to create a custom tile layer by applying the multiply blending operation to the ArcGIS World Hillshade and National Geographic World Map services. The multiply
blend mode multiplies the values of each pixel of the top layer with the corresponding pixel value of the bottom layer. In this sample, multiplying the hillshade tiles with the National Geographic tiles creates a more detailed representation of the terrain than you see from the default National Geographic basemap. You can toggle the blended layer on and off using the LayerList widget in the sample. The BlendLayer is toggled on by default.
National Geographic Default | Custom Blend Layer (ArcGIS Hillshade + National Geographic) |
---|---|
To create a custom tile layer, you must call createSubclass() on BaseTileLayer. BaseTileLayer is a class designed specifically for creating custom tile layers, such as the Blend
created in this sample. When creating a Blend
we define a property called multiply
, which is an array of tile layers that will be blended together.
const BlendLayer = BaseTileLayer.createSubclass({
// multiplyLayers stores tile layers
// used as the basis for creating
// a blend layer. Layers stored in this property
// will be blended using "multiply" operation.
properties: {
multiplyLayers: null
}
});
The tile layers are added to the multiply
array. If a custom tile layer requires loadable resources, then you must load them in the layer using the load() method. This ensures that all loadable resources required for the layer to function are loaded prior to the custom layer resolving and becoming loaded
.
const BlendLayer = BaseTileLayer.createSubclass({
properties: {
multiplyLayers: null
},
// Called when the layer is added to the map
// prior to it being rendered in the view.
load: function() {
// iterate through each layer in multiplyLayers property
this.multiplyLayers.forEach((layer) => {
// call load method on each layer and add as
// a resolving promise of the custom layer.
// The tile layers must load() prior to the BlendLayer
// resolving and moving to the "loaded" status.
this.addResolvingPromise(layer.load());
}, this);
}
});
Once the layer is loaded with its required resources, we must override the fetchTile() method of the Blend
. Within this method, call fetch
on each tile layer returned in the multiply
property. Once each tile layer returns the tile(s) visible in the view, we apply multiply
operation to the tiles so that the Blend
will show the blended image.
// Fetches the tile(s) visible in the view
fetchTile: function (level, row, col, options) {
const tilePromises = this.multiplyLayers.map((layer) => {
// calls fetchTile() on the tile layers returned in multiplyLayers property
// for the tiles visible in the view
return layer.fetchTile(level, row, col, options);
});
return promiseUtils.eachAlways(tilePromises)
.then((results) => {
// Reject with abort error if the request was aborted.
// It is expected that `fetchTile` will already have rejected with abort errors
// in that case but those errors are caught by `eachAlways`, so we need to re-
// throw it
if (options && options.signal && options.signal.aborted) {
throw promiseUtils.createAbortError();
}
// create a canvas
const width = this.tileInfo.size[0];
const height = this.tileInfo.size[0];
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// multiply - multiplies the numbers for each pixel of the top layer (nat geo)
// with the corresponding pixel for the bottom layer (hillshade).
context.globalCompositeOperation = "multiply";
results.forEach((result) => {
const image = result.value;
context.drawImage(image, 0, 0, width, height);
});
return canvas;
});
}