Skip to content

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-02amap / amap-img):数据出入边界自动 WGS84↔GCJ02 互转,无偏移叠加。
  • 天地图tianditu-vec / tianditu-img):数据为 CGCS2000≈WGS-84,本无偏移,仅需 tk 密钥。
  • 🧪 百度 BD-09registerBaiduProtocol):瓦片网格与 MapLibre 不兼容,改用「瓦片纠偏」——addProtocol 拦截后画布 warp 重采样到 WGS-84。warp 纯前端,但瓦片字节须经同源代理(百度无 CORS + 防盗链)。见下文。

详见下文「国内坐标系与无偏移」。

安装

shell
pnpm add @geoverse/core-libre maplibre-gl

maplibre-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)会自动重装,无需手动重新添加。

内置栅格底图

代号基准面说明
osmWGS84OpenStreetMap 栅格(仅本地调试)
carto-lightWGS84CARTO Light(默认推荐,支持 CORS)
carto-darkWGS84CARTO Dark
carto-voyagerWGS84CARTO Voyager
amapGCJ-02高德矢量路网(自动无偏移)
amap-imgGCJ-02高德卫星影像
tianditu-vecWGS84天地图矢量(需 tk 密钥)
tianditu-imgWGS84天地图影像(需 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-libertyOpenFreeMap Liberty(OSM 矢量,多彩)
ofm-positronOpenFreeMap Positron(浅色简约)
ofm-brightOpenFreeMap 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 坐标调用,重写会令交互或叠加物二次偏移,故刻意保持原生):

  1. 你直接注册的原生事件回调(库无法代你包裹任意 map.on 处理器);
  2. 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 必须指向同源透传,按成本从低到高:

  1. 开发期 dev 代理(本仓示例 examples/vite.config.ts/baidu-tile → 百度 + 补 Referer,纯前端跑通)。
  2. 生产:极薄边缘函数(Cloudflare Worker / Nginx 反代,约 15 行:补 Referer + 同源),最省事。
  3. 生产最佳:服务端预 warp 成标准 XYZ / PMTiles(离线高质量 warp + 缓存,客户端零 warp、最清晰)。
  4. 若非必须百度:改用高德(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 10MapLibre GL JS 5(WebGL)
地图类GMap extends ol/MapGMap extends maplibregl.Map
弹窗InfoWindow extends ol/OverlayInfoWindow extends maplibregl.Popup
要素ol/Feature 子类自建 GFeature 基类(持 GeoJSON)
图层ol/layer/* 子类自建 GLayer 基类(GeoJSON 源 + 样式图层)
样式ol/style 对象声明式 style 表达式
坐标系GCJ-02 / BD-09 / 3857 / 3395WGS-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 操作发生在浏览器侧。