Overview
This sample demonstrates how to create a custom ElevationLayer by creating a subclass of BaseElevationLayer. In some cases, you may want to exaggerate elevation in a SceneView to make it more pronounced at smaller scales.
No exaggeration (TopoBathy3D elevation service)
Elevation exaggerated 70x with custom layer
Creating a custom elevation layer
To create a custom ElevationLayer you must call createSubclass() on BaseElevationLayer. We'll name the custom layer Exaggerated
.
Since this sample exaggerates existing elevation values, we require ElevationLayer and load the default ArcGIS TopoBathy3D service as a dependency of the custom layer. This is done inside the load() method.
We'll also create an exaggeration
property to specify the factor by which we'll exaggerate the elevation.
const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
properties: {
// exaggerates the actual elevations by 70x
exaggeration: 70
},
load: function() {
this._elevation = new ElevationLayer({
url: "//elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/TopoBathy3D/ImageServer"
});
// wait for the elevation layer to load before resolving load()
this.addResolvingPromise(
this._elevation.load().then(() => {
// get tileInfo, spatialReference and fullExtent from the elevation service
// this is required for elevation services with a custom spatialReference
this.tileInfo = this._elevation.tileInfo;
this.spatialReference = this._elevation.spatialReference;
this.fullExtent = this._elevation.fullExtent;
})
);
return this;
}
});
Then we must manipulate the fetchTile() method of Exaggerated
so that it returns the proper values for each pixel. We can fetch the actual elevation values using ElevationLayer.fetchTile(), multiply them by the exaggeration
value, and return them to fetch
since the data object specification for fetch
in Elevation
is the same as it is for Base
. The final code for the custom elevation layer should look like the following.
const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
properties: {
// exaggerates the actual elevations by 70x
exaggeration: 70
},
load: function() {
this._elevation = new ElevationLayer({
url: "//elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/TopoBathy3D/ImageServer"
});
// wait for the elevation layer to load before resolving load()
this.addResolvingPromise(
this._elevation.load().then(() => {
// get tileInfo, spatialReference and fullExtent from the elevation service
// this is required for elevation services with a custom spatialReference
this.tileInfo = this._elevation.tileInfo;
this.spatialReference = this._elevation.spatialReference;
this.fullExtent = this._elevation.fullExtent;
})
);
return this;
},
// Fetches the tile(s) visible in the view
fetchTile: function(level, row, col, options) {
return this._elevation.fetchTile(level, row, col, options).then(
function(data) {
const exaggeration = this.exaggeration;
for (let i = 0; i < data.values.length; i++) {
data.values[i] = data.values[i] * exaggeration;
}
return data;
}.bind(this)
);
}
});
Once the layer is created, you must add it to the layers of the Map.ground property and add the map to a SceneView instance.
const map = new Map({
basemap: basemap,
ground: {
layers: [new ExaggeratedElevationLayer()]
}
});
sceneView.map = map;
For this sample we use the virtualLighting, which makes sure that the scene is always nicely lit and there are no parts of the globe in shadow. The position of the light follows the camera and is set behind the camera with a small offset to the left side.
view.environment.lighting = {
type: "virtual" // autocasts as new VirtualLighting()
}