From d0ae1a5baf919cf470d2ab2102587948623cc725 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期三, 24 十二月 2025 17:18:10 +0800
Subject: [PATCH] 将地块编号改成割草机编号+01的自增数字

---
 src/lujing/AoxinglujingNoObstacle.java |  457 ++++++++++++++++++++++----------------------------------
 1 files changed, 183 insertions(+), 274 deletions(-)

diff --git a/src/lujing/AoxinglujingNoObstacle.java b/src/lujing/AoxinglujingNoObstacle.java
index 75717ba..fe884f9 100644
--- a/src/lujing/AoxinglujingNoObstacle.java
+++ b/src/lujing/AoxinglujingNoObstacle.java
@@ -1,331 +1,240 @@
 package lujing;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * 鏃犻殰纰嶇墿鍑稿舰鑽夊湴璺緞瑙勫垝绫� (浼樺寲鐗�)
+ * 鍑稿舰鑽夊湴璺緞瑙勫垝 (鍥磋竟浼樺寲鐗�)
+ * 浼樺寲閲嶇偣锛氬洿杈瑰潗鏍囧榻愭壂鎻忚捣鐐广�佸叏璺緞杩炶疮鎬�
  */
 public class AoxinglujingNoObstacle {
 
-    // 浼樺寲锛氬紩鍏ユ瀬灏忓�肩敤浜庢诞鐐规暟姣旇緝锛屽鐞嗗嚑浣曠簿搴﹁宸�
     private static final double EPSILON = 1e-6;
 
-    /**
-     * 璺緞娈电被
-     */
-    public static class PathSegment {
-        public Point start;
-        public Point end;
-        public boolean isMowing; // true涓哄壊鑽夊伐浣滄锛宖alse涓鸿繃娓℃
-
-        public PathSegment(Point start, Point end, boolean isMowing) {
-            this.start = start;
-            this.end = end;
-            this.isMowing = isMowing;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("[%s -> %s, isMowing=%b]", start, end, isMowing);
-        }
-    }
-
-    /**
-     * 鍧愭爣鐐圭被
-     */
     public static class Point {
-        double x, y;
-
-        public Point(double x, double y) {
-            this.x = x;
-            this.y = y;
-        }
-
+        public double x, y;
+        public Point(double x, double y) { this.x = x; this.y = y; }
         @Override
-        public String toString() {
-            return String.format("(%.4f, %.4f)", x, y);
+        public String toString() { return String.format("%.6f,%.6f", x, y); }
+    }
+
+    public static class PathSegment {
+        public Point start, end;
+        public boolean isMowing;
+        public PathSegment(Point start, Point end, boolean isMowing) {
+            this.start = start; this.end = end; this.isMowing = isMowing;
         }
     }
 
     /**
-     * 瀵瑰鍏紑鐨勯潤鎬佽皟鐢ㄦ柟娉� (淇濈暀瀛楃涓插叆鍙傛牸寮�)
-     *
-     * @param boundaryCoordsStr 鍦板潡杈圭晫鍧愭爣瀛楃涓� "x1,y1;x2,y2;..."
-     * @param mowingWidthStr    鍓茶崏瀹藉害瀛楃涓诧紝濡� "0.34"
-     * @param safetyMarginStr   瀹夊叏杈硅窛瀛楃涓诧紝濡� "0.2"
-     * @return 璺緞娈靛垪琛�
+     * 瀵瑰涓绘帴鍙�
      */
     public static List<PathSegment> planPath(String boundaryCoordsStr, String mowingWidthStr, String safetyMarginStr) {
-        // 1. 瑙f瀽鍙傛暟 (浼樺寲锛氬崟鐙彁鍙栬В鏋愰�昏緫)
         List<Point> originalPolygon = parseCoords(boundaryCoordsStr);
-        double mowingWidth;
-        double safetyMargin;
+        double width = Double.parseDouble(mowingWidthStr);
+        double margin = Double.parseDouble(safetyMarginStr);
 
-        try {
-            mowingWidth = Double.parseDouble(mowingWidthStr);
-            safetyMargin = Double.parseDouble(safetyMarginStr);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException("鍓茶崏瀹藉害鎴栧畨鍏ㄨ竟璺濇牸寮忛敊璇�");
+        return planPathCore(originalPolygon, width, margin);
+    }
+
+    private static List<PathSegment> planPathCore(List<Point> originalPolygon, double width, double margin) {
+        if (originalPolygon.size() < 3) return new ArrayList<>();
+
+        // 1. 纭繚閫嗘椂閽堝苟杩涜瀹夊叏鍐呯缉
+        ensureCCW(originalPolygon);
+        List<Point> workArea = shrinkPolygon(originalPolygon, margin);
+        if (workArea.size() < 3) return new ArrayList<>();
+
+        // 2. 棰勮绠楁渶浼樿搴﹀拰濉厖璺緞鐨勭涓�涓偣
+        double bestAngle = findOptimalScanAngle(workArea);
+        Point firstScanStart = getFirstScanStartPoint(workArea, bestAngle, width);
+
+        // 3. 瀵归綈鍥磋竟璧风偣锛氳鍥磋竟鐨勬渶鍚庝竴涓偣鍒氬ソ杩炴帴鎵弿濉厖鐨勮捣鐐�
+        List<Point> alignedWorkArea = alignBoundaryToStart(workArea, firstScanStart);
+
+        List<PathSegment> finalPath = new ArrayList<>();
+
+        // 4. 銆愮涓�闃舵銆戞坊鍔犲洿杈瑰潗鏍囪矾寰�
+        for (int i = 0; i < alignedWorkArea.size(); i++) {
+            Point p1 = alignedWorkArea.get(i);
+            Point p2 = alignedWorkArea.get((i + 1) % alignedWorkArea.size());
+            finalPath.add(new PathSegment(p1, p2, true));
         }
 
-        // 2. 璋冪敤鏍稿績绠楁硶
-        return planPathCore(originalPolygon, mowingWidth, safetyMargin);
+        // 5. 銆愮浜岄樁娈点�戠敓鎴愬唴閮ㄥ紦瀛楀舰璺緞
+        // 浠庡洿杈归棴鍚堢偣锛坅lignedWorkArea.get(0)锛夊紑濮嬭繛鎺�
+        Point currentPos = alignedWorkArea.get(0);
+        List<PathSegment> zigZagLines = generateZigZagPath(workArea, bestAngle, width, currentPos);
+        
+        finalPath.addAll(zigZagLines);
+
+        return finalPath;
     }
 
     /**
-     * 鏍稿績绠楁硶閫昏緫 (寮虹被鍨嬪叆鍙傦紝渚夸簬娴嬭瘯鍜屽唴閮ㄨ皟鐢�)
+     * 瀵绘壘寮撳瓧褰㈢殑绗竴鏉$嚎鐨勮捣鐐�
      */
-    private static List<PathSegment> planPathCore(List<Point> originalPolygon, double mowingWidth, double safetyMargin) {
-        // 浼樺寲锛氫笁瑙掑舰涔熸槸鍚堟硶鐨勫嚫澶氳竟褰紝闄愬埗鏀逛负灏忎簬3
-        if (originalPolygon == null || originalPolygon.size() < 3) {
-            throw new IllegalArgumentException("澶氳竟褰㈠潗鏍囩偣涓嶈冻3涓紝鏃犳硶鏋勬垚鏈夋晥鍖哄煙");
-        }
-
-        // 纭繚澶氳竟褰㈡槸閫嗘椂閽堟柟鍚�
-        ensureCCW(originalPolygon);
-
-        // 1. 鏍规嵁瀹夊叏杈硅窛鍐呯缉
-        List<Point> shrunkPolygon = shrinkPolygon(originalPolygon, safetyMargin);
-
-        // 浼樺寲锛氬唴缂╁悗濡傛灉澶氳竟褰㈠け鏁堬紙渚嬪鍦板潡澶獎锛屽唴缂╁悗娑堝け锛夛紝鐩存帴杩斿洖绌鸿矾寰勶紝閬垮厤鍚庣画鎶ラ敊
-        if (shrunkPolygon.size() < 3) {
-            // 杩欓噷鍙互璁板綍鏃ュ織锛氬湴鍧楄繃灏忥紝鏃犳硶婊¤冻瀹夊叏璺濈浣滀笟
-            return new ArrayList<>();
-        }
-
-        // 2. 璁$畻鏈�闀胯竟瑙掑害 (浣滀负鎵弿鏂瑰悜)
-        double angle = calculateLongestEdgeAngle(originalPolygon);
-
-        // 3. & 4. 鏃嬭浆鎵弿骞跺壀瑁�
-        List<PathSegment> mowingLines = generateClippedMowingLines(shrunkPolygon, angle, mowingWidth);
-
-        // 5. 寮撳瓧褰㈣繛鎺�
-        return connectPathSegments(mowingLines);
-    }
-
-    // ================= 鏍稿績绠楁硶杈呭姪鏂规硶 =================
-
-    private static List<Point> shrinkPolygon(List<Point> polygon, double margin) {
-        List<Point> newPoints = new ArrayList<>();
-        int n = polygon.size();
-
-        for (int i = 0; i < n; i++) {
-            Point p1 = polygon.get(i);
-            Point p2 = polygon.get((i + 1) % n);
-            Point p0 = polygon.get((i - 1 + n) % n);
-
-            Line line1 = offsetLine(p1, p2, margin);
-            Line line0 = offsetLine(p0, p1, margin);
-
-            Point intersection = getIntersection(line0, line1);
-            
-            // 浼樺寲锛氬鍔犻潪绌哄垽鏂紝涓斿鏋滀氦鐐瑰紓甯歌繙锛堝皷瑙掓晥搴旓級锛屽疄闄呭伐绋嬩腑鍙兘闇�瑕佸垏瑙掑鐞�
-            // 杩欓噷鏆備繚鐣欏熀纭�閫昏緫锛屼絾鍦ㄥ嚫澶氳竟褰腑閫氬父娌¢棶棰�
-            if (intersection != null) {
-                newPoints.add(intersection);
-            }
-        }
-        return newPoints;
-    }
-
-    private static double calculateLongestEdgeAngle(List<Point> polygon) {
-        double maxDistSq = -1;
-        double angle = 0;
-        int n = polygon.size();
-
-        for (int i = 0; i < n; i++) {
-            Point p1 = polygon.get(i);
-            Point p2 = polygon.get((i + 1) % n);
-            double dx = p2.x - p1.x;
-            double dy = p2.y - p1.y;
-            double distSq = dx * dx + dy * dy;
-
-            if (distSq > maxDistSq) {
-                maxDistSq = distSq;
-                angle = Math.atan2(dy, dx);
-            }
-        }
-        return angle;
-    }
-
-    private static List<PathSegment> generateClippedMowingLines(List<Point> polygon, double angle, double width) {
-        List<PathSegment> segments = new ArrayList<>();
-        
-        // 鏃嬭浆鑷虫按骞�
-        List<Point> rotatedPoly = rotatePolygon(polygon, -angle);
-
+    private static Point getFirstScanStartPoint(List<Point> polygon, double angle, double width) {
+        List<Point> rotated = rotatePolygon(polygon, -angle);
         double minY = Double.MAX_VALUE;
-        double maxY = -Double.MAX_VALUE;
-        for (Point p : rotatedPoly) {
-            if (p.y < minY) minY = p.y;
-            if (p.y > maxY) maxY = p.y;
-        }
+        for (Point p : rotated) minY = Math.min(minY, p.y);
 
-        // 浼樺寲锛氳捣濮嬫壂鎻忕嚎澧炲姞涓�涓井灏忕殑鍋忕Щ閲� EPSILON
-        // 閬垮厤鎵弿绾挎濂借惤鍦ㄩ《鐐逛笂锛屽鑷翠氦鐐瑰垽鏂�昏緫鍑虹幇浜屼箟鎬�
-        double currentY = minY + width / 2.0 + EPSILON;
-
-        while (currentY < maxY) {
-            List<Double> xIntersections = new ArrayList<>();
-            int n = rotatedPoly.size();
-            
-            for (int i = 0; i < n; i++) {
-                Point p1 = rotatedPoly.get(i);
-                Point p2 = rotatedPoly.get((i + 1) % n);
-
-                // 浼樺寲锛氬拷鐣ユ按骞崇嚎娈� (p1.y == p2.y)锛岄伩鍏嶉櫎闆堕敊璇紝姘村钩绾挎涓嶅弬涓庡瀭鐩存壂鎻忕嚎姹備氦
-                if (Math.abs(p1.y - p2.y) < EPSILON) continue;
-
-                // 鍒ゆ柇绾挎鏄惁璺ㄨ秺 currentY
-                // 浣跨敤涓ユ牸鐨勪笉绛夊紡閫昏緫閰嶅悎鑼冨洿鍒ゆ柇
-                double minP = Math.min(p1.y, p2.y);
-                double maxP = Math.max(p1.y, p2.y);
-
-                if (currentY >= minP && currentY < maxP) {
-                    // 绾挎�ф彃鍊兼眰X
-                    double x = p1.x + (currentY - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
-                    xIntersections.add(x);
-                }
-            }
-
-            Collections.sort(xIntersections);
-
-            // 鎻愬彇绾挎锛岄�氬父鏄垚瀵瑰嚭鐜�
-            if (xIntersections.size() >= 2) {
-                // 鍙栨渶宸﹀拰鏈�鍙崇偣杩炴帴锛堝簲瀵瑰彲鑳藉嚭鐜扮殑寰皬璁$畻璇樊瀵艰嚧鐨勫涓氦鐐癸級
-                double xStart = xIntersections.get(0);
-                double xEnd = xIntersections.get(xIntersections.size() - 1);
-
-                // 鍙湁褰撶嚎娈甸暱搴﹀ぇ浜庢瀬灏忓�兼椂鎵嶆坊鍔狅紝閬垮厤鐢熸垚鍣偣璺緞
-                if (xEnd - xStart > EPSILON) {
-                    Point rStart = rotatePoint(new Point(xStart, currentY), angle); // 杩欓噷鐨刢urrentY瀹為檯涓婂甫浜唀psilon锛岃繕鍘熸椂娌¢棶棰�
-                    Point rEnd = rotatePoint(new Point(xEnd, currentY), angle);
-                    segments.add(new PathSegment(rStart, rEnd, true));
-                }
-            }
-
-            currentY += width;
-        }
-
-        return segments;
+        double startY = minY + width + EPSILON;
+        List<Double> xIntersections = getXIntersections(rotated, startY);
+        if (xIntersections.isEmpty()) return polygon.get(0);
+        
+        Collections.sort(xIntersections);
+        return rotatePoint(new Point(xIntersections.get(0), startY), angle);
     }
 
-    private static List<PathSegment> connectPathSegments(List<PathSegment> lines) {
+    /**
+     * 閲嶇粍澶氳竟褰㈤《鐐癸紝浣垮緱绱㈠紩0鐨勭偣鏈�闈犺繎濉厖璧风偣
+     */
+    private static List<Point> alignBoundaryToStart(List<Point> polygon, Point target) {
+        int bestIdx = 0;
+        double minDist = Double.MAX_VALUE;
+        for (int i = 0; i < polygon.size(); i++) {
+            double d = Math.hypot(polygon.get(i).x - target.x, polygon.get(i).y - target.y);
+            if (d < minDist) {
+                minDist = d;
+                bestIdx = i;
+            }
+        }
+        List<Point> aligned = new ArrayList<>();
+        for (int i = 0; i < polygon.size(); i++) {
+            aligned.add(polygon.get((bestIdx + i) % polygon.size()));
+        }
+        return aligned;
+    }
+
+    private static List<PathSegment> generateZigZagPath(List<Point> polygon, double angle, double width, Point startPoint) {
         List<PathSegment> result = new ArrayList<>();
-        if (lines.isEmpty()) return result;
+        List<Point> rotated = rotatePolygon(polygon, -angle);
 
-        for (int i = 0; i < lines.size(); i++) {
-            PathSegment currentLine = lines.get(i);
-            Point actualStart, actualEnd;
+        double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE;
+        for (Point p : rotated) {
+            minY = Math.min(minY, p.y);
+            maxY = Math.max(maxY, p.y);
+        }
 
-            // 寮撳瓧褰㈣鍒掞細鍋舵暟琛屾鍚戯紝濂囨暟琛屽弽鍚�
-            if (i % 2 == 0) {
-                actualStart = currentLine.start;
-                actualEnd = currentLine.end;
-            } else {
-                actualStart = currentLine.end;
-                actualEnd = currentLine.start;
+        Point currentPos = startPoint;
+        boolean leftToRight = true;
+        // 璧风偣浠� minY + width 寮�濮嬶紝鍥犱负杈圭紭宸茬粡鍥磋竟鍓茶繃
+        for (double y = minY + width; y < maxY - width / 2; y += width) {
+            List<Double> xInt = getXIntersections(rotated, y);
+            if (xInt.size() < 2) continue;
+            Collections.sort(xInt);
+
+            double xStart = leftToRight ? xInt.get(0) : xInt.get(xInt.size() - 1);
+            double xEnd = leftToRight ? xInt.get(xInt.size() - 1) : xInt.get(0);
+
+            Point pS = rotatePoint(new Point(xStart, y), angle);
+            Point pE = rotatePoint(new Point(xEnd, y), angle);
+
+            // 娣诲姞杩囨浮娈� (濡傛灉鏄粠鍥磋竟鍒囨崲杩囨潵鎴栬�呮崲琛�)
+            if (Math.hypot(currentPos.x - pS.x, currentPos.y - pS.y) > 0.05) {
+                result.add(new PathSegment(currentPos, pS, false));
             }
-
-            // 娣诲姞杩囨浮娈�
-            if (i > 0) {
-                Point prevEnd = result.get(result.size() - 1).end;
-                // 鍙湁褰撹窛绂荤‘瀹炲瓨鍦ㄦ椂鎵嶆坊鍔犺繃娓℃锛堥伩鍏嶉噸鍚堢偣锛�
-                if (distanceSq(prevEnd, actualStart) > EPSILON) {
-                    result.add(new PathSegment(prevEnd, actualStart, false));
-                }
-            }
-
-            result.add(new PathSegment(actualStart, actualEnd, true));
+            result.add(new PathSegment(pS, pE, true));
+            currentPos = pE;
+            leftToRight = !leftToRight;
         }
         return result;
     }
 
-    // ================= 鍩虹鍑犱綍宸ュ叿 (浼樺寲鐗�) =================
-
-    private static List<Point> parseCoords(String s) {
-        List<Point> list = new ArrayList<>();
-        if (s == null || s.trim().isEmpty()) return list;
-        
-        String[] parts = s.split(";");
-        for (String part : parts) {
-            String[] xy = part.split(",");
-            if (xy.length >= 2) {
-                try {
-                    double x = Double.parseDouble(xy[0].trim());
-                    double y = Double.parseDouble(xy[1].trim());
-                    list.add(new Point(x, y));
-                } catch (NumberFormatException e) {
-                    // 蹇界暐鏍煎紡閿欒鐨勫崟涓偣
-                }
+    private static List<Double> getXIntersections(List<Point> rotatedPoly, double y) {
+        List<Double> xIntersections = new ArrayList<>();
+        int n = rotatedPoly.size();
+        for (int i = 0; i < n; i++) {
+            Point p1 = rotatedPoly.get(i);
+            Point p2 = rotatedPoly.get((i + 1) % n);
+            if ((p1.y <= y && p2.y > y) || (p2.y <= y && p1.y > y)) {
+                double x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
+                xIntersections.add(x);
             }
         }
-        return list;
+        return xIntersections;
     }
 
-    // Shoelace鍏紡鍒ゆ柇鏂瑰悜骞惰皟鏁�
-    private static void ensureCCW(List<Point> polygon) {
-        double sum = 0;
+    // --- 鍑犱綍鍩虹宸ュ叿 ---
+
+    private static List<Point> shrinkPolygon(List<Point> polygon, double margin) {
+        List<Point> result = new ArrayList<>();
+        int n = polygon.size();
+        for (int i = 0; i < n; i++) {
+            Point pPrev = polygon.get((i - 1 + n) % n);
+            Point pCurr = polygon.get(i);
+            Point pNext = polygon.get((i + 1) % n);
+
+            double d1x = pCurr.x - pPrev.x, d1y = pCurr.y - pPrev.y;
+            double l1 = Math.hypot(d1x, d1y);
+            double d2x = pNext.x - pCurr.x, d2y = pNext.y - pCurr.y;
+            double l2 = Math.hypot(d2x, d2y);
+
+            double n1x = -d1y / l1, n1y = d1x / l1;
+            double n2x = -d2y / l2, n2y = d2x / l2;
+
+            double bx = n1x + n2x, by = n1y + n2y;
+            double bLen = Math.hypot(bx, by);
+            if (bLen < EPSILON) { bx = n1x; by = n1y; } 
+            else { bx /= bLen; by /= bLen; }
+
+            double cosHalf = n1x * bx + n1y * by;
+            double d = margin / Math.max(cosHalf, 0.1);
+            result.add(new Point(pCurr.x + bx * d, pCurr.y + by * d));
+        }
+        return result;
+    }
+
+    private static double findOptimalScanAngle(List<Point> polygon) {
+        double minH = Double.MAX_VALUE;
+        double bestA = 0;
         for (int i = 0; i < polygon.size(); i++) {
-            Point p1 = polygon.get(i);
-            Point p2 = polygon.get((i + 1) % polygon.size());
-            sum += (p2.x - p1.x) * (p2.y + p1.y);
+            Point p1 = polygon.get(i), p2 = polygon.get((i + 1) % polygon.size());
+            double angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
+            double h = calculatePolygonHeightAtAngle(polygon, angle);
+            if (h < minH) { minH = h; bestA = angle; }
         }
-        // 鍋囪鏍囧噯绗涘崱灏斿潗鏍囩郴锛宻um > 0 涓洪『鏃堕拡锛岄渶瑕佸弽杞负閫嗘椂閽�
-        if (sum > 0) {
-            Collections.reverse(polygon);
+        return bestA;
+    }
+
+    private static double calculatePolygonHeightAtAngle(List<Point> poly, double angle) {
+        double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE;
+        double sin = Math.sin(-angle), cos = Math.cos(-angle);
+        for (Point p : poly) {
+            double ry = p.x * sin + p.y * cos;
+            minY = Math.min(minY, ry); maxY = Math.max(maxY, ry);
         }
-    }
-
-    private static class Line {
-        double a, b, c; 
-        public Line(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
-    }
-
-    private static Line offsetLine(Point p1, Point p2, double dist) {
-        double dx = p2.x - p1.x;
-        double dy = p2.y - p1.y;
-        double len = Math.sqrt(dx * dx + dy * dy);
-        
-        // 闃叉闄や互0
-        if (len < EPSILON) return new Line(0, 0, 0);
-
-        double nx = -dy / len;
-        double ny = dx / len;
-
-        double newX = p1.x + nx * dist;
-        double newY = p1.y + ny * dist;
-
-        double a = -dy;
-        double b = dx;
-        double c = -a * newX - b * newY;
-        return new Line(a, b, c);
-    }
-
-    private static Point getIntersection(Line l1, Line l2) {
-        double det = l1.a * l2.b - l2.a * l1.b;
-        if (Math.abs(det) < EPSILON) return null; // 骞宠
-        double x = (l1.b * l2.c - l2.b * l1.c) / det;
-        double y = (l2.a * l1.c - l1.a * l2.c) / det;
-        return new Point(x, y);
+        return maxY - minY;
     }
 
     private static Point rotatePoint(Point p, double angle) {
-        double cos = Math.cos(angle);
-        double sin = Math.sin(angle);
-        return new Point(p.x * cos - p.y * sin, p.x * sin + p.y * cos);
+        double c = Math.cos(angle), s = Math.sin(angle);
+        return new Point(p.x * c - p.y * s, p.x * s + p.y * c);
     }
 
     private static List<Point> rotatePolygon(List<Point> poly, double angle) {
         List<Point> res = new ArrayList<>();
-        for (Point p : poly) {
-            res.add(rotatePoint(p, angle));
-        }
+        for (Point p : poly) res.add(rotatePoint(p, angle));
         return res;
     }
-    
-    private static double distanceSq(Point p1, Point p2) {
-        return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
+
+    private static void ensureCCW(List<Point> poly) {
+        double s = 0;
+        for (int i = 0; i < poly.size(); i++) {
+            Point p1 = poly.get(i), p2 = poly.get((i + 1) % poly.size());
+            s += (p2.x - p1.x) * (p2.y + p1.y);
+        }
+        if (s > 0) Collections.reverse(poly);
+    }
+
+    private static List<Point> parseCoords(String s) {
+        List<Point> list = new ArrayList<>();
+        for (String p : s.split(";")) {
+            String[] xy = p.split(",");
+            if (xy.length >= 2) list.add(new Point(Double.parseDouble(xy[0]), Double.parseDouble(xy[1])));
+        }
+        return list;
     }
 }
\ No newline at end of file

--
Gitblit v1.10.0