关于网页地图的坐标系

EPSG:4326地理坐标系 和 EPSG:3857Web 墨卡托投影

EPSG:4326
  • 定义:EPSG:4326 是基于 WGS84 椭球的地理坐标系,使用经度(Longitude)和纬度(Latitude)表示地球上的位置。
  • 特点
    • 经度范围为 -180° 至 +180°,纬度范围为 -90° 至 +90°。
    • 单位为度(°),适合精确的地理定位和科学应用。
    • 由于其基于椭球体模型,计算复杂度较高。
  • 应用场景
    • 用于存储地理数据,如 GPS 数据、地理信息系统(GIS)数据等。
    • 适用于大范围的地理信息描述。
EPSG:3857
  • 定义 :EPSG:3857 是基于 Web 墨卡托投影(伪墨卡托投影)的投影坐标系,将地球视为正球体进行投影。
  • 特点
    • 坐标单位为米,范围为纬度 ±85.051129°。
    • 由于投影的等角特性,地图在不同层级上保持形状不变,适合导航和地图显示。
    • 高纬度地区存在面积变形,但计算简单,适合 Web 地图应用。
  • 应用场景
    • 广泛用于 Web 地图服务,如 Google Maps、OpenStreetMap、ArcGIS 等。
    • 适合地图显示和导航,但 不适合存储地理数据
两者关系

通常地理数据以 EPSG:4326 存储,因为其精度高且易于理解;而在 Web 地图中显示时,会转换为 EPSG:3857(大部分地图api底层会进行转换,开发调用api只需要写入 EPSG:4326坐标)。
当网页地图中加载数据时,系统会将 EPSG:4326 坐标转换为 EPSG:3857 坐标,以适应地图的投影和显示。这种转换是必要的,因为 EPSG:3857 坐标系使用的是平面坐标,对地图的缩放和平移效果更好。

各大地图服务提供商所使用的坐标系:

地图服务 坐标系
天地图 CGCS2000(中国2000国家大地坐标系),EPSG代码为4490。
高德地图 GCJ-02(火星坐标系),基于WGS-84加密处理。
百度地图 BD-09,基于GCJ-02二次加密。
谷歌地图 国外使用WGS-84坐标系;在中国使用GCJ-02。
Mapbox 默认使用WGS-84(EPSG:4326)或Web墨卡托投影(EPSG:3857),但可通过修改支持其他坐标系。
ArcGIS 通常使用WGS-84(EPSG:4326)或Web墨卡托投影(EPSG:3857),具体取决于数据源。

说明

  • CGCS2000WGS-84 非常接近,差异在厘米级别,通常可忽略。

关于火星坐标系要注意的

火星坐标系用于避免地图数据被直接使用或分析。由于中国的法律对地图数据有严格的管理,因此在中国境内的地图API(如高德和百度地图)都会对GPS坐标进行加密,转换为火星坐标系。
这种转换使得直接从GPS设备获取的WGS 84坐标不再准确,需要通过特定的算法进行转换才能在这些地图服务中正确显示。

例如:leaflet加载高德地图,如果添加点击事件获取地图上的点那么点的坐标是火星坐标系的,如果向地图上添加已知点,为了加到正确的位置上,也要求是火星坐标系。
下面是java的基于geotools api实现高德火星坐标系转WGS-84坐标系(基于wkb字符串)

java 复制代码
@Slf4j
public class WKBToGeoJSONUtil {

    /**
     * 将WKB十六进制字符串转换为GeoJSON格式的Map
     * @param hexWKB PostGIS返回的WKB十六进制字符串
     * @param decimals 保留的小数位数
     * @return 对应GeoJSON结构的Map
     * @throws Exception 如果解析WKB或生成GeoJSON时发生错误
     */
    public static Map<String, Object> convert(String hexWKB, int decimals) throws Exception {
        Geometry geometry = parseWKB(hexWKB);
        Geometry wgs84Geometry = CoordinateTransform.gcj02ToWgs84(geometry);
        return convertToGeoJSONMap(wgs84Geometry, decimals);
    }

    /**
     * 解析WKB字符串为JTS Geometry对象
     * @param hexWKB WKB十六进制字符串
     * @return 解析后的Geometry对象
     * @throws Exception 如果解析WKB时发生错误
     */
    private static Geometry parseWKB(String hexWKB) throws Exception {
        try {
            WKBReader wkbReader = new WKBReader();
            byte[] wkbBytes = WKBReader.hexToBytes(hexWKB);
            return wkbReader.read(wkbBytes);
        } catch (ParseException e) {
            log.error("Failed to parse WKB string: {}", hexWKB, e);
            throw new Exception("Failed to parse WKB string", e);
        }
    }

    /**
     * 将Geometry对象转换为GeoJSON Map
     * @param geometry JTS Geometry对象
     * @param decimals 保留的小数位数
     * @return GeoJSON格式的Map
     * @throws IOException 如果生成GeoJSON时发生错误
     */
    private static Map<String, Object> convertToGeoJSONMap(Geometry geometry, int decimals) throws IOException {
        try {
            GeometryJSON geometryJSON = new GeometryJSON(decimals);
            StringWriter writer = new StringWriter();
            geometryJSON.write(geometry, writer);
            String geoJsonStr = writer.toString();

            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(geoJsonStr, new TypeReference<Map<String, Object>>() {});
        } catch (IOException e) {
            log.error("Failed to convert Geometry to GeoJSON Map", e);
            throw new IOException("Failed to convert Geometry to GeoJSON Map", e);
        }
    }

