使用java生成mvt切片的方法

目录

成果

使用java切片前端调用成果

新的想法

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

Java能为切片做什么

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

引入依赖

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

<!-- geo包含的依赖 -->
		<dependency>
			<groupId>org.locationtech.jts</groupId>
			<artifactId>jts-core</artifactId>
			<version>1.18.0</version>
		</dependency>
		<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;
		}
		
	}
		

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


public class WGS84 {
	/**
	 * Equatorial radius of earth is required for distance computation.
	 */
	public static final double EQUATORIALRADIUS = 6378137.0;

	/**
	 * Polar radius of earth is required for distance computation.
	 */
	public static final double POLARRADIUS = 6356752.3142;

	/**
	 * The flattening factor of the earth's ellipsoid is required for distance computation.
	 */
	public static final double INVERSEFLATTENING = 298.257223563;

}

/**
 * This class represents a map tile. All mandatory fields are final.
 * 
 */
public class Tile implements Comparable<Tile> {
	static final byte TILE_BYTES_PER_PIXEL = 2;

	/**
	 * The tile size in pixel.
	 */
	public static final short TILE_SIZE = 256;
	private final int hashCode;
	final long pixelX;
	final long pixelY;
	int renderPriority;
	final long x;
	final long y;
	final byte zoomLevel;

	/**
	 * Constructs a new tile with the specified XY number and zoom level.
	 * 
	 * @param x
	 *            the X number of the tile.
	 * @param y
	 *            the Y number of the tile.
	 * @param zoomLevel
	 *            the zoom level of the tile.
	 */
	Tile(long x, long y, byte zoomLevel) {
		this.x = x;
		this.y = y;
		this.zoomLevel = zoomLevel;
		this.hashCode = calculateHashCode();
		this.pixelX = x * TILE_SIZE;
		this.pixelY = y * TILE_SIZE;
		this.renderPriority = Integer.MAX_VALUE;
	}

	public int compareTo(Tile another) {
		return this.renderPriority - another.renderPriority;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		} else if (obj == null) {
			return false;
		} else if (!(obj instanceof Tile)) {
			return false;
		} else {
			Tile other = (Tile) obj;
			if (this.x != other.x) {
				return false;
			} else if (this.y != other.y) {
				return false;
			} else if (this.zoomLevel != other.zoomLevel) {
				return false;
			} else {
				return true;
			}
		}
	}

	@Override
	public int hashCode() {
		return this.hashCode;
	}

	/**
	 * Calculate and save the hash code of this tile.
	 * 
	 * @return the hash code for this tile.
	 */
	private int calculateHashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (this.x ^ (this.x >>> 32));
		result = prime * result + (int) (this.y ^ (this.y >>> 32));
		result = prime * result + this.zoomLevel;
		return result;
	}

	@Override
	public String toString() {
		return "Tile: " + this.x + ", " + this.y + ", " + this.zoomLevel;
	}
}


/*
 * 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);
	}
}

坐标转换工具


import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class CoordinateUtil {

    /**
     * 圆周率
     */
    public final static double pi = Math.PI;

    /**
     * 卫星椭球坐标投影到平面地图坐标系的投影因子
     */
    public final static double a = 6378245.0;

    /**
     * 椭球的偏心率 (a^2 - b^2) / a^2
     */
    public final static double ee = 0.00669342162296594323;


    /**
     * @interface
     * @param x 经度
     * @param y 维度
     * @return 转换结果
     * @Description 墨卡托转WGS84
     */
    public static double[] MercatorToWGS84(double x, double y) {
        double originShift = Math.PI * 6378137;
        double lon = (x / originShift) * 180;
        double lat = (y / originShift) * 180;
        lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
        return new double[]{lon, lat};
    }

    /**
     * @interface
     * @param lng 经度
     * @param lat 维度
     * @return 转换结果
     * @Description WGS84转墨卡托
     */
    public static double[] WGS84ToWebMercator(double lng, double lat) {
        double PI = Math.PI;
        double x = lng * 20037508.34 / 180;
        double y = Math.log(Math.tan((90 + lat) * PI / 360)) / (PI / 180);
        y = y * 20037508.34 / 180;
        return new double[]{x, y};
    }

    /**
     * @interface
     * @param longitude 经度
     * @param latitude 维度
     * @return 转换结果
     * @Description WGS84转CGCS2000
     */
    public static double[] WGS84ToCGS2000(double longitude, double latitude) {
        double[] output = new double[2];
        double longitude1,latitude1, longitude0, X0,Y0, xval,yval;
        //NN曲率半径,测量学里面用N表示
        //M为子午线弧长,测量学里用大X表示
        //fai为底点纬度,由子午弧长反算公式得到,测量学里用Bf表示
        //R为底点所对的曲率半径,测量学里用Nf表示
        double a,f, e2,ee, NN, T,C,A, M, iPI;
        iPI = 0.0174532925199433; //3.1415926535898/180.0;
        a = 6378137.0; f=1/298.257222101; //CGCS2000坐标系参数
        //a=6378137.0; f=1/298.2572236; //wgs84坐标系参数
        longitude0 = 117;//中央子午线 根据实际进行配置
        longitude0 = longitude0 * iPI ;//中央子午线转换为弧度
        longitude1 = longitude * iPI ; //经度转换为弧度
        latitude1 = latitude * iPI ; //纬度转换为弧度
        e2=2*f-f*f;
        ee=e2*(1.0-e2);
        NN = a/Math.sqrt(1.0-e2*Math.sin(latitude1)*Math.sin(latitude1));
        T = Math.tan(latitude1)*Math.tan(latitude1);
        C = ee*Math.cos(latitude1)*Math.cos(latitude1);
        A = (longitude1-longitude0)*Math.cos(latitude1);
        M = a*((1-e2/4-3*e2*e2/64-5*e2*e2*e2/256)*latitude1-(3*e2/8+3*e2*e2/32+45*e2*e2
                *e2/1024)*Math.sin(2*latitude1)
                +(15*e2*e2/256+45*e2*e2*e2/1024)*Math.sin(4*latitude1)-(35*e2*e2*e2/3072)*Math.sin(6*latitude1));
        xval = NN*(A+(1-T+C)*A*A*A/6+(5-18*T+T*T+72*C-58*ee)*A*A*A*A*A/120);
        yval = M+NN*Math.tan(latitude1)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24
                +(61-58*T+T*T+600*C-330*ee)*A*A*A*A*A*A/720);
        X0 = 500000L;
        Y0 = 0;
        xval = xval+X0; yval = yval+Y0;
        //转换为投影
        output[0] = xval;
        output[1] = yval;
        return output;
    }

    private static double formatby6(double num) {
        String result = String.format("%.6f", num);
        return Double.valueOf(result);
    }

    /**
     * @interface
     * @param  x 经度
     * @param y 维度
     * @return 转换结果
     * @Description CGCS2000转WGS84
     */
    public static double[] CGS2000ToWGS84(double x, double y) {
        double L0 = 117;//中央子午线需根据实际情况设置
        double lat ,lon;
        y -= 500000;
        double [] output = new double[2];
        double iPI = 0.0174532925199433;//pi/180
        double a = 6378137.0; //长半轴 m
        double b = 6356752.31414; //短半轴 m
        double f = 1/298.257222101;//扁率 a-b/a
        double e = 0.0818191910428; //第一偏心率 Math.sqrt(5)
        double ee = Math.sqrt(a*a-b*b)/b; //第二偏心率
        double bf = 0; //底点纬度
        double a0 = 1+(3*e*e/4) + (45*e*e*e*e/64) + (175*e*e*e*e*e*e/256) + (11025*e*e*e*e*e*e*e*e/16384) + (43659*e*e*e*e*e*e*e*e*e*e/65536);
        double b0 = x/(a*(1-e*e)*a0);
        double c1 = 3*e*e/8 +3*e*e*e*e/16 + 213*e*e*e*e*e*e/2048 + 255*e*e*e*e*e*e*e*e/4096;
        double c2 = 21*e*e*e*e/256 + 21*e*e*e*e*e*e/256 + 533*e*e*e*e*e*e*e*e/8192;
        double c3 = 151*e*e*e*e*e*e*e*e/6144 + 151*e*e*e*e*e*e*e*e/4096;
        double c4 = 1097*e*e*e*e*e*e*e*e/131072;
        bf = b0 + c1*Math.sin(2*b0) + c2*Math.sin(4*b0) +c3*Math.sin(6*b0) + c4*Math.sin(8*b0); // bf =b0+c1*sin2b0 + c2*sin4b0 + c3*sin6b0 +c4*sin8b0 +...
        double tf = Math.tan(bf);
        double n2 = ee*ee*Math.cos(bf)*Math.cos(bf); //第二偏心率平方成bf余弦平方
        double c = a*a/b;
        double v=Math.sqrt(1+ ee*ee*Math.cos(bf)*Math.cos(bf));
        double mf = c/(v*v*v); //子午圈半径
        double nf = c/v;//卯酉圈半径
        //纬度计算
        lat = bf-(tf/(2*mf)*y)*(y/nf) * (1-1/12*(5+3*tf*tf+n2-9*n2*tf*tf)*(y*y/(nf*nf))+1/360*(61+90*tf*tf+45*tf*tf*tf*tf)*(y*y*y*y/(nf*nf*nf*nf)));
        //经度偏差
        lon = 1/(nf*Math.cos(bf))*y -(1/(6*nf*nf*nf*Math.cos(bf)))*(1+2*tf*tf +n2)*y*y*y + (1/(120*nf*nf*nf*nf*nf*Math.cos(bf)))*(5+28*tf*tf+24*tf*tf*tf*tf)*y*y*y*y*y;
        output[0] = formatby6(lat/iPI);
        output[1] = formatby6(L0+lon/iPI);
        //System.out.println(result[1]+","+result[0]);
        return output;
    }


    /**
     * @interface
     * @param lon 经度
     * @param lat 维度
     * @return 转换结果
     * @Description WGS84 to 火星坐标系 (GCJ-02)
     */
    public static double[] wgs84_To_Gcj02(double lon, double lat) {
        if (outOfChina(lat, lon)) {
            return null;
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * pi;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new double[]{mgLon, mgLat};
    }

    /**
     * @interface
     * @param lon 经度
     * @param lat 维度
     * @return 转换结果
     * @Description (腾讯、高德)火星坐标系 (GCJ-02) to WGS84
     */
    public static double[] gcj02_To_Wgs84(double lon, double lat) {
        double[] gps = transform(lat, lon);
        double lontitude = lon * 2 - gps[1];
        double latitude = lat * 2 - gps[0];
        return new double[]{lontitude, latitude};
    }

    /**
     * @interface
     * @param gg_lon 火星系经度
     * @param gg_lat 火星系维度
     * @return 百度坐标系结果
     * @Description 火星坐标系 (GCJ-02) to 百度坐标系 (BD-09)
     */
    public static double[] gcj02_To_Bd09(double gg_lon, double gg_lat) {
        double x = gg_lon, y = gg_lat;
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * pi);
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * pi);
        double bd_lon = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new double[]{bd_lon, bd_lat};
    }

    /**
     * @interface
     * @param bd_lon 百度系经度
     * @param bd_lat 百度系维度
     * @return 火星系结果
     * @Description 百度坐标系 (BD-09) to 火星坐标系 (GCJ-02)
     */
    public static double[] bd09_To_Gcj02(double bd_lon, double bd_lat) {
        double x = bd_lon - 0.0065, y = bd_lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * pi);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * pi);
        double gg_lon = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new double[]{gg_lon, gg_lat};
    }

    /**
     * @interface
     * @param bd_lat 百度系经度
     * @param bd_lon 百度系维度
     * @return  84坐标系结果
     * @Description 百度坐标系 (BD-09) to WGS84
     */
    public static double[] bd09_To_Wgs84(double bd_lon, double bd_lat) {
        double[] gcj02 = CoordinateUtil.bd09_To_Gcj02(bd_lon, bd_lat);
        double[] map84 = CoordinateUtil.gcj02_To_Wgs84(gcj02[0], gcj02[1]);
        return map84;

    }

    /**
     * @interface
     * @param lat 维度
     * @param lon 经度
     * @return  是/否
     * @Description 判断是否在中国范围内
     */
    public static boolean outOfChina( double lon, double lat) {
        if (lon < 72.004 || lon > 137.8347)
            return true;
        if (lat < 0.8293 || lat > 55.8271)
            return true;
        return false;
    }

    /**
     * @param lat 经度
     * @param lon 维度
     * @return 转换成数组
     * @Description transform
     */
    private static double[] transform(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return new double[]{lat, lon};
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * pi;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new double[]{mgLat, mgLon};
    }

    /**
     * @param x
     * @param y
     * @return
     * @Description transformLat
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * @param x
     * @param y
     * @return
     * @Description transformLon
     */
    public static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
        return ret;
    }


    /**
     * @interface
     * @param longitude 经度
     * @param latitude  纬度
     * @return double[] x y
     * @Description WGS84 to 高斯投影(6度分带)
     */
    public static double[] wgs84_To_Gauss6(double longitude, double latitude) {
        int ProjNo = 0;
        int ZoneWide; // //带宽
        double[] output = new double[2];
        double longitude1, latitude1, longitude0, X0, Y0, xval, yval;
        double a, f, e2, ee, NN, T, C, A, M, iPI;
        iPI = 0.0174532925199433; // //3.1415926535898/180.0;
        ZoneWide = 6; //6度带宽
        a = 6378137.0;
        f = 1.0 / 298.257223563; //WGS84坐标系参数
        //a = 6378245.0;f = 1.0 / 298.3; // 54年北京坐标系参数
        // //a=6378140.0; f=1/298.257; //80年西安坐标系参数
        ProjNo = (int) (longitude / ZoneWide);
        longitude0 = (double) (ProjNo * ZoneWide + ZoneWide / 2);
        longitude0 = longitude0 * iPI;
        longitude1 = longitude * iPI; // 经度转换为弧度
        latitude1 = latitude * iPI; // 纬度转换为弧度
        e2 = 2 * f - f * f;
        ee = e2 / (1.0 - e2);
        NN = a
                / Math.sqrt(1.0 - e2 * Math.sin(latitude1)
                * Math.sin(latitude1));
        T = Math.tan(latitude1) * Math.tan(latitude1);
        C = ee * Math.cos(latitude1) * Math.cos(latitude1);
        A = (longitude1 - longitude0) * Math.cos(latitude1);
        M = a
                * ((1 - e2 / 4 - 3 * e2 * e2 / 64 - 5 * e2 * e2 * e2 / 256)
                * latitude1
                - (3 * e2 / 8 + 3 * e2 * e2 / 32 + 45 * e2 * e2 * e2
                / 1024) * Math.sin(2 * latitude1)
                + (15 * e2 * e2 / 256 + 45 * e2 * e2 * e2 / 1024)
                * Math.sin(4 * latitude1) - (35 * e2 * e2 * e2 / 3072)
                * Math.sin(6 * latitude1));
        // 因为是以赤道为Y轴的,与我们南北为Y轴是相反的,所以xy与高斯投影的标准xy正好相反;
        xval = NN
                * (A + (1 - T + C) * A * A * A / 6 + (5 - 18 * T + T * T + 14
                * C - 58 * ee)
                * A * A * A * A * A / 120);
        yval = M
                + NN
                * Math.tan(latitude1)
                * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 + (61
                - 58 * T + T * T + 270 * C - 330 * ee)
                * A * A * A * A * A * A / 720);
        X0 = 1000000L * (ProjNo + 1) + 500000L;
        Y0 = 0;
        xval = xval + X0;
        yval = yval + Y0;
        output[0] = xval;
        output[1] = yval;
        return output;
    }


    /**
     * @interface
     * 两经纬度之间的距离
     * @param lat_a 纬度a
     * @param lng_a 经度a
     * @param lat_b 纬度b
     * @param lng_b 经度b
     * @return a与b之间的距离
     */
    public static double distanceToPoint(double lat_a, double lng_a, double lat_b, double lng_b) {
        //jts提供的几何要素工厂类
        GeometryFactory geometryFactory = new GeometryFactory();

        //火星坐标(gcj02)转GPS坐标(WGS84)
        double[] wgsPntA = CoordinateUtil.gcj02_To_Wgs84(lng_a, lat_a);
        double[] wgsPntB = CoordinateUtil.gcj02_To_Wgs84(lng_b, lat_b);

        //WGS84->高斯6度分带投影
        double[] gaussPntA = wgs84_To_Gauss6(wgsPntA[0], wgsPntA[1]);
        double[] gaussPntB = wgs84_To_Gauss6(wgsPntB[0], wgsPntB[1]);

        //通过几何要素工厂得到point实体
        Point pntA = geometryFactory.createPoint(new Coordinate(gaussPntA[0], gaussPntA[1]));
        Point pntB = geometryFactory.createPoint(new Coordinate(gaussPntB[0], gaussPntB[1]));
        // 两点距离
        return pntA.distance(pntB);
    }

    /**
     * 保留6位精度
     * @return 小数转换结果
     */
    public static String format(String resource) {
        //保留两位小数
        DecimalFormat decimalFormat = new DecimalFormat("###.000000");
        decimalFormat.setRoundingMode(RoundingMode.DOWN);
        String target = decimalFormat.format(new BigDecimal(resource));
        return target;
    }

    public static void main(String[] args) {
        System.out.println(outOfChina(103.06, 26.91));
        double[] re1 = wgs84_To_Gcj02(103.06, 26.91);
        double[] re2 = wgs84_To_Gauss6(103.06, 26.91);
        System.out.println(re1);
        System.out.println(re2);
    }

}

