package bianjie; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * 自动化边界优化工具类 - 自适应增强版 * 目标:95%+ 还原度,自动识别直线与曲线,最少化点数 */ public class Bianjieyouhuatoxy { private static final double EARTH_RADIUS = 6378137.0; // 基准阈值配置 private static final double MIN_DIST_THRESHOLD = 0.03; // 最小去重位移(3cm) private static final double BASE_EPSILON = 0.05; // 基础共线误差(5cm) private static final double MAX_EPSILON = 0.15; // 直线段最大容忍误差(15cm) private static class PointXY { double x, y; public PointXY(double x, double y) { this.x = x; this.y = y; } } /** * 静态入口方法 * @param originStr "3949.89151752,N,11616.79267501,E" * @param boundaryStr "(lat1,lon1,alt1;...)" */ public static String optimizeBoundary(String originStr, String boundaryStr) { try { // 1. 解析参考原点 double[] originDD = parseNmeaToDD(originStr); double originLat = originDD[0]; double originLon = originDD[1]; // 2. 坐标投影比例 double metersPerDegLat = Math.PI * EARTH_RADIUS / 180.0; double metersPerDegLon = metersPerDegLat * Math.cos(Math.toRadians(originLat)); // 3. 数据清洗与初步投影 List rawPoints = extractPoints(boundaryStr, originLat, originLon, metersPerDegLat, metersPerDegLon); if (rawPoints.size() < 3) return formatOutput(rawPoints); // 4. 第一步:静态滤波(去除物理意义上的重复点) List filteredPoints = staticFilter(rawPoints); // 5. 第二步:自适应特征识别压缩 (核心算法) List optimizedPoints = adaptiveSimplify(filteredPoints); return formatOutput(optimizedPoints); } catch (Exception e) { return "ERROR: " + e.getMessage(); } } /** * 自适应简化算法 * 原理:计算当前点前后的向量夹角,夹角越接近180度(直线),容忍度越高; * 夹角越小(急转弯),容忍度越低。 */ private static List adaptiveSimplify(List points) { if (points.size() < 3) return points; List result = new ArrayList<>(); result.add(points.get(0)); int anchorIdx = 0; // 锚点(线段起点) for (int i = 1; i < points.size() - 1; i++) { PointXY pA = points.get(anchorIdx); PointXY pB = points.get(i); PointXY pC = points.get(i + 1); // 1. 计算点B到AC直线的垂直距离 double dist = getVerticalDistance(pB, pA, pC); // 2. 计算自适应阈值:分析特征 // 计算向量 AB 和 BC 的夹角余弦值 double cosTheta = calculateCosine(pA, pB, pC); // 动态阈值逻辑: // 如果 cosTheta 接近 -1 (即方向几乎没变,180度),使用 MAX_EPSILON // 如果 cosTheta 偏离较大 (转弯),使用 BASE_EPSILON double dynamicEpsilon = BASE_EPSILON + (MAX_EPSILON - BASE_EPSILON) * Math.abs(cosTheta + 1); // 针对割草机特征优化:夹角越锐利,阈值越小,确保拐角不被削平 if (cosTheta > -0.7) { dynamicEpsilon = MIN_DIST_THRESHOLD; // 疑似急转弯,强制保留特征 } if (dist > dynamicEpsilon) { result.add(pB); anchorIdx = i; } } result.add(points.get(points.size() - 1)); return result; } private static double getVerticalDistance(PointXY p, PointXY start, PointXY end) { double area = Math.abs(0.5 * (start.x * end.y + end.x * p.y + p.x * start.y - end.x * start.y - p.x * end.y - start.x * p.y)); double bottom = Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2)); return (bottom == 0) ? 0 : (2.0 * area) / bottom; } private static double calculateCosine(PointXY a, PointXY b, PointXY c) { double v1x = b.x - a.x; double v1y = b.y - a.y; double v2x = c.x - b.x; double v2y = c.y - b.y; double mag1 = Math.sqrt(v1x*v1x + v1y*v1y); double mag2 = Math.sqrt(v2x*v2x + v2y*v2y); if (mag1 == 0 || mag2 == 0) return -1; return (v1x*v2x + v1y*v2y) / (mag1 * mag2); } private static List staticFilter(List input) { List res = new ArrayList<>(); res.add(input.get(0)); for (int i = 1; i < input.size(); i++) { PointXY cur = input.get(i); PointXY last = res.get(res.size() - 1); if (Math.sqrt(Math.pow(cur.x-last.x,2) + Math.pow(cur.y-last.y,2)) > MIN_DIST_THRESHOLD) { res.add(cur); } } return res; } private static double[] parseNmeaToDD(String nmea) { String clean = nmea.replaceAll("[()\\s]", ""); String[] p = clean.split("[,,]"); // 兼容原点格式 (3949.89,N,11616.79,E) 或 (3949.89,11616.79) double lat = dmToDd(Double.parseDouble(p[0])); double lon = (p.length >= 3) ? dmToDd(Double.parseDouble(p[2])) : dmToDd(Double.parseDouble(p[1])); return new double[]{lat, lon}; } private static double dmToDd(double dm) { int d = (int)(dm / 100); return d + (dm - d * 100) / 60.0; } private static List extractPoints(String raw, double oLat, double oLon, double mLat, double mLon) { List list = new ArrayList<>(); String clean = raw.replaceAll("[()\\s]", ""); for (String segment : clean.split(";")) { String[] p = segment.split("[,,]"); if (p.length < 2) continue; double lat = dmToDd(Double.parseDouble(p[0])); double lon = dmToDd(Double.parseDouble(p[1])); list.add(new PointXY((lon - oLon) * mLon, (lat - oLat) * mLat)); } return list; } private static String formatOutput(List points) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < points.size(); i++) { sb.append(String.format(Locale.US, "%.3f,%.3f", points.get(i).x, points.get(i).y)); if (i < points.size() - 1) sb.append(";"); } return sb.toString(); } /** * 根据优化后的坐标字符串计算地块面积 * @param optimizedXYStr 格式为 "X0,Y0;X1,Y1;X2,Y2..." 的字符串 * @return 面积字符串,单位平方米(保留两位小数) */ public static String calculateArea(String optimizedXYStr) { if (optimizedXYStr == null || optimizedXYStr.isEmpty()) { return "0.00"; } List points = new ArrayList<>(); try { String[] pairs = optimizedXYStr.split(";"); for (String pair : pairs) { String[] coords = pair.split(","); if (coords.length == 2) { points.add(new PointXY( Double.parseDouble(coords[0]), Double.parseDouble(coords[1]) )); } } if (points.size() < 3) return "0.00"; // 使用鞋带公式 (Shoelace Formula) 计算多边形面积 double area = 0.0; int n = points.size(); for (int i = 0; i < n; i++) { PointXY p1 = points.get(i); PointXY p2 = points.get((i + 1) % n); // 自动闭合到起点 // 核心计算:(x1*y2 - x2*y1) area += (p1.x * p2.y) - (p2.x * p1.y); } area = Math.abs(area) / 2.0; // 格式化输出,保留两位小数 return String.format(Locale.US, "%.2f", area); } catch (Exception e) { e.printStackTrace(); return "0.00"; } } /** * 根据优化后的坐标字符串计算边界点数 * @param optimizedXYStr 格式为 "X0,Y0;X1,Y1;X2,Y2..." 的字符串 * @return 边界点数 */ public static int calculatePointCount(String optimizedXYStr) { if (optimizedXYStr == null || optimizedXYStr.isEmpty()) { return 0; } try { String[] pairs = optimizedXYStr.split(";"); int count = 0; for (String pair : pairs) { String[] coords = pair.split(","); if (coords.length == 2) { count++; } } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } }