This article describes the process I followed in order to produce this Japan 3D Anaglyph Map. Please note that in order to follow along you must have a basic knowledge of HTML, CSS and JavaScript. You must also have a pair of RED/CYAN glasses!

I have always been obsessed with 3D visualization, from 3D stereo photography to anaglyph 3D and everything in between. It all started at early ages with the classic toy View-Master, it evolved in the University at the photo-interpretation and photo-geology modules and it is still a field I am trying to understand and leverage.

So far I have experimented a little bit with imagery in ArcGIS Pro and ArcGIS Earth, following this 3D Brain Hack blog post by John Nelson, applying the RED/CYAN Anaglyph rendering and presenting some results at the 27th ArcUser Conference in Greece (2019) with a 3D Anaglyph Map Story Map.

I recently read this post by Esri on Instagram that directed me to this WebMap on ArcGIS Online where I got surprised and impressed to realise that this was actually a publicly shared hillshade, in Tile layer format, that covers the overall country of Japan in 3D Anaglyph. Wow! I immediately grabbed my RED/CYAN glasses and started to navigate the map in 3D. Mountain ridges, volcanoes and other geoformations of the amazing Japanese relief were popping out the screen! What an inspiration!

I then thought I had not written a single line of code for a very long time and that I had been left behind with the latest releases of the ArcGIS API for JavaScript. So why not produce something cool with this tile layer?

Eventually, I opened my code editor and started to mash up layers and widgets and stuff. The whole process didn’t take me more than eight hours, mostly because I used already made services from ArcGIS Online and from the Living Atlas.

I was so excited that I decided to share my approach by writting this article. I left unminified the code of the deployed app and I didn’t obfuscate the JavaScript so anyone may see it and benefit from it. I have also produced a Pen in CodePen, where you may further experiment with it.

At the following paragraphs I describe my process, broken to progressing pieces of code snippets.

The HTML

The base of all is the HTML document. It calls the necessary .css and .js functions from the API and it also holds the elements that visualise the map and everything on the map.

You may see the HTML document by clicking Ctrl + U. I will not spend time describing the entire HTML code, apart from the most important lines:

  • line 35, which calls the dark theme .css from the API,
  • line 36, which calls my .css that renders the elements I have pushed on the map,
  • line 38, which calls the .js from the API,
  • line 39, which calls my .js, where all the magic happens,
  • line 44, where the <div> element with id=”viewDiv” that will hold the map,
  • line 46 where the <div> element with id=”mapTitle” that will hold the title of the map and
  • line 53 where the <div> element with id=”logo” that will hold my logo.

The CSS

You may see the CSS document here. Again, there is no need to describe the CSS. It just defines some styling of the map elements.

The JavaScript

You may see the JavaScript document here. At the first lines I declare all available blend modes as variables. I do this in order to easily experiment with the various blend modes and their combinations, at the following steps.

I also declare as variable the coordinates of the default center of the map view in latitude and longitude.

Then, I call some modules from the API. These modules are the necessary ones for a map/view creation, a couple of widgets and the layer types which the WebMap is about to consume.

