使用java生成mvt切片的方法

如何使用java生成geoserver的矢量切片供前端(mapbox等)调用

  • 目录
    • 新的想法
    • Java能为切片做什么
    • 引入依赖
    • 如何转换xyz
    • 数据如何查询
    • 如何输出mvt格式给前端
    • 前端如何调用

目录

好久没发博客了,每日或忙碌、或悠闲、或喜或悲、时怅时朗,或许如苏轼说的“人生如逆旅,我亦是行人”。俱已矣,虽有感于斯,然生活仍要继续。今将最近研究的地理图层服务器的一种生成矢量切片的方法示予诸位,望诸位勉之!

新的想法

在做地理信息项目的时候,总免不了与各种图层打交道,尤其是大数据量的json图层,动辄几十兆的数据需要在前后端之间传输,有些项目又不可能将随时可能变动的json数据存在前端,最终还是需要后端接口返回。因此,最普遍的方法就是借助第三方的空间服务器将数据发布成切片形式由前端调用。但是第三方服务器往往需要采购,开源的第三方服务器往往不被支持(国产化),所以我们希望将这部分功能直接由后端实现…

Java能为切片做什么

java生成vector-tile的方法网上有很多,大部分采用的是no.ecc.vectortile包,这是挪威国家地理信息中心发布的一个支持使用java生成矢量切片的jar。我们也打算采用这种成熟可靠的开源组件(毕竟国产化并没有限制使用第三方jar)。
no.ecc.vectortile

引入依赖

我们先引入需要的依赖,目前能用的vectortile包只有1.2.5可用,其他版本不兼容创建出来的Geometry对象。

<!-- geo包含的依赖 --><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.22.2</version></dependency><dependency><groupId>com.google.protobuf.nano</groupId><artifactId>protobuf-javanano</artifactId><version>3.1.0</version></dependency><dependency><groupId>no.ecc.vectortile</groupId><artifactId>java-vector-tile</artifactId><version>1.2.5</version></dependency><dependency><groupId>com.vividsolutions</groupId><artifactId>jts</artifactId><version>1.13</version></dependency><dependency><groupId>org.osgeo</groupId><artifactId>proj4j</artifactId><version>0.1.0</version></dependency>

如何转换xyz