    /**
     * 坐标转换工具类
     */
    private static class CoordinateTransform {
        private static final double PI = Math.PI;
        private static final double AXIS = 6378245.0;
        private static final double OFFSET = 0.00669342162296594323;

        /**
         * 将GCJ-02坐标系的Geometry转换为WGS84坐标系
         * @param geometry GCJ-02坐标系的Geometry
         * @return WGS84坐标系的Geometry
         */
        public static Geometry gcj02ToWgs84(Geometry geometry) {
            GeometryEditor editor = new GeometryEditor();
            return editor.edit(geometry, new GeometryEditor.CoordinateSequenceOperation() {
                @Override
                public CoordinateSequence edit(CoordinateSequence coordSeq, Geometry geom) {
                    Coordinate[] coords = coordSeq.toCoordinateArray();
                    for (Coordinate coord : coords) {
                        double[] wgs84 = gcj02ToWgs84(coord.x, coord.y);
                        coord.x = wgs84[0];
                        coord.y = wgs84[1];
                    }
                    return new CoordinateArraySequence(coords);
                }
            });
        }

        /**
         * 将GCJ-02坐标转换为WGS84坐标
         * @param lng 经度
         * @param lat 纬度
         * @return WGS84坐标
         */
        private static double[] gcj02ToWgs84(double lng, double lat) {
            if (outOfChina(lng, lat)) {
                return new double[]{lng, lat};
            }
            double[] delta = delta(lng, lat);
            return new double[]{lng - delta[0], lat - delta[1]};
        }

        /**
         * 判断坐标是否在中国范围内
         * @param lng 经度
         * @param lat 纬度
         * @return 是否在中国范围内
         */
        private static boolean outOfChina(double lng, double lat) {
            return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
        }

        /**
         * 计算GCJ-02与WGS84之间的偏移量
         * @param lng 经度
         * @param lat 纬度
         * @return 偏移量
         */
        private static double[] delta(double lng, double lat) {
            double dLat = transformLat(lng - 105.0, lat - 35.0);
            double dLng = transformLng(lng - 105.0, lat - 35.0);
            double radLat = lat / 180.0 * PI;
            double magic = Math.sin(radLat);
            magic = 1 - OFFSET * magic * magic;
            double sqrtMagic = Math.sqrt(magic);
            dLat = (dLat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtMagic) * PI);
            dLng = (dLng * 180.0) / (AXIS / sqrtMagic * Math.cos(radLat) * PI);
            return new double[]{dLng, dLat};
        }

        private static double transformLat(double x, double y) {
            double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y;
            ret += 0.2 * Math.sqrt(Math.abs(x));
            ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
            ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
            return ret;
        }

        private static double transformLng(double x, double y) {
            double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y;
            ret += 0.1 * Math.sqrt(Math.abs(x));
            ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
            ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
            return ret;
        }
    }

    /**
     * 测试方法
     */
    public static void main(String[] args) {
        // 示例 WKB 字符串
        String wkb = "0104000020E6100000......";// wkb字符串

        try {
            Map<String, Object> geoJsonMap = convert(wkb, 6); // 转换为GeoJSON,保留6位小数
            log.info("GeoJSON Map: {}", geoJsonMap);
        } catch (Exception e) {
            log.error("Error converting WKB to GeoJSON", e);
        }
    }
}

效果展示(上图为mapbox4326坐标系、下面是高德地图火星坐标系)

可见偏差比较小。

相关推荐
GIS数据转换器6 小时前
当三维地理信息遇上气象预警:电网安全如何实现“先知先觉”?
人工智能·科技·安全·gis·智慧城市·交互
GIS工具-gistools202111 小时前
Open Source Geospatial Content Management System -GeoNode
开源·gis·server
爱看书的小沐2 天前
【小沐学GIS】基于C++绘制二维瓦片地图2D Map(QT、OpenGL、GIS)
c++·qt·gis·opengl·glfw·glut·二维地图
爱地球的曲奇8 天前
【ArcGISPro学习笔记】布局输出时图例总是有省略号怎么办?
笔记·学习·gis
星火撩猿8 天前
GisWeb实战笔记(1)基于 Vue 3 + Vite + CesiumJS搭建gis开发环境
笔记·gis·地图·cesiumjs
城市数据匠16 天前
25【干货】在Arcgis中根据字段属性重新排序并自动编号的方法(二)
arcgis·gis·城市规划·国土空间规划·自然资源局
维维180-3121-145516 天前
基于ArcGIS的洪水灾害普查、风险评估及淹没制图技术研究
arcgis·gis·遥感·流域·水土
城市数据匠17 天前
26 Arcgis软件常用工具有哪些
arcgis·gis
GIS小小研究僧18 天前
SAR地理编码快速批处理工具
gis·rs·遥感
疯狂学习GIS18 天前
ArcGIS拼接、镶嵌同一空间位置的不同遥感影像
gis·rs·遥感数据