Commit d3605f6a authored by Numanoglu's avatar Numanoglu
Browse files

remove metadata

parent e087346b
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.util.*;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class TestHouseAABB {
private static ConcretePolygon createTrianglePolygon(Point p1, Point p2, Point p3) {
ConcretePolygon poly = new ConcretePolygon();
LinearRing ring = new LinearRing(LinearRingType.EXTERIOR);
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Ring schließen
poly.setExteriorRing(ring);
return poly;
}
private static ConcretePolygon createQuadPolygon(Point p1, Point p2, Point p3, Point p4) {
ConcretePolygon poly = new ConcretePolygon();
LinearRing ring = new LinearRing(LinearRingType.EXTERIOR);
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Ring schließen
poly.setExteriorRing(ring);
return poly;
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import de.hft.stuttgart.citydoctor2.datastructure.bht.*;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.parser.*;
import de.hft.stuttgart.citydoctor2.systemtest.TestUtil;
import de.hft.stuttgart.citydoctor2.check.*;
//import de.hft.stuttgart.citydoctor2.healing.*;
import java.io.IOException;
import java.util.*;
//src/test/java/.../TestCityGmlAABB.java
import java.net.URL;
import java.nio.file.Paths;
public class TestCityGmlAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("=== CityGML AABB Test ===");
// --- 1) Ask user which test case to run ---
Scanner sc = new Scanner(System.in);
System.out.print("Enter TestCase ID (e.g. PO_TWO_INNERS, SO_WRONG_POLY_ORIENTATION_SINGLE) "
+ "or press Enter for default: ");
String id = sc.nextLine().trim();
// Default wählen
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt TestPaths per Reflection
resourcePath = (String) TestPaths.class.getField(id).get(null);
} catch (ReflectiveOperationException ex) {
System.out.println("Unknown testcase: " + id + " (using default)");
}
}
String filePathForParser = resolveResourceOnClasspath(resourcePath);
System.out.println("Using file: " + filePathForParser);
// --- 2) Load CityGML model ---
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
// TODO: Study class model
CityDoctorModel model = TestUtil.loadCityModel(filePathForParser, config);
System.out.println("Model loaded successfully.\n");
List<Building> buildings = model.getBuildings();
// --- 3) Menu: choose granularity ---
System.out.println("\nChoose granularity:");
System.out.println(" 1) Polygon-level (broad-phase)");
System.out.println(" 2) Ring-level (EXTERIOR)");
System.out.println(" 3) Ring-level (INTERIOR)");
System.out.println(" 4) Vertex-level (fine-phase)");
System.out.println(" 5) All of the above");
System.out.print("Your choice [1-5]: ");
int choice = readChoice(sc, 1, 5);
boolean doPolygon = (choice == 1 || choice == 5);
boolean doExtRing = (choice == 2 || choice == 5);
boolean doIntRing = (choice == 3 || choice == 5);
boolean doVertex = (choice == 4 || choice == 5);
// --- 4) Run the selected modes ---
if (doPolygon) runPolygonMode(buildings);
if (doExtRing) runRingMode(buildings, true);
if (doIntRing) runRingMode(buildings, false);
if (doVertex) runVertexMode(buildings);
System.out.println("\n=== Done ===");
}
// ---------- Modes ----------
private static void runPolygonMode(List<Building> buildings) {
System.out.println("\n> Polygon-level AABB (broad-phase)");
Set<ConcretePolygon> polys = collectAllPolygons(buildings);
AABBTree<ConcretePolygon> bvh = new AABBTree<>(
new ArrayList<>(polys),
AABBUtils::getAABB
);
System.out.println("Polygons: " + polys.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runRingMode(List<Building> buildings, boolean exterior) {
String label = exterior ? "EXTERIOR" : "INTERIOR";
System.out.println("\n> Ring-level AABB (" + label + ")");
Set<LinearRing> rings = exterior
? collectExteriorRings(buildings)
: collectInteriorRings(buildings);
AABBTree<LinearRing> bvh = new AABBTree<>(
new ArrayList<>(rings),
AABBUtils::computeAABBFromRing
);
System.out.println(label + " rings: " + rings.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runVertexMode(List<Building> buildings) {
System.out.println("\n> Vertex-level AABB (fine-phase)");
Set<Vertex> verts = collectAllVertices(buildings);
AABBTree<Vertex> bvh = new AABBTree<>(
new ArrayList<>(verts),
AABBUtils::computeAABBFromVertex
);
System.out.println("Vertices: " + verts.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
// ---------- Collectors ----------
/** Collects all ConcretePolygons (unique) via visitor. */
private static Set<ConcretePolygon> collectAllPolygons(List<Building> buildings) {
Set<ConcretePolygon> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
set.add(poly.getOriginal());
}
});
}
return set;
}
/** Collects all EXTERIOR rings (unique). */
private static Set<LinearRing> collectExteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
LinearRing ext = poly.getOriginal().getExteriorRing();
if (ext != null) set.add(ext);
}
});
}
return set;
}
/** Collects all INTERIOR rings (unique). */
private static Set<LinearRing> collectInteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
List<LinearRing> inner = poly.getOriginal().getInnerRings();
if (inner != null && !inner.isEmpty()) {
set.addAll(inner);
}
}
});
}
return set;
}
/** Collects all vertices (unique) from both exterior and interior rings. */
private static Set<Vertex> collectAllVertices(List<Building> buildings) {
Set<Vertex> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
ConcretePolygon cp = poly.getOriginal();
LinearRing ext = cp.getExteriorRing();
if (ext != null) set.addAll(ext.getVertices());
List<LinearRing> inner = cp.getInnerRings();
if (inner != null) {
for (LinearRing lr : inner) set.addAll(lr.getVertices());
}
}
});
}
return set;
}
// ---------- Utils ----------
/* Helper method reads choice of the user */
private static int readChoice(Scanner sc, int min, int max) {
while (true) {
String s = sc.nextLine().trim();
try {
int v = Integer.parseInt(s);
if (v >= min && v <= max) return v;
} catch (NumberFormatException ignored) {}
System.out.print("Please enter a number between " + min + " and " + max + ": ");
}
}
/* Helper method Pathfinder */
private static String resolveResourceOnClasspath(String resourcePath) {
String rp = normalizeResourcePath(resourcePath);
// 1) Klassenpfad via Class.getResource
URL url = TestCityGmlAABB.class.getResource(rp);
// 2) Klassenpfad via Context-ClassLoader:
if (url == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
url = cl.getResource(noSlash);
}
// 3) Wenn gefunden: in Pfad umwandeln
if (url != null) {
try {
return Paths.get(url.toURI()).toString();
} catch (Exception e) {
throw new RuntimeException("Konnte Resource-URI nicht auflösen: " + url, e);
}
}
// 4) Fallbacks: Projekt-relative Kandidaten prüfen
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
String[] candidates = {
"src/test/resources/" + noSlash,
"src/main/resources/" + noSlash,
resourcePath // falls bereits ein echter Dateipfad übergeben wurde
};
for (String cand : candidates) {
java.nio.file.Path p = java.nio.file.Paths.get(cand);
if (java.nio.file.Files.exists(p)) {
return p.toAbsolutePath().toString();
}
}
throw new IllegalArgumentException(
"Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// FÜR ALLE FÄLLE zur Zeit REDUNDAD
private static String normalizeResourcePath(String path) {
String p = path.replace('\\', '/');
// Quellordner-Präfixe entfernen, falls mit übergeben
if (p.startsWith("src/test/resources/")) {
p = p.substring("src/test/resources/".length());
} else if (p.startsWith("src/main/resources/")) {
p = p.substring("src/main/resources/".length());
}
// Für Class.getResource: führenden Slash setzen
if (!p.startsWith("/")) p = "/" + p;
return p;
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
/**
* Central collection of CityGML test file paths used for AABB / healing experiments.
* Files are located under {@code src/test/resources} or {@code src/test/resources/bht/}
* ACHTUNG: mit '!!!' markierte Objekte haben einen falschen Pfad
*/
public final class TestPaths {
private TestPaths() {
// utility class
}
// ==================================================
// Schematron / Validation tests
// ==================================================
/**!!! Not XSD valid, namespaces removed, for schematron experiments. */
public static final String SCHEMATRON =
"CityDoctorParent/CityDoctorValidation/target/test-classes/SimpleSolid_SrefBS-Schematron-Test.gml";
//CityDoctorParent
//"src/test/resources/bht/SimpleSolid_SrefBS-Schematron-Test.gml";
// ==================================================
// LinearRing (LR) test cases
// ==================================================
/**1 LinearRing has only 2 points. */
public static final String LR_2POINTS =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0001-T0001.gml";
/**2 LinearRing has 3 points but is not closed. */
public static final String LR_NOTCLOSED =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0001-T0002.gml";
/**3 LinearRing has consecutive duplicate vertices. */
public static final String LR_DUPLICATE_CONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0001.gml";
/**4 LinearRing has non-consecutive duplicate vertices. */
public static final String LR_DUPLICATE_NONCONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0002.gml";
/**added 5 LinearRing points form a straight line. */
public static final String LR_LINE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0003.gml";
/**added 6 LinearRing has consecutive points closer than 0.1. */
public static final String LR_TOO_CLOSE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0004.gml";
/**added 7 LinearRing has too few points and duplicate points. */
public static final String LR_TOO_FEW_DUP =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0005.gml";
/**8 LinearRing is not closed. */
public static final String LR_NOTCLOSED_2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0003-T0001.gml";
/**added 9 LinearRing is closed but with point distance < 0.1. */
public static final String LR_CLOSED_SMALLDIST =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0003-T0002.gml";
/**10 LinearRing has a loop. */
public static final String LR_LOOP =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml";
/** 11 LinearRing point touches edge with distance < 0.1. */
public static final String LR_TOUCH_EDGE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0005.gml";
// ==================================================
// Polygon (PO) test cases
// ==================================================
/**12 Inner ring intersects outer ring. */
public static final String PO_INTERSECT_OUTER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0001.gml";
/**13 Polygon has 2 inner rings. */
public static final String PO_TWO_INNERS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0002.gml";
/**14 Polygon has 2 intersecting inner rings. */
public static final String PO_INNER_INTERSECT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0003.gml";
/**15 Polygon has 2 inner rings; one vertex too far from the plane. */
public static final String PO_INNER_OFF_PLANE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0002-T0001.gml";
/**added 16 Polygon has normal deviation error. */
public static final String PO_NORMAL_ERROR =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0002-T0002.gml";
/**17 Polygon has disconnected interior surface. */
public static final String PO_DISCONNECTED_INTERIOR_1 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0001.gml";
/**added 18 Polygon has disconnected interior surface due to two inner rings. */
public static final String PO_DISCONNECTED_INTERIOR_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0002.gml";
/** added 19 Inner ring completely outside the outer ring. */
public static final String PO_INNER_OUTSIDE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0004-T0001.gml";
/**added 20 Inner ring completely inside another inner ring. */
public static final String PO_INNER_INSIDE_ANOTHER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0005-T0001.gml";
/**21 Two inner rings with wrong orientation. */
public static final String PO_TWO_INNERS_WRONG_ORIENT =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0006-T0001.gml";
// ==================================================
// Solid (SO) test cases
// ==================================================
/**added 22 Solid consists of only 3 polygons. */
public static final String SO_TOO_FEW_POLYGONS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0001-T0001.gml";
/**added 23 Shell is not watertight. */
public static final String SO_NOT_WATERTIGHT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0002-T0001.gml";
/** 24 Two buildings connected only by a single point. */
public static final String SO_CONNECTED_BY_POINT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
/**25 Roof point slightly deviates (e.g., 10.0 14.95 2.95 vs 10.0 15.0 3.0). */
public static final String SO_ROOF_COORD_DEVIATION_1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0004-T0001.gml";
/**25 Missing polygon. */
public static final String SO_MISSING_POLYGON =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0002.gml";
/*
*
* komplex Data from NRW; whole Quartier
* */
/** 26added EXTRA Whole Church forum. */
public static final String CHURCH_FORUM =
"src/test/resources/bht/LoD2_32_344_5675_1_NW.gml";
/* LOST DATA
/** Slight coordinate deviations on multiple roof polygons (CityGML 2.0 only).
public static final String SO_ROOF_COORD_DEVIATION_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0003.gml";
*/
/**added 27 Solid geometry actually contains two buildings. */
public static final String SO_TWO_BUILDINGS_IN_ONE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0005-T0001.gml";
//
// Falsches Datei !!!!
/**!!! 28 Self-intersecting geometry. */
public static final String SO_SELF_INTERSECTING =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
// ACHTUNG FALSCHES LABEL
/**29 Polygon with wrong orientation. */
public static final String ENTER_LABEL1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0001.gml";
/**30 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0002.gml";
/**31 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL3 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0003.gml";
//
/** LOST! 33 All polygons have wrong orientation. */
public static final String SO_WRONG_POLY_ORIENTATION_ALL =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0008-T0001.gml";
}
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.checks.bht;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import Jama.EigenvalueDecomposition;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
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.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError;
import de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
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.Vertex;
import de.hft.stuttgart.citydoctor2.math.CovarianceMatrix;
import de.hft.stuttgart.citydoctor2.math.DistanceResult;
import de.hft.stuttgart.citydoctor2.math.Matrix3x3d;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
/**
* Checks whether a ring self intersects. Also checks if a point is too close to
* an edge of the ring.
*
* Axis Aligned Boundin Box Early-Out-Version of Matthias Betz's Check
* @author Baris Numanoglu
*/
public class RingSelfIntCheckAABB extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
// check requirement class for default parameters
private double degeneratedRingTolerance = 0.01;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private static final List<CheckId> dependencies;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
deps.add(CheckId.C_GE_R_NOT_CLOSED);
deps.add(CheckId.C_GE_R_DUPLICATE_POINT);
dependencies = Collections.unmodifiableList(deps);
}
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
String epsilonString = parameters.get(EPSILON_NAME);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
if (parameters.containsKey(Requirement.DEGENERATED_RING_TOLERANCE)) {
degeneratedRingTolerance = Double.parseDouble(parameters.get(Requirement.DEGENERATED_RING_TOLERANCE));
}
}
@Override
public void check(LinearRing lr) {
checkRingJava(lr);
}
private void checkRingJava(LinearRing lr) {
// keep original behavior (tiny/degenerate detection first)
List<Vertex> vertices = lr.getVertices();
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
if (checkEigenvalues(lr, vertices, ed)) {
return; // error added, stop here
}
List<Edge> edges = getEdgesForRing(lr);
// --- NEW: Broad-phase AABB for "point touches edge" test ---
// Precompute padded AABBs for all edges (padding = epsilon)
List<EdgeBox> edgeBoxes = new ArrayList<>(edges.size());
for (Edge e : edges) {
edgeBoxes.add(new EdgeBox(e, edgeAabb(e.getFrom(), e.getTo(), epsilon)));
}
// Optimized "point touches edge": first filter by padded edge AABB
for (EdgeBox eb : edgeBoxes) {
if (checkForPointsTouchingEdgeBroadPhase(lr, eb)) {
return;
}
}
// --- Original pairwise edge test, now with AABB early-out ---
for (int i = 0; i < edges.size() - 1; i++) {
Edge e1 = edges.get(i);
EdgeBox eb1 = edgeBoxes.get(i);
for (int j = i + 1; j < edges.size(); j++) {
Edge e2 = edges.get(j);
// share a vertex? skip
if (e1.getConnectionPoint(e2) != null) {
continue;
}
//skip if AABBs don't overlap
EdgeBox eb2 = edgeBoxes.get(j);
if (!overlaps(eb1.box, eb2.box)) {
continue;
}
// Narrow-phase distance test (unchanged)
Segment3d s1 = new Segment3d(e1.getFrom(), e1.getTo());
Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo());
DistanceResult dr = s1.getDistanceResult(s2);
if (dr.distance() < epsilon) {
CheckError err = new RingEdgeIntersectionError(lr, e1, e2, dr.point1());
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
}
}
// no errors detected
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr);
}
private boolean checkEigenvalues(LinearRing lr, List<Vertex> points, EigenvalueDecomposition ed) {
Matrix3x3d mat = new Matrix3x3d(ed.getV().getArray());
List<Vector3d> rotatedVertices = new ArrayList<>();
for (Vertex v : points) {
rotatedVertices.add(mat.mult(v));
}
BoundingBox bbox = BoundingBox.ofPoints(rotatedVertices);
int nrOfEigenvaluesBelowTolerance = 0;
if (bbox.getWidth() < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++;
}
if (bbox.getHeight() < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++;
}
if (bbox.getDepth() < degeneratedRingTolerance) {
nrOfEigenvaluesBelowTolerance++;
}
if (nrOfEigenvaluesBelowTolerance >= 2) {
CheckError err = new DegeneratedRingError(lr);
lr.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
return true;
}
return false;
}
/**
* Original narrow-phase "point near edge" with a broad-phase AABB filter.
*/
private boolean checkForPointsTouchingEdgeBroadPhase(LinearRing lr, EdgeBox eb) {
Segment3d seg = new Segment3d(eb.edge.getFrom(), eb.edge.getTo());
for (Vertex v : lr.getVertices()) {
// skip endpoints
if (v == eb.edge.getFrom() || v == eb.edge.getTo()) {
continue;
}
// Broad-phase: reject if vertex is outside padded edge AABB
if (!eb.box.encloses(v.getX(), v.getY(), v.getZ())) {
continue;
}
// Narrow-phase: exact segment-point distance
if (seg.getDistance(v) < epsilon) {
CheckError err = new PointTouchesEdgeError(lr, eb.edge, v);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return true;
}
}
return false;
}
private List<Edge> getEdgesForRing(LinearRing lr) {
List<Edge> edges = new ArrayList<>();
Geometry geom = lr.getParent().getParent();
for (int i = 0; i < lr.getVertices().size() - 1; i++) {
Vertex v1 = lr.getVertices().get(i);
Vertex v2 = lr.getVertices().get(i + 1);
Edge e = geom.getEdge(v1, v2);
if (e == null) {
throw new IllegalStateException("Edge between v1=" + v1 + " to v2=" + v2 + " is missing");
}
edges.add(e);
}
return edges;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public Set<Requirement> appliesToRequirements() {
return CollectionUtils.singletonSet(Requirement.R_GE_R_SELF_INTERSECTION);
}
@Override
public RequirementType getType() {
return RequirementType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new RingSelfIntCheckAABB();
}
@Override
public CheckId getCheckId() {
return CheckId.C_GE_R_SELF_INTERSECTION;
}
// TODO AUSLAGERN!!!
/* ======== AABB helpers (broad-phase) ======== */
private static final class EdgeBox {
final Edge edge;
final AABB box;
EdgeBox(Edge edge, AABB box) {
this.edge = edge;
this.box = box;
}
}
/** Builds a padded AABB around a segment (two vertices). */
private static AABB edgeAabb(Vertex a, Vertex b, double pad) {
double minX = Math.min(a.getX(), b.getX()) - pad;
double minY = Math.min(a.getY(), b.getY()) - pad;
double minZ = Math.min(a.getZ(), b.getZ()) - pad;
double maxX = Math.max(a.getX(), b.getX()) + pad;
double maxY = Math.max(a.getY(), b.getY()) + pad;
double maxZ = Math.max(a.getZ(), b.getZ()) + pad;
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
/** Returns true if two AABBs overlap (inclusive). */
private static boolean overlaps(AABB a, AABB b) {
return !(a.getMaxX() < b.getMinX() ||
a.getMinX() > b.getMaxX() ||
a.getMaxY() < b.getMinY() ||
a.getMinY() > b.getMaxY() ||
a.getMaxZ() < b.getMinZ() ||
a.getMinZ() > b.getMaxZ());
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
public class BVHStructures {
/**
* Generic node used in BVH tree
*/
public static class Node<E> {
public List<Node<E>> children = new ArrayList<>();
public AABB aabb;
public E element;
public Node() {
// Anchor-3DPoint given by means of its coords
double zeroX = 0;
double zeroY = 0;
double zeroZ = 0;
this.aabb = new AABB(zeroX, zeroY, zeroZ, zeroX, zeroY, zeroZ);
this.element = null;
}
public boolean isLeaf() {
return children.isEmpty() && element != null;
}
}
/**
* Represents a pair of geometry lists resulting from a split
*/
public static class SplitStruct<E> {
public List<E> list1 = new ArrayList<>();
public List<E> list2 = new ArrayList<>();
}
/**
* Stack element used during recursive BVH tree construction
*/
public static class StackElement<E> {
public int level;
public Node<E> node;
public List<E> elements = new ArrayList<>();
public StackElement(int level, Node<E> node, List<E> elements) {
this.level = level;
this.node = node;
this.elements = elements;
}
}
/**
* Pair of AABBs and polygon lists used during splitting
*/
public static class SplitStructB<E> {
public AABB box1, box2;
public List<E> list1 = new ArrayList<>();
public List<E> list2 = new ArrayList<>();
}
/**
* Generic array-based BVH node.
* Stores an AABB and an associated element (typically a leaf object).
*/
public static class NodeB_Ar<E> {
public AABB aabb;
public E element;
public NodeB_Ar(AABB aabb, E element) {
this.aabb = aabb;
this.element = element;
}
}
/**
* Stack element used for array-based BVH construction
*/
public static class StackElementAr {
public int arrayIndex;
public int level;
public AABB aabb;
public List<ConcretePolygon> polygons = new ArrayList<>();
public StackElementAr(int arrayIndex, int level, AABB aabb, List<ConcretePolygon> polygons) {
this.arrayIndex = arrayIndex;
this.level = level;
this.aabb = aabb;
this.polygons = polygons;
}
}
/**
* Creates a comparator to sort elements by their AABB center X coordinate.
*/
public static <E> Comparator<E> compareCenterX(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMinX() + box.getMaxX()) / 2.0;
});
}
/**
* Creates a comparator to sort elements by their AABB center Y coordinate.
*/
public static <E> Comparator<E> compareCenterY(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMinY() + box.getMaxY()) / 2.0;
});
}
/**
* Creates a comparator to sort elements by their AABB center Z coordinate.
*/
public static <E> Comparator<E> compareCenterZ(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMinZ() + box.getMaxZ()) / 2.0;
});
}
/**
* Sorts elements by AABB center along specified axis.
*
* @param elements the list to sort
* @param axis 0 = X, 1 = Y, 2 = Z
* @param aabbGetter a function to extract the AABB from an element
*/
public static <E> void sortElementsByCenterAxis(List<E> elements, int axis, Function<E, AABB> aabbGetter) {
switch (axis) {
case 0 -> elements.sort(compareCenterX(aabbGetter));
case 1 -> elements.sort(compareCenterY(aabbGetter));
case 2 -> elements.sort(compareCenterZ(aabbGetter));
default -> throw new IllegalArgumentException("Invalid axis index: " + axis);
}
}
/**
* Returns the index of the longest axis in the AABB
*/
public static int findLongestAxis(AABB aabb) {
double x = aabb.getMaxX() - aabb.getMinX();
double y = aabb.getMaxY() - aabb.getMinY();
double z = aabb.getMaxZ() - aabb.getMinZ();
if (x > y && x > z) return 0;
if (y > x && y > z) return 1;
return 2;
}
public static <E> AABB getAggregateAABB(List<E> elements, Function<E, AABB> aabbFunc) {
double minX = Double.POSITIVE_INFINITY, minY = Double.POSITIVE_INFINITY, minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY;
for (E e : elements) {
AABB aabb = aabbFunc.apply(e);
minX = Math.min(minX, aabb.getMinX());
minY = Math.min(minY, aabb.getMinY());
minZ = Math.min(minZ, aabb.getMinZ());
maxX = Math.max(maxX, aabb.getMaxX());
maxY = Math.max(maxY, aabb.getMaxY());
maxZ = Math.max(maxZ, aabb.getMaxZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.17.2</version>
</parent>
<properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString>
</properties>
<artifactId>CityDoctorModel</artifactId>
<name>CityDoctorModel</name>
<dependencies>
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-xml</artifactId>
</dependency>
<dependency>
<groupId>de.hft.stuttgart</groupId>
<artifactId>citygml4j-quality-ade</artifactId>
</dependency>
<dependency>
<groupId>gov.nist.math</groupId>
<artifactId>jama</artifactId>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt-main</artifactId>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all-main</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.proj4j</groupId>
<artifactId>proj4j</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.proj4j</groupId>
<artifactId>proj4j-epsg</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
<dependency>
<groupId>io.github.earcut4j</groupId>
<artifactId>earcut4j</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.9.10</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<dotGitDirectory>${project.basedir}/../../.git</dotGitDirectory>
<prefix>git</prefix>
<verbose>false</verbose>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>
${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
<gitDescribe>
<skip>false</skip>
<always>false</always>
</gitDescribe>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package de.hft.stuttgart.citydoctor2.healing.bht;
import de.hft.stuttgart.citydoctor2.datastructure.bht.*;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.parser.*;
import de.hft.stuttgart.citydoctor2.systemtest.TestUtil;
import de.hft.stuttgart.citydoctor2.healing.*;
import de.hft.stuttgart.citydoctor2.check.*;
//import de.hft.stuttgart.citydoctor2.healing.*;
import java.io.IOException;
import java.util.*;
//src/test/java/.../TestCityGmlAABB.java
import java.net.URL;
import java.nio.file.Paths;
public class TestCityGmlAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("=== CityGML AABB Test ===");
// --- 1) Ask user which test case to run ---
Scanner sc = new Scanner(System.in);
System.out.print("Enter TestCase ID (e.g. PO_TWO_INNERS, SO_WRONG_POLY_ORIENTATION_SINGLE) "
+ "or press Enter for default: ");
String id = sc.nextLine().trim();
// Default wählen
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt TestPaths per Reflection
resourcePath = (String) TestPaths.class.getField(id).get(null);
} catch (ReflectiveOperationException ex) {
System.out.println("Unknown testcase: " + id + " (using default)");
}
}
String filePathForParser = resolveResourceOnClasspath(resourcePath);
System.out.println("Using file: " + filePathForParser);
// --- 2) Load CityGML model ---
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
// TODO: Study class model
CityDoctorModel model = de.hft.stuttgart.citydoctor2.healing.TestUtil.loadCityModel(filePathForParser, config);
System.out.println("Model loaded successfully.\n");
List<Building> buildings = model.getBuildings();
// --- 3) Menu: choose granularity ---
System.out.println("\nChoose granularity:");
System.out.println(" 1) Polygon-level (broad-phase)");
System.out.println(" 2) Ring-level (EXTERIOR)");
System.out.println(" 3) Ring-level (INTERIOR)");
System.out.println(" 4) Vertex-level (fine-phase)");
System.out.println(" 5) All of the above");
System.out.print("Your choice [1-5]: ");
int choice = readChoice(sc, 1, 5);
boolean doPolygon = (choice == 1 || choice == 5);
boolean doExtRing = (choice == 2 || choice == 5);
boolean doIntRing = (choice == 3 || choice == 5);
boolean doVertex = (choice == 4 || choice == 5);
// --- 4) Run the selected modes ---
if (doPolygon) runPolygonMode(buildings);
if (doExtRing) runRingMode(buildings, true);
if (doIntRing) runRingMode(buildings, false);
if (doVertex) runVertexMode(buildings);
System.out.println("\n=== Done ===");
}
// ---------- Modes ----------
private static void runPolygonMode(List<Building> buildings) {
System.out.println("\n> Polygon-level AABB (broad-phase)");
Set<ConcretePolygon> polys = collectAllPolygons(buildings);
AABBTree<ConcretePolygon> bvh = new AABBTree<>(
new ArrayList<>(polys),
AABBUtils::getAABB
);
System.out.println("Polygons: " + polys.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runRingMode(List<Building> buildings, boolean exterior) {
String label = exterior ? "EXTERIOR" : "INTERIOR";
System.out.println("\n> Ring-level AABB (" + label + ")");
Set<LinearRing> rings = exterior
? collectExteriorRings(buildings)
: collectInteriorRings(buildings);
AABBTree<LinearRing> bvh = new AABBTree<>(
new ArrayList<>(rings),
AABBUtils::computeAABBFromRing
);
System.out.println(label + " rings: " + rings.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runVertexMode(List<Building> buildings) {
System.out.println("\n> Vertex-level AABB (fine-phase)");
Set<Vertex> verts = collectAllVertices(buildings);
AABBTree<Vertex> bvh = new AABBTree<>(
new ArrayList<>(verts),
AABBUtils::computeAABBFromVertex
);
System.out.println("Vertices: " + verts.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
// ---------- Collectors ----------
/** Collects all ConcretePolygons (unique) via visitor. */
private static Set<ConcretePolygon> collectAllPolygons(List<Building> buildings) {
Set<ConcretePolygon> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
set.add(poly.getOriginal());
}
});
}
return set;
}
/** Collects all EXTERIOR rings (unique). */
private static Set<LinearRing> collectExteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
LinearRing ext = poly.getOriginal().getExteriorRing();
if (ext != null) set.add(ext);
}
});
}
return set;
}
/** Collects all INTERIOR rings (unique). */
private static Set<LinearRing> collectInteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
List<LinearRing> inner = poly.getOriginal().getInnerRings();
if (inner != null && !inner.isEmpty()) {
set.addAll(inner);
}
}
});
}
return set;
}
/** Collects all vertices (unique) from both exterior and interior rings. */
private static Set<Vertex> collectAllVertices(List<Building> buildings) {
Set<Vertex> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
ConcretePolygon cp = poly.getOriginal();
LinearRing ext = cp.getExteriorRing();
if (ext != null) set.addAll(ext.getVertices());
List<LinearRing> inner = cp.getInnerRings();
if (inner != null) {
for (LinearRing lr : inner) set.addAll(lr.getVertices());
}
}
});
}
return set;
}
// ---------- Utils ----------
/* Helper method reads choice of the user */
private static int readChoice(Scanner sc, int min, int max) {
while (true) {
String s = sc.nextLine().trim();
try {
int v = Integer.parseInt(s);
if (v >= min && v <= max) return v;
} catch (NumberFormatException ignored) {}
System.out.print("Please enter a number between " + min + " and " + max + ": ");
}
}
/* Helper method Pathfinder */
private static String resolveResourceOnClasspath(String resourcePath) {
String rp = normalizeResourcePath(resourcePath);
// 1) Klassenpfad via Class.getResource
URL url = TestCityGmlAABB.class.getResource(rp);
// 2) Klassenpfad via Context-ClassLoader:
if (url == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
url = cl.getResource(noSlash);
}
// 3) Wenn gefunden: in Pfad umwandeln
if (url != null) {
try {
return Paths.get(url.toURI()).toString();
} catch (Exception e) {
throw new RuntimeException("Konnte Resource-URI nicht auflösen: " + url, e);
}
}
// 4) Fallbacks: Projekt-relative Kandidaten prüfen
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
String[] candidates = {
"src/test/resources/" + noSlash,
"src/main/resources/" + noSlash,
resourcePath // falls bereits ein echter Dateipfad übergeben wurde
};
for (String cand : candidates) {
java.nio.file.Path p = java.nio.file.Paths.get(cand);
if (java.nio.file.Files.exists(p)) {
return p.toAbsolutePath().toString();
}
}
throw new IllegalArgumentException(
"Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// FÜR ALLE FÄLLE zur Zeit REDUNDAD
private static String normalizeResourcePath(String path) {
String p = path.replace('\\', '/');
// Quellordner-Präfixe entfernen, falls mit übergeben
if (p.startsWith("src/test/resources/")) {
p = p.substring("src/test/resources/".length());
} else if (p.startsWith("src/main/resources/")) {
p = p.substring("src/main/resources/".length());
}
// Für Class.getResource: führenden Slash setzen
if (!p.startsWith("/")) p = "/" + p;
return p;
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
/**
* Central collection of CityGML test file paths used for AABB / healing experiments.
* Files are located under {@code src/test/resources} or {@code src/test/resources/bht/}
* ACHTUNG: mit '!!!' markierte Objekte haben einen falschen Pfad
*/
public final class TestPaths {
private TestPaths() {
// utility class
}
// ==================================================
// Schematron / Validation tests
// ==================================================
/**!!! Not XSD valid, namespaces removed, for schematron experiments. */
public static final String SCHEMATRON =
"CityDoctorParent/CityDoctorValidation/target/test-classes/SimpleSolid_SrefBS-Schematron-Test.gml";
//CityDoctorParent
//"src/test/resources/bht/SimpleSolid_SrefBS-Schematron-Test.gml";
// ==================================================
// LinearRing (LR) test cases
// ==================================================
/**1 LinearRing has only 2 points. */
public static final String LR_2POINTS =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0001-T0001.gml";
/**2 LinearRing has 3 points but is not closed. */
public static final String LR_NOTCLOSED =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0001-T0002.gml";
/**3 LinearRing has consecutive duplicate vertices. */
public static final String LR_DUPLICATE_CONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0001.gml";
/**4 LinearRing has non-consecutive duplicate vertices. */
public static final String LR_DUPLICATE_NONCONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0002.gml";
/**added 5 LinearRing points form a straight line. */
public static final String LR_LINE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0003.gml";
/**added 6 LinearRing has consecutive points closer than 0.1. */
public static final String LR_TOO_CLOSE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0004.gml";
/**added 7 LinearRing has too few points and duplicate points. */
public static final String LR_TOO_FEW_DUP =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0005.gml";
/**8 LinearRing is not closed. */
public static final String LR_NOTCLOSED_2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0003-T0001.gml";
/**added 9 LinearRing is closed but with point distance < 0.1. */
public static final String LR_CLOSED_SMALLDIST =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0003-T0002.gml";
/**10 LinearRing has a loop. */
public static final String LR_LOOP =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml";
/** 11 LinearRing point touches edge with distance < 0.1. */
public static final String LR_TOUCH_EDGE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0005.gml";
// ==================================================
// Polygon (PO) test cases
// ==================================================
/**12 Inner ring intersects outer ring. */
public static final String PO_INTERSECT_OUTER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0001.gml";
/**13 Polygon has 2 inner rings. */
public static final String PO_TWO_INNERS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0002.gml";
/**14 Polygon has 2 intersecting inner rings. */
public static final String PO_INNER_INTERSECT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0003.gml";
/**15 Polygon has 2 inner rings; one vertex too far from the plane. */
public static final String PO_INNER_OFF_PLANE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0002-T0001.gml";
/**added 16 Polygon has normal deviation error. */
public static final String PO_NORMAL_ERROR =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0002-T0002.gml";
/**17 Polygon has disconnected interior surface. */
public static final String PO_DISCONNECTED_INTERIOR_1 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0001.gml";
/**added 18 Polygon has disconnected interior surface due to two inner rings. */
public static final String PO_DISCONNECTED_INTERIOR_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0002.gml";
/** added 19 Inner ring completely outside the outer ring. */
public static final String PO_INNER_OUTSIDE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0004-T0001.gml";
/**added 20 Inner ring completely inside another inner ring. */
public static final String PO_INNER_INSIDE_ANOTHER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0005-T0001.gml";
/**21 Two inner rings with wrong orientation. */
public static final String PO_TWO_INNERS_WRONG_ORIENT =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0006-T0001.gml";
// ==================================================
// Solid (SO) test cases
// ==================================================
/**added 22 Solid consists of only 3 polygons. */
public static final String SO_TOO_FEW_POLYGONS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0001-T0001.gml";
/**added 23 Shell is not watertight. */
public static final String SO_NOT_WATERTIGHT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0002-T0001.gml";
/** 24 Two buildings connected only by a single point. */
public static final String SO_CONNECTED_BY_POINT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
/**25 Roof point slightly deviates (e.g., 10.0 14.95 2.95 vs 10.0 15.0 3.0). */
public static final String SO_ROOF_COORD_DEVIATION_1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0004-T0001.gml";
/**25 Missing polygon. */
public static final String SO_MISSING_POLYGON =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0002.gml";
/*
*
* komplex Data from NRW; whole Quartier
* */
/** 26added EXTRA Whole Church forum. */
public static final String CHURCH_FORUM =
"src/test/resources/bht/LoD2_32_344_5675_1_NW.gml";
/* LOST DATA
/** Slight coordinate deviations on multiple roof polygons (CityGML 2.0 only).
public static final String SO_ROOF_COORD_DEVIATION_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0003.gml";
*/
/**added 27 Solid geometry actually contains two buildings. */
public static final String SO_TWO_BUILDINGS_IN_ONE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0005-T0001.gml";
//
// Falsches Datei !!!!
/**!!! 28 Self-intersecting geometry. */
public static final String SO_SELF_INTERSECTING =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
// ACHTUNG FALSCHES LABEL
/**29 Polygon with wrong orientation. */
public static final String ENTER_LABEL1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0001.gml";
/**30 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0002.gml";
/**31 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL3 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0003.gml";
//
/** LOST! 33 All polygons have wrong orientation. */
public static final String SO_WRONG_POLY_ORIENTATION_ALL =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0008-T0001.gml";
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.util.*;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class Main {
/**
* Entry point for AABB testing
*/
public static void main(String[] args) {
System.out.println("=== AABB TEST ===");
ConcretePolygon polygon = new ConcretePolygon();
LinearRing exterior = new LinearRing(LinearRingType.EXTERIOR);
exterior.addVertex(new Vertex(0, 0, 0));
exterior.addVertex(new Vertex(1, 0, 0));
exterior.addVertex(new Vertex(0, 1, 0));
exterior.addVertex(new Vertex(0, 0, 0));
polygon.setExteriorRing(exterior);
// Define points of a tetrahedron
Point p1 = new Point(0, 0, 0);
Point p2 = new Point(1, 0, 0);
Point p3 = new Point(0.5, 1, 0);
Point p4 = new Point(0.5, 0.5, 1);
// Define triangle faces of the tetrahedron
ConcretePolygon base = new ConcretePolygon();
LinearRing baseRing = new LinearRing(LinearRingType.EXTERIOR);
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
baseRing.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
baseRing.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
base.setExteriorRing(baseRing);
ConcretePolygon side1 = new ConcretePolygon();
LinearRing ring1 = new LinearRing(LinearRingType.EXTERIOR);
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring1.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring1.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
side1.setExteriorRing(ring1);
ConcretePolygon side2 = new ConcretePolygon();
LinearRing ring2 = new LinearRing(LinearRingType.EXTERIOR);
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring2.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring2.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
side2.setExteriorRing(ring2);
ConcretePolygon side3 = new ConcretePolygon();
LinearRing ring3 = new LinearRing(LinearRingType.EXTERIOR);
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring3.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring3.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
side3.setExteriorRing(ring3);
List<ConcretePolygon> poly = Arrays.asList(base, side1, side2, side3);
AABB aabb = BVHStructures.getAggregateAABB(poly);
System.out.println("\nComputed AABB:");
aabb.print();
/*** 3D-Point-coords with Test Properties ***/
// Inside laying Point
double testInsideX = 0.5;
double testInsideY = 0.5;
double testInsideZ = 0.3;
// Outside laying Point
double testOutsideX = 2.0;
double testOutsideY = 2.0;
double testOutsideZ = 2.0;
// On edge laying Point
double testOnEdgeX = 1.0;
double testOnEdgeY = 1.0;
double testOnEdgeZ = 0.0;
System.out.println("\nTest point (inside): " + testInsideX + " " + testInsideY + " " + testInsideZ);
System.out.println("-> contained?: " + aabb.encloses(testInsideX, testInsideY, testInsideZ));
System.out.println("\nTest point (outside): " + testOutsideX + " " + testOutsideY + " " + testOutsideZ);
System.out.println("-> contained?: " + aabb.encloses(testOutsideX, testOutsideY, testOutsideZ));
System.out.println("\nTest point (on edge): " + testOnEdgeX + " " + testOnEdgeY + " " + testOnEdgeZ);
System.out.println("-> contained?: " + aabb.encloses(testOnEdgeX, testOnEdgeY, testOnEdgeZ));
}
}
package de.hft.stuttgart.citydoctor2.checks.aabb;
import java.util.*;
import de.hft.stuttgart.citydoctor2.checks.aabb.AABB;
import de.hft.stuttgart.citydoctor2.checks.aabb.BVHStructures;
import de.hft.stuttgart.citydoctor2.checks.aabb.Point;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class TestDummyAABB {
/**
* Entry point for AABB testing
*/
public static void main(String[] args) {
System.out.println("=== AABB TEST ===");
ConcretePolygon polygon = new ConcretePolygon();
LinearRing exterior = new LinearRing(LinearRingType.EXTERIOR);
exterior.addVertex(new Vertex(0, 0, 0));
exterior.addVertex(new Vertex(1, 0, 0));
exterior.addVertex(new Vertex(0, 1, 0));
exterior.addVertex(new Vertex(0, 0, 0));
polygon.setExteriorRing(exterior);
// Define points of a tetrahedron
Point p1 = new Point(0, 0, 0);
Point p2 = new Point(1, 0, 0);
Point p3 = new Point(0.5, 1, 0);
Point p4 = new Point(0.5, 0.5, 1);
// Define triangle faces of the tetrahedron
ConcretePolygon base = new ConcretePolygon();
LinearRing baseRing = new LinearRing(LinearRingType.EXTERIOR);
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
baseRing.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
baseRing.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
base.setExteriorRing(baseRing);
ConcretePolygon side1 = new ConcretePolygon();
LinearRing ring1 = new LinearRing(LinearRingType.EXTERIOR);
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring1.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring1.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
side1.setExteriorRing(ring1);
ConcretePolygon side2 = new ConcretePolygon();
LinearRing ring2 = new LinearRing(LinearRingType.EXTERIOR);
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring2.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring2.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
side2.setExteriorRing(ring2);
ConcretePolygon side3 = new ConcretePolygon();
LinearRing ring3 = new LinearRing(LinearRingType.EXTERIOR);
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring3.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring3.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
side3.setExteriorRing(ring3);
List<ConcretePolygon> poly = Arrays.asList(base, side1, side2, side3);
AABB aabb = BVHStructures.getAggregateAABB(poly);
System.out.println("\nComputed AABB:");
aabb.print();
/*** 3D-Point-coords with Test Properties ***/
// Inside laying Point
double testInsideX = 0.5;
double testInsideY = 0.5;
double testInsideZ = 0.3;
// Outside laying Point
double testOutsideX = 2.0;
double testOutsideY = 2.0;
double testOutsideZ = 2.0;
// On edge laying Point
double testOnEdgeX = 1.0;
double testOnEdgeY = 1.0;
double testOnEdgeZ = 0.0;
System.out.println("\nTest point (inside): " + testInsideX + " " + testInsideY + " " + testInsideZ);
System.out.println("-> contained?: " + aabb.encloses(testInsideX, testInsideY, testInsideZ));
System.out.println("\nTest point (outside): " + testOutsideX + " " + testOutsideY + " " + testOutsideZ);
System.out.println("-> contained?: " + aabb.encloses(testOutsideX, testOutsideY, testOutsideZ));
System.out.println("\nTest point (on edge): " + testOnEdgeX + " " + testOnEdgeY + " " + testOnEdgeZ);
System.out.println("-> contained?: " + aabb.encloses(testOnEdgeX, testOnEdgeY, testOnEdgeZ));
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import java.util.*;
//import java.util.List;
/**
* @author Baris Numanoglu
*
* Utility class providing AABB-related helper methods.
*
* Contains functionality for computing axis-aligned bounding boxes (AABBs)
* from geometric data structures such as {@link ConcretePolygon} or {@link LinearRing}.
*
* All methods are static.
*/
public class AABBUtils {
public static AABB getAABB(ConcretePolygon poly) {
LinearRing ring = poly.getExteriorRing();
if (ring == null) return null;
return computeAABBFromRing(ring);
}
/**
* Computes an AABB from a LinearRing by iterating over its vertices.
*
* @param ring the LinearRing whose vertices define the AABB
* @return AABB enclosing the ring; returns an empty AABB if the ring has no vertices
*/
public static AABB computeAABBFromRing(LinearRing ring) {
if (ring == null || ring.getVertices() == null || ring.getVertices().isEmpty()) {
return new AABB(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
}
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double maxZ = Double.NEGATIVE_INFINITY;
for (Vertex v : ring.getVertices()) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
minX = Math.min(minX, x);
minY = Math.min(minY, y);
minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
maxZ = Math.max(maxZ, z);
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
/**
* PREMORDIAL: substitutes the earlier semantic of AABB-Point-Init.
* Computes the min and max bounds (6 doubles) from two points.
*
* @param p1 the first point
* @param p2 the second point
* @return an array of 6 doubles: [minX, minY, minZ, maxX, maxY, maxZ]
*/
public static double[] extractBoundsFromPoints(Point p1, Point p2) {
double minX = Math.min(p1.getX(), p2.getX());
double minY = Math.min(p1.getY(), p2.getY());
double minZ = Math.min(p1.getZ(), p2.getZ());
double maxX = Math.max(p1.getX(), p2.getX());
double maxY = Math.max(p1.getY(), p2.getY());
double maxZ = Math.max(p1.getZ(), p2.getZ());
return new double[] { minX, minY, minZ, maxX, maxY, maxZ };
}
////////////////////////////////////////////////////////////////////////////////////////////
/**
* Computes an AABB for a single vertex (degenerate AABB with min=max=vertex coordinates).
*
* @param vertex the vertex to wrap in an AABB
* @return AABB centered at the vertex position
*/
public static AABB computeAABBFromVertex(Vertex vertex) {
if (vertex == null) {
// Return a degenerate AABB with infinite bounds if vertex is null
return new AABB(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
}
double x = vertex.getX();
double y = vertex.getY();
double z = vertex.getZ();
// AABB is degenerate here: min=max=(x,y,z)
return new AABB(x, y, z, x, y, z);
}
/** Returns true if the AABB is (nearly) flat in at least two axes. */
public static boolean isDegenerate(AABB aabb, double tol) {
double dx = aabb.getMaxX() - aabb.getMinX();
double dy = aabb.getMaxY() - aabb.getMinY();
double dz = aabb.getMaxZ() - aabb.getMinZ();
int flatAxes = 0;
if (dx <= tol) flatAxes++;
if (dy <= tol) flatAxes++;
if (dz <= tol) flatAxes++;
return flatAxes >= 2; // cannot host 3 distinct points in 3D
}
/**
* Counts distinct vertices of a ring using a tolerance.
* Implementation: uniform quantization (grid hashing) for speed and determinism.
*/
public static int countDistinctVertices(LinearRing ring, double tol) {
if (ring == null || ring.getVertices() == null) return 0;
// Use a quantization resolution based on tol (avoid div by zero).
final double q = (tol > 0) ? tol : 1e-9;
// Hash set of quantized integer triplets "ix|iy|iz"
Set<Long> buckets = new HashSet<>(ring.getVertices().size() * 2);
for (Vertex v : ring.getVertices()) {
long ix = Math.round(v.getX() / q);
long iy = Math.round(v.getY() / q);
long iz = Math.round(v.getZ() / q);
// pack into a 64-bit key (simple mixing; safe if ranges are reasonable)
long key = mix3(ix, iy, iz);
buckets.add(key);
}
return buckets.size();
}
// TODO replace with older hash functions from project
// Simple 3D integer mix to a 64-bit key (Xorshift-ish) from JavaDoc
private static long mix3(long x, long y, long z) {
long h = x * 73856093L ^ y * 19349663L ^ z * 83492791L;
// final avalanche
h ^= (h >>> 33);
h *= 0xff51afd7ed558ccdL;
h ^= (h >>> 33);
h *= 0xc4ceb9fe1a85ec53L;
h ^= (h >>> 33);
return h;
}
/* COMING SOON */
}
package de.hft.stuttgart.citydoctor2.checks.bht;
import java.io.IOException;
//import java.io.InputStream; //new
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Scanner; //new
import org.junit.Test;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
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.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.healing.TestUtil;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class TestExample {
// InputStream inStream = getClass().getClassLoader().getResourceAsStream("SimpleSolid_SrefBS.gml"); //new
// //if you want to load a src/test/resources file out of a different package
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("Start TestExample... \n"); //new
// String path = args[0];
// String path = "src/test/resources/SimpleSolid_SrefBS.gml"; //new valid LoD2 model
// String path = "src/test/resources/TestBuilding1.gml"; //new block model
//new valid model with inner rings
String path = "src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0002.gml";
// too big, main gets terminated
// String path = "src/test/resources/bht/KreishausFeuerwache.gml";
// String path = "src/test/resources/bht/TestBuilding_SelfInt.gml";
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
CityDoctorModel model = TestUtil.loadCityModel(path, config);
System.out.println("\nModel has been loaded..."); //new
// visitor
// filter duplicate polygons out
Set<ConcretePolygon> allBuildingPolygons = new HashSet<>();
for (Building b : model.getBuildings()) {
b.accept(new CheckableUtilsVisitor() {
public void check(Polygon poly) {
allBuildingPolygons.add(poly.getOriginal());
}
});
}
System.out.println("\nPolygons via visitor:" + allBuildingPolygons); //new
// // for loop
List<Polygon> allBuildingPolygons2 = new ArrayList<>();
for (Building b : model.getBuildings()) {
for (Geometry geom : b.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
allBuildingPolygons2.add(p);
allBuildingPolygons2.add(p.getOriginal()); //new
}
}
}
System.out.println("\nPolygon List:" + allBuildingPolygons2); //new
// new: LinearRings
// visitor
// filter duplicate linear rings out
Set<LinearRing> allBuildingLinearRings = new HashSet<>();
for (Building b : model.getBuildings()) {
b.accept(new CheckableUtilsVisitor() {
public void check(LinearRing ring) {
allBuildingLinearRings.add(ring);
}
});
}
System.out.println("\nLinearRings via visitor:" + allBuildingLinearRings); //new
// new: ExteriorRings
// visitor
// filter duplicate exterior rings out
Set<LinearRing> allBuildingExteriorRings = new HashSet<>();
for (Building b : model.getBuildings()) {
b.accept(new CheckableUtilsVisitor() {
public void check(LinearRing ring) {
allBuildingExteriorRings.add(ring.getParent().getExteriorRing()); // ACHTUNG getParent
}
});
}
System.out.println("\nExteriorRings via visitor:" + allBuildingExteriorRings); //new
// // new: ExteriorRings (List)
// // for loop
// List<LinearRing> allBuildingExteriorRings2 = new ArrayList<>();
// for (Building b : model.getBuildings()) {
// for (Geometry geom : b.getGeometries()) {
// for (Polygon p : geom.getPolygons()) {
// allBuildingExteriorRings2.add(p.getOriginal().getExteriorRing());
// }
// }
// }
// System.out.println("\nExteriorRing List:" + allBuildingExteriorRings2); //new
// new: InnerRings
// visitor
// filter duplicate inner rings out
Set<LinearRing> allBuildingInnerRings = new HashSet<>();
for (Building b : model.getBuildings()) {
b.accept(new CheckableUtilsVisitor() {
public void check(Polygon poly) {
allBuildingInnerRings.addAll(poly.getOriginal().getInnerRings());// ACHTUNG getOriginal
}
});
}
System.out.println("\nInnerRings via visitor:" + allBuildingInnerRings); //new
// // new: InnerRings (List)
// // for loop
// List<LinearRing> allBuildingInnerRings2 = new ArrayList<>();
// for (Building b : model.getBuildings()) {
// for (Geometry geom : b.getGeometries()) {
// for (Polygon p : geom.getPolygons()) {
// for (LinearRing lr : p.getInnerRings()) {
// allBuildingInnerRings2.add(lr);
// }
//
// }
// }
// }
// System.out.println("\nInnerRing List:" + allBuildingInnerRings2); //new
// // get Vertices for LinearRing, ExteriorRing and InteriorRing
// // new: LinearRingVertices
// // visitor
// // filter duplicate linear rings out
// Set<Vertex> allBuildingLinearRingVertices = new HashSet<>();
// for (Building b : model.getBuildings()) {
// b.accept(new CheckableUtilsVisitor() {
// public void check(LinearRing ring) {
// allBuildingLinearRingVertices.addAll(ring.getVertices());
// }
//
// });
// }
// System.out.println("\nLinearRingVertices via visitor:" + allBuildingLinearRingVertices); //new
// // new: ExteriorRingVertices
// // visitor
// // filter duplicate exterior rings out
// Set<Vertex> allBuildingExteriorRingVertices = new HashSet<>();
// for (Building b : model.getBuildings()) {
// b.accept(new CheckableUtilsVisitor() {
// public void check(LinearRing ring) {
// allBuildingExteriorRingVertices.addAll(ring.getParent().getOriginal().getExteriorRing().getVertices());
// }
//
// });
// }
// System.out.println("\nExteriorRingVertices via visitor:" + allBuildingExteriorRingVertices); //new
// // new: ExteriorRingVertices (List)
// // for loop
// List<Vertex> allBuildingExteriorRingVertices2 = new ArrayList<>();
// for (Building b : model.getBuildings()) {
// for (Geometry geom : b.getGeometries()) {
// for (Polygon p : geom.getPolygons()) {
// for (Vertex v : p.getOriginal().getExteriorRing().getVertices()) {
// allBuildingExteriorRingVertices2.add(v);
// }
// }
// }
// }
// System.out.println("\nExteriorRingVertices List:" + allBuildingExteriorRingVertices2); //new
// // new: InnerRingVertices
// // visitor
// // filter duplicate linear vertices out
// Set<Vertex> allBuildingInnerRingVertices = new HashSet<>();
// for (Building b : model.getBuildings()) {
// b.accept(new CheckableUtilsVisitor() {
// public void check(Polygon poly) {
// poly.getOriginal().getInnerRings().forEach(ring->allBuildingInnerRingVertices.addAll(ring.getVertices()));
// }
// });
// }
// System.out.println("\nInnerRingVertices via visitor:" + allBuildingInnerRingVertices); //new
// // new: InnerRingVertices (List)
// // for loop
// List<Vertex> allInnerRingVertices2 = new ArrayList<>();
// for (Building b : model.getBuildings()) {
// for (Geometry geom : b.getGeometries()) {
// for (Polygon p : geom.getPolygons()) {
// for (LinearRing lr : p.getInnerRings()) {
// for (Vertex v : lr.getVertices()) {
// allInnerRingVertices2.add(v);
// }
// }
//
// }
// }
// }
// System.out.println("\nInnerRingVertices List:" + allInnerRingVertices2); //new
// // TODO: HashMap to show Ring and gmlId
// Map<LinearRing, Set<Vertex>> test = new HashMap<>();
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.io.PrintStream;
// TODO: Change Point-Init semantics to Double-Init
/**
* Represents a 3D line segment defined by a start and end point.
* Useful for geometric computations like intersection tests,
* ray casting, or clipping against bounding volumes such as AABBs.
*/
public class Line {
private Point start;
private Point end;
public Line() {
this.start = new Point();
this.end = new Point();
}
public Line(Point start, Point end) {
this.start = start;
this.end = end;
}
public Point getStart() {
return start;
}
public Point getEnd() {
return end;
}
// Calculates Bounding Box for this Line
public AABB getAABB() {
// return new AABB(start, end)
return new AABB(start.getX(),start.getY(),start.getZ(), end.getX(), end.getY(), end.getZ());
}
/*
* calculates the square of length of the line
* to avoid squarerooth-operation
* */
public double getLengthSquared() {
double dx = end.getX() - start.getX();
double dy = end.getY() - start.getY();
double dz = end.getZ() - start.getZ();
return dx * dx + dy * dy + dz * dz;
}
// Direction vector
public Point getDir() {
return new Point(
end.getX() - start.getX(),
end.getY() - start.getY(),
end.getZ() - start.getZ()
);
}
// Normal vector
public Point getNormDir() {
double length = Math.sqrt(getLengthSquared());
if (length == 0) {
return new Point(0, 0, 0);
}
return new Point(
(end.getX() - start.getX()) / length,
(end.getY() - start.getY()) / length,
(end.getZ() - start.getZ()) / length
);
}
public void print() {
System.out.println("Line from " + start + " to " + end);
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import java.util.Arrays;
import java.util.List;
//import de.hft.stuttgart.citydoctor2.checks.geometry.*;
//import de.hft.stuttgart.citydoctor2.checks.aabb.*; //<-evtl *geomCheks hier verlagern
import de.hft.stuttgart.citydoctor2.datastructure.*;
/**
* Manual test runner for basic geometry checks with AABB involvement.
* Without JUnit: prints results directly to the console.
*/
public class GeometryChecksWithAABBTest {
public static void main(String[] args) {
System.out.println("=== Geometry Checks with AABB – Manual Test Runner ===");
//testTooFewPoints();
testNestedRings();
testRingSelfIntersection();
testSolidSelfIntersection();
System.out.println("\n=== Test run finished ===");
}
// --------------------------------------------------------------
// NestedRingsCheck
// --------------------------------------------------------------
private static void testNestedRings() {
System.out.println("\n[TEST] NestedRingsCheck");
// outer ring
LinearRing outer = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
outer.addAllVertices(Arrays.asList(
new Vertex(0,0,0),
new Vertex(1,0,0),
new Vertex(1,1,0),
new Vertex(0,1,0),
new Vertex(0,0,0)
));
ConcretePolygon p = new ConcretePolygon();
p.setExteriorRing(outer);
// inner ring 1
LinearRing inner1 = new LinearRing(LinearRing.LinearRingType.INTERIOR);
inner1.addAllVertices(Arrays.asList(
new Vertex(0.2,0.2,0),
new Vertex(0.8,0.2,0),
new Vertex(0.8,0.8,0),
new Vertex(0.2,0.8,0),
new Vertex(0.2,0.2,0)
));
// inner ring 2 inside inner ring 1
LinearRing inner2 = new LinearRing(LinearRing.LinearRingType.INTERIOR);
inner2.addAllVertices(Arrays.asList(
new Vertex(0.4,0.4,0),
new Vertex(0.6,0.4,0),
new Vertex(0.6,0.6,0),
new Vertex(0.4,0.6,0),
new Vertex(0.4,0.4,0)
));
p.addInteriorRing(inner1);
p.addInteriorRing(inner2);
NestedRingsCheckAABB check = new NestedRingsCheckAABB();
check.check(p);
System.out.println(" - Should detect nested rings: " + p.containsAnyError());
}
// --------------------------------------------------------------
// RingSelfIntCheck
// --------------------------------------------------------------
private static void testRingSelfIntersection() {
System.out.println("\n[TEST] RingSelfIntCheck");
LinearRing ring = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
ring.addAllVertices(Arrays.asList(
new Vertex(0,0,0),
new Vertex(1,1,0),
new Vertex(1,0,0),
new Vertex(0,1,0),
new Vertex(0,0,0)
));
ConcretePolygon p = new ConcretePolygon();
p.setExteriorRing(ring);
Geometry g = new Geometry(GeometryType.SOLID, Lod.LOD2);
g.addPolygon(p); // ensures parent links exist
g.updateEdgesAndVertices();
RingSelfIntCheckAABB check = new RingSelfIntCheckAABB();
check.check(ring);
System.out.println(" - Should detect self-intersection: " + ring.containsAnyError());
}
// --------------------------------------------------------------
// SolidSelfIntCheck
// --------------------------------------------------------------
private static void testSolidSelfIntersection() {
System.out.println("\n[TEST] SolidSelfIntCheck");
// first triangle
ConcretePolygon p1 = new ConcretePolygon();
LinearRing r1 = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
r1.addAllVertices(Arrays.asList(
new Vertex(0,0,0),
new Vertex(1,0,0),
new Vertex(1,1,0),
new Vertex(0,0,0)
));
p1.setExteriorRing(r1);
// second triangle overlapping the first
ConcretePolygon p2 = new ConcretePolygon();
LinearRing r2 = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
r2.addAllVertices(Arrays.asList(
new Vertex(0,0,0),
new Vertex(1,1,0),
new Vertex(0,1,0),
new Vertex(0,0,0)
));
p2.setExteriorRing(r2);
Geometry solid = new Geometry(GeometryType.SOLID, Lod.LOD2);
solid.getPolygons().addAll(List.of(p1, p2));
SolidSelfIntCheckAABB check = new SolidSelfIntCheckAABB();
check.check(solid);
System.out.println(" - Should detect solid self-intersection: " + solid.containsAnyError());
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
import de.hft.stuttgart.citydoctor2.healer.aabb.bht.*;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.parser.*;
import de.hft.stuttgart.citydoctor2.check.*;
import java.io.IOException;
import java.util.*;
//src/test/java/.../TestCityGmlAABB.java
import java.net.URL;
import java.nio.file.Paths;
public class TestCityGmlAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("=== CityGML AABB Test ===");
// --- 1) Ask user which test case to run ---
Scanner sc = new Scanner(System.in);
System.out.print("Enter TestCase ID (e.g. PO_TWO_INNERS, SO_WRONG_POLY_ORIENTATION_SINGLE) "
+ "or press Enter for default: ");
String id = sc.nextLine().trim();
// Default wählen
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt TestPaths per Reflection
resourcePath = (String) TestPaths.class.getField(id).get(null);
} catch (ReflectiveOperationException ex) {
System.out.println("Unknown testcase: " + id + " (using default)");
}
}
String filePathForParser = resolveResourceOnClasspath(resourcePath);
System.out.println("Using file: " + filePathForParser);
// --- 2) Load CityGML model ---
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
// TODO: Study class model
CityDoctorModel model = de.hft.stuttgart.citydoctor2.healing.TestUtil.loadCityModel(filePathForParser, config);
System.out.println("Model loaded successfully.\n");
List<Building> buildings = model.getBuildings();
// --- 3) Menu: choose granularity ---
System.out.println("\nChoose granularity:");
System.out.println(" 1) Polygon-level (broad-phase)");
System.out.println(" 2) Ring-level (EXTERIOR)");
System.out.println(" 3) Ring-level (INTERIOR)");
System.out.println(" 4) Vertex-level (fine-phase)");
System.out.println(" 5) All of the above");
System.out.print("Your choice [1-5]: ");
int choice = readChoice(sc, 1, 5);
boolean doPolygon = (choice == 1 || choice == 5);
boolean doExtRing = (choice == 2 || choice == 5);
boolean doIntRing = (choice == 3 || choice == 5);
boolean doVertex = (choice == 4 || choice == 5);
// --- 4) Run the selected modes ---
if (doPolygon) runPolygonMode(buildings);
if (doExtRing) runRingMode(buildings, true);
if (doIntRing) runRingMode(buildings, false);
if (doVertex) runVertexMode(buildings);
System.out.println("\n=== Done ===");
}
// ---------- Modes ----------
private static void runPolygonMode(List<Building> buildings) {
System.out.println("\n> Polygon-level AABB (broad-phase)");
Set<ConcretePolygon> polys = collectAllPolygons(buildings);
AABBTree<ConcretePolygon> bvh = new AABBTree<>(
new ArrayList<>(polys),
AABBUtils::getAABB
);
System.out.println("Polygons: " + polys.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runRingMode(List<Building> buildings, boolean exterior) {
String label = exterior ? "EXTERIOR" : "INTERIOR";
System.out.println("\n> Ring-level AABB (" + label + ")");
Set<LinearRing> rings = exterior
? collectExteriorRings(buildings)
: collectInteriorRings(buildings);
AABBTree<LinearRing> bvh = new AABBTree<>(
new ArrayList<>(rings),
AABBUtils::computeAABBFromRing
);
System.out.println(label + " rings: " + rings.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runVertexMode(List<Building> buildings) {
System.out.println("\n> Vertex-level AABB (fine-phase)");
Set<Vertex> verts = collectAllVertices(buildings);
AABBTree<Vertex> bvh = new AABBTree<>(
new ArrayList<>(verts),
AABBUtils::computeAABBFromVertex
);
System.out.println("Vertices: " + verts.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
// ---------- Collectors ----------
/** Collects all ConcretePolygons (unique) via visitor. */
private static Set<ConcretePolygon> collectAllPolygons(List<Building> buildings) {
Set<ConcretePolygon> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
set.add(poly.getOriginal());
}
});
}
return set;
}
/** Collects all EXTERIOR rings (unique). */
private static Set<LinearRing> collectExteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
LinearRing ext = poly.getOriginal().getExteriorRing();
if (ext != null) set.add(ext);
}
});
}
return set;
}
/** Collects all INTERIOR rings (unique). */
private static Set<LinearRing> collectInteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
List<LinearRing> inner = poly.getOriginal().getInnerRings();
if (inner != null && !inner.isEmpty()) {
set.addAll(inner);
}
}
});
}
return set;
}
/** Collects all vertices (unique) from both exterior and interior rings. */
private static Set<Vertex> collectAllVertices(List<Building> buildings) {
Set<Vertex> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
ConcretePolygon cp = poly.getOriginal();
LinearRing ext = cp.getExteriorRing();
if (ext != null) set.addAll(ext.getVertices());
List<LinearRing> inner = cp.getInnerRings();
if (inner != null) {
for (LinearRing lr : inner) set.addAll(lr.getVertices());
}
}
});
}
return set;
}
// ---------- Utils ----------
/* Helper method reads choice of the user */
private static int readChoice(Scanner sc, int min, int max) {
while (true) {
String s = sc.nextLine().trim();
try {
int v = Integer.parseInt(s);
if (v >= min && v <= max) return v;
} catch (NumberFormatException ignored) {}
System.out.print("Please enter a number between " + min + " and " + max + ": ");
}
}
/* Helper method Pathfinder */
private static String resolveResourceOnClasspath(String resourcePath) {
String rp = normalizeResourcePath(resourcePath);
// 1) Klassenpfad via Class.getResource
URL url = TestCityGmlAABB.class.getResource(rp);
// 2) Klassenpfad via Context-ClassLoader:
if (url == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
url = cl.getResource(noSlash);
}
// 3) Wenn gefunden: in Pfad umwandeln
if (url != null) {
try {
return Paths.get(url.toURI()).toString();
} catch (Exception e) {
throw new RuntimeException("Konnte Resource-URI nicht auflösen: " + url, e);
}
}
// 4) Fallbacks: Projekt-relative Kandidaten prüfen
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
String[] candidates = {
"src/test/resources/" + noSlash,
"src/main/resources/" + noSlash,
resourcePath // falls bereits ein echter Dateipfad übergeben wurde
};
for (String cand : candidates) {
java.nio.file.Path p = java.nio.file.Paths.get(cand);
if (java.nio.file.Files.exists(p)) {
return p.toAbsolutePath().toString();
}
}
throw new IllegalArgumentException(
"Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// FÜR ALLE FÄLLE zur Zeit REDUNDAD
private static String normalizeResourcePath(String path) {
String p = path.replace('\\', '/');
// Quellordner-Präfixe entfernen, falls mit übergeben
if (p.startsWith("src/test/resources/")) {
p = p.substring("src/test/resources/".length());
} else if (p.startsWith("src/main/resources/")) {
p = p.substring("src/main/resources/".length());
}
// Für Class.getResource: führenden Slash setzen
if (!p.startsWith("/")) p = "/" + p;
return p;
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import de.hft.stuttgart.citydoctor2.checks.aabb.*;
import java.io.IOException;
import java.util.*;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
public class TestHouseAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("===TestHouseAABB===");
/*
* 3D House of St.Niclas
*
* */
// Haus-Basis: Würfelgröße 2x2x2, Dachhöhe +1
double baseSize = 2.0;
double height = 2.0;
double roofHeight = 1.0;
// Bodenpunkte (unten)
Point b1 = new Point(0, 0, 0);
Point b2 = new Point(baseSize, 0, 0);
Point b3 = new Point(baseSize, baseSize, 0);
Point b4 = new Point(0, baseSize, 0);
// Deckpunkte (oben)
Point t1 = new Point(0, 0, height);
Point t2 = new Point(baseSize, 0, height);
Point t3 = new Point(baseSize, baseSize, height);
Point t4 = new Point(0, baseSize, height);
// Dachspitze (Prisma in Mitte der oberen Fläche)
Point roofPeak1 = new Point(baseSize/2, -0.2, height + roofHeight); // vorne
Point roofPeak2 = new Point(baseSize/2, baseSize + 0.2, height + roofHeight); // hinten
List<ConcretePolygon> house = new ArrayList<>();
// Boden (b1,b2,b3,b4)
house.add(createQuadPolygon(b1, b2, b3, b4));
// Wände
house.add(createQuadPolygon(b1, b2, t2, t1)); // vorne
house.add(createQuadPolygon(b2, b3, t3, t2)); // rechts
house.add(createQuadPolygon(b3, b4, t4, t3)); // hinten
house.add(createQuadPolygon(b4, b1, t1, t4)); // links
// Decke unter Dach
house.add(createQuadPolygon(t1, t2, t3, t4));
// Dach: zwei Dreiecksseiten (links/rechts)
house.add(createTrianglePolygon(t1, t2, roofPeak1));
house.add(createTrianglePolygon(t4, t3, roofPeak2));
// Dach: Vorder- und Rückfläche (Prisma)
house.add(createQuadPolygon(t2, t3, roofPeak2, roofPeak1));
house.add(createQuadPolygon(t1, t4, roofPeak2, roofPeak1));
//
AABB houseAABB = BVHStructures.getAggregateAABB(house);
houseAABB.print();
// Define test points PS wegen lesbarkeit als Liste von Koord. vorerst
double[] insidePoint = {1.0, 1.0, 1.0}; // clearly inside (center of the base cube)
double[] roofPoint = {1.0, 1.0, 2.8}; // inside roof area but near the top
double[] outsidePoint = {3.0, 3.0, 3.0}; // clearly outside
double[] edgePoint = {2.0, 2.0, 2.0}; // exactly on the upper cube corner
// Test containment
System.out.println("\nTest point (inside): " + Arrays.toString(insidePoint));
System.out.println("-> contained? " + houseAABB.encloses(insidePoint[0], insidePoint[1], insidePoint[2]));
System.out.println("\nTest point (on roof): " + Arrays.toString(roofPoint));
System.out.println("-> contained? " + houseAABB.encloses(roofPoint[0], roofPoint[1], roofPoint[2]));
System.out.println("\nTest point (outside): " + Arrays.toString(outsidePoint));
System.out.println("-> contained? " + houseAABB.encloses(outsidePoint[0], outsidePoint[1], outsidePoint[2]));
System.out.println("\nTest point (on edge): " + Arrays.toString(edgePoint));
System.out.println("-> contained? " + houseAABB.encloses(edgePoint[0], edgePoint[1], edgePoint[2]));
}
/** Creates a triangular polygon from three points */
private static ConcretePolygon createTrianglePolygon(Point p1, Point p2, Point p3) {
// alocate poly
ConcretePolygon poly = new ConcretePolygon();
// alocate ring
LinearRing ring = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
// vertices of triangle
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Close ring
//
poly.setExteriorRing(ring);
return poly;
}
/** Creates a quadrilateral polygon from four points */
private static ConcretePolygon createQuadPolygon(Point p1, Point p2, Point p3, Point p4) {
ConcretePolygon poly = new ConcretePolygon();
LinearRing ring = new LinearRing(LinearRing.LinearRingType.EXTERIOR);
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ())); // Close ring
poly.setExteriorRing(ring);
return poly;
}
}
package de.hft.stuttgart.citydoctor2.healing.bht;
/**
* Central collection of CityGML test file paths used for AABB / healing experiments.
* Files are located under {@code src/test/resources} or {@code src/test/resources/bht/}
* ACHTUNG: mit '!!!' markierte Objekte haben einen falschen Pfad
*/
public final class TestPaths {
private TestPaths() {
// utility class
}
// ==================================================
// Schematron / Validation tests
// ==================================================
/**!!! Not XSD valid, namespaces removed, for schematron experiments. */
public static final String SCHEMATRON =
"CityDoctorParent/CityDoctorValidation/target/test-classes/SimpleSolid_SrefBS-Schematron-Test.gml";
//CityDoctorParent
//"src/test/resources/bht/SimpleSolid_SrefBS-Schematron-Test.gml";
// ==================================================
// LinearRing (LR) test cases
// ==================================================
/**1 LinearRing has only 2 points. */
public static final String LR_2POINTS =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0001-T0001.gml";
/**2 LinearRing has 3 points but is not closed. */
public static final String LR_NOTCLOSED =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0001-T0002.gml";
/**3 LinearRing has consecutive duplicate vertices. */
public static final String LR_DUPLICATE_CONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0001.gml";
/**4 LinearRing has non-consecutive duplicate vertices. */
public static final String LR_DUPLICATE_NONCONSEC =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0002-T0002.gml";
/**added 5 LinearRing points form a straight line. */
public static final String LR_LINE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0003.gml";
/**added 6 LinearRing has consecutive points closer than 0.1. */
public static final String LR_TOO_CLOSE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0004.gml";
/**added 7 LinearRing has too few points and duplicate points. */
public static final String LR_TOO_FEW_DUP =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0002-T0005.gml";
/**8 LinearRing is not closed. */
public static final String LR_NOTCLOSED_2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0003-T0001.gml";
/**added 9 LinearRing is closed but with point distance < 0.1. */
public static final String LR_CLOSED_SMALLDIST =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-LR-0003-T0002.gml";
/**10 LinearRing has a loop. */
public static final String LR_LOOP =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0004.gml";
/** 11 LinearRing point touches edge with distance < 0.1. */
public static final String LR_TOUCH_EDGE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0005.gml";
// ==================================================
// Polygon (PO) test cases
// ==================================================
/**12 Inner ring intersects outer ring. */
public static final String PO_INTERSECT_OUTER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0001.gml";
/**13 Polygon has 2 inner rings. */
public static final String PO_TWO_INNERS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0002.gml";
/**14 Polygon has 2 intersecting inner rings. */
public static final String PO_INNER_INTERSECT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0001-T0003.gml";
/**15 Polygon has 2 inner rings; one vertex too far from the plane. */
public static final String PO_INNER_OFF_PLANE =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0002-T0001.gml";
/**added 16 Polygon has normal deviation error. */
public static final String PO_NORMAL_ERROR =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0002-T0002.gml";
/**17 Polygon has disconnected interior surface. */
public static final String PO_DISCONNECTED_INTERIOR_1 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0001.gml";
/**added 18 Polygon has disconnected interior surface due to two inner rings. */
public static final String PO_DISCONNECTED_INTERIOR_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0003-T0002.gml";
/** added 19 Inner ring completely outside the outer ring. */
public static final String PO_INNER_OUTSIDE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0004-T0001.gml";
/**added 20 Inner ring completely inside another inner ring. */
public static final String PO_INNER_INSIDE_ANOTHER =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-PO-0005-T0001.gml";
/**21 Two inner rings with wrong orientation. */
public static final String PO_TWO_INNERS_WRONG_ORIENT =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-PO-0006-T0001.gml";
// ==================================================
// Solid (SO) test cases
// ==================================================
/**added 22 Solid consists of only 3 polygons. */
public static final String SO_TOO_FEW_POLYGONS =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0001-T0001.gml";
/**added 23 Shell is not watertight. */
public static final String SO_NOT_WATERTIGHT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0002-T0001.gml";
/** 24 Two buildings connected only by a single point. */
public static final String SO_CONNECTED_BY_POINT =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
/**25 Roof point slightly deviates (e.g., 10.0 14.95 2.95 vs 10.0 15.0 3.0). */
public static final String SO_ROOF_COORD_DEVIATION_1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0004-T0001.gml";
/**25 Missing polygon. */
public static final String SO_MISSING_POLYGON =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0002.gml";
/** LOST! 26 Slight coordinate deviations on multiple roof polygons (CityGML 2.0 only). */
public static final String SO_ROOF_COORD_DEVIATION_2 =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0004-T0003.gml";
/**added 27 Solid geometry actually contains two buildings. */
public static final String SO_TWO_BUILDINGS_IN_ONE =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0005-T0001.gml";
// Falsches Datei !!!!
/**!!! 28 Self-intersecting geometry. */
public static final String SO_SELF_INTERSECTING =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0003-T0001.gml";
// ACHTUNG FALSCHES LABEL
/**29 Polygon with wrong orientation. */
public static final String ENTER_LABEL1 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0001.gml";
/**30 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL2 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0002.gml";
/**31 Polygon with wrong orientation. ACHTUNG FALSCHES LABEL */
public static final String ENTER_LABEL3 =
"src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0007-T0003.gml";
//
/** LOST! 33 All polygons have wrong orientation. */
public static final String SO_WRONG_POLY_ORIENTATION_ALL =
"src/test/resources/bht/SimpleSolid_SrefBS-GE-gml-SO-0008-T0001.gml";
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import java.util.*;
import de.hft.stuttgart.citydoctor2.checks.aabb.AABB;
import de.hft.stuttgart.citydoctor2.checks.aabb.BVHStructures;
import de.hft.stuttgart.citydoctor2.checks.aabb.Point;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class TestDummyAABB {
/**
* Entry point for AABB testing
*/
public static void main(String[] args) {
System.out.println("=== AABB TEST ===");
ConcretePolygon polygon = new ConcretePolygon();
LinearRing exterior = new LinearRing(LinearRingType.EXTERIOR);
exterior.addVertex(new Vertex(0, 0, 0));
exterior.addVertex(new Vertex(1, 0, 0));
exterior.addVertex(new Vertex(0, 1, 0));
exterior.addVertex(new Vertex(0, 0, 0));
polygon.setExteriorRing(exterior);
// Define points of a tetrahedron
Point p1 = new Point(0, 0, 0);
Point p2 = new Point(1, 0, 0);
Point p3 = new Point(0.5, 1, 0);
Point p4 = new Point(0.5, 0.5, 1);
// Define triangle faces of the tetrahedron
ConcretePolygon base = new ConcretePolygon();
LinearRing baseRing = new LinearRing(LinearRingType.EXTERIOR);
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
baseRing.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
baseRing.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
base.setExteriorRing(baseRing);
ConcretePolygon side1 = new ConcretePolygon();
LinearRing ring1 = new LinearRing(LinearRingType.EXTERIOR);
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring1.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring1.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
side1.setExteriorRing(ring1);
ConcretePolygon side2 = new ConcretePolygon();
LinearRing ring2 = new LinearRing(LinearRingType.EXTERIOR);
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring2.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring2.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
side2.setExteriorRing(ring2);
ConcretePolygon side3 = new ConcretePolygon();
LinearRing ring3 = new LinearRing(LinearRingType.EXTERIOR);
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring3.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring3.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
side3.setExteriorRing(ring3);
List<ConcretePolygon> poly = Arrays.asList(base, side1, side2, side3);
AABB aabb = BVHStructures.getAggregateAABB(poly);
System.out.println("\nComputed AABB:");
aabb.print();
/*** 3D-Point-coords with Test Properties ***/
// Inside laying Point
double testInsideX = 0.5;
double testInsideY = 0.5;
double testInsideZ = 0.3;
// Outside laying Point
double testOutsideX = 2.0;
double testOutsideY = 2.0;
double testOutsideZ = 2.0;
// On edge laying Point
double testOnEdgeX = 1.0;
double testOnEdgeY = 1.0;
double testOnEdgeZ = 0.0;
System.out.println("\nTest point (inside): " + testInsideX + " " + testInsideY + " " + testInsideZ);
System.out.println("-> contained?: " + aabb.encloses(testInsideX, testInsideY, testInsideZ));
System.out.println("\nTest point (outside): " + testOutsideX + " " + testOutsideY + " " + testOutsideZ);
System.out.println("-> contained?: " + aabb.encloses(testOutsideX, testOutsideY, testOutsideZ));
System.out.println("\nTest point (on edge): " + testOnEdgeX + " " + testOnEdgeY + " " + testOnEdgeZ);
System.out.println("-> contained?: " + aabb.encloses(testOnEdgeX, testOnEdgeY, testOnEdgeZ));
}
}
package de.hft.stuttgart.citydoctor2.datastructure.bht;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
//import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import java.util.*;
//import java.util.List;
/**
* @author Baris Numanoglu
*
* Utility class providing AABB-related helper methods.
*
* Contains functionality for computing axis-aligned bounding boxes (AABBs)
* from geometric data structures such as {@link ConcretePolygon} or {@link LinearRing}.
*
* All methods are static.
*/
public class AABBUtils {
public static AABB getAABB(ConcretePolygon poly) {
LinearRing ring = poly.getExteriorRing();
if (ring == null) return null;
return computeAABBFromRing(ring);
}
/**
* Computes an AABB from a LinearRing by iterating over its vertices.
*
* @param ring the LinearRing whose vertices define the AABB
* @return AABB enclosing the ring; returns an empty AABB if the ring has no vertices
*/
public static AABB computeAABBFromRing(LinearRing ring) {
if (ring == null || ring.getVertices() == null || ring.getVertices().isEmpty()) {
return new AABB(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
}
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double maxZ = Double.NEGATIVE_INFINITY;
for (Vertex v : ring.getVertices()) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
minX = Math.min(minX, x);
minY = Math.min(minY, y);
minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
maxZ = Math.max(maxZ, z);
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
/**
* PREMORDIAL: substitutes the earlier semantic of AABB-Point-Init.
* Computes the min and max bounds (6 doubles) from two points.
*
* @param p1 the first point
* @param p2 the second point
* @return an array of 6 doubles: [minX, minY, minZ, maxX, maxY, maxZ]
*/
public static double[] extractBoundsFromPoints(Point p1, Point p2) {
double minX = Math.min(p1.getX(), p2.getX());
double minY = Math.min(p1.getY(), p2.getY());
double minZ = Math.min(p1.getZ(), p2.getZ());
double maxX = Math.max(p1.getX(), p2.getX());
double maxY = Math.max(p1.getY(), p2.getY());
double maxZ = Math.max(p1.getZ(), p2.getZ());
return new double[] { minX, minY, minZ, maxX, maxY, maxZ };
}
// NOTE: May replaced later: double[] extractBoundsFromPoints(List<? extends Point> points)??
public static AABB aabbFromVector3d(List<? extends Vector3d> points) {
double minX = Double.POSITIVE_INFINITY, minY = Double.POSITIVE_INFINITY, minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY;
for (Vector3d p : points) {
minX = Math.min(minX, p.getX());
minY = Math.min(minY, p.getY());
minZ = Math.min(minZ, p.getZ());
maxX = Math.max(maxX, p.getX());
maxY = Math.max(maxY, p.getY());
maxZ = Math.max(maxZ, p.getZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
/**
* Computes an AABB for a single vertex (degenerate AABB with min=max=vertex coordinates).
*
* @param vertex the vertex to wrap in an AABB
* @return AABB centered at the vertex position
*/
public static AABB computeAABBFromVertex(Vertex vertex) {
if (vertex == null) {
// Return a degenerate AABB with infinite bounds if vertex is null
return new AABB(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
}
double x = vertex.getX();
double y = vertex.getY();
double z = vertex.getZ();
// AABB is degenerate here: min=max=(x,y,z)
return new AABB(x, y, z, x, y, z);
}
/** Returns true if the AABB is (nearly) flat in at least two axes. */
public static boolean isDegenerate(AABB aabb, double tol) {
double dx = aabb.getMaxX() - aabb.getMinX();
double dy = aabb.getMaxY() - aabb.getMinY();
double dz = aabb.getMaxZ() - aabb.getMinZ();
int flatAxes = 0;
if (dx <= tol) flatAxes++;
if (dy <= tol) flatAxes++;
if (dz <= tol) flatAxes++;
return flatAxes >= 2; // cannot host 3 distinct points in 3D
}
/**
* Counts distinct vertices of a ring using a tolerance.
* Implementation: uniform quantization (grid hashing) for speed and determinism.
*/
public static int countDistinctVertices(LinearRing ring, double tol) {
if (ring == null || ring.getVertices() == null) return 0;
// Use a quantization resolution based on tol (avoid div by zero).
final double q = (tol > 0) ? tol : 1e-9;
// Hash set of quantized integer triplets "ix|iy|iz"
Set<Long> buckets = new HashSet<>(ring.getVertices().size() * 2);
for (Vertex v : ring.getVertices()) {
long ix = Math.round(v.getX() / q);
long iy = Math.round(v.getY() / q);
long iz = Math.round(v.getZ() / q);
// pack into a 64-bit key (simple mixing; safe if ranges are reasonable)
long key = mix3(ix, iy, iz);
buckets.add(key);
}
return buckets.size();
}
// Simple 3D integer mix to a 64-bit key (Xorshift-ish) from JavaDoc
private static long mix3(long x, long y, long z) {
long h = x * 73856093L ^ y * 19349663L ^ z * 83492791L;
// final avalanche
h ^= (h >>> 33);
h *= 0xff51afd7ed558ccdL;
h ^= (h >>> 33);
h *= 0xc4ceb9fe1a85ec53L;
h ^= (h >>> 33);
return h;
}
/** Returns true if 'outer' fully contains 'inner' (inclusive) in axis-aligned sense. */
public static boolean containsAabb(AABB outer, AABB inner) {
return outer.getMinX() <= inner.getMinX()
&& outer.getMinY() <= inner.getMinY()
&& outer.getMinZ() <= inner.getMinZ()
&& outer.getMaxX() >= inner.getMaxX()
&& outer.getMaxY() >= inner.getMaxY()
&& outer.getMaxZ() >= inner.getMaxZ();
}
/** Builds a padded AABB around a segment (two vertices). */
public static AABB edgeAabb(Vertex a, Vertex b, double pad) {
double minX = Math.min(a.getX(), b.getX()) - pad;
double minY = Math.min(a.getY(), b.getY()) - pad;
double minZ = Math.min(a.getZ(), b.getZ()) - pad;
double maxX = Math.max(a.getX(), b.getX()) + pad;
double maxY = Math.max(a.getY(), b.getY()) + pad;
double maxZ = Math.max(a.getZ(), b.getZ()) + pad;
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
/** Returns true if two AABBs overlap (inclusive). */
public static boolean overlaps(AABB a, AABB b) {
return !(a.getMaxX() < b.getMinX() ||
a.getMinX() > b.getMaxX() ||
a.getMaxY() < b.getMinY() ||
a.getMinY() > b.getMaxY() ||
a.getMaxZ() < b.getMinZ() ||
a.getMinZ() > b.getMaxZ());
}
}
package de.hft.stuttgart.citydoctor2.healing.bht;
import de.hft.stuttgart.citydoctor2.datastructure.bht.*;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.parser.*;
import de.hft.stuttgart.citydoctor2.systemtest.TestUtil;
import de.hft.stuttgart.citydoctor2.check.*;
//import de.hft.stuttgart.citydoctor2.healing.*;
import java.io.IOException;
import java.util.*;
//src/test/java/.../TestCityGmlAABB.java
import java.net.URL;
import java.nio.file.Paths;
public class TestCityGmlAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("=== CityGML AABB Test ===");
// --- 1) Ask user which test case to run ---
Scanner sc = new Scanner(System.in);
System.out.print("Enter TestCase ID (e.g. PO_TWO_INNERS, SO_WRONG_POLY_ORIENTATION_SINGLE) "
+ "or press Enter for default: ");
String id = sc.nextLine().trim();
// Default wählen
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt TestPaths per Reflection
resourcePath = (String) TestPaths.class.getField(id).get(null);
} catch (ReflectiveOperationException ex) {
System.out.println("Unknown testcase: " + id + " (using default)");
}
}
String filePathForParser = resolveResourceOnClasspath(resourcePath);
System.out.println("Using file: " + filePathForParser);
// --- 2) Load CityGML model ---
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
// TODO: Study class model
CityDoctorModel model = TestUtil.loadCityModel(filePathForParser, config);
System.out.println("Model loaded successfully.\n");
List<Building> buildings = model.getBuildings();
// --- 3) Menu: choose granularity ---
System.out.println("\nChoose granularity:");
System.out.println(" 1) Polygon-level (broad-phase)");
System.out.println(" 2) Ring-level (EXTERIOR)");
System.out.println(" 3) Ring-level (INTERIOR)");
System.out.println(" 4) Vertex-level (fine-phase)");
System.out.println(" 5) All of the above");
System.out.print("Your choice [1-5]: ");
int choice = readChoice(sc, 1, 5);
boolean doPolygon = (choice == 1 || choice == 5);
boolean doExtRing = (choice == 2 || choice == 5);
boolean doIntRing = (choice == 3 || choice == 5);
boolean doVertex = (choice == 4 || choice == 5);
// --- 4) Run the selected modes ---
if (doPolygon) runPolygonMode(buildings);
if (doExtRing) runRingMode(buildings, true);
if (doIntRing) runRingMode(buildings, false);
if (doVertex) runVertexMode(buildings);
System.out.println("\n=== Done ===");
}
// ---------- Modes ----------
private static void runPolygonMode(List<Building> buildings) {
System.out.println("\n> Polygon-level AABB (broad-phase)");
Set<ConcretePolygon> polys = collectAllPolygons(buildings);
AABBTree<ConcretePolygon> bvh = new AABBTree<>(
new ArrayList<>(polys),
AABBUtils::getAABB
);
System.out.println("Polygons: " + polys.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runRingMode(List<Building> buildings, boolean exterior) {
String label = exterior ? "EXTERIOR" : "INTERIOR";
System.out.println("\n> Ring-level AABB (" + label + ")");
Set<LinearRing> rings = exterior
? collectExteriorRings(buildings)
: collectInteriorRings(buildings);
AABBTree<LinearRing> bvh = new AABBTree<>(
new ArrayList<>(rings),
AABBUtils::computeAABBFromRing
);
System.out.println(label + " rings: " + rings.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
private static void runVertexMode(List<Building> buildings) {
System.out.println("\n> Vertex-level AABB (fine-phase)");
Set<Vertex> verts = collectAllVertices(buildings);
AABBTree<Vertex> bvh = new AABBTree<>(
new ArrayList<>(verts),
AABBUtils::computeAABBFromVertex
);
System.out.println("Vertices: " + verts.size());
if (bvh.getRoot() != null) {
System.out.print("Root AABB: ");
bvh.getRoot().aabb.print();
}
}
// ---------- Collectors ----------
/** Collects all ConcretePolygons (unique) via visitor. */
private static Set<ConcretePolygon> collectAllPolygons(List<Building> buildings) {
Set<ConcretePolygon> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
set.add(poly.getOriginal());
}
});
}
return set;
}
/** Collects all EXTERIOR rings (unique). */
private static Set<LinearRing> collectExteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
LinearRing ext = poly.getOriginal().getExteriorRing();
if (ext != null) set.add(ext);
}
});
}
return set;
}
/** Collects all INTERIOR rings (unique). */
private static Set<LinearRing> collectInteriorRings(List<Building> buildings) {
Set<LinearRing> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
List<LinearRing> inner = poly.getOriginal().getInnerRings();
if (inner != null && !inner.isEmpty()) {
set.addAll(inner);
}
}
});
}
return set;
}
/** Collects all vertices (unique) from both exterior and interior rings. */
private static Set<Vertex> collectAllVertices(List<Building> buildings) {
Set<Vertex> set = new HashSet<>();
for (Building b : buildings) {
b.accept(new CheckableUtilsVisitor() {
@Override
public void check(Polygon poly) {
ConcretePolygon cp = poly.getOriginal();
LinearRing ext = cp.getExteriorRing();
if (ext != null) set.addAll(ext.getVertices());
List<LinearRing> inner = cp.getInnerRings();
if (inner != null) {
for (LinearRing lr : inner) set.addAll(lr.getVertices());
}
}
});
}
return set;
}
// ---------- Utils ----------
/* Helper method reads choice of the user */
private static int readChoice(Scanner sc, int min, int max) {
while (true) {
String s = sc.nextLine().trim();
try {
int v = Integer.parseInt(s);
if (v >= min && v <= max) return v;
} catch (NumberFormatException ignored) {}
System.out.print("Please enter a number between " + min + " and " + max + ": ");
}
}
/* Helper method Pathfinder */
private static String resolveResourceOnClasspath(String resourcePath) {
String rp = normalizeResourcePath(resourcePath);
// 1) Klassenpfad via Class.getResource
URL url = TestCityGmlAABB.class.getResource(rp);
// 2) Klassenpfad via Context-ClassLoader:
if (url == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
url = cl.getResource(noSlash);
}
// 3) Wenn gefunden: in Pfad umwandeln
if (url != null) {
try {
return Paths.get(url.toURI()).toString();
} catch (Exception e) {
throw new RuntimeException("Konnte Resource-URI nicht auflösen: " + url, e);
}
}
// 4) Fallbacks: Projekt-relative Kandidaten prüfen
String noSlash = rp.startsWith("/") ? rp.substring(1) : rp;
String[] candidates = {
"src/test/resources/" + noSlash,
"src/main/resources/" + noSlash,
resourcePath // falls bereits ein echter Dateipfad übergeben wurde
};
for (String cand : candidates) {
java.nio.file.Path p = java.nio.file.Paths.get(cand);
if (java.nio.file.Files.exists(p)) {
return p.toAbsolutePath().toString();
}
}
throw new IllegalArgumentException(
"Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// FÜR ALLE FÄLLE zur Zeit REDUNDAD
private static String normalizeResourcePath(String path) {
String p = path.replace('\\', '/');
// Quellordner-Präfixe entfernen, falls mit übergeben
if (p.startsWith("src/test/resources/")) {
p = p.substring("src/test/resources/".length());
} else if (p.startsWith("src/main/resources/")) {
p = p.substring("src/main/resources/".length());
}
// Für Class.getResource: führenden Slash setzen
if (!p.startsWith("/")) p = "/" + p;
return p;
}
}
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