Commit 5d40c7b6 authored by Matthias Betz's avatar Matthias Betz
Browse files

CityDoctor2 validation open source release

parents
/*-
* 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.math;
/**
* A projected two dimensional vector with refernce to the original 3d vector
*
* @author Matthias Betz
*
*/
public class ProjectedVector2d extends Vector2d {
private Vector3d original;
private boolean reflexive;
private double angle;
public ProjectedVector2d(double x, double y, Vector3d original) {
super(x, y);
this.original = original;
}
public Vector3d getOriginal() {
return original;
}
public void setOriginal(Vector3d original) {
this.original = original;
}
public boolean isReflexive() {
return reflexive;
}
public void setReflexive(boolean reflexive) {
this.reflexive = reflexive;
}
public void setAngle(double angle) {
this.angle = angle;
}
public double getAngle() {
return angle;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(angle);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((original == null) ? 0 : original.hashCode());
result = prime * result + (reflexive ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ProjectedVector2d other = (ProjectedVector2d) obj;
if (Double.doubleToLongBits(angle) != Double.doubleToLongBits(other.angle)) {
return false;
}
if (original == null) {
if (other.original != null) {
return false;
}
} else if (!original.equals(other.original)) {
return false;
}
return reflexive == other.reflexive;
}
}
/*-
* 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.math;
/**
* A Quaternion class storing a quaternion.
*
* @author Matthias Betz
*
*/
public class Quaternion {
private static final double PI2 = Math.PI * 2d;
private double x;
private double y;
private double z;
private double w;
private static final Quaternion IDENTITY = new Quaternion(0d, 0d, 0d, 1d);
/**
* Create a new quaternion containing the rotation necessary to rotate the from
* vector to the to vector
*
* @param from the from vector
* @param to the to vector
* @return a Quaternion containing the rotation
*/
public static Quaternion fromToRotation(Vector3d from, Vector3d to) {
double dot = from.dot(to);
if (dot > 1d) {
dot = 1d;
} else if (dot < -1d) {
dot = -1d;
}
double angle = Math.acos(dot);
double x = from.getY() * to.getZ() - from.getZ() * to.getY();
double y = from.getZ() * to.getX() - from.getX() * to.getZ();
double z = from.getX() * to.getY() - from.getY() * to.getX();
double d = Math.sqrt(x * x + y * y + z * z);
if (d == 0d) {
return IDENTITY;
}
d = 1d / d;
double ang;
if (angle < 0) {
ang = PI2 - (-angle % PI2);
} else {
ang = angle % PI2;
}
ang = ang / 2d;
double sin = Math.sin(ang);
double cos = Math.cos(ang);
Quaternion quad = new Quaternion(d * x * sin, d * y * sin, d * z * sin, cos);
quad.normalize();
return quad;
}
private Quaternion(double x, double y, double z, double w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/**
*
* @return the square length of the quaternion
*/
public double squareLength() {
return x * x + y * y + z * z + w * w;
}
/**
* Normalizes the quaternion by dividing with the length.
*/
public void normalize() {
double length = squareLength();
if (length != 0d && length != 1d) {
length = Math.sqrt(length);
x /= length;
y /= length;
z /= length;
w /= length;
}
}
/**
* Multiply this * other, stored in this, no new instances created
*
* @param other the other Quaternion
* @return this * other
*/
public Quaternion mult(Quaternion other) {
final double newX = w * other.x + x * other.w + y * other.z - z * other.y;
final double newY = w * other.y + y * other.w + z * other.x - x * other.z;
final double newZ = w * other.z + z * other.w + x * other.y - y * other.x;
final double newW = w * other.w - x * other.x - y * other.y - z * other.z;
this.x = newX;
this.y = newY;
this.z = newZ;
this.w = newW;
return this;
}
/**
* Converts the quaternion to a rotation matrix which is faster for multiplying
* with vectors to apply rotations.
*
* @return a new 3x3 matrix containing the rotation described by this
* quaternion.
*/
public Matrix3x3d toRotationMatrix() {
double[][] rot = new double[3][3];
final double xx = x * x;
final double xy = x * y;
final double xz = x * z;
final double xw = x * w;
final double yy = y * y;
final double yz = y * z;
final double yw = y * w;
final double zz = z * z;
final double zw = z * w;
// Set matrix from quaternion
rot[0][0] = 1 - 2 * (yy + zz);
rot[0][1] = 2 * (xy - zw);
rot[0][2] = 2 * (xz + yw);
rot[1][0] = 2 * (xy + zw);
rot[1][1] = 1 - 2 * (xx + zz);
rot[1][2] = 2 * (yz - xw);
rot[2][0] = 2 * (xz - yw);
rot[2][1] = 2 * (yz + xw);
rot[2][2] = 1 - 2 * (xx + yy);
return new Matrix3x3d(rot);
}
}
/*-
* 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.math;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import de.hft.stuttgart.citydoctor2.tesselation.JoglTesselator;
/**
* A ray constructed by a point and a direction. A ray has a starting point and
* a direction, it does not go into the opposite direction unlike a line.
*
* @author Matthias Betz
*
*/
public class Ray {
public static final double EPSILON = 0.0001;
private Vector3d origin;
private Vector3d direction;
public Ray(Vector3d origin, Vector3d direction) {
this.origin = origin;
this.direction = direction.normalize();
}
public Vector3d getPointOnRay(double distanceFromOrigin) {
return origin.plus(direction.mult(distanceFromOrigin));
}
public Vector3d intersectsPolygon(TesselatedPolygon p) {
for (Triangle3d t : p.getTriangles()) {
Vector3d intersection = intersectTriangle(t);
if (intersection != null) {
return intersection;
}
}
return null;
}
public Vector3d intersectTriangle(Triangle3d t) {
return intersectTriangle(t.getP1(), t.getP2(), t.getP3());
}
public Vector3d intersectTriangle(Vector3d v0, Vector3d v1, Vector3d v2) {
Vector3d edge1 = v1.minus(v0);
Vector3d edge2 = v2.minus(v0);
Vector3d p = direction.cross(edge2);
double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) {
return null;
}
double invDet = 1 / det;
Vector3d s = origin.minus(v0);
double u = invDet * s.dot(p);
if (u < 0.0 || u > 1.0) {
return null;
}
Vector3d q = s.cross(edge1);
double v = invDet * direction.dot(q);
if (v < 0.0 || u + v > 1.0) {
return null;
}
double t = edge2.dot(q) * invDet;
if (t > EPSILON) {
// ray intersection
return getPointOnRay(t);
} else {
// line intersection, but not ray intersection
return null;
}
}
public Vector3d intersectsPolygon(Polygon p) {
return intersectsPolygon(JoglTesselator.tesselatePolygon(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.math;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
/**
* A two dimensional ring containing the 2d vertices
*
* @author Matthias Betz
*
*/
public class Ring2d {
private List<Vector2d> ringVertices;
private LinearRing original;
public Ring2d(List<Vector2d> ringVertices, LinearRing original) {
this.ringVertices = ringVertices;
this.original = original;
}
public List<Vector2d> getVertices() {
return ringVertices;
}
public LinearRing getOriginal() {
return original;
}
}
/*-
* 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.math;
/**
* This class represents a line from a point to another point, it can be used to
* calculate the intersection of two edges
*
* @author Matthias Betz
*
*/
public class Segment2d {
private static final double EPSILON = 0.01;
private Vector2d p1;
private Vector2d p2;
public Segment2d(Vector2d p1, Vector2d p2) {
this.p1 = p1;
this.p2 = p2;
}
public boolean intersects(Segment2d other) {
double x1 = p1.getX();
double x2 = p2.getX();
double x3 = other.p1.getX();
double x4 = other.p2.getX();
double y1 = p1.getY();
double y2 = p2.getY();
double y3 = other.getP1().getY();
double y4 = other.getP2().getY();
double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
double numeratora = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
double numeratorb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
if (Math.abs(denominator) < EPSILON) {
denominator = 0.0;
}
if (denominator == 0) {
// both numerators == 0 means, lines are coincident, otherwise parallel
if (numeratora == 0 && numeratorb == 0) {
// check if they are colinear
return containsPoint(other.p1) || containsPoint(other.p2);
} else {
// parallel
return false;
}
} else {
double ua = numeratora / denominator;
double ub = numeratorb / denominator;
return ua > EPSILON && ua < (1 - EPSILON) && ub > EPSILON && ub < (1 - EPSILON);
}
}
public boolean intersectsWithoutColinearity(Segment2d other) {
double x1 = p1.getX();
double x2 = p2.getX();
double x3 = other.p1.getX();
double x4 = other.p2.getX();
double y1 = p1.getY();
double y2 = p2.getY();
double y3 = other.getP1().getY();
double y4 = other.getP2().getY();
double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (Math.abs(denominator) < EPSILON) {
denominator = 0.0;
}
if (denominator == 0) {
// both numerators == 0 means, lines are coincident, otherwise parallel
// parallel lines are not intersecting
return false;
} else {
double numeratora = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
double numeratorb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
double ua = numeratora / denominator;
double ub = numeratorb / denominator;
return ua > EPSILON && ua < (1 - EPSILON) && ub > EPSILON && ub < (1 - EPSILON);
}
}
public Vector2d intersectionPoint(Segment2d other) {
IntersectionPoint2d inter = calculateIntersection(other);
if (inter == null) {
return null;
}
return inter.getIntersectionPoint();
}
public IntersectionPoint2d calculateIntersection(Segment2d other) {
double x1 = p1.getX();
double x2 = p2.getX();
double x3 = other.p1.getX();
double x4 = other.p2.getX();
double y1 = p1.getY();
double y2 = p2.getY();
double y3 = other.getP1().getY();
double y4 = other.getP2().getY();
double a1 = x2 - x1;
double b1 = x4 - x3;
double c1 = x3 - x1;
double a2 = y2 - y1;
double b2 = y4 - y3;
double c2 = y3 - y1;
double detA = b1 * a2 - a1 * b2;
double detA1 = b1 * c2 - c1 * b2;
double detA2 = a1 * c2 - c1 * a2;
if (detA == 0) {
// lines parallel or coincident
if (detA1 == 0 && detA2 == 0) {
// check if they are colinear
if (containsPoint(other.p1)) {
return new IntersectionPoint2d(new Line2d(other.p1, new Vector2d()), 0);
}
if (containsPoint(other.p2)) {
return new IntersectionPoint2d(new Line2d(other.p2, new Vector2d()), 0);
}
return null;
} else {
// parallel
return null;
}
}
double s = detA1 / detA;
double t = detA2 / detA;
if (s < 0 || s > 1 || t < 0 || t > 1) {
// intersection on line but not on segment
return null;
}
Vector2d dir = new Vector2d(a1, a2);
Vector2d start = new Vector2d(x1, y1);
return new IntersectionPoint2d(new Line2d(start, dir), s);
}
public boolean containsPoint(Vector2d p) {
double xDir = p2.getX() - p1.getX();
if (xDir == 0) {
double yDir = p2.getY() - p1.getY();
if (yDir == 0) {
// line is one point
return p.equals(p1);
}
double a = (p.getY() - p1.getY()) / yDir;
return a < (1 - EPSILON) && a > EPSILON;
}
double a = (p.getX() - p1.getX()) / xDir;
if (a < EPSILON || a > (1 - EPSILON)) {
return false;
}
// direction in y
double yDir = p2.getY() - p1.getY();
// check for colinearity
return p1.getY() + a * yDir == p.getY();
}
public double distance(Vector2d p) {
double x1 = p1.getX();
double x2 = p2.getX();
double x3 = p.getX();
double y1 = p1.getY();
double y2 = p2.getY();
double y3 = p.getY();
Vector2d v = p2.minus(p1);
double denominator = v.lengthSquare();
if (denominator == 0) {
// segment has no length, distance is distance to end/begin point
v = p.minus(p1);
return v.length();
}
double numerator = (x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1);
double u = numerator / denominator;
if (u < 0) {
u = 0;
} else if (u > 1) {
u = 1;
}
Vector2d distanceVec = p.minus(p1.plus(v.mult(u)));
return distanceVec.length();
}
public Vector2d getP1() {
return p1;
}
public Vector2d getP2() {
return p2;
}
}
/*-
* 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.math;
import java.io.Serializable;
/**
* A bounded line between two points
*
* @author Matthias Betz
*
*/
public class Segment3d implements Serializable {
private static final long serialVersionUID = -506306945169934099L;
private static final double PRECISION = 0.00000001;
private static final double EPSILON = 0.01;
private final Vector3d pointA;
private final Vector3d pointB;
public Segment3d(Vector3d pointA, Vector3d pointB) {
this.pointA = pointA;
this.pointB = pointB;
}
/**
* calculates the distance from this segment to the other segment
*
* @param otherSeg the other segment
* @return the distance
*/
public double getDistance(Segment3d otherSeg) {
return getDistanceResult(otherSeg).getDistance();
}
public DistanceResult getDistanceResult(Segment3d other) {
// calculate direction vectors and normalize them
Vector3d u = pointB.minus(pointA);
Vector3d v = other.getPointB().minus(other.getPointA());
Vector3d w = pointA.minus(other.getPointA());
double a = u.dot(u);
double b = u.dot(v);
double c = v.dot(v);
double d = u.dot(w);
double e = v.dot(w);
double f = a * c - b * b;
double sc;
double sN;
double sD = f;
double tc;
double tN;
double tD = f;
if (f < PRECISION) {
sN = 0.0;
sD = 1.0;
tN = e;
tD = c;
} else {
sN = b * e - c * d;
tN = a * e - b * d;
if (sN < 0.0) {
sN = 0.0;
tN = e;
tD = c;
} else if (sN > sD) {
sN = sD;
tN = e + b;
tD = c;
}
}
if (tN < 0.0) {
tN = 0.0;
if (-d < 0.0) {
sN = 0.0;
} else if (-d > a) {
sN = sD;
} else {
sN = -d;
sD = a;
}
} else if (tN > tD) {
tN = tD;
if ((-d + b) < 0.0) {
sN = 0;
} else if ((-d + e > a)) {
sN = sD;
} else {
sN = (-d + b);
sD = a;
}
}
sc = getSc(sN, sD);
tc = getSc(tN, tD);
Vector3d us = u.mult(sc);
Vector3d ut = v.mult(tc);
Vector3d point1 = pointA.plus(us);
Vector3d point2 = other.pointA.plus(ut);
Vector3d dP = w.plus(us.minus(ut));
double length = dP.getLength();
return new DistanceResult(point1, point2, length);
}
private double getSc(double sN, double sD) {
double sc;
if (Math.abs(sN) < PRECISION) {
sc = 0.0;
} else {
sc = sN / sD;
}
return sc;
}
public Vector3d getPointA() {
return pointA;
}
public Vector3d getPointB() {
return pointB;
}
public double getDistance(Vector3d v) {
Vector3d ab = pointB.minus(pointA);
Vector3d av = v.minus(pointA);
if (av.dot(ab) <= 0.0) {
return av.getLength();
}
Vector3d bv = v.minus(pointB);
if (bv.dot(ab) >= 0.0) {
return bv.getLength();
}
Vector3d cross = ab.cross(av);
return cross.getLength() / ab.getLength();
}
public Vector3d getNormalThroughPoint(Vector3d p) {
Vector3d dir = p.minus(pointA);
Vector3d lineDir = pointB.minus(pointA);
double lineLengthSquare = lineDir.getSquaredLength();
double scalar = dir.dot(lineDir) / lineLengthSquare;
Vector3d projectedPoint = (lineDir.mult(scalar)).plus(pointA);
return p.minus(projectedPoint);
}
@Override
public String toString() {
return "Segment3d [pointA=" + pointA + ", pointB=" + pointB + "]";
}
public Vector3d intersection(Triangle3d triangle) {
Vector3d v0 = triangle.getP1();
Vector3d v1 = triangle.getP2();
Vector3d v2 = triangle.getP3();
Vector3d edge1 = v1.minus(v0);
Vector3d edge2 = v2.minus(v0);
Vector3d direction = pointB.minus(pointA);
Vector3d p = direction.cross(edge2);
double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) {
return null;
}
double invDet = 1 / det;
Vector3d s = pointA.minus(v0);
double u = invDet * s.dot(p);
if (u < EPSILON || u > (1.0 - EPSILON)) {
return null;
}
Vector3d q = s.cross(edge1);
double v = invDet * direction.dot(q);
if (v < 0.0 || u + v > 1.0) {
return null;
}
double t = edge2.dot(q) * invDet;
if (t > EPSILON && t < 1 - EPSILON) {
// t is in segment
return pointA.plus(direction.mult(t));
}
return null;
}
}
/*-
* 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.math;
/**
* A two dimensional triangle
*
* @author Matthias Betz
*
*/
public class Triangle2d {
private Vector2d p1;
private Vector2d p2;
private Vector2d p3;
private static final double EPSILON = 0;
public Triangle2d(Vector2d p1, Vector2d p2, Vector2d p3) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
public Vector2d getP1() {
return p1;
}
public Vector2d getP2() {
return p2;
}
public Vector2d getP3() {
return p3;
}
public boolean containsPoint(Vector2d point) {
return containsPoint(point, EPSILON);
}
public boolean containsPoint(Vector2d point, double epsilon) {
double a = (p2.getY() - point.getY()) * (p1.getX() - point.getX())
- (p1.getY() - point.getY()) * (p2.getX() - point.getX());
double b = (p3.getY() - point.getY()) * (p1.getX() - point.getX())
- (p1.getY() - point.getY()) * (p3.getX() - point.getX());
if (a * b >= -epsilon) {
return false;
}
double c = (p1.getY() - point.getY()) * (p2.getX() - point.getX())
- (p2.getY() - point.getY()) * (p1.getX() - point.getX());
double d = (p3.getY() - point.getY()) * (p2.getX() - point.getX())
- (p2.getY() - point.getY()) * (p3.getX() - point.getX());
return (c * d < epsilon);
}
public boolean hasCornerPoint(Vector2d p) {
return p.equals(p1) || p.equals(p2) || p.equals(p3);
}
public boolean intersects(Triangle2d other) {
if (hasCornerPoint(other.p1) && hasCornerPoint(other.p2) && hasCornerPoint(other.p3)) {
// same triangle
return true;
}
Segment2d s1T1 = new Segment2d(p1, p2);
Segment2d s2T1 = new Segment2d(p1, p3);
Segment2d s3T1 = new Segment2d(p2, p3);
Segment2d s1T2 = new Segment2d(other.p1, other.p2);
Segment2d s2T2 = new Segment2d(other.p1, other.p3);
Segment2d s3T2 = new Segment2d(other.p2, other.p3);
return s1T1.intersectsWithoutColinearity(s1T2) || s1T1.intersectsWithoutColinearity(s2T2)
|| s1T1.intersectsWithoutColinearity(s3T2) || s2T1.intersectsWithoutColinearity(s1T2)
|| s2T1.intersectsWithoutColinearity(s2T2) || s2T1.intersectsWithoutColinearity(s3T2)
|| s3T1.intersectsWithoutColinearity(s1T2) || s3T1.intersectsWithoutColinearity(s2T2)
|| s3T1.intersectsWithoutColinearity(s3T2) || containsPoint(other.p1) || other.containsPoint(p1);
}
}
/*-
* 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.math;
import java.io.Serializable;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
/**
* A three dimensional triangle described by three points
*
* @author Matthias Betz
*
*/
public class Triangle3d implements Serializable {
private static final long serialVersionUID = -6907333357794272435L;
private static final double EPSILON = 0.0001;
private Vector3d p1;
private Vector3d p2;
private Vector3d p3;
private TesselatedPolygon partOf;
public Triangle3d(Vector3d p1, Vector3d p2, Vector3d p3) {
this(p1, p2, p3, null);
}
public Triangle3d(Vector3d p1, Vector3d p2, Vector3d p3, TesselatedPolygon partOf) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.partOf = partOf;
}
public TesselatedPolygon getPartOf() {
return partOf;
}
public Vector3d getNormal() {
Vector3d side1 = p3.minus(p1);
Vector3d side2 = p2.minus(p1);
return side1.cross(side2);
}
public Vector3d getCentroid() {
double meanX = (p1.getX() + p2.getX() + p3.getX()) / 3d;
double meanY = (p1.getY() + p2.getY() + p3.getY()) / 3d;
double meanZ = (p1.getZ() + p2.getZ() + p3.getZ()) / 3d;
return new Vector3d(meanX, meanY, meanZ);
}
public double getArea() {
double a = p1.getDistance(p2);
double b = p2.getDistance(p3);
double c = p1.getDistance(p3);
double s = 0.5d * (a + b + c);
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
public boolean hasPointAsCorner(Vector3d p) {
return p1.equals(p) || p2.equals(p) || p3.equals(p);
}
public Plane getPlane() {
Vector3d v1 = p1.minus(p3);
Vector3d v2 = p2.minus(p3);
Vector3d n2 = v1.cross(v2);
return new Plane(n2, p1);
}
public boolean doesIntersect(Triangle3d other) {
// plane of other triangle
Plane planeT2 = other.getPlane();
// check if all points are on one side of the plane
double distanceP1T2 = planeT2.getSignedDistance(p1);
double distanceP2T2 = planeT2.getSignedDistance(p2);
double distanceP3T2 = planeT2.getSignedDistance(p3);
if (Math.abs(distanceP1T2) < EPSILON) {
distanceP1T2 = 0.0;
}
if (Math.abs(distanceP2T2) < EPSILON) {
distanceP2T2 = 0.0;
}
if (Math.abs(distanceP3T2) < EPSILON) {
distanceP3T2 = 0.0;
}
if ((distanceP1T2 * distanceP2T2 >= 0.0) && (distanceP1T2 * distanceP3T2 >= 0.0)) {
if (distanceP1T2 == 0.0 && distanceP2T2 == 0.0 && distanceP3T2 == 0.0) {
return doesIntersectCoplanarTriangle(other);
} else {
// points are not coplanar
// all points are on one side -> no intersection possible
return false;
}
}
// plane of this triangle
Plane planeT1 = getPlane();
// check if all points are on one side of the plane
double distanceP1T1 = planeT1.getSignedDistance(other.getP1());
double distanceP2T1 = planeT1.getSignedDistance(other.getP2());
double distanceP3T1 = planeT1.getSignedDistance(other.getP3());
if (Math.abs(distanceP1T1) < EPSILON) {
distanceP1T1 = 0.0;
}
if (Math.abs(distanceP2T1) < EPSILON) {
distanceP2T1 = 0.0;
}
if (Math.abs(distanceP3T1) < EPSILON) {
distanceP3T1 = 0.0;
}
if (((distanceP1T1 * distanceP2T1) >= 0.0) && ((distanceP1T1 * distanceP3T1) >= 0.0)) {
// all points are on one side -> no intersection possible
// coplanar check not necessary, already done previously
return false;
}
return checkTriangleLineIntersection(other, p1, p2) || checkTriangleLineIntersection(other, p1, p3)
|| checkTriangleLineIntersection(other, p2, p3)
|| checkTriangleLineIntersection(this, other.p1, other.p2)
|| checkTriangleLineIntersection(this, other.p1, other.p3)
|| checkTriangleLineIntersection(this, other.p2, other.p3);
}
private boolean checkTriangleLineIntersection(Triangle3d other, Vector3d a, Vector3d b) {
Segment3d seg = new Segment3d(a, b);
Vector3d intersection1 = seg.intersection(other);
return intersection1 != null;
}
public boolean containsPoint(Vector3d point) {
Vector3d a = p1;
Vector3d b = p2;
Vector3d c = p3;
Vector3d p = point;
return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b);
}
private boolean sameSide(Vector3d p1, Vector3d p2, Vector3d a, Vector3d b) {
Vector3d dir = b.minus(a);
Vector3d test1 = p1.minus(a);
Vector3d test2 = p2.minus(a);
Vector3d c1 = dir.cross(test1);
Vector3d c2 = dir.cross(test2);
return c1.dot(c2) > 0;
}
private boolean doesIntersectCoplanarTriangle(Triangle3d other) {
Triangle2d t1 = projectTo2d();
Triangle2d t2 = other.projectTo2d();
return t1.intersects(t2);
}
public Triangle2d projectTo2d() {
Vector3d normal = getNormal();
double x = Math.abs(normal.getX());
double y = Math.abs(normal.getY());
double z = Math.abs(normal.getZ());
if (x > y && x > z) {
// x is the largest value -> project to y-z plane
Vector2d a = new Vector2d(p1.getY(), p1.getZ());
Vector2d b = new Vector2d(p2.getY(), p2.getZ());
Vector2d c = new Vector2d(p3.getY(), p3.getZ());
return new Triangle2d(a, b, c);
} else if (y > x && y > z) {
// y is the largest value -> project to x-z plane
Vector2d a = new Vector2d(p1.getX(), p1.getZ());
Vector2d b = new Vector2d(p2.getX(), p2.getZ());
Vector2d c = new Vector2d(p3.getX(), p3.getZ());
return new Triangle2d(a, b, c);
} else {
// z is the largest value -> project to x-y plane
Vector2d a = new Vector2d(p1.getX(), p1.getY());
Vector2d b = new Vector2d(p2.getX(), p2.getY());
Vector2d c = new Vector2d(p3.getX(), p3.getY());
return new Triangle2d(a, b, c);
}
}
public Vector3d getP1() {
return p1;
}
public Vector3d getP2() {
return p2;
}
public Vector3d getP3() {
return p3;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((p1 == null) ? 0 : p1.hashCode());
result = prime * result + ((p2 == null) ? 0 : p2.hashCode());
result = prime * result + ((p3 == null) ? 0 : p3.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Triangle3d other = (Triangle3d) obj;
if (p1 == null) {
if (other.p1 != null) {
return false;
}
} else if (!p1.equals(other.p1)) {
return false;
}
if (p2 == null) {
if (other.p2 != null) {
return false;
}
} else if (!p2.equals(other.p2)) {
return false;
}
if (p3 == null) {
if (other.p3 != null) {
return false;
}
} else if (!p3.equals(other.p3)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Triangle3d [p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + "]";
}
public void setPartOf(TesselatedPolygon p) {
partOf = 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.math;
/**
* A two dimensional vector
*
* @author Matthias Betz
*
*/
public class Vector2d {
private double x;
private double y;
public Vector2d() {
this(0, 0);
}
public Vector2d(double x, double y) {
this.x = x;
this.y = y;
}
public Vector2d minus(Vector2d other) {
return new Vector2d(x - other.x, y - other.y);
}
public Vector2d mult(double scalar) {
return new Vector2d(scalar * x, scalar * y);
}
public Vector2d plus(Vector2d other) {
return new Vector2d(x + other.x, y + other.y);
}
public double length() {
return Math.sqrt(lengthSquare());
}
public double lengthSquare() {
return x * x + y * y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
/**
* Checks whether each coordinate is within epsilon distance, does NOT use
* euclidean distance.
*
* @param other the other vector
* @param epsilon the tolerance
* @return true if the coordinates are within epsilon distance, false otherwise
*/
public boolean equalsWithinEpsilon(Vector2d other, double epsilon) {
if (Math.abs(other.x - x) > epsilon) {
return false;
}
return Math.abs(other.y - y) <= epsilon;
}
@Override
public String toString() {
return "Vector2d [x=" + x + ", y=" + y + "]";
}
public double dot(Vector2d other) {
return x * other.x + y * other.y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Vector2d other = (Vector2d) obj;
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) {
return false;
}
return Double.doubleToLongBits(y) == Double.doubleToLongBits(other.y);
}
public void normalize() {
double l = length();
x = x / l;
y = y / l;
}
public double cross(Vector2d other) {
return x * other.getY() - other.getX() * y;
}
}
/*-
* 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.math;
import java.io.Serializable;
import java.util.Arrays;
/**
* A three dimensional vector
*
* @author Matthias Betz
*
*/
public class Vector3d implements Serializable {
private static final long serialVersionUID = 3495650092142761365L;
private static final Vector3d ORIGIN = new Vector3d();
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
private double[] coords;
public Vector3d() {
this(0d, 0d, 0d);
}
public Vector3d(double x, double y, double z) {
coords = new double[3];
coords[0] = x;
coords[1] = y;
coords[2] = z;
}
public Vector3d(double[] coords) {
if (coords == null) {
throw new IllegalArgumentException("Coordinates can not be null");
}
this.coords = coords;
}
public Vector3d(Vector3d vec) {
coords = new double[3];
coords[0] = vec.getX();
coords[1] = vec.getY();
coords[2] = vec.getZ();
}
public double getX() {
return coords[0];
}
public double getY() {
return coords[1];
}
public double getZ() {
return coords[2];
}
public void setX(double x) {
coords[0] = x;
}
public void setY(double y) {
coords[1] = y;
}
public void setZ(double z) {
coords[2] = z;
}
/**
* Getter for all coordinates. The changes made to this array are reflected in
* this vector.
*
* @return the coordiantes array
*/
public double[] getCoordinates() {
return coords;
}
public void setCoordinates(double[] coordiantes) {
if (coordiantes == null || coordiantes.length != 3) {
throw new IllegalArgumentException("Vector must have exactly 3 coordinates");
}
this.coords = coordiantes;
}
public double getLength() {
return getDistance(ORIGIN);
}
public double getSquaredLength() {
double x = coords[0];
double y = coords[1];
double z = coords[2];
return x * x + y * y + z * z;
}
public double getDistance(Vector3d other) {
return Math.sqrt(getDistanceSquare(other));
}
public double getDistanceSquare(Vector3d other) {
double x = coords[0] - other.getX();
double y = coords[1] - other.getY();
double z = coords[2] - other.getZ();
return x * x + y * y + z * z;
}
/**
* adds another vector to this one. The result is
* <code><br>(a1 + b1)<br>(a2 + b2)<br>(a3 + b3)</code>
*
* @param b the other vector
* @return the sum of both vectors as a new vector. Values in this instance
* remain unchanged.
*/
public Vector3d plus(Vector3d b) {
double x = coords[0] + b.getX();
double y = coords[1] + b.getY();
double z = coords[2] + b.getZ();
return new Vector3d(x, y, z);
}
/**
* add a value to a coordinate. This will change the coordinates of this vector.
*
* @param coordinateIndex one of <code>Vector3d.X, Vector3d.Y, Vector3d.Z</code>
* @param value the added value
* @return the current instance for chaining commands
*/
public Vector3d plus(int coordinateIndex, double value) {
if (coordinateIndex < 0 || coordinateIndex > 2) {
throw new IllegalArgumentException("coordinateIndex has to be between 0 and 2");
}
coords[coordinateIndex] += value;
return this;
}
/**
* subtract a value to a coordinate. This will change the coordinates of this
* vector.
*
* @param coordinateIndex one of <code>Vector3d.X, Vector3d.Y, Vector3d.Z</code>
* @param value the subtracted value
* @return the current instance for chaining commands
*/
public Vector3d minus(int coordinateIndex, double value) {
if (coordinateIndex < 0 || coordinateIndex > 2) {
throw new IllegalArgumentException("coordinateIndex has to be between 0 and 2");
}
coords[coordinateIndex] -= value;
return this;
}
/**
* subtract another vector from this one. The result is
* <code><br>(a1 - b1)<br>(a2 - b2)<br>(a3 - b3)</code>
*
* @param b the other vector
* @return the result as a new vector. Values in this instance remain unchanged.
*/
public Vector3d minus(Vector3d b) {
double x = coords[0] - b.getX();
double y = coords[1] - b.getY();
double z = coords[2] - b.getZ();
return new Vector3d(x, y, z);
}
/**
* multiply this vector with a scalar.
*
* @param scalar the scalar
* @return the result as a new vector. Values in this instance remain unchanged.
*/
public Vector3d mult(double scalar) {
double x = coords[0] * scalar;
double y = coords[1] * scalar;
double z = coords[2] * scalar;
return new Vector3d(x, y, z);
}
/**
* normalizes this vector. This method changes the coordinates of this instance.
*/
public Vector3d normalize() {
double length = getLength();
// if the length is already 1, do nothing
final double epsilon = 0.0000001;
if (Math.abs(1 - length) < epsilon) {
return this;
}
coords[0] /= length;
coords[1] /= length;
coords[2] /= length;
return this;
}
/**
* calculates the cross product. The result is
* <code><br>(a2 * b3 - a3 * b2)<br>(a3 * b1 - a1 * b3)<br>(a1 * b2 - a2 * b1)</code>
*
* @param b the other vector
* @return the result as a new vector. Values in this instance remain unchanged.
*/
public Vector3d cross(Vector3d b) {
double x = coords[1] * b.getZ() - coords[2] * b.getY();
double y = coords[2] * b.getX() - coords[0] * b.getZ();
double z = coords[0] * b.getY() - coords[1] * b.getX();
return new Vector3d(x, y, z);
}
/**
* Calculates the dot product. The result is
* <code><br>(a1 * b1) + (a2 * b2) + (a3 * b3)<br></code>
*
* @param b the other vector
* @return the dot product
*/
public double dot(Vector3d b) {
return coords[0] * b.getX() + coords[1] * b.getY() + coords[2] * b.getZ();
}
/**
* creates a new copy of this vector.
*
* @return a copy of this vector
*/
public Vector3d copy() {
return new Vector3d(coords[0], coords[1], coords[2]);
}
@Override
public String toString() {
final int maxLen = 5;
StringBuilder builder = new StringBuilder();
builder.append("Vector3d [coords=");
builder.append(coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null);
builder.append("]");
return builder.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(coords);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Vector3d other = (Vector3d) obj;
return Arrays.equals(coords, other.coords);
}
/**
* Two vectors are equals, when the distance between both is less than epsilon
*
* @param other the other vector
* @param epsilon the distance where the vectors are considered the same
* @return true if they are within epsilon distance, false otherwise.
*/
public boolean equalsWithEpsilon(Vector3d other, double epsilon) {
if (other == this) {
return true;
}
Vector3d dif = this.minus(other);
double sqLength = dif.getSquaredLength();
return sqLength < (epsilon * epsilon);
}
public double getCoordinate(int axis) {
return coords[axis];
}
public void plus(double radius) {
coords[0] += radius;
coords[1] += radius;
coords[2] += radius;
}
}
/*-
* 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.math.graph;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
/**
* A graph used to detect cycles.
*
* @author Matthias Betz
*
*/
public class CycleGraph {
private final CycleNode root;
public CycleGraph(CycleNode root) {
this.root = root;
}
public List<List<LinearRing>> getConnectedComponents() {
AtomicInteger index = new AtomicInteger();
Deque<CycleNode> s = new LinkedList<>();
List<List<LinearRing>> components = new LinkedList<>();
Set<CycleNode> nodes = new HashSet<>();
root.collectNodes(nodes);
for (CycleNode node : nodes) {
if (node.getIndex() == -1) {
node.strongConnect(s, index, components);
}
}
return components;
}
public boolean hasMoreThan2CycleDepth() {
return root.hasMoreThan2CycleDepth(null);
}
}
/*-
* 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.math.graph;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
/**
* A node in the cycle graph
*
* @author Matthias Betz
*
*/
public class CycleNode {
private final LinearRing value;
private int index = -1;
private int lowlink = 0;
boolean onStack = false;
private boolean visited = false;
private Set<CycleNode> children;
public CycleNode(LinearRing value) {
children = new HashSet<>();
this.value = value;
}
public Set<CycleNode> getChildren() {
return children;
}
public LinearRing getValue() {
return value;
}
public boolean addChild(CycleNode node) {
return children.add(node);
}
int getIndex() {
return index;
}
void collectNodes(Set<CycleNode> nodes) {
if (nodes.add(this)) {
for (CycleNode node : children) {
node.collectNodes(nodes);
}
}
}
void strongConnect(Deque<CycleNode> s, AtomicInteger counter, List<List<LinearRing>> components) {
index = counter.get();
lowlink = counter.getAndIncrement();
s.push(this);
onStack = true;
for (CycleNode node : children) {
if (node.index == -1) {
node.strongConnect(s, counter, components);
lowlink = Math.min(lowlink, node.lowlink);
} else if (node.onStack) {
lowlink = Math.min(lowlink, node.index);
}
}
if (lowlink == index) {
List<LinearRing> comps = new LinkedList<>();
CycleNode w = null;
do {
w = s.pop();
w.onStack = false;
comps.add(w.getValue());
} while (this != w);
components.add(comps);
}
}
public boolean containsChild(CycleNode node) {
return children.contains(node);
}
public int countEdges(CycleNode parent) {
int edgeCount = 1;
visited = true;
for (CycleNode node : children) {
if (!node.visited && node != parent) {
edgeCount += node.countEdges(this);
}
}
return edgeCount;
}
public void resetVisit() {
visited = false;
for (CycleNode node : children) {
if (node.visited) {
node.resetVisit();
}
}
}
public boolean hasMoreThan2CycleDepth(CycleNode parent) {
visited = true;
boolean result = false;
for (CycleNode node : children) {
if (node.visited && node != parent) {
return true;
} else if (!node.visited) {
result = result || node.hasMoreThan2CycleDepth(this);
}
}
return result;
}
public List<LinearRing> getConnectedRings() {
List<LinearRing> result = new ArrayList<>();
getConnectedRings(result);
return result;
}
private void getConnectedRings(List<LinearRing> rings) {
visited = true;
rings.add(value);
for (CycleNode node : children) {
if (!node.visited) {
node.getConnectedRings(rings);
}
}
}
}
/*-
* 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.math.graph;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
/**
* KD-Tree implementation to filter out duplicate points
*
* @author Matthias Betz
*
*/
public class KDTree {
private static final int K = 3;
private Vertex location;
private int axis;
private KDTree left;
private KDTree right;
public KDTree() {
axis = 0;
}
public KDTree(Vertex median) {
this(median, 0);
}
private KDTree(Vertex median, int depth) {
location = median;
axis = depth % K;
}
public void add(Vertex node) {
if (location == null) {
// root node is empty, set as root
location = node;
return;
}
if (node.getCoordinate(axis) < location.getCoordinate(axis)) {
// left tree
if (left == null) {
// insert node
left = new KDTree(node, axis + 1);
} else {
// search next tree
left.add(node);
}
} else {
// right tree
if (right == null) {
// insert node
right = new KDTree(node, axis + 1);
} else {
// search next tree
right.add(node);
}
}
}
public Vertex getFirstNodeInRadius(Vertex node, double radius) {
// construct box:
Vector3d leftBotFront = node.copy();
leftBotFront.plus(-radius);
Vector3d topRightBehind = node.copy();
topRightBehind.plus(radius);
double radiusSquare = radius * radius;
for (Vertex point : getNodesInRange(leftBotFront, topRightBehind, new ArrayList<>())) {
if (point.getDistanceSquare(node) <= radiusSquare) {
return point;
}
}
return null;
}
public List<Vertex> getNodesInRange(Vertex node, double radius) {
// construct box:
Vector3d leftBotFront = node.copy();
leftBotFront.plus(-radius);
Vector3d topRightBehind = node.copy();
topRightBehind.plus(radius);
List<Vertex> nodesInRangeBox = getNodesInRange(leftBotFront, topRightBehind, new ArrayList<>());
List<Vertex> nodesInRange = new ArrayList<>();
double radiusSquare = radius * radius;
for (Vertex v : nodesInRangeBox) {
if (v.getDistanceSquare(node) <= radiusSquare) {
nodesInRange.add(v);
}
}
return nodesInRange;
}
private List<Vertex> getNodesInRange(Vector3d bbLeft, Vector3d bbRight, List<Vertex> result) {
boolean added = false;
if (location == null) {
return result;
}
double split = location.getCoordinate(axis);
if (bbLeft.getCoordinate(axis) <= split) {
if (bbRight.getCoordinate(axis) >= split) {
// potential result
result.add(location);
added = true;
}
if (left != null) {
left.getNodesInRange(bbLeft, bbRight, result);
}
}
if (bbRight.getCoordinate(axis) >= split) {
if (!added && bbLeft.getCoordinate(axis) <= split) {
result.add(location);
}
if (right != null) {
right.getNodesInRange(bbLeft, bbRight, result);
}
}
return result;
}
public List<Vertex> getVertices() {
List<Vertex> vertices = new ArrayList<>();
getVertices(vertices);
return vertices;
}
private void getVertices(List<Vertex> vertices) {
vertices.add(location);
if (left != null) {
left.getVertices(vertices);
}
if (right != null) {
right.getVertices(vertices);
}
}
}
/*-
* 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.math.graph;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* Graph containing connected polygons
*
* @author Matthias Betz
*
*/
public class PolygonGraph {
private Map<Polygon, PolygonNode> nodeMap;
public PolygonGraph() {
nodeMap = new HashMap<>();
}
private PolygonNode createOrGetNode(Polygon p) {
PolygonNode node = nodeMap.get(p);
if (node != null) {
return node;
} else {
node = new PolygonNode(p);
nodeMap.put(p, node);
return node;
}
}
public void addPolygonConnectionsFromVertex(Vertex v, Geometry geom) {
for (Polygon i : v.getAdjacentPolygons(geom)) {
for (Polygon j : v.getAdjacentPolygons(geom)) {
if (i != j) {
PolygonNode node1 = createOrGetNode(i);
PolygonNode node2 = createOrGetNode(j);
node1.addChild(node2);
node2.addChild(node1);
}
}
}
}
public void addEdge(Edge e) {
if (e.getAdjacentPolygons().size() >= 2) {
// in case more than 2 polygons are there
// will also result in a manifold edge error
for (Polygon p1 : e.getAdjacentPolygons()) {
for (Polygon p2 : e.getAdjacentPolygons()) {
if (p1 == p2) {
continue;
}
PolygonNode node1 = createOrGetNode(p1);
PolygonNode node2 = createOrGetNode(p2);
node1.addChild(node2);
}
}
}
}
public List<List<Polygon>> findConnectedComponents() {
for (PolygonNode node : nodeMap.values()) {
node.setVisited(false);
}
List<List<Polygon>> components = new ArrayList<>();
for (PolygonNode node : nodeMap.values()) {
if (!node.visited()) {
List<Polygon> component = new ArrayList<>();
Deque<PolygonNode> stack = new LinkedList<>();
stack.push(node);
dfs(stack, component);
components.add(component);
}
}
return components;
}
private void dfs(Deque<PolygonNode> stack, List<Polygon> component) {
while (!stack.isEmpty()) {
PolygonNode node = stack.pop();
node.setVisited(true);
component.add(node.getContent());
for (PolygonNode child : node.getChildren()) {
if (!child.visited()) {
child.setVisited(true);
stack.push(child);
}
}
}
}
}
/*-
* 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.math.graph;
import java.util.HashSet;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
/**
* Node in the polygon graph
*
* @author Matthias Betz
*
*/
public class PolygonNode {
private Polygon content;
private Set<PolygonNode> children;
private boolean visited = false;
public PolygonNode(Polygon p) {
children = new HashSet<>();
content = p;
}
public void addChild(PolygonNode node) {
children.add(node);
}
public Set<PolygonNode> getChildren() {
return children;
}
public Polygon getContent() {
return content;
}
public void setVisited(boolean b) {
this.visited = b;
}
public boolean visited() {
return visited;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((content == null) ? 0 : content.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PolygonNode other = (PolygonNode) obj;
if (content == null) {
if (other.content != null)
return false;
} else if (!content.equals(other.content)) {
return false;
}
return true;
}
@Override
public String toString() {
return content.toString();
}
}
/*-
* 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.parser;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
/**
* Handler to find epsg code in CityGML files
*
* @author Matthias Betz
*
*/
public class CityGmlHandler extends DefaultHandler {
private boolean foundEnvelope = false;
private Vector3d lowerCorner;
private Vector3d upperCorner;
private String epsg;
private boolean nextContentIsLowerCorner = false;
private boolean nextContentIsUpperCorner = false;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (foundEnvelope) {
if (qName.endsWith("lowerCorner")) {
nextContentIsLowerCorner = true;
} else if (qName.endsWith("upperCorner")) {
nextContentIsUpperCorner = true;
}
} else if (qName.endsWith("Envelope")) {
foundEnvelope = true;
epsg = attributes.getValue("srsName");
}
}
public Vector3d getLowerCorner() {
return lowerCorner;
}
public Vector3d getUpperCorner() {
return upperCorner;
}
public String getEpsg() {
return epsg;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (nextContentIsLowerCorner) {
lowerCorner = parsePoint(ch, start, length);
nextContentIsLowerCorner = false;
} else if (nextContentIsUpperCorner) {
upperCorner = parsePoint(ch, start, length);
nextContentIsUpperCorner = false;
throw new EnvelopeFoundException("Found envelope, stop parsing");
}
}
private Vector3d parsePoint(char[] ch, int start, int length) {
String s = new String(ch, start, length);
String[] coords = s.split("\\s+");
double x = Double.parseDouble(coords[0]);
double y = Double.parseDouble(coords[1]);
double z = Double.parseDouble(coords[2]);
return new Vector3d(x, y, z);
}
}
/*-
* 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.parser;
/**
* Exception when something went wrong while parsing the CityGML file.
*
* @author Matthias Betz
*
*/
public class CityGmlParseException extends Exception {
private static final long serialVersionUID = 8602390540552748135L;
public CityGmlParseException() {
super();
}
public CityGmlParseException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public CityGmlParseException(String message, Throwable cause) {
super(message, cause);
}
public CityGmlParseException(String message) {
super(message);
}
public CityGmlParseException(Throwable cause) {
super(cause);
}
}
/*-
* 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.parser;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Enumeration;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.CityGMLContext;
import org.citygml4j.builder.jaxb.CityGMLBuilder;
import org.citygml4j.builder.jaxb.CityGMLBuilderException;
import org.citygml4j.model.citygml.CityGML;
import org.citygml4j.model.citygml.ade.ADEException;
import org.citygml4j.model.citygml.ade.binding.ADEContext;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.citygml.core.CityObjectMember;
import org.citygml4j.xml.io.CityGMLInputFactory;
import org.citygml4j.xml.io.reader.CityGMLReadException;
import org.citygml4j.xml.io.reader.CityGMLReader;
import org.citygml4j.xml.io.reader.FeatureReadMode;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;
import org.osgeo.proj4j.proj.Projection;
import org.osgeo.proj4j.units.Units;
import org.w3c.dom.DOMException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.mapper.FeatureMapper;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
/**
* Utility class to parse CityGML files.
*
* @author Matthias Betz
*
*/
public class CityGmlParser {
private static final Logger logger = LogManager.getLogger(CityGmlParser.class);
private static final CRSFactory CRS_FACTORY = new CRSFactory();
// EPSG:31467
private static final Pattern P_EPSG = Pattern.compile("^(EPSG:\\d+)$");
// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
// or
// urn:ogc:def:crs,crs:EPSG::28992
private static final Pattern P_OGC = Pattern.compile("urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
private static final Pattern P_OGC2 = Pattern.compile("urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
private static final Pattern P_URN = Pattern.compile("urn:adv:crs:([^\\*]+)");
private static SAXParserFactory factory;
static {
factory = SAXParserFactory.newInstance();
try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
logger.catching(e);
}
}
private CityGmlParser() {
// only static access
}
public static CityDoctorModel parseCityGmlFile(String file, ParserConfiguration config)
throws CityGmlParseException, InvalidGmlFileException {
return parseCityGmlFile(file, config, null);
}
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l)
throws CityGmlParseException, InvalidGmlFileException {
File file = new File(filePath);
try {
parseEpsgCodeFromFile(file, config);
CityGMLInputFactory inputFactory = setupGmlReader(config);
// try with resources for automatic closing
try (ObservedInputStream ois = new ObservedInputStream(file)) {
if (l != null) {
ois.addListener(l::updateProgress);
}
try (CityGMLReader reader = inputFactory.createCityGMLReader(file.getAbsolutePath(), ois)) {
FeatureMapper mapper = new FeatureMapper(config, file);
while (reader.hasNext()) {
CityGML chunk = reader.nextFeature();
if (chunk instanceof AbstractCityObject) {
AbstractCityObject ag = (AbstractCityObject) chunk;
ag.accept(mapper);
} else if (chunk instanceof CityModel) {
CityModel cModel = (CityModel) chunk;
cModel.unsetCityObjectMember();
mapper.setCityModel(cModel);
}
}
logger.info("Parsed model with {} objects", mapper.getModel().getNumberOfFeatures());
return mapper.getModel();
}
}
} catch (CityGMLReadException e) {
if (e.getCause() instanceof SAXParseException) {
throw new InvalidGmlFileException("This is not a valid GML-File\n" + e.getCause().getMessage(), e);
}
throw new CityGmlParseException(e);
} catch (IOException | CityGMLBuilderException | ParserConfigurationException | SAXException | ADEException e) {
throw new CityGmlParseException(e);
} catch (DOMException e) {
// the citygml4j split per feature tries to insert an element
// this sometimes fails because of namespace mismatch
// fallback solution is to parse the whole file at once
// problems with memory consumption may arise
try {
logger.warn("Failed to read GML file in chunks, falling back to reading the complete file", e);
return parseCityGmlFileComplete(file, config, l);
} catch (CityGMLBuilderException | CityGMLReadException | IOException e1) {
throw new CityGmlParseException(e1);
}
}
}
private static CityDoctorModel parseCityGmlFileComplete(File file, ParserConfiguration config, ProgressListener l)
throws CityGMLBuilderException, IOException, CityGMLReadException, CityGmlParseException {
CityGMLContext context = CityGMLContext.getInstance();
CityGMLBuilder builder = context.createCityGMLBuilder();
CityGMLInputFactory inputFactory = builder.createCityGMLInputFactory();
inputFactory.setProperty(CityGMLInputFactory.FAIL_ON_MISSING_ADE_SCHEMA, false);
inputFactory.setProperty(CityGMLInputFactory.USE_VALIDATION, config.getValidate());
try (ObservedInputStream ois = new ObservedInputStream(file)) {
if (l != null) {
ois.addListener(l::updateProgress);
}
try (CityGMLReader reader = inputFactory.createCityGMLReader(file.getAbsolutePath(), ois)) {
FeatureMapper mapper = new FeatureMapper(config, file);
CityGML chunk = reader.nextFeature();
if (!(chunk instanceof CityModel)) {
throw new CityGmlParseException("Did not read CityModel as first element of gml file.");
}
CityModel model = (CityModel) chunk;
mapper.setCityModel(model);
if (model.isSetCityObjectMember()) {
for (CityObjectMember com : model.getCityObjectMember()) {
if (com.isSetCityObject()) {
com.getCityObject().accept(mapper);
}
}
}
model.unsetCityObjectMember();
return mapper.getModel();
}
}
}
private static CityGMLInputFactory setupGmlReader(ParserConfiguration config)
throws CityGMLBuilderException, ADEException {
CityGMLContext context = CityGMLContext.getInstance();
// setup energy ade stuff, so the parser doesn't crash on encountering this
if (!context.hasADEContexts()) {
for (ADEContext adeContext : ServiceLoader.load(ADEContext.class)) {
context.registerADEContext(adeContext);
}
}
CityGMLBuilder builder = context.createCityGMLBuilder(CityGmlParser.class.getClassLoader());
CityGMLInputFactory inputFactory = builder.createCityGMLInputFactory();
inputFactory.setProperty(CityGMLInputFactory.FEATURE_READ_MODE, FeatureReadMode.SPLIT_PER_FEATURE);
inputFactory.setProperty(CityGMLInputFactory.USE_VALIDATION, config.getValidate());
inputFactory.setProperty(CityGMLInputFactory.FAIL_ON_MISSING_ADE_SCHEMA, false);
inputFactory.setProperty(CityGMLInputFactory.EXCLUDE_FROM_SPLITTING,
new QName[] { new QName("WallSurface"), new QName("RoofSurface"), new QName("GroundSurface"),
new QName("CeilingSurface"), new QName("ClosureSurface"), new QName("FloorSurface"),
new QName("InteriorWallSurface"), new QName("OuterCeilingSurface"),
new QName("OuterFloorSurface"), new QName("BuildingInstallation"), new QName("BuildingPart"),
new QName("Door"), new QName("Window") });
return inputFactory;
}
public static FeatureStream streamCityGml(String file, ParserConfiguration config) throws CityGmlParseException {
File f = new File(file);
return streamCityGml(f, config, null, f.getName());
}
public static FeatureStream streamCityGml(File file, ParserConfiguration config, ProgressListener l,
String fileName) throws CityGmlParseException {
try {
if (getExtension(file.getName()).equals("zip")) {
streamZipFile(file, config);
}
parseEpsgCodeFromFile(file, config);
CityGMLInputFactory inputFactory = setupGmlReader(config);
ArrayBlockingQueue<CityObject> queue = new ArrayBlockingQueue<>(1);
FeatureStream stream = new FeatureStream(queue, fileName, file);
Thread readThread = new Thread(() -> {
// try with resources for automatic closing
try (ObservedInputStream ois = new ObservedInputStream(file)) {
if (l != null) {
ois.addListener(l::updateProgress);
}
readFeatures(file, config, inputFactory, queue, ois, stream);
} catch (IOException e) {
logger.error("Error while reading city gml file\n{}", e.getMessage());
logger.catching(Level.ERROR, e);
}
});
stream.setThread(readThread);
readThread.start();
return stream;
} catch (CityGMLBuilderException | IOException | ParserConfigurationException | SAXException | ADEException e) {
throw new CityGmlParseException(e);
}
}
private static void streamZipFile(File file, ParserConfiguration config) throws CityGmlParseException {
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(ze));
parseEpsgCodeFromStream(bis, config);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (ParserConfigurationException | SAXException e) {
throw new CityGmlParseException(e);
}
}
public static String getExtension(String fileName) {
char ch;
int len;
if (fileName == null || (len = fileName.length()) == 0 || (ch = fileName.charAt(len - 1)) == '/' || ch == '\\'
|| ch == '.') {
return "";
}
int dotInd = fileName.lastIndexOf('.');
int sepInd = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
if (dotInd <= sepInd)
return "";
else
return fileName.substring(dotInd + 1).toLowerCase();
}
public static FeatureStream streamCityGml(File file, ParserConfiguration parserConfig, String fileName)
throws CityGmlParseException {
return streamCityGml(file, parserConfig, null, fileName);
}
/**
* The srsName (The name by which this reference system is identified) inside
* the CityGML file can have multiple formats. This method tries to parse the
* string and detect the corresponding reference system. If it is found, it
* returns a proj4j.CoordinateReferenceSystem. It throws an
* IllegalArgumentException otherwise.
*
* This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
* Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
* those are also included. It isn't guaranteed that those formats are correctly
* parsed, though.
*
* The EPSG ids and parameters are defined in resources ('nad/epsg') inside
* proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
*
* @param srsName
* @return CoordinateReferenceSystem
*/
private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
Matcher mEPSG = P_EPSG.matcher(srsName);
if (mEPSG.find()) {
if ("EPSG:4979".contentEquals(srsName)) {
srsName = "EPSG:4236";
} else if ("EPSG:7415".contentEquals(srsName)) {
return CRS_FACTORY.createFromParameters("EPSG:7415",
"+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs");
}
return CRS_FACTORY.createFromName(srsName);
}
Matcher mOGC = P_OGC.matcher(srsName);
if (mOGC.find()) {
return CRS_FACTORY.createFromName("EPSG:" + mOGC.group(1));
}
Matcher mOGC2 = P_OGC2.matcher(srsName);
if (mOGC2.find()) {
return CRS_FACTORY.createFromName("EPSG:" + mOGC2.group(1));
}
Matcher mURN = P_URN.matcher(srsName);
// NOTE: Could use a HashMap if the switch/case becomes too long.
if (mURN.find()) {
switch (mURN.group(1)) {
case "DE_DHDN_3GK2":
return CRS_FACTORY.createFromName("EPSG:31466");
case "DE_DHDN_3GK3":
return CRS_FACTORY.createFromName("EPSG:31467");
case "DE_DHDN_3GK4":
return CRS_FACTORY.createFromName("EPSG:31468");
case "DE_DHDN_3GK5":
return CRS_FACTORY.createFromName("EPSG:31469");
case "ETRS89_UTM32":
return CRS_FACTORY.createFromName("EPSG:25832");
default:
return null;
}
}
return null;
}
private static void readFeatures(File file, ParserConfiguration config, CityGMLInputFactory inputFactory,
ArrayBlockingQueue<CityObject> queue, ObservedInputStream ois, FeatureStream stream) {
try (CityGMLReader reader = inputFactory.createCityGMLReader(file.getAbsolutePath(), ois)) {
FeatureMapper mapper = new FeatureMapper(config, file);
CityDoctorModel model = mapper.getModel();
while (reader.hasNext() && !stream.isClosed()) {
CityGML chunk = reader.nextFeature();
if (chunk instanceof AbstractCityObject) {
AbstractCityObject ag = (AbstractCityObject) chunk;
ag.accept(mapper);
} else if (chunk instanceof CityModel) {
CityModel cModel = (CityModel) chunk;
cModel.unsetCityObjectMember();
mapper.setCityModel(cModel);
stream.setCityDoctorModel(model);
}
drainCityModel(model, queue);
}
// end of stream
queue.put(FeatureStream.POISON);
logger.debug("End of gml file stream");
} catch (CityGMLReadException e) {
logger.error("Error while reading city gml file\n" + e.getMessage(), e);
} catch (InterruptedException e) {
logger.warn("Interrupted while streaming gml file");
Thread.currentThread().interrupt();
}
}
private static void parseEpsgCodeFromFile(File file, ParserConfiguration config)
throws IOException, ParserConfigurationException, SAXException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
parseEpsgCodeFromStream(bis, config);
}
}
private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config)
throws ParserConfigurationException, SAXException {
SAXParser parser = factory.newSAXParser();
CityGmlHandler handler = new CityGmlHandler();
try {
parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) {
try {
if (handler.getEpsg() == null) {
return;
}
CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
if (crs == null) {
// could not find a coordinate system for srsName
// assuming metric system
return;
}
ProjectionUnitExtractor extractor = new ProjectionUnitExtractor(crs.getProjection());
if (extractor.getUnit() == Units.METRES) {
// coordinate system is in meters, do not convert
logger.info("Coordinate system is in meters, no conversion done");
return;
}
parseMeterConversion(config, crs);
Vector3d low = handler.getLowerCorner();
Vector3d up = handler.getUpperCorner();
double centerLong = low.getX() + ((up.getX() - low.getX()) / 2);
double centerLat = low.getY() + ((up.getY() - low.getY()) / 2);
if (!crs.getName().equals("EPSG:4326")) {
// need to convert coordinates first to WGS84, then find UTM Zone
CoordinateReferenceSystem wgs84 = crsFromSrsName("EPSG:4326");
ProjCoordinate p1 = new ProjCoordinate();
p1.setValue(centerLong, centerLat);
ProjCoordinate p2 = new ProjCoordinate();
BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
bct.transform(p1, p2);
centerLong = p2.x;
centerLat = p2.y;
}
int zone = (int) (31 + Math.round(centerLong / 6));
CoordinateReferenceSystem utm;
if (centerLat < 0) {
// south
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +zone=" + zone + " +south");
} else {
// north
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +zone=" + zone);
}
config.setCoordinateSystem(crs, utm);
} catch (Exception e2) {
logger.debug("Exception while parsing for EPSG code", e2);
logger.warn("Could not read EPSG code, assuming metric system");
}
} catch (Exception e) {
logger.debug("Exception while parsing for EPSG code", e);
logger.warn("Could not read EPSG code, assuming metric system");
}
}
private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
Projection projection = crs.getProjection();
double fromMetres = projection.getFromMetres();
if (fromMetres > 0) {
// also transform height information
config.setFromMetres(fromMetres);
} else {
config.setFromMetres(1.0);
}
}
private static void drainCityModel(CityDoctorModel model, ArrayBlockingQueue<CityObject> queue)
throws InterruptedException {
drainCityObjectList(model.getBuildings(), queue);
drainCityObjectList(model.getBridges(), queue);
drainCityObjectList(model.getVegetation(), queue);
drainCityObjectList(model.getLand(), queue);
drainCityObjectList(model.getTransportation(), queue);
drainCityObjectList(model.getWater(), queue);
model.getBuildings().clear();
model.getBridges().clear();
model.getVegetation().clear();
model.getLand().clear();
model.getTransportation().clear();
model.getWater().clear();
}
private static void drainCityObjectList(List<? extends CityObject> buildings, ArrayBlockingQueue<CityObject> queue)
throws InterruptedException {
for (CityObject co : buildings) {
queue.put(co);
}
}
}
/*-
* 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.parser;
import org.xml.sax.SAXException;
/**
* To stop the SAXParser from further parsing when the relevant section has
* already been found.
*
* @author Matthias Betz
*
*/
public class EnvelopeFoundException extends SAXException {
private static final long serialVersionUID = -9188617211115043815L;
public EnvelopeFoundException() {
super();
}
public EnvelopeFoundException(Exception e) {
super(e);
}
public EnvelopeFoundException(String message, Exception e) {
super(message, e);
}
public EnvelopeFoundException(String message) {
super(message);
}
}
/*-
* 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.parser;
import java.io.File;
import java.util.concurrent.ArrayBlockingQueue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.FeatureType;
/**
* Handler when the CityGML files is split up into seperate features.
*
* @author Matthias Betz
*
*/
public class FeatureStream {
private static final Logger logger = LogManager.getLogger(FeatureStream.class);
private ArrayBlockingQueue<CityObject> queue;
private String fileName;
private CityDoctorModel model;
private boolean closed;
private Thread readThread;
private File file;
static final CityObject POISON = new CityObject() {
private static final long serialVersionUID = -3476076957438256265L;
@Override
public FeatureType getFeatureType() {
return FeatureType.BUILDING;
}
@Override
public void reCreateGeometries(GMLGeometryFactory factory, ParserConfiguration config) {
throw new UnsupportedOperationException();
}
@Override
public AbstractCityObject getGmlObject() {
throw new UnsupportedOperationException();
}
@Override
public void unsetGmlGeometries() {
throw new UnsupportedOperationException();
}
};
FeatureStream(ArrayBlockingQueue<CityObject> queue, String fileName, File file) {
this.queue = queue;
this.fileName = fileName;
this.file = file;
closed = false;
}
public void setThread(Thread readThread) {
this.readThread = readThread;
}
public String getFileName() {
return fileName;
}
/**
* Returns the next element that is parsed or null if no more elements are
* available
*
* @return the next element or null
* @throws InterruptedException if the parsing thread is interrupted
*/
public CityObject next() throws InterruptedException {
CityObject co = queue.take();
if (co == POISON) {
if (readThread != null) {
readThread.join();
}
return null;
}
return co;
}
public void setCityDoctorModel(CityDoctorModel model) {
this.model = model;
}
public CityDoctorModel getCityDoctorModel() {
return model;
}
public void close() {
if (readThread != null) {
readThread.interrupt();
}
closed = true;
if (!queue.isEmpty()) {
queue.poll();
boolean validOffer = queue.offer(POISON);
logger.trace("Stream closed while queue not empty, removing element and adding poison element");
if (!validOffer) {
logger.trace("Could not add poison element to queue");
}
}
}
public boolean isClosed() {
return closed;
}
public File getFile() {
return file;
}
}
Markdown is supported
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