How to add custom 3d Models like a car or truck instead of pins in Azure Map Line layer

Satyam Chauhan 547 Reputation points
2024-07-19T11:49:33.89+00:00

Hi I want to show a route followed by a shipment truck, currently I am showing a green marker for the current location and the blue pins are the previous reported location of the truck.

How can I show a 3d model of a truck instead od the green marker on the line layer. I have a 3d model of the truck in .glb format.

User's image

Azure Maps
Azure Maps
An Azure service that provides geospatial APIs to add maps, spatial analytics, and mobility solutions to apps.
735 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. rbrundritt 18,671 Reputation points Microsoft Employee
    2024-07-19T19:21:20.14+00:00

    You would have to use the WebGlLayer and then use a 3D library like Babylon or Three.js. Here are a few existing samples:

    I played around this morning with the Babylon sample and created a reusable layer for managing a simple 3D model easily. You can try it out here. Here is the code for the custom layer class I created.

    /**
     * A custom WebGLLayer that renders a 3D model on the map.
     * @param {string} modelPath The path to the model file.
     * @param {string} modelFileName The name of the model file.
     * @param {Array.<number>} modelOrigin The position of the model in [longitude, latitude] format.
     * @param {number} modelAltitude The altitude of the model. Default: 0
     * @param {number} headingOffset The heading offset of the model. Adjust model heading so that it points north when heading is 0.
     * @param {number} modelScale The scale of the model.
     * @param {Array.<number>} modelOrientation The orientation of the model in [pitch, roll, yaw] format. Specifies how the model should be rotated so it sits upright.
     
     */
    var Simple3DModelLayer = function (modelPath, modelFileName, modelOrigin, modelAltitude, headingOffset, modelScale, modelOrientation) 
    {
        //modelOrientation = modelOrientation || [Math.PI / 2, 0, 0];
        modelScale = modelScale || 1;
        modelAltitude = modelAltitude || 0;
        modelOrientation = [Math.PI / 2, 0, 0];
        headingOffset = headingOffset || 0;
    
        heading = 0;
    
        let modelCoords = atlas.data.MercatorPoint.fromPosition([...modelOrigin, modelAltitude]);
        let mapModelScale;
        let modelMatrix;
    
        let _scene;
        let _map;
        let _engine;
        let _gl;
        let _modelMesh;
    
        BABYLON.SceneLoaderFlags.ShowLoadingScreen = false;
    
        var updateModelMatrix = () => {
            
            modelCoords = atlas.data.MercatorPoint.fromPosition([...modelOrigin, modelAltitude]);
            mapModelScale = 2 * atlas.data.MercatorPoint.meterInMercatorUnits(modelOrigin[1]) * modelScale;
    
            var headingRadians = (heading + headingOffset) * Math.PI / 180;
            var orientation = BABYLON.Quaternion.FromEulerAngles(modelOrientation[0], modelOrientation[1], modelOrientation[2]);
            
            //Rotate the orientation by the heading.
            orientation = orientation.multiply(BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 1, 0), headingRadians));
    
            modelMatrix = BABYLON.Matrix.Compose(
                new BABYLON.Vector3(mapModelScale, mapModelScale, mapModelScale),
                orientation,
                new BABYLON.Vector3(modelCoords[0], modelCoords[1], modelCoords[2])
            );
    
            //Make the map repaint.
            _map.triggerRepaint();
        }
    
        // Create a renderer that implements atlas.WebGLRenderer
        var renderer = {
            renderingMode: "3d",
    
            // Method called when the layer is added to the map
            onAdd: (map, gl) => {
                _map = map;
                _gl = gl;
    
                // Initialize the Babylon.js engine.
                if(!_engine){
                    _engine = new BABYLON.Engine(gl, true, { useHighPrecisionMatrix: true }, true);
                }
    
                if(!_scene || _scene.isDisposed){
                    _scene = new BABYLON.Scene(_engine);
                    _scene.autoClear = false;
                    _scene.detachControl();
                    _scene.beforeRender = () => {
                        _engine.wipeCaches(true);
                    };
                }
    
                this.camera = new BABYLON.Camera("camera", new BABYLON.Vector3(), _scene);
                const light = new BABYLON.HemisphericLight("light", BABYLON.Vector3.One(), _scene);
    
                BABYLON.SceneLoader.Append(modelPath, modelFileName, _scene);
    
                updateModelMatrix();
            },
    
            onRemove: () => {
                _scene.dispose();
                _scene = null;
    
                _engine.dispose();
                _engine = null;
    
                _map = null;
                _gl = null;
            },
    
            // Method called on each animation frame
            render: (gl, matrix) => {
                // projection & view matrix
                const cameraMatrix = BABYLON.Matrix.FromArray(matrix);
                const mvpMatrix = modelMatrix.multiply(cameraMatrix);
                this.camera.freezeProjectionMatrix(mvpMatrix);
    
                _scene.render(false);
                _map.triggerRepaint();
            }
        };
            
        //Create a unique id for the layer.
        const uniqueId = "Simple3dModelLayer_" + Math.random().toString(36).substr(2, 9);
    
        const layer = new atlas.layer.WebGLLayer(uniqueId, { renderer });
    
        //Extend the layer with some custom methods.
    
        layer.setPosition = function(newModelOrigin) {
            modelOrigin = newModelOrigin;
    
            //Update model matrix.
            updateModelMatrix();
        }
    
        layer.getPosition = function(){
            return modelOrigin;
        }
    
        layer.setAltitude = function (newAltitude) {
            modelAltitude = newAltitude;
    
            //Update model matrix.
            updateModelMatrix();
        }
    
        layer.getAltitude = function(){
            return modelAltitude;
        }
    
        layer.setHeading = function(newHeading) {
            heading = newHeading;
    
            //Update model matrix.
            updateModelMatrix();
        }
    
        layer.getHeading = function(){
            return heading;
        }
    
        layer.setModel = function(newModelPath, newModelFileName) {
            modelPath = newModelPath;
            modelFileName = newModelFileName;
    
            if(_map != null){
                //Dispose the scene and reload the model.
                _scene.dispose();
                renderer.onAdd(_map, _gl);
            }
        }
    
        layer.setScale = function(newModelScale) {
            modelScale = newModelScale;
    
            //Update model matrix.
            updateModelMatrix();
        }
    
        layer.getScale = function(){
            return modelScale;
        }
    
        return layer;
    }
    

    Models are loaded using Babylon.js using SceneLoader.Append which can take in a path value which would be the path to the folder that contains the model.

    The modelOrientation is used to rotate the model so that it sits up properly. With a lot of models I've found you need to pitch the model by PI/2 (I made this the default, so no need to set this unless your model needs a different orientation).

    Headings in the map are such that 0 = north, 90 = east, 180 = south, and 270 = west. With this in mind you will want your model pointing north by default when heading is set to 0. You could use the model orientation, but that would likely take a lot of extra work to get right. So to simplify things I added a headingOffset where you can specify how many degrees to rotate the model so that it's default heading is pointing north.

    Here is a simple example of how to use this new layer:

    // Create an instance of the simplae 3D Model Layer
    layer = new Simple3DModelLayer('', 'BMW_3-Series.glb', sampleCenter, 0, -180, 1);
    
    // Add the layer to the map
    map.layers.add(layer);
    

    Note that you will need to copy and past the Simple3DModelLayer code I provided earlier, and also load Babylon.js into your app:

    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
    

    You can try this sample out here: https://rbrundritt.azurewebsites.net/Demos/AzureMaps/Simple3DModelLayer

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.