projection-api
本文回答:
GeoProjection/LnglatProjection/map.toLngLat/fromLngLat/map.project/unproject/toMercator/fromMercator/wcsToUcs/ucsToWcs的用法。
一、两种投影类
// CAD 底图:CAD 范围映射到 (-180..180, -85..85)
const prj = new vjmap.GeoProjection(res.bounds); // 字符串或 GeoBounds 都可
// 互联网底图:无需 bounds,直接使用经纬度
const prj = new vjmap.LnglatProjection();
公共方法:
prj.toLngLat(cadPoint); // CAD -> 渲染经纬度
prj.fromLngLat(lngLat); // 渲染经纬度 -> CAD
prj.getMapExtent(); // 返回 GeoBounds(CAD 空间)
二、Map 上的等价方法
map.attach(svc, prj) 之后(没 attach 时 toLngLat 退化为恒等 [x,y] 数组、fromLngLat 返回 GeoPoint——互联网底图碰巧能用,CAD 底图直接错):
| Map API | Projection API | 用途 |
|---|---|---|
map.toLngLat(cad) |
prj.toLngLat(cad) |
CAD → 渲染经纬度 |
map.fromLngLat(lngLat) |
prj.fromLngLat(lngLat) |
渲染经纬度 → CAD |
| —(Map 上没有) | prj.toMercator(cad) / prj.fromMercator(mkt) |
CAD ↔ 拉伸墨卡托米(±20037508,非 0~1 归一化、非真实地理墨卡托) |
map.project(lngLat) |
— | 渲染经纬度 → 屏幕像素(引擎原生) |
map.projectEx(lngLat, altitude?) |
— | 带高度的像素投影,3D 俯仰时更稳 |
map.unproject([x, y]) |
— | 屏幕像素 → 渲染经纬度 |
map.getMapExtent() |
prj.getMapExtent() |
当前图的 CAD 范围(GeoBounds) |
map.getGeoBounds(ratio?) |
— | ⚠️ 全图范围 × ratio 的 CAD 范围,不是当前视野、不是经纬度!ratio 默认取 initViewScale(sys_cad2000 为 0.4,缺省回退 0.7);缩放平移都不改变它。要当前视野的 CAD 范围:map.fromLngLat(map.getBounds()) |
map.getEnvelopBounds(wkt) |
— | 解析查询结果的 envelop(墨卡托 WKT)→ CAD 空间 GeoBounds(内部 prj.fromMercator) |
map.updateMapExtent(b) |
— | 动态改变 CAD 范围,重建 projection |
map.wcsToUcs(wcsPt) / map.ucsToWcs(ucsPt) |
— | WCS ↔ UCS(map.hasUcs() 为 true 时有效) |
长度类 API(toMeter/fromMeter 在 Projection 上,Map 上没有!调用报 map.toMeter is not a function)
| API | 语义 | 示例值(sys_cad2000) |
|---|---|---|
prj.toMeter(len) / prj.fromMeter(m) |
CAD 长度 ↔ 拉伸后的墨卡托米 | toMeter(1) = 6152.3——该图 1 图纸单位 = 1 真实米,所以这个"米"不是地面米,只是渲染空间尺度 |
map.geoLengthToHeight(len) |
CAD 长度 → 3D 拉伸高度值 | 内部即 getMapProjection().toMeter(len) |
map.geoToMercatorLength(len) / map.mercatorToGeoLength(len) |
CAD 长度 ↔ 墨卡托长度 | 同一尺度 |
map.pixelToGeoLength(len, zoom) |
像素 → CAD 长度 | = extent.width*len/(2^zoom*512),zoom 必填;查询 simplifyTolerance 用 |
map.geoToPixelLength(len, zoom) |
CAD 长度 → 像素 | 反向 |
map.pixelToHeight(len, zoom) |
像素 → 高度值 | geoLengthToHeight(pixelToGeoLength(...)) |
要真实地面米:有 EPSG 的图图纸单位本身就是米,直接用 CAD 长度;否则
cmdTransform转 WGS84 后算大圆距离。别用toMeter。
三、接受的参数形态
toLngLat/fromLngLat 都支持 点对象 / 数组 / 完整 GeoJSON:
map.toLngLat([100, 200]); // [x, y]
map.toLngLat({ x: 100, y: 200 }); // Point2D
map.toLngLat(vjmap.geoPoint([100, 200])); // GeoPoint
map.toLngLat({ type: "FeatureCollection", ... }); // 整棵 GeoJSON,递归转换
这也是 vjmap.transform.convert(data, fn) 的用法:遍历所有坐标对执行回调。
⚠️ 原地修改陷阱
map.toLngLat / map.fromLngLat 只对 type==='FeatureCollection' 做 cloneDeep;单个 Feature / 裸 Geometry 会被原地改写坐标并返回同一个对象:
// ✅ 安全:FC 走 cloneDeep,原对象不变
const fc2 = map.toLngLat(fc); // fc 仍是 CAD 坐标,fc2 是渲染经纬度
// ❌ 危险:单 Feature / 裸 Geometry 被原地改坏
const feat = { type:'Feature', properties:{}, geometry:{ type:'Point', coordinates:[cx,cy] } };
const out = map.toLngLat(feat); // out === feat,feat.geometry.coordinates 已变成渲染经纬度
map.toLngLat(feat); // 再调一次 = 二次转换,坐标彻底跑偏
// 需要保留原始 CAD 数据时自己 clone,或统一包成 FeatureCollection 再转
const out2 = map.toLngLat(JSON.parse(JSON.stringify(feat)));
vjmap.transform.convert(geojson, …) 同样原地修改;对点/点数组输入才返回新值。
cloneDeep是map.*层的包装(源码Map.prototype.toLngLat对 FC 先cloneDeep再transform.convert);底层prj.toLngLat(GeoProjection)对 FeatureCollection 也原地改写。直接用prj转 GeoJSON 前先自己克隆。
GeoBounds 判定方法
const b = vjmap.GeoBounds.fromArray([0, 0, 100, 80]); // 或 res.bounds(openMap 返回)
b.contains(new vjmap.GeoPoint(50, 40)); // ✅ true —— 收 GeoPoint 或 {x,y} 对象
b.contains([50, 40]); // ❌ 恒 false!数组不被识别(d.ts 只声明 GeoPoint,{x,y} 兼容)
b.intersect(other); // 返回相交 GeoBounds 或 null —— 不是 boolean
b.intersects; // ❌ 不存在(无 s)
b.width(); b.height(); // 100 / 80
b.center(); // GeoPoint {x:50,y:40}
b.scale(2); // ⚠️ 以中心为基准缩放,返回新 GeoBounds(b 不变):得 -50,-40,150,120
b.min / b.max / b.toArray(); // {x,y} / [0,0,100,80]
// 经纬度域判定点用 map.getBounds().contains(ll)(LngLatBounds,收 LngLatLike 数组)
四、常用场景代码
屏幕像素 → CAD 坐标
map.on("click", (e) => {
const lngLat = e.lngLat; // 点击处的渲染经纬度
const cadPt = map.fromLngLat(lngLat); // 转 CAD
console.log("CAD 坐标:", cadPt);
});
CAD 距离 → 屏幕像素
function cadLengthToPixels(cadLength) {
const c = map.getCenter();
const p1 = map.project(c);
const cadCenter = map.fromLngLat(c);
const cadOther = vjmap.geoPoint([cadCenter.x + cadLength, cadCenter.y]);
const p2 = map.project(map.toLngLat(cadOther));
return Math.hypot(p2.x - p1.x, p2.y - p1.y);
}
整片 GeoJSON 批量转换
const cadJson = map.fromLngLat(drawGeoJson); // 所有坐标:渲染经纬度 → CAD
const lngLatJson = map.toLngLat(cadJson); // 反向
五、vjmap.geoPoint 工具
规范化任意入参为 GeoPoint:
vjmap.geoPoint([100, 200]); // 数组
vjmap.geoPoint({ x: 100, y: 200 }); // 对象
vjmap.geoPoint({ lng: 116, lat: 39 });
常与 prj/map API 组合调用。
六、动态范围
如果业务会扩大 CAD 范围(绘图、加实体),res.bounds 可能不够大。要让 projection 跟上:
// 比如 mapext 绘完一条直线后
map.updateMapExtent("[0, 0, 20000, 20000]"); // 传字符串
map.fitMapBounds();
updateMapExtent 内部会重建 GeoProjection,之后再调用 toLngLat 就会使用新范围。
七、点击/查询的坐标方向
map.on('click', e => {
const ll = e.lngLat; // 渲染经纬度(CAD 底图下无地理意义)
const cad = map.fromLngLat(ll); // CAD 坐标——查库/量算用它
const px = e.point; // 屏幕像素
});
pointQueryFeature({x, y, ...}) 的 x/y 是 CAD 坐标——把经纬度值当 CAD 传入点查询会得到 recordCount:0。
enableLayerClickHighlight 完整签名(源码核实,内部已做 fromLngLat → pointQueryFeature,全程无需手动转):
map.enableLayerClickHighlight(
svc,
cb?, // (res, event) => void,res.result[i].geom 已是渲染经纬度
HighlightColor?,// "#0000" 表示不绘制高亮
prefix?, // 高亮图层名前缀,缺省 "highlight"
filterCb?, // (curResult, zoom?, x?, y?) => any,x/y 是 CAD 坐标
enterQueryCb? // (lngLat) => IPointQueryFeatures,改查询参数的钩子(自定义坐标/pixelsize)
);