MapLibre GL JS with terrain layer: How to pin a horizontal plane to specific altitude?
Asked Answered
H

1

7

I based some code on the "Add a 3D model" example to draw only a horizontal plane on a map which uses setTerrain to add a terrain layer. My intention is to draw semitransparent layers at heights above sea level and have them intersect with mountains, somewhat similar to contour lines.

I created a 1km-wide square at altitude 0. Altitude 0 is height of terrain at center of the visible map area. When I drag the map and release the mouse, altitude 0 gets reset to the new altitude 0 of the terrain at the center, making the plane change its relative altitude:

issue plane relocating

Elevation is exaggerated to better illustrate the issue.

How to specify height of the plane in meters above sea level and have it fixed at that height when dragging the map? I think I need to get the height of the terrain at the center then add/substract it from the plane's z-position, but how to do this inside a custom layer's render function?

Hollerman answered 18/8, 2022 at 21:52 Comment(0)
E
0

Just like you said, a naive solution would be to get the height of the terrain at the screen's center and the height of your model and subtract them. This could be achieved like so:

// edited version of https://maplibre.org/maplibre-gl-js/docs/examples/add-3d-model/

  render(gl, matrix) {
    
    const modelOrigin = new LngLat(11.53, 47.67);  // just an example
    const modelElevation = map.terrain ? map.terrain.getElevationForLngLatZoom(modelOrigin, map.getZoom()) : 0;
    const modelOriginMercator = mlg.MercatorCoordinate.fromLngLat(modelOrigin, 0);
  
    // calculating the difference between center-elevation and model-elevation
    const terrainCenterElevation = map.transform.elevation;
    const deltaMetersZ = modelElevation - terrainCenterElevation;
    const mercatorPerMeter = modelOriginMercator.meterInMercatorCoordinateUnits();
    const deltaMercatorZ = deltaMetersZ * mercatorPerMeter;

    const modelTransform = {
      translateX: modelOriginMercator.x,
      translateY: modelOriginMercator.y,
      translateZ: modelOriginMercator.z + deltaMercatorZ,
      rotateX: Math.PI / 2,
      rotateY: 0,
      rotateZ: 0,
      scale: modelOriginMercator.meterInMercatorCoordinateUnits()
    };

    const rotationX = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), modelTransform.rotateX);
    const rotationY = new Matrix4().makeRotationAxis(new Vector3(1, 0, 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);
    const cameraProjectionMatrix = m.multiply(l);

    // @ts-ignore
    this.camera.projectionMatrix = cameraProjectionMatrix;
    // @ts-ignore
    this.renderer.resetState();
    // @ts-ignore
    this.renderer.render(this.scene, this.camera);
    map.triggerRepaint();


}
  • maplibre seems to always position the threejs-scene exactly at the height of the screen's center ... so when getting the model's height with mlg.MercatorCoordinate.fromLngLat(modelOrigin, 0), we don't need to provide the actual model elevation and just pass 0
  • deltaMercatorZ is being added to the model's z-translation

Just a few notes on maplibre's nomenclature in transform.ts:

  • elevation [meters]: height of terrain under center
  • center [lng, lat]: position of center of screen in world
  • centerPoint [pixel]: position of center of screen on screen
  • altitude [m]: height of camera relative to sea-level

I think there might be a more elegant solution, but this might do the job for now!

Estriol answered 29/11, 2023 at 12:19 Comment(1)
Update: the team at maplibre was so nice to merge a PR that will add documentation about this particular technique - my understanding is that this example will be published with the next maplibre release.Estriol

© 2022 - 2024 — McMap. All rights reserved.