Appearance
MapLibre 版(@geoverse/core-libre)
@geoverse/core-libre 是 GeoVerse 工具包的 MapLibre GL JS 引擎版核心包,与基于 OpenLayers 的 geoverse 并列、相互独立。它镜像了 geoverse 的核心 API 心智 (GMap / Marker / Circle / Polygon / Polyline / GVectorLayer / InfoWindow 等), 让你以近乎一致的写法把项目迁移到 MapLibre 生态——同时享有矢量切片底图、WebGL 渲染、 声明式样式表达式等 MapLibre 原生能力。
该选哪个包?
- 需要开箱即用的百度 BD-09(不想自建瓦片代理)、或已有大量 OpenLayers 代码 → 用
geoverse。 - 需要矢量切片底图 / WebGL 渲染 / MapLibre 生态插件,数据以 WGS-84 为主,叠加标准底图或高德(GCJ-02)/天地图国内底图 → 用
@geoverse/core-libre。
完整对比与决策清单见引擎选型。
要素编辑
需要在 MapLibre 上做要素编辑(撤销重做 / 打断合并 / 变换 / 节点增删 / 拓扑 / 后端同步)?用 @geoverse/editor-libre(与 OpenLayers 轨共用同一编辑引擎)。
坐标系范围(当前版本)
MapLibre GL JS 原生只渲染 Web Mercator(EPSG:3857),无法像 OpenLayers 那样注册自定义投影或重投影瓦片。本包通过数据边界变换支持:
- ✅ 标准 WGS-84 底图(OSM / CARTO / OpenFreeMap)。
- ✅ 高德 GCJ-02(
amap/amap-img):数据出入边界自动 WGS84↔GCJ02 互转,无偏移叠加。 - ✅ 天地图(
tianditu-vec/tianditu-img):数据为 CGCS2000≈WGS-84,本无偏移,仅需tk密钥。 - 🧪 百度 BD-09(
registerBaiduProtocol):瓦片网格与 MapLibre 不兼容,改用「瓦片纠偏」——addProtocol拦截后画布 warp 重采样到 WGS-84。warp 纯前端,但瓦片字节须经同源代理(百度无 CORS + 防盗链)。见下文。
详见下文「国内坐标系与无偏移」。
安装
shell
pnpm add @geoverse/core-libre maplibre-glmaplibre-gl 是 peerDependency,由宿主项目安装。除本包样式外,还需引入 MapLibre 自身的样式:
ts
import { GMap, Marker, GVectorLayer } from '@geoverse/core-libre';
import 'maplibre-gl/dist/maplibre-gl.css'; // MapLibre 基础样式(容器、Popup、控件)
import '@geoverse/core-libre/style.css'; // 本包定制样式UMD 直引(全局名 GeoVerseLibre,已内置 maplibre-gl):
html
<link rel="stylesheet" href="core-libre.css" />
<script src="core-libre.umd.js"></script>
<script>
var map = new GeoVerseLibre.GMap({ container: 'map', base: 'carto-light' });
</script>快速开始
ts
import { GMap, GVectorLayer, Marker } from '@geoverse/core-libre';
import 'maplibre-gl/dist/maplibre-gl.css';
const map = new GMap({
container: 'map', // 容器元素或其 id(也可用 target 别名)
base: 'carto-light', // 内置底图(默认 'osm')
center: [118.18, 24.49], // WGS-84 经纬度 [lng, lat]
zoom: 12,
});
const layer = new GVectorLayer();
map.addLayer(layer);
layer.addFeature(new Marker({ position: [118.18, 24.49], color: '#e4572e' }));就绪门控(重要)
MapLibre 的样式是异步加载的。本包的图层会自动等待样式就绪后再安装,你无需手动等待 load 事件即可 map.addLayer(...)。若要在就绪后执行自定义逻辑:
ts
map.whenReady(() => {
/* 此时可安全 addSource / addLayer */
});底图
MapLibre 中"底图即一份 style"。base 选项支持内置代号、完整 style 对象、style URL 或 false(空白)。 切换底图用 map.setBase(...)(别名 switchBase):
ts
map.setBase('carto-dark'); // 切换内置栅格底图
map.setBase('ofm-liberty'); // 切换内置矢量切片底图
map.setBase('https://example.com/style.json'); // 任意 style URL
map.setBase(false); // 空白底图(仅叠加自有数据)切底图后,本包管理的图层(GVectorLayer / GVectorTileLayer)会自动重装,无需手动重新添加。
内置栅格底图
| 代号 | 基准面 | 说明 |
|---|---|---|
osm | WGS84 | OpenStreetMap 栅格(仅本地调试) |
carto-light | WGS84 | CARTO Light(默认推荐,支持 CORS) |
carto-dark | WGS84 | CARTO Dark |
carto-voyager | WGS84 | CARTO Voyager |
amap | GCJ-02 | 高德矢量路网(自动无偏移) |
amap-img | GCJ-02 | 高德卫星影像 |
tianditu-vec | WGS84 | 天地图矢量(需 tk 密钥) |
tianditu-img | WGS84 | 天地图影像(需 tk 密钥) |
可经第二参覆盖瓦片地址、版权、字体:
ts
import { createBaseLayer } from '@geoverse/core-libre';
const style = createBaseLayer('osm', {
tiles: ['https://your-tiles/{z}/{x}/{y}.png'],
attribution: '© Your Org',
glyphs: 'https://your-fonts/{fontstack}/{range}.pbf',
});
new GMap({ container: 'map', base: style });内置矢量切片底图
矢量底图是一份完整 style(含矢量源、图层、字形、精灵),以 provider 的 style URL 提供,无需 API key:
| 代号 | 说明 |
|---|---|
ofm-liberty | OpenFreeMap Liberty(OSM 矢量,多彩) |
ofm-positron | OpenFreeMap Positron(浅色简约) |
ofm-bright | OpenFreeMap Bright(明亮) |
ts
new GMap({ container: 'map', base: 'ofm-liberty' });矢量 vs 栅格
矢量切片在客户端渲染,可任意缩放清晰、样式可改、要素可交互、体积更小;栅格瓦片为预渲染图片, 适合卫星影像 / WMS 等。OpenFreeMap 为社区资助的免费服务,生产高频使用请自托管或捐助。
国内坐标系与无偏移
MapLibre 只有单一坐标空间(WGS-84 经 Web Mercator 渲染),不能注册自定义投影。本包以 数据边界变换消除高德等国内底图的偏移:把 MapLibre 坐标空间当作当前底图的原生基准面 (高德=GCJ-02),底图瓦片原样加载(清晰、无重采样),库在数据出入边界自动 WGS84 ↔ GCJ02 互转。使用者侧 API 始终是 WGS-84——你照常传经纬度即可:
ts
// 高德底图:直接传 WGS-84,库自动消除偏移
const map = new GMap({
container: 'map',
base: 'amap',
center: [116.39, 39.91],
zoom: 12,
});
const layer = new GVectorLayer();
layer.addFeature(new Marker({ position: [116.39, 39.91] })); // WGS-84,落点无偏移
map.addLayer(layer);
map.getCenter(); // 返回 WGS-84
map.setBase('carto-dark'); // 切回标准底图,视觉中心不跳变,要素自动重对齐天地图(CGCS2000≈WGS-84,本无偏移)只需 tk 密钥:
ts
new GMap({
container: 'map',
base: 'tianditu-vec',
baseConfig: { token: '你的天地图tk' },
});显式坐标换算与边界泄漏
透明变换覆盖要素数据与绝大多数地图方法:相机 setCenter/panTo/panBy/flyTo/ fitBounds/getCenter/getBounds、要素查询 queryRenderedFeatures(几何反算为 WGS-84)、 InfoWindow、要素 click 事件的 evt.lngLat——这些入出参均为 WGS-84,照常使用即可。
仍处于地图空间(datum)、需用 helper 显式换算的有两类(均因被 maplibre 内部以 datum 坐标调用,重写会令交互或叠加物二次偏移,故刻意保持原生):
- 你直接注册的原生事件回调(库无法代你包裹任意
map.on处理器); easeTo/jumpTo(被拖拽惯性、panBy调用)与project/unproject(被 Popup / Marker 等 DOM 叠加物以_lngLat调用来定位)——直接调用时请自行用fromWGS84/toWGS84换算(一般用上面的setCenter/flyTo即可,无需直接用终端/投影)。
ts
map.getDatum(); // 'WGS84' | 'GCJ02' | 'BD09'
map.toWGS84(ll); // datum → WGS-84
map.fromWGS84(ll); // WGS-84 → datum
// 直接 map.on('click') 的原生回调里,e.lngLat 处于 datum,需自行还原:
map.on('click', (e) => {
const [lng, lat] = map.toWGS84(e.lngLat);
});
// 直接用终端/投影时自行换算:
map.easeTo({ center: map.fromWGS84([116.39, 39.91]) });
const [lng, lat] = map.toWGS84(map.unproject(point));为什么是"纠数据"而非"纠瓦片/纠投影"
社区另有"纠瓦片/纠投影矩阵"方案(如 gisarmory 插件)——让底图 1:1、数据不动,但需深度 patch 引擎内部投影矩阵,伴随高层级抖动、旋转错位、屏幕边缘空白等问题,且 MapLibre v5 不再 导出 Transform、投影子系统私有化,对库不可维护。本包采用数据边界变换(纠数据), 精度最高、不耦合内部、跨版本稳定;代价仅是上面这一处原生回调需显式换算。
百度 BD-09(瓦片纠偏,🧪 实验性)
百度瓦片网格(origin[0,0]、2^(18-z)、y 向上)与 MapLibre 的标准 Web Mercator 网格不兼容, 不能用数据边界变换。改用「瓦片纠偏」:registerBaiduProtocol 注册一个 addProtocol 处理器,拦截标准瓦片请求 → 取覆盖该范围的百度瓦片 → 在 OffscreenCanvas 上仿射 warp 重采样 成 256×256(落进 WGS-84 空间,数据零变换、datum 仍 WGS84)。
ts
import maplibregl from 'maplibre-gl';
import { GMap, registerBaiduProtocol } from '@geoverse/core-libre';
// 注册一次;tileUrl 必须指向同源代理(见下方约束)
// useWorker:把 fetch+解码+warp 放进 Web Worker,CPU 不占主线程(不改变 CORS)
registerBaiduProtocol(maplibregl, {
tileUrl: (x, y, z) => `/baidu-tile/${z}/${x}/${y}`,
useWorker: true,
});
new GMap({
container: 'map',
base: {
version: 8,
sources: {
basemap: {
type: 'raster',
tiles: ['baidu://{z}/{x}/{y}'],
tileSize: 256,
},
},
layers: [{ id: 'basemap', type: 'raster', source: 'basemap' }],
},
});必须经同源代理(CORS + 防盗链)
百度 *.bdimg.com 瓦片无 Access-Control-Allow-Origin、且有防盗链 403——浏览器会让画布 「污染」而无法读回/上传纹理。这是硬安全边界,Worker + OffscreenCanvas 也绕不过(它只优化 性能)。MapLibre 是 WebGL 渲染、瓦片必须上传为纹理(需读回 CORS 干净像素),不像 OpenLayers/Leaflet 用 <img> 仅显示可绕开。所以 tileUrl 必须指向同源透传,按成本从低到高:
- 开发期 dev 代理(本仓示例
examples/vite.config.ts的/baidu-tile→ 百度 + 补 Referer,纯前端跑通)。 - 生产:极薄边缘函数(Cloudflare Worker / Nginx 反代,约 15 行:补
Referer+ 同源),最省事。 - 生产最佳:服务端预 warp 成标准 XYZ / PMTiles(离线高质量 warp + 缓存,客户端零 warp、最清晰)。
- 若非必须百度:改用高德(GCJ-02)/天地图——无需代理、无需 warp、原生清晰(见上文)。
库不内置代理以免引入外部依赖。
降低模糊(retina)
warp 重采样 + 把 256px 瓦片喂给高 DPI 屏会发虚。让代理请求百度 scaler=2(512px retina 瓦片), 本库 warp 会自适应按源分辨率输出(512×512),既不上采样、又给高 DPI 屏 retina 纹理,显著变清晰。 示例代理已用 scaler=2。代价是 ~4× 瓦片字节与画布像素(useWorker 可把开销移出主线程)。
要素
要素是 GeoJSON(WGS-84 经纬度),由 GVectorLayer 以声明式样式图层渲染。样式以构造参数传入:
ts
import {
Marker,
Circle,
Polygon,
Polyline,
GVectorLayer,
} from '@geoverse/core-libre';
const layer = new GVectorLayer();
map.addLayer(layer);
layer.addFeature(
new Marker({ position: [118.1, 24.5], color: '#e4572e', size: 8 }),
);
layer.addFeature(
new Circle({ center: [118.1, 24.5], radius: 800, fillColor: '#17bebb' }),
); // radius 单位:米
layer.addFeature(
new Polyline({
path: [
[118.0, 24.4],
[118.2, 24.6],
],
strokeColor: '#3a86ff',
strokeWeight: 4,
}),
);
layer.addFeature(
new Polygon({
path: [
[118.0, 24.4],
[118.2, 24.4],
[118.2, 24.6],
[118.0, 24.4],
],
fillColor: '#ffd166',
fillOpacity: 0.4,
}),
);Marker不传icon渲染为圆点;传icon(图片 URL)渲染为图标(自动按需加载图像)。Circle为米制地理圆(用 turf 生成多边形近似),改setRadius/setCenter会重建几何。- 要素提供
setStrokeColor/setStrokeWeight/setFillColor/setVisible/setPositions/setPath等便捷方法。
矢量切片图层(GVectorTileLayer)
叠加自有矢量瓦片(与底图相互独立)。指定 tiles[] 模板或 url(TileJSON 端点)及 source-layer:
ts
import { GVectorTileLayer } from '@geoverse/core-libre';
map.addLayer(
new GVectorTileLayer({
url: 'https://example.com/tiles.json', // 或 tiles: ['https://host/{z}/{x}/{y}.pbf']
sourceLayer: 'roads', // 矢量瓦片内的 source-layer 名
type: 'line', // fill / line / circle / symbol
paint: { 'line-color': '#ff3300', 'line-width': 2 },
minzoom: 6,
}),
);自定义 style
paint / layout 直接透传 MapLibre 样式规格, 你的图层定义需匹配矢量瓦片的 source-layer 与字段。优先用 url(TileJSON)以自动读取 zoom 范围等元信息。
要素交互事件
GMap 在地图层面统一监听指针事件,并向命中的要素派发 click / mouseover / mouseout:
ts
const marker = new Marker({ position: [118.2, 24.5] });
marker.on('click', (evt) => {
console.log('clicked at', evt.lngLat); // [lng, lat]
});
layer.addFeature(marker);构造要素时传 cancelBubble: true 可阻止事件冒泡到其下层要素。
信息弹窗(InfoWindow)
InfoWindow 继承自 maplibregl.Popup,内容支持 HTML 字符串 / 元素 id / DOM 元素,位置用 WGS-84 经纬度:
ts
import { InfoWindow } from '@geoverse/core-libre';
const info = new InfoWindow({ content: '<b>厦门</b>' });
info.open(map, [118.1, 24.5]);
// info.close();弹窗位置恒以 WGS-84 记录;挂到 GMap 后会按其当前基准面投影,并在 setBase 切换基准面(如高德 GCJ-02 ↔ 标准底图)时自动重新定位,无需手动更新。
与 geoverse(OpenLayers 版)的差异
| 方面 | geoverse(OL) | @geoverse/core-libre(MapLibre) |
|---|---|---|
| 渲染引擎 | OpenLayers 10 | MapLibre GL JS 5(WebGL) |
| 地图类 | GMap extends ol/Map | GMap extends maplibregl.Map |
| 弹窗 | InfoWindow extends ol/Overlay | InfoWindow extends maplibregl.Popup |
| 要素 | ol/Feature 子类 | 自建 GFeature 基类(持 GeoJSON) |
| 图层 | ol/layer/* 子类 | 自建 GLayer 基类(GeoJSON 源 + 样式图层) |
| 样式 | ol/style 对象 | 声明式 style 表达式 |
| 坐标系 | GCJ-02 / BD-09 / 3857 / 3395 | WGS-84 + 高德 GCJ-02/天地图(数据边界变换);百度 BD-09(瓦片纠偏,需同源代理) |
| 事件订阅 | feature.onPointer(...) | feature.on(...) |
销毁
ts
map.destroy(); // 释放 WebGL 上下文与 DOM(等价于 maplibre remove)SSR 安全
import '@geoverse/core-libre' 模块顶层零副作用,可在 Next/Nuxt 服务端安全引入; 只需保证 new GMap() 等 DOM 操作发生在浏览器侧。
