Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
CityDoctor
CityDoctor2
Commits
996d862a
Commit
996d862a
authored
Sep 04, 2026
by
Luna Riegel
Browse files
Refactor: Improve alignment calculations
parent
f9d4d45d
Changes
3
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/check/Requirement.java
View file @
996d862a
...
@@ -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
;
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/topology/MisalignedUtilityConnectionCheck.java
View file @
996d862a
...
@@ -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.
Segment
3d
;
import
de.hft.stuttgart.citydoctor2.math.
Vector
3d
;
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
override
s
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_RO
TATION
:
case
ASYMMETRIC_RO
LL
:
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
(
s
ubjConVerts
.
isEmpty
()
&&
c
andConVerts
.
isEmpty
())
{
if
(
s
EdgeLoop
.
isEmpty
()
&&
c
EdgeLoop
.
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
);
}
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/util/ConnectionAlignmentType.java
View file @
996d862a
package
de.hft.stuttgart.citydoctor2.checks.util
;
package
de.hft.stuttgart.citydoctor2.checks.util
;
public
enum
ConnectionAlignmentType
{
public
enum
ConnectionAlignmentType
{
ALIGNED
,
ASYMMETRIC_RO
TATION
,
SHEARED
,
UNKNOWN
ALIGNED
,
ASYMMETRIC_RO
LL
,
SHEARED
,
DISCONNECTED
,
UNKNOWN
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment