1. 7 海图GIS平台开发
Shipxy
  • 船讯网API服务概述
  • 文档目录
  • 注册与创建应用
  • 多语言SDK引入
  • 标准API服务
    • 1 船舶查询
      • 1.1 船舶位置查询
        • 1.1.1 单船位置查询
        • 1.1.2 多船位置查询
        • 1.1.3 船队位置查询
      • 1.2 船舶模糊查询
      • 1.3 周边船舶查询
      • 1.4 区域船舶查询
      • 1.5 船舶船籍查询
      • 1.6 船舶档案查询
      • 1.7 档案服务扩展
    • 2 港口查询
      • 2.1 港口信息查询
      • 2.2 港口当前靠泊船查询
      • 2.3 港口当前到锚船查询
      • 2.4 港口预抵船舶查询
    • 3 历史行为
      • 3.1 船舶历史轨迹查询
      • 3.2 船舶互相搭靠记录查询
    • 4 挂靠记录
      • 4.1 船舶历史挂靠记录
      • 4.2 船舶挂靠指定港口记录
      • 4.3 船舶当前挂靠信息
      • 4.4 港口挂靠历史船舶
    • 5 航线规划
      • 5.1 点到点航线规划
      • 5.2 港到港航线规划
      • 5.3 预计到达时间(ETA)查询
    • 6 天气气象
      • 6.1 新全球气象
        • 6.1.1 实时气象数据
        • 6.1.2 未来气象预报
      • 6.2 全球台风
        • 6.2.1 获取全球台风列表
        • 6.2.2 获取单个台风信息
      • 6.3 国内港口潮汐
        • 6.3.1 查询国内潮汐观测站列表
        • 6.3.2 查询单个观测站潮汐详情
      • 6.4 全球港口潮汐
        • 6.4.1 查询全球潮汐观测站列表
        • 6.4.2 查询单个观测站潮汐详情
      • 6.5 海区气象
      • 6.6 单点海洋气象
      • 6.7 历史气象记录
    • 7 海图GIS平台开发
      • 7.1 快速入门:从注册到接入
      • 7.2 地图引擎基础:地图控制与业务绘制
      • 7.3 船位展示服务
      • 7.4 历史轨迹服务
      • 7.5 图层与气象服务
      • 7.6 航线绘制与区域回放服务
      • 7.7 Vue项目接入指南
      • 7.8 API Key后端代理接入
      • 7.9 坐标系转换指南
      • 7.10 私有化部署开发指南
        • 01 私有化地图加载绘制
        • 02 船舶与轨迹绘制实践
        • 03 气象图层效果绘制
    • 8 海事数据
      • 8.1 航行警告查询
    • 9 监控推送
      • 9.1 监控船队管理
        • 9.1.1 创建船队
        • 9.1.2 更新船队信息
        • 9.1.3 查询船队
        • 9.1.4 删除船队
        • 9.1.5 船队船舶增加
        • 9.1.6 船队船舶批量更新
        • 9.1.7 船队船舶删除
      • 9.2 区域监控推送
        • 9.2.1 区域创建
        • 9.2.2 区域更新
        • 9.2.3 区域查询
        • 9.2.4 区域删除
        • 9.2.5 区域监控推送内容
      • 9.3 船舶航速提醒推送
        • 9.3.1 新增船舶订阅
        • 9.3.2 删除订阅船舶信息
        • 9.3.3 查询订阅船舶列表
        • 9.3.4 船舶航速异常推送
      • 9.4 实时船位推送
      • 9.5 船舶到离港事件推送
      • 9.6 船舶动态ETA推送
      • 9.7 船舶AIS信号消失事件推送
      • 9.8 船舶搭靠事件推送
    • 文档附录
      • 船舶类型对照表
      • 服务码返回说明
      • 海区对照表
      • 航行状态对照表
      • 绕航节点清单
      • 航标类型对照表
      • 区域船二进制解析说明
      • 常见问题
  • AI智能体应用
    • AI大模型接入MCP服务
    • 智能体场景应用
      • 运力资源智能体
      • 船舶风控智能体
      • 运输规划智能体
      • 航次时效预测智能体
      • 船舶安全监控智能体
      • 多式联运协同智能体
      • 大宗贸易态势分析智能体
  • 视频监控AI识别预警服务
    • 视频监控服务接入指南
    • 视频监控API服务说明
    • 视频监控场景应用指南
    • 视频监控服务FAQ与最佳实践
  • 场景化开发示例
    • 大宗物流运输智能决策平台
      • 1、港口运力监控与找船
      • 2、船队货运跟踪与 ETA
      • 3、货运异常预警中心
      • 4、大宗航运态势分析与经营决策大屏
      • 5、航次时效预测与滞期费测算
      • 6、航次能效与碳强度管理
    • 海上设施安全监管与决策平台
      • 1、监管区域与事件推送(总览大屏)
      • 2、设施周边安全监控
      • 3、偏航识别与轨迹回放
      • 4、气象海况与航行警告
      • 5、海上安全态势监管大屏与执法效能分析
      • 6、水域通航密度与碰撞风险预测
      • 7、设施运维作业窗口智能排程与船舶调度
  1. 7 海图GIS平台开发

7.9 坐标系转换指南

ShipXY 海图 GIS 平台 —— 坐标系与转换指南

适用版本:ElaneMap H5 API 3.5
适用读者:接入船讯网海图 GIS 平台的前端 / 全栈开发人员
官方示例:https://api.shipxy.com/h5s/api/3.5/demo/


