Commit 81be0b1d authored by Riegel's avatar Riegel
Browse files

Merge branch 'dev_GUI' into 'dev'

Open source release of CityDoctorGUI and other extensions.

See merge request !6
parents 12d96d95 5a4d0a74
Pipeline #10056 passed with stage
in 1 minute and 6 seconds
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;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.Collections;
import java.util.List;
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.PolygonSameOrientationError;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
public class HealSameOrientationError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealSameOrientationError.class);
@Override
public boolean visit(PolygonSameOrientationError err, ModificationListener l) {
logger.debug("Executing Repair for PolygonSameOrientationError");
List<Vertex> vertices = err.getInnerRing().getVertices();
Collections.reverse(vertices);
return true;
}
@Override
public HealSameOrientationError createNew() {
return new HealSameOrientationError();
}
@Override
public HealingID getID() {
return HealingID.P_SAME_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.SolidNotClosedError;
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;
public class HealSolidNotClosedCpp implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealSolidNotClosedCpp.class);
@Override
public HealingMethod createNew() {
return new HealSolidNotClosedCpp();
}
@Override
public boolean visit(SolidNotClosedError err, ModificationListener l) {
logger.debug("Executing Repair for PolygonSameOrientationError");
// Wenn auf CPP Seite implementiert dann das hier entfernen:
if (HealSolidNotClosedError.fixVerticesCloseToEdges(err)) {
// maybe errors are fixed don't try other fixes until next iteration
return true;
}
// Bis hier
// ----------------------------------------------
// get Geometry and Geometry informations
// ----------------------------------------------
Geometry geometry = err.getGeometry();
String gmlId_string = geometry.getParent().getGmlId().toString();
// ----------------------------------------------
// create feature and fill feature with geometry
// ----------------------------------------------
CppFeature feature = new CppFeature( gmlId_string);
// MeshSurface surface = MeshSurface.of(geometry);
// DebugUtils.printPolygon3d(surface.getPolygons().toArray(new CDPolygonNs[0]));
// DebugUtils.printGeoknechtPolygon(surface.getPolygons().toArray(new CDPolygonNs[0]));
feature.setGeometry( geometry );
feature.setLoD(geometry.getLod());
// ----------------------------------------------
// start healing method
// ----------------------------------------------
CppPolygonHealing cppPolygonHealing = new CppPolygonHealing(feature);
cppPolygonHealing.createCppObject();
CppHealResult healResult = cppPolygonHealing.healSolidNotClosed();
if(healResult.isEmpty())
return false;
healResult.mergeIntoGeometry(geometry);
// ----------------------------------------------
// dispose cpp Objects
// ----------------------------------------------
cppPolygonHealing.disposeCppObject();
feature.disposeCppObject();
return true;
}
@Override
public HealingID getID() {
return HealingID.S_NOT_CLOSED_CPP;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
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.SolidNotClosedError;
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;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class HealSolidNotClosedError implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealSolidNotClosedError.class);
private static double epsilon = 0.1;
@Override
public boolean visit(SolidNotClosedError err, ModificationListener l) {
logger.debug("Executing Repair for SolidNotClosedError");
if (fixVerticesCloseToEdges(err)) {
// maybe errors are fixed don't try other fixes until next iteration
return true;
}
return fixMissingPolygons(err);
}
private boolean fixMissingPolygons(SolidNotClosedError err) {
if (err.getErrorEdges().size() <= 1) {
// what to do with only one edge? Impossible?
logger.error("{} only has one error edge, cannot fix", err.getGeometry());
return false;
}
List<LinkedList<Edge>> candidatesForPolygon = new ArrayList<>();
for (Edge e0 : err.getErrorEdges()) {
for (Edge e1 : err.getErrorEdges()) {
if (e1 == e0) {
// don't check same edge
continue;
}
Vertex v0 = e0.getConnectionPoint(e1);
if (v0 != null) {
// connected edges
Vector3d ab = getVectorFromPoint(e0, v0);
Vector3d ac = getVectorFromPoint(e1, v0);
Vector3d norm = ab.cross(ac);
Plane plane = new Plane(norm, v0);
LinkedList<Edge> connectedEdges = new LinkedList<>();
connectedEdges.add(e0);
connectedEdges.add(e1);
// collect all other edges which are in the same plane and connected
collectEdgesConnectedAndInPlane(err, e0, e1, plane, connectedEdges);
candidatesForPolygon.add(connectedEdges);
}
}
}
// build a polygon for the side where the most edges are available
if (candidatesForPolygon.isEmpty()) {
return false;
}
Collections.sort(candidatesForPolygon, (o1, o2) -> o2.size() - o1.size());
LinkedList<Edge> candidate = candidatesForPolygon.get(0);
// build a new polygon out of the connected edges
buildPolygonFromEdges(err.getGeometry(), candidate);
// have added a polygon, don't try to add more in this iteration
return true;
}
private void buildPolygonFromEdges(Geometry geom, LinkedList<Edge> connectedEdges) {
Polygon p = new ConcretePolygon();
geom.addPolygon(p);
LinearRing ext = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(ext);
// edges form a loop?
EdgeGraph graph = new EdgeGraph();
for (Edge e : connectedEdges) {
graph.addEdge(e, connectedEdges);
}
List<List<Edge>> cycles = graph.getCycles();
// edges form a loop when nr edges >= nr vertices
if (!cycles.isEmpty()) {
// loop
createRingFromLoop(cycles.get(0), ext);
} else {
// no cycle means open ring
createRingFromNotClosedEdges(connectedEdges, ext);
}
}
private void createRingFromNotClosedEdges(List<Edge> connectedEdges, LinearRing ext) {
Edge firstEdge = connectedEdges.get(0);
connectedEdges.remove(0);
ext.addVertex(firstEdge.getFrom());
ext.addVertex(firstEdge.getTo());
Vertex currentVertex = firstEdge.getTo();
boolean insertAtEnd = true;
while (!connectedEdges.isEmpty()) {
boolean addedEdge = false;
for (Edge e : connectedEdges) {
Vertex oppositeV = e.getOppositeVertex(currentVertex);
if (oppositeV != null) {
// found a connected edge
// insert point depending where the edge is connected
insertPointIntoRing(ext, insertAtEnd, oppositeV);
currentVertex = oppositeV;
connectedEdges.remove(e);
addedEdge = true;
break;
}
}
if (!addedEdge) {
if (!insertAtEnd) {
// could not add more edges to lr, stop here
// close ring
ext.addVertex(ext.getVertices().get(0));
return;
}
// no more edges in this direction, go the other direction
insertAtEnd = false;
currentVertex = firstEdge.getFrom();
}
}
// close ring
ext.addVertex(ext.getVertices().get(0));
}
private void insertPointIntoRing(LinearRing ext, boolean insertAtEnd, Vertex oppositeV) {
if (insertAtEnd) {
ext.addVertex(oppositeV);
} else {
// insert at the beginning
ext.addVertex(0, oppositeV);
}
}
private void createRingFromLoop(List<Edge> connectedEdges, LinearRing ext) {
Edge firstEdge = connectedEdges.get(0);
connectedEdges.remove(0);
ext.addVertex(firstEdge.getFrom());
ext.addVertex(firstEdge.getTo());
Vertex currentVertex = firstEdge.getTo();
while (!connectedEdges.isEmpty()) {
boolean addedEdge = false;
for (Edge e : connectedEdges) {
Vertex oppositeV = e.getOppositeVertex(currentVertex);
if (oppositeV != null) {
ext.addVertex(oppositeV);
currentVertex = oppositeV;
connectedEdges.remove(e);
addedEdge = true;
break;
}
}
if (!addedEdge) {
return;
}
}
}
private void collectEdgesConnectedAndInPlane(SolidNotClosedError err, Edge e0, Edge e1, Plane plane,
LinkedList<Edge> connectedEdges) {
for (Edge e2 : err.getErrorEdges()) {
if (e2 == e0 || e2 == e1) {
// don't check the starting edges
continue;
}
boolean connected = isConnectedToEdges(connectedEdges, e2);
if (connected && plane.getDistance(e2.getTo()) < epsilon
&& plane.getDistance(e2.getFrom()) < epsilon) {
// edge is in plane and connected
connectedEdges.add(e2);
}
}
}
private boolean isConnectedToEdges(List<Edge> connectedEdges, Edge e2) {
for (Edge planeEdge : connectedEdges) {
if (planeEdge.getConnectionPoint(e2) != null) {
return true;
}
}
return false;
}
private Vector3d getVectorFromPoint(Edge e0, Vertex v0) {
Vector3d ab;
if (v0 == e0.getTo()) {
ab = e0.getFrom().minus(v0);
} else {
ab = e0.getTo().minus(v0);
}
return ab;
}
public static boolean fixVerticesCloseToEdges(SolidNotClosedError err) {
boolean changed = false;
Geometry geom = err.getGeometry();
for (Edge e : err.getErrorEdges()) {
Segment3d seg = new Segment3d(e.getFrom(), e.getTo());
for (Vertex v : geom.getVertices()) {
if (v == e.getFrom() || v == e.getTo()) {
continue;
}
// check if there is an edge adjacent to the vertex that goes in the same
// direction
if (seg.getDistance(v) < epsilon && doParallelEdgesExist(geom, seg, v)) {
addVertexToEdge(e, v);
changed = true;
}
}
}
return changed;
}
private static boolean doParallelEdgesExist(Geometry geom, Segment3d seg, Vertex v) {
Vector3d dir = seg.getPointB().minus(seg.getPointA());
dir = dir.normalize();
for (Edge e2 : geom.getEdgesAdjacentTo(v)) {
Vector3d dir2 = e2.getTo().minus(e2.getFrom());
dir2 = dir2.normalize();
if (Math.abs(dir2.dot(dir)) > 0.99) {
// at least one edge is parallel
return true;
}
}
return false;
}
private static void addVertexToEdge(Edge e, Vertex v) {
for (LinearRing lr : e.getAdjacentRings()) {
for (int i = 0; i < lr.getVertices().size() - 1; i++) {
Vertex v1 = lr.getVertices().get(i);
Vertex v2 = lr.getVertices().get(i + 1);
if (e.formedByIgnoreDirection(v1, v2)) {
lr.addVertex(i + 1, v);
break;
}
}
}
}
@Override
public HealSolidNotClosedError createNew() {
return new HealSolidNotClosedError();
}
@Override
public HealingID getID() {
return HealingID.S_NOT_CLOSED;
}
}
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.RingTooFewPointsError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class HealTooFewPoints implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealTooFewPoints.class);
@Override
public boolean visit(RingTooFewPointsError err, ModificationListener l) {
logger.debug("Executing Repair for RingTooFewPointsError");
LinearRing ring = err.getRing();
// ring is only a line, remove ring
if (ring.getType() == LinearRingType.INTERIOR) {
ring.getParent().removeInnerRing(ring);
} else {
// remove whole polygon
ring.getParent().remove();
}
return true;
}
@Override
public HealTooFewPoints createNew() {
return new HealTooFewPoints();
}
@Override
public HealingID getID() {
return HealingID.R_TOO_FEW_POINTS;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.util.List;
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.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.Plane;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
public class HealWrongDormers implements HealingMethod {
private static final Logger logger = LogManager.getLogger(HealWrongDormers.class);
private static final double EPSILON = 0.2;
@Override
public boolean visit(MultipleConnectedComponentsError err, ModificationListener l) {
logger.debug("Executing Repair for MultipleConnectedComponentsError");
for (int i = 0; i < err.getComponents().size() - 1; i++) {
List<Polygon> component1 = err.getComponents().get(i);
for (int j = i + 1; j < err.getComponents().size(); j++) {
List<Polygon> component2 = err.getComponents().get(j);
if (checkForOverlappingPolygons(component1, component2)) {
return true;
}
}
}
return false;
}
private boolean checkForOverlappingPolygons(List<Polygon> component1,
List<Polygon> component2) {
for (Polygon p1 : component1) {
for (Polygon p2 : component2) {
if (isPolygonInOtherPolygon(p1, p2)) {
// p1 is in p2
createInnerRing(p1, p2);
return true;
}
if (isPolygonInOtherPolygon(p2, p1)) {
// p2 is in p1
createInnerRing(p2, p1);
return true;
}
}
}
return false;
}
private void createInnerRing(Polygon poly1, Polygon poly2) {
LinearRing innerRing = new LinearRing(LinearRingType.INTERIOR);
poly2.addInteriorRing(innerRing);
for (Vertex v : poly1.getExteriorRing().getVertices()) {
innerRing.addVertex(v);
}
poly1.remove();
}
private boolean isPolygonInOtherPolygon(Polygon poly1, Polygon poly2) {
Plane plane = calculatePlane(poly2);
for (Vertex v : poly1.getExteriorRing().getVertices()) {
if (plane.getDistance(v) > EPSILON || !poly2.isPointInsideExteriorRing(v)) {
return false;
}
}
return true;
}
private Plane calculatePlane(Polygon p) {
Vector3d normal = p.calculateNormalNormalized();
return new Plane(normal, p.getExteriorRing().getVertices().get(0));
}
@Override
public HealWrongDormers createNew() {
return new HealWrongDormers();
}
@Override
public HealingID getID() {
return HealingID.S_WRONG_DORMERS;
}
}
package de.hft.stuttgart.citydoctor2.healing.math;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
public class EdgeGraph {
private Map<Edge, EdgeNode> nodes = new HashMap<>();
public void addEdge(Edge e, List<Edge> edges) {
if (nodes.containsKey(e)) {
return;
}
EdgeNode node = new EdgeNode(e);
nodes.put(e, node);
for (Edge childEdge : getEdgesConnected(e, edges)) {
EdgeNode childNode = nodes.get(childEdge);
if (childNode != null && childNode != node) {
node.addChild(childNode);
childNode.addChild(node);
}
}
}
private List<Edge> getEdgesConnected(Edge e, List<Edge> edges) {
List<Edge> result = new ArrayList<>();
for (Edge potentialEdge : edges) {
if (potentialEdge.getConnectionPoint(e) != null) {
result.add(potentialEdge);
}
}
return result;
}
public List<List<Edge>> getCycles() {
List<List<Edge>> cycles = new ArrayList<>();
for (EdgeNode n : nodes.values()) {
List<Edge> edges = new ArrayList<>();
n.collectConnectedEdges(edges, null);
if (edges.size() > 2) {
cycles.add(edges);
}
}
return cycles;
}
}
package de.hft.stuttgart.citydoctor2.healing.math;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
public class EdgeNode {
private Edge e;
private boolean visited = false;
public boolean finished = false;
private List<EdgeNode> children = new ArrayList<>();
public EdgeNode(Edge e) {
this.e = e;
}
public void resetVisit() {
visited = false;
}
public Edge getEdge() {
return e;
}
public void addChild(EdgeNode childNode) {
children.add(childNode);
}
public List<EdgeNode> getChildren() {
return children;
}
public boolean collectConnectedEdges(List<Edge> edges, EdgeNode parent) {
if (finished) {
return false;
}
if (visited) {
createCycle(edges);
return true;
}
visited = true;
edges.add(e);
for (EdgeNode n : children) {
if (n != parent && n.collectConnectedEdges(edges, this)) {
// found cycle
return true;
}
}
edges.remove(edges.size() - 1);
finished = true;
return false;
}
private void createCycle(List<Edge> edges) {
for (int i = 0; i < edges.size(); i++) {
Edge compare = edges.get(i);
if (compare == e) {
for (int j = 0; j < i; j++) {
edges.remove(0);
}
}
}
}
}
/*-
* 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.optimization;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.CityObject;
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;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
public class MeshGenerator {
private static final double EPSILON = 0.0001;
private MeshGenerator() {
}
public static void replaceGeometryWithMeshedGeometry(Building b, double maxArea) {
replacePolygonsInGeometry(b, maxArea);
for (BoundarySurface bs : b.getBoundarySurfaces()) {
replacePolygonsInGeometry(bs, maxArea);
}
for (Installation bi : b.getBuildingInstallations()) {
replacePolygonsInGeometry(bi, maxArea);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
replacePolygonsInGeometry(bs, maxArea);
}
}
}
private static void replacePolygonsInGeometry(CityObject co, double maxArea) {
for (Geometry geom : co.getGeometries()) {
Map<Polygon, List<ConcretePolygon>> replacementPolys = new HashMap<>();
for (Polygon p : geom.getPolygons()) {
if (!p.isLink() && (p.getExteriorRing().getVertices().size() > 4 || !p.getInnerRings().isEmpty())) {
TesselatedPolygon tesselate = p.tesselate();
List<ConcretePolygon> newPolys = new ArrayList<>();
for (Triangle3d t : tesselate.getTriangles()) {
addTriangles(t, maxArea, newPolys);
}
replacementPolys.put(p, newPolys);
}
}
for (Entry<Polygon, List<ConcretePolygon>> r : replacementPolys.entrySet()) {
geom.replacePolygon(r.getKey(), r.getValue().toArray(new ConcretePolygon[r.getValue().size()]));
}
}
}
private static void addTriangles(Triangle3d t, double precision, List<ConcretePolygon> newPolys) {
double area = t.getArea();
if (area > precision) {
double dis1 = t.getP1().getDistance(t.getP2());
double dis2 = t.getP2().getDistance(t.getP3());
double dis3 = t.getP1().getDistance(t.getP3());
if (dis1 >= dis2 && dis1 >= dis3) {
splitTriangles(t.getP1(), t.getP2(), t.getP3(), precision, newPolys);
return;
}
if (dis2 >= dis1 && dis2 >= dis3) {
splitTriangles(t.getP2(), t.getP3(), t.getP1(), precision, newPolys);
return;
}
splitTriangles(t.getP3(), t.getP1(), t.getP2(), precision, newPolys);
} else if (area > EPSILON) {
ConcretePolygon newPoly = new ConcretePolygon();
LinearRing ext = new LinearRing(LinearRingType.EXTERIOR);
newPoly.setExteriorRing(ext);
Vertex v1 = new Vertex(t.getP1());
ext.addVertex(v1);
ext.addVertex(new Vertex(t.getP2()));
ext.addVertex(new Vertex(t.getP3()));
ext.addVertex(v1);
newPolys.add(newPoly);
}
}
private static void splitTriangles(Vector3d v0, Vector3d v1, Vector3d v2, double precision,
List<ConcretePolygon> newPolys) {
double x = 0.5 * (v0.getX() + v1.getX());
double y = 0.5 * (v0.getY() + v1.getY());
double z = 0.5 * (v0.getZ() + v1.getZ());
Vector3d vNeu = new Vector3d(x, y, z);
Triangle3d tNeu1 = new Triangle3d(v0, vNeu, v2);
Triangle3d tNeu2 = new Triangle3d(v2, vNeu, v1);
addTriangles(tNeu1, precision, newPolys);
addTriangles(tNeu2, precision, newPolys);
}
}
package de.hft.stuttgart.citydoctor2.healer;
import java.io.IOException;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class HealingTestUtil {
private HealingTestUtil() {
// only static access
}
public static CityDoctorModel loadCityModel(String path) throws CityGmlParseException, IOException, InvalidGmlFileException {
ParserConfiguration config = new ParserConfiguration(8, false);
CityDoctorModel m = CityGmlParser.parseCityGmlFile(path, config);
return m;
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.healer.Healer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class HealAllPolygonsWrongOrientationErrorTest {
@Test
public void testAllPolygonsWrongOrientation() throws CityGmlParseException, IOException, InvalidGmlFileException {
String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0008-T0001.gml";
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
CityDoctorModel model = TestUtil.loadCityModel(path, config);
Checker c = new Checker(config, model);
c.runChecks();
Geometry geom = model.getBuildings().get(0).getGeometries().get(0);
Assert.assertTrue(geom.containsError(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION));
Healer healer = new Healer(c);
healer.healCityObject(model.getBuildings().get(0));
Assert.assertFalse(geom.containsError(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION));
}
}
package de.hft.stuttgart.citydoctor2.healing;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.healer.Healer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class HealConsecutivePointsSameErrorTest {
@Test
public void test() throws CityGmlParseException, IOException, InvalidGmlFileException {
String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0001.gml";
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
// disable schematron testing
config.setSchematronFilePathInGlobalParameters(null);
CityDoctorModel model = TestUtil.loadCityModel(path, config);
Checker c = new Checker(config, model);
c.runChecks();
Geometry geom = model.getBuildings().get(0).getGeometries().get(0);
Assert.assertTrue(geom.containsError(CheckId.C_GE_R_DUPLICATE_POINT));
Healer healer = new Healer(c);
healer.healCityObject(model.getBuildings().get(0));
Assert.assertFalse(geom.containsError(CheckId.C_GE_R_DUPLICATE_POINT));
}
}
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