数据如何查询

如果是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();
				// m就是properties
				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>

前端调用方式2

<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> ";



const vecUrl = "http://t0.tianditu.com/img_w/wmts?tk=447f9b2515d3bc8641c9156b29535282";
const cvaUrl = "http://t0.tianditu.com/cia_w/wmts?tk=447f9b2515d3bc8641c9156b29535282";
//实例化source对象
var tdtVec = {
  //类型为栅格瓦片
  "type": "raster",
  'tiles': [
    //请求地址
    vecUrl + "&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles"
  ],
  //分辨率
  'tileSize': 256
};
var tdtCva = {
  "type": "raster",
  'tiles': [
    cvaUrl + "&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles"
  ],
  'tileSize': 256
}

var style={
  //设置版本号,一定要设置
  "version": 8,
  //添加来源
  "sources": {
    "tdtVec": tdtVec,
    "tdtCva": tdtCva
  },
  "layers": [
    {
      //图层id,要保证唯一性
      "id": "tdtVec",
      //图层类型
      "type": "raster",
      //数据源
      "source": "tdtVec",
      //图层最小缩放级数
      "minzoom": 0,
      //图层最大缩放级数
      "maxzoom": 17
    },
    {
      "id": "tdtCva",
      "type": "raster",
      "source": "tdtCva",
      "minzoom": 0,
      "maxzoom": 17
    }
  ],
}