目录

  • 1. 为什么需要关注坐标系?
  • 2. 坐标系基础概念
    • 2.1 WGS84(国际通用坐标系)
    • 2.2 GCJ-02(火星坐标系)
    • 2.3 BD-09(百度坐标系)
    • 2.4 三者关系一览
  • 3. ShipXY 平台坐标系使用规则
    • 3.1 核心规则
    • 3.2 为什么地图/卫星图模式需要转换?
    • 3.3 为什么海图模式不需要转换?
    • 3.4 决策流程图
  • 4. 坐标系转换算法
    • 4.1 中国区域判定
    • 4.2 WGS84 → GCJ-02 转换原理
  • 5. 代码实现
    • 5.1 JavaScript 版(前端集成)
    • 5.2 Python 版(后端处理)
    • 5.3 Java 版(企业级后端)
  • 6. 与 ShipXY API 集成示例
    • 6.1 封装坐标适配器
    • 6.2 初始化地图并添加标注
    • 6.3 响应地图模式切换
    • 6.4 船位数据展示
    • 6.5 历史轨迹展示
  • 7. 完整 Demo 页面
  • 8. 最佳实践与常见问题
    • 8.1 何时转换,何时不转换
    • 8.2 性能优化建议
    • 8.3 常见陷阱
    • 8.4 FAQ

1. 为什么需要关注坐标系?

在接入 ShipXY 海图 GIS 平台时,很多开发者会遇到一个令人困惑的问题:同一个经纬度坐标,在地图模式和海图模式下显示的位置不一致,存在几十到几百米的偏移。

这不是 Bug,而是中国地理坐标系的特殊性导致的。国内法律要求所有在中国境内发布的地理信息必须经过加密偏移处理(即 GCJ-02 火星坐标系),而国际通用的 GPS 坐标(WGS84)和电子海图坐标均为未加密的标准坐标。ShipXY 平台同时使用多种瓦片图源,不同图源采用的坐标系不同,因此在开发时需要根据当前地图模式进行坐标转换。

一句话总结:你的船舶 AIS 数据是 WGS84 坐标,在地图/卫星图模式下需要转换为 GCJ-02 才能正确显示在中国地区的底图上;在海图模式下则直接使用 WGS84 即可。


2. 坐标系基础概念

2.1 WGS84(国际通用坐标系)

属性说明
全称World Geodetic System 1984
定义者美国国防部 / NGA
用途GPS 全球定位、国际海事电子海图(ENC)、国际航空
精度厘米级(精确定位)/ 米级(民用 GPS)
加密无加密,原始坐标

ShipXY 场景:船舶 AIS 上报的位置数据、历史轨迹数据、港口坐标等业务数据均使用 WGS84 坐标系。

2.2 GCJ-02(火星坐标系)

属性说明
全称GCJ-02(俗称"火星坐标系")
定义者中国国家测绘地理信息局
用途中国境内所有公开发布的电子地图
加密基于 WGS84 的非线性加密偏移,偏移量约 50~500 米
特点偏移量随地理位置非线性变化,无法通过简单加减常数还原

ShipXY 场景:地图模式(谷歌地图瓦片)和卫星图模式(卫星影像瓦片)在中国地区使用的底图瓦片为 GCJ-02 坐标系。

在 ShipXY 平台中,我们仅关注 WGS84 → GCJ-02 的单向转换(用于将业务数据适配到地图/卫星图底图)。


3. ShipXY 平台坐标系使用规则

3.1 核心规则

ShipXY ElaneMap H5 API 3.5 提供三种地图底图模式,不同模式下坐标系要求不同:

地图模式底图图源适用区域坐标系是否需要转换转换方向
地图模式谷歌/类似地图瓦片GCJ-02(中国陆地及沿海)✅ 需要WGS84 → GCJ-02
卫星图模式卫星影像瓦片GCJ-02(中国陆地及沿海)✅ 需要WGS84 → GCJ-02
海图模式电子海图瓦片WGS84❌ 不需要—

关键结论:

  • 地图模式 + 卫星图模式:中国陆地及沿海地区的底图瓦片使用 GCJ-02 坐标系,如果你的业务数据(如船位)是 WGS84,必须转换为 GCJ-02 后再标注到地图上,否则点位会偏移 50~500 米。
  • 海图模式:电子海图遵循国际海事标准(IHO S-57/S-101),底图瓦片使用 WGS84 坐标系,业务数据直接使用即可,无需转换。
  • 适用范围说明:需要转换的区域为中国陆地范围边界及沿海地区,不包含南海远洋区域。远洋及公海区域的坐标在所有模式下均使用 WGS84,无需转换。

3.2 为什么地图/卫星图模式需要转换?

  1. 法律要求:中国《测绘法》规定,在中国境内公开发布的地图必须使用 GCJ-02 加密坐标系。ShipXY 地图模式和卫星图模式使用的瓦片图源(如谷歌地图瓦片)在中国陆地及沿海区域已按法规要求进行了 GCJ-02 加密偏移处理。

  2. 瓦片与数据不匹配:你的船舶 AIS 数据来自卫星定位,是标准 WGS84 坐标。如果直接将 WGS84 坐标标注在 GCJ-02 的底图上,由于底图本身有非线性偏移,标注点会出现肉眼可见的位置偏差(尤其在中国沿海和内陆地区)。

  3. 偏移量示例:

    城市WGS84 经纬度GCJ-02 经纬度纬度偏移经度偏移实际距离偏移
    上海31.2304°N, 121.4737°E31.2278°N, 121.4784°E-0.0026°+0.0047°~490 米
    北京39.9042°N, 116.4074°E39.9056°N, 116.4132°E+0.0014°+0.0058°~490 米
    深圳22.5431°N, 114.0579°E22.5405°N, 114.0627°E-0.0026°+0.0048°~460 米
    大连38.9140°N, 121.6147°E38.9117°N, 121.6199°E-0.0023°+0.0052°~470 米

    可以看到,在中国主要港口城市,WGS84 与 GCJ-02 的偏移量约为 460~490 米。对于船舶监控这种对位置精度要求较高的场景,这个偏移是不可接受的。

3.3 为什么海图模式不需要转换?

  1. 国际海事标准:电子海图(Electronic Navigational Chart, ENC)遵循国际海道测量组织(IHO)的 S-57/S-101 标准,该标准强制要求使用 WGS84 坐标系。

  2. 船舶导航需求:电子海图用于船舶实际导航,坐标精度直接关系航行安全,不允许有任何加密偏移。

  3. ShipXY 海图瓦片:ShipXY 的海图底图瓦片源为国际标准电子海图,使用 WGS84 坐标系,因此你的 WGS84 业务数据可以直接标注,无需任何转换。