通过转换函数将xyz转换为bbox,也就是查询范围

	public static String xyz2prj4326BBox(int z, int x, int y) {String bbox = "";double n = Math.pow(2, z);double lon_min = (x / n) * 360.0 - 180.0;double lat_min = 90.0 - (((y + 1) / n) * 360.0);double lon_max = ((x + 1) / n) * 360.0 - 180.0;double lat_max = 90.0 - ((y / n) * 360.0);bbox = lon_min + ","+lat_min+","+lon_max+","+lat_max;return bbox;}public static String parseXyz2Bound(int x,int y,int z){StringBuilder sb = new StringBuilder("POLYGON ((");double lngLeft = MercatorProjection.tileXToLongitude(x, (byte)z) - 0.00105;double latUp = MercatorProjection.tileYToLatitude(y, (byte)z) + 0.00105;double lngRight = MercatorProjection.tileXToLongitude(x + 1, (byte)z) + 0.00105;double latDown = MercatorProjection.tileYToLatitude(y + 1, (byte)z) - 0.00105;sb.append(lngLeft +" "+latUp+", ");sb.append(lngRight +" "+latUp+", ");sb.append(lngRight +" "+latDown+", ");sb.append(lngLeft +" "+latDown+", ");sb.append(lngLeft +" "+latUp+")) ");return sb.toString();}public static void convert2Piexl(int x, int y, int z, Geometry geom){double px = MercatorProjection.tileXToPixelX(x);double py = MercatorProjection.tileYToPixelY(y);Coordinate[] cs = geom.getCoordinates();byte zoom = (byte)z;for(Coordinate c : cs){c.x = (int)(((MercatorProjection.longitudeToPixelX(c.x, zoom)) - px) * 16);c.y = (int)(((MercatorProjection.latitudeToPixelY(c.y, zoom)) - py) * 16);//			c.z = 218;}}

这里我们直接踩前人的肩膀

package com.address.geo.utils;/** Copyright 2010, 2011, 2012 mapsforge.org** This program is free software: you can redistribute it and/or modify it under the* terms of the GNU Lesser General Public License as published by the Free Software* Foundation, either version 3 of the License, or (at your option) any later version.** This program is distributed in the hope that it will be useful, but WITHOUT ANY* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public License along with* this program. If not, see <http://www.gnu.org/licenses/>.*//*** 前人的肩膀* A static class that implements spherical mercator projection.*/
public final class MercatorProjection {private MercatorProjection() {}/*** Convert a longitude coordinate (in degrees) to a horizontal distance in* meters from the zero meridian.* * @param longitude*            in degrees* @return longitude in meters in spherical mercator projection*/public static double longitudeToMetersX(double longitude) {return WGS84.EQUATORIALRADIUS * Math.toRadians(longitude);}/*** Convert a meter measure to a longitude.* * @param x*            in meters* @return longitude in degrees in spherical mercator projection*/public static double metersXToLongitude(double x) {return Math.toDegrees(x / WGS84.EQUATORIALRADIUS);}/*** Convert a meter measure to a latitude.* * @param y*            in meters* @return latitude in degrees in spherical mercator projection*/public static double metersYToLatitude(double y) {return Math.toDegrees(Math.atan(Math.sinh(y / WGS84.EQUATORIALRADIUS)));}/*** Convert a latitude coordinate (in degrees) to a vertical distance in* meters from the equator.* * @param latitude*            in degrees* @return latitude in meters in spherical mercator projection*/public static double latitudeToMetersY(double latitude) {return WGS84.EQUATORIALRADIUS* Math.log(Math.tan(Math.PI / 4+ 0.5 * Math.toRadians(latitude)));}/*** Calculate the distance on the ground that is represented by a single* pixel on the map.* * @param latitude*            the latitude coordinate at which the resolution should be*            calculated.* @param zoom*            the zoom level at which the resolution should be calculated.* @return the ground resolution at the given latitude and zoom level.*/public static double calculateGroundResolution(double latitude, byte zoom) {return Math.cos(latitude * Math.PI / 180) * 40075016.686/ ((long) Tile.TILE_SIZE << zoom);}/*** Convert a latitude coordinate (in degrees) to a pixel Y coordinate at a* certain zoom level.* * @param latitude*            the latitude coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the pixel Y coordinate of the latitude value.*/public static double latitudeToPixelY(double latitude, byte zoom) {double sinLatitude = Math.sin(latitude * Math.PI / 180);return (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude))/ (4 * Math.PI))* ((long) Tile.TILE_SIZE << zoom);}/*** Convert a latitude coordinate (in degrees) to a tile Y number at a* certain zoom level.* * @param latitude*            the latitude coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the tile Y number of the latitude value.*/public static long latitudeToTileY(double latitude, byte zoom) {return pixelYToTileY(latitudeToPixelY(latitude, zoom), zoom);}/*** Convert a longitude coordinate (in degrees) to a pixel X coordinate at a* certain zoom level.* * @param longitude*            the longitude coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the pixel X coordinate of the longitude value.*/public static double longitudeToPixelX(double longitude, byte zoom) {return (longitude + 180) / 360 * ((long) Tile.TILE_SIZE << zoom);}/*** Convert a longitude coordinate (in degrees) to the tile X number at a* certain zoom level.* * @param longitude*            the longitude coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the tile X number of the longitude value.*/public static long longitudeToTileX(double longitude, byte zoom) {return pixelXToTileX(longitudeToPixelX(longitude, zoom), zoom);}/*** Convert a pixel X coordinate at a certain zoom level to a longitude* coordinate.* * @param pixelX*            the pixel X coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the longitude value of the pixel X coordinate.*/public static double pixelXToLongitude(double pixelX, byte zoom) {return 360 * ((pixelX / ((long) Tile.TILE_SIZE << zoom)) - 0.5);}/*** Convert a pixel X coordinate to the tile X number.* * @param pixelX*            the pixel X coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the tile X number.*/public static long pixelXToTileX(double pixelX, byte zoom) {return (long) Math.min(Math.max(pixelX / Tile.TILE_SIZE, 0), Math.pow(2, zoom) - 1);}/*** Convert a tile X number to a pixel X coordinate.* * @param tileX*            the tile X number that should be converted* @return the pixel X coordinate*/public static double tileXToPixelX(long tileX) {return tileX * Tile.TILE_SIZE;}/*** Convert a tile Y number to a pixel Y coordinate.* * @param tileY*            the tile Y number that should be converted* @return the pixel Y coordinate*/public static double tileYToPixelY(long tileY) {return tileY * Tile.TILE_SIZE;}/*** Convert a pixel Y coordinate at a certain zoom level to a latitude* coordinate.* * @param pixelY*            the pixel Y coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the latitude value of the pixel Y coordinate.*/public static double pixelYToLatitude(double pixelY, byte zoom) {double y = 0.5 - (pixelY / ((long) Tile.TILE_SIZE << zoom));return 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;}/*** Converts a pixel Y coordinate to the tile Y number.* * @param pixelY*            the pixel Y coordinate that should be converted.* @param zoom*            the zoom level at which the coordinate should be converted.* @return the tile Y number.*/public static long pixelYToTileY(double pixelY, byte zoom) {return (long) Math.min(Math.max(pixelY / Tile.TILE_SIZE, 0), Math.pow(2, zoom) - 1);}/*** Convert a tile X number at a certain zoom level to a longitude* coordinate.* * @param tileX*            the tile X number that should be converted.* @param zoom*            the zoom level at which the number should be converted.* @return the longitude value of the tile X number.*/public static double tileXToLongitude(long tileX, byte zoom) {return pixelXToLongitude(tileX * Tile.TILE_SIZE, zoom);}/*** Convert a tile Y number at a certain zoom level to a latitude coordinate.* * @param tileY*            the tile Y number that should be converted.* @param zoom*            the zoom level at which the number should be converted.* @return the latitude value of the tile Y number.*/public static double tileYToLatitude(long tileY, byte zoom) {return pixelYToLatitude(tileY * Tile.TILE_SIZE, zoom);}/*** Computes the amount of latitude degrees for a given distance in pixel at* a given zoom level.* * @param deltaPixel*            the delta in pixel* @param lat*            the latitude* @param zoom*            the zoom level* @return the delta in degrees*/public static double deltaLat(double deltaPixel, double lat, byte zoom) {double pixelY = latitudeToPixelY(lat, zoom);double lat2 = pixelYToLatitude(pixelY + deltaPixel, zoom);return Math.abs(lat2 - lat);}
}

