Commit 996d862a authored by Luna Riegel's avatar Luna Riegel
Browse files

Refactor: Improve alignment calculations

parent f9d4d45d
......@@ -36,6 +36,8 @@ public class Requirement implements Serializable {
private static final String LOWER_ANGLE_NAME = "lowerAngle";
private static final String UPPER_ANGLE_NAME = "upperAngle";
private static final String MAX_ANGLE_DEVIATION = "maxAngleDeviation";
private static final String ANGLE_THRESHOLD = "angleThreshold";
private static final String CONNECTION_SEARCH_RADIUS = "connectionSearchRadius";
public static final Requirement R_GE_R_TOO_FEW_POINTS = new Requirement("R_GE_R_TOO_FEW_POINTS", RequirementType.GEOMETRY);
public static final Requirement R_GE_R_NOT_CLOSED = new Requirement("R_GE_R_NOT_CLOSED", RequirementType.GEOMETRY);
......@@ -95,6 +97,9 @@ public class Requirement implements Serializable {
defaultParameters.add(new DefaultParameter(DEGENERATED_RING_TOLERANCE, "0", Unit.METER));
R_GE_R_SELF_INTERSECTION.parameters = Collections.unmodifiableList(defaultParameters);
defaultParameters = new ArrayList<>();
defaultParameters.add(new DefaultParameter(CONNECTION_SEARCH_RADIUS, "0.01", Unit.METER));
defaultParameters.add(new DefaultParameter(ANGLE_THRESHOLD, "30", Unit.DEGREE));
}
private final String id;
......
......@@ -19,20 +19,17 @@ import de.hft.stuttgart.citydoctor2.checks.util.ConnectionAlignmentType;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.checks.util.UtilityNetworkUtils;
import de.hft.stuttgart.citydoctor2.database.CityObjectCache;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractLinearRing;
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.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.FeatureType;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Pair;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
......@@ -43,11 +40,15 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class MisalignedUtilityConnectionCheck extends Check {
private static final List<CheckId> dependencies;
private static final String ANGLE_THRESHOLD = "angleThreshold";
private static final String CONNECTION_SEARCH_RADIUS = "connectionSearchRadius";
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION);
......@@ -61,6 +62,7 @@ public class MisalignedUtilityConnectionCheck extends Check {
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private double searchRadius = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private double angleThreshold = 30;
private CityObjectCache cache;
@Override
......@@ -93,9 +95,15 @@ public class MisalignedUtilityConnectionCheck extends Check {
Map<String, String> localParameters = params.get(getCheckId());
this.cache = cache;
if (localParameters == null) {
// no parameters
// no parameter overrides
return;
}
if (localParameters.containsKey(CONNECTION_SEARCH_RADIUS)) {
searchRadius = Double.parseDouble(localParameters.get(CONNECTION_SEARCH_RADIUS));
}
if (localParameters.containsKey(ANGLE_THRESHOLD)) {
angleThreshold = Double.parseDouble(localParameters.get(ANGLE_THRESHOLD));
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
......@@ -121,13 +129,6 @@ public class MisalignedUtilityConnectionCheck extends Check {
return;
}
List<Geometry> subjGeoms = new ArrayList<>();
// subject.accept(new CheckableUtilsVisitor() {
// @Override
// public void check(Geometry geom) {
// subjGeoms.add(geom);
// }
// });
subject.accept(new CheckableUtilsVisitor() {
@Override
public void check(BoundarySurface bs) {
......@@ -160,10 +161,6 @@ public class MisalignedUtilityConnectionCheck extends Check {
intersections = intersections.stream()
.filter(isect -> isect.getType() != PolygonIntersection.IntersectionType.NONE)
.toList();
for (PolygonIntersection intersection : intersections) {
List<Segment3d> lines = intersection.getLines();
}
List<Pair<Vertex, Vertex>> pairs = getVertexPairs(sGeo, cGeo);
if (pairs.size() < 3) {
if (!pairs.isEmpty()) {
......@@ -172,12 +169,12 @@ public class MisalignedUtilityConnectionCheck extends Check {
MisalignedConnectionError.MisalignmentType.HANGING, ErrorType.ERROR)));
}
} else {
ConnectionAlignmentType alignment = checkConnectionAlignment(sGeo, cGeo, pairs);
ConnectionAlignmentType alignment = checkConnectionAlignment(sGeo, cGeo, pairs, intersections);
switch(alignment) {
case ALIGNED:
connected = true;
break;
case ASYMMETRIC_ROTATION:
case ASYMMETRIC_ROLL:
subject.addCheckResult(new CheckResult(this,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignmentType.ASYMMETRIC_ROTATION, ErrorType.ERROR)));
......@@ -214,10 +211,6 @@ public class MisalignedUtilityConnectionCheck extends Check {
}
}
//
// private boolean checkForEndToEndConnection(Geometry sGeom, CityObject cGeo) {
// if (sGeom.getParent().getFeatureType() != FeatureType.BOUNDARY_SURFACE || cGeo.getPar)
// }
private List<Pair<Vertex, Vertex>> getVertexPairs(Geometry subject, Geometry candidate){
if (candidate.getVertices() == null){
......@@ -235,47 +228,96 @@ public class MisalignedUtilityConnectionCheck extends Check {
return connectionPairs;
}
private ConnectionAlignmentType checkConnectionAlignment(Geometry subject, Geometry candidate, List<Pair<Vertex,Vertex>> pairs){
ConcretePolygon sPoly = new ConcretePolygon();
LinearRing sRing = new LinearRing(AbstractLinearRing.LinearRingType.EXTERIOR);
sPoly.setParent(new Geometry(GeometryType.MULTI_SURFACE, subject.getLod(), Geometry.Orientation.INWARD));
ArrayList<Vertex> sPairVerts = new ArrayList<>();
ArrayList<Vertex> cPairVerts = new ArrayList<>();
sPoly.setExteriorRing(sRing);
private ConnectionAlignmentType checkConnectionAlignment(Geometry subject, Geometry candidate,
List<Pair<Vertex, Vertex>> pairs, List<PolygonIntersection> intersections) {
ArrayList<Vertex> sPairVertices = new ArrayList<>();
ArrayList<Vertex> cPairVertices = new ArrayList<>();
pairs.forEach(pair -> {
sPairVerts.add(pair.getValue0());
cPairVerts.add(pair.getValue1());
sRing.addVertex(pair.getValue0());
sPairVertices.add(pair.getValue0());
cPairVertices.add(pair.getValue1());
});
Plane connectionPlane = Plane.of(sPoly);
List<Vertex> subjConVerts = UtilityNetworkUtils.edgeLoopSelection(subject, sPairVerts, connectionPlane, searchRadius);
List<Vertex> candConVerts = UtilityNetworkUtils.edgeLoopSelection(candidate, cPairVerts, connectionPlane, searchRadius);
Plane connectionPlane = deriveConnectionPlane(sPairVertices, intersections);
if (connectionPlane == null) {
return ConnectionAlignmentType.UNKNOWN;
}
List<Vertex> sEdgeLoop = UtilityNetworkUtils.edgeLoopSelection(subject, sPairVertices, connectionPlane, searchRadius);
List<Vertex> cEdgeLoop = UtilityNetworkUtils.edgeLoopSelection(candidate, cPairVertices, connectionPlane, searchRadius);
if (subjConVerts.isEmpty() && candConVerts.isEmpty()) {
// No missed vertices near connection, connection of features is aligned
if (sEdgeLoop.isEmpty() && cEdgeLoop.isEmpty()) {
// --> Face-to-Face connection with no missed vertices
return ConnectionAlignmentType.ALIGNED;
}
Vertex subjCentroid = UtilityNetworkUtils.getCentroid(subjConVerts);
Vertex candCentroid = UtilityNetworkUtils.getCentroid(candConVerts);
if (subjCentroid.equalsWithEpsilon(candCentroid, searchRadius)) {
if(subjConVerts.size() == candConVerts.size()){
// Connection plane centroids of both features equal, and are missing equal number of connection vertices
// -> both features likely of same shape but with a roll, yaw or pitch mismatch
return ConnectionAlignmentType.ASYMMETRIC_ROTATION;
} else {
// The form of both features differs, and they are connected by some vertex pairs
// Uncertain if erroneous, add warning
return ConnectionAlignmentType.UNKNOWN;
Vertex subjCentroid = UtilityNetworkUtils.getCentroid(sEdgeLoop.isEmpty() ? sPairVertices : sEdgeLoop);
Vertex candCentroid = UtilityNetworkUtils.getCentroid(cEdgeLoop.isEmpty() ? cPairVertices : cEdgeLoop);
boolean centroidsAligned = subjCentroid.equalsWithEpsilon(candCentroid, searchRadius);
boolean subjPlanar = isPlanarToPlane(sEdgeLoop, connectionPlane, epsilon);
boolean candPlanar = isPlanarToPlane(cEdgeLoop, connectionPlane, epsilon);
if (subjPlanar && candPlanar) {
return centroidsAligned ? ConnectionAlignmentType.ALIGNED : ConnectionAlignmentType.SHEARED;
}
Plane subjLocalPlane = OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(sEdgeLoop.size() >= 3 ? sEdgeLoop : sPairVertices);
Plane candLocalPlane = OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(cEdgeLoop.size() >= 3 ? cEdgeLoop : cPairVertices);
if (subjLocalPlane == null || candLocalPlane == null) {
return ConnectionAlignmentType.UNKNOWN;
}
double angle = angleBetweenPlanes(subjLocalPlane, candLocalPlane);
if (angle > angleThreshold) {
return ConnectionAlignmentType.ASYMMETRIC_ROLL;
}
return centroidsAligned ? ConnectionAlignmentType.ALIGNED : ConnectionAlignmentType.SHEARED;
}
private Plane deriveConnectionPlane(List<Vertex> sPairVerts, List<PolygonIntersection> intersections) {
// Calculate intersection plane
if (!intersections.isEmpty()) {
List<Vector3d> intersectPoints = intersections.stream()
.flatMap(isect -> isect.getLines().stream())
.flatMap(segment -> Stream.of(segment.getPointA(), segment.getPointB()))
.toList();
if (!intersectPoints.isEmpty()) {
return OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(intersectPoints);
}
} else{
// Centroids are misaligned, connection is sheared off along the connection plane
return ConnectionAlignmentType.SHEARED;
// --> No overlap, analyze vertex pairs
}
if (sPairVerts.size() < 3) {
// Not enough pairs for calculation of plane
return null;
}
return OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(sPairVerts);
}
private boolean isPlanarToPlane(List<Vertex> verts, Plane plane, double eps) {
if (verts.isEmpty()) {
return true;
}
for (Vertex v : verts) {
if (plane.getDistance(v) > eps) {
return false;
}
}
return true;
}
private double angleBetweenPlanes(Plane a, Plane b) {
Vector3d na = a.getNormal();
Vector3d nb = b.getNormal();
double cos = na.dot(nb) / (na.getLength() * nb.getLength());
cos = Math.max(-1.0, Math.min(1.0, cos));
// abs() because normal direction (in/out) isn't meaningful for this comparison
double angleRad = Math.acos(Math.abs(cos));
return Math.toDegrees(angleRad);
}
......
package de.hft.stuttgart.citydoctor2.checks.util;
public enum ConnectionAlignmentType {
ALIGNED, ASYMMETRIC_ROTATION, SHEARED, UNKNOWN
ALIGNED, ASYMMETRIC_ROLL, SHEARED, DISCONNECTED, UNKNOWN
}
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