Commit ffdae21a authored by Riegel's avatar Riegel
Browse files

Open Source release of CityDoctor GUI

parent a5a82382
Pipeline #10029 failed with stage
in 8 seconds
requirements:
R_GE_R_TOO_FEW_POINTS:
enabled: true
R_GE_R_NOT_CLOSED:
enabled: true
R_GE_R_CONSECUTIVE_POINTS_SAME:
enabled: true
R_GE_R_SELF_INTERSECTION:
enabled: true
R_GE_S_MULTIPLE_CONNECTED_COMPONENTS:
enabled: true
R_GE_P_INTERIOR_DISCONNECTED:
enabled: true
R_GE_P_INTERSECTING_RINGS:
enabled: true
R_GE_P_NON_PLANAR:
enabled: true
parameters:
# one of ("distance", "angle", "both")
type: distance
# in m
distanceTolerance: 0.01
# in degree
angleTolerance: 1
R_GE_P_HOLE_OUTSIDE:
enabled: true
R_GE_P_ORIENTATION_RINGS_SAME:
enabled: true
R_GE_P_INNER_RINGS_NESTED:
enabled: true
R_GE_S_TOO_FEW_POLYGONS:
enabled: true
R_GE_S_NOT_CLOSED:
enabled: true
R_GE_S_NON_MANIFOLD_EDGE:
enabled: true
R_GE_S_POLYGON_WRONG_ORIENTATION:
enabled: true
R_GE_S_ALL_POLYGONS_WRONG_ORIENTATION:
enabled: true
R_GE_S_NON_MANIFOLD_VERTEX:
enabled: true
R_GE_S_SELF_INTERSECTION:
enabled: true
R_SE_BS_IS_WALL:
enabled: false
parameters:
lowerAngle: '45'
upperAngle: '135'
R_SE_BS_IS_FLOOR:
enabled: false
R_SE_BS_GROUND_UNFRAGMENTED:
enabled: false
R_SE_BS_IS_GROUND:
enabled: false
R_SE_BS_IS_CEILING:
enabled: false
globalParameters:
numberOfRoundingPlaces: 8
# in m
minVertexDistance: 0.0001
schematronPath: 'checkForSolid.xml'
useStreaming: true
xmlValidation: false
/** --------------------------------------------------
* Hochschule f�r Technik Stuttgart
* Fachbereich Vermessung , Informatik und Mathematik
* Schellingstr . 24
* D - 70174 Stuttgart
*
* Projekt CityDoktor
*
* Copyright (c) 2011 HFT Stuttgart. All rights reserved.
* HFT Stuttgart and its licensors retain all intellectual property and
* proprietary rights in and to this software and related documentation.
* Any use, reproduction, disclosure, or distribution of this software
* and related documentation without an express license agreement from
* HFT Stuttgart is strictly prohibited.
*
* Please refer to the applicable HFT Stuttgart end user license agreement (EULA)
* associated with this source code for terms and conditions that govern
* your use of this HFT Stuttgart software.
*
* 11.09.2012
* bogdahn
* @author
* @version
*
*/
package de.hft.stuttgart.citydoctor2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.connect.edge.NativeException;
/**
* @author bogdahn
*
*/
public class CppInitializer {
private static final String SUN_ARCH_DATA_MODEL = "sun.arch.data.model";
private static Logger logger = LogManager.getLogger(CppInitializer.class);
private static native boolean initCppLibrary() throws NativeException;
private static native boolean checkVersions(String jdkVersion) throws NativeException;
public static synchronized void initCpp() {
// init CPP JNI Checks
String dataModel = System.getProperty(SUN_ARCH_DATA_MODEL);
if (dataModel.equals("64")) {
// System.loadLibrary( "CityDoctor2CPP-windows-x64-sgd" ); // debug dll
System.loadLibrary("../CityDoctorHealer/lib/CityDoctor2CPP-windows-x64-s");
// System.loadLibrary("CityDoctor2CPP-windows-x64-s");
// System.loadLibrary("lib/libTestHeal");
} else if (dataModel.equals("32")) {
// System.loadLibrary( "CityDoctor2CPP-windows-x86-sgd" ); // debug dll
// System.loadLibrary("../CityDoctorHealer/lib/CityDoctor2CPP-windows-x86-s");
// System.loadLibrary("CityDoctor2CPP-windows-x86-s");
System.err.println("Load native library failed.");
System.err.println("Unknown architecture: " + dataModel);
System.err.println("Valid values are: \"64\"");
System.err.println("No library will be loaded");
} else {
System.err.println("Load native library failed.");
System.err.println("Unknown architecture: " + dataModel);
System.err.println("Valid values are: \"64\"");
System.err.println("No library will be loaded");
}
try {
if (initCppLibrary()) {
// checkVersions(System.getProperty("java.version")); // TODO : checkVersions
// funktioniert noch nicht!!
}
} catch (NativeException ne) {
System.err.println("Native Exception");
System.err.println(ne.getType());
System.err.println(ne.getMessage());
ne.printStackTrace();
System.exit(1);
} catch (IllegalArgumentException e) {
e.printStackTrace();
System.exit(1);
}
}
private CppInitializer() {
// only static utility
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* @author wewetzer
* @version 1.0
*/
public class CppFeature extends CppReferenceHandlingBase {
private native long createCppFeature(String type) throws OutOfMemoryError;
private native void disposeCppFeature(long ptrToCppFeature) throws NativePointerCastException;
// setting geometry and attributes
private native boolean setGeometry(long ptrToFeature, int geomType, double[] pointArray, int[] pointIdMap,
int[] polygonArray, int[] polygonIdMap) throws NativeException;
private native boolean setLoD(long ptrToFeature, int LoD) throws NativeException;
// gets the id of a corresponding C++ entity to a given Java entity id
private native int getCppPointId(long ptrToFeature, int javaPointId) throws NativeException;
private native int getCppPolygonId(long ptrToFeature, int javaPolygonId) throws NativeException;
private native double[] getPoints(long ptrToFeature) throws NativeException;
// debug native methods
private native void printMesh(long ptrToFeature) throws NativeException;
private native void writeMeshToInventor(long ptrToFeature, String fileName) throws NativeException;
private String type;
public CppFeature(String type) {
this.type = type;
createCppObject();
}
/**
* Creates an C++ feature object
*
* @throws IllegalStateException Thrown if the encapsulated "long-Pointer"
* isn't 0
* @throws NativeOutOfMemoryException If there is no more memory on the native
* side
*
* @see {@link disposeCppObject}
* @see de.hft.stuttgart.citydoctor2.connect.edge.CppReferenceHandlingBase#createCppObject()
*/
private void createCppObject() {
if (0L != ptrToCppObject) {
throw new IllegalStateException("Can't create new C++ object, there is already one!");
}
// Maybe an exception is thrown
ptrToCppObject = createCppFeature(type);
}
@Override
public void disposeCppObject() throws NativePointerCastException {
if (0L != ptrToCppObject) {
disposeCppFeature(ptrToCppObject);
ptrToCppObject = 0L;
}
}
public void setGeometry(Geometry geometry) throws NativeException {
checkNullPointer();
CppGeometryData geometryData = createCppFeatureInput(geometry);
setGeometry(ptrToCppObject, 0, geometryData.pointArr, geometryData.pointIdMap, geometryData.polygonArr, geometryData.polygonIdMap);
}
public void setLoD(Lod lod) throws NativeException {
checkNullPointer();
int LoD = -1;
if(lod == Lod.LOD0) {
LoD = 0;
}
else if(lod == Lod.LOD1) {
LoD = 1;
}
else if(lod == Lod.LOD2) {
LoD = 2;
}
else if(lod == Lod.LOD3) {
LoD = 3;
}
else if(lod == Lod.LOD4) {
LoD = 4;
}
setLoD(ptrToCppObject, LoD);
}
/**
* Returns the id of the corresponding C++ point to a given Java point id
*
* @param javaPointId The given Java point id
* @return The corresponding C++ point id
* @throws IllegalArgumentException If the feature pointer is 0
* @throws NativeException If an error occurred during the execution of
* the native code
*/
public int getCppPointId(int javaPointId) throws NativeException {
checkNullPointer();
return this.getCppPointId(ptrToCppObject, javaPointId);
}
/**
* Returns the id of the corresponding C++ polygon to a given java polygon
* id
*
* @param javaPolygonId The given Java polygon id
* @return The corresponding C++ polygon id
* @throws IllegalArgumentException If the feature pointer is 0
* @throws NativeException If an error occurred during the execution of
* the native code
*/
public int getCppPolygonId(int javaPolygonId) throws NativeException {
checkNullPointer();
return this.getCppPolygonId(ptrToCppObject, javaPolygonId);
}
public double[] getPoints() throws NativeException {
checkNullPointer();
return this.getPoints(ptrToCppObject);
}
/**
* Debug method, that prints the mesh
*
* @throws IllegalArgumentException If the feature pointer is 0
* @throws NativeException If an error occurred during the execution of
* the native code
*/
public void printMesh() throws NativeException {
checkNullPointer();
this.printMesh(ptrToCppObject);
}
/**
* Writes the current stored level of detail 1 mesh in the file "filename.iv" as
* Open Inventor file.
*
* @param fileName The name of the output file
* @throws IllegalArgumentException If the feature pointer is 0
* @throws NativeException If an error occurred during the execution of
* the native code n
*/
public void writeMeshToInventor(String fileName) throws NativeException {
checkNullPointer();
this.writeMeshToInventor(ptrToCppObject, fileName);
}
/**
* Writes the current stored level of detail 2 mesh in the file "filename.iv" as
* Open Inventor file.
*
* @param fileName The name of the output file
* @throws IllegalArgumentException If the feature pointer is 0
* @throws NativeException If an error occurred during the execution of
* the native code n
*/
private void printGeomDataAsCppCode(CppGeometryData cppGeoData) {
System.out.println("\n \n \n");
double[] pnts = cppGeoData.pointArr;
System.out.println("const size_t numCoords = " + pnts.length / 3 + ";");
System.out.println("Coordinate3d * coords[" + pnts.length / 3 + "];");
for (int i = 0; i < pnts.length; i += 3) {
System.out.print("coords[ " + i / 3 + " ] = new Coordinate3d( ");
System.out.println(pnts[i] + ", " + pnts[i + 1] + ", " + pnts[i + 2] + " );");
}
System.out.println("");
int[] polys = cppGeoData.polygonArr;
System.out.println("const size_t numPolys = " + cppGeoData.polygonIdMap.length + ";");
System.out.println("const size_t sizePolys = " + polys.length + ";");
System.out.print("int polyArr[] = {");
for (int i = 0; i < polys.length - 1; ++i) {
if (-1 == polys[i]) {
System.out.println();
System.out.print(" " + polys[i] + ", ");
} else if (-2 == polys[i]) {
System.out.print(" " + polys[i] + ", ");
} else {
System.out.print(" " + polys[i] + ", ");
}
}
System.out.println(" " + polys[polys.length - 1] + " \n};\n");
if (cppGeoData.boundarySurfaceTypeArr.length > 0) {
System.out.println("BoundarySurfaceType types[numPolys] = {");
for (int i = 0; i < cppGeoData.boundarySurfaceTypeArr.length; i++) {
switch (cppGeoData.boundarySurfaceTypeArr[i]) {
case (1): {
System.out.println(" roofsurface,");
break;
}
case (2): {
System.out.println(" wallsurface,");
break;
}
case (3): {
System.out.println(" groundsurface,");
break;
}
default: {
System.out.println(" undefined,");
}
}
;
}
System.out.println("};");
System.out.println("return createHouse( coords, numCoords, polyArr, numPolys, sizePolys, types, true);");
}
}
/**
* generates all the necessary geometric input for the CppFeature
*
* @param geom
*/
private CppGeometryData createCppFeatureInput(Geometry geometry) {
// ====================================================================
// Vertices
List<Vertex> vertices = geometry.getVertices();
double[] pointArr = new double[vertices.size() * 3];
int[] pointIdMap = new int[vertices.size()];
Map<Vertex, Integer> vertexToIndexMap = new HashMap<>(vertices.size());
for (int i = 0; i < vertices.size(); i++) {
Vertex v = vertices.get(i);
vertexToIndexMap.put(v, i);
pointArr[i * 3 + 0] = v.getX();
pointArr[i * 3 + 1] = v.getY();
pointArr[i * 3 + 2] = v.getZ();
pointIdMap[i] = i;
}
// ====================================================================
// Polygons & BoundarySurfaceType
List<Polygon> polygons = geometry.getPolygons();
int numReferencedVertices = 0;
int[] polygonIdMap = new int[polygons.size()];
for (int i = 0; i < polygons.size(); i++) {
Polygon p = polygons.get(i);
polygonIdMap[i] = i;
numReferencedVertices += p.getExteriorRing().getVertices().size();
for (LinearRing lr : p.getInnerRings()) {
numReferencedVertices += lr.getVertices().size();
}
}
int[] polygonArr = new int[numReferencedVertices];
int[] boundarySurfaceTypeArr = new int[polygons.size()];
int polyCounter = 0;
for (int polyIndex = 0; polyIndex < polygons.size(); polyIndex++) {
Polygon p = polygons.get(polyIndex);
// exterior ring = -1
polygonArr[polyCounter++] = -1;
for (int i = 0; i < p.getExteriorRing().getVertices().size() - 1; i++) {
Vertex v = p.getExteriorRing().getVertices().get(i);
int id = vertexToIndexMap.get(v);
polygonArr[polyCounter++] = id;
}
for (LinearRing lr : p.getInnerRings()) {
// interior ring = -2
polygonArr[polyCounter++] = -2;
for (int i = 0; i < lr.getVertices().size() - 1; i++) {
Vertex v = lr.getVertices().get(i);
int id = vertexToIndexMap.get(v);
polygonArr[polyCounter++] = id;
}
}
if (geometry.getLod() == Lod.LOD2) {
BoundarySurface bs = p.getPartOfSurface();
if (bs != null) {
boundarySurfaceTypeArr[polyIndex] = bs.getType().ordinal();
} else {
boundarySurfaceTypeArr[polyIndex] = 999999;
}
}
}
return new CppGeometryData(pointArr, pointIdMap, polygonArr, polygonIdMap, boundarySurfaceTypeArr);
}
private class CppGeometryData {
private double[] pointArr;
private int[] pointIdMap;
private int[] polygonArr;
private int[] polygonIdMap;
private int[] boundarySurfaceTypeArr;
private CppGeometryData(double[] pointArr, int[] pointIdMap, int[] polygonArr, int[] polygonIdMap,
int[] bfTypeArr) {
this.pointArr = pointArr;
this.pointIdMap = pointIdMap;
this.polygonArr = polygonArr;
this.polygonIdMap = polygonIdMap;
this.boundarySurfaceTypeArr = bfTypeArr;
}
}
public String getType() {
return type;
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
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;
public class CppHealResult extends CppReferenceHandlingBase {
private native void delete(long ptr);
private native int[] getChangedOrAddedVertexIds(long ptr);
private native double[] getChangedOrAddedVertexValues(long ptr);
private native int[] getChangedOrAddedPolygonIds(long ptr);
private native int[] getChangedOrAddedPolygon(long ptr, int id);
public CppHealResult(long ptr) {
ptrToCppObject = ptr;
}
@Override
public void disposeCppObject() {
if (ptrToCppObject != 0) {
delete(ptrToCppObject);
ptrToCppObject = 0;
}
}
public void mergeIntoGeometry(Geometry geom) {
Map<Integer, Vertex> vertexMap = updateVertices(geom);
int[] changedPolygons = getChangedOrAddedPolygonIds(ptrToCppObject);
List<Polygon> deletedPolygons = new ArrayList<>();
for (int i = 0; i < changedPolygons.length; i++) {
int polyId = changedPolygons[i];
int[] vertices = getChangedOrAddedPolygon(ptrToCppObject, polyId);
if (vertices == null || vertices.length == 0) {
if (polyId >= geom.getPolygons().size()) {
throw new IllegalStateException("Cannot delete a polygon which does not exist");
}
Polygon p = geom.getPolygons().get(polyId);
deletedPolygons.add(p);
} else {
Polygon p;
if (polyId < geom.getPolygons().size()) {
p = geom.getPolygons().get(polyId);
// empty the polygon
p.removeRings();
} else {
p = new ConcretePolygon();
geom.addPolygon(p);
}
createPolygon(geom, vertexMap, vertices, p);
}
}
for (Polygon p : deletedPolygons) {
geom.getPolygons().remove(p);
}
}
private void createPolygon(Geometry geom, Map<Integer, Vertex> vertexMap, int[] vertices, Polygon p) {
LinearRing lr = null;
for (int j = 0; j < vertices.length; j++) {
if (vertices[j] == -1) {
if (p.getExteriorRing() != null) {
throw new IllegalStateException("Cannot create a polygon with more than 1 exterior rings");
}
lr = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
} else if (vertices[j] == -2) {
lr = new LinearRing(LinearRingType.INTERIOR);
p.addInteriorRing(lr);
} else {
Objects.requireNonNull(lr, "No ring type specified, before supplying vertices");
int index = vertices[j];
Vertex v;
if (index < geom.getVertices().size()) {
v = geom.getVertices().get(index);
} else {
v = vertexMap.get(index);
}
Objects.requireNonNull(v, "Polygon references vertex, which does not exist");
lr.addVertex(v);
}
}
// close the rings
p.getExteriorRing().addVertex(p.getExteriorRing().getVertices().get(0));
for (LinearRing inner : p.getInnerRings()) {
inner.addVertex(inner.getVertices().get(0));
}
}
private Map<Integer, Vertex> updateVertices(Geometry geom) {
int[] changedVertices = getChangedOrAddedVertexIds(ptrToCppObject);
double[] changedVertexValues = getChangedOrAddedVertexValues(ptrToCppObject);
Map<Integer, Vertex> vertexMap = new HashMap<>();
for (int i = 0; i < changedVertices.length; i++) {
int id = changedVertices[i];
double x = changedVertexValues[i * 3 + 0];
double y = changedVertexValues[i * 3 + 1];
double z = changedVertexValues[i * 3 + 2];
if (id >= geom.getVertices().size()) {
// new vertex
Vertex newV = new Vertex(x, y, z);
vertexMap.put(id, newV);
System.out.println("New Vertex: " + newV);
} else {
// change vertex
Vertex changedV = geom.getVertices().get(id);
System.out.print("Changed Vertex from " + changedV + " to ");
changedV.setX(x);
changedV.setY(y);
changedV.setZ(z);
System.out.println(changedV);
}
}
return vertexMap;
}
public void print() {
int[] changedVertices = getChangedOrAddedVertexIds(ptrToCppObject);
System.out.println("Changed or added vertex ids: " + Arrays.toString(changedVertices));
double[] changedVertexValues = getChangedOrAddedVertexValues(ptrToCppObject);
if (changedVertices.length * 3 != changedVertexValues.length) {
throw new IllegalStateException("Number of indices and number of values do not match");
}
for (int i = 0; i < changedVertices.length; i++) {
System.out.println(String.format("Vertex %d = [%f, %f, %f]", changedVertices[i],
changedVertexValues[i * 3 + 0], changedVertexValues[i * 3 + 1], changedVertexValues[i * 3 + 2]));
}
System.out.println();
int[] changedPolygons = getChangedOrAddedPolygonIds(ptrToCppObject);
System.out.println("Changed or added polygon ids: " + Arrays.toString(changedPolygons));
for (int i = 0; i < changedPolygons.length; i++) {
int polyId = changedPolygons[i];
System.out.println("Polygon " + polyId + ":");
int[] vertices = getChangedOrAddedPolygon(ptrToCppObject, polyId);
if (vertices == null || vertices.length == 0) {
System.out.println("deleted");
} else {
for (int j = 0; j < vertices.length; j++) {
if (vertices[j] == -1) {
System.out.println("Exterior ring:");
} else if (vertices[j] == -2) {
System.out.println("Interior ring:");
} else {
System.out.println(vertices[j]);
}
}
}
}
}
public boolean isEmpty() {
int[] changedVertices = getChangedOrAddedVertexIds(ptrToCppObject);
int[] changedPolygons = getChangedOrAddedPolygonIds(ptrToCppObject);
if(changedVertices.length == 0 && changedPolygons.length == 0)
return true;
return false;
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
public abstract class CppHealing extends CppReferenceHandlingBase {
// Checks will operate on this VPDFeature
CppFeature feature;
public CppHealing(CppFeature feature) {
this.feature = feature;
}
public CppFeature getNestedFeature() {
return feature;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
//*************************************************************************************
// vorerst auskommentiert, da CppChecks nicht mehr verwendet wird
// @Override
// public boolean equals(Object obj)
// {
// if (obj instanceof CppChecks)
// {
// return (super.equals(obj) && (feature == ((CppChecks)obj).feature));
// }
// else
// {
// return super.equals(obj);
// }
// }
//**************************************************************************************
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int hash = super.hashCode();
hash = hash * 19 + feature.hashCode() * 3;
return hash;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuffer sf = new StringBuffer();
sf.append(super.toString());
sf.append("Feature:\n\t");
sf.append(feature);
return sf.toString();
}
}
\ No newline at end of file
package de.hft.stuttgart.citydoctor2.connect.edge;
public enum CppHealingResultChangeType {
ADD, DELETE, CHANGE, REPLACE
}
package de.hft.stuttgart.citydoctor2.connect.edge;
public enum CppHealingResultType {
VERTEX, POLYGON, EDGE
}
package de.hft.stuttgart.citydoctor2.connect.edge;
// im alten CD:
//
public class CppPolygonHealing extends CppHealing {
// ----- native methods
// create and release stored C++ polygon check object
private native long createCppPolygonHealing(long ptrToCppFeature) throws OutOfMemoryError;
private native void disposeCppPolygonHealing(long ptrToCppFeature) throws NativePointerCastException;
// healing
private native long healPlanarity(long ptrToCppFeature, int[] polyIds)
throws NativeException, IllegalArgumentException;
private native long healSolidNotClosed(long ptrToCppFeature)
throws NativeException, IllegalArgumentException;
public CppPolygonHealing(CppFeature feature) {
super(feature);
}
/**
* Creates an C++ object to use the native solid check methods.
*
* @throws IllegalStateException Thrown if the encapsulated "long-Pointer" isn't
* 0
* @throws OutOfMemoryError If there is no more memory on the native side
*
* @see {@link disposeCppObject}
* @see de.hft.stuttgart.citydoctor.connect.edGe.CppReferenceHandlingBase#createCppObject()
*/
public void createCppObject() throws IllegalStateException, OutOfMemoryError {
if (0L != ptrToCppObject) {
throw new IllegalStateException("Can't create new C++ object, there is allready one!");
}
// Maybe an exception is thrown and there should be no crap in ptrToCppObject
long tmpPointer = createCppPolygonHealing(feature.ptrToCppObject);
ptrToCppObject = tmpPointer;
}
@Override
public void disposeCppObject() throws NativePointerCastException {
if (0L != ptrToCppObject) {
disposeCppPolygonHealing(this.ptrToCppObject);
ptrToCppObject = 0L;
}
}
public CppHealResult healPlanarity(int[] polyIds) throws NativeException, IllegalArgumentException {
checkNullPointer();
return new CppHealResult( healPlanarity( this.feature.ptrToCppObject, polyIds ));
}
public CppHealResult healSolidNotClosed() throws NativeException, IllegalArgumentException{
checkNullPointer();
return new CppHealResult( healSolidNotClosed( this.feature.ptrToCppObject ));
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
/**
* This class is part of the JNI interface which controls the access to the
* check- and heal kernel in native C++ code.<br>
* It's represent a base class for classes which encapsulates a C++ object.
* Therefor two abstract methods {@link createCppObject} and
* {@link disposeCppObject} are defined, to create and dispose a C++ object. All
* non-abstract subclasses are requested to implement at least the two native
* methods<br>
* <br>
* <code>
* native long create[MeaningfulName](long ptrToCppFeature);<br>
* native void dispose[MeaningfulName](long ptrToCppPolygonCheck);
* </code><br>
* <br>
*
* @author wewetzer
* @version 1.0
*
*/
public abstract class CppReferenceHandlingBase {
// This long holds the address of an C++ object
long ptrToCppObject = 0L;
/**
* This method frees the occupied memory of the created C++ object. The method
* should be called after the instance of an child class is nor longer needed,
* to avoid memory leaks.
*
* @see {@link createCppObject}
*/
public abstract void disposeCppObject();
/**
* Checks if the pointer to the corresponding C++ object is 0. In this case an
* exception is thrown. This method should be called at first in every method,
* that uses native methods!
*
* @throws IllegalArgumentException if the pointer to the corresponding C++
* object i 0
*/
public void checkNullPointer() {
if (0L == ptrToCppObject) {
throw new IllegalArgumentException("C++ object pointer is null");
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CppReferenceHandlingBase other = (CppReferenceHandlingBase) obj;
return ptrToCppObject == other.ptrToCppObject;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
if (0L != ptrToCppObject) {
disposeCppObject();
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (ptrToCppObject ^ (ptrToCppObject >>> 32));
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return ("ptrToCppObject\n\t" + ptrToCppObject + "\n");
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
public class CppTestHeal {
private CppTestHeal() {
// only static usage
}
private static native long healGeometry(long ptrToCppFeature);
public static CppHealResult healGeometry(CppFeature feature) {
return new CppHealResult(healGeometry(feature.ptrToCppObject));
}
}
package de.hft.stuttgart.citydoctor2.connect.edge;
/**
* This class represents the parent class of all exceptions thrown by the native
* c++ library for the check- and healkernel for the project CityDoctor. An
* additionally sting field has been added to identify the thrown Exception, if
* not a specialized exception has been caught. This keeps the code clearly
* arranged, because most of the exception that will be thrown represents an
* severe error in C++, so there it doesn't makes sense to carry on with the
* java program
*
* @author wewetzer
* @version 1.0
*/
public abstract class NativeException extends RuntimeException {
private static final long serialVersionUID = 443321035265866910L;
String exceptionType = "not specified";
/**
* Standard constructor.
*/
public NativeException() {
super("Message not specified");
}
/**
* Constructs a NativeException with the given message an the given type
*
* @param message The message of the exception
*/
public NativeException(String message) {
super(message);
}
/**
* Returns the type of the exception
*
* @return The type of the exception
*/
public String getType() {
return exceptionType;
}
}
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;
}
}
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