☰
地图服务加载指南
2026/10/17 7:55:38 网站建设 项目流程

目录

一、引言

二、核心概念:四类服务到底有什么区别

2.1 一句话总结

2.2 详细解释

三、环境准备

3.1 引入 OpenLayers

3.2 一个通用的地图容器

3.3 本文用到的服务

四、加载 WMS 服务

​编辑五、加载 WMTS 服务

​编辑六、加载 XYZ 瓦片

​编辑七、加载 WFS 矢量数据

八、踩坑与调试专题

8.1 跨域问题(CORS)

8.2 坐标系不一致导致偏移

8.3 图层叠加顺序

8.4 图层透明度

8.5 如何排查服务请求

九、总结与进阶方向

9.1 一句话回顾

9.2 进阶方向

附录:完整示例模板


面向 GIS 入门开发者,用 OpenLayers + 原生 HTML 从零开始,一次性讲清四种最常用的地图服务如何在前端加载。

一、引言

很多人在做 WebGIS 时,第一反应是"我能不能直接贴一张地图图片上去"?答案是可以,但只适合静态展示。

真实项目中,我们通常需要的是动态、可交互、数据实时更新的地图。这时候就要用"地图服务"(Map Service)来提供数据。

前端通过 HTTP 请求这些服务,然后在地图上渲染。OpenLayers 是目前支持最完整、最流行的开源 WebGIS 前端库之一,下面我们就用它来逐个击破。

二、核心概念:四类服务到底有什么区别

开始写代码前,先弄懂这四个概念。它们的本质区别决定了我们怎么调用它们。

2.1 一句话总结

服务

全称

返回内容

数据类型

典型用途

WMS

Web Map Service

渲染好的图片

栅格

动态地图,按需渲染

WMTS

Web Map Tile Service

预切好的瓦片图片

栅格

缓存瓦片底图,性能好

XYZ

XYZ Tiles

瓦片图片

栅格

第三方瓦片服务,规则简单

WFS

Web Feature Service

要素数据(GeoJSON/GML)

矢量

拿数据,做交互查询

2.2 详细解释

WMS(Web Map Service)

  • 每次请求时,服务器实时渲染出一张地图图片返回给你。
  • 特点:灵活,可动态指定图层、样式、范围;但每次都要服务器重新渲染,性能较差。
  • 常用:数据量不大、需要动态叠加图层时。

WMTS(Web Map Tile Service)

  • 服务器提前把地图切成很多张小瓦片,按层级缓存好,前端按需请求。
  • 特点:性能好、响应快,适合做大范围底图;但灵活性不如 WMS。
  • 常用:作为底图、影像。

XYZ(XYZ Tiles)

  • 一种简化版的瓦片规则,用 {z}/{x}/{y} 表示层级和行列号。
  • 特点:规则简单,几乎所有第三方瓦片服务(如 OSM、高德)都兼容。
  • 常用:加载第三方底图瓦片。

WFS(Web Feature Service)

  • 前面几个返回的都是"图片",而 WFS 返回的是真正的数据(点、线、面及属性)。
  • 特点:拿到数据后前端可以自由渲染、样式化、做点击查询、编辑。
  • 常用:矢量数据交互,如行政区划、道路、POI 点。

记忆口诀:WMS 现画现卖,WMTS 提前切好,XYZ 是简单瓦片,WFS 给数据。

三、环境准备

我们采用CDN 引入 + 原生 HTML的方式,不需要安装任何构建工具,复制保存成 .html 文件直接浏览器打开即可。

3.1 引入 OpenLayers

<!-- 样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css"> <!-- 核心库 --> <script src="https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"></script> 💡 jsdelivr 在部分网络环境下访问不稳定,若加载失败可替换为国内镜像,如 https://registry.npmmirror.com/ol/8.2.0/files/dist/ol.js。

3.2 一个通用的地图容器

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>地图服务加载指南</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css"> <style> #map { width: 100%; height: 500px; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"></script> <script> // 各示例代码写在这里 </script> </body> </html>

3.3 本文用到的服务

