Demographic Terrains


Fig 1 – Population skyline of New York - Census SF1 P0010001 and Bing Road Map

Demographic Terrain

My last blog post, Demographic Landscapes, described leveraging new SFCGAL PostGIS 3D functions to display 3D extruded polygons with X3Dom. However, there are other ways to display census demographics with WebGL. WebGL meshes are a good way to handle terrain DEM. In essence the US Census is providing demographic value terrains, so 3D terrain meshes are an intriguing approach to visualization.

Babylon.js is a powerful 3D engine written in javascript for rendering WebGL scenes. In order to use Babylon.js, US Census SF1 data needs to be added as WebGL 3D mesh objects. Babylon.js offers a low effort tool for generating these meshes from grayscale height map images: BABYLON.Mesh.CreateGroundFromHeightMap.

Modifying the Census WMS service to produce grayscale images at the highest SF1 polygon resolution i.e. tabblock, is the easiest approach to generating these meshes. I added an additional custom request to my WMS service, “request=GetHeightMap,” which returns a PixelFormat.Format32bppPArgb bitmap with demographic values coded as grayscale. This is equivalent to a 255 range classifier for population values.

Example GetHeightMap request:
http://<server>/CensusSF1/WMSService.svc/<token>/NAD83/USA/SF1QP/WMSLatLon?REQUEST=GetHeightMap&SERVICE=WMS&VERSION=1.3.0&LAYERS=TabBlock&STYLES=P0010001 &FORMAT=image/png&BGCOLOR=0xFFFFFF&TRANSPARENT=TRUE&CRS=EPSG:4326 &BBOX=39.25212350301827,-105.70779069335937,40.22049698135099,-104.23836930664062
&WIDTH=1024&HEIGHT=1024

Fig 2 - grayscale heightmap from Census SF1 P001001 at tabblock polygon level for Denver metro area

Once a grayscale image is available it can be added to the Babylon scene, as a heightmap.

/* Name
  * Height map image url
  * mesh Width
  * mesh Height
  * Number of subdivisions (increase the complexity of
this mesh in order to improve the visual quality of it)
  * Minimum height : The lowest level of the mesh
  * Maximum height : the highest level of the mesh
  * scene
  * Updatable: if this mesh can be updated dynamically in the future (Boolean)
  **/
var ground = BABYLON.Mesh.CreateGroundFromHeightMap("ground", heightmap,
256, 256, 500, 0, 10, scene, false);

WMS GetMap requests are also useful for adding a variety of textures to the generated demographic terrain.

var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
groundMaterial.diffuseTexture = new BABYLON.Texture( image, scene);

Fig 3 – texture from Bing Maps AerialWithLabels over population terrain


Sample Babylon.js Demographic Terrain Scene using ArcRotateCamera
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Babylon HeightMap</title>
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.0.min.js"></script>
    <!-- Babylon.js -->
    <script src="http://www.babylonjs.com/hand.minified-1.2.js"></script>
    <script src="http://cdn.babylonjs.com/2-1/babylon.js"></script>
    <style>
        html, body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>
<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        if (!BABYLON.Engine.isSupported()) {
            $("body").html('<div id="webglmessage"><h1>Sorry, your Browser does no implement WebGL</h1></div>');
        }
        else {
            var canvas = document.getElementById("renderCanvas");
            var engine = new BABYLON.Engine(canvas, true);

            var createScene = function () {
                var scene = new BABYLON.Scene(engine);

                // Light
                var spot = new BABYLON.SpotLight("spot", new BABYLON.Vector3(0, 30, 10), new BABYLON.Vector3(0, -1, 0), 17, 1, scene);
                spot.diffuse = new BABYLON.Color3(1, 1, 1);
                spot.specular = new BABYLON.Color3(0, 0, 0);
                spot.intensity = 0.5;

                //ArcRotateCamera Camera
                var camera = new BABYLON.ArcRotateCamera("Camera", -1.57079633, 1.0, 256, new BABYLON.Vector3.Zero(), scene);
                camera.lowerBetaLimit = 0.1;
                camera.upperBetaLimit = (Math.PI / 2) * 0.9;
                camera.lowerRadiusLimit = 30;
                camera.upperRadiusLimit = 256;
                scene.activeCamera = camera;
                scene.activeCamera.attachControl(canvas);

                var image = 'textures/NY_Map.jpg';
                var heightmap = 'textures/NY_HeightMap.jpg';

                // Ground
                var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
                groundMaterial.diffuseTexture = new BABYLON.Texture(image, scene);

                var ground = BABYLON.Mesh.CreateGroundFromHeightMap("ground", heightmap, 256, 141, 750, 0, 2.5, scene, false);
                ground.material = groundMaterial;

                // Skybox
                var skybox = BABYLON.Mesh.CreateBox("skyBox", 800.0, scene);
                var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
                skyboxMaterial.backFaceCulling = false;
                skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("textures/skybox", scene);
                skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
                skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
                skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
                skybox.material = skyboxMaterial;

                return scene;
            }

            var scene = createScene();

            engine.runRenderLoop(function () {
                scene.render();
            });

            // Resize
            window.addEventListener("resize", function () {
                engine.resize();
            });
        }
    </script>
</body>
</html>

Modifying the experimental Census UI involves adding a link button, scale dropdown, and a camera selector to the Demographics tab.

Fig 4 – “View HeightMap” button with Camera selector added to experimental Census UI

Babylon.js offers a range of cameras, including cameras for use with VR headsets.

  • ArcRotate – rotates a target pivot using mouse and cursor keys
  • Free – first person shooter camera
  • Touch – includes touch gesture events for camera control
  • WebVRFree – dual eye images for VR head set devices

WebVRFreeCamera is suited for use in Google Cardboard headsets.


Fig 5 – Babylon.js WebVRFreeCamera Census P001001 terrain


I have a $4 Google Cardboard headset on order. Soon I can try it using a Babylon.js WebVRFreeCamera to navigate population terrains.


Fig 6 - $4.00 Google cardboard kit

Microsoft HoloLens will up the coolness but not the hipster factor while improving usability immensely ( when released ). I’m inclined to the minimalist movement myself, but I’d be willing to write a Windows 10 app with the HoloLens SDK to see how well it performs.

Fig 7 - Microsoft HoloLens is a future possibility for viewing Census Terrains

Performance

Viewing performance is fine once loaded as WebGL, but producing the height map involves:

  1. (Server) PostGIS query join to return intersected tabblocks of selected demographic
  2. (Server) Polygon draw to grayscale image with population value normalized by US maximum
  3. (Client) Babylon translates grayscale to webgl mesh

This can require some patience looking at the heavenly but empty skybox for a few seconds, 5-10s on my laptop.

Future directions for inquiry

  • Compare performance with batch processed SF1 tiles. Tiles could combine a 3D vector mesh with a 2D value array to reduce size of multiple demographic tile pyramids.
  • Explore Babylon.js LOD mesh simplification.
  • Explore Babylon.js octree submeshes with multiple mesh tiles.
  • Use PostGIS MapAlgebra on multi-variate value arrays.

Fig 8 – Population view of Denver - Census SF1 P0010001 scale 3

Increasing the scale exaggerates relative population. My how Denver has grown!


Fig 9 – Population view of Denver - Census SF1 P0010001 scale 20

One thought on “Demographic Terrains

  1. Pingback: Demographic Terrains | Geo-How-To News