使用Java对轨迹进行抽稀,并生成mvt(Map Vector Tile)瓦片

1. 原理

  • Java对轨迹抽稀:道格拉斯普克算法
  • 生成mvt瓦片:VectorTileEncoder.addFeature

2. pom依赖

 <!-- geometry相关类 -->
<dependency>
     <groupId>com.vividsolutions</groupId>
     <artifactId>jts-core</artifactId>
     <version>1.17.1</version>
 </dependency>
<dependency>
    <groupId>com.vividsolutions</groupId>
    <artifactId>jts-core</artifactId>
    <version>1.14.0</version>
</dependency>
 <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.70</version>
 </dependency>
  <!-- vectorTile -->
  <dependency>
      <groupId>no.ecc.vectortile</groupId>
      <artifactId>java-vector-tile</artifactId>
      <version>1.2.1</version>
  </dependency>

注意 locationtech 和 vividsolutions有的类名完全一致
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Point;

3. Java对轨迹道格拉斯普克抽稀源码

package no.ecc.vectortile;

import com.alibaba.fastjson.JSON;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;

import java.util.*;

/*************************************
 *Class Name:Doulag
 *Description:<道格拉斯普克抽稀>
 *@author:Seminar
 *@create:2021/1/14
 *@since 1.0.0
 *************************************/
public class Doulag {
    
    

    /**
     * 计算两点距离
     *
     * @param point1
     * @param point2
     * @return
     */
    private static double calculationDistance(double[] point1, double[] point2) {
    
    
        double lat1 = point1[0];
        double lat2 = point2[0];
        double lng1 = point1[1];
        double lng2 = point2[1];
        double radLat1 = lat1 * Math.PI / 180.0;
        double radLat2 = lat2 * Math.PI / 180.0;
        double a = radLat1 - radLat2;
        double b = (lng1 * Math.PI / 180.0) - (lng2 * Math.PI / 180.0);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        return s * 6370996.81;

    }

    /**
     * 计算点pX到点pA和pB所确定的直线的距离
     *
     * @param start
     * @param end
     * @param center
     * @return
     */
    private static double distToSegment(double[] start, double[] end, double[] center) {
    
    
        double a = Math.abs(calculationDistance(start, end));
        double b = Math.abs(calculationDistance(start, center));
        double c = Math.abs(calculationDistance(end, center));
        double p = (a + b + c) / 2.0;
        double s = Math.sqrt(Math.abs(p * (p - a) * (p - b) * (p - c)));
        return s * 2.0 / a;
    }

    /**
     * 递归方式压缩轨迹
     *
     * @param coordinate
     * @param result
     * @param start
     * @param end
     * @param dMax
     * @return
     */
    private static List<double[]> compressLine(List<double[]> coordinate, List<double[]> result, int start, int end, int dMax) {
    
    
        if (start < end) {
    
    
            double maxDist = 0;
            int currentIndex = 0;
            double[] startPoint = coordinate.get(start);
            double[] endPoint = coordinate.get(end);
            for (int i = start + 1; i < end; i++) {
    
    
                double currentDist = distToSegment(startPoint, endPoint, coordinate.get(i));
                if (currentDist > maxDist) {
    
    
                    maxDist = currentDist;
                    currentIndex = i;
                }
            }
            if (maxDist >= dMax) {
    
    
                //将当前点加入到过滤数组中
                result.add(coordinate.get(currentIndex));
                //将原来的线段以当前点为中心拆成两段,分别进行递归处理
                compressLine(coordinate, result, start, currentIndex, dMax);
                compressLine(coordinate, result, currentIndex + 1, end, dMax);
            } else {
    
    
                result.remove(endPoint);
            }
        }
        return result;
    }

