概述
可通过多种方式实现矢量切片的制作,前面讲到了基于postgis数据库、tippecanoe、Qgis等方式,本文讲述基于spring Boot框架下java的实现。
实现效果
实现代码
后端代码
- 引入依赖
<dependency>
<artifactId>giscat-vector-mvt</artifactId>
<groupId>org.wowtools</groupId>
<version>g1.6.1</version>
</dependency>
- MvtController
package com.lzugis.controller;
import com.lzugis.utils.FileUtil;
import io.swagger.annotations.Api;
import org.locationtech.jts.geom.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wowtools.giscat.vector.mvt.MvtBuilder;
import org.wowtools.giscat.vector.mvt.MvtLayer;
import org.wowtools.giscat.vector.pojo.Feature;
import org.wowtools.giscat.vector.pojo.FeatureCollection;
import org.wowtools.giscat.vector.pojo.converter.GeoJsonFeatureConverter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "矢量切片")
@RestController
@RequestMapping("tile")
public class MvtController {
private static final FeatureCollection areaFeatureCollection;//面数据
private static final FeatureCollection lineFeatureCollection;//线数据
private static final FeatureCollection pointFeatureCollection;//点数据
private static final GeometryFactory geometryFactory = new GeometryFactory();
private static final FileUtil fileUtil = new FileUtil();
static {
//构造示例数据
GeometryFactory gf = new GeometryFactory();
String strJson = fileUtil.getFileContent("data\\province.geojson");
areaFeatureCollection = GeoJsonFeatureConverter.fromGeoJsonFeatureCollection(strJson, gf);
ArrayList<Feature> pointFeatures = new ArrayList<>(areaFeatureCollection.getFeatures().size());
ArrayList<Feature> lineFeatures = new ArrayList<>(areaFeatureCollection.getFeatures().size());
for (Feature feature : areaFeatureCollection.getFeatures()) {
Feature pointFeature = new Feature();
ArrayList center = (ArrayList) feature.getProperties().get("center");
if (center != null) {
Point point = gf.createPoint(new Coordinate((Double) center.get(0), (Double) center.get(1)));
Map map = new HashMap();
map.put("name", feature.getProperties().get("name"));
pointFeature.setProperties(map);
pointFeature.setGeometry(point);
pointFeatures.add(pointFeature);
}
Feature lineFeature = new Feature();
if (feature.getGeometry() instanceof MultiPolygon) {
MultiPolygon multiPolygon = (MultiPolygon) feature.getGeometry();
LineString[] lines = new LineString[multiPolygon.getNumGeometries()];
for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
Polygon polygon = (Polygon) multiPolygon.getGeometryN(i);
lines[i] = gf.createLineString(polygon.getExteriorRing().getCoordinates());
}
MultiLineString ml = gf.createMultiLineString(lines);
lineFeature.setGeometry(ml);
} else {
LineString line = gf.createLineString(feature.getGeometry().getCoordinates());
lineFeature.setGeometry(line);
}
lineFeatures.add(lineFeature);
}
lineFeatureCollection = new FeatureCollection();
lineFeatureCollection.setFeatures(lineFeatures);
pointFeatureCollection = new FeatureCollection();
pointFeatureCollection.setFeatures(pointFeatures);
}
@RequestMapping("/{z}/{x}/{y}")
public void getTile(@PathVariable byte z, @PathVariable int x, @PathVariable int y, HttpServletResponse response) {
//构造一个MvtBuilder对象
MvtBuilder mvtBuilder = new MvtBuilder(z, x, y, geometryFactory);
//向mvt中添加layer
MvtLayer layer = mvtBuilder.getOrCreateLayer("province-polygon");
//向layer中添加feature
for (Feature feature : areaFeatureCollection.getFeatures()) {
//这里简单地从内存中取数据并判断其是否与瓦片有交集,实际运用中可从数据库查询,例如postgis的ST_intersects函数
if (mvtBuilder.getBbox().envIntersects(feature.getGeometry())) {
layer.addFeature(feature);
}
}
//如法炮制添加layer
layer = mvtBuilder.getOrCreateLayer("province-line");
for (Feature feature : lineFeatureCollection.getFeatures()) {
if (mvtBuilder.getBbox().envIntersects(feature.getGeometry())) {
layer.addFeature(feature);
}
}
//如法炮制添加layer
layer = mvtBuilder.getOrCreateLayer("province-point");
for (Feature feature : pointFeatureCollection.getFeatures()) {
if (mvtBuilder.getBbox().envIntersects(feature.getGeometry())) {
layer.addFeature(feature);
}
}
//数据添加完毕,转为
byte[] bytes = mvtBuilder.toBytes();
String vtContentType = "application/octet-stream";
exportByte(bytes, vtContentType, response);
}
//将bytes写进HttpServletResponse
private void exportByte(byte[] bytes, String contentType, HttpServletResponse response) {
response.setContentType(contentType);
try (OutputStream os = response.getOutputStream()) {
os.write(bytes);
os.flush();
} catch (org.apache.catalina.connector.ClientAbortException e) {
//地图移动时客户端主动取消, 产生异常"你的主机中的软件中止了一个已建立的连接",无需处理
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
前端代码
<div id="map"></div>
<script>
var mapStyle = {
"version": 8,
"name": "Dark",
"sources": {
'province-data': {
'type': 'vector',
'tiles': ['http://localhost:18888/lzugis/tile/{z}/{x}/{y}']
}
},
"layers": [
{
'id': 'province-line',
'type': 'line',
'source': 'province-data',
'source-layer': 'province-line',
'paint': {
'line-color': '#31aa00',
'line-width': 2
}
},
{
'id': 'province-point',
'type': 'circle',
'source': 'province-data',
'source-layer': 'province-point',
'paint': {
'circle-color': '#f00',
'circle-radius': 5
}
}
]
};
window.map = new mapboxgl.Map({
container: 'map',
maxZoom: 10,
minZoom: 3,
zoom: 3,
center: [109.1699, 45.9719],
style: mapStyle,
attributionControl: false
});
map.on('click', function (e) {
const coords = e.lngLat;
const r = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5],
];
const features = map.queryRenderedFeatures(r, {});
console.log(features);
})
</script>