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<PointXY> rawPoints = extractPoints(boundaryStr, originLat, originLon, metersPerDegLat, metersPerDegLon);
|
if (rawPoints.size() < 3) return formatOutput(rawPoints);
|
|
// 4. 第一步:静态滤波(去除物理意义上的重复点)
|
List<PointXY> filteredPoints = staticFilter(rawPoints);
|
|
// 5. 第二步:自适应特征识别压缩 (核心算法)
|
List<PointXY> optimizedPoints = adaptiveSimplify(filteredPoints);
|
|
return formatOutput(optimizedPoints);
|
} catch (Exception e) {
|
return "ERROR: " + e.getMessage();
|
}
|
}
|
|
/**
|
* 自适应简化算法
|
* 原理:计算当前点前后的向量夹角,夹角越接近180度(直线),容忍度越高;
|
* 夹角越小(急转弯),容忍度越低。
|
*/
|
private static List<PointXY> adaptiveSimplify(List<PointXY> points) {
|
if (points.size() < 3) return points;
|
|
List<PointXY> 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<PointXY> staticFilter(List<PointXY> input) {
|
List<PointXY> 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<PointXY> extractPoints(String raw, double oLat, double oLon, double mLat, double mLon) {
|
List<PointXY> 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<PointXY> 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<PointXY> 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;
|
}
|
}
|
}
|