Convert a 2D Choropleth Map from Dash into 3D using OpenCV and Aerialod

Danish Wang
5 min readOct 6, 2023

--

Chapter 1: Background Story

Few months ago, I came across a course created by Alasdair Rae.
As stated in the description of the course:

This short course is for anyone who wants to learn how to make stunning 3D visualisations of terrain data, population density data, or any other kind of data that varies across geographic space. In fact, it doesn’t even have to be geographic data — you can use it to visualise any raster image (e.g. png files, jpg files, tif files) in incredible detail.

The instructor will explain how to make stunning 3D visualizations from any raster image. Therefore, I took the course. And after taking the course, I was interested in doing the same project with a different theme. Therefore, I used my previous Dash project as experimental material.

Chapter 2: Doing the Experiment

2.1 Preparation

Make sure you already have the OpenCV module installed in your Python environment, and already downloaded Aerialod app from here.

2.2 Take an image from the Dash App

The Dash app has a feature that allows us to download images from any graph shown on the screen.

Just click the icon showed by the image above to download it. You can rename the image file if you want to. I renamed it with Indonesia Archipelago.png

2.3 Convert the Image into Heightmap

A heightmap is a grayscale image that is used to represent the elevation of a terrain. Each pixel in the image represents a different elevation, with black pixels representing the lowest elevations and white pixels representing the highest elevations.

Now let’s use OpenCV module to convert the image into grayscale.

OpenCV has cvtColor() method to be used as a color converter. It takes two parameters, first is the image we want to convert, second is the conversion code.

import cv2

# Load the RGB density image
rgb_image = cv2.imread('C:\\Projects\\Researches\\Indonesia Archipelago.png')

# Convert the RGB image into Grayscale image
gray_image = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2GRAY)

Later in the Aerialod, I want the area with higher percentage of poverty should be represented with higher terrain. As stated before:

A heightmap is a grayscale image … with black pixels representing the lowest elevations and white pixels representing the highest elevations.

Unfortunately, the result of the grayscale image does not satisfy the condition. Here is the result:

We need to invert the color.

2.4 Invert the Color

To invert the color, we only need to apply inverted image formula, that is:

255 - pixel value of the grayscaled image

The reasoning behind the inverted image formula is based on the way that digital images are stored. Digital images are stored as a grid of pixels, where each pixel has a value that represents the brightness of the pixel. The pixel values can range from 0 to 255, where 0 represents black and 255 represents white.

When an image is inverted, the pixel values are reversed. This means that the pixels that were originally black become white, and the pixels that were originally white become black. The inverted image formula achieves this by subtracting the original pixel value from 255. This results in a new pixel value that is the opposite of the original pixel value.

So, the syntax would be:

inverted_image = 255 - gray_image

Please put in mind that gray_image is a NumPy array data type that contains each pixel from the grayscaled image. Any scalar arithmetic operation on the array will be done elementwise, and we don’t have to implement any iteration method explicitly.

This is the final result of the inverted image:

Now, let’s put it into Aerialod!

2.5 Aerialod

Aerialod, put it simply, is a 3D renderer heightmap image. To use it, we only need to extract the compressed file, and run the Aerialod.exe file located in the main folder.

This is the default screen when you run it:

Now let’s just drag-and-drop the image into the screen.

This is the result:

Well, I‘m not happy with the result. The height of the ocean area is higher than some of the terrain. I think the issue was because of the pixel value of the ocean area, it was not low enough so it was treated like a high terrain compared to some islands.

Let’s fix it.

2.6 Fixing the Issue

First, I need to know the pixel value of the ocean. Let’s take a pixel sample at (0, 0) of the screen.

Simply use this syntax to print it out:

print(gray_image[0][0])

And we see the value is 216:

Because all of the ocean area shown in the previous image is at the same level, I think it is safe to assume that the ocean area has the pixel value of 216.

To highlight the ocean in the inverted image, I will convert the ocean’s pixel value to 255. This will result in a pixel value of 0 when inverted.

Just add this code snippet right before the inversion step:

for i in range(gray_image.shape[0]):
for j in range(gray_image.shape[1]):
if gray_image[i][j] == 216:
gray_image[i][j] = 255

This is the final result:

I notice that the coastlines of the islands are highlighted with bright colors. This may create noise at the edges of the islands, but I think it should not be a major problem for now. Let’s do a demo!

Chapter 3: Final Result Demo

--

--