Commit d3605f6a authored by Numanoglu's avatar Numanoglu
Browse files

remove metadata

parent e087346b
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<section name="Workbench">
<section name="ResizableDialogBounds">
<item key="x" value="560"/>
<item key="y" value="115"/>
<item key="width" value="800"/>
<item key="height" value="600"/>
</section>
</section>
package de.hft.stuttgart.citydoctor2.datastructure.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
/*-
* 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;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABBUtils;
/**
* Checks whether a ring self intersects. Also checks if a point is too close to
* an edge of the ring.
*
* @author Matthias Betz
* @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.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.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;
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 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;
}
}
/*-
* 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 (AABBUtils.overlaps(a, b)) return true;
}
}
return false;
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
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;
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.
* This class avoids JUnit and 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.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.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";
/**!!!! 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";
/**!!!! 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";
/**!!!! 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";
/**!!!! 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";
/**!!!! 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";
/**!!!! 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";
/** !!!! 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";
/**!!!! 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
// ==================================================
/**!!!! 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";
/**!!!! 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";
/** !!!! 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";
/**!!!! 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";
//
/** !!!! 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.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.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.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;
}
/* ---------------- AABB helpers (broad-phase) ---------------- */
/** Returns true if 'outer' fully contains 'inner' (inclusive) in axis-aligned sense. */
private 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();
}
}
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