Texture Map
Demonstration of the Texture Map Prefab used as a dot density map of airport locations in the US.
js
// SPDX-License-Identifier: Apache-2.0
// Copyright : J.P. Morgan Chase & Co.
import * as anu from '@jpmorganchase/anu';
import * as BABYLON from '@babylonjs/core';
import * as d3 from 'd3';
import data from './data/airports.csv';
//Create and export a function that takes a Babylon engine and returns a Babylon Scene
export function textureMap(engine){
//Create an empty Scene
const scene = new BABYLON.Scene(engine);
//Add some lighting
new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 10, 0), scene);
//Add a camera that rotates around the origin and adjust its properties
const camera = new BABYLON.ArcRotateCamera('Camera', 0, 0, 0, new BABYLON.Vector3(0, 0, 0), scene);
camera.position = new BABYLON.Vector3(0, 3, -0.05);
camera.wheelPrecision = 20;
camera.minZ = 0;
camera.attachControl(true);
//Use the Texture Map prefab to create a plane with an OpenLayers map canvas as the texture
let textureMap = anu.createTextureMap('map', { meshSize: 2 });
//Get the OpenLayers map object from the prefab which we will need to customize its settings
let map = textureMap.map;
//Change the view parameters of the map to focus on the US
map.getView().setCenter([-100, 40]);
map.getView().setZoom(5);
//Turn on keyboard controls on the TextureMap prefab (uses WASD and -+)
//Due to a technical quirk, this function must be called *after* setting the center and zoom of the view
textureMap.keyboardControls(scene);
//To help create our dots, the Texture Map prefab generates scale functions for us to convert lon/lat to positions in Babylon's coordinate space
let scaleLon = textureMap.scaleLon;
let scaleLat = textureMap.scaleLat;
//Create a D3 scale for color, using Anu helper functions map scale outputs to Color4 objects based on the 'schemecategory10' palette from D3
let scaleC = d3.scaleOrdinal(anu.ordinalChromatic('d310').toColor4(52));
//We use Mesh instancing here for better performance, first we create a Mesh that serves as the root Node
let rootSphere = anu.create('sphere', 'sphere', { diameter: 0.02 });
rootSphere.isVisible = false;
rootSphere.registerInstancedBuffer('color', 4); //We need an InstancedBuffer to set the color of instances
//Select our map object as a Selection object which will serve as our CoT
let chart = anu.selectName('map', scene);
//Create instanced sphere meshes from our rootSphere as children of our CoT for each row of our data and set their visual encodings using method chaining
let spheres = chart.bindInstance(rootSphere, data)
.setInstancedBuffer('color', new BABYLON.Color4(0, 0, 0, 1));
//We want to position our spheres whenever the map is loaded and updated (i.e., panned or zoomed), use OpenLayers' callback functions for this
//N.B: Texture Map's scale functions are only created after the map is fully rendered, so we need to use this callback regardless
map.on('postrender', () => {
//Create instanced sphere meshes from our rootSphere as children of our CoT for each row of our data and set their visual encodings using method chaining
spheres.positionX((d) => scaleLon([d.longitude, d.latitude])) //These scale functions need as arguments both the longitude and latitude in an array
.positionZ((d) => scaleLat([d.longitude, d.latitude])) //This is a requirement from the OpenLayers API
.setInstancedBuffer('color', (d) => scaleC(d.state))
.prop('isVisible', (d,n,i) => { //Custom function to determine if a sphere is inside or outside of the Texture Map which will show/hide the sphere as needed
let parentBoundingBox = textureMap.mesh.getBoundingInfo().boundingBox;
return !(n.position.x > parentBoundingBox.maximum.x ||
n.position.x < parentBoundingBox.minimum.x ||
n.position.z > parentBoundingBox.maximum.z ||
n.position.z < parentBoundingBox.minimum.z);
});
});
return scene;
}