什么是矢量瓦片服务
矢量瓦片技术是WebGIS中一项重要的地图数据组织与可视化技术,它通过预切割和分层技术,将矢量数据以瓦片的形式高效传输并在客户端渲染。
矢量瓦片是一种将矢量地理数据按照金字塔模型切割成小块并进行高效组织、传输和渲染的技术。与传统栅格瓦片不同,矢量瓦片包含的是几何和属性信息而非预渲染图像,这使得它们具有以下核心特点:
对比于普通的WMTS瓦片技术:
特性 | 矢量瓦片 | 栅格瓦片 |
---|---|---|
数据格式 | 几何+属性(MVT、GeoJSON等),显示为pbf格式 | 图片(PNG、JPEG等) |
客户端渲染 | 需要WebGL/Canvas支持 | 直接显示图片 |
样式灵活性 | 高,可动态修改 | 低,样式固定 |
交互能力 | 强,支持要素级查询,pop弹框 | 弱,仅能获取像素信息 |
数据量 | 相对较小 | 相对较大 |
制作成本 | 前期处理复杂 | 相对简单 |
技术方案:
切片工具方案
- GeoServer:通过GeoWebCache扩展支持矢量切片生成,支持多种数据源和输出格式
- Tegola:开源Go语言编写的矢量切片服务器,支持PostGIS数据源和MVT格式
- ArcGIS Enterprise:提供完整的矢量切片生产与发布能力
maputnik
拉取maputnik:
bath
docker pull maputnik/editor
docker exec -it geoserver bash
sudo cp *.jar /usr/local/geoserver/WEB-INF/lib/
geoserver发布的WCS服务以及ArcGIS api for js调用
html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>GeoScene Maps SDK for JavaScript Tutorials: Display a map</title>
<!-- @@Start(link) -->
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.29/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.29/"></script>
<script>
require(["esri/config", "esri/Map", "esri/views/MapView", "esri/layers/VectorTileLayer", "esri/layers/WebTileLayer", "esri/layers/WCSLayer"], function (geosceneConfig, Map, MapView, VectorTileLayer, WebTileLayer, WCSLayer) {
const map = new Map({
basemap: []
});
// 1. 创建天地图矢量底图图层 (球面墨卡托投影 vec_w)
var tdtBaseLayer = new WebTileLayer({
urlTemplate: "https://t{subDomain}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk=key",
subDomains: ["0", "1", "2", "3", "4", "5", "6", "7"], // 使用多个子域名提升加载效
title: "天地图矢量底图"
});
// 2. 创建天地图矢量注记图层 (球面墨卡托投影 cva_w)
var tdtAnnoLayer = new WebTileLayer({
urlTemplate: "https://t{subDomain}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk=key",
subDomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
title: "天地图矢量注记"
});
// 3. 将底图和注记添加到地图中
map.add(tdtBaseLayer);
map.add(tdtAnnoLayer);
const view = new MapView({
map: map,
center: [111.4, 37.9],
zoom: 4,
container: "viewDiv"
});
var tileLyr = new VectorTileLayer({
style: {
"version": 8,
"sources": {
"osm": {
// 修正后的瓦片URL模板
// 矢量切片服务的url类似于(可以参考getcapabilities):http://<ip地址>/geoserver/it.geosolutions/gwc/service/wmts/rest/<图层名称>/<样式(类似于xx:yy,可以省略)>/<EPSG的坐标系>/<EPSG的坐标系>:{z}/{y}/{x}?format=application/vnd.mapbox-vector-tile
"tiles": ["http://localhost:8000/geoserver/it.geosolutions/gwc/service/wmts/rest/nanchang_bridge/cite:testshiliang/EPSG:900913/EPSG:900913:{z}/{y}/{x}?format=application/vnd.mapbox-vector-tile"
],
"type": "vector"
}
},
"layers": [
{
"id": "nanchang_bridge",
"type": "line",
"source": "osm", // 确保此处指向上面定义的source名称
"source-layer": "nanchang_bridge", // 替换为GeoServer中实际的图层名
"paint": {
"line-color": "#269746", // 使用line-color而不是fill-color
"line-width": 7
}
}
]
},
title: 'mvt',
});
map.add(tileLyr);
console.log(tileLyr);
// 加载WCS并添加WCS的弹窗
const wcsLayer = new WCSLayer({
// 替换为您的 WCS 服务地址
url: "http://localhost:8000/geoserver/wcs",
// 可选:指定覆盖范围标识符,默认为第一个覆盖范围
coverageId: "nurc:Img_Sample",
// 版本一定是1.0.0
version: "1.0.0",
});
// 设置弹框
function getAdditionalInfo(pixelValue) {
if (!pixelValue || pixelValue === "无数据") return "";
try {
const rgb = pixelValue.split(',').map(val => parseInt(val.trim()));
if (rgb.length >= 3) {
return `
<div style="margin-top: 15px; padding: 10px; background: #e8f4fd; border-radius: 4px; border-left: 4px solid #1890ff;">
<strong style="color: #1890ff;">RGB解析:</strong>
<div style="margin-top: 5px;">
<span style="color: #ff4d4f;">R: ${rgb[0]}</span>,
<span style="color: #52c41a;">G: ${rgb[1]}</span>,
<span style="color: #1890ff;">B: ${rgb[2]}</span>
</div>
</div>
`;
}
} catch (e) {
console.warn("解析RGB值失败:", e);
}
return "";
}
// 将图层添加到地图
map.add(wcsLayer);
view.whenLayerView(wcsLayer).then(layerView => {
wcsLayer.popupTemplate = {
highlightEable: true,
title: wcsLayer.title,
content: (e) => {
// 从 graphic.attributes 中获取数据
const attributes = e.graphic.attributes;
const pixelValue = attributes["Raster.ServicePixelValue"] || "无数据";
const rawPixelValue = attributes["Raster.ServicePixelValue.Raw"] || "无数据";
const objectId = attributes.ObjectId || "未知";
// 解析RGB值
let rgbDisplay = "无法解析";
if (pixelValue && pixelValue !== "无数据") {
const rgbArray = pixelValue.split(',').map(val => val.trim());
if (rgbArray.length >= 3) {
rgbDisplay = `
<div style="display: flex; align-items: center; margin: 5px 0;">
<div style="width: 20px; height: 20px; background: rgb(${pixelValue}); border: 1px solid #ccc; margin-right: 10px;"></div>
<span>RGB(${pixelValue})</span>
</div>
`;
}
}
return `
<div style="min-width: 200px; font-family: Arial, sans-serif;">
<h4 style="margin-bottom: 15px; color: #333;">像素详细信息</h4>
<div style="margin-bottom: 10px;">
<strong style="color: #666;">对象ID:</strong>
<span style="float: right;">${objectId}</span>
</div>
<div style="margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong style="color: #666; display: block; margin-bottom: 8px;">颜色值:</strong>
${rgbDisplay}
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #666;">服务像素值:</strong>
<div style="font-family: monospace; background: #f8f8f8; padding: 5px; margin-top: 5px; border-radius: 3px;">
${pixelValue}
</div>
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #666;">原始像素值:</strong>
<div style="font-family: monospace; background: #f8f8f8; padding: 5px; margin-top: 5px; border-radius: 3px;">
${rawPixelValue}
</div>
</div>
${getAdditionalInfo(pixelValue)}
</div>
`;
}
};
})
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>