3.4 决策流程图

                    ┌─────────────────────────────┐
                    │   开始:获得一个 WGS84 坐标   │
                    │   (来自 AIS / GPS / 业务数据) │
                    └──────────────┬──────────────┘
                                   │
                                   ▼
                    ┌─────────────────────────────┐
                    │   当前地图是什么模式?        │
                    └──────────────┬──────────────┘
                                   │
                 ┌─────────────────┼─────────────────┐
                 │                 │                  │
                 ▼                 ▼                  ▼
          ┌────────────┐    ┌────────────┐    ┌────────────┐
          │  地图模式   │    │ 卫星图模式  │    │  海图模式   │
          └─────┬──────┘    └─────┬──────┘    └─────┬──────┘
                │                 │                  │
                ▼                 ▼                  ▼
        ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
        │ 坐标在中国    │  │ 坐标在中国    │  │   直接使用    │
        │ 陆地及沿海?   │  │ 陆地及沿海?   │  │   WGS84 坐标  │
        └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
               │                 │                  │
        ┌──────┴──────┐   ┌──────┴──────┐          │
        │             │   │             │          │
        ▼             ▼   ▼             ▼          │
     ┌──────┐    ┌──────┐ ┌──────┐  ┌──────┐      │
     │ 是   │    │ 否   │ │ 是   │  │ 否   │      │
     └──┬───┘    └──┬───┘ └──┬───┘  └──┬───┘      │
        │           │        │         │           │
        ▼           ▼        ▼         ▼           │
   ┌──────────┐ ┌──────┐ ┌──────────┐ ┌──────┐    │
   │WGS84→    │ │直接  │ │WGS84→    │ │直接  │    │
   │GCJ-02    │ │使用  │ │GCJ-02    │ │使用  │    │
   └────┬─────┘ └──┬───┘ └────┬─────┘ └──┬───┘    │
        │          │          │         │         │
        └──────────┴──────────┴─────────┴─────────┘
                            │
                            ▼
                 ┌─────────────────────┐
                 │  标注到 ShipXY 地图  │
                 └─────────────────────┘

4. 坐标系转换算法

4.1 中国陆地及沿海区域判定

并非所有坐标都需要转换——只有落在中国陆地及沿海地区的坐标才需要。南海远洋区域、公海、其他国家港口的坐标在所有模式下均使用 WGS84,无需转换。

判定方法采用经纬度边界框检查(Bounding Box):

/**
 * 判断坐标是否在中国陆地及沿海地区(不含南海远洋区域)
 * @param {number} lng - 经度
 * @param {number} lat - 纬度
 * @returns {boolean} - true 表示在转换范围内(需要转换),false 表示在范围外(无需转换)
 */
function isInChina(lng, lat) {
    // 中国陆地及沿海地区大致经纬度范围
    // 纬度下限 18.0:覆盖海南岛(18.2°N)及北部湾,排除南海深处远洋区域
    // 纬度上限 53.55:覆盖中国最北端(漠河约 53.5°N)
    // 经度范围 73.66~135.05:覆盖中国全境
    return lng > 73.66 && lng < 135.05 && lat > 18.0 && lat < 53.55;
}

范围说明:

  • 本判定覆盖中国陆地领土及沿海近岸水域,包括海南岛、北部湾等近海区域。
  • 不包含南海远洋区域(纬度低于 18°N 的南海深处)及其他远洋区域。这些区域的中国境外/远洋坐标无需转换。
  • 这是一个粗略的矩形边界框判定。对于更精确的陆地边界匹配,可引入 GeoJSON 行政区划数据进行点-多边形检测。

4.2 WGS84 → GCJ-02 转换原理

GCJ-02 加密算法基于一组非线性偏移函数。核心思想是对 WGS84 坐标施加一个与位置相关的偏移量,得到 GCJ-02 坐标。

偏移计算公式:

K = 偏移参数常量组
  x = 经度, y = 纬度

transformLat(x, y):
  ret = -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*sqrt(|x|)
  ret += (20.0*sin(6.0*x*π) + 20.0*sin(2.0*x*π)) * 2.0/3.0
  ret += (20.0*sin(y*π) + 40.0*sin(y/3.0*π)) * 2.0/3.0
  ret += (160.0*sin(y/12.0*π) + 320.0*sin(y*π/30.0)) * 2.0/3.0
  return ret

transformLng(x, y):
  ret = 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*sqrt(|x|)
  ret += (20.0*sin(6.0*x*π) + 20.0*sin(2.0*x*π)) * 2.0/3.0
  ret += (20.0*sin(x*π) + 40.0*sin(x/3.0*π)) * 2.0/3.0
  ret += (150.0*sin(x/12.0*π) + 300.0*sin(x/30.0*π)) * 2.0/3.0
  return ret

dLat = transformLat(x - 105.0, y - 35.0)
dLng = transformLng(x - 105.0, y - 35.0)
radLat = y / 180.0 * π
magic = sin(radLat)
magic = 1 - ee * magic * magic      // ee = 0.00669342162296594323
sqrtMagic = sqrt(magic)
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * π)
dLng = (dLng * 180.0) / (a / sqrtMagic * cos(radLat) * π)

// a = 6378245.0  (克拉索夫斯基椭球体长半轴)

GCJ-02 纬度 = y + dLat
GCJ-02 经度 = x + dLng

5. 代码实现

5.1 JavaScript 版(前端集成)

这是最常用的版本,直接在前端页面中使用,与 ShipxyAPI 无缝配合。

/**
 * ============================================================
 * ShipXY 坐标系转换工具 (JavaScript)
 * 支持 WGS84 → GCJ-02 单向转换
 * ============================================================
 */
