Commit 930c05f1 authored by Riegel's avatar Riegel
Browse files

Fix: False positive self-intersection errors

parent efa2d84e
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version> <version>3.14.1</version>
</parent> </parent>
<artifactId>CityDoctorCheckResult</artifactId> <artifactId>CityDoctorCheckResult</artifactId>
<dependencies> <dependencies>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version> <version>3.14.1</version>
</parent> </parent>
<artifactId>CityDoctorEdge</artifactId> <artifactId>CityDoctorEdge</artifactId>
<dependencies> <dependencies>
......
...@@ -77,6 +77,41 @@ public class PolyLine extends BaseEntity { ...@@ -77,6 +77,41 @@ public class PolyLine extends BaseEntity {
public PolyLineSegment getLastSegment() { public PolyLineSegment getLastSegment() {
return mpLast; return mpLast;
} }
public boolean isNullLine() {
return isNullLine(0.00);
}
public boolean isNullLine(double tolerance) {
// If either start or end Segment is null, return immediately
if (mpFirst == null || mpLast == null) {
return true;
}
PolyLineSegment currentSegment = mpFirst;
double length = 0.00;
// Add length of all segments, starting from mpFirst
do {
double segmentLength = currentSegment.getLengthVector().getLength();
if (segmentLength > tolerance) {
length += segmentLength;
}
if (currentSegment.getNext() != null) {
currentSegment = currentSegment.getNext();
}
} while (currentSegment.getNext() != null);
// Since mpLast will be missed due to loop condition add its length afterwards
double segmentLength = mpLast.getLengthVector().getLength();
if (segmentLength > tolerance) {
length += segmentLength;
}
// Check if total length is less than the set tolerance
return length <= tolerance;
}
@Override @Override
public String toString() { public String toString() {
......
...@@ -57,6 +57,10 @@ public class PolyLineSegment extends BaseEntity { ...@@ -57,6 +57,10 @@ public class PolyLineSegment extends BaseEntity {
public PolyLineSegment getNext() { public PolyLineSegment getNext() {
return mpNext; return mpNext;
} }
public Vector3d getLengthVector() {
return mpEnd.getPoint().minus(mpStart.getPoint());
}
@Override @Override
public String toString() { public String toString() {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version> <version>3.14.1</version>
</parent> </parent>
<properties> <properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString> <versionString>${project.version}-${git.commit.id.abbrev}</versionString>
......
...@@ -62,7 +62,6 @@ import org.citygml4j.xml.reader.CityGMLInputFactory; ...@@ -62,7 +62,6 @@ import org.citygml4j.xml.reader.CityGMLInputFactory;
import org.citygml4j.xml.reader.CityGMLReadException; import org.citygml4j.xml.reader.CityGMLReadException;
import org.citygml4j.xml.reader.CityGMLReader; import org.citygml4j.xml.reader.CityGMLReader;
import org.citygml4j.xml.reader.FeatureInfo; import org.citygml4j.xml.reader.FeatureInfo;
import org.citygml4j.xml.schema.CityGMLSchemaHandler;
import org.citygml4j.xml.writer.CityGMLChunkWriter; import org.citygml4j.xml.writer.CityGMLChunkWriter;
import org.citygml4j.xml.writer.CityGMLOutputFactory; import org.citygml4j.xml.writer.CityGMLOutputFactory;
import org.citygml4j.xml.writer.CityGMLWriteException; import org.citygml4j.xml.writer.CityGMLWriteException;
...@@ -76,7 +75,10 @@ import org.xml.sax.InputSource; ...@@ -76,7 +75,10 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException; import org.xml.sax.SAXNotSupportedException;
import org.xmlobjects.schema.SchemaHandler;
import org.xmlobjects.schema.SchemaHandlerException; import org.xmlobjects.schema.SchemaHandlerException;
import org.xmlobjects.stream.XMLReader;
import org.xmlobjects.stream.XMLReaderFactory;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
...@@ -124,7 +126,7 @@ public class CityGmlParser { ...@@ -124,7 +126,7 @@ public class CityGmlParser {
System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
FACTORY = SAXParserFactory.newInstance(); FACTORY = SAXParserFactory.newInstance();
try { try {
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) { } catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
logger.catching(e); logger.catching(e);
} }
...@@ -526,7 +528,8 @@ public class CityGmlParser { ...@@ -526,7 +528,8 @@ public class CityGmlParser {
handler = new GMLValidationHandler(); handler = new GMLValidationHandler();
} }
try { try {
CityGMLSchemaHandler schemaHandler = context.getDefaultSchemaHandler(); SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler);
Source[] schemas = schemaHandler.getSchemas(); Source[] schemas = schemaHandler.getSchemas();
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
...@@ -540,6 +543,17 @@ public class CityGmlParser { ...@@ -540,6 +543,17 @@ public class CityGmlParser {
} }
} }
private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
throws CityGmlParseException {
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects())
.withSchemaHandler(schemaHandler)
.createReader(file)) {
reader.nextTag();
} catch (Exception e) {
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
}
}
private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) { private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
drainCityObjectList(model.getBuildings(), cityObjectConsumer); drainCityObjectList(model.getBuildings(), cityObjectConsumer);
drainCityObjectList(model.getBridges(), cityObjectConsumer); drainCityObjectList(model.getBridges(), cityObjectConsumer);
......
package de.hft.stuttgart.citydoctor2.parser;
import org.xmlobjects.schema.SchemaHandler;
public class ValidationSchemaHandler extends SchemaHandler {
ValidationSchemaHandler(SchemaHandler other) {
copy(other);
}
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version> <version>3.14.1</version>
</parent> </parent>
<artifactId>CityDoctorValidation</artifactId> <artifactId>CityDoctorValidation</artifactId>
<name>CityDoctorValidation</name> <name>CityDoctorValidation</name>
......
...@@ -34,9 +34,12 @@ import de.hft.stuttgart.citydoctor2.check.ResultStatus; ...@@ -34,9 +34,12 @@ import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError; import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection.IntersectionType;
/** /**
* Check for self intersecting solids * Check for self intersecting solids
...@@ -101,7 +104,7 @@ public class SolidSelfIntCheck extends Check { ...@@ -101,7 +104,7 @@ public class SolidSelfIntCheck extends Check {
public List<CheckId> getDependencies() { public List<CheckId> getDependencies() {
return dependencies; return dependencies;
} }
@Override @Override
public Set<Requirement> appliesToRequirements() { public Set<Requirement> appliesToRequirements() {
return CollectionUtils.singletonSet(Requirement.R_GE_S_SELF_INTERSECTION); return CollectionUtils.singletonSet(Requirement.R_GE_S_SELF_INTERSECTION);
......
...@@ -118,10 +118,17 @@ public class SelfIntersectionUtil { ...@@ -118,10 +118,17 @@ public class SelfIntersectionUtil {
Polygon p1, Polygon p2) { Polygon p1, Polygon p2) {
EdgePolygon edgeP1 = edgePolyMap.get(p1); EdgePolygon edgeP1 = edgePolyMap.get(p1);
EdgePolygon edgeP2 = edgePolyMap.get(p2); EdgePolygon edgeP2 = edgePolyMap.get(p2);
List<PolygonPolygonIntersection> result = IntersectPlanarPolygons.intersectPolygons(edgeP1, edgeP2, 0.000001, 0.001); List<PolygonPolygonIntersection> results = IntersectPlanarPolygons.intersectPolygons(edgeP1, edgeP2, 0.000001, 0.001);
if (!result.isEmpty()) { if (!results.isEmpty()) {
// at least one intersection happened // at least one intersection happened
intersections.add(PolygonIntersection.lines(Collections.emptyList(), p1, p2)); for (PolygonPolygonIntersection result: results) {
// Ensure that Intersection-Edge is not a Null-Line.
// If it is a Null-Line, detected Intersection is a False-Positive.
if (!result.getPolyLine().isNullLine()) {
intersections.add(PolygonIntersection.lines(Collections.emptyList(), p1, p2));
}
}
} }
} }
......
...@@ -196,5 +196,7 @@ public class RingSelfIntCheckTest { ...@@ -196,5 +196,7 @@ public class RingSelfIntCheckTest {
assertSame(ResultStatus.OK, cr.getResultStatus()); assertSame(ResultStatus.OK, cr.getResultStatus());
} }
} }
package de.hft.stuttgart.citydoctor2.checks.geometry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
/**
* Additional test-cases for testing found false positive self-intersection errors.
* The building models in these tests have big meshes,
* and thus induce a spike in memory usage when loaded or on starting a check.
* Excluded from Maven tests due to the limited memory of the CI/CD-Runners.
*
* @author Riegel
*/
public class SolidSelfIntCheckFalsePositiveBigMeshTest {
private void testFalsePositiveExample(String gml_filepath) throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel m = CityGmlParser.parseCityGmlFile(gml_filepath, config.getParserConfiguration());
Checker c = new Checker(config, m);
c.runChecks();
Building building = m.getBuildings().get(0);
System.out.println(building.containsAnyError());
/*
* The examples have no actual self-intersections, but can contain other actual model defects.
* If an error is detected, it is thus required to check if the
* false positive self-intersection triggered it.
*/
if (building.containsAnyError()) {
Geometry buildingGeom = building.getGeometry(GeometryType.SOLID, Lod.LOD2);
buildingGeom.clearCheckResults();
SolidSelfIntCheck check = new SolidSelfIntCheck();
check.check(buildingGeom);
CheckResult cr = buildingGeom.getCheckResult(check);
assertNotNull(cr);
//Ensure that the found error is not a self-intersection error
assertEquals("False-positive self-intersection should not be detected by self-intersection check",
ResultStatus.OK, cr.getResultStatus());
}
}
@Test
public void testKnownFalsePositiveExample_BigMesh1() throws CityGmlParseException, InvalidGmlFileException {
testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh1.gml");
}
@Test
public void testKnownFalsePositiveExample_BigMesh2() throws CityGmlParseException, InvalidGmlFileException {
testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive_Big_Mesh2.gml");
}
}
...@@ -18,7 +18,9 @@ ...@@ -18,7 +18,9 @@
*/ */
package de.hft.stuttgart.citydoctor2.checks.geometry; package de.hft.stuttgart.citydoctor2.checks.geometry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
...@@ -28,8 +30,11 @@ import de.hft.stuttgart.citydoctor2.check.Checker; ...@@ -28,8 +30,11 @@ import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration; import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils; import de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
...@@ -72,5 +77,45 @@ public class SolidSelfIntCheckTest { ...@@ -72,5 +77,45 @@ public class SolidSelfIntCheckTest {
c.runChecks(); c.runChecks();
assertFalse(m.getBuildings().get(0).containsAnyError()); assertFalse(m.getBuildings().get(0).containsAnyError());
} }
private void testFalsePositiveExample(String gml_filepath) throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel m = CityGmlParser.parseCityGmlFile(gml_filepath, config.getParserConfiguration());
Checker c = new Checker(config, m);
c.runChecks();
Building building = m.getBuildings().get(0);
System.out.println(building.containsAnyError());
/*
* The examples have no actual self-intersections, but can contain other actual model defects.
* If an error is detected, it is thus required to check if the
* false positive self-intersection triggered it.
*/
if (building.containsAnyError()) {
Geometry buildingGeom = building.getGeometry(GeometryType.SOLID, Lod.LOD2);
buildingGeom.clearCheckResults();
SolidSelfIntCheck check = new SolidSelfIntCheck();
check.check(buildingGeom);
CheckResult cr = buildingGeom.getCheckResult(check);
assertNotNull(cr);
//Ensure that the found error is not a self-intersection error
assertEquals("False-positive self-intersection should not be detected by self-intersection check",
ResultStatus.OK, cr.getResultStatus());
}
}
@Test
public void testKnownFalsePositiveExample1() throws CityGmlParseException, InvalidGmlFileException {
testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive1.gml");
}
@Test
public void testKnownFalsePositiveExample2() throws CityGmlParseException, InvalidGmlFileException {
testFalsePositiveExample("src/test/resources/SolidSelfIntTest-known_false_positive2.gml");
}
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version> <version>3.14.1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>CityDoctorParent</name> <name>CityDoctorParent</name>
<properties> <properties>
...@@ -35,6 +35,9 @@ ...@@ -35,6 +35,9 @@
<version>2.22.0</version> <version>2.22.0</version>
<configuration> <configuration>
<testFailureIgnore>false</testFailureIgnore> <testFailureIgnore>false</testFailureIgnore>
<excludes>
<exclude>**/SolidSelfIntCheckFalsePositiveBigMeshTest.java</exclude>
</excludes>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
...@@ -105,12 +108,12 @@ ...@@ -105,12 +108,12 @@
<dependency> <dependency>
<groupId>org.citygml4j</groupId> <groupId>org.citygml4j</groupId>
<artifactId>citygml4j-core</artifactId> <artifactId>citygml4j-core</artifactId>
<version>3.1.0</version> <version>3.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.citygml4j</groupId> <groupId>org.citygml4j</groupId>
<artifactId>citygml4j-xml</artifactId> <artifactId>citygml4j-xml</artifactId>
<version>3.1.0</version> <version>3.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment