唯杰地图 logo唯杰地图
VJMAP
EN

gis-transforms

vjmap/references/coord/gis-transforms.md 6.0 KB153 行
原文

本文回答:WGS84 / GCJ02 / BD09 / EPSG:3857 / 国家 2000 / 任意 EPSG 之间的相互转换。

一、主流坐标系速查

坐标系 用在哪 vjmap 枚举(真实值 / 可用别名,源码核实)
WGS84 (EPSG:4326) 国外地图、GPS、OSM、国际矢量底图、天地图 WGS84 / WGS1984、EPSG4326
GCJ02(火星) 高德、谷歌中国、腾讯地图 GCJ02 / AMap
BD09(百度) 百度地图 BD09 / BD09LL、Baidu、BMap
BD09MC(百度墨卡托米) 百度地图内部 BD09MC / BD09Meter
EPSG:3857(Web 墨卡托) vjmap 底层、大多数 tile 服务 EPSG3857 / EPSG900913、EPSG102100、WebMercator、WM
国家 2000 (CGCS2000) 国内 CAD 图、测绘局产出 一组编码 EPSG:4490~4554 等
西安 80 / 北京 54 国内早期测绘 EPSG:2401~2442 等

CRSTypes 枚举值只有 5 个真实值(上表第二列 / 前),其余写法都是别名。

二、前端一把梭 vjmap.transform.convert

// 单点
const gcj = [116.397442, 39.909188];
const wgs = vjmap.transform.convert(
  gcj,
  vjmap.transform.CRSTypes.GCJ02,
  vjmap.transform.CRSTypes.WGS84
);
// 结果:[116.3912, 39.907] 左右

// 整棵 GeoJSON
const data = {
  type: "FeatureCollection",
  features: [ /* ... */ ]
};
const newData = vjmap.transform.convert(
  data,
  vjmap.transform.CRSTypes.GCJ02,
  vjmap.transform.CRSTypes.WGS84
);

这个 API 同时支持两种调用方式:

// 1) (data, fromCrs, toCrs) 指定坐标系互转
vjmap.transform.convert(data, CRSTypes.GCJ02, CRSTypes.WGS84);

// 2) (data, callback) 用户自行决定每个点怎么变
vjmap.transform.convert(data, (pt) => {
  return [pt[0] + 10, pt[1] + 10];
});

⚠️ 两个行为注意:

  • GeoJSON 输入是原地改坐标并返回同一对象(点/点数组才返回新值)——要保留原始数据先自己深拷贝。
  • crsFrom === crsTo 时 convert 直接原样返回(不转换也不 clone)——别用它做"顺便拷贝一份"。

三、自定义 EPSG 之间转换:svc.cmdTransform(服务端)

适合国家 2000 / 北京 54 等国内投影坐标转经纬度:

// 把 4527 坐标转成 wgs84 4326
const res = await svc.cmdTransform("EPSG:4527", "EPSG:4326", vjmap.geoPoint([x, y]));
const lngLat = res[0];   // res 是数组(批量转)

反向同理:await svc.cmdTransform("EPSG:4326", "EPSG:4527", vjmap.geoPoint([lng, lat]))。注意 points 必须 vjmap.geoPoint(...) 包装,传裸 [x,y] 数组会序列化成 undefined,undefined;且 srs=源坐标系、crs=目标坐标系(与 wmsTileUrl 的语义相反),返回 [[x,y],...] 数组。

四、前端调 proj4(离线)

npm i proj4
import proj4 from "proj4";

proj4.defs([
  ["EPSG:4549", "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"]
]);

const lngLat = proj4("EPSG:4549", "EPSG:4326", [cadX, cadY]);

五、四参数转换(没有 EPSG 的 CAD 图)

当 CAD 图没有标准坐标系,需要给 2 个以上的公共点:

const cadPts = [vjmap.geoPoint([x1, y1]), vjmap.geoPoint([x2, y2]), ...];
const destPts = [vjmap.geoPoint([a1, b1]), vjmap.geoPoint([a2, b2]), ...];

const fourparam = vjmap.coordTransfromGetFourParamter(cadPts, destPts, false);
//  => { dx, dy, scale, rotate }

// 应用
const mapped = vjmap.coordTransfromByFourParamter(vjmap.geoPoint([x, y]), fourparam);

// 反向
const inv = vjmap.coordTransfromByInvFourParamter(vjmap.geoPoint([x, y]), fourparam);

六、典型 CAD → 火星坐标链路

国家 2000 CAD(EPSG:4527) → WGS84 → GCJ02 高德:

async function cadToAMap(cadPt) {
  const [wgs] = await svc.cmdTransform("EPSG:4527", "EPSG:4326", vjmap.geoPoint(cadPt));
  const gcj = vjmap.transform.convert(
    wgs,
    vjmap.transform.CRSTypes.EPSG4326,
    vjmap.transform.CRSTypes.AMap
  );
  return gcj;
}

反向(用户在高德上点了一个点,还原回 CAD):

async function aMapToCad(gcj) {
  const wgs = vjmap.transform.convert(gcj, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
  const [cad] = await svc.cmdTransform("EPSG:4326", "EPSG:4527", vjmap.geoPoint(wgs));
  return cad;
}

七、EPSG 辅助 API

// 通过 CAD 坐标 X 的前两位 + 坐标系族,获取 EPSG 码
vjmap.transform.getEpsgParam(
  vjmap.transform.EpsgCrsTypes.CGCS2000,
  39                                // 前两位;39 表示 3 度带号 39
).epsg;                              // 返回 "EPSG:4527"(带号变体,x_0=39500000)

// 反查:按坐标范围查候选 EPSG(X 6 位不带号时会返回 20+ 个候选)
vjmap.transform.getEpsgCode([x, y], crsType?, is3DegreeBelt?);

坐标系族(EpsgCrsTypes 枚举,大小写敏感):Wgs84、Merc3857、CGCS2000、Xian80、Beijing54、NewBeijing54。

⚠️ 带号 vs 不带号变体:getEpsgParam 只产带号变体(4527 要求 X=39xxxxxx)。图纸 X 是 6 位不带号(≈50xxxx)时要选不带号变体——如同中央经线 117°E 的 EPSG:4509(x_0=500000)。两者 proj4 参数只差 x_0,选错 X 偏约 39,000,000(大到"完全看不见",易误判成没叠上)。详见 cad-epsg。

八、常见坑

  1. 把 GCJ02 当 WGS84 直接扔进 OSM → 偏移约 300 米。国内必做加密转换。
  2. 同一点用 convert 转了两次火星 → 偏移翻倍。记录每个变量目前所在的 CRS。
  3. cmdTransform 没 await → 拿到的是 Promise 对象。
  4. cmdTransform 第三参传了 [lng, lat] 数字数组 → 内部按 p.x + "," + p.y 拼请求,实际发出 "undefined,undefined",换算结果恒定不变。必须 vjmap.geoPoint([lng, lat])。鼠标跟随不要打这个 HTTP,用本地高斯正算。
  5. proj4 没 defs EPSG → 抛 Non-numerical or undefined coefficient。必须先 proj4.defs(...) 注册。