1. 概述

在开发涉及地图的应用时,我们经常会遇到坐标转换的问题。大多数情况下,我们需要将经纬度坐标转换为二维平面上的点,以便在地图上展示。幸运的是,可以借助墨卡托投影(Mercator Projection)的公式来解决这个问题。

本教程将介绍墨卡托投影的基本原理,并演示如何实现其两种变体。

2. 墨卡托投影

墨卡托投影 是由弗兰德斯地图学家 Gerardus Mercator 在 1569 年提出的一种地图投影方式。地图投影的作用是将地球表面上的经纬度坐标映射到一个平面坐标系中的点,换句话说,就是将地球表面的点转换为地图上的点

墨卡托投影有两种实现方式:

  • 球面墨卡托投影(Spherical Mercator):将地球视为一个球体。
  • 椭球墨卡托投影(Elliptical Mercator):将地球视为一个椭球体。

我们将分别实现这两种方式。

首先定义一个抽象基类,用于两种投影方式的实现:

abstract class Mercator {
    final static double RADIUS_MAJOR = 6378137.0;
    final static double RADIUS_MINOR = 6356752.3142;

    abstract double yAxisProjection(double input);
    abstract double xAxisProjection(double input);
}

这个类中定义了地球的长半轴(赤道半径)和短半轴(极地半径),单位为米。众所周知,地球并不是一个完美的球体,因此我们需要两个半径来更准确地描述其形状:

  • 长半轴:从地心到赤道的距离
  • 短半轴:从地心到南北极的距离

2.1. 球面墨卡托投影

球面投影将地球简化为一个球体,与椭球投影相比,虽然不够精确,但计算简单,适合快速估算。这种投影方式在距离测量上存在误差,地图上图形的形状比例也会有轻微失真,因此国家、湖泊、河流等地理对象的形状和比例无法完全精确保留

这种投影也被称为 Web Mercator 投影,广泛应用于 Google Maps 等 Web 地图服务中。

下面是其实现代码:

public class SphericalMercator extends Mercator {

    @Override
    double xAxisProjection(double input) {
        return Math.toRadians(input) * RADIUS_MAJOR;
    }

    @Override
    double yAxisProjection(double input) {
        return Math.log(Math.tan(Math.PI / 4 + Math.toRadians(input) / 2)) * RADIUS_MAJOR;
    }
}

📌 注意事项:

  • 使用一个统一的半径(RADIUS_MAJOR)来表示地球半径,而非两个。
  • 实现了 xAxisProjectionyAxisProjection 两个方法,分别用于计算 X 和 Y 坐标。
  • 使用了 Java 自带的 Math 工具类,简化计算。

测试示例:

Assert.assertEquals(2449028.7974520186, sphericalMercator.xAxisProjection(22));
Assert.assertEquals(5465442.183322753, sphericalMercator.yAxisProjection(44));

✅ 该投影方式的输出范围为:
(-20037508.34, -23810769.32, 20037508.34, 23810769.32)

2.2. 椭球墨卡托投影

椭球投影将地球视为一个椭球体,因此在形状和比例上更加准确。尽管不是 100% 精确,但它能更好地反映地球的真实形状。不过由于计算复杂,实际应用中不如球面投影常见。

实现如下:

class EllipticalMercator extends Mercator {
    @Override
    double yAxisProjection(double input) {

        input = Math.min(Math.max(input, -89.5), 89.5);
        double earthDimensionalRateNormalized = 1.0 - Math.pow(RADIUS_MINOR / RADIUS_MAJOR, 2);

        double inputOnEarthProj = Math.sqrt(earthDimensionalRateNormalized) * 
          Math.sin( Math.toRadians(input));

        inputOnEarthProj = Math.pow(((1.0 - inputOnEarthProj) / (1.0+inputOnEarthProj)), 
          0.5 * Math.sqrt(earthDimensionalRateNormalized));
        
        double inputOnEarthProjNormalized = 
          Math.tan(0.5 * ((Math.PI * 0.5) - Math.toRadians(input))) / inputOnEarthProj;
        
        return (-1) * RADIUS_MAJOR * Math.log(inputOnEarthProjNormalized);
    }

    @Override
    double xAxisProjection(double input) {
        return RADIUS_MAJOR * Math.toRadians(input);
    }
}

📌 这种方式的复杂度主要体现在 Y 轴投影上,因为需要考虑地球的扁率。

测试示例:

Assert.assertEquals(2449028.7974520186, ellipticalMercator.xAxisProjection(22));
Assert.assertEquals(5435749.887511954, ellipticalMercator.yAxisProjection(44));

✅ 该投影方式的输出范围为:
(-20037508.34, -34619289.37, 20037508.34, 34619289.37)

3. 总结

当我们需要将经纬度坐标转换为二维平面上的点时,可以使用墨卡托投影。根据精度需求,我们可以选择:

  • ⚠️ 球面墨卡托:计算简单,适合大多数 Web 地图应用
  • 椭球墨卡托:精度更高,但计算复杂,适合对精度要求较高的专业地图应用

📌 本文完整代码可在 GitHub 上获取:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-math-2


原始标题:Convert Latitude and Longitude to a 2D Point in Java