数据如何查询

如果是postgres,并安装了postgis插件,那么只需要一句sql就可以查询出mvt格式的切片

		-- code是我查询st_r_sn表的条件(诸位自行斟酌适应自己的条件)String sql = "WITH mvtgeom AS (" +"SELECT ST_AsMVTGeom(T.geometry, ST_TileEnvelope(" + xyz2prj4326BBox(z, x, y)+", 4490 ),4096,0,true) AS geom, " +"qh_name as name, id from st_r_sn as T where qh_code = '"+code+"')" +"SELECT ST_AsMVT(mvtgeom.*) as data FROM mvtgeom";

如果是达梦这个继承了oracle衣钵的国产数据库,那就稍微有点麻烦了,需要先安装dmgeo扩展包,然后用ecc转换,值得注意的是你的空间字段需要指定SRID(坐标系)和查询语句一致,如4490,如果在数据入库时并没有指定SRID,那么达梦
会默认是这个字段的SRID=0,你也可以用setSRID函数重新赋值

		String sql = "select qh_name as name, dmgeo.st_astext(geom) as geom from t_geo " +"where qh_code = '"+code+"' and dmgeo.st_intersects(geom, dmgeo.st_geomfromtext(?, 4490))";

如何输出mvt格式给前端

如果是postgres或者金仓数据库,直接输出byte数组

		Map<String, Object> results = jdbc.queryForMap(sql);ByteArrayOutputStream out = new ByteArrayOutputStream();GZIPOutputStream gzip;try {gzip = new GZIPOutputStream(out);gzip.write((byte[]) results.get("data"));gzip.close();} catch (IOException e) {e.printStackTrace();}return out.toByteArray();

