Commit d3605f6a authored by Numanoglu's avatar Numanoglu
Browse files

remove metadata

parent e087346b
This source diff could not be displayed because it is too large. You can view the blob instead.
<?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