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
aeae0705
Commit
aeae0705
authored
Sep 22, 2026
by
Numanoglu
Browse files
Fix outgoing edge collection for sweep healing
parent
4c8cddf2
Pipeline
#12464
passed with stage
in 2 minutes and 47 seconds
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healing/SweepUtilities.java
View file @
aeae0705
...
@@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager;
...
@@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager;
import
org.apache.logging.log4j.Logger
;
import
org.apache.logging.log4j.Logger
;
import
de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError
;
import
de.hft.stuttgart.citydoctor2.check.error.NonPlanarPolygonDistancePlaneError
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityObject
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
...
@@ -21,6 +22,7 @@ public class SweepUtilities {
...
@@ -21,6 +22,7 @@ public class SweepUtilities {
private
Plane
targetPlane
;
private
Plane
targetPlane
;
private
Polygon
errorPolygon
;
private
Polygon
errorPolygon
;
private
CityObject
topLevelFeature
;
private
double
minSweepEdgeLength
=
0.01
;
private
double
minSweepEdgeLength
=
0.01
;
private
double
pointOnPlaneTolerance
=
0.00011
;
private
double
pointOnPlaneTolerance
=
0.00011
;
...
@@ -42,6 +44,7 @@ public class SweepUtilities {
...
@@ -42,6 +44,7 @@ public class SweepUtilities {
targetPlane
=
err
.
getPlane
();
targetPlane
=
err
.
getPlane
();
errorPolygon
=
err
.
getPolygon
();
errorPolygon
=
err
.
getPolygon
();
topLevelFeature
=
findTopLevelFeature
(
errorPolygon
);
}
}
...
@@ -59,7 +62,7 @@ public class SweepUtilities {
...
@@ -59,7 +62,7 @@ public class SweepUtilities {
collectSweepVertices
(
errorPolygon
);
collectSweepVertices
(
errorPolygon
);
for
(
SweepVertex
v
:
sweepVertices
)
{
for
(
SweepVertex
v
:
sweepVertices
)
{
v
.
collectOutgoingEdges
(
errorPolygon
,
minSweepEdgeLength
);
v
.
collectOutgoingEdges
(
topLevelFeature
,
errorPolygon
,
minSweepEdgeLength
,
pointOnPlaneTolerance
);
v
.
initStatus
(
targetPlane
,
pointOnPlaneTolerance
,
true
);
v
.
initStatus
(
targetPlane
,
pointOnPlaneTolerance
,
true
);
}
}
...
@@ -207,7 +210,6 @@ public class SweepUtilities {
...
@@ -207,7 +210,6 @@ public class SweepUtilities {
Vector3d
thirdPoint
=
multiVerts
.
get
(
2
).
getSweepVertex
();
Vector3d
thirdPoint
=
multiVerts
.
get
(
2
).
getSweepVertex
();
Vector3d
firstDirection
=
secondPoint
.
minus
(
firstPoint
);
Vector3d
firstDirection
=
secondPoint
.
minus
(
firstPoint
);
Vector3d
secondDirection
=
thirdPoint
.
minus
(
firstPoint
);
Vector3d
secondDirection
=
thirdPoint
.
minus
(
firstPoint
);
// Normalieren?!
Vector3d
newNormal
=
firstDirection
.
cross
(
secondDirection
);
Vector3d
newNormal
=
firstDirection
.
cross
(
secondDirection
);
if
(
newNormal
.
getLength
()
<
pointOnPlaneTolerance
)
{
if
(
newNormal
.
getLength
()
<
pointOnPlaneTolerance
)
{
...
@@ -267,4 +269,12 @@ public class SweepUtilities {
...
@@ -267,4 +269,12 @@ public class SweepUtilities {
return
sweepVertices
;
return
sweepVertices
;
}
}
// Kaskadiert via Gettres bis ganz nach oben
private
CityObject
findTopLevelFeature
(
Polygon
polygon
)
{
if
(
polygon
==
null
||
polygon
.
getParent
()
==
null
||
polygon
.
getParent
().
getParent
()
==
null
)
{
return
null
;
}
return
polygon
.
getParent
().
getParent
().
getTopLevelCityObject
();
}
}
}
CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healing/SweepVertex.java
View file @
aeae0705
package
de.hft.stuttgart.citydoctor2.healing
;
package
de.hft.stuttgart.citydoctor2.healing
;
import
java.util.ArrayList
;
import
java.util.LinkedList
;
import
java.util.LinkedList
;
import
java.util.List
;
import
java.util.List
;
import
org.apache.logging.log4j.LogManager
;
import
org.apache.logging.log4j.LogManager
;
import
org.apache.logging.log4j.Logger
;
import
org.apache.logging.log4j.Logger
;
import
de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityObject
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
import
de.hft.stuttgart.citydoctor2.datastructure.Edge
;
import
de.hft.stuttgart.citydoctor2.datastructure.Edge
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.LinearRing
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.math.Plane
;
import
de.hft.stuttgart.citydoctor2.math.Plane
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
...
@@ -72,6 +76,47 @@ public class SweepVertex {
...
@@ -72,6 +76,47 @@ public class SweepVertex {
}
}
}
}
public
void
collectOutgoingEdges
(
CityObject
topLevelFeature
,
Polygon
errorPoly
,
double
minSweepEdgeLength
,
double
pointTolerance
)
{
if
(
topLevelFeature
==
null
)
{
collectOutgoingEdges
(
errorPoly
,
minSweepEdgeLength
);
return
;
}
outgoingEdges
.
clear
();
if
(
errorPoly
==
null
)
{
logger
.
error
(
"Cannot collect outgoing edges: polygon is null"
);
status
=
SweepVertexStatus
.
ERROR
;
return
;
}
if
(
pointTolerance
<=
0
)
{
logger
.
error
(
"Cannot collect outgoing edges: pointTolerance must be positive"
);
status
=
SweepVertexStatus
.
ERROR
;
return
;
}
for
(
Geometry
geometry
:
collectGeometries
(
topLevelFeature
))
{
if
(
geometry
.
getEdges
()
==
null
)
{
geometry
.
updateEdgesAndVertices
();
}
for
(
Edge
edge
:
geometry
.
getEdges
())
{
Vertex
oppositeVertex
=
findOppositeEndpointAtSweepVertex
(
edge
,
pointTolerance
);
if
(
oppositeVertex
==
null
)
{
continue
;
}
if
(
isEdgeOfErrorPolygon
(
edge
,
errorPoly
,
pointTolerance
))
{
continue
;
}
if
(
sweepVertex
.
getDistance
(
oppositeVertex
)
<
minSweepEdgeLength
)
{
logger
.
error
(
"Cannot collect outgoing edges: outgoing edge is shorter than minSweepEdgeLength"
);
status
=
SweepVertexStatus
.
ERROR
;
return
;
}
addOutgoingEdgeIfMissing
(
new
Edge
(
sweepVertex
,
oppositeVertex
),
pointTolerance
);
}
}
}
public
void
initStatus
(
Plane
targetPlane
,
double
pointOnPlaneTolerance
,
boolean
bWithMulti
)
{
public
void
initStatus
(
Plane
targetPlane
,
double
pointOnPlaneTolerance
,
boolean
bWithMulti
)
{
if
(
targetPlane
==
null
)
{
if
(
targetPlane
==
null
)
{
...
@@ -200,4 +245,96 @@ public class SweepVertex {
...
@@ -200,4 +245,96 @@ public class SweepVertex {
return
newPoint
!=
null
;
return
newPoint
!=
null
;
}
}
/**
* Sammelt alle Geometrien des Top-Level-Features. Damit sieht der Sweep auch
* Nachbarflächen, die in CityGML nicht in derselben Geometry wie das
* Fehlerpolygon liegen.
*/
private
List
<
Geometry
>
collectGeometries
(
CityObject
topLevelFeature
)
{
List
<
Geometry
>
geometries
=
new
ArrayList
<>();
topLevelFeature
.
accept
(
new
CheckableUtilsVisitor
()
{
@Override
public
void
check
(
Geometry
geometry
)
{
geometries
.
add
(
geometry
);
}
});
return
geometries
;
}
/**
* Prüft, ob genau ein Endpunkt der Kante koordinatengleich zum Sweep-Punkt ist.
* Falls ja, wird der andere Endpunkt als Richtungspunkt der abführenden Kante
* geliefert.
*/
private
Vertex
findOppositeEndpointAtSweepVertex
(
Edge
edge
,
double
pointTolerance
)
{
boolean
fromIsSweepVertex
=
edge
.
getFrom
().
equalsWithEpsilon
(
sweepVertex
,
pointTolerance
);
boolean
toIsSweepVertex
=
edge
.
getTo
().
equalsWithEpsilon
(
sweepVertex
,
pointTolerance
);
if
(
fromIsSweepVertex
==
toIsSweepVertex
)
{
return
null
;
}
return
fromIsSweepVertex
?
edge
.
getTo
()
:
edge
.
getFrom
();
}
/**
* Erkennt Kanten, die zur Fehlerfläche selbst gehören. Diese sind nicht
* abführend, auch wenn sie am Sweep-Punkt liegen.
*/
private
boolean
isEdgeOfErrorPolygon
(
Edge
edge
,
Polygon
errorPoly
,
double
pointTolerance
)
{
if
(
isEdgeOfRing
(
edge
,
errorPoly
.
getExteriorRing
(),
pointTolerance
))
{
return
true
;
}
for
(
LinearRing
innerRing
:
errorPoly
.
getInnerRings
())
{
if
(
isEdgeOfRing
(
edge
,
innerRing
,
pointTolerance
))
{
return
true
;
}
}
return
false
;
}
/**
* Prüft eine Kante gegen alle Kanten eines Rings. Die Ringrichtung ist dabei
* egal, weil dieselbe geometrische Kante in Nachbarflächen umgekehrt auftreten
* kann.
*/
private
boolean
isEdgeOfRing
(
Edge
edge
,
LinearRing
ring
,
double
pointTolerance
)
{
if
(
ring
==
null
)
{
return
false
;
}
List
<
Vertex
>
vertices
=
ring
.
getVertices
();
for
(
int
i
=
0
;
i
<
vertices
.
size
()
-
1
;
i
++)
{
Vertex
first
=
vertices
.
get
(
i
);
Vertex
second
=
vertices
.
get
(
i
+
1
);
if
(
sameUndirectedSegment
(
edge
.
getFrom
(),
edge
.
getTo
(),
first
,
second
,
pointTolerance
))
{
return
true
;
}
}
return
false
;
}
/**
* Vergleicht zwei ungerichtete Strecken mit Punkt-Toleranz.
*/
private
boolean
sameUndirectedSegment
(
Vertex
firstA
,
Vertex
secondA
,
Vertex
firstB
,
Vertex
secondB
,
double
pointTolerance
)
{
return
firstA
.
equalsWithEpsilon
(
firstB
,
pointTolerance
)
&&
secondA
.
equalsWithEpsilon
(
secondB
,
pointTolerance
)
||
firstA
.
equalsWithEpsilon
(
secondB
,
pointTolerance
)
&&
secondA
.
equalsWithEpsilon
(
firstB
,
pointTolerance
);
}
/**
* Fügt die gefundene Kante nur einmal hinzu. Die synthetische Edge enthält den
* echten sweepVertex, damit SweepEdge.create später getOppositeVertex verwenden
* kann.
*/
private
void
addOutgoingEdgeIfMissing
(
Edge
candidate
,
double
pointTolerance
)
{
Vertex
candidateTarget
=
candidate
.
getOppositeVertex
(
sweepVertex
);
for
(
Edge
existing
:
outgoingEdges
)
{
Vertex
existingTarget
=
existing
.
getOppositeVertex
(
sweepVertex
);
if
(
existingTarget
!=
null
&&
existingTarget
.
equalsWithEpsilon
(
candidateTarget
,
pointTolerance
))
{
return
;
}
}
outgoingEdges
.
add
(
candidate
);
}
}
}
CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/SweepUtilitiesRealGmlTest.java
View file @
aeae0705
package
de.hft.stuttgart.citydoctor2.healing
;
package
de.hft.stuttgart.citydoctor2.healing
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.nio.file.Path
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.EnumMap
;
import
java.util.EnumMap
;
import
java.util.HashMap
;
import
java.util.HashMap
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Map
;
...
@@ -32,39 +35,28 @@ import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
...
@@ -32,39 +35,28 @@ import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
/*
/*
* @Numanoglu
* @Numanoglu
*
*
* WIP !
* */
/**
* Export einzelner Buildings funktioniert noch immer nicht
*
*
* Führt den Planaritätscheck über den normalen CityDoctor-Checker aus.
* Führt den Planaritätscheck über den normalen CityDoctor-Checker aus.
*
*
* Wichtig: Der PlanarCheck sollte hier nicht isoliert per
* Der Test unterscheidet rohe Fehler-Einträge und eindeutige Fehler-Fazetten.
* geometry.accept(check) aufgerufen werden. Der Checker bildet den echten
* Das ist nötig, weil linked polygons denselben fachlichen Fehler mehrfach in
* Validierungspfad nach: Er initialisiert die Check-Parameter, berücksichtigt
* die Error-Sammlung bringen können. Deduplication geschieht deshalb über
* Abhängigkeiten und bereitet die Geometrien vor. Dadurch entspricht der Test
* Feature-ID, Geometry-ID und Polygon-ID, nicht über Java-Objektidentität.
* eher dem Verhalten aus GUI und regulärer Validierung.
*
* Der Parameter type = both aktiviert beide Planaritätsvarianten:
* distance: Punktabstand zur Regressionsebene, liefert
* GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE.
* angle: Normalenabweichung der triangulierten Teilflächen, liefert
* GE_P_NON_PLANAR_POLYGON_NORMALS_DEVIATION.
*
*
* Für den Sweep ist momentan vor allem der DistancePlane-Fehler relevant,
* Nach doHeal() wird der Checker erneut auf dem ganzen Modell ausgeführt. Damit
* weil dieser bereits eine Zielebene im NonPlanarPolygonDistancePlaneError
* bildet der Test zumindest den ersten Schritt des GUI-Healers nach: eine
* mitliefert. Die Normalenabweichung wird hier trotzdem mitgeprüft, damit der
* Fazette heilen, Geometrie neu prüfen, danach sehen, ob im Gesamtmodell neue
* Test sichtbar macht, ob eine reale GUI-Meldung zwar non-planar ist, aber
* Fehler entstanden sind.
* noch nicht in den aktuellen Sweep-Pfad fällt.
*/
*/
public
class
SweepUtilitiesRealGmlTest
{
public
class
SweepUtilitiesRealGmlTest
{
private
static
final
double
POINT_ON_PLANE_TOLERANCE
=
0.001
;
private
static
final
double
POINT_ON_PLANE_TOLERANCE
=
0.001
;
private
static
final
double
SWEEP_EDGE_AND_ANGLE_TOLERANCE
=
0.001
;
private
static
final
double
SWEEP_EDGE_AND_ANGLE_TOLERANCE
=
0.001
;
private
static
final
String
STRICT_PLANAR_DISTANCE_TOLERANCE
=
"0.001"
;
private
static
final
String
PLANAR_DISTANCE_TOLERANCE
=
"0.01"
;
private
static
final
String
PLANAR_CHECK_TYPE
=
PlanarCheck
.
BOTH
;
private
static
final
String
PLANAR_CHECK_TYPE
=
PlanarCheck
.
DISTANCE
;
private
static
final
boolean
PRINT_EACH_RELEVANT_SWEEP_VERTEX
=
true
;
/**
/**
* Reales ganzes CityGML:NpR1 . Der Test lädt die Datei über den
* Reales ganzes CityGML:NpR1 . Der Test lädt die Datei über den
...
@@ -76,53 +68,35 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -76,53 +68,35 @@ public class SweepUtilitiesRealGmlTest {
checkRealGmlAndTrySweepHealing
(
"src/test/resources/sweep/non-planar-distance-plane/NP_R_1.gml"
);
checkRealGmlAndTrySweepHealing
(
"src/test/resources/sweep/non-planar-distance-plane/NP_R_1.gml"
);
}
}
/**
* Reales größeres GML:LoD21005WithVal3dityErrors: mit bekannten val3dity-Volumenfehlern(AG-Qual).
* Für den Sweep ist hier zuerst wichtig, ob zusätzlich passende nicht-planare Polygonfehler vom
* Typ DistancePlane gefunden werden. (Da einzelne Gebäude-Exports mit solchen Fehler 0byte-Daten)
*/
@Test
public
void
reportsAndTriesSweepForLoD21005WithVal3dityErrors
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
{
checkRealGmlAndTrySweepHealing
(
"src/test/resources/sweep/non-planar-distance-plane/LoD2_10_05_mit_val3dity_volumenfehlern.gml"
);
}
/**
* Reales CityGML: LoD2_426_6002_2_SH. Diese Datei wurde im GUI Starter bereits
* als passend untersucht.
* Es muss NonPlanarDistancePlane-Fehler enthalten!
*/
@Test
public
void
reportsAndTriesSweepForLoD242660022Sh
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
{
checkRealGmlAndTrySweepHealing
(
"src/test/resources/sweep/non-planar-distance-plane/LoD2_426_6002_2_SH.xml"
);
}
private
void
checkRealGmlAndTrySweepHealing
(
String
path
)
private
void
checkRealGmlAndTrySweepHealing
(
String
path
)
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
{
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
{
String
fileName
=
Path
.
of
(
path
).
getFileName
().
toString
();
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
config
.
setSchematronFilePathInGlobalParameters
(
null
);
config
.
getRequirements
().
get
(
Requirement
.
R_GE_P_NON_PLANAR
.
toString
()).
getParameters
()
config
.
getRequirements
().
get
(
Requirement
.
R_GE_P_NON_PLANAR
.
toString
()).
getParameters
()
.
put
(
PlanarCheck
.
TYPE
,
PLANAR_CHECK_TYPE
);
.
put
(
PlanarCheck
.
TYPE
,
PLANAR_CHECK_TYPE
);
config
.
getRequirements
().
get
(
Requirement
.
R_GE_P_NON_PLANAR
.
toString
()).
getParameters
()
config
.
getRequirements
().
get
(
Requirement
.
R_GE_P_NON_PLANAR
.
toString
()).
getParameters
()
.
put
(
PlanarCheck
.
DISTANCE_TOLERANCE
,
STRICT_
PLANAR_DISTANCE_TOLERANCE
);
.
put
(
PlanarCheck
.
DISTANCE_TOLERANCE
,
PLANAR_DISTANCE_TOLERANCE
);
CityDoctorModel
model
=
TestUtil
.
loadCityModel
(
path
,
config
);
CityDoctorModel
model
=
TestUtil
.
loadCityModel
(
path
,
config
);
Checker
checker
=
new
Checker
(
config
,
model
);
Checker
checker
=
new
Checker
(
config
,
model
);
checker
.
runChecks
();
checker
.
runChecks
();
List
<
NonPlanarPolygonDistancePlaneError
>
errors
=
collectDistancePlaneErrors
(
model
);
List
<
CheckError
>
rawErrors
=
collectErrors
(
model
);
Map
<
ErrorId
,
Integer
>
errorCounts
=
countErrorsById
(
model
);
List
<
NonPlanarPolygonDistancePlaneError
>
errors
=
collectUniqueDistancePlaneErrors
(
rawErrors
);
Map
<
ErrorId
,
Integer
>
rawErrorCounts
=
countErrorsById
(
rawErrors
);
int
rawDistancePlaneCount
=
countDistancePlaneErrors
(
rawErrors
);
System
.
out
.
println
();
System
.
out
.
println
();
System
.
out
.
println
(
"=== Real-GML Sweep-Test ==="
);
System
.
out
.
println
(
"=== Real-GML Sweep-Test ==="
);
System
.
out
.
println
(
"Datei: "
+
path
);
System
.
out
.
println
(
"Datei: "
+
fileName
);
System
.
out
.
println
(
"Pfad: "
+
path
);
printModelSummary
(
model
);
printModelSummary
(
model
);
System
.
out
.
println
(
"PlanarCheck: Typ "
+
PLANAR_CHECK_TYPE
+
", distanceTolerance = "
System
.
out
.
println
(
"PlanarCheck: Typ "
+
PLANAR_CHECK_TYPE
+
", distanceTolerance = "
+
STRICT_PLANAR_DISTANCE_TOLERANCE
);
+
PLANAR_DISTANCE_TOLERANCE
);
System
.
out
.
println
(
"Fehlerzählung nach ErrorId: "
+
errorCounts
);
System
.
out
.
println
(
"Fehlerzählung roh nach ErrorId: "
+
rawErrorCounts
);
System
.
out
.
println
(
"Gefundene NonPlanarDistancePlane-Fehler: "
+
errors
.
size
());
System
.
out
.
println
(
"Rohe NonPlanarDistancePlane-Einträge: "
+
rawDistancePlaneCount
);
System
.
out
.
println
(
"Eindeutige NonPlanarDistancePlane-Fazetten: "
+
errors
.
size
());
System
.
out
.
println
(
"Mehrfach eingesammelte DistancePlane-Einträge: "
+
(
rawDistancePlaneCount
-
errors
.
size
()));
assertFalse
(
"Die Datei muss mindestens einen NonPlanarPolygonDistancePlaneError enthalten."
,
errors
.
isEmpty
());
assertFalse
(
"Die Datei muss mindestens einen NonPlanarPolygonDistancePlaneError enthalten."
,
errors
.
isEmpty
());
int
healedCount
=
0
;
int
healedCount
=
0
;
...
@@ -133,7 +107,7 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -133,7 +107,7 @@ public class SweepUtilitiesRealGmlTest {
SWEEP_EDGE_AND_ANGLE_TOLERANCE
);
SWEEP_EDGE_AND_ANGLE_TOLERANCE
);
System
.
out
.
println
();
System
.
out
.
println
();
System
.
out
.
println
(
"Fehler "
+
(
i
+
1
)
+
" von "
+
errors
.
size
());
System
.
out
.
println
(
"Fehler "
+
(
i
+
1
)
+
" von "
+
errors
.
size
()
+
" in Datei "
+
fileName
);
printErrorSummary
(
error
);
printErrorSummary
(
error
);
utilities
.
initialize
();
utilities
.
initialize
();
...
@@ -145,6 +119,11 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -145,6 +119,11 @@ public class SweepUtilitiesRealGmlTest {
utilities
.
doHeal
();
utilities
.
doHeal
();
boolean
polygonPlanarAfterHealing
=
isPolygonPlanar
(
error
.
getPolygon
());
boolean
polygonPlanarAfterHealing
=
isPolygonPlanar
(
error
.
getPolygon
());
System
.
out
.
println
(
"PlanarCheck nach doHeal() für diese Fazette: "
+
polygonPlanarAfterHealing
);
System
.
out
.
println
(
"PlanarCheck nach doHeal() für diese Fazette: "
+
polygonPlanarAfterHealing
);
assertTrue
(
"Die aktuell geheilte Fazette muss nach doHeal() planar sein."
,
polygonPlanarAfterHealing
);
recheckWholeModel
(
checker
,
model
);
int
remainingDistancePlaneFacets
=
printModelErrorSummaryAfterHealing
(
model
);
assertEquals
(
"Nach doHeal() und erneutem Checker-Lauf dürfen keine DistancePlane-Fazetten übrig sein."
,
0
,
remainingDistancePlaneFacets
);
if
(
polygonPlanarAfterHealing
)
{
if
(
polygonPlanarAfterHealing
)
{
healedCount
++;
healedCount
++;
}
else
{
}
else
{
...
@@ -164,39 +143,103 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -164,39 +143,103 @@ public class SweepUtilitiesRealGmlTest {
healedCount
>
0
);
healedCount
>
0
);
}
}
// Evtl.Obsolet
private
PlanarCheck
createStrictDistancePlanarCheck
()
{
private
PlanarCheck
createStrictDistancePlanarCheck
()
{
PlanarCheck
check
=
new
PlanarCheck
();
PlanarCheck
check
=
new
PlanarCheck
();
Map
<
CheckId
,
Map
<
String
,
String
>>
parameters
=
new
HashMap
<>();
Map
<
CheckId
,
Map
<
String
,
String
>>
parameters
=
new
HashMap
<>();
Map
<
String
,
String
>
planarParameters
=
new
HashMap
<>();
Map
<
String
,
String
>
planarParameters
=
new
HashMap
<>();
planarParameters
.
put
(
PlanarCheck
.
TYPE
,
PLANAR_CHECK_TYPE
);
planarParameters
.
put
(
PlanarCheck
.
TYPE
,
PLANAR_CHECK_TYPE
);
planarParameters
.
put
(
PlanarCheck
.
DISTANCE_TOLERANCE
,
STRICT_
PLANAR_DISTANCE_TOLERANCE
);
planarParameters
.
put
(
PlanarCheck
.
DISTANCE_TOLERANCE
,
PLANAR_DISTANCE_TOLERANCE
);
parameters
.
put
(
CheckId
.
C_GE_P_NON_PLANAR
,
planarParameters
);
parameters
.
put
(
CheckId
.
C_GE_P_NON_PLANAR
,
planarParameters
);
check
.
init
(
parameters
,
null
);
check
.
init
(
parameters
,
null
);
return
check
;
return
check
;
}
}
private
List
<
NonPlanarPolygonDistancePlaneError
>
collectDistancePlaneErrors
(
CityDoctorModel
model
)
{
/**
* Prüft das komplette Modell erneut. Das ist näher am GUI-Healer als eine reine
* Einzelpolygon-Prüfung, weil dadurch auch neu entstandene Nachbarfehler
* sichtbar werden.
*/
private
void
recheckWholeModel
(
Checker
checker
,
CityDoctorModel
model
)
{
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
cityObject
.
prepareForChecking
();
checker
.
executeChecksForCheckable
(
cityObject
);
}
}
/**
* Druckt die Fehlerlage nach einem Heal-Schritt. Besonders wichtig ist der
* Vergleich zwischen rohen DistancePlane-Einträgen und eindeutigen Fazetten.
*/
private
int
printModelErrorSummaryAfterHealing
(
CityDoctorModel
model
)
{
List
<
CheckError
>
errorsAfterHealing
=
collectErrors
(
model
);
List
<
NonPlanarPolygonDistancePlaneError
>
distancePlaneErrorsAfterHealing
=
collectUniqueDistancePlaneErrors
(
errorsAfterHealing
);
Map
<
ErrorId
,
Integer
>
errorCountsAfterHealing
=
countErrorsById
(
errorsAfterHealing
);
int
rawDistancePlaneCountAfterHealing
=
countDistancePlaneErrors
(
errorsAfterHealing
);
System
.
out
.
println
(
"Fehlerzählung im Gesamtmodell nach doHeal(): "
+
errorCountsAfterHealing
);
System
.
out
.
println
(
"Rohe NonPlanarDistancePlane-Einträge nach doHeal(): "
+
rawDistancePlaneCountAfterHealing
);
System
.
out
.
println
(
"Eindeutige NonPlanarDistancePlane-Fazetten nach doHeal(): "
+
distancePlaneErrorsAfterHealing
.
size
());
return
distancePlaneErrorsAfterHealing
.
size
();
}
/**
* Sammelt alle aktuell im Modell gespeicherten CheckError-Objekte.
*/
private
List
<
CheckError
>
collectErrors
(
CityDoctorModel
model
)
{
List
<
CheckError
>
allErrors
=
new
ArrayList
<>();
List
<
CheckError
>
allErrors
=
new
ArrayList
<>();
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
cityObject
.
collectContainedErrors
(
allErrors
);
cityObject
.
collectContainedErrors
(
allErrors
);
}
}
return
allErrors
;
}
List
<
NonPlanarPolygonDistancePlaneError
>
distancePlaneErrors
=
new
ArrayList
<>();
/**
* Filtert DistancePlane-Fehler und entfernt fachliche Duplikate. Linked
* Polygons können denselben Fehler mehrfach liefern; für den Sweep wollen wir
* jede betroffene Fazette nur einmal behandeln.
*/
private
List
<
NonPlanarPolygonDistancePlaneError
>
collectUniqueDistancePlaneErrors
(
List
<
CheckError
>
allErrors
)
{
Map
<
String
,
NonPlanarPolygonDistancePlaneError
>
distancePlaneErrorsByFacet
=
new
LinkedHashMap
<>();
for
(
CheckError
error
:
allErrors
)
{
for
(
CheckError
error
:
allErrors
)
{
if
(
error
.
getErrorId
()
==
ErrorId
.
GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE
)
{
if
(
error
.
getErrorId
()
==
ErrorId
.
GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE
)
{
distancePlaneErrors
.
add
((
NonPlanarPolygonDistancePlaneError
)
error
);
NonPlanarPolygonDistancePlaneError
distancePlaneError
=
(
NonPlanarPolygonDistancePlaneError
)
error
;
distancePlaneErrorsByFacet
.
putIfAbsent
(
createFacetKey
(
distancePlaneError
),
distancePlaneError
);
}
}
}
}
return
distancePlaneErrors
;
return
new
ArrayList
<>(
distancePlaneErrors
ByFacet
.
values
())
;
}
}
private
Map
<
ErrorId
,
Integer
>
countErrorsById
(
CityDoctorModel
model
)
{
/**
List
<
CheckError
>
allErrors
=
new
ArrayList
<>();
* Zählt rohe DistancePlane-Einträge ohne Deduplication.
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
*/
cityObject
.
collectContainedErrors
(
allErrors
);
private
int
countDistancePlaneErrors
(
List
<
CheckError
>
allErrors
)
{
int
count
=
0
;
for
(
CheckError
error
:
allErrors
)
{
if
(
error
.
getErrorId
()
==
ErrorId
.
GE_P_NON_PLANAR_POLYGON_DISTANCE_PLANE
)
{
count
++;
}
}
}
return
count
;
}
/**
* Stabiler fachlicher Schlüssel für eine Fazette. Objektidentität reicht hier
* nicht, weil derselbe GML-Fehler über linked polygons mehrfach auftauchen kann.
*/
private
String
createFacetKey
(
NonPlanarPolygonDistancePlaneError
error
)
{
Polygon
polygon
=
error
.
getPolygon
();
Geometry
geometry
=
polygon
.
getParent
();
CityObject
cityObject
=
geometry
.
getParent
();
return
cityObject
.
getGmlId
().
getGmlString
()
+
"|"
+
geometry
.
getGmlId
().
getGmlString
()
+
"|"
+
polygon
.
getGmlId
().
getGmlString
();
}
/**
* Zählt Fehler nach ErrorId für kompakte Debug-Ausgaben.
*/
private
Map
<
ErrorId
,
Integer
>
countErrorsById
(
List
<
CheckError
>
allErrors
)
{
Map
<
ErrorId
,
Integer
>
errorCounts
=
new
HashMap
<>();
Map
<
ErrorId
,
Integer
>
errorCounts
=
new
HashMap
<>();
for
(
CheckError
error
:
allErrors
)
{
for
(
CheckError
error
:
allErrors
)
{
errorCounts
.
merge
(
error
.
getErrorId
(),
1
,
Integer:
:
sum
);
errorCounts
.
merge
(
error
.
getErrorId
(),
1
,
Integer:
:
sum
);
...
@@ -234,6 +277,7 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -234,6 +277,7 @@ public class SweepUtilitiesRealGmlTest {
System
.
out
.
println
(
"Gebäude/Feature: "
+
cityObject
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Gebäude/Feature: "
+
cityObject
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Geometrie: "
+
geometry
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Geometrie: "
+
geometry
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Polygone in Parent-Geometrie: "
+
geometry
.
getPolygons
().
size
());
System
.
out
.
println
(
"Kaputte Fazette: "
+
polygon
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Kaputte Fazette: "
+
polygon
.
getGmlId
().
getGmlString
());
System
.
out
.
println
(
"Maximaler Abstand laut Error: "
+
error
.
getDistance
());
System
.
out
.
println
(
"Maximaler Abstand laut Error: "
+
error
.
getDistance
());
Vertex
errorVertex
=
error
.
getVertex
();
Vertex
errorVertex
=
error
.
getVertex
();
...
@@ -250,22 +294,46 @@ public class SweepUtilitiesRealGmlTest {
...
@@ -250,22 +294,46 @@ public class SweepUtilitiesRealGmlTest {
}
}
Map
<
SweepVertexStatus
,
Integer
>
statusCounts
=
new
EnumMap
<>(
SweepVertexStatus
.
class
);
Map
<
SweepVertexStatus
,
Integer
>
statusCounts
=
new
EnumMap
<>(
SweepVertexStatus
.
class
);
Map
<
Integer
,
Integer
>
outgoingEdgeCounts
=
new
HashMap
<>();
for
(
SweepVertexStatus
status
:
SweepVertexStatus
.
values
())
{
for
(
SweepVertexStatus
status
:
SweepVertexStatus
.
values
())
{
statusCounts
.
put
(
status
,
0
);
statusCounts
.
put
(
status
,
0
);
}
}
for
(
SweepVertex
sweepVertex
:
sweepVertices
)
{
for
(
SweepVertex
sweepVertex
:
sweepVertices
)
{
SweepVertexStatus
status
=
sweepVertex
.
getStatus
();
SweepVertexStatus
status
=
sweepVertex
.
getStatus
();
statusCounts
.
put
(
status
,
statusCounts
.
get
(
status
)
+
1
);
statusCounts
.
put
(
status
,
statusCounts
.
get
(
status
)
+
1
);
outgoingEdgeCounts
.
merge
(
sweepVertex
.
countOutgoingEdges
(),
1
,
Integer:
:
sum
);
}
}
System
.
out
.
println
(
"initialize(): SweepVertices gesamt: "
+
sweepVertices
.
size
());
System
.
out
.
println
(
"initialize(): SweepVertices gesamt: "
+
sweepVertices
.
size
());
System
.
out
.
println
(
"Statuszählung: "
+
statusCounts
);
System
.
out
.
println
(
"Statuszählung: "
+
statusCounts
);
System
.
out
.
println
(
"Abführende-Kanten-Zählung pro Punkt: "
+
outgoingEdgeCounts
);
System
.
out
.
println
(
"Fall-3-Kandidaten nach Topologie, also Punkte mit mehr als 1 abführender Kante: "
+
countMultiCandidatesByOutgoingEdges
(
sweepVertices
));
System
.
out
.
println
(
"Hinweis: MULTI kann nach initialize() schon wieder verschwunden sein, weil initialize() bei MULTI die Zielebene anpasst und danach neu klassifiziert."
);
for
(
int
i
=
0
;
i
<
sweepVertices
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
sweepVertices
.
size
();
i
++)
{
SweepVertex
sweepVertex
=
sweepVertices
.
get
(
i
);
SweepVertex
sweepVertex
=
sweepVertices
.
get
(
i
);
if
(!
PRINT_EACH_RELEVANT_SWEEP_VERTEX
||
!
isRelevantForDebugOutput
(
sweepVertex
))
{
continue
;
}
Vertex
vertex
=
sweepVertex
.
getSweepVertex
();
Vertex
vertex
=
sweepVertex
.
getSweepVertex
();
System
.
out
.
println
(
" SweepVertex "
+
i
+
": Status "
+
sweepVertex
.
getStatus
()
+
", abführende Kanten "
System
.
out
.
println
(
" SweepVertex "
+
i
+
": Status "
+
sweepVertex
.
getStatus
()
+
", abführende Kanten "
+
sweepVertex
.
countOutgoingEdges
()
+
", Punkt x="
+
vertex
.
getX
()
+
", y="
+
vertex
.
getY
()
+
sweepVertex
.
countOutgoingEdges
()
+
", Punkt x="
+
vertex
.
getX
()
+
", y="
+
vertex
.
getY
()
+
", z="
+
vertex
.
getZ
());
+
", z="
+
vertex
.
getZ
());
}
}
}
}
private
int
countMultiCandidatesByOutgoingEdges
(
List
<
SweepVertex
>
sweepVertices
)
{
int
count
=
0
;
for
(
SweepVertex
sweepVertex
:
sweepVertices
)
{
if
(
sweepVertex
.
countOutgoingEdges
()
>
1
)
{
count
++;
}
}
return
count
;
}
private
boolean
isRelevantForDebugOutput
(
SweepVertex
sweepVertex
)
{
return
sweepVertex
.
getStatus
()
!=
SweepVertexStatus
.
ON_PLANE
||
sweepVertex
.
countOutgoingEdges
()
>
0
;
}
}
}
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