快速导航
从零开始学GeoServer源码一(开篇)
从零开始学GeoServer源码二(搭建开发环境)
从零开始学GeoServer源码三(断点应该打在哪?)
从零开始学GeoServer源码四(自定义插件或拓展数据源)
从零开始学GeoServer源码五(切片原理及自定义插件支持wms、wmts、tms)
从零开始学GeoServer源码六(如何打包发布?)
从零开始学GeoServer源码七(如何注册服务并发布3dtiles和cesium的地形terrain?)
从零开始学GeoServer源码八(内存溢出?Out of Memory Error ?)
从零开始学GeoServer源码九(如何集成Cesium以实现预览3dtiles和terrain服务?)
从零开始学GeoServer源码十(如何修改菜单项以整合我们的功能?)
从零开始学GeoServer源码十一(如何解决No Multipart-config for Servlet错误)
从零开始学GeoServer源码十二(GeoServer中的切片规则)
从零开始学GeoServer源码十三(GeoServer生成的矢量切片缺失问题)
从零开始学GeoServer源码十四(GeoServer Cloud微服务版本初体验)
目录
1.前言
众所周知,GeoServer 中的切片并不是TMS 规则的切片,因此本期我们要聊一聊 GeoServer 中的切片规则,以及如何在 OpenLayers 中加载 GeoServer 的切片。以及如何在GeoServer 中设置切片的规则,和已经切好的切片如何转换为TMS 切片,当然我们也要聊一聊在这一过程中有哪些疑难杂症。
2.切片原理
切片原理可以参考作者之前的另一篇博文从零开始学GeoServer源码五(切片原理及自定义插件支持wms、wmts、tms),我在这里要说的是,不论是 wmts 还是 tms ,他们都是同一个切片原理,只是存储方式的不同和访问时的方式不同而已。切片原理其实不难理解,只是在前端拼接的时候很多人容易产生混乱。
3.一探究竟,揭开GeoServer中切片的神秘面纱
因为 GeoServer 会默认自动生成切片,也就是说当访问的切片不存在的时候,GeoServer 就会自动去生成这个切片。或者我们也可以手动去生成,即在左侧菜单栏点击 Tile Layers,然后找到想要的切片的图层,点击 Seed/Truncate 进入切片界面。当我们在 GeoServer 中生成了切片以后,默认会存储在 data_dir/gwc 文件夹中。
我们以 china_bgmap_world 图层为例,生成了切片,我们点进去看看切片长什么样。
怎么说呢?确实很魔性。和普通的 TMS 规则的切片命名完全不一样。乍一看,确实令人无从下手。但是当我们仔细阅读 GeoServer 的使用文档,就会发现官方文档里就有介绍。这里我把官方地址贴上来看看GeoServer2.19用户手册。详细的计算方式也贴出来GeoWebCache默认切片命名源码,读者可以自行查看。
我们在左侧菜单里也能找到类似的设置:
默认采用的是 GeoWebCache 的命名方式。回到第1级的文件夹里,对比官方文档,我们就知道了,第1级的文件夹中的两个文件夹是将全球分为了两个正方形,即左半球和右半球。他们的命名方式是 xc_yc ,其中 xc 和 yc 的计算方式为:
xc=x/(2^(z/2))
yc=y/(2^(z/2))
也就是对应这个文件夹里的命名方式
而 0_0 和 1_0 中的切片命名方式则是 x_y ,x为列,y为行,也就是说为 列_行。这两文件夹里的切片都可以拼接成一个正方形,我们来手动拼一下 0_0 来看看。
拼接前:
拼接后:
Perfect!完美!果然是个正方形,这是左半球,那显然1_0 就是右半球了。
4.坐标原点在哪?
在搞明白切片的命名规则之后,我们接下来的工作就是如何加载了。我们还是以OpenLayers 为例来讲解怎么加载。但是在此之前,我们还有一个问题要搞明白,坐标原点在哪?因为坐标原点关系着我们的加载方式。
还是参考官方文档,我们发现,默认的切片方式坐标原点在左下角,y轴方向为从南到北。和 OSGEO TMS 的坐标是一致的。而最后一种切片方式叫 SLIPYY ,有的版本叫做 XYZ 或者 Google TMS,y轴方向为从北到南。
5.加载
5.1 WMTS
wmts 的加载比较简单,因为传递的参数里包括了很多信息,GeoServer 可以根据这些信息进行各种坐标系的转换,以及切片规则的转换。所以我们不再赘述,直接上 OpenLayers 代码。
var wmts = new ol.layer.Tile({
source: new ol.source.WMTS({
url: "http://localhost:8085/geoserver/gwc/service/wmts",
layer: 'china:bgmap_world',
matrixSet: 'EPSG:4326',
format: 'image/jpeg',
projection: 'EPSG:4326',
tileGrid: new ol.tilegrid.WMTS({
tileSize: [256,256],
origin:[-180,90],
resolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 6.866455078125E-4, 3.4332275390625E-4, 1.71661376953125E-4, 8.58306884765625E-5, 4.291534423828125E-5, 2.1457672119140625E-5, 1.0728836059570312E-5, 5.364418029785156E-6, 2.682209014892578E-6, 1.341104507446289E-6, 6.705522537231445E-7, 3.3527612686157227E-7],
matrixIds: ['EPSG:4326:0', 'EPSG:4326:1', 'EPSG:4326:2', 'EPSG:4326:3', 'EPSG:4326:4', 'EPSG:4326:5', 'EPSG:4326:6', 'EPSG:4326:7', 'EPSG:4326:8', 'EPSG:4326:9', 'EPSG:4326:10', 'EPSG:4326:11', 'EPSG:4326:12', 'EPSG:4326:13', 'EPSG:4326:14', 'EPSG:4326:15', 'EPSG:4326:16', 'EPSG:4326:17', 'EPSG:4326:18', 'EPSG:4326:19', 'EPSG:4326:20', 'EPSG:4326:21']
}),
style: '',
wrapX: false
})
});
5.2 TMS
//google tms
var googletms=new ol.layer.Tile({
source: new ol.source.XYZ({
url: "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/{z}/{x}/{y}.jpeg",
}),
});
//osgeo tms
var osgeotms=new ol.layer.Tile({
source: new ol.source.XYZ({
url: "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/{z}/{x}/{-y}.jpeg",
}),
});
5.3 特殊情况下的TMS
我们知道 OpenLayers 中的 ol.source.XYZ 类可以在 tileUrlFunction 中自定义 url 的路径,这种就比较适合一些特殊情况下的 tms 。什么叫做特殊情况?我们来看个例子。还是以 china:bgmap_world 图层为例。当我们在 GeoServer 中完成切片以后,进行加载。
var osgeotms=new ol.layer.Tile({
source: new ol.source.XYZ({
url: "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/{z}/{x}/{-y}.jpeg",
}),
});
发现了什么?明显加载不对,只加载了一部分,于是调整加载方式:
var tms=new ol.layer.Tile({
source: new ol.source.XYZ({
url: "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/{z}/{x}/{y}.jpeg",
format: "image/jpeg",
projection: projection,
tileGrid: new ol.tilegrid.WMTS({
tileSize: [256,256],
origin: [-180.0, 90.0],
resolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 6.866455078125E-4, 3.4332275390625E-4, 1.71661376953125E-4, 8.58306884765625E-5, 4.291534423828125E-5, 2.1457672119140625E-5, 1.0728836059570312E-5, 5.364418029785156E-6, 2.682209014892578E-6, 1.341104507446289E-6, 6.705522537231445E-7, 3.3527612686157227E-7],
matrixIds: ['EPSG:4326:0', 'EPSG:4326:1', 'EPSG:4326:2', 'EPSG:4326:3', 'EPSG:4326:4', 'EPSG:4326:5', 'EPSG:4326:6', 'EPSG:4326:7', 'EPSG:4326:8', 'EPSG:4326:9', 'EPSG:4326:10', 'EPSG:4326:11', 'EPSG:4326:12', 'EPSG:4326:13', 'EPSG:4326:14', 'EPSG:4326:15', 'EPSG:4326:16', 'EPSG:4326:17', 'EPSG:4326:18', 'EPSG:4326:19', 'EPSG:4326:20', 'EPSG:4326:21']
}),
wrapX: false
}),
});
这次发现了什么?列号正常,行号反了。为什么会这样?这就是我们在从零开始学GeoServer源码五(切片原理及自定义插件支持wms、wmts、tms)中讲过的,osgeo tms 和 google tms 行号是反的。现在我们切是用 osgeo tms 规则切的,加载使用 google tms 加载的,当然是反的,所以再调整一下代码:
var tms=new ol.layer.Tile({
source: new ol.source.XYZ({
extent:[-180,-90,180,90],
wrapX: false,
format: "image/jpeg",
projection: projection,
tileGrid: new ol.tilegrid.WMTS({
extent:[-180,-90,180,90],
tileSize: [256,256],
origin: [-180.0, 90.0],
resolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 6.866455078125E-4, 3.4332275390625E-4, 1.71661376953125E-4, 8.58306884765625E-5, 4.291534423828125E-5, 2.1457672119140625E-5, 1.0728836059570312E-5, 5.364418029785156E-6, 2.682209014892578E-6, 1.341104507446289E-6, 6.705522537231445E-7, 3.3527612686157227E-7],
matrixIds: ['EPSG:4326:0', 'EPSG:4326:1', 'EPSG:4326:2', 'EPSG:4326:3', 'EPSG:4326:4', 'EPSG:4326:5', 'EPSG:4326:6', 'EPSG:4326:7', 'EPSG:4326:8', 'EPSG:4326:9', 'EPSG:4326:10', 'EPSG:4326:11', 'EPSG:4326:12', 'EPSG:4326:13', 'EPSG:4326:14', 'EPSG:4326:15', 'EPSG:4326:16', 'EPSG:4326:17', 'EPSG:4326:18', 'EPSG:4326:19', 'EPSG:4326:20', 'EPSG:4326:21']
}),
tileUrlFunction: function (tileCoord) {
console.log(tileCoord)
var z = tileCoord[0];
var x = tileCoord[1];
var y = tileCoord[2];
y = Math.pow(2, z) - tileCoord[2] -1;
// console.log("old",tileCoord,"new",[z,x,y]);
return "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/" + z + "/" + x + "/" + y + ".jpeg";
},
}),
});
注意重点是这句代码:
y = Math.pow(2, z) - tileCoord[2] -1;
相信这种写法很多人都知道,这是 osgeo tms 和 google tms 行号转换的方法,但是很多人没有注意到的是,如果行号都是正数,的确用这种算法,但是不知什么原因,OpenLayers 中识别到的行号是个负值,所以就不能这么计算了。我们来看下控制台打印出来的行列号:
看到了吧,行号是负数,第0级是1行2列,分别对应的是 0/0/0 和0/1/0,显然控制台打印的行值是错误的,那么怎么改呢?第0级是总行数1行,也就是20=1,再加上这个错误的行值,-1+20=0,这不就拿到正确的行值了吗?所以,继续修改代码:
var tms=new ol.layer.Tile({
source: new ol.source.XYZ({
extent:[-180,-90,180,90],
wrapX: false,
format: "image/jpeg",
projection: projection,
tileGrid: new ol.tilegrid.WMTS({
extent:[-180,-90,180,90],
tileSize: [256,256],
origin: [-180.0, 90.0],
resolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 6.866455078125E-4, 3.4332275390625E-4, 1.71661376953125E-4, 8.58306884765625E-5, 4.291534423828125E-5, 2.1457672119140625E-5, 1.0728836059570312E-5, 5.364418029785156E-6, 2.682209014892578E-6, 1.341104507446289E-6, 6.705522537231445E-7, 3.3527612686157227E-7],
matrixIds: ['EPSG:4326:0', 'EPSG:4326:1', 'EPSG:4326:2', 'EPSG:4326:3', 'EPSG:4326:4', 'EPSG:4326:5', 'EPSG:4326:6', 'EPSG:4326:7', 'EPSG:4326:8', 'EPSG:4326:9', 'EPSG:4326:10', 'EPSG:4326:11', 'EPSG:4326:12', 'EPSG:4326:13', 'EPSG:4326:14', 'EPSG:4326:15', 'EPSG:4326:16', 'EPSG:4326:17', 'EPSG:4326:18', 'EPSG:4326:19', 'EPSG:4326:20', 'EPSG:4326:21']
}),
tileUrlFunction: function (tileCoord) {
console.log(tileCoord)
var z = tileCoord[0];
var x = tileCoord[1];
var y = tileCoord[2];
y = Math.pow(2, z) + tileCoord[2];
// console.log("old",tileCoord,"new",[z,x,y]);
return "http://localhost:8085/geoserver/gwc/service/tms/1.0.0/china:bgmap_world@EPSG:4326@jpeg/" + z + "/" + x + "/" + y + ".jpeg";
},
}),
});
注意重点是这句代码:
y = Math.pow(2, z) + tileCoord[2] ;
现在就正常了。
6.GeoServer默认切片怎么转为TMS规则切片
这个其实很简单了,我们在上面已经对 GeoServer 中的切片规则进行了分析,那么只需要一个简单的小程序,就能转换,我给出一个 demo,以 java 代码为例:
@RequestMapping(value ="/totms",method = RequestMethod.GET)
@ResponseBody
public void GeoServerToTMS() throws IOException {
String path="F:\\geoserver-2.19.3-bin)\\geoserver-2.19.3-bin\\data dir\\gwc\\china bgmap_world";
ReadFileRecuritily(path);
}
private void ReadFileRecuritily(String path) throws IOException {
File source = new File(path);
File[] files = source.listFiles();
String[] split = path.split("\\\\");
String levelName = split[split.length - 2];
String[] levelNameSplit = levelName.split("_");
Integer level = 0;
if (levelName.startsWith("EPSG")) {
level = Integer.parseInt(levelNameSplit[levelNameSplit.length - 1]);
}
for (int i = 0; i < files.length; i++) {
//如果是文件夹,递归
if (files[i].isDirectory()) {
ReadFileRecuritily(files[i].getPath());
} else {
//如果是文件,读取
if (files[i].isFile()) {
String name = files[i].getName().replace(".jpeg", "");
String[] s = name.split("_");
Integer col = Integer.parseInt(s[0]);
Integer row = Integer.parseInt(s[1]);
//接贝到另一个地方
File tar = new File("D:\\test\\" + level + "\\" + col + "\\" + row + ".jpeg");
FileUtils.copyFile(files[i], tar);
}
}
}
}
7.总结
本文我们通过深入的分析 osgeo tms 和 google tms 的规则,以及 GeoServer 中的切片规则设置与切片原理,以及 OpenLayers 中出现的诡异行值进行了深入的分析,最后根据切片原理,计算出我们需要的行值。本文是一个发现问题,思考问题,解决问题的过程,在理解上是有一定难度的,希望读者仔细揣摩。
更多精彩内容见公众号AIGIS