From 2d2ca9f808a0c0e5d68f0c92430958af905307e7 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Sun, 11 Mar 2018 16:50:27 +0100
Subject: [PATCH] Add new validation test for broken circle buildings.

---
 .../josm/data/validation/OsmValidator.java         |  2 +
 .../data/validation/tests/BrokenCircleShape.java   | 86 ++++++++++++++++++++++
 2 files changed, 88 insertions(+)
 create mode 100644 src/org/openstreetmap/josm/data/validation/tests/BrokenCircleShape.java

diff --git a/src/org/openstreetmap/josm/data/validation/OsmValidator.java b/src/org/openstreetmap/josm/data/validation/OsmValidator.java
index a173b49ef..21aa850be 100644
--- a/src/org/openstreetmap/josm/data/validation/OsmValidator.java
+++ b/src/org/openstreetmap/josm/data/validation/OsmValidator.java
@@ -33,6 +33,7 @@ import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
 import org.openstreetmap.josm.data.validation.tests.Addresses;
 import org.openstreetmap.josm.data.validation.tests.ApiCapabilitiesTest;
 import org.openstreetmap.josm.data.validation.tests.BarriersEntrances;
+import org.openstreetmap.josm.data.validation.tests.BrokenCircleShape;
 import org.openstreetmap.josm.data.validation.tests.Coastlines;
 import org.openstreetmap.josm.data.validation.tests.ConditionalKeys;
 import org.openstreetmap.josm.data.validation.tests.CrossingWays;
@@ -144,6 +145,7 @@ public final class OsmValidator {
         PublicTransportRouteTest.class, // 3600 .. 3699
         RightAngleBuildingTest.class, // 3700 .. 3799
         BuildingOutsideResidentialArea.class, // 3800 .. 3899
+        BrokenCircleShape.class, // 3900 .. 3999
     };
 
     /**
diff --git a/src/org/openstreetmap/josm/data/validation/tests/BrokenCircleShape.java b/src/org/openstreetmap/josm/data/validation/tests/BrokenCircleShape.java
new file mode 100644
index 000000000..a306c70e8
--- /dev/null
+++ b/src/org/openstreetmap/josm/data/validation/tests/BrokenCircleShape.java
@@ -0,0 +1,86 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.List;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * Checks for circular buildings that have a deformed shape.
+ *
+ * @since xxx
+ */
+public class BrokenCircleShape extends Test {
+  /**
+   * Constructs a new {@code RightAngleBuildingTest} test.
+   */
+  public BrokenCircleShape() {
+    super(tr("Broken circle shape"),
+        tr("Checks for circular buildings that have a deformed shape."));
+  }
+
+  @Override
+  public void visit(Way w) {
+    if (!w.isUsable() || !w.isClosed() || !isBuilding(w))
+      return;
+
+    int count = w.getNodesCount() - 1;
+    if (count <= 6)
+      return;
+
+    /* Identify that the shape has most of nodes organized in a circle.  */
+    double circleAngle = 180 * (count - 2) / count;
+
+    int matches = 0;
+    double maximalAngleDifference = Config.getPref().getDouble("validator.BrokenCircleShape.maximalAngleDifference", 5);
+    for (Pair<Double, Node> pair: w.getAngles())
+      if (Math.abs(pair.a - circleAngle) < maximalAngleDifference)
+        matches++;
+
+    /* Continue if most of the angles are fine.  */
+    if (matches < count * 0.6)
+      return;
+
+    double minRatio = Config.getPref().getDouble("validator.BrokenCircleShape.minimalRatio", 0.1);
+
+    /* Remove the last (closing) node of way. **/
+    List<Node> nodes = w.getNodes ();
+    nodes.remove (nodes.size () - 1);
+
+    double cx = 0;
+    double cy = 0;
+
+    for (Node n: nodes) {
+      cx += n.getEastNorth().east();
+      cy += n.getEastNorth().north();
+    }
+
+    Node center = new Node (new EastNorth(cx / count, cy / count));
+
+    double maxDistance = 0;
+    double minDistance = Double.MAX_VALUE;
+
+    for (Node n: nodes) {
+      double distance = center.getEastNorth ().distance (n.getEastNorth ());
+      if (distance > maxDistance)
+        maxDistance = distance;
+      if (distance < minDistance)
+        minDistance = distance;
+    }
+
+    if ((1.0 - minDistance / maxDistance) > minRatio)
+      errors.add(TestError.builder(this, Severity.WARNING, 3901)
+          .message(tr("Broken circular shape"))
+          .primitives(w)
+          .build());
+  }
+}
-- 
2.16.3