/* Request the necessary modules from the API */
require([
        'esri/Map',
        'esri/views/MapView',
        'esri/widgets/Home',
        'esri/widgets/Fullscreen',
        'esri/layers/TileLayer',
        'esri/layers/WebTileLayer',
        'esri/layers/VectorTileLayer'
    ],

Then I initiate the main function, which applies these modules and accomodates all variables and events that will follow.

/* Initiate the main function */
        function (
            Map,
            MapView,
            Home,
            Fullscreen,
            TileLayer,
            WebTileLayer,
            VectorTileLayer
        )
         {

The base layer for the WebMap is the classic World Imagery service. I call it via its url and I assign it to a new TileLayer variable.

/* World Imagery */
const imagery = new TileLayer({
  url:'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
  blendMode:'color-burn',
  opacity:0.8,
  copyright:'World Imagery'
});

Provided I have already instantiated the map and view variables (see snippets at the last paragraphs) I declare the imagery to be a layer of the map, so I get this picture:

I notice that it is too vibrant and saturated and that it should be less prominent when I superimpose the 3D Anaglyph layer.

So, at its parameters I change its blend mode to color-burn and, in order to have it working, I also change the overall map’s background to a subtle gray, because this is the color with which it will blend. I also slightly decrease its opacity.

At the moment it doesn’t look right, but this is only to serve as a basemap for the 3D Anaglyph Layer.

Now it is time to call the 3D Anaglyph layer. For this I create a new WebTileLayer variable to which I declare the url to be that of the 3D Anaglyph layer. Look at that url. At the end it has the part {level}/{col}/{row}. This is where the information about the tiles is stored.

/* 3D Anaglyph Hillshade */
const anaglyph = new WebTileLayer({
  urlTemplate:'https://maps.gsi.go.jp/xyz/anaglyphmap_gray/{level}/{col}/{row}.png',
  blendMode:'multiply',
  opacity:1.0,
  copyright:'Anaglyph Gray'
});

The moment I add this as a layer on the map, I get to see it on the browser. Put on some RED/CYAN Glasses and start navigating the map. The relief starts to pop-out of the screen!

But without the imagery as a basemap I don’t think it is that informative. So I simply go to its parameters and change the blend mode to multiply. Now I can clearly see both, the 3D Anaglyph and the imagery below it and, most important, the 3D Anaglyph sustains its readability because of the decreased vibrancy and saturation of the imagery!

As I pan and zoom the map I realise the vast variety of the relief. Why not add some information about the altitude. I very recently read this post on Esri’s blog about Topographic (with Contours) Multisource vector tile layers and I was really happy to find out that a World Contours layer with a Worldwide coverage has been available as a vector tile layer in ArcGIS Online!

Back in my script, I create a variable to hold the contours and I assign to its parameters the url of that layer.

/* World Contours */
const contours = new VectorTileLayer({
  url:'https://basemaps.arcgis.com/arcgis/rest/services/World_Contours_v2/VectorTileServer',
  blendMode:'normal',
  opacity:1.0,
  copyright:'World Contours'
});

The World Contours layer is added to the map and I now can see it below the 3D Anaglyph and on top of the imagery.

Lastly, I need a couple of reference layers. I select from ArcGIS Online the World Transportation layer, as well as the World Light Gray Reference layer and I add them to my script.

/* World Transportation */
const transportation = new TileLayer({ url:’https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer’,
blendMode:’luminosity’,
opacity:0.6,
copyright:’World Transportation’
});

For the World Transportation layer, particularly, I change its blend mode to luminosity, because this layer is by default served in colors, but I want it in a grayscale mode. I also decrease its opacity.

/* World Light Gray Reference */
const labels = new TileLayer({
url:'https://services.arcgisonline.com/arcgis/rest/services/Canvas/World_Light_Gray_Reference/MapServer',
  blendMode:'normal',
  opacity:1.0,
  copyright:'World Light Gray Reference'
});

Here is how all these layers look together. Not bad I think. I wear once again my RED/CYAN glasses to make sure that the prominent layer, the 3D Anaglyph, is clearly visible and legible and that no other layer is negatively interfering with it.

In my JavaScript I declare the map and the view variables. I pass all layers, created above as variables, in the parameters of the map. Order matters. The first layer at the script’s parameters will be drawn first on canvas and all other successively on top of it.

/* Map */
const map = new Map({
    layers: [
        imagery,
        contours,
        transportation,
        anaglyph,
        labels
        ]
    });

At the view parameters I select the HTML element with id=’viewDiv’ to hold the map and I also declare the scale and center coordinates of the initial extent of the map. It is also important to change the backgroung color to a light gray in order to have the imagery layer aptly blended with it. Just for fun, try to change this color to see each time how different the imagery layer will look!

/* View */
const view = new MapView({

    container:'viewDiv',
    map:map,

    scale:550000,
    center:[lng,lat],

    background: {
        color:'#f9f9f9'
        }
    });

For better user experience I also add two useful widgets, a home button and a fullscreen button.

/* Home Button */
const homeBtn = new Home({
    view: view
    });

/* Full Screen Button */
fullscreen = new Fullscreen({
    view: view
    });

/* Add the buttons to the View */
view.ui.add(homeBtn, 'top-left');
view.ui.add(fullscreen, 'top-left');

Last but not least, I create at the HTML document two elements, one with id=’mapTitle’, which will hold the title and subtitle of the map, as well as one with id=’logo’, which will hold my logo. I then pass them to the view.

/* Add the Title element to the View  */
view.ui.add('mapTitle', 'top-right');

/* Add the Logo element to the View */
view.ui.add('logo', 'bottom-right');

And I am all set up! Here is the final result:

After a couple of days of having finished the WebMap , I realised that it was the 26th day of the #30DayMapChallenge 2020 event, whose subject for that day was Map with a New Tool. I decided to dedicate it to the event by adding the relative information as a subtitle and I also posted in on my social accounts!

You may access the WebMap here https://www.prettymap.gr/japan-3d-anaglyph-map/ or scroll to the bottom of this post, where lives an embedded version of it! You may also wok with it in CodePen.

Kindest Regards from Crete, Greece

Spiros