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