Commit c67a5961 authored by Numanoglu's avatar Numanoglu
Browse files

Bring over AABB bht work (model + validation + healer tests) from cf200f0

parent 8536ec35
Pipeline #12312 failed with stage
in 1 minute and 41 seconds
package de.hft.stuttgart.citydoctor2.healing.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 testcase = sc.nextLine().trim();
// Default path if nothing entered
//String path = TestPaths.PO_TWO_INNERS; // default example
/*
*
*
*
*
* */
//
// DEBUG: relative path problems
String path = "src/test/resources/bht/TestBuilding_SelfInt.gml"; //not just SelfInt errors
/*
* TODO: class Testmodel.getTestPath(); getName() etc. pp
*
*
* */
String id = sc.nextLine().trim();
// Default wählen (eine deiner Konstanten)
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt z. B. TestPaths.SO_NOT_WATERTIGHT 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: import package
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(
"Pap:Resource nicht im Klassenpfad gefunden: " + resourcePath + " (versucht als: " + rp + ")"
);
}
// FÜR ALLE FÄLLE Corrections via string manip GERADE ws 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 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;
}
/**
* SOON OBSOLETE: Generic Version all ready exists
* Calculates the smallest AABB that encloses all given polygons
*/
public static AABB getAggregateAABB(List<ConcretePolygon> polygons) {
if (polygons.size() == 1) {
return AABBUtils.getAABB(polygons.get(0));
}
double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE, maxZ = -Double.MAX_VALUE;
for (ConcretePolygon p : polygons) {
AABB aabb = AABBUtils.getAABB(p);
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);
}
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
package de.hft.stuttgart.citydoctor2.healing.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 (eine deiner Konstanten)
String resourcePath = TestPaths.PO_TWO_INNERS;
if (!id.isEmpty()) {
try {
// Holt z. B. TestPaths.SO_NOT_WATERTIGHT 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 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;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an Axis-Aligned Bounding Box (AABB) using six double values.
*/
public class AABB {
private double minX, minY, minZ;
private double maxX, maxY, maxZ;
/**
* Default constructor initializing to a zero-sized box at origin.
*/
/**
* Constructs an AABB from explicit min and max coordinates.
*/
public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
this.minX = Math.min(minX, maxX);
this.minY = Math.min(minY, maxY);
this.minZ = Math.min(minZ, maxZ);
this.maxX = Math.max(minX, maxX);
this.maxY = Math.max(minY, maxY);
this.maxZ = Math.max(minZ, maxZ);
}
public double getMinX() { return minX; }
public double getMinY() { return minY; }
public double getMinZ() { return minZ; }
public double getMaxX() { return maxX; }
public double getMaxY() { return maxY; }
public double getMaxZ() { return maxZ; }
public double getCenterX() { return (minX + maxX) / 2.0; }
public double getCenterY() { return (minY + maxY) / 2.0; }
public double getCenterZ() { return (minZ + maxZ) / 2.0; }
/**
* Returns the center point of the AABB as a double array [x, y, z].
*
* @return the center coordinates
*/
public double[] getCenter() {
double centerX = (minX + maxX) / 2.0;
double centerY = (minY + maxY) / 2.0;
double centerZ = (minZ + maxZ) / 2.0;
return new double[] { centerX, centerY, centerZ };
}
/**
* PREMORDIAL CHEAT TODO correct!
* Returns the 8 corner points of the AABB as list of double[3] arrays.
*/
public List<double[]> getCornerPoints() {
List<double[]> points = new ArrayList<>(8);
points.add(new double[]{minX, minY, minZ});
points.add(new double[]{minX, minY, maxZ});
points.add(new double[]{minX, maxY, minZ});
points.add(new double[]{minX, maxY, maxZ});
points.add(new double[]{maxX, minY, minZ});
points.add(new double[]{maxX, minY, maxZ});
points.add(new double[]{maxX, maxY, minZ});
points.add(new double[]{maxX, maxY, maxZ});
return points;
}
/**
* Checks whether a point lies inside or on the surface of the AABB.
*/
public boolean encloses(double x, double y, double z) {
return minX <= x && x <= maxX &&
minY <= y && y <= maxY &&
minZ <= z && z <= maxZ;
}
/**
* Checks if this and other boxes intersect
*/
public boolean intersects(AABB other) {
return !(other.maxX < this.minX || other.minX > this.maxX ||
other.maxY < this.minY || other.minY > this.maxY ||
other.maxZ < this.minZ || other.minZ > this.maxZ);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof AABB other)) return false;
return minX == other.minX && minY == other.minY && minZ == other.minZ &&
maxX == other.maxX && maxY == other.maxY && maxZ == other.maxZ;
}
@Override
public int hashCode() {
return Double.hashCode(minX) ^ Double.hashCode(maxX) ^
Double.hashCode(minY) ^ Double.hashCode(maxY) ^
Double.hashCode(minZ) ^ Double.hashCode(maxZ);
}
public void print() {
System.out.printf("min: %.2f, %.2f, %.2f%n", minX, minY, minZ);
System.out.printf("max: %.2f, %.2f, %.2f%n", maxX, maxY, maxZ);
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
import de.hft.stuttgart.citydoctor2.checks.aabb.*;
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 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.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";
}
package de.hft.stuttgart.citydoctor2.checks.bht;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import java.io.IOException;
//import java.io.InputStream; //new
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
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.Checker;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
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.Checkable;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.check.error.DegeneratedRingError;
import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError;
import de.hft.stuttgart.citydoctor2.check.error.RingDuplicatePointError;
import de.hft.stuttgart.citydoctor2.check.error.RingEdgeIntersectionError;
import de.hft.stuttgart.citydoctor2.checks.geometry.SolidSelfIntCheck;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
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.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
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.gui.table.ErrorStat;
//import de.hft.stuttgart.citydoctor2.gui.tree.ErrorItemVisitor;
//import de.hft.stuttgart.citydoctor2.gui.tree.Renderable;
//import de.hft.stuttgart.citydoctor2.gui.tree.node.ErrorNode;
import de.hft.stuttgart.citydoctor2.healing.TestUtil;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.visitors.CheckableErrorCollector;
import javafx.scene.control.TreeItem;
public class TestExampleAABB {
public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException {
System.out.println("Start TestExampleAABB..."); //new
// String path = args[0];
// String path = "src/test/resources/TestBuilding1.gml"; //new block model
String path = "src/test/resources/bht/TestBuilding_SelfInt.gml"; //not just SelfInt errors
// String path = "src/test/resources/bht/SolidSelfIntTest1.gml"; //valid model
// String path = "src/test/resources/bht/DorstenGartenstrasse_SelfInt.gml"; //SelfInt and NotClosed errors
////Info: if test model doesn't include the desired error, use:
//// addCheckResult(new CheckResult(CheckId.C_GE_S_SELF_INTERSECTION), ResultStatus.ERROR, Mockito.mock(CheckError.class)) hinzufügen
System.out.println("\nModel has been loaded...\n"); //new
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.setSchematronFilePathInGlobalParameters(null);
// CityDoctorModel model = CityGmlParser.parseCityGmlFile("src/test/resources/bht/SolidSelfIntTest1.gml", config.getParserConfiguration());
CityDoctorModel model = TestUtil.loadCityModel(path, config);
Checker c = new Checker(config, model);
c.runChecks();
Building building = model.getBuildings().get(0);
// assertFalse(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
// assertEquals(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
// assertNotNull(building.containsError(CheckId.C_GE_S_SELF_INTERSECTION));
// System.out.println("\n" + building + " contains " + CheckId.C_GE_S_SELF_INTERSECTION); //new
List<CheckError> errors = new ArrayList<>();
building.collectContainedErrors(errors);
//features of errors containing CheckId.C_GE_R_SELF_INTERSECTION
// errors.get(0).getFeature().containsError(CheckId.C_GE_R_SELF_INTERSECTION);
// System.out.println("\n" + building + " contains the Errors " + errors ); //shows list of unknown check errors
//Check for Unknown Errors
// List<CheckError> AllErrors = errors.stream().filter(checkError -> checkError.getErrorId().equals(ErrorId.UNKNOWN_ERROR)).collect(Collectors.toList()); // returns UnknownCheckError
// System.out.println("\n" + building + " contains the Errors " + AllErrors ); //shows list of unknown check errors
//Check for Solid SelfIntersection
// List<CheckError> selfIntSolidErrors = errors.stream().filter(checkError -> checkError.getErrorId().equals(ErrorId.GE_S_SELF_INTERSECTION)).collect(Collectors.toList());
// System.out.println("\n" + building + " contains the Solid Errors " + selfIntSolidErrors ); //shows list of solid selfInt
//Check for Ring SelfIntersection
List<CheckError> selfIntRingErrors = errors.stream().filter(checkError -> checkError.getErrorId().equals(ErrorId.GE_R_SELF_INTERSECTION)).collect(Collectors.toList());
System.out.println("\n" + building + " contains the Ring Errors " + selfIntRingErrors ); //shows list of ring selfInt
//Get Vertices and BoundingBox of LinearRing(s) with SelfIntErrors
for (CheckError error : errors) {
if (error instanceof PointTouchesEdgeError err1) {
System.out.println("\nVertices of PointTouchesEdgeError: " + err1.getRing().getVertices());
BoundingBox bbox1 = BoundingBox.ofPoints(err1.getRing().getVertices());
System.out.println("\nBoundingBox: " + Arrays.toString(bbox1.getBox()));
}
if (error instanceof RingEdgeIntersectionError err2) {
System.out.println("\nVertices of RingEdgeIntersectionError: " + err2.getRing().getVertices());
BoundingBox bbox2 = BoundingBox.ofPoints(err2.getRing().getVertices());
System.out.println("\nBoundingBox: " + Arrays.toString(bbox2.getBox()));
}
if (error instanceof RingDuplicatePointError err3) {
System.out.println("\nVertices of RingDuplicatePointError: " + err3.getRing().getVertices());
BoundingBox bbox3 = BoundingBox.ofPoints(err3.getRing().getVertices());
System.out.println("\nBoundingBox: " + Arrays.toString(bbox3.getBox()));
}
if (error instanceof DegeneratedRingError err4) {
System.out.println("\nVertices of DegeneratedRingError: " + err4.getRing().getVertices());
BoundingBox bbox4 = BoundingBox.ofPoints(err4.getRing().getVertices());
System.out.println("\nBoundingBox: " + Arrays.toString(bbox4.getBox()));
}
}
// //Get BoundingBoxes out of Vertices
// // Test: edge from vertex v1 to vertex v2
// List<Vector3d> v3d = new ArrayList<>();
// Vector3d v1 = new Vector3d(375755.655, 5718919.166, 72.052);
// Vector3d v2 = new Vector3d(375755.655, 5718919.166, 67.772);
// v3d.add(v1);
// v3d.add(v2);
// System.out.println("\nVector Array: " + v3d);
// BoundingBox vbox = BoundingBox.ofPoints(v3d);
// System.out.println("\nBounding Box: " + Arrays.toString(vbox.getBox()));
//
// BoundingBox box = BoundingBox.of(new Vector3d[]{new Vector3d(375755.655, 5718919.166, 72.052), new Vector3d(375755.655, 5718919.166, 67.772)});
// System.out.println("\nBounding Box: " + Arrays.toString(box.getBox()));
//Other available informations for BoundingBox
// System.out.println("\nCenter of Bounding Box: " + box.getCenter());
// System.out.println("\nDiagonal of Bounding Box: " + box.getDiagonal());
// System.out.println("\nVolume of Bounding Box: " + box.getVolume());
// System.out.println("\nHeight of Bounding Box: " + box.getHeight());
// System.out.println("\nWidth of Bounding Box: " + box.getWidth());
}
}
\ No newline at end of file
package de.hft.stuttgart.citydoctor2.checks.bht;
import de.hft.stuttgart.citydoctor2.checks.aabb.*;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.parser.*;
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;
}
//*/
}
/*-
* 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.Set;
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.GeometrySelfIntersection;
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.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABBUtils;
/**
* Check for self intersecting solids
*
* @author Baris Numanoglu
*/
public class SolidSelfIntCheckAABB extends Check {
private static final List<CheckId> dependencies;
static {
ArrayList<CheckId> deps = new ArrayList<>();
dependencies = Collections.unmodifiableList(deps);
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_HOLE_OUTSIDE);
deps.add(CheckId.C_GE_P_INNER_RINGS_NESTED);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_ORIENTATION_RINGS_SAME);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
deps.add(CheckId.C_GE_S_MULTIPLE_CONNECTED_COMPONENTS);
deps.add(CheckId.C_GE_S_NON_MANIFOLD_EDGE);
deps.add(CheckId.C_GE_S_NON_MANIFOLD_VERTEX);
deps.add(CheckId.C_GE_S_POLYGON_WRONG_ORIENTATION);
}
@Override
public void check(Geometry g) {
if (g.getType() != GeometryType.SOLID && g.getType() != GeometryType.COMPOSITE_SURFACE) {
return;
}
// --- cheap broad-phase using per-polygon AABBs ---
List<Polygon> polys = g.getPolygons();
if (polys == null || polys.size() <= 1) {
g.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
return;
}
// Build AABBs once
AABB[] boxes = new AABB[polys.size()];
for (int i = 0; i < polys.size(); i++) {
ConcretePolygon cp = polys.get(i).getOriginal();
boxes[i] = AABBUtils.getAABB(cp);
}
// If no AABB pair overlaps, self-intersection is impossible
if (!anyOverlap(boxes)) {
g.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
return;
}
// --- ---
CheckResult cr;
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g);
if (intersections.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError e = new SolidSelfIntError(g, intersections);
cr = new CheckResult(this, ResultStatus.ERROR, e);
}
g.addCheckResult(cr);
}
@SuppressWarnings("unused")
private CheckResult oldIntersectionAlgorithm(Geometry g) {
CheckResult cr;
GeometrySelfIntersection intersect = SelfIntersectionUtil.doesSolidSelfIntersect(g);
if (intersect != null) {
cr = new CheckResult(this, ResultStatus.ERROR, null);
} else {
cr = new CheckResult(this, ResultStatus.OK, null);
}
return cr;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public Set<Requirement> appliesToRequirements() {
return CollectionUtils.singletonSet(Requirement.R_GE_S_SELF_INTERSECTION);
}
@Override
public RequirementType getType() {
return RequirementType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new SolidSelfIntCheckAABB();
}
@Override
public CheckId getCheckId() {
return CheckId.C_GE_S_SELF_INTERSECTION;
}
/* ---------------- AABB helpers (broad-phase) ---------------- */
/** Returns true if there exists any overlapping AABB pair. */
private static boolean anyOverlap(AABB[] boxes) {
for (int i = 0; i < boxes.length - 1; i++) {
AABB a = boxes[i];
for (int j = i + 1; j < boxes.length; j++) {
AABB b = boxes[j];
if (overlaps(a, b)) return true;
}
}
return false;
}
/** Helper: AABB overlap test. */
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;
/**
* Represents a CityGML test model with metadata for easier handling in tests.
*/
public class TestModel {
/** A short unique ID (e.g. "PO_TWO_INNERS", "SO_NOT_WATERTIGHT"). */
private final String id;
/** Path to the GML file (classpath or relative path under src/test/resources). */
private final String resourcePath;
/** Human-readable description of the test case. */
private final String description;
/** Category of test (e.g. "PO" = polygon, "SO" = solid, "LR" = linear ring). */
private final String category;
/** Optional: Author or source of the model (if useful for tracing). */
private final String source;
/** Optional: Known type of geometry error (e.g. "ring not closed", "not watertight"). */
private final String errorType;
// --- Constructor ---
public TestModel(String id, String resourcePath, String description,
String category, String source, String errorType) {
this.id = id;
this.resourcePath = resourcePath;
this.description = description;
this.category = category;
this.source = source;
this.errorType = errorType;
}
// --- Getters ---
public String getId() { return id; }
public String getResourcePath() { return resourcePath; }
public String getDescription() { return description; }
public String getCategory() { return category; }
public String getSource() { return source; }
public String getErrorType() { return errorType; }
@Override
public String toString() {
return "[" + id + "] " + description + " (" + resourcePath + ")";
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
/**
* Represents a CityGML test model with metadata for easier handling in tests.
*/
public class TestModel {
/** A short unique ID (e.g. "PO_TWO_INNERS", "SO_NOT_WATERTIGHT"). */
private final String id;
/** Path to the GML file (classpath or relative path under src/test/resources). */
private final String resourcePath;
/** Human-readable description of the test case. */
private final String description;
/** Category of test (e.g. "PO" = polygon, "SO" = solid, "LR" = linear ring). */
private final String category;
/** Optional: Author or source of the model (if useful for tracing). */
private final String source;
/** Optional: Known type of geometry error (e.g. "ring not closed", "not watertight"). */
private final String errorType;
// --- Constructor ---
public TestModel(String id, String resourcePath, String description,
String category, String source, String errorType) {
this.id = id;
this.resourcePath = resourcePath;
this.description = description;
this.category = category;
this.source = source;
this.errorType = errorType;
}
// --- Getters ---
public String getId() { return id; }
public String getResourcePath() { return resourcePath; }
public String getDescription() { return description; }
public String getCategory() { return category; }
public String getSource() { return source; }
public String getErrorType() { return errorType; }
@Override
public String toString() {
return "[" + id + "] " + description + " (" + resourcePath + ")";
}
}
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