为了贴合国内实际项目,本文示例统一使用自建 GeoServer 提供 WMS / WFS 业务图层,地图视图使用EPSG:4326(经纬度投影),中心定位到经纬度 [117.0, 36.65]:

  • 自建 GeoServer(WMS / WFS 业务图层):http://localhost:8080/geoserver/...,需自己发布图层,下文用 jinan:districts(行政区图层)占位。

⚠️ 注意:GeoServer 地址和图层名都要换成你自己的。本文示例重在讲清写法和参数含义,不保证复制即可运行。

四、加载 WMS 服务

WMS 的核心是 ol.source.ImageWMS(也可用底层的 ol.source.Image + ol.source.wms.createLoader),把它塞进 ol.layer.Image 图层即可。它会在视图范围内变化时,按需向服务器要一张渲染好的图片。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>加载 WMS 服务</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css"> <style> #map { width: 100%; height: 500px; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"></script> <script> const layer = new ol.layer.Image({ source: new ol.source.Image({ loader: ol.source.wms.createLoader({ url: 'https://demo.mapserver.org/cgi-bin/wms?', params: { LAYERS: ['bluemarble,country_bounds,cities'], VERSION: '1.3.0', FORMAT: 'image/png', }, projection: 'EPSG:4326', // note serverType only needs to be set when hidpi is true hidpi: true, serverType: 'mapserver', }), }), }); const map = new ol.Map({ layers: [layer], target: 'map', view: new ol.View({ projection: 'EPSG:4326', center: [0, 0], zoom: 2, }), }); </script> </body> </html>

几个容易忽略的点:

  • WMS 对应 `Image` 图层,不是 Tile——因为它返回的是单张整图,不是切片。
  • params.LAYERS 是服务端定义好的图层名,数组形式一次可请求多个图层,写错会直接 4xx。
  • projection 为图层投影,需与视图投影一致(本文统一用 EPSG:4326),否则定位会跑偏。
  • hidpi: true 时才需显式指定 serverType,让 OL 按对应服务器(如 mapserver、geoserver)的参数风格发请求。
  • `center` 的单位由视图投影决定:本文统一使用 EPSG:4326,单位是经纬度(度);若视图用 EPSG:3857 则单位是米,两者不能混搭。

下图是 WMS 服务加载后的实际效果示意:

五、加载 WMTS 服务

WMTS 比较特殊——它的瓦片网格、层级分辨率由服务端定义,OL 无法凭空猜出来。若已知全部参数,可以手动构造 tileGrid 直接加载,如下所示。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>加载 WMTS 服务</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css"> <style> #map { width: 100%; height: 500px; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"></script> <script> const projection = ol.proj.get('EPSG:3857'); const projectionExtent = projection.getExtent(); const size = ol.extent.getWidth(projectionExtent) / 256; const resolutions = new Array(19); const matrixIds = new Array(19); for (let z = 0; z < 19; ++z) { // generate resolutions and matrixIds arrays for this WMTS resolutions[z] = size / Math.pow(2, z); matrixIds[z] = z; } const map = new ol.Map({ layers: [ new ol.layer.Tile({ source: new ol.source.OSM(), }), new ol.layer.Tile({ opacity: 0.7, source: new ol.source.WMTS({ attributions: 'Tiles © <a href="https://mrdata.usgs.gov/geology/state/"' + ' target="_blank">USGS</a>', url: 'https://mrdata.usgs.gov/mapcache/wmts', layer: 'sgmc2', matrixSet: 'GoogleMapsCompatible', format: 'image/png', projection: projection, tileGrid: new ol.tilegrid.WMTS({ origin: ol.extent.getTopLeft(projectionExtent), resolutions: resolutions, matrixIds: matrixIds, }), style: 'default', wrapX: true, }), }), ], target: 'map', view: new ol.View({ projection: 'EPSG:3857', center: [-11158582, 4813697], zoom: 4, }), }); </script> </body> </html>

为什么 WMTS 要手动配 `tileGrid`?因为 WMS/XYZ 的网格规则(缩放、切分)是固定约定,OL 能自己算;而 WMTS 的 matrixSet、分辨率、边界都在服务端定义,必须手动告诉 OL 才能算得准。这是 WMTS 与其它三个最本质的差别。

下图是 WMTS 服务加载后的实际效果示意:

六、加载 XYZ 瓦片

XYZ 是纯约定驱动的:只要给一个 URL 模板,OL 会自动把 {z}/{x}/{y} 替换成当前缩放级别和瓦片行列号,无需任何额外请求。

const gaodeLayer = new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', maxZoom: 18 }) }); const map = new ol.Map({ target: 'map', layers: [gaodeLayer], view: new ol.View({ center: [117.0, 36.65], // 地图中心(经纬度,EPSG:4326 视图下无需 fromLonLat) zoom: 10 }) });

要点:

  • {z}/{x}/{y} 是 OL 内部约定的占位符,会自动替换,不用自己拼 URL。
  • maxZoom 声明服务支持的最大层级,防止去请求不存在的瓦片。
  • 绝大多数第三方瓦片是EPSG:3857,若视图用 EPSG:4326,瓦片会因投影不匹配而偏移或拉伸,此时可选用支持 EPSG:4326 的瓦片服务,或将视图切回 EPSG:3857。
  • 注意遵守所使用瓦片服务的条款;域名里的编号段(如高德的 webrd0{1-4})可用于负载分担。

下图是 XYZ 瓦片加载后的实际效果示意:

七、加载 WFS 矢量数据

WFS 直接挂一个 ol.source.Vector,把请求地址配好,OL 自己负责发起请求、解析、渲染——完全不需要手写 fetch。

// 1. 建一个矢量图层,配好样式 const vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ format: new ol.format.GeoJSON(), // 默认策略只加载一次全量数据,加上 bbox 策略才会随视图范围变化重新请求 strategy: ol.loadingstrategy.bbox, url: function (extent, resolution, projection) { // 2. 返回 WFS GetFeature 的请求地址(随当前视图范围变化) return '/geoserver/gcsp-geoserver/ows' + '?service=WFS' + '&version=1.1.0' + '&request=GetFeature' + '&typeName=gcsp-geoserver:ceshibuilding' + '&outputFormat=application/json' + '&srsname=EPSG:4326' + '&bbox=' + extent.join(',') + ',EPSG:4326'; } }), style: new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ff0000', width: 2 }), fill: new ol.style.Fill({ color: 'rgba(255,0,0,0.2)' }) }) }); const map = new ol.Map({ target: 'map', layers: [ // 高德矢量底图(EPSG:3857 瓦片) new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}' }) }), vectorLayer ], // 视图使用 EPSG:4326,center 直接写经纬度 view: new ol.View({ center: [117.0, 36.65], zoom: 10 }) // 地图中心(经纬度) });

说明:

  • ol.source.Vector 默认策略是一次性加载全量数据(只调用一次 url 函数),想要"范围联动"必须显式配置 `strategy: ol.loadingstrategy.bbox`,OL 才会在每次视图变化时用新范围重新请求。
  • format: new ol.format.GeoJSON() 告诉 OL 用什么格式解析返回的数据。如果服务端返回默认的 XML(GML),就换成 new ol.format.WFS()——它支持读取 GML2/GML3,也能写入(如构造 GetFeature 请求),但解析 GML 时需根据数据版本在 format 里指定 gmlFormat。
  • outputFormat=application/json 是让服务返回 GeoJSON,最省事。
  • 矢量图层天然支持点击选中、样式化、编辑等交互,比栅格图层灵活得多。

下图是 WFS 矢量数据加载后的实际效果示意:

八、踩坑与调试专题

8.1 跨域问题(CORS)

  • WMS/WFS 跨域请求,服务端必须开启 CORS 头(Access-Control-Allow-Origin)。
  • 自建 GeoServer 时,在 web.xml 或代理层配置允许跨域。
  • 排查方法:浏览器 F12 → Network,看请求是否报 CORS 错误。

8.2 坐标系不一致导致偏移

  • 这是最常见的问题!数据是 EPSG:4326,地图却是 EPSG:3857,位置就会偏。
  • 解决:让 source 的 projection 与视图一致;本文示例统一使用 EPSG:4326 视图,extent 和 center 都直接写经纬度,减少了投影转换的心智负担。如果必须用 EPSG:3857 视图(如加载 3857 瓦片底图),矢量数据可在 format 里声明数据投影和目标投影,OL 会自动做转换:

format: new ol.format.GeoJSON({
dataProjection: 'EPSG:4326', // 服务端返回数据的投影
featureProjection: 'EPSG:3857' // 地图视图的投影
})

  • 经验:网络底图瓦片基本都是 3857,若视图用 4326 需选用对应投影的瓦片;矢量数据用 4326 最省事,与 GPS 经纬度一致。

8.3 图层叠加顺序

  • OpenLayers 中 layers 数组靠前的最下面,靠后的最上面。
  • 想叠加在上面,就放在数组后面,或用 map.addLayer() 追加。

8.4 图层透明度

// 让叠加层半透明,方便看下面的底图
layer.setOpacity(0.7);

8.5 如何排查服务请求

  1. 按 F12 打开开发者工具 → Network 面板。
  2. 刷新页面,观察发送的地图请求。
  3. 看请求 URL 参数是否正确、返回状态码是否为 200。
  4. 若 4xx/5xx,点开请求看错误信息,通常能定位是参数、坐标还是权限问题。

九、总结与进阶方向

9.1 一句话回顾

  • WMS:要动态渲染图片 → 用 ImageWMS。
  • WMTS:要高性能瓦片 → 用 WMTS + Capabilities 解析。
  • XYZ:要第三方简单瓦片 → 用 XYZ + URL 模板。
  • WFS:要矢量数据做交互 → 用 Vector 图层 + format: GeoJSON 直配。

9.2 进阶方向

  • 矢量瓦片(Vector Tiles):用 VectorTile 图层,性能更高。
  • 栅格重投影:跨坐标系加载影像。
  • 交互操作:点击选中、弹窗、要素编辑。
  • 性能优化:图层分级加载、离屏渲染、瓦片缓存策略。

附录:完整示例模板

把下面代码保存为 map.html,先将里面的GeoServer 地址 / 图层名换成你自己的(并确保 GeoServer 已发布对应图层、开启了跨域)。准备就绪后浏览器打开,即可看到加载了WFS 矢量数据(行政区图层)并叠加在高德底图上的地图。这段代码充分体现了 OL 的核心用法:给图层配好 source,其余交给 OL 自己完成。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>地图服务加载指南</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css"> <style> html, body { height: 100%; margin: 0; } #map { width: 100%; height: 100%; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"></script> <script> // 矢量图层:直接配 source,WFS 请求由 OL 自动发起 const vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ format: new ol.format.GeoJSON(), // 关键:bbox 策略,视图范围变化时自动重新请求对应区域的数据 strategy: ol.loadingstrategy.bbox, url: function (extent, resolution, projection) { return '/geoserver/gcsp-geoserver/ows' + '?service=WFS' + '&version=1.1.0' + '&request=GetFeature' + '&typeName=gcsp-geoserver:ceshibuilding' + '&outputFormat=application/json' + '&srsname=EPSG:4326' + '&bbox=' + extent.join(',') + ',EPSG:4326'; } }), style: new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ff0000', width: 2 }), fill: new ol.style.Fill({ color: 'rgba(255,0,0,0.2)' }) }) }); const map = new ol.Map({ target: 'map', layers: [ // 高德矢量底图(EPSG:3857 瓦片) new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}' }) }), vectorLayer ], view: new ol.View({ // 视图使用 EPSG:4326,center 直接写经纬度 center: [117.0, 36.65], // 地图中心(经纬度) zoom: 10 }) }); </script> </body> </html>

想换成 WMS / WMTS / XYZ,只需把layers数组里的内容换成前面几章对应的图层实例即可,地图框架本身不用动。

写在最后:四类服务各有适用场景,前端选择的核心是——要图片还是要数据,要性能还是要灵活。掌握了 WMS / WMTS / WFS / XYZ 这四种,WebGIS 前端的数据加载你就算入门了。欢迎收藏,遇到问题可以在评论区交流!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询