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.checks;
import java.util.List;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckType;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
/**
* This class is for having a read only access to a check for accessing meta
* information like parameters and type. It also can create a new instance of
* the check. This was created to support parallel processing of multiple GML
* files with different configurations. It isolates the configuration of a check
* into the instance instead of having global check instances.
*
* @author Matthias Betz
*
*/
public class CheckPrototype {
private Check c;
public CheckPrototype(Check c) {
this.c = c;
}
/**
* @return A new instance of the check.
*/
public Check createCheck() {
return c.createNewInstance();
}
public CheckId getCheckId() {
return c.getCheckId();
}
public List<CheckId> getDependencies() {
return c.getDependencies();
}
public List<DefaultParameter> getDefaultParameter() {
return c.getDefaultParameter();
}
public CheckType getType() {
return c.getType();
}
}
/*-
* 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;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.checks.geometry.CloseCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.ConCompCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.DuplicatePointsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.FaceOrientCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.FaceOutCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.HoleOutsideCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.InteriorDisconnectedCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.ManifoldVertexCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.NumPointsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.OverUsedEdgeCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PlanarCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PolygonSameOrientationCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PolygonSelfIntCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.RingSelfIntCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.SolidNotClosedCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.SolidSelfIntCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.TooFewPolygonsCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsCeilingCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsFloorCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsGroundCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsWallCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.RoofSurfaceUnfragmentedCheck;
/**
* Container class for all checks. If new checks are added, they need to be
* published here to be accessed by the checker instance.
*
* @author Matthias Betz
*
*/
public class Checks {
private static Logger logger = LogManager.getLogger(Checks.class);
private static List<CheckPrototype> checkPrototypes;
private static Map<CheckId, CheckPrototype> prototypeMap;
private Map<CheckId, Check> checkMap;
static {
checkPrototypes = new ArrayList<>();
prototypeMap = new EnumMap<>(CheckId.class);
// add new checks here
publish(new NumPointsCheck());
publish(new CloseCheck());
publish(new DuplicatePointsCheck());
publish(new RingSelfIntCheck());
publish(new PlanarCheck());
publish(new PolygonSameOrientationCheck());
publish(new HoleOutsideCheck());
publish(new NestedRingsCheck());
publish(new PolygonSelfIntCheck());
publish(new InteriorDisconnectedCheck());
publish(new ConCompCheck());
publish(new SolidNotClosedCheck());
publish(new OverUsedEdgeCheck());
publish(new FaceOrientCheck());
publish(new FaceOutCheck());
publish(new TooFewPolygonsCheck());
publish(new ManifoldVertexCheck());
publish(new SolidSelfIntCheck());
// semantic checks
publish(new IsWallCheck());
publish(new IsFloorCheck());
publish(new IsCeilingCheck());
publish(new IsGroundCheck());
publish(new RoofSurfaceUnfragmentedCheck());
}
/**
* Creates new checks for every available check prototype and stores them in
* this container. Access the checks with {@link Checks#getCheckForId(CheckId)}.
*/
public Checks() {
checkMap = new EnumMap<>(CheckId.class);
for (CheckPrototype proto : checkPrototypes) {
Check check = proto.createCheck();
checkMap.put(check.getCheckId(), check);
}
}
private static void publish(Check check) {
CheckPrototype prototype = new CheckPrototype(check);
checkPrototypes.add(prototype);
prototypeMap.put(prototype.getCheckId(), prototype);
}
/**
*
* @return All available checks as prototypes, where they can be instantiated.
*/
public static List<CheckPrototype> getCheckPrototypes() {
return checkPrototypes;
}
/**
*
* @param key The check ID
* @return The prototype for the given ID or null if there is no such prototype.
*/
public static CheckPrototype getCheckPrototypeForId(CheckId key) {
return prototypeMap.get(key);
}
/**
*
* @param id The check ID
* @return The check for the check ID or null if it does not exist
*/
public Check getCheckForId(CheckId id) {
Check c = checkMap.get(id);
if (c == null) {
logger.warn("Could not find check for id: {}", id);
}
return c;
}
}
/*-
* 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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
/**
* This is the content handler for parsing the Svrl xml files created by a
* Schematron run. They are parsed and error objects are created and possibly
* associated to GML-IDs.
*
* @author Matthias Betz
*
*/
public class SvrlContentHandler implements ContentHandler {
private boolean nextElementIsErrorText = false;
private boolean nextIsTextContent = false;
private StringBuilder buffer;
private Map<String, SchematronError> featureErrors;
private List<SchematronError> generalErrors;
public SvrlContentHandler() {
featureErrors = new HashMap<>();
generalErrors = new ArrayList<>();
}
public Map<String, SchematronError> getFeatureErrors() {
return featureErrors;
}
public List<SchematronError> getGeneralErrors() {
return generalErrors;
}
@Override
public void setDocumentLocator(Locator locator) {
// not needed
}
@Override
public void startDocument() throws SAXException {
// not needed
}
@Override
public void endDocument() throws SAXException {
// not needed
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
// not needed
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
// not needed
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if ("failed-assert".equals(localName)) {
nextElementIsErrorText = true;
} else if (nextElementIsErrorText && "text".equals(localName)) {
nextIsTextContent = true;
buffer = new StringBuilder();
nextElementIsErrorText = false;
} else {
nextElementIsErrorText = false;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// not needed
if (nextIsTextContent && "text".equals(localName)) {
String text = buffer.toString();
int index = text.indexOf(": ");
if (index == -1) {
// general error
generalErrors.add(new SchematronError(text));
} else {
String gmlId = text.substring(0, index);
featureErrors.put(gmlId, new SchematronError(text));
}
buffer = null;
nextIsTextContent = false;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (nextIsTextContent) {
buffer.append(ch, start, length);
}
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
// not needed
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
// not needed
}
@Override
public void skippedEntity(String name) throws SAXException {
// not needed
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.RingNotClosedError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* CP_CLOSE checks the closeness a linear ring. A linear ring must end where it
* started.
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*
*/
public class CloseCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
static {
ArrayList<CheckId> deps = new ArrayList<>();
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public CloseCheck() {
super(CheckId.C_GE_R_NOT_CLOSED);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public void check(LinearRing lr) {
Vertex first = lr.getVertices().get(0);
Vertex last = lr.getVertices().get(lr.getVertices().size() - 1);
CheckResult cr;
if (!first.equalsWithNeighbors(last)) {
CheckError err = new RingNotClosedError(lr);
cr = new CheckResult(this, ResultStatus.ERROR, err);
} else {
cr = new CheckResult(this, ResultStatus.OK, null);
}
lr.addCheckResult(cr);
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new CloseCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.MultipleConnectedComponentsError;
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.Vertex;
import de.hft.stuttgart.citydoctor2.math.graph.PolygonGraph;
/**
* Checks if all parts of a solid are connected or not.
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*
*/
public class ConCompCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public ConCompCheck() {
super(CheckId.C_GE_S_MULTIPLE_CONNECTED_COMPONENTS);
}
@Override
public void check(Geometry g) {
// only for solids
if (g.getType() != GeometryType.SOLID && g.getType() != GeometryType.COMPOSITE_SURFACE) {
return;
}
PolygonGraph pg = new PolygonGraph();
for (Vertex v : g.getVertices()) {
pg.addPolygonConnectionsFromVertex(v, g);
}
List<List<Polygon>> components = pg.findConnectedComponents();
CheckResult cr;
if (components.size() > 1) {
CheckError err = new MultipleConnectedComponentsError(g, components);
cr = new CheckResult(this, ResultStatus.ERROR, err);
} else {
cr = new CheckResult(this, ResultStatus.OK, null);
}
g.addCheckResult(cr);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new ConCompCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.Unit;
import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.DuplicatePointError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
/**
* CP_DUPPOINT checks if there is any double point in a linear ring. Each point
* must occur only once in a linear ring. <br/>
* <br/>
* <b>Dependency:</b> <br/>
* <dd>
* <dd>CP_CLOSE</dd></dd>
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*
*/
public class DuplicatePointsCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<DefaultParameter> defaultParameters;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
deps.add(CheckId.C_GE_R_NOT_CLOSED);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<DefaultParameter> defaultParameter = new ArrayList<>();
defaultParameter.add(new DefaultParameter(EPSILON_NAME, "0.0001", Unit.METER));
defaultParameters = Collections.unmodifiableList(defaultParameter);
}
private double epsilon = 0.0001;
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME);
if (epsilonString == null) {
epsilon = 0.0001;
} else {
epsilon = Double.parseDouble(epsilonString);
}
}
public DuplicatePointsCheck() {
super(CheckId.C_GE_R_DUPLICATE_POINT);
}
@Override
public List<DefaultParameter> getDefaultParameter() {
return defaultParameters;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public void check(LinearRing lr) {
List<Vertex> pointList = lr.getVertices();
// check for consecutive points first
for (int i = 0; i < pointList.size() - 1; i++) {
Vertex point1 = pointList.get(i);
Vertex point2 = pointList.get(i + 1);
if (point1.equalsWithEpsilon(point2, epsilon)) {
// consecutive points same
CheckError err = new ConsecutivePointSameError(lr, point1, point2);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
}
// ignore last point, because last point = first, but this is allowed
for (int i = 0; i < pointList.size() - 2; i++) {
for (int j = i + 2; j < pointList.size() - 1; j++) {
Vertex point1 = pointList.get(i);
Vertex point2 = pointList.get(j);
if (point1.equalsWithEpsilon(point2, epsilon)) {
// non consecutive points same
CheckError err = new DuplicatePointError(lr, point1, point2);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr);
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new DuplicatePointsCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.PolygonWrongOrientationError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
/**
* This class checks the orientation of each face of a solid
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*
*/
public class FaceOrientCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
deps.add(CheckId.C_GE_S_NOT_CLOSED);
deps.add(CheckId.C_GE_S_NON_MANIFOLD_EDGE);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public FaceOrientCheck() {
super(CheckId.C_GE_S_POLYGON_WRONG_ORIENTATION);
}
@Override
public void check(Geometry g) {
// only for solids
if (g.getType() != GeometryType.SOLID) {
return;
}
List<Edge> faultyEdges = new ArrayList<>();
for (Edge edge : g.getEdges()) {
if (edge.getNumberOfHalfEdges() != edge.getNumberOppositeHalfEdges()) {
faultyEdges.add(edge);
}
}
CheckResult cr;
if (faultyEdges.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError err = new PolygonWrongOrientationError(g, faultyEdges);
cr = new CheckResult(this, ResultStatus.ERROR, err);
}
g.addCheckResult(cr);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new FaceOrientCheck();
}
}
\ 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.AllPolygonsWrongOrientationError;
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.math.Ray;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
/**
*
* @author Matthias Betz - 71bema1mst@hft-stuttgart.de
*
*/
public class FaceOutCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
deps.add(CheckId.C_GE_S_NOT_CLOSED);
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_SELF_INTERSECTION);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public FaceOutCheck() {
super(CheckId.C_GE_S_ALL_POLYGONS_WRONG_ORIENTATION);
}
@Override
public void check(Geometry g) {
// only check solids
if (g.getType() != GeometryType.SOLID) {
return;
}
boolean isOutwardOriented = geometryIsOutwardOrientedJava(g);
CheckResult cr;
if (isOutwardOriented) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError err = new AllPolygonsWrongOrientationError(g);
cr = new CheckResult(this, ResultStatus.ERROR, err);
}
g.addCheckResult(cr);
}
private boolean geometryIsOutwardOrientedJava(Geometry g) {
Vector3d[] bbox = g.calculateBoundingBox().getBox();
// tesselate polygons
ArrayList<TesselatedPolygon> tessPolygons = new ArrayList<>();
for (Polygon p : g.getPolygons()) {
tessPolygons.add(p.tesselate());
}
if (tessPolygons.isEmpty()) {
throw new IllegalStateException("No polygons found in geometry");
}
// get any triangle to construct a point in any polygon
TesselatedPolygon p1 = null;
for (TesselatedPolygon tessPoly : tessPolygons) {
if (!tessPoly.getTriangles().isEmpty()) {
p1 = tessPoly;
break;
}
}
if (p1 == null) {
throw new IllegalStateException("No polygon could be triangulated");
}
Triangle3d t = findSuitableTriangle(p1);
// find the centroid of a triangle
Vector3d centroid = t.getCentroid();
// create a point outside of the geometry
Vector3d outsidePoint = bbox[0].copy().minus(Vector3d.X, 5).minus(Vector3d.Y, 5).minus(Vector3d.Z, 5);
// create a second point outside of the geometry
// the check can fail if the building is exactly oriented so that the ray is
// parallel to a side of the building
// in order to avoid this we check two rays, if one of those says it is oriented
// correctly it is oriented correctly
Vector3d secondOutsidePoint = bbox[0].copy().minus(Vector3d.X, 5).minus(Vector3d.Y, 10).minus(Vector3d.Z, 5);
return checkIfGeometryIsWrongOriented(tessPolygons, centroid, outsidePoint)
|| checkIfGeometryIsWrongOriented(tessPolygons, centroid, secondOutsidePoint);
}
private boolean checkIfGeometryIsWrongOriented(ArrayList<TesselatedPolygon> tessPolygons, Vector3d centroid,
Vector3d outsidePoint) {
// centroid point is now in the polygon, calculate the direction vector of the
// ray
Vector3d outsidePointDirection = centroid.minus(outsidePoint);
// construct a ray to check for intersections
Ray ray = new Ray(outsidePoint, outsidePointDirection);
// check for intersections, store closest polygon in tessPoly
TesselatedPolygon tessPoly = null;
double distance = Double.MAX_VALUE;
for (TesselatedPolygon tp : tessPolygons) {
Vector3d intersection = ray.intersectsPolygon(tp);
if (intersection != null) {
// calculate distance between intersection point and outsidePoint
double d = outsidePoint.getDistance(intersection);
// check if polygon is closer
if (d < distance) {
tessPoly = tp;
distance = d;
}
}
}
if (tessPoly == null) {
throw new IllegalStateException("Constructed a ray that does not intersect any polygon");
}
// calculate normal of the closest polygon
Vector3d normal = tessPoly.getOriginal().calculateNormal();
// calculate cos(gamma) of normal and ray direction, the normal is normalized,
// don't need to get the length
double cosAngle = outsidePointDirection.dot(normal) / outsidePointDirection.getLength();
// cos is negative = ok
return cosAngle <= 0;
}
private Triangle3d findSuitableTriangle(TesselatedPolygon p1) {
Triangle3d t = p1.getTriangles().get(0);
double maxArea = t.getArea();
for (Triangle3d tri : p1.getTriangles()) {
double area = tri.getArea();
if (area > maxArea) {
maxArea = area;
t = tri;
}
}
return t;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new FaceOutCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.HoleOutsideError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
public class HoleOutsideCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_P_ORIENTATION_RINGS_SAME);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public HoleOutsideCheck() {
super(CheckId.C_GE_P_HOLE_OUTSIDE);
}
@Override
public void check(Polygon p) {
List<LinearRing> holesOutside = new ArrayList<>();
for (LinearRing lr : p.getInnerRings()) {
boolean isOutside = true;
for (Vertex v : lr.getVertices()) {
if (p.isPointInsideExteriorRing(v)) {
isOutside = false;
break;
}
}
if (isOutside) {
holesOutside.add(lr);
}
}
// add hole outside check result
if (holesOutside.isEmpty()) {
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
} else {
CheckError err = new HoleOutsideError(p, holesOutside);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
}
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new HoleOutsideCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.Unit;
import de.hft.stuttgart.citydoctor2.check.error.InteriorDisconnectedError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.math.graph.CycleNode;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
public class InteriorDisconnectedCheck extends Check {
private static final String EPSILON_NAME = "Epsilon";
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<DefaultParameter> defaultParameters;
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);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<DefaultParameter> defaultParameter = new ArrayList<>();
defaultParameter.add(new DefaultParameter(EPSILON_NAME, "0.0001", Unit.METER));
defaultParameters = Collections.unmodifiableList(defaultParameter);
}
private double epsilon = 0.0001;
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME);
if (epsilonString == null) {
epsilon = 0.0001;
} else {
epsilon = Double.parseDouble(epsilonString);
}
}
public InteriorDisconnectedCheck() {
super(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
}
@Override
public void check(Polygon p) {
if (p.getInnerRings().isEmpty()) {
// no interior rings means polygon is ok
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
}
Map<LinearRing, CycleNode> nodeMap = new HashMap<>();
setupNodeMap(p, nodeMap);
List<LinearRing> rings = new ArrayList<>();
rings.add(p.getExteriorRing());
rings.addAll(p.getInnerRings());
for (int i = 0; i < rings.size() - 1; i++) {
LinearRing ring = rings.get(i);
List<Segment3d> segments = createSegmentsFromRing(ring);
for (int j = i + 1; j < rings.size(); j++) {
LinearRing borderingRing = rings.get(j);
if (isRingTouchingBorderingRing(p, nodeMap, ring, segments, borderingRing)) {
return;
}
}
}
for (CycleNode node : nodeMap.values()) {
if (node.hasMoreThan2CycleDepth(null)) {
// found ring in graph with more than 1 node
// collect rings
node.resetVisit();
List<LinearRing> connectedRings = node.getConnectedRings();
CheckError err = new InteriorDisconnectedError(p, connectedRings);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return;
}
// check next node, reset visit status
node.resetVisit();
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
}
private boolean isRingTouchingBorderingRing(Polygon p, Map<LinearRing, CycleNode> nodeMap, LinearRing ring,
List<Segment3d> segments, LinearRing borderingRing) {
for (Segment3d seg : segments) {
for (int k = 0; k < borderingRing.getVertices().size() - 1; k++) {
Vertex v = borderingRing.getVertices().get(k);
if (seg.getDistance(v) < epsilon) {
CycleNode n1 = nodeMap.get(ring);
CycleNode n2 = nodeMap.get(borderingRing);
boolean added1 = n1.addChild(n2);
boolean added2 = n2.addChild(n1);
if (!added1 || !added2) {
List<LinearRing> connectedRings = new ArrayList<>();
connectedRings.add(n1.getValue());
connectedRings.add(n2.getValue());
CheckError err = new InteriorDisconnectedError(p, connectedRings);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return true;
}
}
}
}
return false;
}
private List<Segment3d> createSegmentsFromRing(LinearRing ring) {
List<Segment3d> segments = new ArrayList<>();
for (int k = 0; k < ring.getVertices().size() - 1; k++) {
Vertex v1 = ring.getVertices().get(k);
Vertex v2 = ring.getVertices().get(k + 1);
segments.add(new Segment3d(v1, v2));
}
return segments;
}
private void setupNodeMap(Polygon p, Map<LinearRing, CycleNode> nodeMap) {
CycleNode ext = new CycleNode(p.getExteriorRing());
nodeMap.put(ext.getValue(), ext);
for (LinearRing lr : p.getInnerRings()) {
CycleNode node = new CycleNode(lr);
nodeMap.put(lr, node);
}
}
@Override
public List<DefaultParameter> getDefaultParameter() {
return defaultParameters;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new InteriorDisconnectedCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.NonManifoldVertexError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
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.Vertex;
import de.hft.stuttgart.citydoctor2.math.graph.PolygonGraph;
/**
* Checks if the polygons adjacent to a vertex are attached via edges or are
* they discontinuous.
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*
*/
public class ManifoldVertexCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
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_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public ManifoldVertexCheck() {
super(CheckId.C_GE_S_NON_MANIFOLD_VERTEX);
}
@Override
public void check(Geometry geom) {
// only for solids
if (geom.getType() != GeometryType.SOLID) {
return;
}
for (Vertex v : geom.getVertices()) {
PolygonGraph polyGraph = new PolygonGraph();
for (Edge e : geom.getEdgesAdjacentTo(v)) {
polyGraph.addEdge(e);
}
List<List<Polygon>> components = polyGraph.findConnectedComponents();
if (components.size() > 1) {
CheckError err = new NonManifoldVertexError(geom, components, v);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
geom.addCheckResult(cr);
return;
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
geom.addCheckResult(cr);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new ManifoldVertexCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.NestedRingError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
public class NestedRingsCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_P_ORIENTATION_RINGS_SAME);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public NestedRingsCheck() {
super(CheckId.C_GE_P_INNER_RINGS_NESTED);
}
@Override
public void check(Polygon p) {
for (LinearRing interiorRing : p.getInnerRings()) {
for (LinearRing checkRing : p.getInnerRings()) {
if (checkRing == interiorRing) {
// do not compare with itself
continue;
}
// are all points from checkRing inside interiorRing?
if (areAllPointsInside(interiorRing, checkRing)) {
// found all vertices inside another interior ring
CheckError err = new NestedRingError(p, interiorRing, checkRing);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return;
}
}
}
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
}
private boolean areAllPointsInside(LinearRing ring, LinearRing checkRing) {
boolean isInside = true;
for (Vertex v : checkRing.getVertices()) {
if (!ring.isPointInside(v)) {
isInside = false;
break;
}
}
return isInside;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new NestedRingsCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.Unit;
import de.hft.stuttgart.citydoctor2.check.error.NullAreaError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Line3d;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionLine;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.tesselation.JoglTesselator;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedRing;
public class NullAreaCheck extends Check {
private static final String DELTA_NAME = "delta";
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<DefaultParameter> defaultParameters;
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);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<DefaultParameter> defParameters = new ArrayList<>();
defParameters.add(new DefaultParameter(DELTA_NAME, "0.0001", Unit.SQUARE_METER));
defaultParameters = Collections.unmodifiableList(defParameters);
}
private double delta = 0.0001d;
public NullAreaCheck() {
super(CheckId.NULL_AREA);
}
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
if (parameters.containsKey(DELTA_NAME)) {
delta = Double.parseDouble(parameters.get(DELTA_NAME));
}
}
@Override
public List<DefaultParameter> getDefaultParameter() {
return defaultParameters;
}
@Override
public void check(LinearRing lr) {
boolean areaIsNull = checkWithDistanceToRegressionLine(lr, delta);
CheckResult res;
if (areaIsNull) {
// degenerate ring
res = new CheckResult(this, ResultStatus.ERROR, new NullAreaError(lr));
} else {
res = new CheckResult(this, ResultStatus.OK, null);
}
lr.addCheckResult(res);
}
private boolean checkWithDistanceToRegressionLine(LinearRing lr, double delta) {
Line3d reg = OrthogonalRegressionLine.getRegressionLine(lr.getVertices());
for (Vertex v : lr.getVertices()) {
if (reg.distanceToPoint(v) >= delta) {
return false;
}
}
return true;
}
@SuppressWarnings("unused")
private boolean checkWithTesselation(LinearRing lr) {
TesselatedRing tr = JoglTesselator.tesselateRing(lr);
double area = 0D;
for (Triangle3d t : tr.getTriangles()) {
area = area + t.getArea();
}
return area < 0.0001;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new NullAreaCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPointsError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* CP_NUMPOINTS checks the minimum number of point in a linear ring. At least
* three vertices are minimum for a linear ring. <br/>
* <br/>
* <b>Dependency:</b> <br/>
* <dd>
* <dd>none</dd></dd>
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*/
public class NumPointsCheck extends Check {
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<CheckId> dependencies;
static {
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<CheckId> temp = new ArrayList<>();
temp.add(CheckId.C_GE_R_NOT_CLOSED);
dependencies = Collections.unmodifiableList(temp);
}
public NumPointsCheck() {
super(CheckId.C_GE_R_TOO_FEW_POINTS);
}
/**
* checks if every linear ring has at least 3 distinct points
*/
@Override
public void check(LinearRing lr) {
// put all vertices into a set, ignoring duplicates
Set<Vertex> vertices = new HashSet<>(lr.getVertices().size());
// if the ring has two vertices which are neighbors (can be the same point)
// count them, as they don't count as a distinct point
int numberOfContainingNeighbors = 0;
for (Vertex v : lr.getVertices()) {
boolean added = vertices.add(v);
if (!added) {
for (Vertex neighbor : v.getNeighbors()) {
if (vertices.contains(neighbor)) {
numberOfContainingNeighbors++;
}
}
}
}
// 2 points or less = always error
if (vertices.size() - numberOfContainingNeighbors < 3) {
CheckError err = new TooFewPointsError(lr);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
lr.addCheckResult(cr);
return;
}
// rest is okay
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
lr.addCheckResult(cr);
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new NumPointsCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.ManifoldEdgeError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
/**
* This class detects half edges with more than two neighbors, i.e.
* non-2-manifold geometries
*
* @author dwagner, alam
*/
public class OverUsedEdgeCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public OverUsedEdgeCheck() {
super(CheckId.C_GE_S_NON_MANIFOLD_EDGE);
}
@Override
public void check(Geometry g) {
// only check solids
if (g.getType() != GeometryType.SOLID) {
return;
}
List<Edge> errorEdges = new ArrayList<>();
for (Edge edge : g.getEdges()) {
if (edge.getNumberOfAllHalfEdges() > 2) {
errorEdges.add(edge);
}
}
CheckResult cr;
if (errorEdges.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError err = new ManifoldEdgeError(g, errorEdges);
cr = new CheckResult(this, ResultStatus.ERROR, err);
}
g.addCheckResult(cr);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new OverUsedEdgeCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.Unit;
import de.hft.stuttgart.citydoctor2.check.error.DistanceError;
import de.hft.stuttgart.citydoctor2.check.error.NormalDeviationError;
import de.hft.stuttgart.citydoctor2.check.error.TinyEdgeError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.CovarianceMatrix;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.tesselation.JoglTesselator;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
/**
* Check class to check for planarity issues
*
* @author Matthias Betz
*
*/
public class PlanarCheck extends Check {
private static final String DISTANCE = "distance";
private static final String DELTA_NAME = "distanceTolerance";
private static final String RADIANT = "angleTolerance";
private static final String TYPE = "type";
private static final String TINY_EDGE_TOLERANCE = "tinyEdgeTolerance";
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<DefaultParameter> defaultParameters;
static {
ArrayList<CheckId> deps = new ArrayList<>(4);
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);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>(1);
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<DefaultParameter> defParameters = new ArrayList<>(3);
defParameters.add(new DefaultParameter(TYPE, DISTANCE, Unit.NONE));
defParameters.add(new DefaultParameter(DELTA_NAME, "0.01", Unit.METER));
defParameters.add(new DefaultParameter(RADIANT, "0.1", Unit.RADIAN));
defParameters.add(new DefaultParameter(TINY_EDGE_TOLERANCE, "0.00000", Unit.METER));
defaultParameters = Collections.unmodifiableList(defParameters);
}
private String planarCheckType = DISTANCE;
private double rad = 0.1;
private double delta = 0.01;
private double tinyEdgeTolerance = 0.00000;
public PlanarCheck() {
super(CheckId.C_GE_P_NON_PLANAR);
}
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
if (parameters.containsKey(TYPE)) {
planarCheckType = parameters.get(TYPE);
} else {
planarCheckType = DISTANCE;
}
if (parameters.containsKey(RADIANT)) {
rad = Double.parseDouble(parameters.get(RADIANT));
} else {
rad = 0.1;
}
if (parameters.containsKey(DELTA_NAME)) {
delta = Double.parseDouble(parameters.get(DELTA_NAME));
} else {
delta = 0.01;
}
if (parameters.containsKey(TINY_EDGE_TOLERANCE)) {
tinyEdgeTolerance = Double.parseDouble(parameters.get(TINY_EDGE_TOLERANCE));
} else {
tinyEdgeTolerance = 0.00002;
}
}
@Override
public List<DefaultParameter> getDefaultParameter() {
return defaultParameters;
}
@Override
public void check(Polygon p) {
if (DISTANCE.equals(planarCheckType)) {
planarDistance(p);
} else if ("angle".equals(planarCheckType)) {
// check for tiny edge as well
// store all used points in temporary list
ArrayList<Vertex> vertices = collectVertices(p);
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
Vector3d eigenvalues = OrthogonalRegressionPlane.getEigenvalues(ed);
if (checkEigenvalues(p, eigenvalues)) {
// found tiny edge error, abort further checking
return;
}
planarNormalDeviation(p);
} else if ("both".equals(planarCheckType)) {
planarDistance(p);
planarNormalDeviation(p);
} else {
throw new IllegalStateException("Illegal planar check type was given: " + planarCheckType
+ "\nChoose one of: distance, angle, both");
}
}
private void planarNormalDeviation(Polygon p) {
TesselatedPolygon tp = JoglTesselator.tesselatePolygon(p);
ArrayList<Vector3d> normals = new ArrayList<>();
for (Triangle3d t : tp.getTriangles()) {
Vector3d normal = t.getNormal();
normals.add(normal);
}
Vector3d averageNormal = calculateAverageNormal(normals);
averageNormal.normalize();
for (Vector3d normal : normals) {
normal.normalize();
double deviation = normal.dot(averageNormal);
double radiant = Math.acos(deviation);
if (radiant > rad) {
CheckError err = new NormalDeviationError(p, radiant);
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return;
}
}
CheckResult cr = p.getCheckResult(this.getCheckId());
// only change check result if missing, otherwise might override error from
// distance check
if (cr == null) {
p.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
}
}
private Vector3d calculateAverageNormal(ArrayList<Vector3d> normals) {
double x = 0D;
double y = 0D;
double z = 0D;
for (Vector3d normal : normals) {
x += normal.getX();
y += normal.getY();
z += normal.getZ();
}
x = x / normals.size();
y = y / normals.size();
z = z / normals.size();
return new Vector3d(x, y, z);
}
private void planarDistance(Polygon p) {
// store all used points in temporary list
ArrayList<Vertex> vertices = collectVertices(p);
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
Vector3d eigenvalues = OrthogonalRegressionPlane.getEigenvalues(ed);
if (checkEigenvalues(p, eigenvalues)) {
// found tiny edge error, abort further checking
return;
}
Plane plane = OrthogonalRegressionPlane.calculatePlane(centroid, ed, eigenvalues);
for (Vertex v : vertices) {
double distance = plane.getDistance(v);
if (distance > delta) {
CheckError err = new DistanceError(p, distance, v, plane);
p.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
return;
}
}
CheckResult cr = p.getCheckResult(this.getCheckId());
// only change check result if missing
if (cr == null) {
p.addCheckResult(new CheckResult(this, ResultStatus.OK, null));
}
}
private ArrayList<Vertex> collectVertices(Polygon p) {
ArrayList<Vertex> vertices = new ArrayList<>();
// only go to n - 1 points, because last point = first point
for (int i = 0; i < p.getExteriorRing().getVertices().size() - 1; i++) {
Vertex v = p.getExteriorRing().getVertices().get(i);
vertices.add(v);
}
for (LinearRing lr : p.getInnerRings()) {
for (int i = 0; i < lr.getVertices().size() - 1; i++) {
Vertex v = lr.getVertices().get(i);
vertices.add(v);
}
}
return vertices;
}
private boolean checkEigenvalues(Polygon p, Vector3d eigenvalues) {
int nrOfEigenvaluesBelowTolerance = 0;
for (double d : eigenvalues.getCoordinates()) {
if (d <= tinyEdgeTolerance) {
nrOfEigenvaluesBelowTolerance++;
}
}
if (nrOfEigenvaluesBelowTolerance >= 2) {
CheckError err = new TinyEdgeError(p);
p.addCheckResult(new CheckResult(this, ResultStatus.ERROR, err));
return true;
}
return false;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new PlanarCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.SameOrientationError;
import de.hft.stuttgart.citydoctor2.checks.util.Orientation;
import de.hft.stuttgart.citydoctor2.checks.util.RingOrientationUtil;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.math.Polygon2d;
import de.hft.stuttgart.citydoctor2.math.Ring2d;
public class PolygonSameOrientationCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_NON_PLANAR);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public PolygonSameOrientationCheck() {
super(CheckId.C_GE_P_ORIENTATION_RINGS_SAME);
}
@Override
public void check(Polygon p) {
if (p.getInnerRings().isEmpty()) {
// no inner rings, valid
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
return;
}
Polygon2d poly2d = Polygon2d.withProjection(p);
Orientation extOrientation = RingOrientationUtil.calculateOrientation(poly2d.getExterior());
for (Ring2d inner : poly2d.getInteriorRings()) {
if (RingOrientationUtil.calculateOrientation(inner) == extOrientation) {
CheckError err = new SameOrientationError(p, inner.getOriginal());
CheckResult cr = new CheckResult(this, ResultStatus.ERROR, err);
p.addCheckResult(cr);
return;
}
}
// no error found, or the method would have terminated previously
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new PolygonSameOrientationCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.PolygonSelfIntError;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.math.Polygon2d;
import de.hft.stuttgart.citydoctor2.math.Ring2d;
import de.hft.stuttgart.citydoctor2.math.Segment2d;
import de.hft.stuttgart.citydoctor2.math.Vector2d;
import de.hft.stuttgart.citydoctor2.utils.Pair;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
public class PolygonSelfIntCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_P_HOLE_OUTSIDE);
deps.add(CheckId.C_GE_P_INNER_RINGS_NESTED);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Polygon.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public PolygonSelfIntCheck() {
super(CheckId.C_GE_P_INTERSECTING_RINGS);
}
@Override
public void check(Polygon p) {
if (p.getInnerRings().isEmpty()) {
// polygons with no internal rings are always ok
CheckResult cr = new CheckResult(this, ResultStatus.OK, null);
p.addCheckResult(cr);
return;
}
CheckResult cr;
SerializablePair<LinearRing, LinearRing> intersectingRings = interiorRingsIntersectWithExterior(p);
if (intersectingRings != null) {
CheckError err = new PolygonSelfIntError(p, intersectingRings);
cr = new CheckResult(this, ResultStatus.ERROR, err);
} else {
cr = new CheckResult(this, ResultStatus.OK, null);
}
p.addCheckResult(cr);
}
public SerializablePair<LinearRing, LinearRing> interiorRingsIntersectWithExterior(Polygon p) {
List<Pair<LinearRing, List<Segment2d>>> ringSegments = projectTo2D(p);
for (int i = 0; i < ringSegments.size() - 1; i++) {
Pair<LinearRing, List<Segment2d>> ring = ringSegments.get(i);
for (int j = i + 1; j < ringSegments.size(); j++) {
Pair<LinearRing, List<Segment2d>> ring2 = ringSegments.get(j);
if (doRingsIntersect(ring.getValue1(), ring2.getValue1())) {
return new SerializablePair<>(ring.getValue0(), ring2.getValue0());
}
}
}
return null;
}
private List<Pair<LinearRing, List<Segment2d>>> projectTo2D(Polygon p) {
List<Pair<LinearRing, List<Segment2d>>> ringSegments = new ArrayList<>();
Polygon2d projectedPolygon = Polygon2d.withProjection(p);
createSegments(ringSegments, projectedPolygon.getExterior());
for (Ring2d interior : projectedPolygon.getInteriorRings()) {
createSegments(ringSegments, interior);
}
return ringSegments;
}
private void createSegments(List<Pair<LinearRing, List<Segment2d>>> ringSegments, Ring2d ring) {
List<Vector2d> vertices = ring.getVertices();
List<Segment2d> segments = new ArrayList<>();
for (int i = 0; i < vertices.size() - 1; i++) {
segments.add(new Segment2d(vertices.get(i), vertices.get(i + 1)));
}
ringSegments.add(new Pair<>(ring.getOriginal(), segments));
}
private boolean doRingsIntersect(List<Segment2d> r1, List<Segment2d> r2) {
for (Segment2d seg1 : r1) {
for (Segment2d seg2 : r2) {
if (seg1.intersects(seg2)) {
return true;
}
}
}
return false;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new PolygonSelfIntCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.DefaultParameter;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.Unit;
import de.hft.stuttgart.citydoctor2.check.error.EdgeIntersectionError;
import de.hft.stuttgart.citydoctor2.check.error.PointTouchesEdgeError;
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.DistanceResult;
import de.hft.stuttgart.citydoctor2.math.Segment3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
public class RingSelfIntCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
private static final List<DefaultParameter> defaultParameters;
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);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(LinearRing.class);
applicableToClasses = Collections.unmodifiableList(classes);
ArrayList<DefaultParameter> defaultParameter = new ArrayList<>();
defaultParameter.add(new DefaultParameter(EPSILON_NAME, "0.0001", Unit.METER));
defaultParameters = Collections.unmodifiableList(defaultParameter);
}
private double epsilon = 0.0001;
public RingSelfIntCheck() {
super(CheckId.C_GE_R_SELF_INTERSECTION);
}
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
String epsilonString = parameters.get(EPSILON_NAME);
if (epsilonString == null) {
epsilon = 0.0001;
} else {
epsilon = Double.parseDouble(epsilonString);
}
}
@Override
public void check(LinearRing lr) {
checkRingJava(lr);
}
private void checkRingJava(LinearRing lr) {
List<Edge> edges = getEdgesForRing(lr);
for (Edge e : edges) {
if (checkForPointsTouchingEdge(lr, e)) {
return;
}
}
for (int i = 0; i < edges.size() - 1; i++) {
Edge e1 = edges.get(i);
for (int j = i + 1; j < edges.size(); j++) {
Edge e2 = edges.get(j);
if (e1.getConnectionPoint(e2) != null) {
continue;
}
Segment3d s1 = new Segment3d(e1.getFrom(), e1.getTo());
Segment3d s2 = new Segment3d(e2.getFrom(), e2.getTo());
DistanceResult dr = s1.getDistanceResult(s2);
if (dr.getDistance() < epsilon) {
// intersection
CheckError err = new EdgeIntersectionError(lr, e1, e2, dr.getPoint1());
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 checkForPointsTouchingEdge(LinearRing lr, Edge e1) {
Segment3d seg = new Segment3d(e1.getFrom(), e1.getTo());
for (Vertex v : lr.getVertices()) {
if (v != e1.getFrom() && v != e1.getTo() && seg.getDistance(v) < epsilon) {
CheckError err = new PointTouchesEdgeError(lr, e1, 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 List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public List<DefaultParameter> getDefaultParameter() {
return defaultParameters;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new RingSelfIntCheck();
}
}
/*-
* 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.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.CheckType;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.SolidNotClosedError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
/**
* This class detects half edges without neighbor, i.e. holes in solid
* geometries
*
* @author dwagner, alam
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
*/
public class SolidNotClosedCheck extends Check {
private static final List<CheckId> dependencies;
private static final List<Class<? extends Checkable>> applicableToClasses;
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);
deps.add(CheckId.C_GE_R_SELF_INTERSECTION);
deps.add(CheckId.C_GE_P_INTERIOR_DISCONNECTED);
deps.add(CheckId.C_GE_P_INTERSECTING_RINGS);
deps.add(CheckId.C_GE_P_NON_PLANAR);
deps.add(CheckId.C_GE_S_TOO_FEW_POLYGONS);
dependencies = Collections.unmodifiableList(deps);
ArrayList<Class<? extends Checkable>> classes = new ArrayList<>();
classes.add(Geometry.class);
applicableToClasses = Collections.unmodifiableList(classes);
}
public SolidNotClosedCheck() {
super(CheckId.C_GE_S_NOT_CLOSED);
}
@Override
public void check(Geometry g) {
// only for solids
if (g.getType() != GeometryType.SOLID) {
return;
}
List<Edge> errorEdges = new ArrayList<>();
for (Edge e : g.getEdges()) {
if (e.getNumberOfAllHalfEdges() < 2) {
errorEdges.add(e);
}
}
CheckResult cr;
if (errorEdges.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
CheckError err = new SolidNotClosedError(g, errorEdges);
cr = new CheckResult(this, ResultStatus.ERROR, err);
}
g.addCheckResult(cr);
}
@Override
public List<CheckId> getDependencies() {
return dependencies;
}
@Override
public List<Class<? extends Checkable>> getApplicableToClasses() {
return applicableToClasses;
}
@Override
public CheckType getType() {
return CheckType.GEOMETRY;
}
@Override
public Check createNewInstance() {
return new SolidNotClosedCheck();
}
}
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