经纬志
首页 / Leaflet / 教程

GeoJSON 数据加载

先看结论

GeoJSON 是 Web 地图最通用的矢量交换格式。Leaflet 用一个 L.geoJSON 就能把点、线、面转成图层,样式和交互通过三个回调控制。

最后验证 2026-11-03 · Leaflet 1.9.4 · 更新 2026-10-27 · 引用本文

结构回顾

一个合法的 GeoJSON 对象要么是单个 Feature,要么是 FeatureCollection。几何部分支持 Point、LineString、Polygon、 MultiPoint、MultiLineString、MultiPolygon 和 GeometryCollection。

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": { "name": "中关村", "level": 2 },
      "geometry": {
        "type": "Point",
        "coordinates": [116.316, 39.983]
      }
    }
  ]
}

坐标顺序与 Leaflet 相反

GeoJSON 规范是 [经度, 纬度],Leaflet 的 LatLng 是 [纬度, 经度]。直接手写数据时把顺序写反,点会跑到几千公里外, 而且不会报错。L.geoJSON 在内部会自动翻转, 所以从标准文件读进来的数据不需要手动处理;只有自己拼 L.marker 时才需要留心。

三个关键回调

L.geoJSON(data, options) 的 options 里有三个回调决定了最终呈现:

选项作用返回值
pointToLayer把点要素换成自定义图层一个 Layer,例如 L.circleMarker
style为线、面设置路径样式Path 的 options 对象
onEachFeature逐个要素绑定弹窗、事件、提示无
filter返回 false 的要素直接跳过布尔值
coordsToLatLng自定义坐标转换,用于非 WGS84 数据LatLng
L.geoJSON(data, {
  style: function (feature) {
    return {
      color: feature.properties.color || '#1f5fd0',
      weight: 2,
      fillOpacity: 0.22
    };
  },
  pointToLayer: function (feature, latlng) {
    return L.circleMarker(latlng, {
      radius: 6,
      color: '#ffffff',
      weight: 2,
      fillColor: '#1f5fd0',
      fillOpacity: 1
    });
  },
  onEachFeature: function (feature, layer) {
    layer.bindPopup(
      '<b>' + feature.properties.name + '</b><br>' +
      '等级 ' + feature.properties.level
    );
  },
  filter: function (feature) {
    return feature.properties.level >= 2;
  }
}).addTo(map);

点要素默认会被画成 Marker。默认 Marker 依赖一张 PNG 图标,数量一多既慢又容易缺图。 用 pointToLayer 返回 L.circleMarker,渲染更轻,也不需要任何图片资源。

获取远程数据

Leaflet 1.9 不再内置 AJAX 辅助方法,用浏览器原生的 fetch 即可。 重点是把 HTTP 错误和 JSON 解析错误分开处理,否则用户只会看到一张空地图:

fetch('/data/poi.geojson')
  .then(function (res) {
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return res.json();
  })
  .then(function (data) {
    var layer = L.geoJSON(data, { pointToLayer: dot }).addTo(map);
    map.fitBounds(layer.getBounds(), { padding: [24, 24] });
  })
  .catch(function (err) {
    console.error('数据加载失败', err);
  });

getBounds() 在没有要素时返回无效的范围,直接 fitBounds 会让地图跳到奇怪的位置。加一个 layer.getLayers().length 判断更稳妥。

样式随属性变化

专题图通常按属性分级设色。把分级逻辑写在 style 回调里,返回不同的 fillColor;数值变化后调用 layer.setStyle(fn) 重新计算, 不需要删除重建整个图层。

function ramp(v) {
  return v > 80 ? '#7f1d1d' : v > 50 ? '#c2410c' : v > 20 ? '#a16207' : '#17694a';
}

layer.setStyle(function (feature) {
  return { fillColor: ramp(feature.properties.value), fillOpacity: 0.75 };
});

数据量大时

  • 地图初始化时加 preferCanvas: true,矢量改用 canvas 渲染,几千个面也不会卡。
  • 给 L.geoJSON 传 interactive: false,省掉每个要素的事件绑定。
  • 先用 filter 剔除无关要素,比全部渲染后再隐藏便宜得多。
  • 超过几万个要素时,Leaflet 的单线程渲染会成为瓶颈,应换用矢量瓦片或 WebGL 方案。

常见问题

面画出来了但空洞没显示

带洞的多边形要用嵌套数组:外环在前,内环依次在后。 如果只传一层坐标数组,Leaflet 会把它当成单个外环。

数据是 GCJ-02,叠在 OSM 上整体偏移

这不是 GeoJSON 的问题。coordsToLatLng 可以在解析阶段统一转换坐标, 但更推荐在数据生产环节转成 WGS84,避免每个前端各写一套转换。

属性里的数字变成了字符串

GeoJSON 不约束 properties 的值类型。分级设色前用 Number(feature.properties.value) 显式转换,否则字符串比较会得到意外结果。

引用本文

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