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