Commit 99c8f6a8 authored by Riegel's avatar Riegel
Browse files

Merge branch 'dev' into 'master'

Fix: False positive self-intersection errors

See merge request !5
parents efa2d84e 930c05f1
Pipeline #10022 passed with stage
in 1 minute and 2 seconds
......@@ -4,7 +4,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
</parent>
<artifactId>CityDoctorCheckResult</artifactId>
<dependencies>
......
......@@ -4,7 +4,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
</parent>
<artifactId>CityDoctorEdge</artifactId>
<dependencies>
......
......@@ -78,6 +78,41 @@ public class PolyLine extends BaseEntity {
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
public String toString() {
return "PolyLine [mpFirst=" + mpFirst + ", mpLast=" + mpLast + "]";
......
......@@ -58,6 +58,10 @@ public class PolyLineSegment extends BaseEntity {
return mpNext;
}
public Vector3d getLengthVector() {
return mpEnd.getPoint().minus(mpStart.getPoint());
}
@Override
public String toString() {
return "PolyLineSegment [mpStart=" + mpStart + ", mpEnd=" + mpEnd + "]";
......
......@@ -4,7 +4,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
</parent>
<properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString>
......
......@@ -62,7 +62,6 @@ import org.citygml4j.xml.reader.CityGMLInputFactory;
import org.citygml4j.xml.reader.CityGMLReadException;
import org.citygml4j.xml.reader.CityGMLReader;
import org.citygml4j.xml.reader.FeatureInfo;
import org.citygml4j.xml.schema.CityGMLSchemaHandler;
import org.citygml4j.xml.writer.CityGMLChunkWriter;
import org.citygml4j.xml.writer.CityGMLOutputFactory;
import org.citygml4j.xml.writer.CityGMLWriteException;
......@@ -76,7 +75,10 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xmlobjects.schema.SchemaHandler;
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.CityObject;
......@@ -124,7 +126,7 @@ public class CityGmlParser {
System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
FACTORY = SAXParserFactory.newInstance();
try {
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
Please register or sign in to reply
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
logger.catching(e);
}
......@@ -526,7 +528,8 @@ public class CityGmlParser {
handler = new GMLValidationHandler();
}
try {
CityGMLSchemaHandler schemaHandler = context.getDefaultSchemaHandler();
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler);
Source[] schemas = schemaHandler.getSchemas();
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
......@@ -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) {
drainCityObjectList(model.getBuildings(), 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 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
</parent>
<artifactId>CityDoctorValidation</artifactId>
<name>CityDoctorValidation</name>
......
......@@ -34,9 +34,12 @@ import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
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.GeometryType;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection.IntersectionType;
/**
* Check for self intersecting solids
......
......@@ -118,13 +118,20 @@ public class SelfIntersectionUtil {
Polygon p1, Polygon p2) {
EdgePolygon edgeP1 = edgePolyMap.get(p1);
EdgePolygon edgeP2 = edgePolyMap.get(p2);
List<PolygonPolygonIntersection> result = IntersectPlanarPolygons.intersectPolygons(edgeP1, edgeP2, 0.000001, 0.001);
if (!result.isEmpty()) {
List<PolygonPolygonIntersection> results = IntersectPlanarPolygons.intersectPolygons(edgeP1, edgeP2, 0.000001, 0.001);
if (!results.isEmpty()) {
// at least one intersection happened
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));
}
}
}
}
/**
* Checks if two polygons are intersecting. Result may be nothing, a line or a
* polygon if they are planar and intersecting.
......
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 @@
*/
package de.hft.stuttgart.citydoctor2.checks.geometry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import org.junit.Assert;
import org.junit.Test;
......@@ -28,8 +30,11 @@ 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.checks.util.GeometryTestUtils;
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;
......@@ -73,4 +78,44 @@ public class SolidSelfIntCheckTest {
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 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
<packaging>pom</packaging>
<name>CityDoctorParent</name>
<properties>
......@@ -35,6 +35,9 @@
<version>2.22.0</version>
<configuration>
<testFailureIgnore>false</testFailureIgnore>
<excludes>
<exclude>**/SolidSelfIntCheckFalsePositiveBigMeshTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
......@@ -105,12 +108,12 @@
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-core</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-xml</artifactId>
<version>3.1.0</version>
<version>3.2.0</version>
</dependency>
<dependency>
<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