Last updated

Map in 3D

This tutorial shows how to use and modify a 2D TiledMap plane in a 3D Plot.

A Map plane is a 2D surface that displays images from a WMTS service.
Depending on the distance between the camera and the surface, the Map will load high/low resolution tiles.
Note that because of the possible inclination angle between the camera and the surface, not all tiles will be at the same resolution.
This shape is in the coordinate system of EPSG:3857.

# World Map

This tutorial demonstrates how to display a world map in 3D.


.cg-tooltip-holder {
  position: relative;
}

.cg-tooltip {
  position: absolute;
  display: block;
  padding: 2px 12px 3px 7px;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 13px;
  background: white !important;
  opacity: 0.9;
  color: #333333;
  border: solid 1px gray;
  border-radius: 5px;
  text-align: left;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  border-radius: 5px;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
}
/* Default setting for tooltip */
.cg-tooltip-container {
  position: absolute;
  display: block;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 12px;
  padding: 3px 7px;
  background: #f7f7f7;
  color: #333333;
  border: 1px solid #938e8e;
  opacity: 0.8;
  text-align: left;
  box-shadow: 3px 3px 10px #888;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
  user-select: none;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
  .cg-tooltip-container {
    border-radius: 0;
  }
}
/* Default left arrow for tooltip */
.cg-tooltip-arrow-left::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 5px solid transparent;
  border-left: 0;
  border-right: 5px solid  #938e8e;
  transform: translate(calc(-100%), -50%);
}
.cg-tooltip-arrow-left::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 4px solid transparent;
  border-left: 0;
  border-right: 4px solid #f7f7f7;
  transform: translate(calc(-100%), -50%);
}
/* Default top arrow for tooltip */
.cg-tooltip-arrow-top::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 5px solid transparent;
  border-top: 0;
  border-bottom: 5px solid #938e8e;
  transform: translate(-50%, -100%);
}
.cg-tooltip-arrow-top::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 4px solid transparent;
  border-top: 0;
  border-bottom: 4px solid #f7f7f7;
  transform: translate(-50%, -100%);
}
/* Default right arrow for tooltip */
.cg-tooltip-arrow-right::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 5px solid transparent;
  border-right: 0;
  border-left: 5px solid #938e8e;
  transform: translate(100%, -50%);
}
.cg-tooltip-arrow-right::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 4px solid transparent;
  border-right: 0;
  border-left: 4px solid #f7f7f7;
  transform: translate(100%, -50%);
}
/* Default bottom arrow for tooltip */
.cg-tooltip-arrow-bottom::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0px;
  border: 5px solid transparent;
  border-bottom: 0;
  border-top: 5px solid #938e8e;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
.cg-tooltip-arrow-bottom::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0;
  border: 4px solid transparent;
  border-bottom: 0;
  border-top: 4px solid #f7f7f7;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
/* Tooltip item name */
/* Tooltip item value */
/* .cg-tooltip-item-value */
/* Tooltip item value */
.cg-tooltip-item-unit {
  text-transform: none;
}

