I was recently tasked to create a custom basemap layer for our apps that combine some of ESRI's published Vector Basemaps and one of their hillshade basemaps. To accomplish, I did something like this:
/**
* Returns a custom ESRI Basemap
*
* @returns {Promise<Object>} ESRI Basemap
*/
export async function createCustomHillshadeBasemap() {
const [Basemap, esriRequest, TileLayer, VectorTileLayer] = await loadModules([
"esri/Basemap",
"esri/request",
"esri/layers/TileLayer",
"esri/layers/VectorTileLayer",
]);
let labelStyle;
let canvasStyle;
// The label style used by the World ESRI Dark Gray Canvas (taken from looking at network request, is this subject to change?):
let labelStyleUrl = "https://www.arcgis.com/sharing/rest/content/items/747cb7a5329c478cbe6981076cc879c5/resources/styles/root.json";
labelStyle = await esriRequest(labelStyleUrl, {
responseType: "json",
});
// The canvas style used by the World ESRI Dark Gray Canvas:
let canvasStyleUrl =
"https://www.arcgis.com/sharing/rest/content/items/5e9b3685f4c24d8781073dd928ebda50/resources/styles/root.json";
canvasStyle = await esriRequest(canvasStyleUrl, {
responseType: "json",
});
let baseLayers = [
new VectorTileLayer({
effect: "opacity(80%) brightness(90%)",
style: canvasStyle.data,
title: "darkCanvas",
url: "https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer",
}),
new VectorTileLayer({
style: labelStyle.data,
title: "labels",
url: "https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer",
}),
];
baseLayers.unshift(
new TileLayer({
title: "hillshade",
url: "https://services.arcgisonline.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer",
})
);
const basemap = new Basemap({
baseLayers: baseLayers,
id: "basemap",
title: "basemap",
});
return basemap;
}
Above works well. But note at line 25 I am using a url to json that I found ESRI uses for its basemap vector styling rules. The only way I encountered this was by inspecting the network requests in my browser when loading this layer. What I'm wondering, is: will this change? Is that style url something we can count on as a constant or will what that entails change without notice (possible concern)? Or is it possible that the url suddenly changes (note that hex string as part of the url) and we're suddenly referencing a dead endpoint (bigger concern)?
I didn't see any documentation on this so wondering if there are any pitfalls I should be aware of. Does ESRI want us to make our own styles and publish them ourselves (I hope not) and/or is there a more recommended way to create a custom basemap layer in ArcGIS JavaScript API that does not require specifying the style url for the basemap if we just want to combine a couple of ESRI's already published sources?
Solved! Go to Solution.
That json is just an item that lives in ArcGIS Online. That won't change, and I think style updates are done as new items, so yes it's constant. Can you accomplish the same thing with the VT Style Editor? You can create your own style to use in your apps, and if you need to finetune it, you can edit the JSON in the editor.
Ok, I think you can simplify this rather try to grab the style like that. All Basemaps are published in LivingAtlas.
https://livingatlas.arcgis.com/en/browse
All Basemaps are published as WebMaps, but we have a Basemap class in the API you can use for it.
https://developers.arcgis.com/javascript/latest/api-reference/esri-Basemap.html
You can create the Basemap from the LivingAtlas item you found, then add your hillshade into the baseLayers. The add() method takes an index as the second argument.
https://developers.arcgis.com/javascript/latest/api-reference/esri-core-Collection.html#add
Then you can get a map like this.
https://codepen.io/pen?editors=0010
That might be easier so you don't need to manually load the style file, and create the VT layers.
async function load() {
const basemap = new Basemap({
portalItem: {
id: "358ec1e175ea41c3bf5c68f0da11ae2b"
}
});
const hillshade = new TileLayer({
portalItem: {
id: "1b243539f4514b6ba35e7d995890db1d"
}
});
await basemap.load();
basemap.baseLayers.forEach((layer) => {
layer.effect = "opacity(80%) brightness(90%)";
});
basemap.baseLayers.add(hillshade, 0);
const map = new ArcGISMap({
basemap
});
const view = new MapView({
container: "viewDiv",
map,
center: [-118, 34],
zoom: 4
});
}
That json is just an item that lives in ArcGIS Online. That won't change, and I think style updates are done as new items, so yes it's constant. Can you accomplish the same thing with the VT Style Editor? You can create your own style to use in your apps, and if you need to finetune it, you can edit the JSON in the editor.
Thanks, I haven't much used VT Style Editor but looking at it now, I don't see a way to recreate my use case above-- where I essentially want the existing Grey Basemap Style as-is. I just want the canvas transparent and draped over another layer-- Raster Hillshade. If someone knows a trick with the VT Style Editor to combine two different basemaps (a vector and raster) and set some transparency effect on them would be great to hear. It seems to be more about editing individual layers that comprise a vector basemap, rather than combining them with raster services.
Good to know style json styles are constants and we can depend on them to be available.
Ok, I think you can simplify this rather try to grab the style like that. All Basemaps are published in LivingAtlas.
https://livingatlas.arcgis.com/en/browse
All Basemaps are published as WebMaps, but we have a Basemap class in the API you can use for it.
https://developers.arcgis.com/javascript/latest/api-reference/esri-Basemap.html
You can create the Basemap from the LivingAtlas item you found, then add your hillshade into the baseLayers. The add() method takes an index as the second argument.
https://developers.arcgis.com/javascript/latest/api-reference/esri-core-Collection.html#add
Then you can get a map like this.
https://codepen.io/pen?editors=0010
That might be easier so you don't need to manually load the style file, and create the VT layers.
async function load() {
const basemap = new Basemap({
portalItem: {
id: "358ec1e175ea41c3bf5c68f0da11ae2b"
}
});
const hillshade = new TileLayer({
portalItem: {
id: "1b243539f4514b6ba35e7d995890db1d"
}
});
await basemap.load();
basemap.baseLayers.forEach((layer) => {
layer.effect = "opacity(80%) brightness(90%)";
});
basemap.baseLayers.add(hillshade, 0);
const map = new ArcGISMap({
basemap
});
const view = new MapView({
container: "viewDiv",
map,
center: [-118, 34],
zoom: 4
});
}
Thanks you. This is much nicer. Using the portalId makes sense. Marking as answer and will test later this week in our apps to confirm.