diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/CheckId.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/CheckId.java index 16b967ae36a7bcdc9ac74ae7641a357faab27f46..d45fc4890a5fcf1c7cfed3d09fc0a00c67049126 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/CheckId.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/CheckId.java @@ -37,6 +37,7 @@ public class CheckId implements Serializable { public static final CheckId C_GE_R_NULL_AREA = new CheckId("C_GE_R_NULL_AREA"); public static final CheckId C_GE_S_NON_MANIFOLD_EDGE = new CheckId("C_GE_S_NON_MANIFOLD_EDGE"); public static final CheckId C_GE_S_POLYGON_WRONG_ORIENTATION = new CheckId("C_GE_S_POLYGON_WRONG_ORIENTATION"); + public static final CheckId C_GE_S_INTERIOR_FACE = new CheckId("C_GE_S_INTERIOR_FACE"); public static final CheckId C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION = new CheckId( "C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION"); public static final CheckId C_GE_S_NON_MANIFOLD_VERTEX = new CheckId("C_GE_S_NON_MANIFOLD_VERTEX"); diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/ErrorId.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/ErrorId.java index 4b9045e2d81e957fc1f332bc5e563c4abec06c51..5d4370f905dba485e8a984a608614d1ba25e2f7f 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/ErrorId.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/ErrorId.java @@ -47,6 +47,7 @@ public class ErrorId implements Serializable { public static final ErrorId GE_R_NULL_AREA = new ErrorId("GE_R_NULL_AREA"); public static final ErrorId GE_R_TOO_FEW_POINTS = new ErrorId("GE_R_TOO_FEW_POINTS"); public static final ErrorId GE_S_NON_MANIFOLD_EDGE = new ErrorId("GE_S_NON_MANIFOLD_EDGE"); + public static final ErrorId GE_S_INTERIOR_FACE = new ErrorId("GE_S_INTERIOR_FACE"); public static final ErrorId GE_P_NON_PLANAR_POLYGON_NORMALS_DEVIATION = new ErrorId( "GE_P_NON_PLANAR_POLYGON_NORMALS_DEVIATION"); public static final ErrorId GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE = new ErrorId( diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/Requirement.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/Requirement.java index 82c76162a00c1559fd52d1873c9a023e021513e1..c744f4e9f6873b5827fd3349e48e4b7f6be5bbb1 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/Requirement.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/Requirement.java @@ -47,6 +47,7 @@ public class Requirement implements Serializable { public static final Requirement R_GE_P_INNER_RINGS_NESTED = new Requirement("R_GE_P_INNER_RINGS_NESTED", RequirementType.GEOMETRY); public static final Requirement R_GE_S_TOO_FEW_POLYGONS = new Requirement("R_GE_S_TOO_FEW_POLYGONS", RequirementType.GEOMETRY); public static final Requirement R_GE_S_NOT_CLOSED = new Requirement("R_GE_S_NOT_CLOSED", RequirementType.GEOMETRY); + public static final Requirement R_GE_S_INTERIOR_FACE = new Requirement("R_GE_S_INTERIOR_FACE", RequirementType.GEOMETRY); public static final Requirement R_GE_S_NON_MANIFOLD_EDGE = new Requirement("R_GE_S_NON_MANIFOLD_EDGE", RequirementType.GEOMETRY); public static final Requirement R_GE_S_POLYGON_WRONG_ORIENTATION = new Requirement( "R_GE_S_POLYGON_WRONG_ORIENTATION", RequirementType.GEOMETRY); diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/error/InteriorFaceError.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/error/InteriorFaceError.java new file mode 100644 index 0000000000000000000000000000000000000000..743ba00d0b870801e0d5b8798a340fc46dc5bf07 --- /dev/null +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/error/InteriorFaceError.java @@ -0,0 +1,81 @@ +package de.hft.stuttgart.citydoctor2.check.error; + +import java.util.List; + +import de.hft.stuttgart.citydoctor2.check.CheckError; +import de.hft.stuttgart.citydoctor2.check.ErrorId; +import de.hft.stuttgart.citydoctor2.check.ErrorReport; +import de.hft.stuttgart.citydoctor2.check.ErrorType; +import de.hft.stuttgart.citydoctor2.check.ErrorVisitor; +import de.hft.stuttgart.citydoctor2.check.HealingMethod; +import de.hft.stuttgart.citydoctor2.check.ModificationListener; +import de.hft.stuttgart.citydoctor2.datastructure.Geometry; +import de.hft.stuttgart.citydoctor2.datastructure.GmlElement; +import de.hft.stuttgart.citydoctor2.datastructure.Polygon; + + +/** + * If a polygon is fully inside a solid geometry this error is created. + * + * @author Alexander Riegel + */ +public class InteriorFaceError implements CheckError{ + + + private static final long serialVersionUID = 6633158315015689806L; + + private List polygons; + private Geometry geom; + + + + public InteriorFaceError(Geometry geom, List polygons) { + super(); + this.polygons = polygons; + this.geom = geom; + } + + public Geometry getGeometry() { + return geom; + } + + public List getPolygons(){ + return polygons; + } + @Override + public ErrorType getType() { + return ErrorType.ERROR; + } + + @Override + public ErrorId getErrorId() { + return ErrorId.GE_S_INTERIOR_FACE; + } + + @Override + public GmlElement getFeature() { + return getGeometry(); + } + + @Override + public void accept(ErrorVisitor errorVisitor) { + errorVisitor.visit(this); + } + + public String toString() { + return "InteriorFaceError [Polygons=" + polygons + ", geom=" + geom + "]"; + } + + @Override + public boolean accept(HealingMethod method, ModificationListener l) { + return method.visit(this, l); + } + + @Override + public void report(ErrorReport report) { + report.add(geom); + polygons.forEach(x -> report.add(x)); + + } + +} diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/Checks.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/Checks.java index aa9ce41d27dedf7c13283001b25a0fceb639592c..1d872328c77fccaa39c1ddc2c21d003432a5edd4 100644 --- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/Checks.java +++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/Checks.java @@ -34,6 +34,7 @@ import de.hft.stuttgart.citydoctor2.checks.geometry.AllPolygonsWrongOrientationC import de.hft.stuttgart.citydoctor2.checks.geometry.DuplicatePointsCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.HoleOutsideCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.InteriorDisconnectedCheck; +import de.hft.stuttgart.citydoctor2.checks.geometry.InteriorFacesCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.ManifoldVertexCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.MultipleConnectedComponentCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck; @@ -102,6 +103,7 @@ public class Checks { publish(new PolygonWrongOrientationCheck()); publish(new AllPolygonsWrongOrientationCheck()); publish(new TooFewPolygonsCheck()); + publish(new InteriorFacesCheck()); publish(new ManifoldVertexCheck()); publish(new SolidSelfIntCheck()); diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheck.java new file mode 100644 index 0000000000000000000000000000000000000000..a3d5f0fc3230c83fc12cf71398161402f189fc2d --- /dev/null +++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheck.java @@ -0,0 +1,100 @@ +package de.hft.stuttgart.citydoctor2.checks.geometry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import de.hft.stuttgart.citydoctor2.check.Check; +import de.hft.stuttgart.citydoctor2.check.CheckId; +import de.hft.stuttgart.citydoctor2.check.Requirement; +import de.hft.stuttgart.citydoctor2.check.RequirementType; +import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; +import de.hft.stuttgart.citydoctor2.datastructure.Geometry; +import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; + +/** + * This class checks if a solid geometry has polygons that are fully enclosed inside the boundary surfaces. + * + * @author Alexander Riegel + */ +public class InteriorFacesCheck extends Check { + + private static final List dependencies; + + static { + ArrayList deps = new ArrayList<>(); + deps.add(CheckId.C_GE_R_TOO_FEW_POINTS); + deps.add(CheckId.C_GE_R_NOT_CLOSED); + deps.add(CheckId.C_GE_R_DUPLICATE_POINT); + deps.add(CheckId.C_GE_R_SELF_INTERSECTION); + deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED); + deps.add(CheckId.C_GE_P_INTERSECTING_RINGS); + deps.add(CheckId.C_GE_P_NON_PLANAR); + deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS); + dependencies = Collections.unmodifiableList(deps); + } + + @Override + public void check(Geometry g) { + + // only check solids + if (g.getType() != GeometryType.SOLID) { + return; + } + // TODO: Implement check logic + // Assuming geometry is well defined (no holes or dangling polygons). + // Check should run just before the ManifoldEdgesCheck + // Step 1: + // Check pairs of polygons for their distance + // if they are too closely together (within EPSILON) add them to confirmed set + + // Step 2: + // get centroid of each polygon and move it EPSILON * normalized polygon normal vector + // if that point is inside the solid's geometry, add polygon to confirmed set + + // Step 3: + // Use intersections of the polygon normal vector(raycast from centroid) + // with other polygons to establish a list of suspects. + // Check all found suspects for number of intersections. + // Use intersection count to eliminate wrongly suspected polygons + // After sorting in ascending number of intersections: + // 0 intersections are safe to remove from suspects (safe) + // 1 intersection is guaranteed to be an interior face (confirmed) + // Move these polygons into their respective sets + + // Intersection counts >= 2 require more analysis, + // using dot product of the normalized polygon normal vectors. + // Count the number of positive (same face direction) and negative (opposite face directions) dot products. + // If the intersected polygon is in the confirmed set, ignore them + // If positive count = negative count, move into preliminary safe set + // + + } + + @Override + public Set appliesToRequirements() { + return CollectionUtils.singletonSet(Requirement.R_GE_S_INTERIOR_FACE); + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public RequirementType getType() { + return RequirementType.GEOMETRY; + } + + @Override + public Check createNewInstance() { + return new InteriorFacesCheck(); + } + + @Override + public CheckId getCheckId() { + return CheckId.C_GE_S_INTERIOR_FACE; + } + +} diff --git a/CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheckTest.java b/CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheckTest.java new file mode 100644 index 0000000000000000000000000000000000000000..51a1b704a51930e330f340e89f7aa622324e1078 --- /dev/null +++ b/CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorFacesCheckTest.java @@ -0,0 +1,72 @@ +package de.hft.stuttgart.citydoctor2.checks.geometry; + +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import de.hft.stuttgart.citydoctor2.check.CheckError; +import de.hft.stuttgart.citydoctor2.check.CheckResult; +import de.hft.stuttgart.citydoctor2.check.ErrorId; +import de.hft.stuttgart.citydoctor2.check.ResultStatus; +import de.hft.stuttgart.citydoctor2.check.error.InteriorFaceError; +import de.hft.stuttgart.citydoctor2.datastructure.Geometry; +import de.hft.stuttgart.citydoctor2.datastructure.Polygon; +import de.hft.stuttgart.citydoctor2.datastructure.Vertex; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils.createGoodGeometry; +import static de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils.createVertex; +import static de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils.addPolygon; + +public class InteriorFacesCheckTest { + + Geometry geom; + + @Before + public void setup() { + geom = createGoodGeometry(); + } + + @Test + public void testGoodGeometry() { + InteriorFacesCheck check = new InteriorFacesCheck(); + check.check(geom); + CheckResult cr = geom.getCheckResult(check); + assertNotNull("Check result must not be null", cr); + assertEquals("Good geometry must return OK check result", ResultStatus.OK, cr.getResultStatus()); + } + + @Test + public void testSimpleGeometryWithInteriorFace() { + Vertex v0 = createVertex(5,0,0,geom); + Vertex v1 = createVertex(5,0,10,geom); + Vertex v2 = createVertex(5,10,10,geom); + Vertex v3 = createVertex(5,10,0,geom); + Polygon insertedInteriorFace = addPolygon(geom, v0, v1, v2, v3); + geom.updateEdgesAndVertices(); + + InteriorFacesCheck check = new InteriorFacesCheck(); + check.check(geom); + CheckResult cr = geom.getCheckResult(check); + assertNotNull(cr); + assertEquals("Check should have detected an error", ResultStatus.ERROR, cr.getResultStatus()); + assertEquals("ID of found Error should be GE_S_INTERIOR_FACE",ErrorId.GE_S_INTERIOR_FACE,cr.getError().getErrorId()); + + CheckError foundError = cr.getError(); + assertTrue("Check should have returned a InteriorFaceError",foundError instanceof InteriorFaceError); + + List interiorFaces = ((InteriorFaceError) foundError).getPolygons(); + assertNotNull(interiorFaces); + assertTrue("InteriorFaces should have a single entry", interiorFaces.size() == 1); + + Polygon foundInteriorFace = interiorFaces.getFirst(); + assertEquals("Found interior face should be the inserted polygon", insertedInteriorFace, foundInteriorFace); + } + + +}