Commit 9de70078 authored by Luna Riegel's avatar Luna Riegel
Browse files

Feat: Add UtilityLines connection checks

parent 1b5f851b
Pipeline #12412 failed with stage
in 59 seconds
...@@ -8,6 +8,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; ...@@ -8,6 +8,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError; import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError; import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError; import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError; import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError; import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError; import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
...@@ -216,4 +218,12 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor { ...@@ -216,4 +218,12 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor {
@Override @Override
public void visit(FeatureCollisionError err){ public void visit(FeatureCollisionError err){
} }
@Override
public void visit(FloatingUtilityError err) {
}
@Override
public void visit(MisalignedConnectionError err) {
}
} }
...@@ -58,7 +58,8 @@ public record CheckId(String name) implements Serializable { ...@@ -58,7 +58,8 @@ public record CheckId(String name) implements Serializable {
public static final CheckId C_SE_POLYGON_WITHOUT_SURFACE = new CheckId("C_SE_POLYGON_WITHOUT_SURFACE"); public static final CheckId C_SE_POLYGON_WITHOUT_SURFACE = new CheckId("C_SE_POLYGON_WITHOUT_SURFACE");
public static final CheckId C_TO_FEATURE_COLLISION = new CheckId("C_TO_FEATURE_COLLISION"); public static final CheckId C_TO_FEATURE_COLLISION = new CheckId("C_TO_FEATURE_COLLISION");
public static final CheckId C_TO_UTILITY_LINES_DISCONNECTED = new CheckId("C_TO_UTILITY_LINES_DISCONNECTED"); public static final CheckId C_TO_UTILITY_CONNECTION_MISALIGNED = new CheckId("C_TO_UTILITY_CONNECTION_MISALIGNED");
public static final CheckId C_TO_FLOATING_UTILITY = new CheckId("C_TO_FLOATING_UTILITY");
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
......
...@@ -68,7 +68,7 @@ public record ErrorId(String name) implements Serializable { ...@@ -68,7 +68,7 @@ public record ErrorId(String name) implements Serializable {
public static final ErrorId SE_POLYGON_WITHOUT_SURFACE = new ErrorId("SE_POLYGON_WITHOUT_SURFACE"); public static final ErrorId SE_POLYGON_WITHOUT_SURFACE = new ErrorId("SE_POLYGON_WITHOUT_SURFACE");
public static final ErrorId SC_SCHEMA_VALIDATION = new ErrorId("SC_SCHEMA_VALIDATION"); public static final ErrorId SC_SCHEMA_VALIDATION = new ErrorId("SC_SCHEMA_VALIDATION");
public static final ErrorId TO_FEATURE_COLLISION = new ErrorId("TO_FEATURE_COLLISION"); public static final ErrorId TO_FEATURE_COLLISION = new ErrorId("TO_FEATURE_COLLISION");
public static final ErrorId TO_FLOATING_UTILITY = new ErrorId("TO_FLOATING_UTILITY");
@Override @Override
......
...@@ -26,6 +26,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; ...@@ -26,6 +26,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError; import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError; import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError; import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError; import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError; import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError; import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
...@@ -70,90 +72,94 @@ import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError; ...@@ -70,90 +72,94 @@ import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
*/ */
public interface ErrorVisitor { public interface ErrorVisitor {
public void visit(RingError err); void visit(RingError err);
public void visit(PolygonError err); void visit(PolygonError err);
public void visit(RingSelfIntersectionError err); void visit(RingSelfIntersectionError err);
public void visit(PlanarityError err); void visit(PlanarityError err);
public void visit(PolygonHoleOutsideError err); void visit(PolygonHoleOutsideError err);
public void visit(NonManifoldEdgeError err); void visit(NonManifoldEdgeError err);
public void visit(MultipleConnectedComponentsError err); void visit(MultipleConnectedComponentsError err);
public void visit(NestedRingError err); void visit(NestedRingError err);
public void visit(NonManifoldVertexError err); void visit(NonManifoldVertexError err);
public void visit(PolygonWrongOrientationError err); void visit(PolygonWrongOrientationError err);
public void visit(PolygonSameOrientationError err); void visit(PolygonSameOrientationError err);
public void visit(SolidNotClosedError err); void visit(SolidNotClosedError err);
public void visit(DependenciesNotMetError err); void visit(DependenciesNotMetError err);
public void visit(UnknownCheckError err); void visit(UnknownCheckError err);
public void visit(RingNotClosedError err); void visit(RingNotClosedError err);
public void visit(ConsecutivePointSameError err); void visit(ConsecutivePointSameError err);
public void visit(AllPolygonsWrongOrientationError err); void visit(AllPolygonsWrongOrientationError err);
public void visit(PolygonInteriorDisconnectedError err); void visit(PolygonInteriorDisconnectedError err);
public void visit(NullAreaError err); void visit(NullAreaError err);
public void visit(RingTooFewPointsError err); void visit(RingTooFewPointsError err);
public void visit(NonPlanarPolygonNormalsDeviation err); void visit(NonPlanarPolygonNormalsDeviation err);
public void visit(NonPlanarPolygonDistancePlaneError err); void visit(NonPlanarPolygonDistancePlaneError err);
public void visit(PolygonIntersectingRingsError err); void visit(PolygonIntersectingRingsError err);
public void visit(SolidSelfIntError err); void visit(SolidSelfIntError err);
public void visit(TooFewPolygonsError err); void visit(TooFewPolygonsError err);
public void visit(RingDuplicatePointError err); void visit(RingDuplicatePointError err);
public void visit(RingEdgeIntersectionError err); void visit(RingEdgeIntersectionError err);
public void visit(PointTouchesEdgeError err); void visit(PointTouchesEdgeError err);
public void visit(NotCeilingError err); void visit(NotCeilingError err);
public void visit(NotFloorError err); void visit(NotFloorError err);
public void visit(NotWallError err); void visit(NotWallError err);
public void visit(NotGroundError err); void visit(NotGroundError err);
public void visit(SchematronError err); void visit(SchematronError err);
public void visit(SurfaceUnfragmentedError err); void visit(SurfaceUnfragmentedError err);
public void visit(DegeneratedRingError err); void visit(DegeneratedRingError err);
public void visit(AttributeMissingError err); void visit(AttributeMissingError err);
public void visit(AttributeValueWrongError err); void visit(AttributeValueWrongError err);
public void visit(AttributeInvalidError err); void visit(AttributeInvalidError err);
public void visit(PolygonWithoutSurfaceError err); void visit(PolygonWithoutSurfaceError err);
public void visit(CheckError err); void visit(CheckError err);
public void visit(SolidError err); void visit(SolidError err);
public void visit(XMLValidationError err); void visit(XMLValidationError err);
public void visit(FeatureCollisionError err); void visit(FeatureCollisionError err);
void visit(FloatingUtilityError err);
void visit(MisalignedConnectionError err);
} }
...@@ -72,6 +72,7 @@ public class Requirement implements Serializable { ...@@ -72,6 +72,7 @@ public class Requirement implements Serializable {
public static final Requirement R_TO_FEATURE_COLLISION = new Requirement("R_TO_FEATURE_COLLISION", RequirementType.TOPOLOGY); public static final Requirement R_TO_FEATURE_COLLISION = new Requirement("R_TO_FEATURE_COLLISION", RequirementType.TOPOLOGY);
public static final Requirement R_TO_UTILITY_LINES_DISCONNECTED = new Requirement("R_TO_UTILITY_LINES_DISCONNECTED", RequirementType.TOPOLOGY); public static final Requirement R_TO_UTILITY_LINES_DISCONNECTED = new Requirement("R_TO_UTILITY_LINES_DISCONNECTED", RequirementType.TOPOLOGY);
static { static {
// fill requirements with default parameters // fill requirements with default parameters
ArrayList<DefaultParameter> defaultParameters = new ArrayList<>(); ArrayList<DefaultParameter> defaultParameters = new ArrayList<>();
......
package de.hft.stuttgart.citydoctor2.check.error;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ErrorVisitor;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
public class FloatingUtilityError implements CheckError {
private Geometry subjectGeom;
private GmlId subjectGmlId;
public FloatingUtilityError(CityObject co, Geometry subjectGeom) {
this.subjectGeom = subjectGeom;
this.subjectGmlId = co.getGmlId();
}
@Override
public ErrorType getType() {
return ErrorType.ERROR;
}
@Override
public ErrorId getErrorId() {
return ErrorId.TO_FLOATING_UTILITY;
}
@Override
public GmlElement getFeature() {
return subjectGeom;
}
public Geometry getSubjectGeom(){
return subjectGeom;
}
@Override
public void accept(ErrorVisitor errorVisitor) {
errorVisitor.visit(this);
}
@Override
public boolean accept(HealingMethod method, ModificationListener l) {
return false;
}
@Override
public void report(ErrorReport report) {
}
}
package de.hft.stuttgart.citydoctor2.check.error;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ErrorVisitor;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
public class MisalignedConnectionError implements CheckError {
private MisalignmentType misType;
private ErrorType errType;
private Geometry subjectGeom;
private GmlId subjectGmlId;
private GmlId candidateGmlId;
//TODO: ADD
public enum MisalignmentType{
ASYMMETRIC_ROTATION, SHEARED, HANGING, UNKNOWN
}
public MisalignedConnectionError(Geometry subjectGeom, CityObject subject, GmlId candidateGmlId,
MisalignmentType misType, ErrorType errType) {
this.subjectGeom = subjectGeom;
this.subjectGmlId = subject.getGmlId();
this.candidateGmlId = candidateGmlId;
this.misType = misType;
this.errType = errType;
}
@Override
public ErrorType getType() {
return errType;
}
@Override
public ErrorId getErrorId() {
return ErrorId.TO_FLOATING_UTILITY;
}
@Override
public GmlElement getFeature() {
return subjectGeom;
}
public Geometry getSubjectGeom() {
return subjectGeom;
}
public GmlId getSubjectGmlId() {
return subjectGmlId;
}
public GmlId getCandidateGmlId() {
return candidateGmlId;
}
@Override
public void accept(ErrorVisitor errorVisitor) {
errorVisitor.visit(this);
}
public String getMisalignmentType() {
return misType.toString();
}
@Override
public boolean accept(HealingMethod method, ModificationListener l) {
return false;
}
@Override
public void report(ErrorReport report) {
}
}
...@@ -25,6 +25,7 @@ import java.util.Map; ...@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import de.hft.stuttgart.citydoctor2.checks.topology.FeatureCollisionCheck; import de.hft.stuttgart.citydoctor2.checks.topology.FeatureCollisionCheck;
import de.hft.stuttgart.citydoctor2.checks.topology.MisalignedUtilityConnectionCheck;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
...@@ -116,7 +117,7 @@ public class Checks { ...@@ -116,7 +117,7 @@ public class Checks {
//topology checks //topology checks
publish(new FeatureCollisionCheck()); publish(new FeatureCollisionCheck());
publish(new MisalignedUtilityConnectionCheck());
// load checks from service loader // load checks from service loader
ServiceLoader<Check> checkLoader = ServiceLoader.load(Check.class); ServiceLoader<Check> checkLoader = ServiceLoader.load(Check.class);
for (Check c : checkLoader) { for (Check c : checkLoader) {
......
package de.hft.stuttgart.citydoctor2.checks.topology;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.GlobalParameters;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.checks.Checks;
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.Building;
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.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class FloatingUtilityCheck extends Check{
private static final List<CheckId> dependencies;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION);
deps.add(CheckId.C_TO_FLOATING_UTILITY);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
}
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private double searchRadius = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private CityObjectCache cache;
@Override
public Set<Requirement> appliesToRequirements() {
return Set.of(Requirement.R_TO_UTILITY_LINES_DISCONNECTED);
}
@Override
public CheckId getCheckId() {
return CheckId.C_TO_FLOATING_UTILITY;
}
@Override
public RequirementType getType() {
return RequirementType.TOPOLOGY;
}
@Override
public Check createNewInstance() {
return new FloatingUtilityCheck();
}
@Override
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config, CityObjectCache cache) {
Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
this.cache = cache;
}
@Override
public void check(Building b) {
checkUtilityLinesConnections(b);
}
@Override
public void check(OtherConstructionObject oco) {
checkUtilityLinesConnections(oco);
}
private void checkUtilityLinesConnections(CityObject subject){
Collection<CityObject> candidates = cache.getCandidates(subject, searchRadius);
if (candidates.isEmpty()) {
FloatingUtilityError err = new FloatingUtilityError(subject, subject.getHighestLodGeometry());
subject.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
return;
}
List<Geometry> subjGeoms = new ArrayList<>();
subject.accept(new CheckableUtilsVisitor() {
@Override
public void check(Geometry geom) {
subjGeoms.add(geom);
}
});
for (Geometry geom : subjGeoms) {
for(CityObject candidate : candidates){
}
}
boolean misalignedConnections = false;
boolean freeFloating = false;
for (Geometry sGeo : subjGeoms) {
final boolean[] connected = {false};
final boolean[] misalignment = {false};
for (CityObject candidate : candidates) {
candidate.accept(new CheckableUtilsVisitor() {
@Override
public void check(Geometry cGeo) {
if(sGeo.getLod() != cGeo.getLod()){
return;
}
List<Pair<Vertex, Vertex>> pairs = getVertexPairs(sGeo, cGeo);
if (pairs.size() < 3) {
if (!pairs.isEmpty()) {
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignedConnectionError.MisalignmentType.HANGING, ErrorType.ERROR)));
}
} else {
switch(checkConnectionAlignment(sGeo, cGeo, pairs)) {
case ALIGNED:
connected[0] = true;
break;
case ASYMMETRIC_ROTATION:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignedConnectionError.MisalignmentType.ASYMMETRIC_ROTATION, ErrorType.ERROR)));
misalignment[0] = true;
break;
case SHEARED:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignedConnectionError.MisalignmentType.SHEARED, ErrorType.ERROR)));
misalignment[0] = true;
break;
case UNKNOWN:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.WARNING, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignedConnectionError.MisalignmentType.UNKNOWN, ErrorType.WARNING)));
misalignment[0] = true;
break;
}
}
}
});
if (!connected[0]) {
// Geometry has no connections and is "floating in space"
freeFloating = true;
FloatingUtilityError err = new FloatingUtilityError(subject, sGeo);
subject.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
} else if (misalignment[0]) {
misalignedConnections = true;
}
}
}
if (!misalignedConnections && !freeFloating) {
subject.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
}
}
private List<Pair<Vertex, Vertex>> getVertexPairs(Geometry subject, Geometry candidate){
if (candidate.getVertices() == null){
candidate.prepareForChecking();
}
List<Vertex> subjectVerts = subject.getVertices();
List<Pair<Vertex, Vertex>> connectionPairs = new ArrayList<>();
for (Vertex cVert : candidate.getVertices()) {
for (Vertex sVert : subjectVerts) {
if(sVert.equalsWithEpsilon(cVert, searchRadius)){
connectionPairs.add(new Pair<>(sVert, cVert));
}
}
}
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);
pairs.forEach(pair -> {
sPairVerts.add(pair.getValue0());
cPairVerts.add(pair.getValue1());
sRing.addVertex(pair.getValue0());
});
Plane connectionPlane = Plane.of(sPoly);
List<Vertex> subjConVerts = UtilityNetworkUtils.edgeLoopSelection(subject, sPairVerts, connectionPlane, searchRadius);
List<Vertex> candConVerts = UtilityNetworkUtils.edgeLoopSelection(candidate, cPairVerts, connectionPlane, searchRadius);
if (subjConVerts.isEmpty() && candConVerts.isEmpty()) {
// No missed vertices near connection, connection of features is aligned
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;
}
} else{
// Centroids are misaligned, connection is sheared off along the connection plane
return ConnectionAlignmentType.SHEARED;
}
}
}
package de.hft.stuttgart.citydoctor2.checks.topology;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.GlobalParameters;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError.MisalignmentType;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.util.ConnectionAlignmentType;
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.Building;
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.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MisalignedUtilityConnectionCheck extends Check {
private static final List<CheckId> dependencies;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
}
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private double searchRadius = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private CityObjectCache cache;
@Override
public Set<Requirement> appliesToRequirements() {
return Set.of(Requirement.R_TO_UTILITY_LINES_DISCONNECTED);
}
@Override
public CheckId getCheckId() {
return CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED;
}
@Override
public RequirementType getType() {
return RequirementType.TOPOLOGY;
}
@Override
public Check createNewInstance() {
return new MisalignedUtilityConnectionCheck();
}
@Override
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config, CityObjectCache cache) {
Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
this.cache = cache;
}
@Override
public void check(Building b) {
checkUtilityLinesConnections(b);
}
@Override
public void check(OtherConstructionObject oco) {
checkUtilityLinesConnections(oco);
}
private void checkUtilityLinesConnections(CityObject subject){
Collection<CityObject> candidates = cache.getCandidates(subject, epsilon);
if (candidates.isEmpty()) {
FloatingUtilityError err = new FloatingUtilityError(subject, subject.getHighestLodGeometry());
subject.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
return;
}
List<Geometry> subjGeoms = new ArrayList<>();
subject.accept(new CheckableUtilsVisitor() {
@Override
public void check(Geometry geom) {
subjGeoms.add(geom);
}
});
boolean misalignedConnections = false;
boolean freeFloating = false;
for (Geometry sGeo : subjGeoms) {
final boolean[] connected = {false};
final boolean[] misalignment = {false};
for (CityObject candidate : candidates) {
candidate.accept(new CheckableUtilsVisitor() {
@Override
public void check(Geometry cGeo) {
if(sGeo.getLod() != cGeo.getLod()){
return;
}
List<Pair<Vertex, Vertex>> pairs = getVertexPairs(sGeo, cGeo);
if (pairs.size() < 3) {
if (!pairs.isEmpty()) {
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignedConnectionError.MisalignmentType.HANGING, ErrorType.ERROR)));
}
} else {
switch(checkConnectionAlignment(sGeo, cGeo, pairs)) {
case ALIGNED:
connected[0] = true;
break;
case ASYMMETRIC_ROTATION:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignmentType.ASYMMETRIC_ROTATION, ErrorType.ERROR)));
misalignment[0] = true;
break;
case SHEARED:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.ERROR, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignmentType.SHEARED, ErrorType.ERROR)));
misalignment[0] = true;
break;
case UNKNOWN:
subject.addCheckResult(new CheckResult(CheckId.C_TO_UTILITY_CONNECTION_MISALIGNED,
ResultStatus.WARNING, new MisalignedConnectionError(sGeo, subject, candidate.getGmlId(),
MisalignmentType.UNKNOWN, ErrorType.WARNING)));
misalignment[0] = true;
break;
}
}
}
});
if (!connected[0]) {
// Geometry has no connections and is "floating in space"
freeFloating = true;
FloatingUtilityError err = new FloatingUtilityError(subject, sGeo);
subject.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
} else if (misalignment[0]) {
misalignedConnections = true;
}
}
}
if (!misalignedConnections && !freeFloating) {
subject.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
}
}
private List<Pair<Vertex, Vertex>> getVertexPairs(Geometry subject, Geometry candidate){
if (candidate.getVertices() == null){
candidate.prepareForChecking();
}
List<Vertex> subjectVerts = subject.getVertices();
List<Pair<Vertex, Vertex>> connectionPairs = new ArrayList<>();
for (Vertex cVert : candidate.getVertices()) {
for (Vertex sVert : subjectVerts) {
if(sVert.equalsWithEpsilon(cVert, searchRadius)){
connectionPairs.add(new Pair<>(sVert, cVert));
}
}
}
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);
pairs.forEach(pair -> {
sPairVerts.add(pair.getValue0());
cPairVerts.add(pair.getValue1());
sRing.addVertex(pair.getValue0());
});
Plane connectionPlane = Plane.of(sPoly);
List<Vertex> subjConVerts = UtilityNetworkUtils.edgeLoopSelection(subject, sPairVerts, connectionPlane, searchRadius);
List<Vertex> candConVerts = UtilityNetworkUtils.edgeLoopSelection(candidate, cPairVerts, connectionPlane, searchRadius);
if (subjConVerts.isEmpty() && candConVerts.isEmpty()) {
// No missed vertices near connection, connection of features is aligned
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;
}
} else{
// Centroids are misaligned, connection is sheared off along the connection plane
return ConnectionAlignmentType.SHEARED;
}
}
}
package de.hft.stuttgart.citydoctor2.checks.topology;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
public class UtilityConnectionAngleCheck {
// private void checkUtilityLinesConnections(CityObject subject){
// Collection<CityObject> candidates = cache.getCandidates(subject, epsilon);
// List<Geometry> subjGeoms = new ArrayList<>();
// subject.accept(new CheckableUtilsVisitor() {
// @Override
// public void check(Geometry geom) {
// subjGeoms.add(geom);
// }
// });
// boolean disconnected = true;
// for (Geometry sGeo : subjGeoms) {
// for (CityObject candidate : candidates) {
// candidate.accept(new CheckableUtilsVisitor() {
// @Override
// public void check(Geometry cGeo) {
// if(sGeo.getLod() != cGeo.getLod()){
// return;
// }
// List<PolygonIntersection> intersections = getIntersections(sGeo, cGeo);
// if(intersections.isEmpty()){
// return;
// }
// List<Vector3d> intersectionPoints = extractIntersectionPoints(intersections);
// if(intersectionPoints.isEmpty()){
// return;
// }
//
// }
// });
//
//
// }
// }
//
// }
//
// private List<PolygonIntersection> getIntersections(Geometry subjectGeom, Geometry candidateGeom) {
// List<PolygonIntersection> intersections = SelfIntersectionUtil
// .calculateGeometryIntersections(subjectGeom, candidateGeom, epsilon);
// intersections = intersections.stream()
// .filter(sect -> sect.getType() != PolygonIntersection.IntersectionType.NONE)
// .toList();
// if (intersections.isEmpty()) {
// return Collections.emptyList();
// }
// return intersections;
// }
//
// private List<Vector3d> extractIntersectionPoints(List<PolygonIntersection> intersections) {
// List<Vector3d> intersectionPoints = new ArrayList<>();
// for (PolygonIntersection intersection : intersections) {
// intersection.getLines().forEach(line -> {
// intersectionPoints.add(line.getPointA());
// intersectionPoints.add(line.getPointB());
// });
// }
// List<Vector3d> points = deduplicatePoints(intersectionPoints);
// if (points.isEmpty()||points.size() < 3) {
// return Collections.emptyList();
// }
// return points;
// }
//
// private List<Vector3d> deduplicatePoints(Collection<Vector3d> points) {
// Predicate<Vector3d> duplicateFilter = new Predicate<Vector3d>() {
// List<Vector3d> distinctPoints = new ArrayList<>();
// @Override
// public boolean test(Vector3d v) {
// for(Vector3d dPoint : distinctPoints){
// if(dPoint.equalsWithEpsilon(v, epsilon)){
// return false;
// }
// }
// distinctPoints.add(v);
// return true;
// }
// };
// return points.stream().filter(duplicateFilter).toList();
// }
}
package de.hft.stuttgart.citydoctor2.checks.topology;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import java.util.Set;
public class UtilityLinesDisconnectedCheck extends Check {
@Override
public Set<Requirement> appliesToRequirements() {
return Set.of(Requirement.R_TO_UTILITY_LINES_DISCONNECTED);
}
@Override
public CheckId getCheckId() {
return CheckId.C_TO_UTILITY_LINES_DISCONNECTED;
}
@Override
public RequirementType getType() {
return RequirementType.TOPOLOGY;
}
@Override
public Check createNewInstance() {
return new UtilityLinesDisconnectedCheck();
}
}
package de.hft.stuttgart.citydoctor2.checks.util;
public enum ConnectionAlignmentType {
ALIGNED, ASYMMETRIC_ROTATION, SHEARED, UNKNOWN
}
package de.hft.stuttgart.citydoctor2.checks.util;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Plane;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class UtilityNetworkUtils {
public static List<Vertex> radialSelectVertices(Geometry geom, Plane plane, double searchRadius){
List<Vertex> vertices = new ArrayList<>();
geom.getVertices().forEach(vertex -> {
if (plane.getDistance(vertex) <= searchRadius) {
vertices.add(vertex);
}
});
return vertices;
}
public static List<Vertex> edgeLoopSelection(Geometry geom, List<Vertex> seedPoints, Plane loopPlane, double searchRadius){
List<Vertex> unboundPoints = radialSelectVertices(geom, loopPlane, searchRadius);
unboundPoints.removeAll(seedPoints);
return edgeLoopCrawl(geom, seedPoints, unboundPoints);
}
private static List<Vertex> edgeLoopCrawl(Geometry geom, List<Vertex> seedPoints, List<Vertex> unboundPoints){
List<Vertex> fixedPoints = new ArrayList<>(seedPoints);
List<Vertex> freePoints = new ArrayList<>(unboundPoints);
List<Vertex> caughtPoints = new ArrayList<>();
for(Vertex fix: fixedPoints){
for(Vertex free: freePoints){
Edge edge = geom.getEdge(fix, free);
if (edge != null) {
caughtPoints.add(free);
}
}
freePoints.removeAll(caughtPoints);
}
if(caughtPoints.isEmpty()){
return Collections.emptyList();
} else if (freePoints.isEmpty()){
return caughtPoints;
} else {
List<Vertex> loopPoints = new ArrayList<>(caughtPoints);
loopPoints.addAll(edgeLoopCrawl(geom, caughtPoints, freePoints));
return loopPoints;
}
}
public static Vertex getCentroid(Collection<Vertex> vertices) {
Vertex centroid = new Vertex(0,0,0);
vertices.forEach(centroid::plus);
centroid.mult(((double) 1 /vertices.size()));
return centroid;
}
}
...@@ -10,6 +10,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; ...@@ -10,6 +10,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError; import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError; import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError; import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError; import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError; import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError; import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
...@@ -234,4 +236,13 @@ public class GlobalErrorVisitor implements ErrorVisitor { ...@@ -234,4 +236,13 @@ public class GlobalErrorVisitor implements ErrorVisitor {
public void visit(FeatureCollisionError err){ public void visit(FeatureCollisionError err){
} }
@Override
public void visit(MisalignedConnectionError err) {
}
@Override
public void visit(FloatingUtilityError err) {
}
} }
...@@ -12,6 +12,8 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError; ...@@ -12,6 +12,8 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError;
import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError; import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError; import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError; import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError; import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError; import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
...@@ -312,4 +314,14 @@ public class ListErrorVisitor implements ErrorVisitor { ...@@ -312,4 +314,14 @@ public class ListErrorVisitor implements ErrorVisitor {
//TODO: Implement handling of error here //TODO: Implement handling of error here
} }
@Override
public void visit(FloatingUtilityError err){
//TODO: Implement highlighting
}
@Override
public void visit(MisalignedConnectionError err){
//TODO: Implement highlighting
}
} }
...@@ -13,6 +13,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; ...@@ -13,6 +13,8 @@ import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError; import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError; import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError; import de.hft.stuttgart.citydoctor2.check.error.FeatureCollisionError;
import de.hft.stuttgart.citydoctor2.check.error.FloatingUtilityError;
import de.hft.stuttgart.citydoctor2.check.error.MisalignedConnectionError;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError; import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError; import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError; import de.hft.stuttgart.citydoctor2.check.error.NonManifoldEdgeError;
...@@ -493,4 +495,29 @@ public class ErrorItemVisitor extends AbstractErrorVisitor { ...@@ -493,4 +495,29 @@ public class ErrorItemVisitor extends AbstractErrorVisitor {
//TODO: Implement handling of error here //TODO: Implement handling of error here
// Will require new Error Node-type that is capable of rendering 2 CityObjects // Will require new Error Node-type that is capable of rendering 2 CityObjects
} }
@Override
public void visit(MisalignedConnectionError err) {
TextNode subTypeTextNode = new TextNode("MisalignedConnection");
TreeItem<Renderable> subTypeItem = new TreeItem<>(subTypeTextNode);
TopologyErrorNode subjectGeoNode = new TopologyErrorNode( err.getSubjectGeom().getDisplayText(),
renderer -> renderer.highlightTopoErrorGeometry(err.getSubjectGeom(), false));
TreeItem<Renderable> subjectGeoItem = new TreeItem<>(subjectGeoNode);
TextNode misAlignTypeTextNode = new TextNode(err.getMisalignmentType());
TreeItem<Renderable> misAlignTypeItem = new TreeItem<>(misAlignTypeTextNode);
subjectGeoItem.getChildren().add(misAlignTypeItem);
root.getChildren().add(subTypeItem);
root.getChildren().add(subjectGeoItem);
//TODO: Proper handling of error here
}
@Override
public void visit(FloatingUtilityError err) {
TextNode subTypeTextNode = new TextNode("FloatingUtility");
TreeItem<Renderable> subTypeItem = new TreeItem<>(subTypeTextNode);
root.getChildren().add(subTypeItem);
//TODO: Proper handling of error here
}
} }
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