【Java代码】道格拉斯-普克 Douglas-Peucker 抽稀算法分析及15w个坐标点抽稀到3.7w耗时从360s+优化到365ms接近1000倍的速度提升源码分享(并行流+多线程+泛型)
生活随笔
收集整理的這篇文章主要介紹了
【Java代码】道格拉斯-普克 Douglas-Peucker 抽稀算法分析及15w个坐标点抽稀到3.7w耗时从360s+优化到365ms接近1000倍的速度提升源码分享(并行流+多线程+泛型)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1.分析
算法詳細流程可查看《道格拉斯抽稀算法流程圖解+使用JDK8方法實現+詳細注解源碼》經典的 D-P 算法描述如下【紅色部分用于輔助理解 可忽略】:
算法耗時及問題分析:
2.優化
1?? 個算法使用的封裝類和 1?? 個業務使用封裝類用于舉例【為了簡潔注解未使用DOC規范】:
// 類1 坐標數據封裝 @Data public class PointData {// 標記是否刪除(0保留 1刪除)private int isDelete;// 數據點的 index 用于優化運算速度(核心優化點)private int indexEx;// 數據點的經度private double longitudeEx;// 數據點的緯度private double latitudeEx; }// 類2 業務查詢返回的數據封裝【主要是實現了 PointData 類】具體字段不再貼出 @Data public class Mobile2g extends PointData implements Serializable { }線程池不做過多介紹:
public class ThreadAllBaseStation {private static int corePoolSize = Runtime.getRuntime().availableProcessors();private static ThreadFactory namedFactory = new ThreadFactoryBuilder().setNameFormat("XXX數據查詢線程-%d").build();/*** corePoolSize用于指定核心線程數量* maximumPoolSize指定最大線程數* keepAliveTime和TimeUnit指定線程空閑后的最大存活時間*/public static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize + 1, 10L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1000), namedFactory, new ThreadPoolExecutor.AbortPolicy()); }優化后的算法核心代碼【為了簡潔部分注解未使用DOC規范】:
@Slf4j public class Douglas {// 點數據private static List<? extends PointData> points = new ArrayList<>();// 距離閾值(值越大效果越好但丟失細節就越多)【這個值已經算很小的了】private static final double DISTANCE_THRESHOLD = 0.00001;// 最大線程數private static final int MAX_THREAD_SIZE = 60;/*** 標記要刪除的點(并獲取分割點數據)** @param from 矢量曲線的起始點* @param to 矢量曲線的終止點* @return 起始點 終止點 分割點*/private static PointData[] markDeletePointsAndGetSplitPoint(PointData from, PointData to) {// 計算由起始點和終止點構成直線方程一般式的系數double fromLat = from.getLatitudeEx();double fromLng = from.getLongitudeEx();double toLat = to.getLatitudeEx();double toLng = to.getLongitudeEx();// 求解點到直線距離方程的參數double fromLatMinusToLat = fromLat - toLat;double fromLngMinusToLng = fromLng - toLng;double sqrtPowAddVal = Math.sqrt(Math.pow(fromLatMinusToLat, 2) + Math.pow(fromLngMinusToLng, 2));double parameterA = (fromLatMinusToLat) / sqrtPowAddVal;double parameterB = (toLng - fromLng) / sqrtPowAddVal;double parameterC = (fromLng * toLat - toLng * fromLat) / sqrtPowAddVal;// 獲取點數據下標(并排除相鄰兩點的情況)【最核心的優化】int fromIndex = from.getIndexEx();int toIndex = to.getIndexEx();if (toIndex == fromIndex + 1) {return new PointData[]{};}// 起止點之間的點到起止點連線的距離數據集合并獲取最大距離及分割點double distanceMax = 0;PointData splitPoint = null;double powAddRes = Math.pow(parameterA, 2) + Math.pow(parameterB, 2);for (int i = fromIndex + 1; i < toIndex; i++) {double lng = points.get(i).getLongitudeEx();double lat = points.get(i).getLatitudeEx();// 點到直線的距離double distance = Math.abs(parameterA * (lng) + parameterB * (lat) + parameterC) / Math.sqrt(powAddRes);if (distance > distanceMax) {distanceMax = distance;splitPoint = points.get(i);}}// 最大距離與距離閾值進行比較if (distanceMax < DISTANCE_THRESHOLD) {// 小于則標記 points(fromIndex,toIndex) 內的坐標(用于刪除)for (int i = fromIndex + 1; i < toIndex; i++) {points.get(i).setIsDelete(1);}} else {// 返回起止點和分割點坐標return new PointData[]{from, to, splitPoint};}return new PointData[]{};}/*** 進行抽稀:標記要刪除的點并篩選** @param source 矢量曲線點數據* @return 抽稀后的點數據*/public static List<? extends PointData> toDilute(List<? extends PointData> source) throws InterruptedException {// 抽稀前的點數據points = source;// 進行標記if (!CollectionUtils.isEmpty(points)) {// 首次標記long s1 = System.currentTimeMillis();PointData[] pointData = markDeletePointsAndGetSplitPoint(points.get(0), points.get(points.size() - 1));long s2 = System.currentTimeMillis();log.info("首次標記用時:" + (s2 - s1) + "ms");// 如果有分割點將進行分割標記int pointDataLength = 3;if (pointData.length == pointDataLength) {CopyOnWriteArrayList<PointData[]> splitPointList = new CopyOnWriteArrayList<>();splitPointList.add(pointData);// 使用線程池進行分割標記do {splitPointList = markDeletePointsAndGetSplitPointByThread(splitPointList);} while (!splitPointList.isEmpty());}}// 篩選掉標記為刪除的點return points.parallelStream().filter(point -> point.getIsDelete() != 1).collect(Collectors.toList());}/*** 使用多線程:標記要刪除的點(并獲取分割點數據)** @param splitPointList 起始點及分割點棧數據* @return 新的起始點及分割點棧數據* @throws InterruptedException 線程等待可能出現的問題*/private static CopyOnWriteArrayList<PointData[]> markDeletePointsAndGetSplitPointByThread(CopyOnWriteArrayList<PointData[]> splitPointList) throws InterruptedException {// 新一輪要處理的分割數據CopyOnWriteArrayList<PointData[]> splitPointListRes = new CopyOnWriteArrayList<>();int pointDataLength = 3;int splitPointListSize = splitPointList.size();log.info("當前列表Size:" + splitPointListSize);int numberBatch = MAX_THREAD_SIZE;double number = splitPointListSize * 1.0 / numberBatch;int n = ((Double) Math.ceil(number)).intValue();for (int i = 0; i < n; i++) {int end = numberBatch * (i + 1);if (end > splitPointListSize) {end = splitPointListSize;}List<PointData[]> pointData = splitPointList.subList(numberBatch * i, end);log.info("當前執行 " + numberBatch * i + " 到 " + end + "條數據");CountDownLatch countDownLatch = new CountDownLatch(pointData.size());ThreadPoolExecutor executor = ThreadAllBaseStation.executor;pointData.forEach(point -> {executor.submit(() -> {PointData[] pointDataLeft = markDeletePointsAndGetSplitPoint(point[0], point[2]);if (pointDataLeft.length == pointDataLength) {splitPointListRes.add(pointDataLeft);}PointData[] pointDataRight = markDeletePointsAndGetSplitPoint(point[2], point[1]);if (pointDataRight.length == pointDataLength) {splitPointListRes.add(pointDataRight);}countDownLatch.countDown();});});countDownLatch.await();}return splitPointListRes;} }算法使用結合業務【偽代碼】:
@OverrideDatapublic void douglasAlgorithmTest() {// 抽稀前數據查詢(坐標點需要有序)List<Mobile2g> beforeDouglas = selectData("queryParam");// 抽稀字段處理(坐標點)【優化】beforeDouglas.forEach(bd -> {bd.setIndexEx(indexEx.getAndIncrement());bd.setLongitudeEx(bd.getLongitude());bd.setLatitudeEx(bd.getLatitude());});// 抽稀后保存List<Mobile2g> afterDouglas = null;try {// 調用抽稀算法afterDouglas = (List<Mobile2g>) Douglas.toDilute(beforeDouglas);} catch (InterruptedException e) {e.printStackTrace();}}3.總結
總結
以上是生活随笔為你收集整理的【Java代码】道格拉斯-普克 Douglas-Peucker 抽稀算法分析及15w个坐标点抽稀到3.7w耗时从360s+优化到365ms接近1000倍的速度提升源码分享(并行流+多线程+泛型)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java代码】道格拉斯-普克 Doug
- 下一篇: 【Java代码】坐标系说明+WGS84\