var CoordTransform = (function () {

    // 克拉索夫斯基椭球体参数
    var a = 6378245.0;               // 长半轴
    var ee = 0.00669342162296594323; // 偏心率平方

    /**
     * 判断坐标是否在中国陆地及沿海地区(不含南海远洋区域)
     */
    function isInChina(lng, lat) {
        return lng > 73.66 && lng < 135.05 && lat > 18.0 && lat < 53.55;
    }

    /**
     * 纬度偏移变换
     */
    function transformLat(x, y) {
        var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y
                + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI)
             + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI)
             + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI)
             + 320.0 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 经度偏移变换
     */
    function transformLng(x, y) {
        var ret = 300.0 + x + 2.0 * y + 0.1 * x * x
                + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI)
             + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI)
             + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI)
             + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * WGS84 → GCJ-02(火星坐标系)
     * 仅对中国陆地及沿海地区的坐标进行转换,其他区域原样返回
     * @param {number} lng - WGS84 经度
     * @param {number} lat - WGS84 纬度
     * @returns {{lng: number, lat: number}} GCJ-02 坐标
     */
    function wgs84ToGcj02(lng, lat) {
        // 中国陆地及沿海地区以外不转换
        if (!isInChina(lng, lat)) {
            return { lng: lng, lat: lat };
        }
        var dLat = transformLat(lng - 105.0, lat - 35.0);
        var dLng = transformLng(lng - 105.0, lat - 35.0);
        var radLat = lat / 180.0 * Math.PI;
        var magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        var sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
        return {
            lng: lng + dLng,
            lat: lat + dLat
        };
    }

    return {
        isInChina: isInChina,
        wgs84ToGcj02: wgs84ToGcj02
    };
})();

5.2 Python 版(后端处理)

适用于后端对 AIS 数据进行批量坐标转换后再返回前端的场景。

"""
ShipXY 坐标系转换工具 (Python)
支持 WGS84 -> GCJ-02 单向转换
"""
import math

# 克拉索夫斯基椭球体参数
_a = 6378245.0                # 长半轴
_ee = 0.00669342162296594323  # 偏心率平方


def is_in_china(lng: float, lat: float) -> bool:
    """判断坐标是否在中国陆地及沿海地区(不含南海远洋区域)"""
    return 73.66 < lng < 135.05 and 18.0 < lat < 53.55


