Skip to main content

πŸ—ΊοΈ Mapbox

Introduction#

πŸšΆβ€β™€οΈ Mapbox GL JS is a library that can be paired with IFC.js to provide worldly context for your models. Building on top of our knowledge from previous tutorials, we're going to find out how to view our models inside a custom map.

We all have different learning styles. ☝ Check out the Github repo for the full working example. This tutorial was modeled after this Mapbox example.

How to do it#

Groundwork#

πŸ’Ύ We'll get you up and running with a simple map instance, but do check out the Mapbox GL JS docs for more in-depth guides and sweet examples. Let's first load the Mapbox dependencies β€” in this guide we'll just add them with HTML tags.

<script src='https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.css' rel='stylesheet' />

Then add a simple div to hold our map:

<div id="map"></div>

Some styling πŸ’„

#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

And finally some imports from web-ifc-three and three that we'll use later on.

import {
Matrix4, Vector3,
DirectionalLight, AmbientLight,
PerspectiveCamera, Scene, WebGLRenderer,
} from "three";
import { IFCLoader } from "web-ifc-three/IFCLoader";

Init map#

With our structure in place, let's initialize our map πŸ—ΊοΈ.

mapboxgl.accessToken = 'YOUR_API_KEY_HERE';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 20.5,
center: [13.4453, 52.4910],
pitch: 75,
bearing: -80,
antialias: true
});

Prepping our model#

πŸ“ In order for Mapbox to display our model correctly, we need to define our position, rotation and scale in map terms.

const modelOrigin = [13.4453, 52.4910];
const modelAltitude = 0;
const modelRotate = [Math.PI / 2, .72, 0];
// translate to map coordinates
const modelAsMercatorCoordinate =
mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin, modelAltitude);
const modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};

Depending on your model, you may have to tweak the modelOrigin, modelAltitude and modelRotate to get things fitting right.

Setting the scene#

🎸 Now we'll start to configure our Three.js scene for our custom Mapbox layer. First some larger scope definitions:

const scene = new Scene();
const camera = new PerspectiveCamera();
const renderer = new WebGLRenderer({
// here we inject our Three.js scene into Mapbox
canvas: map.getCanvas(),
antialias: true
});
renderer.autoClear = false;

πŸŒͺ️Then in customLayer we'll include an onAdd function to load our IFC model and some lighting to the scene, as well as a render function to apply our position, rotation and scale changes.

const customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function () {
//load model
const ifcLoader = new IFCLoader();
ifcLoader.ifcManager.setWasmPath( '../../../' );
ifcLoader.load( '../../../IFC/01.ifc', function ( model ) {
scene.add( model );
});
//add lighting
const directionalLight = new DirectionalLight(0x404040);
const directionalLight2 = new DirectionalLight(0x404040);
const ambientLight = new AmbientLight( 0x404040, 3 );
directionalLight.position.set(0, -70, 100).normalize();
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight, directionalLight2, ambientLight);
},
render: function (gl, matrix) {
//apply model transformations
const rotationX = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), modelTransform.rotateX);
const rotationY = new Matrix4().makeRotationAxis(new Vector3(0, 1, 0), modelTransform.rotateY);
const rotationZ = new Matrix4().makeRotationAxis(new Vector3(0, 0, 1), modelTransform.rotateZ);
const m = new Matrix4().fromArray(matrix);
const l = new Matrix4()
.makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
camera.projectionMatrix = m.multiply(l);
renderer.resetState();
renderer.render(scene, camera);
map.triggerRepaint();
}
};

The last thing to do is simply add our customLayer when our map style is loaded.

map.on('style.load', () => {
map.addLayer(customLayer, 'waterway-label');
});

The result#

Github repo

Next steps#

πŸŽ‰πŸŽ‰πŸŽ‰ Yoohoo! You should now be able to load your model into Mapbox and place it anywhere in the world you'd like.