var map = new mapboxgl.Map({
    container: 'map', // container id
    style: style,
    center: [104.068594,30.667451],
    zoom: 13
});

map.addControl(new mapboxgl.NavigationControl(), 'top-left');

map.on('load',function(){

        // 切片
        map.addLayer({
            'id': 'boundary1',
            'type': 'fill',
            'source': {
                    type: 'vector',
                    tiles: [
                        `http://127.0.0.1:9494/geo/building/{z}/{x}/{y}`
                    ],
                    minzoom: 15,
                    maxzoom: 18,
                },
            'source-layer': 'building',
            'paint': {
                'fill-color': 'yellow', // blue color fill
                'fill-opacity': 0.5
            }
        });


        map.on('click', function(e) {
            var features = map.queryRenderedFeatures(e.point);
                if (features.length > 0) {
                    console.log(features); // 输出点击位置的要素信息
                }
        });


})

    </script>
</body>
</html>
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
GeoServer可以通过扩展插件geoserver-mvt生成MVT(Mapbox Vector Tile)格式的矢量瓦片数据。下面是一些简单的步骤: 1. 安装geoserver-mvt插件:在GeoServer的安装目录下,进入webapps/geoserver/WEB-INF/lib目录,将geoserver-mvt插件的jar文件复制到该目录下,然后重启GeoServer。 2. 创建一个MVT数据源:在GeoServer中,选择“Stores” -> “Add new Store” -> “MVT”来创建一个新的MVT数据源。在数据源配置中,需要指定MVT的名称、描述和访问地址等信息。访问地址的格式通常是:`http://localhost:8080/geoserver/gwc/service/wmts/mvt?`。 3. 创建一个MVT图层:在GeoServer中,选择“Layers” -> “Add new Layer”来创建一个MVT图层。在图层配置中,需要选择刚才创建的MVT数据源,并指定图层的样式和矢量数据源等信息。矢量数据源可以是GeoServer中的一个矢量数据集,也可以是外部的矢量数据源。 4. 配置WMTS服务:在GeoServer中,选择“Services” -> “WMTS”来配置WMTS服务。在服务配置中,需要指定服务名称、服务地址和图层列表等信息。服务地址的格式通常是:`http://localhost:8080/geoserver/gwc/service/wmts?`。 5. 使用MVT数据:现在,我们可以在Mapbox、OpenLayers等地图库中使用MVT数据了。我们只需要在地图中添加一个WMTS图层,并指定服务地址和图层名称等参数即可。 需要注意的是,MVT数据源和图层的配置需要根据具体的矢量数据和样式进行调整,这里只是一个简单的示例。另外,MVT格式的矢量瓦片数据需要支持的地图库和客户端也有所限制。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sun_falls

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值