def _transform_lat(x: float, y: float) -> float:
    """纬度偏移变换"""
    ret = (-100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y
           + 0.1 * x * y + 0.2 * math.sqrt(abs(x)))
    ret += (20.0 * math.sin(6.0 * x * math.pi)
            + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(y * math.pi)
            + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(y / 12.0 * math.pi)
            + 320.0 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0
    return ret


def _transform_lng(x: float, y: float) -> float:
    """经度偏移变换"""
    ret = (300.0 + x + 2.0 * y + 0.1 * x * x
           + 0.1 * x * y + 0.1 * math.sqrt(abs(x)))
    ret += (20.0 * math.sin(6.0 * x * math.pi)
            + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(x * math.pi)
            + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(x / 12.0 * math.pi)
            + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0
    return ret


def wgs84_to_gcj02(lng: float, lat: float) -> tuple:
    """
    WGS84 -> GCJ-02 (火星坐标系)
    仅对中国陆地及沿海地区的坐标进行转换,其他区域原样返回
    返回 (gcj02_lng, gcj02_lat)
    """
    if not is_in_china(lng, lat):
        return (lng, lat)

    d_lat = _transform_lat(lng - 105.0, lat - 35.0)
    d_lng = _transform_lng(lng - 105.0, lat - 35.0)
    rad_lat = lat / 180.0 * math.pi
    magic = math.sin(rad_lat)
    magic = 1 - _ee * magic * magic
    sqrt_magic = math.sqrt(magic)
    d_lat = (d_lat * 180.0) / ((_a * (1 - _ee)) / (magic * sqrt_magic) * math.pi)
    d_lng = (d_lng * 180.0) / (_a / sqrt_magic * math.cos(rad_lat) * math.pi)

    return (lng + d_lng, lat + d_lat)


# ===================== 使用示例 =====================
if __name__ == "__main__":
    # 上海港 WGS84 坐标
    wgs_lng, wgs_lat = 121.4737, 31.2304
    gcj_lng, gcj_lat = wgs84_to_gcj02(wgs_lng, wgs_lat)
    print(f"WGS84:  ({wgs_lng}, {wgs_lat})")
    print(f"GCJ-02: ({gcj_lng:.6f}, {gcj_lat:.6f})")
    print(f"偏移: 经度 {gcj_lng - wgs_lng:.6f}°, 纬度 {gcj_lat - wgs_lat:.6f}°")

5.3 Java 版(企业级后端)

适用于 Java Spring Boot 等企业级后端服务。

/**
 * ShipXY 坐标系转换工具 (Java)
 * 支持 WGS84 -> GCJ-02 单向转换
 */
public class CoordTransform {

    private static final double A = 6378245.0;
    private static final double EE = 0.00669342162296594323;
    private static final double PI = Math.PI;

    /**
     * 判断坐标是否在中国陆地及沿海地区(不含南海远洋区域)
     */
    public static boolean isInChina(double lng, double lat) {
        return lng > 73.66 && lng < 135.05 && lat > 18.0 && lat < 53.55;
    }

    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 + 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.0 * 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 + 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;
    }

    /**
     * WGS84 -> GCJ-02
     * 仅对中国陆地及沿海地区的坐标进行转换,其他区域原样返回
     * @return double[]{lng, lat}
     */
    public static double[] wgs84ToGcj02(double lng, double lat) {
        if (!isInChina(lng, lat)) {
            return new double[]{lng, 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 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI);
        dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI);
        return new double[]{lng + dLng, lat + dLat};
    }
}

6. 与 ShipXY API 集成示例

6.1 封装坐标适配器

在实际开发中,建议封装一个坐标适配器,根据当前地图模式自动决定是否转换坐标,避免在业务代码中到处写 if-else。

/**
 * ShipXY 坐标适配器
 * 根据当前地图模式自动处理坐标转换
 */
var CoordAdapter = (function () {

    // 地图模式常量(与 ShipxyAPI 内部枚举对应)
    var MAP_MODE_NORMAL = 'normal';    // 地图模式
    var MAP_MODE_SATELLITE = 'satellite'; // 卫星图模式
    var MAP_MODE_SEA = 'sea';          // 海图模式

    var currentMode = MAP_MODE_SEA;    // 默认海图模式

    /**
     * 设置当前地图模式
     * @param {string} mode - 'normal' | 'satellite' | 'sea'
     */
    function setMode(mode) {
        currentMode = mode;
        console.log('[CoordAdapter] 地图模式切换为: ' + mode);
    }

    /**
     * 判断当前模式是否需要坐标转换
     */
    function needTransform() {
        return currentMode === MAP_MODE_NORMAL || currentMode === MAP_MODE_SATELLITE;
    }

    /**
     * 将 WGS84 坐标适配为当前地图模式所需的坐标
     * - 地图/卫星图模式:WGS84 → GCJ-02(仅中国陆地及沿海地区)
     * - 海图模式:直接返回 WGS84
     *
     * @param {number} lng - WGS84 经度
     * @param {number} lat - WGS84 纬度
     * @returns {{lng: number, lat: number}} 适配后的坐标
     */
    function adapt(lng, lat) {
        if (needTransform()) {
            return CoordTransform.wgs84ToGcj02(lng, lat);
        }
        return { lng: lng, lat: lat };
    }

    /**
     * 批量适配坐标(用于轨迹、船队等多点场景)
     * @param {Array<{lng: number, lat: number}>} coords - WGS84 坐标数组
     * @returns {Array<{lng: number, lat: number}>} 适配后的坐标数组
     */
    function adaptBatch(coords) {
        return coords.map(function (c) {
            return adapt(c.lng, c.lat);
        });
    }

    return {
        setMode: setMode,
        needTransform: needTransform,
        adapt: adapt,
        adaptBatch: adaptBatch,
        MODE_NORMAL: MAP_MODE_NORMAL,
        MODE_SATELLITE: MAP_MODE_SATELLITE,
        MODE_SEA: MAP_MODE_SEA
    };
})();

6.2 初始化地图并添加标注

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>ShipXY 坐标转换示例</title>
    <script src="https://api.shipxy.com/h5s/api/3.5/plugins/jquery/jquery.min.js"></script>
    <script src="https://api.shipxy.com/h5s/api/3.5/?k=您的密钥"></script>
    <style>
        .my-map {
            position: absolute;
            left: 0; top: 0;
            width: 100%; height: 100%;
            overflow: hidden;
            outline: none;
            background-color: #A3CCFF;
        }
    </style>
</head>
<body>
<div id="map" class="my-map"></div>
<script>
    // 1. 初始化地图(默认海图模式)
    var map = new ShipxyAPI.Map("map", { ak: "您的密钥" });

    // 2. 设置初始模式
    CoordAdapter.setMode(CoordAdapter.MODE_SEA);

    // 3. 添加船舶标注(WGS84 数据来自 AIS)
    var shipWgs84 = { lng: 121.4737, lat: 31.2304 };  // 上海港

    // 适配坐标
    var displayCoord = CoordAdapter.adapt(shipWgs84.lng, shipWgs84.lat);
    console.log("原始 WGS84: ", shipWgs84);
    console.log("显示坐标: ", displayCoord);

    // 添加标注(注意 Leaflet 坐标顺序为 [纬度, 经度])
    var marker = L.marker([displayCoord.lat, displayCoord.lng], {
        title: "测试船舶"
    }).addTo(map);

    marker.bindPopup("船舶位置(已适配坐标系)<br>WGS84: " +
        shipWgs84.lng.toFixed(6) + ", " + shipWgs84.lat.toFixed(6) + "<br>" +
        "显示: " + displayCoord.lng.toFixed(6) + ", " + displayCoord.lat.toFixed(6));

    // 定位到该点
    map.setView([displayCoord.lat, displayCoord.lng], 10);
</script>
</body>
</html>

6.3 响应地图模式切换

/**
 * 监听地图模式切换,自动更新坐标适配器
 */
function onMapModeChange(newMode) {
    // 更新适配器模式
    switch (newMode) {
        case 'normal':
            CoordAdapter.setMode(CoordAdapter.MODE_NORMAL);
            break;
        case 'satellite':
            CoordAdapter.setMode(CoordAdapter.MODE_SATELLITE);
            break;
        case 'sea':
        default:
            CoordAdapter.setMode(CoordAdapter.MODE_SEA);
            break;
    }

    // 刷新所有标注点的坐标
    refreshAllMarkers();
}

/**
 * 刷新地图上所有标注的坐标
 * 适用于模式切换后重新计算坐标位置
 */
function refreshAllMarkers() {
    // 假设 shipDataList 是你的船舶数据列表(WGS84 原始数据)
    shipDataList.forEach(function (ship) {
        if (ship.marker) {
            var coord = CoordAdapter.adapt(ship.lng, ship.lat);
            ship.marker.setLatLng([coord.lat, coord.lng]);
        }
    });

    // 如果有轨迹线,也需要刷新
    if (currentTrackPolyline) {
        var adaptedCoords = CoordAdapter.adaptBatch(
            rawTrackCoords.map(function (c) {
                return { lng: c[0], lat: c[1] };
            })
        );
        currentTrackPolyline.setLatLngs(
            adaptedCoords.map(function (c) {
                return [c.lat, c.lng];
            })
        );
    }
}

6.4 船位数据展示

/**
 * 渲染船位服务返回的船舶数据
 * ShipXY 船位 API 返回的是 WGS84 坐标
 */
function renderShips(shipList) {
    shipList.forEach(function (ship) {
        // 适配坐标
        var coord = CoordAdapter.adapt(ship.lon, ship.lat);

        // 创建船舶图标标注
        var shipMarker = L.marker([coord.lat, coord.lng], {
            icon: createShipIcon(ship),
            title: ship.name || ('MMSI:' + ship.mmsi)
        }).addTo(map);

        // 保存标注引用,用于模式切换时刷新
        ship.marker = shipMarker;

        // 弹窗信息
        shipMarker.bindPopup(
            '<b>' + (ship.name || '未知') + '</b><br>' +
            'MMSI: ' + ship.mmsi + '<br>' +
            '航速: ' + (ship.sog || 0) + ' 节<br>' +
            '航向: ' + (ship.cog || 0) + '°<br>' +
            '状态: ' + (ship.navStatus || '未知') + '<br>' +
            '<hr>' +
            '原始 WGS84: ' + ship.lon.toFixed(6) + ', ' + ship.lat.toFixed(6) + '<br>' +
            '显示坐标: ' + coord.lng.toFixed(6) + ', ' + coord.lat.toFixed(6)
        );
    });
}

// 示例:通过 ShipXY 区域船位服务获取数据
// var searchService = ShipxyAPI.SeachShipService(map, {...});
// searchService.getData(function(data) {
//     renderShips(data.ships);
// });

6.5 历史轨迹展示

/**
 * 渲染历史轨迹
 * ShipXY 轨迹 API 返回的是 WGS84 坐标数组
 */
function renderTrack(trackPoints) {
    // trackPoints: [{lon, lat, time, sog, cog}, ...]

    // 适配所有轨迹点坐标
    var adaptedPoints = trackPoints.map(function (p) {
        var coord = CoordAdapter.adapt(p.lon, p.lat);
        return {
            lat: coord.lat,
            lng: coord.lng,
            time: p.time,
            sog: p.sog,
            cog: p.cog,
            // 保留原始 WGS84 坐标用于弹窗展示
            originalLon: p.lon,
            originalLat: p.lat
        };
    });

    // 绘制轨迹线
    var latlngs = adaptedPoints.map(function (p) {
        return [p.lat, p.lng];
    });
    var polyline = L.polyline(latlngs, {
        color: '#FF6B35',
        weight: 3,
        opacity: 0.8
    }).addTo(map);

    // 在关键点添加标注(起点、终点、锚泊点等)
    var startMarker = L.marker(latlngs[0], { title: '起点' })
        .addTo(map)
        .bindPopup('起点<br>WGS84: ' + adaptedPoints[0].originalLon.toFixed(6) +
                   ', ' + adaptedPoints[0].originalLat.toFixed(6));

    var endMarker = L.marker(latlngs[latlngs.length - 1], { title: '终点' })
        .addTo(map)
        .bindPopup('终点<br>WGS84: ' + adaptedPoints[adaptedPoints.length - 1].originalLon.toFixed(6) +
                   ', ' + adaptedPoints[adaptedPoints.length - 1].originalLat.toFixed(6));

    // 适配地图视野
    map.fitBounds(polyline.getBounds(), { padding: [50, 50] });

    // 保存引用,用于模式切换时刷新
    currentTrackPolyline = polyline;
    rawTrackCoords = trackPoints.map(function (p) {
        return [p.lon, p.lat];
    });
}

7. 完整 Demo 页面

以下是一个可直接运行的完整 Demo 页面,展示坐标系转换效果。替换 您的密钥 为实际的 ShipXY API Key 后即可使用。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ShipXY 坐标系转换演示</title>
    <script src="https://api.shipxy.com/h5s/api/3.5/plugins/jquery/jquery.min.js"></script>
    <script src="https://api.shipxy.com/h5s/api/3.5/?k=您的密钥"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft YaHei", sans-serif; }
        #map {
            position: absolute;
            left: 0; top: 0;
            width: 100%; height: 100%;
            background-color: #A3CCFF;
        }
        .control-panel {
            position: absolute;
            top: 10px; right: 10px;
            z-index: 1000;
            background: rgba(255,255,255,0.95);
            border-radius: 8px;
            padding: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            width: 340px;
            font-size: 13px;
        }
        .control-panel h3 {
            font-size: 15px;
            margin-bottom: 10px;
            color: #333;
        }
        .control-panel .section {
            margin-bottom: 12px;
            padding-bottom: 12px;
            border-bottom: 1px solid #eee;
        }
        .control-panel .section:last-child {
            border-bottom: none;
        }
        .control-panel label {
            display: inline-block;
            width: 70px;
            color: #666;
        }
        .control-panel input {
            width: 100px;
            padding: 3px 6px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 13px;
        }
        .control-panel button {
            padding: 5px 12px;
            margin: 2px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 13px;
            background: #1890ff;
            color: #fff;
        }
        .control-panel button:hover { background: #40a9ff; }
        .control-panel button.active { background: #096dd9; }
        .control-panel .info {
            background: #f6f8fa;
            padding: 8px;
            border-radius: 4px;
            font-family: monospace;
            font-size: 12px;
            line-height: 1.8;
            color: #333;
        }
        .control-panel .offset { color: #ff4d4f; font-weight: bold; }
        .control-panel .note { color: #999; font-size: 11px; margin-top: 4px; }
    </style>
</head>
<body>
<div id="map"></div>

<div class="control-panel">
    <h3>ShipXY 坐标系转换演示</h3>

    <div class="section">
        <label>经度 (WGS84):</label>
        <input type="text" id="inputLng" value="121.4737">
        <br>
        <label>纬度 (WGS84):</label>
        <input type="text" id="inputLat" value="31.2304">
        <br>
        <button onclick="updatePoint()">更新点位</button>
    </div>

    <div class="section">
        <label>地图模式:</label>
        <button id="btnSea" class="active" onclick="switchMode('sea')">海图模式</button>
        <button id="btnNormal" onclick="switchMode('normal')">地图模式</button>
        <button id="btnSatellite" onclick="switchMode('satellite')">卫星图</button>
    </div>

    <div class="section">
        <div class="info" id="coordInfo">
            WGS84: 121.473700, 31.230400<br>
            GCJ-02: --<br>
            偏移: --<br>
            是否在中国: 是<br>
            当前模式: 海图模式 (无需转换)
        </div>
        <div class="note">蓝点 = WGS84 原始坐标 | 红点 = GCJ-02 转换后坐标</div>
    </div>
</div>

<script>
    // ============ 坐标转换工具 ============
    var CoordTransform = (function () {
        var a = 6378245.0, ee = 0.00669342162296594323;
        function isInChina(lng, lat) {
            // 中国陆地及沿海地区(不含南海远洋区域)
            return lng > 73.66 && lng < 135.05 && lat > 18.0 && lat < 53.55;
        }
        function transformLat(x, y) {
            var ret = -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*Math.sqrt(Math.abs(x));
            ret += (20.0*Math.sin(6.0*x*Math.PI) + 20.0*Math.sin(2.0*x*Math.PI)) * 2.0/3.0;
            ret += (20.0*Math.sin(y*Math.PI) + 40.0*Math.sin(y/3.0*Math.PI)) * 2.0/3.0;
            ret += (160.0*Math.sin(y/12.0*Math.PI) + 320.0*Math.sin(y*Math.PI/30.0)) * 2.0/3.0;
            return ret;
        }
        function transformLng(x, y) {
            var ret = 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*Math.sqrt(Math.abs(x));
            ret += (20.0*Math.sin(6.0*x*Math.PI) + 20.0*Math.sin(2.0*x*Math.PI)) * 2.0/3.0;
            ret += (20.0*Math.sin(x*Math.PI) + 40.0*Math.sin(x/3.0*Math.PI)) * 2.0/3.0;
            ret += (150.0*Math.sin(x/12.0*Math.PI) + 300.0*Math.sin(x/30.0*Math.PI)) * 2.0/3.0;
            return ret;
        }
        function wgs84ToGcj02(lng, lat) {
            if (!isInChina(lng, lat)) return { lng: lng, lat: lat };
            var dLat = transformLat(lng - 105.0, lat - 35.0);
            var dLng = transformLng(lng - 105.0, lat - 35.0);
            var radLat = lat / 180.0 * Math.PI;
            var magic = Math.sin(radLat);
            magic = 1 - ee * magic * magic;
            var sqrtMagic = Math.sqrt(magic);
            dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
            dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
            return { lng: lng + dLng, lat: lat + dLat };
        }
        return { isInChina: isInChina, wgs84ToGcj02: wgs84ToGcj02 };
    })();

    // ============ 地图初始化 ============
    var map = new ShipxyAPI.Map("map", { ak: "您的密钥" });
    var currentMode = 'sea';
    var wgs84Marker = null;   // 蓝点(WGS84 原始)
    var gcj02Marker = null;   // 红点(GCJ-02 转换后)
    var offsetLine = null;    // 偏移连线

    function updatePoint() {
        var lng = parseFloat(document.getElementById('inputLng').value);
        var lat = parseFloat(document.getElementById('inputLat').value);
        var inChina = CoordTransform.isInChina(lng, lat);
        var gcj = CoordTransform.wgs84ToGcj02(lng, lat);
        var needTransform = (currentMode === 'normal' || currentMode === 'satellite') && inChina;

        // 清除旧标注
        if (wgs84Marker) { map.removeLayer(wgs84Marker); wgs84Marker = null; }
        if (gcj02Marker) { map.removeLayer(gcj02Marker); gcj02Marker = null; }
        if (offsetLine) { map.removeLayer(offsetLine); offsetLine = null; }

        // 地图/卫星图模式:显示两个点(原始 vs 转换后)
        // 海图模式:只显示原始点
        var displayCoord = needTransform ? gcj : { lng: lng, lat: lat };

        // 蓝点 = 实际应标注的位置(适配当前模式)
        wgs84Marker = L.circleMarker([displayCoord.lat, displayCoord.lng], {
            radius: 8, color: '#1890ff', fillColor: '#1890ff',
            fillOpacity: 0.8, weight: 2
        }).addTo(map).bindPopup(
            '<b>显示坐标(已适配模式)</b><br>' +
            '当前模式: ' + getModeName(currentMode) + '<br>' +
            (needTransform ? '已从 WGS84 转换为 GCJ-02' : '使用原始 WGS84') + '<br>' +
            '坐标: ' + displayCoord.lng.toFixed(6) + ', ' + displayCoord.lat.toFixed(6)
        );

        // 如果需要转换,再显示红色对比点(未转换的 WGS84 直接标注,展示偏移)
        if (needTransform) {
            gcj02Marker = L.circleMarker([lat, lng], {
                radius: 8, color: '#ff4d4f', fillColor: '#ff4d4f',
                fillOpacity: 0.8, weight: 2
            }).addTo(map).bindPopup(
                '<b>未转换的 WGS84 坐标</b><br>' +
                '直接标注在 GCJ-02 底图上会产生偏移<br>' +
                '坐标: ' + lng.toFixed(6) + ', ' + lat.toFixed(6)
            );

            // 偏移连线
            offsetLine = L.polyline(
                [[displayCoord.lat, displayCoord.lng], [lat, lng]],
                { color: '#ff4d4f', weight: 2, dashArray: '5,5' }
            ).addTo(map);
        }

        // 更新信息面板
        var dLng = gcj.lng - lng, dLat = gcj.lat - lat;
        var distance = Math.sqrt(dLng*dLng + dLat*dLat) * 111000; // 粗略估算米数
        document.getElementById('coordInfo').innerHTML =
            '<b>WGS84:</b> ' + lng.toFixed(6) + ', ' + lat.toFixed(6) + '<br>' +
            '<b>GCJ-02:</b> ' + gcj.lng.toFixed(6) + ', ' + gcj.lat.toFixed(6) + '<br>' +
            '<span class="offset">偏移: 经度 ' + (dLng >= 0 ? '+' : '') + dLng.toFixed(6) +
            '°, 纬度 ' + (dLat >= 0 ? '+' : '') + dLat.toFixed(6) + '° (≈' + Math.round(distance) + '米)</span><br>' +
            '是否在中国: ' + (inChina ? '是' : '否') + '<br>' +
            '当前模式: ' + getModeName(currentMode) + ' ' +
            (needTransform ? '(✅ 需要转换)' : '(❌ 无需转换)');

        // 定位地图
        map.setView([displayCoord.lat, displayCoord.lng], 13);
    }

    function switchMode(mode) {
        currentMode = mode;
        // 更新按钮样式
        document.querySelectorAll('.control-panel button').forEach(function(btn) {
            btn.classList.remove('active');
        });
        document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).classList.add('active');
        // TODO: 调用 ShipxyAPI 切换底图
        // 刷新点位显示
        updatePoint();
    }

    function getModeName(mode) {
        return { 'normal': '地图模式', 'satellite': '卫星图模式', 'sea': '海图模式' }[mode];
    }

    // 初始加载
    updatePoint();
</script>
</body>
</html>

8. 最佳实践与常见问题

8.1 何时转换,何时不转换

场景地图/卫星图模式海图模式
中国陆地及沿海船位标注✅ WGS84 → GCJ-02❌ 直接使用 WGS84
远洋/公海船位标注❌ 直接使用 WGS84❌ 直接使用 WGS84
历史轨迹绘制(中国沿海)✅ 逐点转换❌ 直接使用 WGS84
港口/航标标注(中国沿海)✅ 转换❌ 直接使用 WGS84
区域多边形查询✅ 顶点转 GCJ-02❌ 直接使用 WGS84

8.2 性能优化建议

  1. 批量转换:对于轨迹等大量坐标点,使用批量转换函数,减少函数调用开销。

  2. 缓存转换结果:对于不频繁变动的静态标注(如港口位置),转换后缓存结果,避免重复计算。

  3. 区域外跳过:isInChina() 判定开销极小(4 次浮点比较),但能跳过大量远洋/公海坐标的三角函数计算。远洋船舶无需转换。

  4. 前端转换优先:坐标转换计算量很小(单次 < 0.01ms),建议在前端完成,避免增加后端接口延迟。批量场景(如万级轨迹点)也可在后端预转换。

  5. 模式切换时批量刷新:模式切换时统一刷新所有标注,而不是逐个响应事件。可以使用 requestAnimationFrame 或 debounce 优化。

8.3 常见陷阱

陷阱说明解决方案
Leaflet 坐标顺序Leaflet 使用 [lat, lng] 顺序,而业务数据通常是 {lng, lat} 或 [lng, lat]转换时注意顺序,统一封装适配层
地图模式未切换就标注默认海图模式不加转换,切到地图模式后点位偏移模式切换后立即刷新所有标注
轨迹点部分在远洋轨迹跨越大洋,部分点在中国沿海部分在远洋wgs84ToGcj02() 内部已自动判断,远洋点原样返回
海图叠加地图图层在海图上叠加地图瓦片作为混合底图需要根据实际显示的底图决定坐标系,建议避免混用

8.4 FAQ

Q1: 偏移量有多大?不转换会怎样?

A: 在中国陆地及沿海地区,WGS84 与 GCJ-02 的偏移量约为 50500 米,具体取决于地理位置。在沿海港口区域通常约 460490 米。对于船舶监控,这个偏移会导致船舶图标显示在错误的航道甚至岸上。

Q2: 转换算法的精度如何?

A: WGS84 → GCJ-02 正向转换精度极高(与官方偏移一致),完全满足船舶监控和可视化展示的需求。

Q3: 坐标转换是否合法?

A: GCJ-02 算法是公开的非保密算法,在开发者社区广泛使用。坐标转换本身是技术对接需要,不涉及违法问题。但在公开发布的地图产品中,仍需确保符合国家测绘法规的相关要求。

Q4: ShipXY 船位 API 返回的是什么坐标系?

A: ShipXY 所有业务数据 API(船位、轨迹、港口等)返回的均为 WGS84 坐标系。坐标转换仅在前端展示环节根据地图模式按需进行。

Q5: 如何判断当前是否在地图/卫星图模式?

A: 可通过监听 ShipxyAPI 的底图切换事件,或检查 map 对象的当前图层类型。建议在应用层维护一个 currentMode 变量,在用户切换底图时同步更新。

Q6: 南海远洋区域的船舶需要转换坐标吗?

A: 不需要。本文档定义的转换范围为中国陆地及沿海地区(纬度 18°N 以北),南海远洋区域及公海区域的坐标在所有模式下均使用 WGS84,无需转换。


附录:快速参考卡

┌─────────────────────────────────────────────────────────────────┐
│                    ShipXY 坐标转换速查表                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  业务数据始终是 WGS84,展示时按地图模式适配:                      │
│                                                                 │
│  ┌─────────────┐  中国陆地及沿海  ┌──────────────┐              │
│  │   WGS84     │ ────地图/卫星────▶ │   GCJ-02     │              │
│  │ (AIS/GPS)   │      模式         │  (标注到地图)  │              │
│  └─────────────┘                  └──────────────┘              │
│        │                                                        │
│        │─────海图模式────────▶ 直接标注(WGS84 = 海图坐标)        │
│        │                                                        │
│        │──远洋/南海/公海─────▶ 直接标注(无需任何转换)            │
│                                                                 │
│  核心函数:                                                      │
│    CoordTransform.wgs84ToGcj02(lng, lat)  → {lng, lat}          │
│    CoordTransform.isInChina(lng, lat)     → boolean             │
│                                                                 │
│  适配器(推荐使用):                                              │
│    CoordAdapter.setMode('sea' | 'normal' | 'satellite')          │
│    CoordAdapter.adapt(lng, lat)           → {lng, lat}          │
│    CoordAdapter.adaptBatch(coords)        → [{lng, lat}, ...]   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
上一页
7.8 API Key后端代理接入
下一页
7.10 私有化部署开发指南