.cg-tooltip-item-name {
    text-transform: capitalize;
    white-space: nowrap;
    vertical-align: middle;
    font-size: 13px;
}
.cg-tooltip-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  white-space: pre-wrap;
  font-size: 12px;
  line-height: 100%;
  margin: 1px 0;
}
.cg-tooltip-title {
  font-size: 13px;
  height: 14px;
  text-transform: capitalize;
}
.cg-tooltip-title .cg-tooltip-symbol {
  margin-right: 0 !important;
}
.cg-tooltip-title-name {
  vertical-align: middle;
}
.cg-tooltip-row + .cg-tooltip-row {
  margin-top: 4px;
}
.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {
  margin-top: 5px;
}
.cg-tooltip-item-value + .cg-tooltip-item-unit {
    margin-left: 1px;
}
/* Tooltip symbol */
.cg-tooltip-symbol-cell {
  display: inline-flex;
  min-width: 13px; /* 10px size + 3px margin */
}
.cg-tooltip-symbol {
  margin-right: 3px;
  background-color: transparent;
  display: block;
}
.cg-tooltip-symbol > img {
  display: block;
}
.cg-tooltip-list-cell {
  display: inline-flex;
}
.cg-tooltip-list-symbol {
  display: block;
  margin-right: 3px;
  width: 6px;
  height: 6px;
  vertical-align: middle;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
.cg-tooltip-symbol-legacy {
  border-radius: 4px;
  margin-right: 5px;
  height: 8px;
  width: 8px;
  display: inline-block;
}
.cg-tooltip-title-legacy {
  font-weight: 900;
}

/* Tooltip symbol circle */
.cg-tooltip-symbol.circle {
  height: 10px;
  width: 10px;
  display: inline-block;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
/* Tooltip symbol line */
.cg-tooltip-symbol.line {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: scale(1.2, 0.2);
}
/* Tooltip symbol diamond */
.cg-tooltip-symbol.diamond {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: rotate(45deg);
    border-radius: 0px;
}
/* Tooltip symbol square */
.cg-tooltip-symbol.square {
    height: 10px;
    width: 10px;
    display: inline-block;
    border-radius: 0px;
    border: 1px solid rgba(0,0,0,.4);
}

# Map Options

This tutorial demonstrates how to reduce the extent of the map plane.
It also shows how to add additional models and objects to the map.
For example, this map contains several underground wells (in blue) with tops (red).
Rotate the camera to look under the map to find the wells.

import { Plot } from "@int/geotoolkit3d/Plot.ts";
import { Box2, Vector2, Vector3 } from "/node_modules/.vite/deps/three.js?v=2235af65";
import { CompassAxis } from "@int/geotoolkit3d/scene/compass/CompassAxis.ts";
import { TiledMap } from "@int/geotoolkit3d/scene/map/TiledMap.ts";
import { PointSet } from "@int/geotoolkit3d/scene/pointset/PointSet.ts";
import { TrajectoryLine } from "@int/geotoolkit3d/scene/well/TrajectoryLine.ts";
import { Wells } from "/src/code/Carnac3D/helpers/wells.ts";
function createPlot(mapextent, divElement, distance) {
  const center = new Vector2();
  mapextent.getCenter(center);
  const plot = new Plot({
    "container": divElement,
    "camera": {
      "position": new Vector3(center.x, center.y, distance),
      "lookat": new Vector3(center.x, center.y, 0),
      "minnear": 10
    }
  });
  plot.getCompass().setCompassObject(new CompassAxis());
  return plot;
}
function createScene(divElement) {
  const mapsize = 5e4;
  const newExtent = new Box2(
    new Vector2(-10970653039878588e-9 - mapsize, 4630768545770666e-9 - mapsize),
    new Vector2(-10970653039878588e-9 + mapsize, 4630768545770666e-9 + mapsize)
  );
  const plot = createPlot(newExtent, divElement, 15e4);
  const root = plot.getRoot();
  plot.setOptions({
    "scale": new Vector3(1, 1, 5)
  });
  root.add(new TiledMap({
    "server": ["https://demo.int.com/osm_tiles/"],
    "formatterfunction": function(z, x, y) {
      return z + "/" + y + "/" + x + ".png";
    },
    "extent": newExtent,
    "maplimits": newExtent,
    "maxlod": 16
  }));
  createWells(root);
  return plot;
}
function createWells(root) {
  const reference = new Vector3(-10970653039878588e-9, 4630768545770666e-9, 0);
  const axis = new Vector3(0, 0, 1);
  const wellheads = {
    x: [],
    y: [],
    z: []
  };
  const well3410A44 = Wells.getWell_34_10_A_44();
  let i = 0;
  const xMap = function(e) {
    return e * (1 + i / 20);
  };
  const yMap = function(e) {
    return e * (1 + i / 22);
  };
  const zMap = function(e, j) {
    return Math.min(e - Math.cos(i) * 400, 0);
  };
  for (; i < 50; i++) {
    const trajectoryPath = new TrajectoryLine({
      data: [{
        x: well3410A44.xDeviation.map(xMap),
        y: well3410A44.yDeviation.map(yMap),
        z: well3410A44.elevation.map(zMap)
      }],
      color: "blue"
    });
    trajectoryPath.rotateOnAxis(axis, i * 10 * Math.PI / 3);
    trajectoryPath.position.copy(reference);
    const xLocation = Math.cos(i * 20) * 25e3;
    const yLocation = Math.sin(i * 3) * 35500;
    trajectoryPath.position.x += xLocation;
    trajectoryPath.position.y += yLocation;
    wellheads.x.push(xLocation);
    wellheads.y.push(yLocation);
    wellheads.z.push(0);
    root.add(trajectoryPath);
  }
  const pointSet = new PointSet({
    "data": wellheads,
    "symbolsizeisindevice": true,
    "symbolminsize": 3,
    "symbolmaxsize": 3,
    "colorprovider": "red"
  });
  pointSet.position.copy(reference);
  root.add(pointSet);
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));