使用java生成mvt切片的方法

news2024/12/24 0:35:33

如何使用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 id
    style: {
            "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 pattern
    const 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 animation
    animateDashArray(0);

})

    </script>
</body>
</html>

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

访问学者怎样准备申请推荐信

作为访问学者&#xff0c;申请推荐信是非常重要的一步&#xff0c;它能够在您的申请过程中增加信誉度和竞争力。一个优秀的推荐信可以更好地展现您的学术能力、研究潜力和个人品质。以下是知识人网小编整理的关于如何准备申请推荐信的建议&#xff1a; 1. 确定推荐人&#xff1…

阿里云OSS上传文件工具类

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

20本期刊影响因子上涨,7月SCI/SSCI/AHCI/EI刊源表已更新!

2023年7月SCI/SSCI/AHCI/EI期刊目录更新 2023年6月28日发布的最新《期刊引证报告》中&#xff0c;我处合作期刊中&#xff0c;7月刊源表有20本期刊影响因子上涨&#xff0c;同时新增多本快刊&#xff01; 重磅&#xff01;2023年JCR正式发布&#xff08;附影响因子名单下载&a…

[github-100天机器学习]day2 simple linear regression

https://github.com/LiuChuang0059/100days-ML-code/blob/master/Day2_SImple_Linear_regression/README.md 简单线性回归 使用单一特征预测响应值。基于自变量X来预测因变量Y的方法&#xff0c;假设两者线性相关&#xff0c;寻找一种根据特征或自变量X的线性函数来预测Y。 目…

工资难以突破升职加薪必看,资深测试经理教你怎么做好“管理岗”!

要了解测试管理岗位需要具备的素质&#xff0c;我们先来看下测试管理岗位的职责。以下是 Boss 直聘上某几家的公司的测试经理的岗位要求&#xff1a; 如果你想学习接口自动化测试&#xff0c;我这边给你推荐一套视频&#xff0c;这个视频可以说是B站播放全网第一的接口自动化测…

从零开始搭建STM32CubeMX开发环境

本文记录一下如何从零开始使用STM32CubeMX&#xff0c;包括软件的安装&#xff0c;环境的搭建&#xff0c;配置代码的生成等&#xff1b; 本文以STM32G030C8T6为例&#xff0c;如果你的单片机不是以STM32G030C8T6为例&#xff0c;换成你的单片机类型即可&#xff0c;过程都是通…

03_单一职责模式

单一职责 在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随着需求的变化&#xff0c;子类急剧膨胀&#xff0c;同时充斥着重复代码&#xff0c;这时候的关键是划清责任。 装饰模式 动态&#xff08;组合&#xff09;地给一个对象增…

系统没有“internet信息服务(IIS)管理器”

系统没有“internet信息服务&#xff08;IIS&#xff09;管理器” 解决方案1.打开控制面板&#xff0c;找到并打开程序。2.找到并打开程序里的启用或关闭windows功能。3.在‘Internet Information Services’下的‘web管理工具’中找到IIS相关功能&#xff0c;在前面的复选框中…

探索数字孪生世界:市场上五款炙手可热的数字孪生产品介绍

山海鲸可视化&#xff1a;山海鲸可视化是一款国内领先的数字孪生软件&#xff0c;具有强大的GIS功能和可视化效果&#xff0c;广泛应用于城市规划、建筑设计和智慧城市等领域。 华为云数字孪生&#xff1a;华为云数字孪生平台提供了全面的数字化解决方案&#xff0c;包括智慧城…

链表中倒数第k个结点(快慢指针问题)

⭐️ 往期相关文章 &#x1f4ab;链接1&#xff1a;leetcode 876.链表的中间结点(快慢指针问题) &#x1f4ab;链接2&#xff1a;leetcode 206.反转链表 &#x1f4ab;链接3&#xff1a;leetcode 203.移除链表元素 &#x1f4ab;链接4&#xff1a;数据结构-手撕单链表代码详解…

实训笔记7.4

实训笔记7.4 7.4一、座右铭二、IDEA集成开发环境的安装和使用三、DEBUG断点调试四、Java设计模式4.1 适配器模式4.2 动态代理模式4.3 单例设计模式 五、Java中网络编程5.1 网络编程三个核心要素5.2 TCP网络编程 六、基于网络编程的聊天系统6.1 需求分析6.2 系统设计6.2.1 概要设…

解放运营人员:钡铼技术S475物联网网关实现养殖环境的远程监控与告警

在养殖行业中&#xff0c;对环境参数的精确监测与控制至关重要。然而&#xff0c;传统的监测方法往往存在诸多痛点&#xff0c;如数据采集不准确、传输速度慢、可视化效果差等。为了解决这些问题&#xff0c;钡铼技术公司推出了其旗舰产品——S475多功能RTU&#xff0c;该产品在…

如何利用思维导图提高项目管理效率

思维导图 是一种强大的工具&#xff0c;可以帮助我们更好地组织和管理项目。它是一种以图形方式展现思维和概念之间关系的方法&#xff0c;通过将主题、子主题和分支串联起来&#xff0c;帮助我们清晰地了解任务的层次结构和相互关系。在项目管理中&#xff0c;思维导图可以帮助…

数据生成实体类解决方案

文章目录 数据生成实体类解决方案 简介工作原理解析JSON生成实体类示例JSON消息解析JSON核心方法&#xff1a;调用示例&#xff1a;将数据保存到实体类中。将实体类转为输出为JSON。 思考 数据生成实体类解决方案 直接将xml导入到Studio里即可。下载文件连接&#xff1a; CSDN…

GPIO点灯

简述&#xff1a;本人使用教材为《嵌入式系统原理与应用》&#xff0c;GPIOCON控制输出&#xff0c;GPIODAT控制高电平和低电平&#xff0c;高电平点亮&#xff0c;低电平熄灭。

【若依】框架搭建,前端向后端如何发送请求,验证码的实现

若依框架 若依框架&#xff08;Ruoyi&#xff09;是一款基于Spring Boot和Spring Cloud的开源快速开发平台。它提供了一系列的基础功能和通用组件&#xff0c;能够帮助开发者快速构建企业级应用。若依框架采用了模块化的设计理念&#xff0c;用户可以选择需要的功能模块进行集…

全网最全,华为可信专业级认证介绍

1&#xff1a;华为可信专业级认证是什么&#xff1f; 华为在推动技术人员的可信认证&#xff0c;算是一项安全合规的工作。专业级有哪些考试呢&#xff1f;共有四门&#xff1a; 科目一&#xff1a;上级编程&#xff0c;对比力扣2道中等、1道困难&#xff1b; 科目二&#xff…

C++ STL --哈希表

目录 1. unordered系列关联式容器 1.1 unordered_map 1.1.1 unordered_map的文档介绍 1.1.2 unordered_map的接口说明 1.2 unordered_set 1.3 在线OJ 2. 底层结构 2.1 哈希概念 2.2 哈希冲突 2.3 哈希函数 2.4 哈希冲突解决 2.4.1 闭散列 2.4.2 开散列 3. 模拟实现…

中学生用什么样护眼台灯好?适合暑假学习的护眼台灯推荐

终于到了暑假&#xff0c;本来是有大把的“娱乐”时间&#xff0c;可现在看来此“娱乐”和正常出门玩耍的娱乐不太一样。不管是大学生还是中小学生&#xff0c;不少孩子不再出门玩耍&#xff0c;而是宅在家空调WiFi西瓜&#xff0c;抱着手机往那一趴。加上平时还需要抽出时间完…

DatenLord前沿技术分享 No.29

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;通过软硬件深度融合的方式打通云云壁垒&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。BSV的…