How to convert latitude and longitude coordinates into three-dimensional coordinates in three.js or babylon.js

Requirements

There are following requirements. According to the longitude and latitude coordinates returned by the interface, the data must be correctly rendered in the three-dimensional scene in three.js, or the movement trajectory of the object must be drawn based on the longitude and latitude coordinates.

Analysis

We all know that the rendering of three-dimensional three.js uses right-hand Cartesian coordinates. It does not provide us with the conversion method of longitude, latitude and Cartesian coordinates like cesium. When converting, pay attention to the direction of the coordinates (whether Involving rotation), Scale, Coordinate origin, etc.

Can we use the cesium method to convert the longitude and latitude into Karl coordinates, and then render it in three.js or babylon.js?

The answer is yes, but there is a problem, that is, the direction of the coordinate axes and the origin are different. It is impossible to locate by just relying on one coordinate point. Information about the coordinate system is also needed. In these coordinate systems, once it is involved to the spin, then the difficulty will increase exponentially.

In three.js right-hand Descartes, the height is represented by the Y value, but in Descartes in cesium, there is no way to represent the height. It can only be represented by longitude and latitude, for example (lon, lat, height). The coordinate system information can be seen Picture below

cesium WGS84 coordinate system

cesium Cartesian coordinate system

three.js coordinate system

For example, the system developed by cesium uses cesium’s Cartesian coordinate system. A car moves in a certain direction, such as true north. In your system, for example, three.js system, it is difficult for you to synchronize the movements of both sides based on the latitude and longitude or the Cartesian coordinate information of cesium, so what should you do? Let’s take a look at the following method

The implementation steps are as follows

Step 1: Set the origin, convert the latitude and longitude to Mercator, translate, and convert Mercator to the right-hand Cartesian of three.js

In this case, we can manually convert the latitude and longitude into Mercator; then correspond the converted longitude to the x of the position in three.js, and the dimension to the z value in three.js, but have used cesium or openlayers Brothers should know that the converted coordinates are very large in value, which is inconvenient for our calculations or anything else. We can first specify a coordinate origin, and the coordinates input next will be based on this origin. Positioning is equivalent to doing a translation. This coordinate conversion is a commonly used method.

Step 2: Correct scaling

In terms of coordinate axis direction and scale, the first is the direction. When moving towards the east direction, the longitude and latitude will increase. At this time, the x in three.js will increase, and the same will be true for the dimensions.

Key points: Converting WGS84 coordinates longitude and latitude into Mercator, and then placing it in a three-dimensional scene for positioning requires a certain amount of scaling. The scaling factor depends on the size of the scene. How to determine it? For example, in some satellite base map downloading software, You want to download a base map with a diameter of 5 kilometers, but you find that the obtained diameter is greater than 5 kilometers. The scaling factor is the actual diameter obtained from the input diameter. When we want to restore the actual scene one to one, we also need to model it. Scale according to this coefficient;

Next is the code implementation. The naming of each method in the code is a comment, which should be easy to understand. There are two versions, one is to manually convert the latitude and longitude to Mercator (pseudo-Mercator 3857), and the other is to use < strong>proj4

Step 3: Rotate

There is no rotation involved here, skip it

proj4 version

class GeoCoordinateConverter {
  [x: string]: any;
  offsetX: any;
  offsetZ: any;
  source: any;
  destination: any;
  constructor(latitude:any, longitude:any) {
    //Define the projected coordinate system, taking WGS84 as an example here
    proj4.defs("WGS84", " + proj=longlat + datum=WGS84 + no_defs");
    this.source = proj4.Proj("WGS84");
    this.destination = proj4.Proj(
      " + proj=merc + a=6378137 + b=6378137 + lat_ts=0.0 + lon_0=0.0 + x_0=0.0 + y_0=0 + k=1.0 + units=m + nadgrids=@null + wktext + no_defs"
    );

    this.mercatorOrigin = proj4.transform(this.source, this.destination, [
      longitude,
      latitude,
    ]);
    this.offsetX = -this.mercatorOrigin.x;
    this.offsetZ = -this.mercatorOrigin.y;
  }

  convertLatLngToThreeJS(latitude:any, longitude:any) {
    var mercatorCoordinates = proj4.transform(this.source, this.destination, [
      longitude,
      latitude,
    ]);

    const x = (mercatorCoordinates.x + this.offsetX) / 1.0870778616;
    const z = (mercatorCoordinates.y + this.offsetZ) / 1.0870778616;
    return { x: x, y: 0, z: z };
  }
}

// Usage example
var converter = new GeoCoordinateConverter(23.5684, 111.3565);
converter.convertLatLngToThreeJS(23.569, 111.357);
converter.convertLatLngToThreeJS(23.5699, 111.357);

Manually calculated version

//Usage example
var converter = new GeoCoordinateConverter(23.5684, 111.3565);
converter.convertLatLngToThreeJS(23.569, 111.357);
converter.convertLatLngToThreeJS(23.5699, 111.357);

class GeoCoordinateConverterT {
  private longitude: number;
  private latitude: number;
  private mercatorOrigin: [number, number];
  private offsetX: number;
  private offsetZ: number;

  constructor(longitude: number, latitude: number) {
    this.longitude = longitude;
    this.latitude = latitude;
    this.mercatorOrigin = this.latLngToWebMercator(longitude, latitude);
    this.offsetX = -this.mercatorOrigin[0];
    this.offsetZ = -this.mercatorOrigin[1];
  }

  private latLngToWebMercator(longitude: number, latitude: number): [number, number] {
    const earthRad = 6378137.0;
    const x = (longitude * Math.PI / 180) * earthRad;
    const a = (latitude * Math.PI / 180);
    const y = (earthRad / 2) * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
    return [x, y];
  }

  public convertLatLngToThreeJS(longitude: number, latitude: number): { x: number, y: number, z: number } {
    const mercatorCoordinates = this.latLngToWebMercator(longitude, latitude);
    const x = (mercatorCoordinates[0] + this.offsetX) / 1.0870778616;
    const z = (mercatorCoordinates[1] + this.offsetZ) / 1.0870778616;
    return { x, y: 0, z };
  }

  public convertThreeJSToLatLng(x: number, z: number): { longitude: number, latitude: number } {
    const inverseX = x - this.offsetX;
    const inverseZ = z - this.offsetZ;

    const earthRad = 6378137.0;
    const latitude = (Math.atan(Math.exp(inverseZ / earthRad)) * 360 / Math.PI) - 90;
    const longitude = (inverseX / (earthRad * Math.PI / 180));

    return { longitude, latitude };
  }
}

// Usage example
const convertert = new GeoCoordinateConverterT(111.3565, 23.5684);
const threeJsCoordinates = convertert.convertLatLngToThreeJS(111.3579, 23.5699);

console.log(threeJsCoordinates);

const latLng = convertert.convertThreeJSToLatLng(500,500);

console.log(latLng);

After calculation, the error of the two versions is not large, less than 3 meters, which is almost universal. You can also manually select several points and quadrant values in different directions to handle the scaling coefficient differently. It is better to multiply the quadrant value by the coefficient and fluctuate up and down by 0.01. etc., so as to improve the accuracy. When some larger scenes are used, it is recommended to use proj4

Why does it say to multiply by a scaling factor, but I divide it by the scaling factor? Because in this way, the entire scene does not need to be scaled when modeling.