经纬志
首页 / MapLibre / 教程

GeoJSON 数据源

先看结论

业务数据大多是 GeoJSON。加一个 source、配一个图层, 数据就上了图;后续更新只改 source 的 data,不需要重建图层。

最后验证 2026-11-03 · MapLibre GL JS 4.x · 更新 2026-10-27 · 引用本文

数据源的两种写法

data 可以是 GeoJSON 对象,也可以是一个 URL。 对象适合由前端计算或接口返回后直接上图的场景,URL 适合静态文件。

/* 内联对象 */
map.addSource('stations', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: [
      { type: 'Feature', properties: { name: 'A' },
        geometry: { type: 'Point', coordinates: [116.39, 39.90] } }
    ]
  }
});

/* 远程文件 */
map.addSource('zones', {
  type: 'geojson',
  data: '/data/zones.geojson'
});

两种写法都要求坐标系是 WGS84 经纬度,坐标顺序 [经度, 纬度],可选第三位高程。 如果数据来自投影后的米制坐标,必须先转换,否则点会落在赤道附近。

按几何类型选图层

几何图层 type关键属性
Point / MultiPointcirclecircle-radius、circle-color
Pointsymbolicon-image、text-field
LineStringlineline-color、line-width、line-dasharray
Polygonfillfill-color、fill-opacity
Polygon 边界line与 fill 共用 source,单独描边
点密度heatmapheatmap-radius、heatmap-weight
map.addLayer({
  id: 'station-dots',
  type: 'circle',
  source: 'stations',
  paint: {
    'circle-radius': ['interpolate', ['linear'], ['zoom'], 3, 3, 7, 8],
    'circle-color': '#1f5fd0',
    'circle-stroke-color': '#fff',
    'circle-stroke-width': 1.5
  }
});

一个 source 可以被多个图层复用。常见的组合是面用 fill 填色、同一个 source 再用 line 描边, 分开之后填充透明度不会影响边界清晰度。

更新数据只改 setData

var src = map.getSource('stations');

src.setData(nextFeatureCollection);

图层不用删除重建,视野、交互状态都保持不变。 每次 setData 会全量替换数据并重新解析, 高频更新(比如每秒多次)时应先在外部合并变更,再一次性写入。

数据量大时先聚合

GeoJSON source 支持在客户端聚合点,把密集点合并成带计数的簇:

map.addSource('points', {
  type: 'geojson',
  data: points,
  cluster: true,
  clusterRadius: 45,
  clusterMaxZoom: 8
});

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': '#1f5fd0',
    'circle-radius': ['step', ['get', 'point_count'], 12, 20, 18, 100, 26]
  }
});

聚合后要素的 properties 里会多出 point_count 与 cluster_id。展示单个点时,需要另加一个 filter: ['!', ['has', 'point_count']] 的图层, 否则原始点与簇会同时出现。

聚合适合几万个点。到几十万时,浏览器端解析和三角化会成为瓶颈, 应该改用矢量瓦片,把聚合与简化放到服务端。

feature-state 与选中态

高亮单个要素时不必改数据。给 source 指定 promoteId(或 generateId), 再用 setFeatureState 标记状态,图层用 feature-state 读取。

map.addSource('parcels', {
  type: 'geojson',
  data: parcels,
  promoteId: 'code'
});

map.setFeatureState({ source: 'parcels', id: 'BJ-01' }, { hover: true });

map.addLayer({
  id: 'parcels-fill',
  type: 'fill',
  source: 'parcels',
  paint: {
    'fill-color': ['case', ['boolean', ['feature-state', 'hover'], false],
                   '#c2410c', '#1f5fd0']
  }
});

没有 promoteId 时,要素 id 来自数据的 id 字段; 两者都没有就取不到状态,高亮会静默失效。

查询点击到了哪个要素

map.on('click', 'parcels-fill', function (e) {
  var f = e.features[0];
  console.log(f.properties.code, f.properties.name);
});

/* 鼠标移入时改指针,是交互细节里最容易被忽略的一条 */
map.on('mouseenter', 'parcels-fill', function () {
  map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'parcels-fill', function () {
  map.getCanvas().style.cursor = '';
});

事件绑在图层上时,图层必须已存在。写在 load 回调之前会绑不上,且没有报错。样式切换后图层被清掉,事件仍然留着, 但再也触发不了,重建图层时需要重新绑定。

常见错误

data 传成 JSON 字符串

data: JSON.stringify(fc) 不会报错,但 source 解析为空, 地图上没有要素。要么传对象,要么传 URL 字符串。

只改对象不调用 setData

直接修改原 FeatureCollection 的 features 数组,地图不会自动刷新。 MapLibre 不监听对象内部变化,必须显式调用 setData。

属性值类型混乱

同一个字段一会儿是数字、一会儿是字符串,表达式里做数值比较时 部分要素会被过滤掉。入库前统一类型,比在表达式里做兼容更省事。

引用本文

经纬志(2026)。《GeoJSON 数据源》。https://www.jingweizhi.com/maplibre/tutorial/geojson-source.html(访问日期 2026-11-03)。