    /**
     * @param coordinate 原始轨迹Array<{latitude,longitude}>
     * @param dMax       允许最大距离误差
     * @return douglasResult 抽稀后的轨迹
     */
    public static List<double[]> douglasPeucker(List<double[]> coordinate, int dMax) {
    
    
        //抽稀点数量需要大于2
        if (coordinate == null || coordinate.size() <= 2) {
    
    
            return null;
        }

        List<double[]> coordinate2 = new ArrayList<>();
        for (int i = 0; i < coordinate.size(); i++) {
    
    
            double[] point = Arrays.copyOf(coordinate.get(i), 3);
            point[2] = i;
            coordinate2.add(point);
        }
        List<double[]> result = new ArrayList<>();
        result = compressLine(coordinate2, result, 0, coordinate2.size() - 1, dMax);
        result.add(coordinate2.get(0));
        result.add(coordinate2.get(coordinate.size() - 1));
        Collections.sort(result, new Comparator<double[]>() {
    
    
            @Override
            public int compare(double[] u1, double[] u2) {
    
    
                if (u1[2] > u2[2]) {
    
    
                    return 1;
                } else if (u1[2] < u2[2]) {
    
    
                    return -1;
                }
                return 0; //相等为0
            }
        });

        return result;
    }

    public static void main(String[] args) {
    
    
        GeometryFactory gf = new GeometryFactory();
        Coordinate c1 = new Coordinate(1, 1.4);
        Coordinate c2 = new Coordinate(1, 1.5);
        Coordinate c3 = new Coordinate(1, 1.6);
        Coordinate c4 = new Coordinate(10, 100);
        Coordinate c5 = new Coordinate(100, 200);
        double dinstanceTolerance = 1;
        Geometry geometry = gf.createLineString(new Coordinate[]{
    
    c1, c2, c3, c4, c5});
        // 使用原来jar包中带的方法
        Geometry g1 = DouglasPeuckerSimplifier.simplify(geometry, dinstanceTolerance);  // 1米范围内的点抽稀掉,还可为其他值double类型
        System.out.println("g1: " + g1.getGeometryType().toString());
        System.out.println("g1Coordinates size: " + g1.getCoordinates().length);
        System.out.println("g1Coordinates: " + JSON.toJSONString(g1.getCoordinates()));


        // 使用自己写的方法
        List<double[]> coordinates = new ArrayList<>();
        coordinates.add(new double[]{
    
    1d, 1.4d});
        coordinates.add(new double[]{
    
    1d, 1.5d});
        coordinates.add(new double[]{
    
    1d, 1.6d});
        coordinates.add(new double[]{
    
    10d, 100d});
        coordinates.add(new double[]{
    
    100d, 200d});
        List<double[]> res = douglasPeucker(coordinates, 1);
        System.out.println("douglasPeucker size: " + res.size());
        System.out.println("g1Coordinates: " + JSON.toJSONString(res));
    }

}

4. Java生成线瓦片源码

package no.ecc.vectortile;

import junit.framework.TestCase;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class VectorTileDecoderTest extends TestCase {
    
    

    private GeometryFactory gf = new GeometryFactory();

    public void testLineString() throws IOException {
    
    
        Coordinate c1 = new Coordinate(1, 2);
        Coordinate c2 = new Coordinate(10, 20);
        Coordinate c3 = new Coordinate(100, 200);
        Geometry geometry = gf.createLineString(new Coordinate[]{
    
    c1, c2, c3});

        Map<String, Object> attributes = new HashMap<String, Object>();
        attributes.put("aa", "bb");
        attributes.put("cc", "bb");

        String layerName = "layer";

        VectorTileEncoder e = new VectorTileEncoder(4096, 16, false);
        e.addFeature(layerName, attributes, geometry);
        byte[] encoded = e.encode();

        VectorTileDecoder d = new VectorTileDecoder();
        assertEquals(1, d.decode(encoded).getLayerNames().size());
        assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next());

        assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes());
        assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry());
    }
}

参考

猜你喜欢

转载自blog.csdn.net/qq_40985985/article/details/112628029