GIS 服务端构建
GIS 服务端构建
开源地图服务
其中经常用到的大致有三个分别是:WMS 服务、WFS 服务、WMTS 服务。
首先对于栅格数据有两种服务需要大家掌握,即 WMS 服务和 WMTS 服务
WMS 服务全称为 Web Map Service 即网络地图服务。核心就是提供一副网络地图。如何提供?实际上就是给你一张图片,这张图片的内容是一副地图,我们之前说过栅格数据最终渲染到浏览器上都被转换成了图片,那 wms 就是将地图提前处理成图片格式然后发布出来以供浏览器调用。所以 wms 的核心就是一句话:为你提供一张图片格式的地图
WMTS 服务,全称:Web Map Tile Service ,即网络地图切片服务。WMTS 就是把地图分成许多等份,每一份通常是 256x256 像素大小的图片。用户在浏览器端查看地图的时候,根据用户当前所处的缩放层级,来加载对应的该层级之下的切片。
首先以一整张图片的左上角为切片原点,然后按照 256 像素*256 像素对图片进行切割,切割完之后再对图片进行编号,编号的规则是这样的:首先要确定当前的层级(也就是当前的比例尺)我们简称为 z,然后在按照行和列的概念对每一副小地图进行编号
行号我们记作 x,列号记作 y。因此根据三个数字 xyz 就能够获取唯一一张图片,这也是在 wmts 的 url 里出现 zxy 的原因。另外根据这个原理,其实我们完全可以在服务器上自定义一个新的规则来获取瓦片那就是按照文件夹来划分 z 和 y,然后每个文件夹里面按照x的编号来放置图片。这就是民间较为流行但并没有成为官方标准的xyz 方式,实时上这也是所谓的 TMS 的规范和标准
区别:
- 一个标准的 WMS 的过程是客户端根据浏览器当前的缩放层级和分辨率计算出需要请求的地图范围,这个范围即请求参数中的 width 和 height,然后发送请求到服务端,服务端根据请求参数里的宽和高从地图数据中裁剪出应有的范围,然后将这个范围内的部分以图片 png/jpeg 的格式返回到前端,也就是每一次鼠标的拖拽以及地图的缩放,只请求回来一张图片
- 而 WMTS 则是根据当前的分辨率(缩放层级)自动计算出当前层级下的瓦片行列号,讲这些瓦片全都返回,注意是全部,尽管你在屏幕上只看到了几十张瓦片,但是它确确实实是按照层级一次行全部返回的。也就是说 WMTS 随着地图的缩放请求一次要返回很多张图片。但是仅拖拽不缩放是不会发起请求的
WMS 是支持要素查询的。也就是说虽然WMS返回给你的是一张图片,但是它是支持你通过不同的参数传递来查询图片对应的要素属性信息。所以如此说来 WMS 服务的查询也是有局限性的,只能够查询矢量数据发布的WMS服务,对于栅格数据(tiff 等)就不具备查询能力了,不过一般也不会有人对栅格数据做查询
对于矢量数据类型也有两种地图服务分别是 WFS 服务和矢量切片服务
WFS 全称WebFeature Service,即网络要素服务。一个点,一条线,一个面都可以被称作是一个要素,那么 WFS 服务其实就是返回一些要素数据。再进一步讲,WFS 服务通常返回的就是GeoJSON 数据。
矢量切片(Vector Tile)诞生的原因其实和 WMTS 诞生的原因一样,都是大量的数据会造成浏览器性能的卡顿,矢量切片其实就是对矢量数据进行切片,切片的原理和栅格切片类似,也是按照其数据范围进行切片,只不过不需要像栅格那样去裁切图像,而是将数据组织进行变换,按照层级和范围去划分数据,另外矢量切片的结果通常是需要进行数据压缩的。mapbox 提供了一种矢量切片的标准叫做 MVT。谷歌提出了一种矢量切片数据的压缩格式.pbf。
最后一种服务既可以访问栅格数据也可以访问矢量数据,即 TMS 服务。全称Tile MapService,瓦片地图服务。对于栅格数据来讲,和 WMTS 类似,但是切片的起始原点和编号都与 WMTS 不同。对于矢量数据来讲,TMS 也是遵循了大众的习惯直接以xyz 的方式去访问矢量切片的结果 pbf。因此 TMS 比较全面。
GeoServer
geoserver 首先需要下载安装,官网:https://geoserver.org/ 。
使用 geoserver 之前请先确保你的电脑有 java 环境
找到官网首页点击 stable 下面的版本号然后跳转之后按照下面的方式下载
- Mac选Web Archive,需要有Tomcat
- Windows选Windows Installer
geoserver 的默认登录名为 admin,密码为:geoserver。登录以后就可以开始正常的操作了。
数据发布
geoserver 支持发布三种类型的数据,geotiff(就是我们的 tiff 影像),静态矢量数据,以及链接数据库进行数据的发布
我们登录以后找到左侧的“存储仓库”(也可能叫数据存储之类的,总之是和存储相关)然后选择“添加新的存储仓库”,在弹出的页面中你可以看到我上面说的三种类型的数据发布方式
最常用的:PostGIS、Shapefile、GeoTIFF
我们把我们准备好的杭州市的 shape 数据进行发布,大家手头如果有 shape 数据就用自己的数据,没有数据的直接 arcgis 里或者 QGIS 里画一个。我们选择上面图中的shapefile 选项,然后通过本地路径找到我们的 shape 数据。
切片
请求一个 wmts 服务必须按照 OGC 的规范提供必须的几个参数,分别是 url、layerName、format、style、tileMatrixSet、matrixId 等。
最重要的是tileMatrixSet(切片矩阵的名字,通常以坐标系命名,也可以自定义) 和 matrixId
- tileMatrixSet(切片矩阵的名字,通常以坐标系命名,也可以自定义)
- matrixId --- 记录切片切了多少个层级
查看方式:
Tile Layers 找到要用的图层名称
Seed/Truncate是用来切片、重新切片的
点击图层名称可以看到一些信息
- Tile Caching中可以看到Gridset(切好的网格)
- 这时候去Tile Caching --- Gridsets(用于设置切片策略),看这个策略具体信息,主要关注Tile Matrix Set
- Tile width/height in pixels 长宽一般作为除数
- Level 有多少层级创建的数组就有多大
- Pixel Size是每个分辨率,这个是可计算出来的
- Scale比例尺(了解)
- Tiles --- 若
1x1,则两个乘数的比值也有用,用于计算分辨率
Select One下拉框可以进行预览拿到url
通过这个链接也能看到一些信息 http://127.0.0.1:8090/geoserver/gwc/service/wmts?version=1.1.1&service=wmts&request=GetCapabilities
关于发布,参考这个文章
import Map from "ol/Map.js";
import WMTS from "ol/source/WMTS.js";
import WMTSTileGrid from "ol/tilegrid/WMTS.js";
import { get as getProjection } from "ol/proj.js";
import { getTopLeft, getWidth } from "ol/extent.js";
import TileLayer from "ol/layer/Tile.js";
import View from "ol/View.js";
import { transform } from "ol/proj";
const projection = getProjection("EPSG:4326");
const projectionExtent = projection.getExtent();
// 除以像素尺寸
const size = getWidth(projectionExtent) / 256;
// 缩放层级
const resolutions = new Array(22);
const matrixIds = new Array(22);
for (let z = 0; z < 22; ++z) {
// Tiles --- 若1x1时, size / Math.pow(2, z),否则需要再除以两值比
resolutions[z] = size / Math.pow(2, z) / 2;
matrixIds[z] = "EPSG:4326:" + z;
}
export default {
mounted() {
new Map({
layers: [
new TileLayer({
source: new WMTS({
// 通过 Select One下拉框可以进行预览拿到url
url: "http://127.0.0.1:8090/geoserver/gwc/service/wmts",
layer: "grandmap:china-map-all",
matrixSet: "EPSG:4326",
format: "image/png",
projection: projection,
tileGrid: new WMTSTileGrid({
origin: getTopLeft(projectionExtent),
resolutions: resolutions,
matrixIds: matrixIds,
}),
style: "",
wrapX: true,
}),
}),
],
target: "map",
view: new View({
// 转换坐标
center: transform(
[117.773681640625, 23.47802734375],
"EPSG:4326",
"EPSG:3857"
),
zoom: 8,
minZoom: 2,
maxZoom: 22,
}),
});
},
};
var OLMapMgr = function() {
function OLMapMgr(mapDivId, centerCoordinates, initialZoom) {
if (!(this instanceof OLMapMgr)) {
throw new TypeError("Cannot call a class as a function");
}
if (!OLMapMgr.instance) {
this.version = {
project: "olmap-master",
name: "openlayers extend library by gis team",
date: "20210513",
revision: "svn-version-na",
version: "0.1.6",
type: "beta",
publisher: "frankxu"
};
this.clickHandlers = new Set();
this.exclusiveClickHandlers = new Set();
this.moveEndHandlers = new Set();
this.zoom = initialZoom;
this.mousePositionType = OLMapMgr.mousePositionType.standard;
this.exclusiveClickState = false;
this.exclusiveClickType = undefined;
this.getPointByClickProxy = undefined;
this.map = new ol.Map({
controls: ol.control.defaults({
attribution: false,
rotate: false,
zoom: true
}),
target: mapDivId,
layers: [],
view: new ol.View({
center: ol.proj.transform([centerCoordinates.lon, centerCoordinates.lat], "EPSG:4326", "EPSG:3857"),
zoom: this.zoom,
minZoom: 2,
maxZoom: 22
})
});
this.viewTypeLayers = [];
OLMapMgr.instance = this;
}
}
OLMapMgr.prototype.init = function() {
var self = this;
this.map.addControl(new ol.control.MousePosition({
undefinedHTML: "outside",
projection: "EPSG:4326",
coordinateFormat: function(coordinates) {
coordinates[0] = coordinates[0] % 360;
if (coordinates[0] > 180) {
coordinates[0] -= 360;
}
if (coordinates[0] < -180) {
coordinates[0] += 360;
}
return self.formatDegree(coordinates[0]) + " " + self.formatDegree(coordinates[1]);
}
}));
this.scaleLineControl = new ol.control.ScaleLine({
units: "metric"
});
this.map.addControl(this.scaleLineControl);
this.tipBaseUtilMgr = new OLMapMgr(this);
this.tipBaseUtilMgr.init();
};
OLMapMgr.prototype.startMapEventListen = function() {
var self = this;
this.map.on("click", function(event) {
if (self.exclusiveClickState) {
console.log("map.click main listener closed!");
} else {
self.clickHandle(event);
}
});
this.map.on("moveend", function(event) {
self.moveEndHandle(event);
});
};
OLMapMgr.prototype.clickHandle1 = function(event) {
if (this.getPointByClickProxy) {
var projection = this.map.getView().getProjection();
var coordinates = ol.proj.transform(event.coordinate, projection, "EPSG:4326");
this.getPointByClickProxy.onMapClick({
lon: coordinates[0],
lat: coordinates[1]
});
}
var handlers = Array.from(this.clickHandlers);
var found = false;
var feature = undefined;
var handler = undefined;
var pixel = this.map.getEventPixel(event.originalEvent);
this.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if (!found) {
handlers.forEach(function(handler) {
if (!found && handler.layers.indexOf(layer) !== -1) {
found = handler.onFeatureClick(feature, layer, event);
feature = feature;
handler = handler;
}
});
}
return feature;
});
Array.from(this.exclusiveClickHandlers).forEach(function(handler) {
handler.executeExclusiveClick(handler, feature);
});
return feature;
};
OLMapMgr.prototype.clickHandle = function(event) {
if (this.getPointByClickProxy) {
var projection = this.map.getView().getProjection();
var coordinates = ol.proj.transform(event.coordinate, projection, "EPSG:4326");
this.getPointByClickProxy.onMapClick({
lon: coordinates[0],
lat: coordinates[1]
});
}
var handlers = Array.from(this.clickHandlers);
var found = false;
var feature = undefined;
var handler = undefined;
var pixel = this.map.getEventPixel(event.originalEvent);
this.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
handlers.forEach(function(handler) {
if (!found && handler.layers.indexOf(layer) !== -1) {
found = handler.onFeatureClick(feature, layer, event);
feature = feature;
handler = handler;
}
});
return feature;
});
Array.from(this.exclusiveClickHandlers).forEach(function(handler) {
handler.executeExclusiveClick(handler, feature);
});
return feature;
};
OLMapMgr.prototype.moveEndHandle = function(event) {
var handlers = Array.from(this.moveEndHandlers);
var oldZoom = this.zoom;
var newZoom = this.map.getView().getZoom();
this.zoom = newZoom;
handlers.forEach(function(handler) {
handler.onMoveEnd(newZoom, oldZoom);
});
return event.frameState.focus;
};
OLMapMgr.prototype.setExclusiveClick = function(state, type) {
this.exclusiveClickState = state;
this.exclusiveClickType = type;
};
OLMapMgr.prototype.formatDegree = function(value) {
if (this.mousePositionType === 2) {
return (value < 0 ? "-" : "") + Math.abs(value).toFixed(8) + "°";
}
var absValue = Math.abs(value);
var degrees = Math.floor(absValue);
return (value < 0 ? "-" : "") + degrees + "°" + Math.floor(60 * (absValue - degrees)) + "'" + (3600 * (absValue - degrees) % 60).toFixed(2) + '"';
};
OLMapMgr.instance = null;
OLMapMgr.mousePositionType = {
standard: 1,
decimal: 2
};
return OLMapMgr;
}();
