Commit 97a65114 authored by Riegel's avatar Riegel
Browse files

Merge branch 'dev' into 'master'

Version 3.15.0

See merge request !8
parents 99c8f6a8 5950ea5f
Pipeline #10106 passed with stage
in 3 minutes and 15 seconds
package de.hft.stuttgart.citydoctor2.connect.edge;
/**
* @author wewetzer
* @version 1.0
*/
public class NativePointerCastException extends NativeException
{
// version control for serialization
private static final long serialVersionUID = 1L;
public NativePointerCastException()
{
exceptionType = "Native pointer cast exception";
}
public NativePointerCastException(String message)
{
super(message);
exceptionType = "Native pointer cast exception";
}
}
package de.hft.stuttgart.citydoctor2.healer;
import java.util.Objects;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
public class GeometryFilter extends Check {
private GeometryListener l;
@Override
public void check(Geometry geom) {
l.visit(geom);
}
public GeometryFilter(GeometryListener l) {
Objects.requireNonNull(l);
this.l = l;
}
@Override
public RequirementType getType() {
return null;
}
@Override
public Check createNewInstance() {
return null;
}
@Override
public CheckId getCheckId() {
return null;
}
@Override
public Set<Requirement> appliesToRequirements() {
return null;
}
}
package de.hft.stuttgart.citydoctor2.healer;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
public interface GeometryListener {
public void visit(Geometry geom);
}
package de.hft.stuttgart.citydoctor2.healer;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.ade.ADEException;
import org.citygml4j.core.model.CityGMLVersion;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractCityObjectProperty;
import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.core.util.geometry.GeometryFactory;
import org.citygml4j.xml.CityGMLContext;
import org.citygml4j.xml.writer.CityGMLOutputFactory;
import org.citygml4j.xml.writer.CityGMLWriteException;
import org.citygml4j.xml.writer.CityGMLWriter;
import de.hft.stuttgart.citydoctor2.CityDoctorValidation;
import de.hft.stuttgart.citydoctor2.check.AbstractCheck;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError;
import de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError;
import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException;
import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parameter.ArgumentParser;
import de.hft.stuttgart.citydoctor2.parser.CityGmlConsumer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.reporting.XmlStreamReporter;
import de.hft.stuttgart.citydoctor2.reporting.pdf.PdfStreamReporter;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import de.hft.stuttgart.quality.QualityADEModule;
public class Healer {
private static final Logger logger = LogManager.getLogger(Healer.class);
private int numIterations = 200;
private int schematronIterations = 2;
private Checker checker;
private ModificationListener l;
private HealingPlan plan;
static {
// CppInitializer.initCpp();
}
public static void main(String[] args) throws CityGmlParseException, IOException,
InvalidGmlFileException, CityDoctorWriteException {
ArgumentParser argParser = new ArgumentParser(args);
String inputFile = CityDoctorValidation.getInputFile(argParser, false);
String outputFile = getOutputFile(argParser);
String xmlOutput = CityDoctorValidation.getXmlOutput(argParser);
String pdfOutput = CityDoctorValidation.getPdfOutput(argParser);
ValidationConfiguration config = CityDoctorValidation.getValidationConfig(argParser, true);
startHealingProcess(inputFile, outputFile, xmlOutput, pdfOutput, config);
}
private static void startHealingProcess(String inputFile, String outputFile, String xmlOutput, String pdfOutput,
ValidationConfiguration config) throws CityGmlParseException, IOException,
InvalidGmlFileException, CityDoctorWriteException {
ParserConfiguration parserConfig = config.getParserConfiguration();
if (config.isUseStreaming()) {
Healer.streamHeal(new File(inputFile), outputFile, xmlOutput, pdfOutput, config);
} else {
CityDoctorModel model = CityGmlParser.parseCityGmlFile(inputFile, parserConfig);
Checker c = new Checker(config, model);
Healer healer = new Healer(c, (ModificationListener) null);
healer.healModel(model);
model.saveAs(outputFile, true);
}
}
public static void streamHeal(File inputFile, String outputFileName, String xmlOutput, String pdfOutput,
ValidationConfiguration config) throws CityGmlParseException, IOException {
if (!inputFile.exists() || !inputFile.isFile()) {
logger.error("Inputfile does not exist or is not a file: {}", inputFile.getAbsolutePath());
}
Checker c = new Checker(config, null);
Healer healer = new Healer(c, (ModificationListener) null);
try (BufferedOutputStream xmlBos = Checker.getXmlOutputMaybe(xmlOutput);
BufferedOutputStream pdfBos = Checker.getPdfOutputMaybe(pdfOutput)) {
String fileName = inputFile.getName();
XmlStreamReporter xmlReporter;
if (xmlBos != null) {
xmlReporter = new XmlStreamReporter(xmlBos, fileName, config);
} else {
xmlReporter = null;
}
PdfStreamReporter pdfReporter;
if (pdfBos != null) {
pdfReporter = new PdfStreamReporter(pdfBos, fileName, config);
} else {
pdfReporter = null;
}
CityGmlConsumer coConsumer = new CityGmlConsumer() {
@Override
public void accept(CityObject co) {
healer.healCityObject(co);
}
};
CityGmlParser.streamCityGml(inputFile, config.getParserConfiguration(), coConsumer, outputFileName);
Checker.writeReport(xmlReporter);
Checker.writeReport(pdfReporter);
} catch (CheckReportWriteException e) {
logger.error(Localization.getText("Checker.failReports"), e);
}
}
private static String getOutputFile(ArgumentParser argParser) {
String outputFile;
if (!argParser.containsOption("out")) {
logger.error("No output file specified. (-out [FILE])");
System.exit(10);
}
List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
logger.error("Specify exactly one file as output.");
System.exit(11);
}
outputFile = outFiles.get(0);
return outputFile;
}
public static void heal(File in, File configFile, File out)
throws FileNotFoundException, CityGmlParseException, InvalidGmlFileException, CityDoctorWriteException {
ValidationConfiguration config;
if (configFile == null) {
config = ValidationConfiguration.loadStandardValidationConfig();
} else {
config = ValidationConfiguration.loadValidationConfig(configFile.getAbsolutePath());
}
CityDoctorModel model = CityGmlParser.parseCityGmlFile(in.getAbsolutePath(), config.getParserConfiguration());
Checker c = new Checker(config, model);
Healer healer = new Healer(c, (ModificationListener) null);
healer.healModel(model);
model.saveAs(out.getAbsolutePath(), true);
}
public Healer(Checker c) {
this(c, (ModificationListener) null);
}
public Healer(Checker c, ModificationListener l) {
plan = HealingMethods.createHealingPlanFromAllMethods();
checker = c;
if (l == null) {
// create useless modification listener to avoid having to check on == null
l = new ModificationListener() {
@Override
public void polygonRemoved(Polygon p) {
// not observing anything
}
@Override
public void polygonAdded(Polygon p) {
// not observing anything
}
};
}
this.l = l;
}
public Healer(Checker c, ModificationListener l, HealingPlan plan) {
this(c, l);
this.plan = Objects.requireNonNull(plan);
}
public Healer(Checker c, HealingPlan plan) {
this(c);
this.plan = Objects.requireNonNull(plan);
}
public void setNumberOfIterations(int iterations) {
if (iterations < 1) {
throw new IllegalArgumentException("Iterations may not be lower than 1");
}
numIterations = iterations;
}
public void healModel(CityDoctorModel model) {
model.createFeatureStream().forEach(this::healCityObject);
}
public void healCityObject(CityObject co) {
logger.info("Repairing feature {}", co.getGmlId());
String schematronPath = checker.getConfig().getSchematronFilePath();
if (schematronPath != null && !schematronPath.isEmpty()) {
healSemanticErrors(co);
}
// healing geometries
GeometryFilter filter = new GeometryFilter(g -> heal(g, co));
co.accept(filter);
}
private void healSemanticErrors(CityObject co) {
// if a schematron file has been declared this needs to execute
for (int i = 0; i < schematronIterations; i++) {
co.clearAllContainedCheckResults();
executeSchematron(co);
List<CheckError> errors = new ArrayList<>();
co.collectContainedErrors(errors);
boolean healedSomething = false;
for (CheckError err : errors) {
if (err instanceof AttributeMissingError || err instanceof AttributeValueWrongError) {
for (HealingMethod m : plan.getHealingMethods()) {
if (err.accept(m, l)) {
healedSomething = true;
break;
}
}
}
}
if (!healedSomething) {
// nothing was changed, don't check for anything more
break;
}
}
// recheck for geometry errors as they were removed when executing the
// schematron stuff
co.prepareForChecking();
checker.executeChecksForCheckable(co);
if (checker.getConfig().getParserConfiguration().useLowMemoryConsumption()) {
co.clearMetaInformation();
}
}
private void executeSchematron(CityObject co) {
ValidationConfiguration config = checker.getConfig();
try {
GeometryFactory factory = GeometryFactory.newInstance();
// write geometries in citygml4j datastructure
co.reCreateGeometries(factory, checker.getConfig().getParserConfiguration());
byte[] gml = writeCityGml(co.getGmlObject());
// remove them again to save memory
co.unsetGmlGeometries();
SvrlContentHandler handler = Checker.executeSchematronValidationIfAvailable(config,
new ByteArrayInputStream(gml));
if (handler != null) {
handler.getFeatureErrors().computeIfPresent(co.getGmlId().getGmlString(), (key, list) -> {
Checker.handleSchematronErrorsForCityObject(list, co);
return list;
});
}
} catch (CityGMLWriteException e) {
throw new IllegalStateException(e);
}
}
/**
* Writes one feature to a byte array.
*
* @param aco the feature to write
* @return the byte array containing the feature
* @throws ADEException
* @throws CityGMLBuilderException
* @throws CityGMLWriteException
*/
private byte[] writeCityGml(AbstractCityObject aco) throws CityGMLWriteException {
CityModel model = new CityModel();
model.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
CityGMLContext context = CityGmlParser.getContext();
CityGMLOutputFactory outputFactory = context.createCityGMLOutputFactory(CityGMLVersion.v2_0);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (CityGMLWriter writer = outputFactory.createCityGMLWriter(out)) {
writer.withIndent(" ");
writer.withDefaultPrefixes();
writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI);
writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI,
QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd");
writer.withDefaultSchemaLocations();
writer.write(model);
writer.flush();
return out.toByteArray();
}
}
public void heal(Geometry geom, CityObject co) {
heal(geom, co, numIterations);
}
public void heal(Geometry geom, CityObject co, int numIterations) {
if (checker.getConfig().getParserConfiguration().useLowMemoryConsumption()) {
co.prepareForChecking();
}
if (!co.isValidated()) {
// check it if it has not been checked yet
checker.executeChecksForCheckable(co);
}
try {
for (int i = 0; i < numIterations; i++) {
List<CheckError> errors = collectErrorsFromGeometry(geom);
if (errors.isEmpty()) {
break;
} else {
logger.debug("Found errors: ");
for (CheckError err : errors) {
logger.debug(err.getErrorId());
}
}
boolean cannotBeHealed = executeHealingLoop(errors);
logger.debug("End heal iteration: {}", (i + 1));
if (cannotBeHealed) {
logger.debug("Geometry: {} cannot be healed", geom.getGmlId());
// break iteration loop, nothing more to be done
break;
}
co.prepareForChecking();
filterOutDuplicateVertices(co);
// recheck for errors
checker.executeChecksForCheckable(co);
}
} catch (Exception e) {
logger.debug("Failed to heal geometry", e);
} finally {
if (checker.getConfig().getParserConfiguration().useLowMemoryConsumption()) {
co.clearMetaInformation();
}
}
}
private void filterOutDuplicateVertices(CityObject co) {
Map<Vertex, Vertex> map = new HashMap<>();
co.accept(new AbstractCheck() {
@Override
public void check(LinearRing ring) {
for (int i = 0; i < ring.getVertices().size(); i++) {
Vertex v = ring.getVertices().get(i);
Vertex duplicate = map.get(v);
if (duplicate == null) {
map.put(v, v);
} else if (duplicate != v) {
ring.getVertices().set(i, duplicate);
}
}
}
});
}
private boolean executeHealingLoop(List<CheckError> errors) {
for (HealingMethod method : plan.getHealingMethods()) {
for (CheckError err : errors) {
if (err.accept(method, l)) {
// geometry got changed, update, recheck, restart
return false;
}
}
}
return true;
}
private List<CheckError> collectErrorsFromGeometry(Geometry geom) {
List<CheckError> errors = new ArrayList<>();
geom.collectContainedErrors(errors);
return errors;
}
}
package de.hft.stuttgart.citydoctor2.healer;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
public class HealingMethodPrototype {
public HealingMethod method;
public static HealingMethodPrototype of(HealingMethod method) {
return new HealingMethodPrototype(method);
}
public HealingMethodPrototype(HealingMethod method) {
this.method = method;
}
public HealingMethod createMethod() {
return method.createNew();
}
}
package de.hft.stuttgart.citydoctor2.healer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.hft.stuttgart.citydoctor2.healing.GeometrySimplifier;
import de.hft.stuttgart.citydoctor2.healing.HealAllPolygonsWrongOrientationError;
import de.hft.stuttgart.citydoctor2.healing.HealConsecutivePointsSameError;
import de.hft.stuttgart.citydoctor2.healing.HealHoleOutsideError;
import de.hft.stuttgart.citydoctor2.healing.HealMainBuilding;
import de.hft.stuttgart.citydoctor2.healing.HealMissingSolid;
import de.hft.stuttgart.citydoctor2.healing.HealNonManifoldEdgeError;
import de.hft.stuttgart.citydoctor2.healing.HealPlanarPolygonError;
import de.hft.stuttgart.citydoctor2.healing.HealPolygonWithoutSurface;
import de.hft.stuttgart.citydoctor2.healing.HealPolygonWrongOrientationError;
import de.hft.stuttgart.citydoctor2.healing.HealRingNotClosedError;
import de.hft.stuttgart.citydoctor2.healing.HealRingSelfIntError;
import de.hft.stuttgart.citydoctor2.healing.HealSameOrientationError;
import de.hft.stuttgart.citydoctor2.healing.HealSolidNotClosedError;
import de.hft.stuttgart.citydoctor2.healing.HealTooFewPoints;
import de.hft.stuttgart.citydoctor2.healing.HealWrongDormers;
public class HealingMethods {
private static List<HealingMethodPrototype> healingMethods;
private HealingMethods() {
// only static access
}
static {
healingMethods = new ArrayList<>();
healingMethods.add(HealingMethodPrototype.of(new HealMissingSolid()));
healingMethods.add(HealingMethodPrototype.of(new HealMainBuilding()));
healingMethods.add(HealingMethodPrototype.of(new HealConsecutivePointsSameError()));
healingMethods.add(HealingMethodPrototype.of(new HealTooFewPoints()));
healingMethods.add(HealingMethodPrototype.of(new HealRingNotClosedError()));
healingMethods.add(HealingMethodPrototype.of(new HealRingSelfIntError()));
healingMethods.add(HealingMethodPrototype.of(new HealPlanarPolygonError()));
// healingMethods.add(HealingMethodPrototype.of(new HealPlanarPolygonCpp()));
healingMethods.add(HealingMethodPrototype.of(new HealSameOrientationError()));
healingMethods.add(HealingMethodPrototype.of(new HealHoleOutsideError()));
healingMethods.add(HealingMethodPrototype.of(new HealNonManifoldEdgeError()));
healingMethods.add(HealingMethodPrototype.of(new HealSolidNotClosedError()));
// healingMethods.add(HealingMethodPrototype.of(new HealSolidNotClosedCpp()));
healingMethods.add(HealingMethodPrototype.of(new HealPolygonWrongOrientationError()));
healingMethods.add(HealingMethodPrototype.of(new GeometrySimplifier()));
healingMethods.add(HealingMethodPrototype.of(new HealWrongDormers()));
healingMethods.add(HealingMethodPrototype.of(new HealAllPolygonsWrongOrientationError()));
healingMethods.add(HealingMethodPrototype.of(new HealPolygonWithoutSurface()));
healingMethods = Collections.unmodifiableList(healingMethods);
}
public static HealingPlan createHealingPlanFromAllMethods() {
HealingPlan plan = new HealingPlan();
for (HealingMethodPrototype proto : healingMethods) {
plan.addHealingMethod(proto.createMethod());
}
return plan;
}
public static List<HealingMethodPrototype> getAvailableHealingMethods() {
return healingMethods;
}
}
package de.hft.stuttgart.citydoctor2.healer;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
public class HealingPlan {
private List<HealingMethod> methods;
public HealingPlan() {
methods = new ArrayList<>();
}
public void addHealingMethod(HealingMethod method) {
methods.add(method);
}
public List<HealingMethod> getHealingMethods() {
return methods;
}
@Override
public String toString() {
return "HealingPlan [methods=" + methods + "]";
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.healing.math.EdgeGraph;
import de.hft.stuttgart.citydoctor2.math.Plane;
public class GeometrySimplifier implements HealingMethod {
private static final Logger logger = LogManager.getLogger(GeometrySimplifier.class);
private static final double EPSILON = 0.1;
@Override
public HealingID getID() {
return HealingID.S_GEOMETRIC_SIMPLIFIER;
}
@Override
public boolean visit(MultipleConnectedComponentsError err, ModificationListener l) {
logger.debug("Executing Repair for MultipleConnectedComponentsError");
// first merge similar polygons
for (List<Polygon> component : err.getComponents()) {
for (int i = 0; i < component.size() - 1; i++) {
Polygon p0 = component.get(i);
Plane plane = new Plane(p0.calculateNormalNormalized(), p0.getExteriorRing().getVertices().get(0));
for (int j = i + 1; j < component.size(); j++) {
Polygon p1 = component.get(j);
if (isPolygonInPlane(plane, p1) && isPolygonConnectedViaEdge(p0, p1, err.getGeometry())) {
ConcretePolygon p = mergePolygons(p0, p1);
p1.remove();
err.getGeometry().replacePolygon(p0, p);
return true;
}
}
}
}
return false;
}
private boolean isPolygonConnectedViaEdge(Polygon p1, Polygon p2, Geometry g) {
List<Edge> edges = g.getEdgesAdjacentTo(p1);
for (Edge e : edges) {
if (e.getAdjacentPolygons().contains(p2)) {
return true;
}
}
return false;
}
private ConcretePolygon mergePolygons(Polygon p1, Polygon p2) {
Geometry geom = p1.getParent();
List<Edge> edgesP1 = geom.getEdgesAdjacentTo(p1);
List<Edge> edgesP2 = geom.getEdgesAdjacentTo(p2);
Set<Edge> setOfEdgesFromP1 = new HashSet<>(edgesP1);
Set<Edge> duplicateEdges = new HashSet<>();
for (Edge e : edgesP2) {
if (setOfEdgesFromP1.contains(e)) {
duplicateEdges.add(e);
}
}
if (duplicateEdges.isEmpty()) {
throw new IllegalStateException(
"Trying to merge two polygon which are not connected " + p1.getGmlId() + ", " + p2.getGmlId());
}
List<Edge> newEdges = new ArrayList<>();
filterDuplicateEdges(edgesP1, duplicateEdges, newEdges);
filterDuplicateEdges(edgesP2, duplicateEdges, newEdges);
if (newEdges.isEmpty()) {
return copyPolygon(p1);
}
List<List<Edge>> rings = findCyclesInEdges(newEdges);
// find the exterior ring in the new rings
ConcretePolygon p = new ConcretePolygon();
List<LinearRing> newRings = new ArrayList<>();
for (List<Edge> edges : rings) {
LinearRing ring = createRingFromEdges(edges);
newRings.add(ring);
}
if (rings.size() == 1) {
// only one ring -> the ring is an exterior
LinearRing ext = newRings.get(0);
ext.setType(LinearRingType.EXTERIOR);
p.setParent(geom);
p.setExteriorRing(ext);
return p;
} else {
for (LinearRing lr : newRings) {
if (isRingExterior(newRings, lr)) {
return createPolygon(newRings, lr, p);
}
}
throw new IllegalStateException(
"Merging polygons produced intersecting edges," + " no exterior ring could be found");
}
}
private ConcretePolygon copyPolygon(Polygon p1) {
ConcretePolygon newPoly = new ConcretePolygon();
ConcretePolygon original = p1.getOriginal();
LinearRing ext = new LinearRing(LinearRingType.EXTERIOR);
newPoly.setExteriorRing(ext);
for (Vertex v : original.getExteriorRing().getVertices()) {
ext.addVertex(v);
}
for (LinearRing lr : original.getInnerRings()) {
LinearRing inner = new LinearRing(LinearRingType.INTERIOR);
newPoly.addInteriorRing(inner);
for (Vertex v : lr.getVertices()) {
inner.addVertex(v);
}
}
return newPoly;
}
private ConcretePolygon createPolygon(List<LinearRing> newRings, LinearRing lr, ConcretePolygon p) {
lr.setType(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
for (LinearRing innerRing : newRings) {
if (lr == innerRing) {
continue;
}
p.addInteriorRing(innerRing);
}
return p;
}
private boolean isRingExterior(List<LinearRing> newRings, LinearRing lr) {
for (LinearRing other : newRings) {
if (lr == other) {
continue;
}
for (Vertex v : lr.getVertices()) {
if (other.isPointInside(v)) {
return false;
}
}
}
return true;
}
private LinearRing createRingFromEdges(List<Edge> edges) {
Edge startEdge = edges.get(0);
Vertex start = startEdge.getFrom();
Vertex currentVertex = startEdge.getTo();
edges.remove(0);
LinearRing lr = new LinearRing(LinearRingType.INTERIOR);
lr.addVertex(start);
lr.addVertex(currentVertex);
while (!edges.isEmpty()) {
boolean removedEdge = false;
for (Edge e : edges) {
Vertex oppositeV = e.getOppositeVertex(currentVertex);
if (oppositeV != null) {
lr.addVertex(oppositeV);
currentVertex = oppositeV;
edges.remove(e);
removedEdge = true;
break;
}
}
if (!removedEdge) {
// avoid endless loop in case of errors
logger.error("Could not create ring from edges, edges are not a loop");
break;
}
}
return lr;
}
private void filterDuplicateEdges(List<Edge> edges, Set<Edge> duplicateEdges, List<Edge> newEdges) {
for (Edge e : edges) {
if (!duplicateEdges.contains(e)) {
newEdges.add(e);
}
}
}
private List<List<Edge>> findCyclesInEdges(List<Edge> edges) {
EdgeGraph graph = new EdgeGraph();
for (Edge e : edges) {
graph.addEdge(e, edges);
}
return graph.getCycles();
}
private boolean isPolygonInPlane(Plane plane, Polygon potentialPoly) {
for (Vertex v : potentialPoly.getExteriorRing().getVertices()) {
if (plane.getDistance(v) > EPSILON) {
return false;
}
}
return true;
}
@Override
public GeometrySimplifier createNew() {
return new GeometrySimplifier();
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.Collections;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.AllPolygonsWrongOrientationError;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
public class HealAllPolygonsWrongOrientationError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealAllPolygonsWrongOrientationError.class);
@Override
public boolean visit(AllPolygonsWrongOrientationError err, ModificationListener l) {
logger.debug("Executing Repair for AllPolygonsWrongOrientationError");
Geometry geom = err.getGeometry();
for (Polygon p : geom.getPolygons()) {
Collections.reverse(p.getExteriorRing().getVertices());
for (LinearRing lr : p.getInnerRings()) {
Collections.reverse(lr.getVertices());
}
}
return true;
}
@Override
public HealAllPolygonsWrongOrientationError createNew() {
return new HealAllPolygonsWrongOrientationError();
}
@Override
public HealingID getID() {
return HealingID.S_ALL_POLYGONS_WRONG_ORIENTATION;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class HealConsecutivePointsSameError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealConsecutivePointsSameError.class);
@Override
public boolean visit(ConsecutivePointSameError err, ModificationListener l) {
logger.debug("Executing Repair for ConsecutivePointSameError");
LinearRing lr = err.getRing();
Vertex consV1 = err.getVertex1();
Vertex consV2 = err.getVertex2();
for (int i = 0; i < lr.getVertices().size() - 1; i++) {
Vertex v1 = lr.getVertices().get(i + 0);
Vertex v2 = lr.getVertices().get(i + 1);
if (v1 == consV1 && v2 == consV2) {
Vector3d middle = v1.plus(v2).mult(0.5);
v2.setX(middle.getX());
v2.setY(middle.getY());
v2.setZ(middle.getZ());
lr.getVertices().remove(i);
return true;
}
}
return false;
}
@Override
public HealConsecutivePointsSameError createNew() {
return new HealConsecutivePointsSameError();
}
@Override
public HealingID getID() {
return HealingID.R_CONSECUTIVE_POINTS_SAME;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.PolygonHoleOutsideError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
public class HealHoleOutsideError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealHoleOutsideError.class);
@Override
public boolean visit(PolygonHoleOutsideError err, ModificationListener l) {
logger.debug("Executing Repair for PolygonHoleOutsideError");
for (LinearRing lr : err.getHolesOutside()) {
err.getPolygon().removeInnerRing(lr);
}
return true;
}
@Override
public HealHoleOutsideError createNew() {
return new HealHoleOutsideError();
}
@Override
public HealingID getID() {
return HealingID.P_HOLE_OUTSIDE;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.building.BuildingPartProperty;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
public class HealMainBuilding implements HealingMethod {
private static final HealingID ID = new HealingID("SE_MISSING_MAIN_BUILDING");
private static final Logger logger = LogManager.getLogger(HealMainBuilding.class);
@Override
public HealingMethod createNew() {
return new HealMainBuilding();
}
@Override
public boolean visit(AttributeMissingError err, ModificationListener l) {
if (!(err.getFeature() instanceof Building)) {
return false;
}
if (!err.getNameOfAttribute().equalsIgnoreCase("Main Building")) {
return false;
}
logger.debug("Executing Repair for AttributeMissingError with message Main Building");
Building b = (Building) err.getFeature();
System.out.println("Building: " + b.getGmlId());
BuildingPart largestPart = null;
double largestArea = 0;
for (BuildingPart part : b.getBuildingParts()) {
double area = 0;
for (BoundarySurface bs : part.getBoundarySurfaces()) {
if (bs.getType() == BoundarySurfaceType.GROUND) {
Geometry geometry = bs.getHighestLodGeometry();
for (Polygon p : geometry.getPolygons()) {
area += p.getArea();
}
}
}
if (area > largestArea) {
largestPart = part;
largestArea = area;
}
}
if (largestPart == null) {
// no suitable part found
return false;
}
System.out.println("Part: " + largestPart.getGmlId() + " has area: " + largestArea);
b.getBuildingParts().remove(largestPart);
var gmlBuilding = (org.citygml4j.core.model.building.Building) b.getGmlObject();
BuildingPartProperty buildingPartProp = findBuildingPartProp(largestPart, gmlBuilding);
if (buildingPartProp == null) {
return false;
}
gmlBuilding.getBuildingParts().remove(buildingPartProp);
b.getGeometries().addAll(largestPart.getGeometries());
b.getBoundarySurfaces().addAll(largestPart.getBoundarySurfaces());
b.getBuildingInstallations().addAll(largestPart.getBuildingInstallations());
org.citygml4j.core.model.building.BuildingPart partObject = buildingPartProp.getObject();
gmlBuilding.getBoundaries().addAll(partObject.getBoundaries());
gmlBuilding.getBuildingInstallations().addAll(partObject.getBuildingInstallations());
gmlBuilding.setRoofType(partObject.getRoofType());
gmlBuilding.getHeights().clear();
gmlBuilding.getHeights().addAll(partObject.getHeights());
return true;
}
private BuildingPartProperty findBuildingPartProp(BuildingPart largestPart, org.citygml4j.core.model.building.Building gmlBuilding) {
for (BuildingPartProperty prop : gmlBuilding.getBuildingParts()) {
if (prop.getObject() != null && prop.getObject() == largestPart.getGmlObject()) {
return prop;
}
}
return null;
}
@Override
public HealingID getID() {
return ID;
}
}
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.AbstractCheck;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
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.datastructure.GmlElement;
import de.hft.stuttgart.citydoctor2.datastructure.LinkedPolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
public class HealMissingSolid implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealMissingSolid.class);
@Override
public HealingMethod createNew() {
return new HealMissingSolid();
}
@Override
public boolean visit(AttributeMissingError err, ModificationListener l) {
logger.debug("Executing Repair for AttributeMissingError");
if (err.getNameOfAttribute().equalsIgnoreCase("lod1solid")) {
return createSolid(err.getFeature(), Lod.LOD1);
} else if (err.getNameOfAttribute().equalsIgnoreCase("lod2solid")) {
return createSolid(err.getFeature(), Lod.LOD2);
} else if (err.getNameOfAttribute().equalsIgnoreCase("lod3solid")) {
return createSolid(err.getFeature(), Lod.LOD3);
} else if (err.getNameOfAttribute().equalsIgnoreCase("lod4solid")) {
return createSolid(err.getFeature(), Lod.LOD4);
} else if (err.getNameOfAttribute().equalsIgnoreCase("any solid")) {
return createSolid(err.getFeature());
}
return false;
}
private boolean createSolid(GmlElement feature) {
if (feature instanceof AbstractBuilding ab) {
List<Polygon> findPolygons = collectPolygons(ab);
if (findPolygons.isEmpty()) {
return false;
}
Lod highestLod = Lod.LOD1;
for (Polygon p : findPolygons) {
if (p.getParent().getLod().isHigher(highestLod)) {
highestLod = p.getParent().getLod();
}
}
return createSolid(feature, highestLod);
}
return false;
}
private boolean createSolid(GmlElement feature, Lod lod) {
if (feature instanceof Building building) {
boolean createdSolidInMainFeature = createSolidInFeature(lod, building);
boolean hasCreatedSolid = createdSolidInMainFeature;
for (BuildingPart part : building.getBuildingParts()) {
boolean createdSolidInPart = createSolidInFeature(lod, part);
hasCreatedSolid = hasCreatedSolid || createdSolidInPart;
}
return hasCreatedSolid;
} else if (feature instanceof BuildingPart part) {
return createSolidInFeature(lod, part);
}
return false;
}
private List<Polygon> collectPolygons(CityObject co) {
List<Polygon> polygons = new ArrayList<>();
AbstractCheck polygonCheck = new AbstractCheck() {
@Override
public void check(Polygon p) {
if (!p.isLink()) {
polygons.add(p);
}
}
};
co.accept(polygonCheck);
return polygons;
}
private boolean createSolidInFeature(Lod lod, AbstractBuilding building) {
Geometry geom = new Geometry(GeometryType.SOLID, lod);
List<Polygon> polygons = collectPolygons(building, lod, geom);
for (Polygon p : polygons) {
geom.addPolygon(p);
}
if (geom.getPolygons().isEmpty()) {
// no geometry has been created
return false;
}
// remove both solid and multisurface geometry
building.removeGeometry(lod);
building.addGeometry(geom);
return true;
}
private List<Polygon> collectPolygons(AbstractBuilding feature, Lod lod, Geometry solidGeometry) {
List<Polygon> polygons = new ArrayList<>();
// collect all concrete polygons from main geometry if any
for (Geometry geom : feature.getGeometries()) {
if (geom.getLod().equals(lod)) {
for (Polygon p : geom.getPolygons()) {
if (!p.isLink()) {
// concrete polygon, add to list
polygons.add(p);
}
}
}
}
for (BoundarySurface bs : feature.getBoundarySurfaces()) {
collectPolygons(bs, lod, polygons, solidGeometry);
}
for (Installation bi : feature.getBuildingInstallations()) {
collectPolygons(bi, lod, polygons, solidGeometry);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
collectPolygons(bs, lod, polygons, solidGeometry);
}
}
return polygons;
}
/**
* Only find concretePolygons and add them as link to the list
*
* @param co
* @param lod
* @param polygons
*/
private void collectPolygons(CityObject co, Lod lod, List<Polygon> polygons, Geometry solidGeometry) {
for (Geometry geom : co.getGeometries()) {
if (geom.getLod().equals(lod)) {
for (Polygon p : geom.getPolygons()) {
if (p instanceof ConcretePolygon cp) {
// concrete polygon, create link and add to list
polygons.add(new LinkedPolygon(cp, solidGeometry));
}
}
}
}
}
@Override
public HealingID getID() {
return HealingID.SE_MISSING_LOD2_SOLID;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.Vector2d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.Pair;
public class HealNonManifoldEdgeError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealNonManifoldEdgeError.class);
@Override
public boolean visit(NonManifoldEdgeError err, ModificationListener l) {
logger.debug("Executing Repair for NonManifoldEdgeError");
// count the number of manifold edges per polygon
Map<Polygon, AtomicInteger> errorCountMap = new HashMap<>();
for (Edge e : err.getEdges()) {
for (Polygon p : e.getAdjacentPolygons()) {
AtomicInteger count = errorCountMap.computeIfAbsent(p, k -> new AtomicInteger(0));
count.incrementAndGet();
}
}
// find the polygons with the highest number of error edges
List<Pair<Polygon, AtomicInteger>> faultyPolygons = new ArrayList<>();
for (Entry<Polygon, AtomicInteger> e : errorCountMap.entrySet()) {
faultyPolygons.add(new Pair<Polygon, AtomicInteger>(e.getKey(), e.getValue()));
}
Collections.sort(faultyPolygons, (o1, o2) -> o2.getValue1().intValue() - o1.getValue1().intValue());
int highestValue = faultyPolygons.get(0).getValue1().intValue();
// remove the polygon with the highest amount, if it is the only one with the
// highest number
return removeHighestPolygon(err, errorCountMap, faultyPolygons, highestValue, l);
}
private boolean removePolygonWithWrongAngle(NonManifoldEdgeError err, ModificationListener l) {
Edge errorEdge = err.getEdges().get(0);
List<Polygon> polygons = errorEdge.getAdjacentPolygons();
Segment3d edgeSegment = new Segment3d(errorEdge.getFrom(), errorEdge.getTo());
List<Pair<Vector3d, Polygon>> normals = collectNormalsofAdjacentPolygons(errorEdge, polygons, edgeSegment);
Vector3d center = err.getGeometry().getCenter();
Vector3d centerNormal = edgeSegment.getNormalThroughPoint(center);
Vector3d edgeDir = errorEdge.getTo().minus(errorEdge.getFrom());
double x = Math.abs(edgeDir.getX());
double y = Math.abs(edgeDir.getY());
double z = Math.abs(edgeDir.getZ());
Vector2d projectedCenterNormal;
List<Pair<Vector2d, Polygon>> projectedNormals = new ArrayList<>();
if (x > y && x > z) {
for (Pair<Vector3d, Polygon> normal : normals) {
Vector3d normalVec = normal.getValue0();
Vector2d projectedNormal = new Vector2d(normalVec.getY(), normalVec.getZ());
projectedNormal.normalize();
projectedNormals.add(new Pair<>(projectedNormal, normal.getValue1()));
}
projectedCenterNormal = new Vector2d(centerNormal.getY(), centerNormal.getZ());
} else if (y > x && y > z) {
for (Pair<Vector3d, Polygon> normal : normals) {
Vector3d normalVec = normal.getValue0();
Vector2d projectedNormal = new Vector2d(normalVec.getX(), normalVec.getZ());
projectedNormal.normalize();
projectedNormals.add(new Pair<>(projectedNormal, normal.getValue1()));
}
projectedCenterNormal = new Vector2d(centerNormal.getX(), centerNormal.getZ());
} else {
for (Pair<Vector3d, Polygon> normal : normals) {
Vector3d normalVec = normal.getValue0();
Vector2d projectedNormal = new Vector2d(normalVec.getX(), normalVec.getY());
projectedNormal.normalize();
projectedNormals.add(new Pair<>(projectedNormal, normal.getValue1()));
}
projectedCenterNormal = new Vector2d(centerNormal.getX(), centerNormal.getY());
}
projectedCenterNormal.normalize();
double normalAngle = Math.atan2(projectedCenterNormal.getY(), projectedCenterNormal.getX());
removePolygonsWithWrongAngle(err, errorEdge, projectedNormals, normalAngle, l);
return true;
}
private void removePolygonsWithWrongAngle(NonManifoldEdgeError err, Edge errorEdge,
List<Pair<Vector2d, Polygon>> projectedNormals, double normalAngle, ModificationListener l) {
Polygon lowestPolygon = null;
Polygon highestPolygon = null;
double lowestAngle = 500d;
double highestAngle = -500d;
for (Pair<Vector2d, Polygon> normal : projectedNormals) {
Vector2d normalVec = normal.getValue0();
double angle = Math.atan2(normalVec.getY(), normalVec.getX());
angle = angle - normalAngle;
if (angle > Math.PI) {
angle -= 2 * Math.PI;
}
if (angle < Math.PI) {
angle += 2 * Math.PI;
}
if (angle < lowestAngle) {
lowestAngle = angle;
lowestPolygon = normal.getValue1();
}
if (angle > highestAngle) {
highestAngle = angle;
highestPolygon = normal.getValue1();
}
}
for (Polygon p : errorEdge.getAdjacentPolygons()) {
if (p != lowestPolygon && p != highestPolygon) {
l.polygonRemoved(p);
p.remove();
return;
}
}
}
private List<Pair<Vector3d, Polygon>> collectNormalsofAdjacentPolygons(Edge errorEdge, List<Polygon> polygons,
Segment3d edgeSegment) {
List<Pair<Vector3d, Polygon>> normals = new ArrayList<>();
for (Polygon p : polygons) {
for (Vertex v : p.getExteriorRing().getVertices()) {
if (v != errorEdge.getFrom() && v != errorEdge.getTo()) {
Vector3d normal = edgeSegment.getNormalThroughPoint(v);
normals.add(new Pair<>(normal, p));
break;
}
}
}
return normals;
}
private boolean removeHighestPolygon(NonManifoldEdgeError err, Map<Polygon, AtomicInteger> errorCountMap,
List<Pair<Polygon, AtomicInteger>> faultyPolygons, int highestValue, ModificationListener l) {
if (faultyPolygons.get(1).getValue1().intValue() != highestValue) {
// remove faulty polygon
Polygon poly = faultyPolygons.get(0).getValue0();
l.polygonRemoved(poly);
poly.remove();
return true;
} else {
// two polygons have the same amount of error edges
// search for more error edges, where number of half edges is < 2 (not closed
// error)
searchForNotClosedEdges(err, errorCountMap, faultyPolygons, highestValue);
// resort the polygons again
Collections.sort(faultyPolygons, (o1, o2) -> o2.getValue1().intValue() - o1.getValue1().intValue());
highestValue = faultyPolygons.get(0).getValue1().intValue();
if (faultyPolygons.get(1).getValue1().intValue() != highestValue) {
// remove faulty polygon
Polygon poly = faultyPolygons.get(0).getValue0();
l.polygonRemoved(poly);
poly.remove();
return true;
} else {
removePolygonWithWrongAngle(err, l);
return true;
}
}
}
private void searchForNotClosedEdges(NonManifoldEdgeError err, Map<Polygon, AtomicInteger> errorCountMap,
List<Pair<Polygon, AtomicInteger>> faultyPolygons, int highestValue) {
int index = 0;
while (index < faultyPolygons.size() && faultyPolygons.get(index).getValue1().intValue() == highestValue) {
Polygon p = faultyPolygons.get(index).getValue0();
List<Edge> edges = err.getGeometry().getEdgesAdjacentTo(p);
for (Edge e : edges) {
if (e.getNumberOfAllHalfEdges() < 2) {
// found another error edge
for (Polygon temp : e.getAdjacentPolygons()) {
AtomicInteger count = errorCountMap.computeIfAbsent(temp, k -> new AtomicInteger(0));
count.incrementAndGet();
}
}
}
index++;
}
}
@Override
public HealNonManifoldEdgeError createNew() {
return new HealNonManifoldEdgeError();
}
@Override
public HealingID getID() {
return HealingID.S_NON_MANIFOLD_EDGE;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError;
import de.hft.stuttgart.citydoctor2.connect.edge.CppFeature;
import de.hft.stuttgart.citydoctor2.connect.edge.CppHealResult;
import de.hft.stuttgart.citydoctor2.connect.edge.CppPolygonHealing;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
public class HealPlanarPolygonCpp implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealPlanarPolygonCpp.class);
@Override
public HealingMethod createNew() {
return new HealPlanarPolygonCpp();
}
@Override
public boolean visit(NonPlanarPolygonDistancePlaneError err, ModificationListener l) {
logger.debug("Executing Repair for NonPlanarPolygonDistancePlaneError");
// ----------------------------------------------
// get Geometry and Geometry informations
// ----------------------------------------------
Geometry geometry;
if (err.getPolygon().isLinkedTo()) {
geometry = err.getPolygon().getLinkedFromPolygon().getParent();
} else {
geometry = err.getPolygon().getParent();
}
List<Integer> polyIdsList = new ArrayList<>();
for (int i = 0; i < geometry.getPolygons().size(); i++) {
Polygon p = geometry.getPolygons().get(i);
CheckResult cr = p.getCheckResult(CheckId.C_GE_P_NON_PLANAR);
if (cr.getResultStatus() == ResultStatus.ERROR
&& cr.getError().getErrorId() == ErrorId.GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE) {
polyIdsList.add(i);
}
}
int[] polyIds = new int[polyIdsList.size()];
for (int i = 0; i < polyIdsList.size(); i++) {
polyIds[i] = polyIdsList.get(i);
}
String gmlId_string = geometry.getParent().getGmlId().toString();
// ----------------------------------------------
// create feature and fill feature with geometry
// ----------------------------------------------
CppFeature feature = new CppFeature(gmlId_string);
feature.setGeometry(geometry);
feature.setLoD(geometry.getLod());
// ----------------------------------------------
// start healing method
// ----------------------------------------------
CppPolygonHealing cppPolygonHealing = new CppPolygonHealing(feature);
cppPolygonHealing.createCppObject();
CppHealResult healResult = cppPolygonHealing.healPlanarity(polyIds);
if(healResult.isEmpty())
return false;
healResult.mergeIntoGeometry(geometry);
// ----------------------------------------------
// dispose cpp Objects
// ----------------------------------------------
cppPolygonHealing.disposeCppObject();
feature.disposeCppObject();
return true;
}
@Override
public HealingID getID() {
return HealingID.P_NON_PLANAR_POLYGON_CPP;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.HashSet;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class HealPlanarPolygonError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealPlanarPolygonError.class);
@Override
public boolean visit(NonPlanarPolygonDistancePlaneError err, ModificationListener l) {
logger.debug("Executing Repair for NonPlanarPolygonDistancePlaneError");
Plane plane = err.getPlane();
Polygon p = err.getPolygon();
Set<Vertex> vertices = new HashSet<>();
// collect all vertices, eliminating duplicates
collectVertices(p, vertices);
// project each vertex to the plane and change the coordinates of existing
// vertices
for (Vertex v : vertices) {
Vector3d newPoint = plane.projectPointToPlane(v);
v.setX(newPoint.getX());
v.setY(newPoint.getY());
v.setZ(newPoint.getZ());
}
return true;
}
private void collectVertices(Polygon p, Set<Vertex> vertices) {
collectVertices(p.getExteriorRing(), vertices);
for (LinearRing lr : p.getInnerRings()) {
collectVertices(lr, vertices);
}
}
private void collectVertices(LinearRing ring, Set<Vertex> vertices) {
for (Vertex v : ring.getVertices()) {
vertices.add(v);
}
}
@Override
public HealPlanarPolygonError createNew() {
return new HealPlanarPolygonError();
}
@Override
public HealingID getID() {
return HealingID.P_NON_PLANAR_POLYGON;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import org.citygml4j.core.model.construction.AbstractConstructionSurface;
import org.citygml4j.core.model.construction.GroundSurface;
import org.citygml4j.core.model.construction.RoofSurface;
import org.citygml4j.core.model.construction.WallSurface;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.PolygonWithoutSurfaceError;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
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.datastructure.LinkedPolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class HealPolygonWithoutSurface implements HealingMethod {
@Override
public boolean visit(PolygonWithoutSurfaceError err, ModificationListener l) {
Polygon polygon = err.getPolygon();
if (!(polygon instanceof ConcretePolygon cp)) {
return false;
}
CityObject parent = polygon.getParent().getParent();
if (!(parent instanceof AbstractBuilding ab)) {
return false;
}
Vector3d n = polygon.calculateNormal();
double tilt = Math.acos(n.getZ() / n.getLength()) * 180 / Math.PI;
AbstractConstructionSurface acs;
BoundarySurface bs = new BoundarySurface(null);
if (tilt < 75.0) {
acs = new RoofSurface();
bs.setType(BoundarySurfaceType.ROOF);
} else if (tilt > 75.0 && tilt < 170.0) {
acs = new WallSurface();
bs.setType(BoundarySurfaceType.WALL);
} else if (tilt > 170.0) {
acs = new GroundSurface();
bs.setType(BoundarySurfaceType.GROUND);
} else {
return false;
}
bs.setGmlObject(acs);
addBoundarySurfaceToBuilding(cp, ab, bs);
ab.getGmlObject().getBoundaries().add(new AbstractSpaceBoundaryProperty(acs));
return true;
}
private void addBoundarySurfaceToBuilding(ConcretePolygon cp, AbstractBuilding ab, BoundarySurface bs) {
Geometry parentGeometry = cp.getParent();
parentGeometry.getPolygons().remove(cp);
ab.addBoundarySurface(bs);
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, parentGeometry.getLod());
bs.addGeometry(geom);
geom.addPolygon(cp);
parentGeometry.addPolygon(new LinkedPolygon(cp, parentGeometry));
}
@Override
public HealingID getID() {
return HealingID.SE_POLYGON_WITHOUT_SURFACE;
}
@Override
public HealingMethod createNew() {
return new HealPolygonWithoutSurface();
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.PolygonWrongOrientationError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.utils.Pair;
public class HealPolygonWrongOrientationError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealPolygonWrongOrientationError.class);
private Random r = new Random();
@Override
public boolean visit(PolygonWrongOrientationError err, ModificationListener l) {
logger.debug("Executing Repair for PolygonWrongOrientationError");
Map<Polygon, AtomicInteger> polygonCounter = new HashMap<>();
for (Edge e : err.getEdges()) {
for (Polygon p : e.getAdjacentPolygons()) {
AtomicInteger counter = polygonCounter.computeIfAbsent(p, k -> new AtomicInteger(0));
counter.incrementAndGet();
}
}
LinkedList<Pair<Polygon, AtomicInteger>> data = new LinkedList<>();
for (Entry<Polygon, AtomicInteger> e : polygonCounter.entrySet()) {
data.add(new Pair<>(e.getKey(), e.getValue()));
}
Collections.sort(data, (o1, o2) -> o2.getValue1().get() - o1.getValue1().get());
Pair<Polygon, AtomicInteger> item = data.pop();
// stupid random stuff
// attempt to avoid creating an endless loop in some cases by reversing the same
// polygon over and over again
// choose a polygon randomly from a list, all must have the same number of
// faulty edges connected to them
List<Pair<Polygon, AtomicInteger>> samePolygons = new ArrayList<>();
samePolygons.add(item);
for (Pair<Polygon, AtomicInteger> temp : data) {
if (temp.getValue1().get() == item.getValue1().get()) {
samePolygons.add(temp);
} else {
break;
}
}
int index = r.nextInt(samePolygons.size());
Polygon p = samePolygons.get(index).getValue0();
Collections.reverse(p.getExteriorRing().getVertices());
for (LinearRing lr : p.getInnerRings()) {
Collections.reverse(lr.getVertices());
}
return true;
}
@Override
public HealingMethod createNew() {
return new HealPolygonWrongOrientationError();
}
@Override
public HealingID getID() {
return HealingID.S_POLYGON_WRONG_ORIENTATION;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.RingNotClosedError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
public class HealRingNotClosedError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealRingNotClosedError.class);
@Override
public boolean visit(RingNotClosedError err, ModificationListener l) {
logger.debug("Executing Repair for RingNotClosedError");
LinearRing ring = err.getRing();
// add another point to close the ring
// Geometry doesn't matter
// only used for adjacency, but vertex already contained in ring
ring.addVertex(ring.getVertices().get(0));
return true;
}
@Override
public HealRingNotClosedError createNew() {
return new HealRingNotClosedError();
}
@Override
public HealingID getID() {
return HealingID.R_NOT_CLOSED;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.HealingID;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError;
import de.hft.stuttgart.citydoctor2.check.error.RingDuplicatePointError;
import de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.math.graph.KDTree;
public class HealRingSelfIntError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealRingSelfIntError.class);
@Override
public boolean visit(RingEdgeIntersectionError err, ModificationListener l) {
logger.info("Executing Repair for RingEdgeIntersectionError");
// introduce a new vertex and create two rings out of one
LinearRing r = err.getRing();
Geometry geom = r.getParent().getParent();
LinearRing r1 = new LinearRing(r.getType());
LinearRing r2 = new LinearRing(r.getType());
// if exterior ring -> 2 new polygons, otherwise only 2 inner rings
if (err.getRing().getType() == LinearRingType.EXTERIOR) {
ConcretePolygon p1 = new ConcretePolygon();
p1.setExteriorRing(r1);
ConcretePolygon p2 = new ConcretePolygon();
p2.setExteriorRing(r2);
geom.replacePolygon(err.getRing().getParent(), p1, p2);
} else {
Polygon parent = err.getRing().getParent();
parent.removeInnerRing(err.getRing());
parent.addInteriorRing(r1);
parent.addInteriorRing(r2);
}
LinearRing currentRing = r1;
Vertex inter = new Vertex(err.getIntersection());
inter = checkDuplicatePoints(geom, inter);
for (int i = 0; i < r.getVertices().size() - 1; i++) {
Vertex v = r.getVertices().get(i);
currentRing.addVertex(v);
Vertex nextV = r.getVertices().get(i + 1);
Edge e1 = err.getEdge1();
Edge e2 = err.getEdge2();
if (e1.formedByIgnoreDirection(v, nextV) || e2.formedByIgnoreDirection(v, nextV)) {
currentRing.addVertex(inter);
// switch rings
if (currentRing == r1) {
currentRing = r2;
} else {
currentRing = r1;
}
}
}
// close both rings
r1.addVertex(r1.getVertices().get(0));
r2.addVertex(r2.getVertices().get(0));
return true;
}
private Vertex checkDuplicatePoints(Geometry geom, Vertex inter) {
KDTree tree = new KDTree();
for (Vertex v : geom.getVertices()) {
tree.add(v);
}
Vertex newVertex = tree.getFirstNodeInRadius(inter, 0.0001);
if (newVertex == null) {
newVertex = inter;
}
return newVertex;
}
@Override
public boolean visit(RingDuplicatePointError err, ModificationListener l) {
logger.info("Executing Repair for RingDuplicatePointError");
LinearRing r = err.getRing();
// merge points together in case they are different
Vertex duplicate1 = err.getVertex1();
Vertex duplicate2 = err.getVertex2();
double x = (duplicate1.getX() + duplicate2.getX()) / 2;
double y = (duplicate1.getY() + duplicate2.getY()) / 2;
double z = (duplicate1.getZ() + duplicate2.getZ()) / 2;
Vector3d avgPoint = new Vector3d(x, y, z);
duplicate1.setX(avgPoint.getX());
duplicate1.setY(avgPoint.getY());
duplicate1.setZ(avgPoint.getZ());
duplicate2.setX(avgPoint.getX());
duplicate2.setY(avgPoint.getY());
duplicate2.setZ(avgPoint.getZ());
// create 2 new rings touching in the duplicate point
LinearRing r1 = new LinearRing(r.getType());
LinearRing r2 = new LinearRing(r.getType());
if (r.getType() == LinearRingType.EXTERIOR) {
createNewPolygonsFromRings(r, r1, r2);
} else {
// replace inner ring with two new ones
r.getParent().removeInnerRing(r);
r.getParent().addInteriorRing(r1);
r.getParent().addInteriorRing(r2);
}
LinearRing currentRing = r1;
// do not use the last vertex
for (int i = 0; i < r.getVertices().size() - 1; i++) {
Vertex v = r.getVertices().get(i);
if (v == duplicate1 || v == err.getVertex2()) {
// switch rings
if (currentRing == r2) {
currentRing = r1;
} else {
currentRing = r2;
}
}
currentRing.addVertex(v);
}
// close both rings
r1.addVertex(r1.getVertices().get(0));
r2.addVertex(r2.getVertices().get(0));
eliminateIdenticalPointsWithDifferentInstance(err.getRing().getParent().getParent());
return true;
}
private void eliminateIdenticalPointsWithDifferentInstance(Geometry geom) {
Map<Vertex, Vertex> vertexMap = new HashMap<>();
for (Polygon p : geom.getPolygons()) {
checkRing(p.getExteriorRing(), vertexMap);
for (LinearRing ring : p.getInnerRings()) {
checkRing(ring, vertexMap);
}
}
}
private void checkRing(LinearRing ring, Map<Vertex, Vertex> vertexMap) {
for (int i = 0; i < ring.getVertices().size(); i++) {
Vertex v = ring.getVertices().get(i);
if (vertexMap.containsKey(v)) {
ring.getVertices().set(i, vertexMap.get(v));
} else {
vertexMap.put(v, v);
}
}
}
private void createNewPolygonsFromRings(LinearRing r, LinearRing r1, LinearRing r2) {
Polygon p = r.getParent();
Geometry geom = p.getParent();
// create two new polygons
ConcretePolygon p1 = new ConcretePolygon();
p1.setExteriorRing(r1);
ConcretePolygon p2 = new ConcretePolygon();
p2.setExteriorRing(r2);
// replace the old polygon with the new ones
geom.replacePolygon(p, p1, p2);
}
@Override
public boolean visit(PointTouchesEdgeError err, ModificationListener l) {
logger.debug("Executing Repair for PointTouchesEdgeError");
Edge e = err.getEdge();
for (LinearRing r : e.getAdjacentRings()) {
int index = -1;
for (int i = 0; i < r.getVertices().size() - 1; i++) {
Vertex v1 = r.getVertices().get(i);
Vertex v2 = r.getVertices().get(i + 1);
if (e.getOppositeVertex(v1) == v2) {
index = i;
break;
}
}
if (index == -1) {
StringBuilder sb = new StringBuilder("Ring does not contain edge.\n");
sb.append("Ring: ");
sb.append(r.getGmlId().toString());
sb.append('\n');
for (Vertex v : r.getVertices()) {
sb.append(v);
sb.append('\n');
}
sb.append("Edge:");
sb.append('\n');
sb.append(e.getFrom());
sb.append('\n');
sb.append(e.getTo());
throw new IllegalStateException(sb.toString());
}
r.getVertices().add(index + 1, err.getVertex());
}
return true;
}
@Override
public HealRingSelfIntError createNew() {
return new HealRingSelfIntError();
}
@Override
public HealingID getID() {
return HealingID.R_SELF_INTERSECTION;
}
}
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