Skip to content

Texture Maps

Overview

The Texture map and globe prefabs provide a quick way to add geographic maps to your scene by using map layers and mesh textures. This prefab implements the OpenLayers API to provide support for map layers and geographic utilities. Additionally, these prefabs provide methods and functions to assist in mapping to Lat Lon positions and map interactions.

Usage

js
//name (required), options (optional), babylon scene (optional)

//returns instance of textureMap object
let textureMap = anu.createTextureMap(name: String, options?: {}, scene?: BABYLON.Scene);

//returns instance of textureGlobe object
let textureGlobe = anu.createTextureGlobe(name: String, options?: {}, scene?: BABYLON.Scene);

Options

Texture Map

PropertyValueDefault
layers(Tilelayers[ ]) list of map layer objects from OpenLayers[ new TileLayer({ source: new OSM() }) ]
mapHeight(number) the height of the texture in pixels1000
mapWidth(number) the width of the texture in pixels2000
meshSize(number) the scale of the ground mesh50
view(View) map view object from OpenLayersnew View({ center: [0, 0], zoom: 1 })

Texture Globe

PropertyValueDefault
layers(Tilelayers[ ]) list of map layer objects from OpenLayers[ new TileLayer({ source: new OSM(), extent: [-180, -90, 180, 90] }) ]
resolution(Vector2) the resolutions of the texture in pixels 2:1 ratio recommendednew Vector2(1000, 500)
diameter(number) the diameter of the sphere mesh1
view(View) map view object from OpenLayersnew View({projection: 'EPSG:4326', extent[-180, -90, 180, 90], center: [0, 0], zoom: 0})

Methods and Properties

Texture Map

Property / MethodDescription
containerthe DOM element holding the map texture canvas
mapthe open layers Map object
texturethe AdvancedDynamicTexture object used for the map
contextthe DOM canvas object context
meshthe ground mesh
scaleLon([lon,lat])a function that take a lat lon coordinate as a list [lon, lat] and returns the x positions
scaleLat([lon,lat])a function that take a lat lon coordinate as a list [lon, lat] and returns the y positions

Texture Globe

Property / MethodDescription
containerthe DOM element holding the map texture canvas
mapthe open layers Map object
texturethe AdvancedDynamicTexture object used for the map
contextthe DOM canvas object context
meshthe ground mesh
lonLatToVector3([lon,lat])function that take lon lat array [lon, lat] and returns a Vector3(x,y,z) position

Examples

js
// SPDX-License-Identifier: Apache-2.0
// Copyright : J.P. Morgan Chase & Co.

import * as anu from '@jpmorganchase/anu';
import * as d3 from "d3";
import { Scene, HemisphericLight, ArcRotateCamera, Vector3, Color4 } from '@babylonjs/core';
import data from './data/airports.csv'; //Our data

//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 Scene(engine);

  //Add some lighting
  new HemisphericLight('light1', new Vector3(0, 10, 0), scene);

  //Add a camera that rotates around the origin and adjust its properties
  const camera = new ArcRotateCamera("Camera", -(Math.PI / 4) * 3, Math.PI / 4, 10, new Vector3(0, 0, 0), scene);
  camera.wheelPrecision = 30; // Adjust the sensitivity of the mouse wheel's zooming
  camera.minZ = 0;            // Adjust the distance of the camera's near plane
  camera.attachControl(true); // Allow the camera to respond to user controls
  camera.position = new Vector3(0, 3, -0.25);

  //createTextureMap() is an Anu prefab that easily allows us to create a plane with an OpenLayers map canvas as the texture
  let textureMap = anu.createTextureMap('map');

  //Get the OpenLayers map object from the prefab
  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);

  //The prefab also generates scale functions for us
  //This allows us to convert longitude and latitude into their respective positions in the plane's local coordinate space in Babylon
  let scaleLon = textureMap.scaleLon;
  let scaleLat = textureMap.scaleLat;

  //Because our data has over 3000 points, we will use mesh instancing for better performance
  //Create a mesh to be our root instance, and register a buffer for color
  let rootSphere = anu.create('sphere', 'sphere', { diameter: 0.25 });
  rootSphere.isVisible = false;
  rootSphere.registerInstancedBuffer("color", 4);
  rootSphere.instancedBuffers.color = new Color4(0, 0, 0, 1);   //Placeholder color, will be overwritten later

  //Create our D3 color scale to assign colors to each data point depending on the US state they are in
  let scaleC = d3.scaleOrdinal(anu.ordinalChromatic('d310').toColor4());

  //Our map can be panned using WASD and -+ controls
  //Therefore, we need a helper function to determine if a data point is visible on the map or not
  let bounds = function (mesh) {
    //We use the bounding box of the plane mesh as our boundary for testing if a point is inside or outside of it
    let parentBoundingBox = textureMap.mesh.getBoundingInfo().boundingBox;

    return !(mesh.position.x > parentBoundingBox.maximum.x ||
             mesh.position.x < parentBoundingBox.minimum.x ||
             mesh.position.z > parentBoundingBox.maximum.z ||
             mesh.position.z < parentBoundingBox.minimum.z);
  }

  //Make an Anu selection of our map for it to serve as our CoT
  let CoT = anu.selectName('map', scene);

  //Create our spheres for our data
  let spheres = CoT.bindInstance(rootSphere, data)
    .setInstancedBuffer("color", new Color4(0, 0, 0, 1));

  //We want the spheres to updates whenever the map is panned and zoomed
  //Therefore we use OpenLayers' events to update our spheres after the map has finished rendering (postrender)
  map.on("postrender", () => {
    spheres.positionX((d) => scaleLon([d.longitude, d.latitude]))  //These scale functions need to be pased 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) => bounds(n))   //Our function from before to determine if the point should be visible or not
  });

  //Rescale the map to make it easier to see
  textureMap.scaling = new Vector3(0.05, 0.05, 0.05);

  return scene;
}
js
// SPDX-License-Identifier: Apache-2.0
// Copyright : J.P. Morgan Chase & Co.

import * as anu from '@jpmorganchase/anu';
import * as d3 from "d3";
import { Scene, HemisphericLight, ArcRotateCamera, Vector2, Vector3, Color4 } from '@babylonjs/core';
import data from './data/airports.csv'; //Our data

//Create and export a function that takes a Babylon engine and returns a Babylon Scene
export function textureGlobe(engine) {

  //Create an empty Scene
  const scene = new Scene(engine);

  //Add some lighting
  new HemisphericLight('light1', new Vector3(0, 10, 0), scene);

  //Add a camera that rotates around the origin and adjust its properties
  const camera = new ArcRotateCamera("Camera", -(Math.PI / 4) * 3, Math.PI / 4, 10, new Vector3(0, 0, 0), scene);
  camera.wheelPrecision = 30; // Adjust the sensitivity of the mouse wheel's zooming
  camera.minZ = 0;            // Adjust the distance of the camera's near plane
  camera.attachControl(true); // Allow the camera to respond to user controls
  camera.position = new Vector3(0, 2, -2.5)

  //createTextureGlobe() is an Anu prefab that easily allows us to create a sphere with an OpenLayers map canvas as the texture
  let globe = anu.createTextureGlobe('globe', { resolution: new Vector2(5000, 2500), diameter: 2 });

  //Because our data has over 3000 points, we will use mesh instancing for better performance
  //Create a mesh to be our root instance, and register a buffer for color
  let rootSphere = anu.create('sphere', 'sphere', { diameter: 0.005 });
  rootSphere.isVisible = false;
  rootSphere.registerInstancedBuffer("color", 4);
  rootSphere.instancedBuffers.color = new Color4(0, 0, 0, 1);   //Placeholder color, will be overwritten later

  //Create our D3 color scale to assign colors to each data point depending on the US state they are in
  let scaleC = d3.scaleOrdinal(anu.ordinalChromatic('d310').toColor4());

  //Make an Anu selection of our globe for it to serve as our CoT
  let CoT = anu.selectName('globe', scene);

  //Create our spheres for our data
  let spheres = CoT.bindInstance(rootSphere, data)
                   .setInstancedBuffer("color", (d) => scaleC(d.state))
                   .position((d) => globe.lonLatToVector3([d.longitude, d.latitude]));  //Helper function to convert a longitude and latitude into a 3D point on the globe

  return scene;
}