如果是达梦

		try {String tile = parseXyz2Bound(x, y, z);List<Map<String, Object>> results = jdbc.queryForList(sql, tile);VectorTileEncoder vte = new VectorTileEncoder(4096, 16, false);for (Map<String, Object> m : results) {String wkt = (String) m.get("geom");Geometry geom = new WKTReader().read(wkt);convert2Piexl(x, y, z, geom);m.remove("geom");Random r = new Random();long id = r.nextLong();vte.addFeature("boundary", m, geom, id);}if (results.size() > 0) {return vte.encode();} else {System.out.println("区划" + code + "的索引x:" + x + ",y:" + y + ",z:" + z + "在范围" + tile + "未查询到数据");return null;}} catch (com.vividsolutions.jts.io.ParseException e) {e.printStackTrace();}return null;

最后在controller上调用就可以了

	@ApiOperation("mvt切片")@ApiImplicitParams({@ApiImplicitParam(name = "code", value = "查询代码", paramType = "path", dataType = "String", required = true),@ApiImplicitParam(name = "z", value = "层", paramType = "path", dataType = "int", required = true),@ApiImplicitParam(name = "x", value = "行", paramType = "path", dataType = "int", required = true),@ApiImplicitParam(name = "y", value = "列", paramType = "path", dataType = "int", required = true)})@RequestMapping(value = "/mvt/{code}/{z}/{x}/{y}", produces="application/x-protobuf", method = RequestMethod.GET)public byte[] spatial(@PathVariable String code, @PathVariable int x, @PathVariable int y, @PathVariable int z) {return dao.getMvtTile(code, x, y, z);}

前端如何调用


