/*-
* 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 .
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
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.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
/**
* Representation of a geometry containing the polygons and edges
*
* @author Matthias Betz
*
*/
public class Geometry extends GmlElement {
private static final long serialVersionUID = 2539031030917731575L;
private static Random r = new Random();
private GeometryType type;
private Lod lod;
private CityObject parent;
private List polygons = new ArrayList<>();
private List edges;
private Map, Edge> edgeMap;
private List vertices;
public Geometry(GeometryType type, Lod lod) {
this.lod = lod;
this.type = type;
}
void setParent(CityObject parent) {
this.parent = parent;
}
public CityObject getParent() {
return parent;
}
public void setType(GeometryType type) {
this.type = type;
}
public GeometryType getType() {
return type;
}
public Lod getLod() {
return lod;
}
public List getPolygons() {
return polygons;
}
public void addPolygon(Polygon cdPoly) {
polygons.add(cdPoly);
cdPoly.setParent(this);
}
public void updateEdges() {
edges = new ArrayList<>();
edgeMap = new HashMap<>();
Map duplicacyMap = new HashMap<>();
for (Polygon p : polygons) {
LinearRing extRing = p.getExteriorRing();
createEdgesFromRing(duplicacyMap, p, extRing);
for (LinearRing inner : p.getInnerRings()) {
createEdgesFromRing(duplicacyMap, p, inner);
}
}
}
void anonymize() {
setGmlId(GmlId.generateId());
for (Polygon p : polygons) {
p.anonymize();
}
double minX = Double.MAX_VALUE;
double minY = Double.MAX_VALUE;
double minZ = Double.MAX_VALUE;
for (Vertex v : vertices) {
if (v.getX() < minX) {
minX = v.getX();
}
if (v.getY() < minY) {
minY = v.getY();
}
if (v.getZ() < minZ) {
minZ = v.getZ();
}
}
double varX = r.nextDouble() * 10;
double varY = r.nextDouble() * 10;
double varZ = r.nextDouble() * 10;
for (Vertex v : vertices) {
v.setX(v.getX() - minX + varX);
v.setY(v.getY() - minY + varY);
v.setZ(v.getZ() - minZ + varZ);
}
}
private void createEdgesFromRing(Map duplicacyMap, Polygon p, LinearRing ring) {
// only go to size -1 as ring should be closed
for (int i = 0; i < ring.getVertices().size() - 1; i++) {
Vertex v0 = ring.getVertices().get(i + 0);
Vertex v1 = ring.getVertices().get(i + 1);
Edge tempEdge = new Edge(v0, v1);
Edge e = duplicacyMap.get(tempEdge);
if (e == null) {
// no duplicate found, enter edges
Edge oppositeEdge = new Edge(v1, v0);
duplicacyMap.put(tempEdge, tempEdge);
duplicacyMap.put(oppositeEdge, tempEdge);
for (Vertex v0Neighbor : v0.getNeighbors()) {
Edge alternativeEdge = new Edge(v0Neighbor, v1);
Edge oppositeAlternativeEdge = new Edge(v1, v0Neighbor);
duplicacyMap.put(alternativeEdge, tempEdge);
duplicacyMap.put(oppositeAlternativeEdge, tempEdge);
for (Vertex v1Neighbor : v1.getNeighbors()) {
alternativeEdge = new Edge(v0Neighbor, v1Neighbor);
oppositeAlternativeEdge = new Edge(v1Neighbor, v0Neighbor);
duplicacyMap.put(alternativeEdge, tempEdge);
duplicacyMap.put(oppositeAlternativeEdge, tempEdge);
}
}
for (Vertex v : v1.getNeighbors()) {
Edge alternativeEdge = new Edge(v0, v);
Edge oppositeAlternativeEdge = new Edge(v, v0);
duplicacyMap.put(alternativeEdge, tempEdge);
duplicacyMap.put(oppositeAlternativeEdge, tempEdge);
}
e = tempEdge;
edges.add(e);
edgeMap.put(new SerializablePair<>(v0, v1), e);
edgeMap.put(new SerializablePair<>(v1, v0), e);
} else {
e.addHalfEdge(v0, v1);
}
e.addAdjacentPolygon(p);
e.addAdjacentRing(ring);
}
}
public Vector3d getCenter() {
return calculateBoundingBox().getCenter();
}
/**
* This will calculate two points where every other point in the geometry is
* between those two. If the geometry does not contain points or is null, an
* empty array is returned. This is considered an axis aligned bounding box.
* Only the exterior rings of the polygons is used as inner rings should be
* within the exterior or they are faulty.
*
* @return an array of two points. The first contains the lowest coordinates,
* the second contains the highest coordinates
*/
public BoundingBox calculateBoundingBox() {
return BoundingBoxCalculator.calculateBoundingBox(polygons);
}
public double calculateVolume() {
double sum = 0;
Vector3d center = calculateBoundingBox().getCenter();
for (Polygon p : polygons) {
TesselatedPolygon tesselatedPolygon = p.tesselate();
for (Triangle3d t : tesselatedPolygon.getTriangles()) {
Vector3d p1 = t.getP1().minus(center);
Vector3d p2 = t.getP2().minus(center);
Vector3d p3 = t.getP3().minus(center);
sum += det(p1.getCoordinates(), p2.getCoordinates(), p3.getCoordinates());
}
}
return sum / 6d;
}
private double det(double[] a, double[] b, double[] c) {
return -c[0] * b[1] * a[2] + b[0] * c[1] * a[2] + c[0] * a[1] * b[2] - a[0] * c[1] * b[2] - b[0] * a[1] * c[2]
+ a[0] * b[1] * c[2];
}
@Override
public boolean containsError(CheckId checkIdentifier) {
boolean hasError = super.containsError(checkIdentifier);
if (hasError) {
return true;
}
for (Polygon p : polygons) {
if (p.containsError(checkIdentifier)) {
return true;
}
}
return false;
}
@Override
public void clearAllContainedCheckResults() {
super.clearCheckResults();
for (Polygon p : polygons) {
p.clearAllContainedCheckResults();
}
}
@Override
public void collectContainedErrors(List errors) {
super.collectContainedErrors(errors);
for (Polygon p : polygons) {
p.collectContainedErrors(errors);
}
}
@Override
public boolean containsAnyError() {
boolean hasError = super.containsAnyError();
if (hasError) {
return true;
}
for (Polygon p : polygons) {
if (p.containsAnyError()) {
return true;
}
}
return false;
}
@Override
public void accept(Check c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
}
for (Polygon p : polygons) {
p.accept(c);
}
}
@Override
public String toString() {
return "Geometry [type=" + type + ", lod=" + lod + ", id=" + getGmlId() + "]";
}
public List getVertices() {
return vertices;
}
public void setVertices(List vertices) {
this.vertices = vertices;
}
public List getEdges() {
return edges;
}
public List getEdgesAdjacentTo(Vertex v) {
List adjacentEdges = new ArrayList<>();
for (Edge e : edges) {
if (e.getFrom() == v || e.getTo() == v) {
adjacentEdges.add(e);
}
}
return adjacentEdges;
}
public Edge getEdge(Vertex v1, Vertex v2) {
Edge edge = edgeMap.get(new SerializablePair<>(v1, v2));
if (edge != null) {
return edge;
}
for (Vertex neighborV1 : v1.getNeighbors()) {
edge = edgeMap.get(new SerializablePair<>(neighborV1, v2));
if (edge != null) {
return edge;
}
for (Vertex neighborV2 : v2.getNeighbors()) {
edge = edgeMap.get(new SerializablePair<>(neighborV1, neighborV2));
if (edge != null) {
return edge;
}
}
}
for (Vertex neighborV2 : v2.getNeighbors()) {
edge = edgeMap.get(new SerializablePair<>(neighborV2, v1));
if (edge != null) {
return edge;
}
}
return null;
}
public void updateEdgesAndVertices() {
updateVertices();
updateEdges();
}
public void updateVertices() {
Set vertexSet = new HashSet<>();
for (Polygon p : getPolygons()) {
updateRing(p, vertexSet, p.getExteriorRing());
for (LinearRing inner : p.getInnerRings()) {
updateRing(p, vertexSet, inner);
}
}
vertices = new ArrayList<>(vertexSet);
}
private void updateRing(Polygon p, Set vertexSet, LinearRing ring) {
for (Vertex v : ring.getVertices()) {
if (vertexSet.add(v)) {
// new vertex, clear adjacent rings in case new rings have been added
v.clearAdjacentRings(this);
}
// add ring to adjacent rings of vertex
v.addAdjacentRing(ring, this);
}
}
public void removePolygon(Polygon p) {
p.removeRings();
polygons.remove(p);
if (p.isLink()) {
Polygon original = p.getOriginal();
original.getParent().polygons.remove(original);
} else if (p.isLinkedTo()) {
Polygon link = p.getLinkedFromPolygon();
link.getParent().polygons.remove(link);
}
}
public void replacePolygon(Polygon p, ConcretePolygon... polygons) {
removePolygon(p);
if (p.isLink()) {
Geometry geom2 = p.getOriginal().getParent();
for (ConcretePolygon newPoly : polygons) {
geom2.addPolygon(newPoly);
addPolygon(new LinkedPolygon(newPoly, this));
}
} else if (p.isLinkedTo()) {
Geometry geom2 = p.getLinkedFromPolygon().getParent();
for (ConcretePolygon newPoly : polygons) {
addPolygon(newPoly);
geom2.addPolygon(new LinkedPolygon(newPoly, this));
}
} else {
for (Polygon newPoly : polygons) {
addPolygon(newPoly);
}
}
}
public List getEdgesAdjacentTo(Polygon p) {
List list = new ArrayList<>();
for (Edge e : edges) {
if (e.getAdjacentPolygons().contains(p)) {
list.add(e);
}
}
return list;
}
public boolean containsPolygon(Polygon other) {
for (Polygon p : polygons) {
if (p.getGmlId().equals(other.getGmlId())) {
return true;
}
}
return false;
}
@Override
public Geometry copy() {
return (Geometry) super.copy();
}
@Override
public void prepareForChecking() {
updateEdgesAndVertices();
}
@Override
public void clearMetaInformation() {
edges = null;
vertices = null;
edgeMap = null;
}
}