当前位置: 首页 > news >正文

关于网页地图的坐标系

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字符串)

@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坐标系、下面是高德地图火星坐标系)
在这里插入图片描述
可见偏差比较小。

相关文章:

  • 《深度学习实战》第6集:扩散模型(Diffusion Models)与高质量图像生成
  • Ruby基础
  • 【免费】YOLO[笑容]目标检测全过程(yolo环境配置+labelimg数据集标注+目标检测训练测试)
  • Spring Boot 接口 JSON 序列化优化:忽略 Null 值的九种解决方案详解
  • Python--内置模块和开发规范(上)
  • DeepSeek可实现智能派工,提升售后服务效率
  • ubuntu部署gitlab-ce及数据迁移
  • 【北京迅为】iTOP-RK3568OpenHarmony系统南向驱动开发-第2章 内核HDF驱动框架架构
  • 【容器化】低版本docker拉取ubuntn 22.04镜像启动容器执行apt update提示 NO_PUBKEY 871920D1991BC93C
  • 腿足机器人之十四-强化学习SAC算法
  • 指针的进阶(提高篇)
  • python绘图之灰度图
  • 一个实用的 python 库!
  • 天佐.乾坤袋 基于抽屉式文件存储的NoSql数据库
  • Windows上使用go-ios实现iOS17自动化
  • 点云配准技术的演进与前沿探索:从传统算法到深度学习融合(4)
  • 【Python系列】Python 连接 PostgreSQL 数据库并查询数据
  • 在 IntelliJ IDEA 中启动多个注册到 Nacos 的服务
  • 鸿蒙app 开发中 对于 layoutWeight 的理解
  • SslConnection::SslConnection()详解
  • wordpress搬家 数据库/北京中文seo
  • 哪些网站做的好处和坏处/黄页88网站推广方案
  • 行业网平台/seo教程免费
  • 建设网站的网络公司/河南怎样做网站推广
  • 网站建设制作设计/百度指数是怎么计算的
  • 网站上如何做电子手册/seo站外优化平台