<html>
<head>
<meta charset='utf-8'/>
<title data-i18n="resources.title_beijingMVTVectorTile"></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script type="text/javascript" src="jquery.min.js"></script>
<!-- <script type="text/javascript" src="iclient9-mapboxgl.min.js"></script> -->
<script type="text/javascript" src="mapbox-gl.min.js"></script>
<link type="text/css" rel="stylesheet" href="mapbox-gl.min.css"/>
<script type="text/javascript" src="mapbox-gl-draw.js"></script>
<link type="text/css" rel="stylesheet" href="mapbox-gl-draw.css"/><style>body {margin: 0;padding: 0;}#draws{position: absolute;z-index: 10;margin-left: 200px;margin-top: 50px;}#draw_area{position: absolute;z-index: 10;margin-left: 100px;margin-top: 50px;}#map {position: absolute;top: 0;bottom: 0;width: 100%;}
</style>
</head>
<body><div id="draw_area" style="color: chocolate; font-weight: bold;"></div><div id="draws"></div><div id='map'></div><script type="text/javascript">    var host = window.isLocal ? window.server : 'https://iserver.supermap.io';;
//var host = window.isLocal ? window.server : "http://192.168.1.13:8090";
var drawHandleModel;
mapboxgl.accessToken = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4M29iazA2Z2gycXA4N2pmbDZmangifQ.-g_vE53SD2WrJ6tFX7QHmA';
var attribution = "<a href='https://www.mapbox.com/about/maps/' target='_blank'>© Mapbox </a>" +" with <span>© <a href='https://iclient.supermap.io' target='_blank'>SuperMap iClient</a> | </span>" +" Map Data <span>© <a href='http://support.supermap.com.cn/product/iServer.aspx' target='_blank'>SuperMap iServer</a></span> ";var map = new mapboxgl.Map({container: 'map', // container idstyle: {"version": 8,"sources": {"raster-tiles": {"attribution": attribution,"type": "raster","tiles": [host + '/iserver/services/map-china400/rest/maps/ChinaDark/zxyTileImage.png?z={z}&x={x}&y={y}'],"tileSize": 256}},"layers": [{"id": "simple-tiles","type": "raster","source": "raster-tiles","minzoom": 0,"maxzoom": 22}]},center: [104.641354,28.758767],zoom: 7
});map.addControl(new mapboxgl.NavigationControl(), 'top-left');map.on('load',function(){// // 四川省界// map.addLayer({//     'id': 'boundary1',//     'type': 'line',//     'source': {//             type: 'vector',//             tiles: [//             // 切片服务地址tagola切片//                 `http://127.0.0.1:9494/address/geo/boundary/510000000000/{z}/{x}/{y}`//             ],//             minzoom: 3,//             maxzoom: 16,//         },//     'source-layer': 'boundary',//     'paint': {//         'line-color':'red',//         'line-width': 5//     }// });//成都市map.addLayer({'id': 'boundary2','type': 'fill','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510100000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'fill-color': 'yellow', // blue color fill'fill-opacity': 0.5}});// 自贡市map.addLayer({'id': 'boundary3','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510300000000/{z}/{x}/{y}`,],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 攀枝花市map.addLayer({'id': 'boundary4','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510400000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 泸州市map.addLayer({'id': 'boundary5','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510500000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 德阳市map.addLayer({'id': 'boundary6','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510600000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 绵阳市map.addLayer({'id': 'boundary7','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510700000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 广元市map.addLayer({'id': 'boundary8','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510800000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 遂宁市map.addLayer({'id': 'boundary9','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510900000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 内江市map.addLayer({'id': 'boundary10','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511000000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 乐山市map.addLayer({'id': 'boundary11','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511100000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 南充市map.addLayer({'id': 'boundary13','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511300000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 眉山市map.addLayer({'id': 'boundary14','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511400000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 宜宾市map.addLayer({'id': 'boundary15','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511500000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 广安市map.addLayer({'id': 'boundary16','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511600000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 达州市map.addLayer({'id': 'boundary17','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511700000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 雅安市map.addLayer({'id': 'boundary18','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511800000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 巴中市map.addLayer({'id': 'boundary19','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/511900000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 资阳市map.addLayer({'id': 'boundary20','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/512000000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 阿坝map.addLayer({'id': 'boundary32','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/513200000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 甘孜map.addLayer({'id': 'boundary33','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/513300000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 凉山map.addLayer({'id': 'boundary34','type': 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/513400000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary','paint': {'line-color':'#66B852','line-width':3.5}});// 省界用动画效果map.addLayer({type: 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510000000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary',id: 'line-background',paint: {'line-color': 'yellow','line-width': 6,'line-opacity': 0.4}});map.addLayer({type: 'line','source': {type: 'vector',tiles: [// 切片服务地址tagola切片`http://127.0.0.1:9494/address/geo/boundary/510000000000/{z}/{x}/{y}`],minzoom: 5,maxzoom: 16,},'source-layer': 'boundary',id: 'line-dashed',paint: {'line-color': 'yellow','line-width': 6,'line-dasharray': [0, 4, 3]}});// 线段动画效果// technique based on https://jsfiddle.net/2mws8y3q/// an array of valid line-dasharray values, specifying the lengths of the alternating dashes and gaps that form the dash patternconst dashArraySequence = [[0, 4, 3],[0.5, 4, 2.5],[1, 4, 2],[1.5, 4, 1.5],[2, 4, 1],[2.5, 4, 0.5],[3, 4, 0],[0, 0.5, 3, 3.5],[0, 1, 3, 3],[0, 1.5, 3, 2.5],[0, 2, 3, 2],[0, 2.5, 3, 1.5],[0, 3, 3, 1],[0, 3.5, 3, 0.5]];let step = 0;function animateDashArray(timestamp) {// Update line-dasharray using the next value in dashArraySequence. The// divisor in the expression `timestamp / 50` controls the animation speed.const newStep = parseInt((timestamp / 50) % dashArraySequence.length);if (newStep !== step) {map.setPaintProperty('line-dashed','line-dasharray',dashArraySequence[step]);step = newStep;}// Request the next frame of the animation.requestAnimationFrame(animateDashArray);}// start the animationanimateDashArray(0);})</script>
</body>
</html>

最后由于数据保密问题不能发完整代码,但是思路是前人走过的,可以放心使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/9087.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用SQL语句创建触发器

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…

Spark SQL生产优化经验--任务参数配置模版

大表扫描 特殊case说明&#xff1a;当任务存在扫event_log表时需注意&#xff0c;若对event_log表进行了过滤&#xff0c;且过滤比很高&#xff0c;如下图的case&#xff0c;input为74T&#xff0c;但shuffle write仅为3.5G&#xff0c;那么建议提高单partition的读取数据量&a…

接口测试之测试原则、测试用例、测试流程......

一、接口的介绍 软件测试中&#xff0c;常说的接口有两种&#xff1a;图形用户接口&#xff08;GUI&#xff0c;人与程序的接口&#xff09;、应用程序编程接口&#xff08;API&#xff09;。 接口&#xff08;API&#xff09;是系统与系统之间&#xff0c;模块与模块之间或者…

elasticsearch学习篇:初识ES

一、什么是ES 1、基础概念 是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容es是elastic stack(ELK)的核心&#xff0c;负责存储、搜索、分析数据。 ELK包括以下内容&#xff1a; ELK被广泛应用在日志数据…

【Web3】认识元宇宙

元宇宙在Web3中扮演着重要的角色&#xff0c;可以带来许多创新和变革。Web3是下一代互联网的概念&#xff0c;强调去中心化、区块链技术和加密货币的应用。 元宇宙在Web3中的几个作用&#xff1a; 去中心化的虚拟世界&#xff1a;元宇宙通过使用区块链技术和去中心化的网络结构…

【MySQL事务】保证数据完整性的利器

1、事务的认识 事务&#xff1a;事务就是将多个SQL给打包在一起&#xff0c;组成一个整体。组成这个整体的各个SQL&#xff0c;要么全部成功&#xff0c;要么全部失败。 举例说明&#xff1a; 情人节到了&#xff0c;滑稽老铁打算给他女朋友小美发给红包&#xff0c;但是他又害…

QWebEngine应用---执行javascript

我们都知道现代前端技术是基于html、css和javascript进行显示交互的&#xff0c;其中html和css属于静态界面显示&#xff0c;辅以javascript使页面交互更丰富。浏览器作为前端页面显示的基石&#xff0c;提供一套显示、交互、调试的东西。QWebEngine同样也提供了这些功能&#…

spring如何使用junit进行测试

第一步maven的pom.xml引入坐标&#xff1a; <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency> 第二步编写测试方法&#xff1a; 第三步 定义scope类型

ARM实验-ARM主程序调用ARM/C语言子程序

一、实验名称&#xff1a;ARM主程序调用ARM/C语言子程序 二、实验目的&#xff1a; 了解ARM应用程序框架。了解ARM汇编程序函数和C语言程序函数相互调用时&#xff0c;遵循的ATPCS标准&#xff1b;了解和掌握ARM汇编程序调用C语言程序函数的基本方法&#xff1b;了解和掌握AR…

学习Maven Web 应用

Maven Web 应用 本章节我们将学习如何使用版本控制系统 Maven 来管理一个基于 web 的项目&#xff0c;如何创建、构建、部署已经运行一个 web 应用。 创建 Web 应用 我们可以使用 maven-archetype-webapp 插件来创建一个简单的 Java web 应用。 打开命令控制台&#xff0c;…

LNMT(linux下nignx+mysql+tomcat(中间件)应用)部署应用、及各服务介绍、部署开源站点jpress

目录 一、环境准备 二、tomcat1和tomcat2服务器&#xff0c;安装配置tomcat 1.tomcat服务器介绍 2.JDK软件介绍 3.查看JDK是否安装 4.tomcat1和tomcat2服务器&#xff0c;安装JDK1.8.0_191&#xff08;JDK必须和nginx版本相适应&#xff0c;不然一直报错&#xff09; 5.安…

软件测试之【单元测试、系统测试、集成测试】

一、单元测试的概念 单元测试&#xff08;Unit Testing&#xff09;是对软件基本组成单元进行的测试&#xff0c;如函数&#xff08;function或procedure&#xff09;或一个类的方法&#xff08;method&#xff09;。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0…