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;
import
org.apache.logging.log4j.Logger
;
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.Polygon
;
import
de.hft.stuttgart.citydoctor2.datastructure.Vertex
;
...
...
@@ -21,6 +22,7 @@ public class SweepUtilities {
private
Plane
targetPlane
;
private
Polygon
errorPolygon
;
private
CityObject
topLevelFeature
;
private
double
minSweepEdgeLength
=
0.01
;
private
double
pointOnPlaneTolerance
=
0.00011
;
...
...
@@ -42,6 +44,7 @@ public class SweepUtilities {
targetPlane
=
err
.
getPlane
();
errorPolygon
=
err
.
getPolygon
();
topLevelFeature
=
findTopLevelFeature
(
errorPolygon
);
}
...
...
@@ -59,7 +62,7 @@ public class SweepUtilities {
collectSweepVertices
(
errorPolygon
);
for
(
SweepVertex
v
:
sweepVertices
)
{
v
.
collectOutgoingEdges
(
errorPolygon
,
minSweepEdgeLength
);
v
.
collectOutgoingEdges
(
topLevelFeature
,
errorPolygon
,
minSweepEdgeLength
,
pointOnPlaneTolerance
);
v
.
initStatus
(
targetPlane
,
pointOnPlaneTolerance
,
true
);
}
...
...
@@ -207,7 +210,6 @@ public class SweepUtilities {
Vector3d
thirdPoint
=
multiVerts
.
get
(
2
).
getSweepVertex
();
Vector3d
firstDirection
=
secondPoint
.
minus
(
firstPoint
);
Vector3d
secondDirection
=
thirdPoint
.
minus
(
firstPoint
);
// Normalieren?!
Vector3d
newNormal
=
firstDirection
.
cross
(
secondDirection
);
if
(
newNormal
.
getLength
()
<
pointOnPlaneTolerance
)
{
...
...
@@ -267,4 +269,12 @@ public class SweepUtilities {
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
;
import
java.util.ArrayList
;
import
java.util.LinkedList
;
import
java.util.List
;
import
org.apache.logging.log4j.LogManager
;
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.Edge
;
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.math.Plane
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
...
...
@@ -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
)
{
if
(
targetPlane
==
null
)
{
...
...
@@ -200,4 +245,96 @@ public class SweepVertex {
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
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
java.io.IOException
;
import
java.nio.file.Path
;
import
java.util.ArrayList
;
import
java.util.EnumMap
;
import
java.util.HashMap
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -32,39 +35,28 @@ import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
/*
* @Numanoglu
*
* WIP !
* */
/**
* Export einzelner Buildings funktioniert noch immer nicht
*
*
* Führt den Planaritätscheck über den normalen CityDoctor-Checker aus.
*
* Wichtig: Der PlanarCheck sollte hier nicht isoliert per
* geometry.accept(check) aufgerufen werden. Der Checker bildet den echten
* Validierungspfad nach: Er initialisiert die Check-Parameter, berücksichtigt
* Abhängigkeiten und bereitet die Geometrien vor. Dadurch entspricht der Test
* 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.
* Der Test unterscheidet rohe Fehler-Einträge und eindeutige Fehler-Fazetten.
* Das ist nötig, weil linked polygons denselben fachlichen Fehler mehrfach in
* die Error-Sammlung bringen können. Deduplication geschieht deshalb über
* Feature-ID, Geometry-ID und Polygon-ID, nicht über Java-Objektidentität.
*
* Für den Sweep ist momentan vor allem der DistancePlane-Fehler relevant,
* weil dieser bereits eine Zielebene im NonPlanarPolygonDistancePlaneError
* mitliefert. Die Normalenabweichung wird hier trotzdem mitgeprüft, damit der
* Test sichtbar macht, ob eine reale GUI-Meldung zwar non-planar ist, aber
* noch nicht in den aktuellen Sweep-Pfad fällt.
* Nach doHeal() wird der Checker erneut auf dem ganzen Modell ausgeführt. Damit
* bildet der Test zumindest den ersten Schritt des GUI-Healers nach: eine
* Fazette heilen, Geometrie neu prüfen, danach sehen, ob im Gesamtmodell neue
* Fehler entstanden sind.
*/
public
class
SweepUtilitiesRealGmlTest
{
private
static
final
double
POINT_ON_PLANE_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_CHECK_TYPE
=
PlanarCheck
.
BOTH
;
private
static
final
String
PLANAR_DISTANCE_TOLERANCE
=
"0.01"
;
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
...
...
@@ -76,53 +68,35 @@ public class SweepUtilitiesRealGmlTest {
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
)
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
{
String
fileName
=
Path
.
of
(
path
).
getFileName
().
toString
();
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
setSchematronFilePathInGlobalParameters
(
null
);
config
.
getRequirements
().
get
(
Requirement
.
R_GE_P_NON_PLANAR
.
toString
()).
getParameters
()
.
put
(
PlanarCheck
.
TYPE
,
PLANAR_CHECK_TYPE
);
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
);
Checker
checker
=
new
Checker
(
config
,
model
);
checker
.
runChecks
();
List
<
NonPlanarPolygonDistancePlaneError
>
errors
=
collectDistancePlaneErrors
(
model
);
Map
<
ErrorId
,
Integer
>
errorCounts
=
countErrorsById
(
model
);
List
<
CheckError
>
rawErrors
=
collectErrors
(
model
);
List
<
NonPlanarPolygonDistancePlaneError
>
errors
=
collectUniqueDistancePlaneErrors
(
rawErrors
);
Map
<
ErrorId
,
Integer
>
rawErrorCounts
=
countErrorsById
(
rawErrors
);
int
rawDistancePlaneCount
=
countDistancePlaneErrors
(
rawErrors
);
System
.
out
.
println
();
System
.
out
.
println
(
"=== Real-GML Sweep-Test ==="
);
System
.
out
.
println
(
"Datei: "
+
path
);
System
.
out
.
println
(
"Datei: "
+
fileName
);
System
.
out
.
println
(
"Pfad: "
+
path
);
printModelSummary
(
model
);
System
.
out
.
println
(
"PlanarCheck: Typ "
+
PLANAR_CHECK_TYPE
+
", distanceTolerance = "
+
STRICT_PLANAR_DISTANCE_TOLERANCE
);
System
.
out
.
println
(
"Fehlerzählung nach ErrorId: "
+
errorCounts
);
System
.
out
.
println
(
"Gefundene NonPlanarDistancePlane-Fehler: "
+
errors
.
size
());
+
PLANAR_DISTANCE_TOLERANCE
);
System
.
out
.
println
(
"Fehlerzählung roh nach ErrorId: "
+
rawErrorCounts
);
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
());
int
healedCount
=
0
;
...
...
@@ -133,7 +107,7 @@ public class SweepUtilitiesRealGmlTest {
SWEEP_EDGE_AND_ANGLE_TOLERANCE
);
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
);
utilities
.
initialize
();
...
...
@@ -145,6 +119,11 @@ public class SweepUtilitiesRealGmlTest {
utilities
.
doHeal
();
boolean
polygonPlanarAfterHealing
=
isPolygonPlanar
(
error
.
getPolygon
());
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
)
{
healedCount
++;
}
else
{
...
...
@@ -164,39 +143,103 @@ public class SweepUtilitiesRealGmlTest {
healedCount
>
0
);
}
// Evtl.Obsolet
private
PlanarCheck
createStrictDistancePlanarCheck
()
{
PlanarCheck
check
=
new
PlanarCheck
();
Map
<
CheckId
,
Map
<
String
,
String
>>
parameters
=
new
HashMap
<>();
Map
<
String
,
String
>
planarParameters
=
new
HashMap
<>();
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
);
check
.
init
(
parameters
,
null
);
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
<>();
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
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
)
{
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
<>();
for
(
CityObject
cityObject
:
model
.
createFeatureStream
().
toList
())
{
cityObject
.
collectContainedErrors
(
allErrors
);
/**
* Zählt rohe DistancePlane-Einträge ohne Deduplication.
*/
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
<>();
for
(
CheckError
error
:
allErrors
)
{
errorCounts
.
merge
(
error
.
getErrorId
(),
1
,
Integer:
:
sum
);
...
...
@@ -234,6 +277,7 @@ public class SweepUtilitiesRealGmlTest {
System
.
out
.
println
(
"Gebäude/Feature: "
+
cityObject
.
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
(
"Maximaler Abstand laut Error: "
+
error
.
getDistance
());
Vertex
errorVertex
=
error
.
getVertex
();
...
...
@@ -250,22 +294,46 @@ public class SweepUtilitiesRealGmlTest {
}
Map
<
SweepVertexStatus
,
Integer
>
statusCounts
=
new
EnumMap
<>(
SweepVertexStatus
.
class
);
Map
<
Integer
,
Integer
>
outgoingEdgeCounts
=
new
HashMap
<>();
for
(
SweepVertexStatus
status
:
SweepVertexStatus
.
values
())
{
statusCounts
.
put
(
status
,
0
);
}
for
(
SweepVertex
sweepVertex
:
sweepVertices
)
{
SweepVertexStatus
status
=
sweepVertex
.
getStatus
();
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
(
"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
++)
{
SweepVertex
sweepVertex
=
sweepVertices
.
get
(
i
);
if
(!
PRINT_EACH_RELEVANT_SWEEP_VERTEX
||
!
isRelevantForDebugOutput
(
sweepVertex
))
{
continue
;
}
Vertex
vertex
=
sweepVertex
.
getSweepVertex
();
System
.
out
.
println
(
" SweepVertex "
+
i
+
": Status "
+
sweepVertex
.
getStatus
()
+
", abführende Kanten "
+
sweepVertex
.
countOutgoingEdges
()
+
", Punkt x="
+
vertex
.
getX
()
+
", y="
+
vertex
.
getY
()
+
", 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