Commit 1da24add authored by Matthias Betz's avatar Matthias Betz
Browse files

Merge branch 'CheckEngineRework' into 'master'

Check engine rework

See merge request betzms/citydoctor2!4
parents 09470a4d cd494d75
Pipeline #2115 passed with stage
in 2 minutes and 53 seconds
......@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
......@@ -46,15 +47,16 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
* The general check class containing the methods which will be called by check
* engine. To create a new check override one or more check(...) methods and
* implement the {@link Check#getApplicableToClasses()} method by returning a
* list of classes you wish to check. CheckResult objects can be attached to
* every Checkable. If the check has parameters override the
* {@link Check#init(Map, ParserConfiguration)} method to get the value of a
* parameter if a user has specified one in the validation plan. It will be
* contained in the Map as a string. If you have parameters you will need to
* override the {@link Check#getDefaultParameter()} method as well to declare
* which parameters you have, which name and default value they have. The name
* will be the key for the Map in the init method previously mentioned. If your
* check has dependencies you can declare them in the
* list of classes you wish to check.<br>
* CheckResult objects can be attached to every Checkable. If the check has
* parameters override the {@link Check#init(Map, ParserConfiguration)} method
* to get the value of a parameter if a user has specified one in the validation
* plan. It will be contained in the Map as a string. If you have parameters you
* will need to override the {@link Check#getDefaultParameter()} method as well
* to declare which parameters you have, which name and default value they have.
* The name will be the key for the Map in the init method previously
* mentioned.<br>
* If your check has dependencies you can declare them in the
* {@link Check#getDependencies()} method. Be sure not to create cyclic
* dependencies as that would result in undefined behavior.
*
......@@ -66,7 +68,7 @@ public abstract class Check {
private List<Class<Checkable>> applicableToClasses = new ArrayList<>(2);
@SuppressWarnings("unchecked")
public Check() {
protected Check() {
Method[] declaredMethods = getClass().getDeclaredMethods();
for (Method m : declaredMethods) {
if ("check".equals(m.getName())) {
......@@ -97,6 +99,8 @@ public abstract class Check {
public List<CheckId> getDependencies() {
return Collections.emptyList();
}
public abstract Set<Requirement> appliesToRequirements();
/**
* Getter for the check id.
......@@ -110,7 +114,7 @@ public abstract class Check {
*
* @return the check type
*/
public abstract CheckType getType();
public abstract RequirementType getType();
/**
* Checks whether the check can be executed on this checkable, meaning the
......@@ -145,7 +149,7 @@ public abstract class Check {
}
return true;
}
private boolean canBeApplied(Checkable c) {
for (Class<Checkable> checkableClass : getApplicableToClasses()) {
if (checkableClass.isAssignableFrom(c.getCheckClass())) {
......@@ -306,7 +310,7 @@ public abstract class Check {
*
* @param params the parameter map containing the parameters for the check in
* String form. The key should be the same String provided by the
* {@link Check#getDefaultParameter()} method
* {@link Requirement#getDefaultParameter()} method
* @param config sometimes there are global parameters which can be used by
* checks. Those are be stored in this container
*/
......@@ -314,16 +318,6 @@ public abstract class Check {
}
/**
* This methods gives checks the possibility to declare default parameters, used
* primarily in the GUI.
*
* @return a list of default parameters, not null
*/
public List<DefaultParameter> getDefaultParameter() {
return Collections.emptyList();
}
@Override
public String toString() {
return "Check [id=" + getCheckId() + "]";
......
......@@ -44,14 +44,13 @@ public class CheckId implements Serializable {
public static final CheckId C_GE_P_HOLE_OUTSIDE = new CheckId("C_GE_P_HOLE_OUTSIDE");
public static final CheckId IS_CEILING = new CheckId("IS_CEILING");
public static final CheckId C_GE_P_INNER_RINGS_NESTED = new CheckId("C_GE_P_INNER_RINGS_NESTED");
public static final CheckId C_SEM_BS_NOT_CEILING = new CheckId("C_SEM_BS_NOT_CEILING");
public static final CheckId C_SEM_BS_NOT_FLOOR = new CheckId("C_SEM_BS_NOT_FLOOR");
public static final CheckId C_SEM_BS_NOT_GROUND = new CheckId("C_SEM_BS_NOT_GROUND");
public static final CheckId C_SEM_F_MISSING_ID = new CheckId("C_SEM_F_MISSING_ID");
public static final CheckId C_SEM_BS_GROUND_NOT_FRAGMENTED = new CheckId("C_SEM_BS_GROUND_NOT_FRAGMENTED");
public static final CheckId C_SEM_BS_IS_WALL = new CheckId("C_SEM_BS_IS_WALL");
public static final CheckId C_SEM_SCHEMATRON = new CheckId("C_SEM_SCHEMATRON");
public static final CheckId C_SEM_BS_ROOF_NOT_FRAGMENTED = new CheckId("C_SEM_BS_ROOF_NOT_FRAGMENTED");
public static final CheckId C_SE_BS_IS_CEILING = new CheckId("C_SE_BS_IS_CEILING");
public static final CheckId C_SE_BS_IS_FLOOR = new CheckId("C_SE_BS_IS_FLOOR");
public static final CheckId C_SE_BS_IS_GROUND = new CheckId("C_SE_BS_IS_GROUND");
public static final CheckId C_SE_BS_GROUND_NOT_FRAGMENTED = new CheckId("C_SE_BS_GROUND_NOT_FRAGMENTED");
public static final CheckId C_SE_BS_IS_WALL = new CheckId("C_SE_BS_IS_WALL");
public static final CheckId C_SE_SCHEMATRON = new CheckId("C_SE_SCHEMATRON");
public static final CheckId C_SE_BS_ROOF_UNFRAGMENTED = new CheckId("C_SE_BS_ROOF_UNFRAGMENTED");
public static final CheckId C_GE_S_NOT_CLOSED = new CheckId("C_GE_S_NOT_CLOSED");
public static final CheckId C_GE_P_ORIENTATION_RINGS_SAME = new CheckId("C_GE_P_ORIENTATION_RINGS_SAME");
......
......@@ -31,7 +31,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
/**
* Interface to indicate that this object can be checked by Checks.
*
* @author Matthias Betz - 12bema1bif@hft-stuttgart.de
* @author Matthias Betz
*
*/
public abstract class Checkable implements Serializable {
......
......@@ -18,6 +18,8 @@
*/
package de.hft.stuttgart.citydoctor2.check;
import java.io.Serializable;
/**
* Describes a parameter for a check including the default value as a String.
* All values are a String as they are passed to the check as a Map<String,
......@@ -26,15 +28,17 @@ package de.hft.stuttgart.citydoctor2.check;
* @author Matthias Betz
*
*/
public class DefaultParameter {
public class DefaultParameter implements Serializable {
private static final long serialVersionUID = -4587211298078974066L;
private String name;
private String defaultValue;
private String value;
private Unit unitType;
public DefaultParameter(String name, String defaultValue, Unit unitType) {
this.name = name;
this.defaultValue = defaultValue;
this.value = defaultValue;
this.unitType = unitType;
}
......@@ -56,8 +60,8 @@ public class DefaultParameter {
*
* @return the defaultValue
*/
public String getDefaultValue() {
return defaultValue;
public String getValue() {
return value;
}
/*
......@@ -68,10 +72,10 @@ public class DefaultParameter {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("CheckParameter [name=");
builder.append("DefaultParameter [name=");
builder.append(name);
builder.append(", defaultValue=");
builder.append(defaultValue);
builder.append(", value=");
builder.append(value);
builder.append("]");
return builder.toString();
}
......
......@@ -56,14 +56,13 @@ public class ErrorId implements Serializable {
public static final ErrorId GE_P_ORIENTATION_RINGS_SAME = new ErrorId("GE_P_ORIENTATION_RINGS_SAME");
public static final ErrorId GE_S_NOT_CLOSED = new ErrorId("GE_S_NOT_CLOSED");
public static final ErrorId GE_S_TOO_FEW_POLYGONS = new ErrorId("GE_S_TOO_FEW_POLYGONS");
public static final ErrorId SEM_F_MISSING_ID = new ErrorId("SEM_F_MISSING_ID");
public static final ErrorId SEM_BS_NOT_CEILING = new ErrorId("SEM_BS_NOT_CEILING");
public static final ErrorId SEM_BS_NOT_WALL = new ErrorId("SEM_BS_NOT_WALL");
public static final ErrorId SEM_BS_NOT_FLOOR = new ErrorId("SEM_BS_NOT_FLOOR");
public static final ErrorId SEM_BS_NOT_GROUND = new ErrorId("SEM_BS_NOT_GROUND");
public static final ErrorId SEM_SCHEMATRON_ERROR = new ErrorId("SEM_SCHEMATRON_ERROR");
public static final ErrorId SEM_BS_UNFRAGMENTED = new ErrorId("SEM_BS_UNFRAGMENTED");
public static final ErrorId GE_P_TINY_EDGE = new ErrorId("GE_P_TINY_EDGE");
public static final ErrorId SE_BS_NOT_CEILING = new ErrorId("SE_BS_NOT_CEILING");
public static final ErrorId SE_BS_NOT_WALL = new ErrorId("SE_BS_NOT_WALL");
public static final ErrorId SE_BS_NOT_FLOOR = new ErrorId("SE_BS_NOT_FLOOR");
public static final ErrorId SE_BS_NOT_GROUND = new ErrorId("SE_BS_NOT_GROUND");
public static final ErrorId SE_SCHEMATRON_ERROR = new ErrorId("SE_SCHEMATRON_ERROR");
public static final ErrorId SE_BS_UNFRAGMENTED = new ErrorId("SE_BS_UNFRAGMENTED");
public static final ErrorId GE_P_DEGENERATED_POLYGON = new ErrorId("GE_P_DEGENERATED_POLYGON");
private String name;
......
/*-
* 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.check;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Requirement implements Serializable {
private static final long serialVersionUID = -590639811553512803L;
private static final String DISTANCE_TOLERANCE = "distanceTolerance";
private static final String ANGLE_TOLERANCE = "angleTolerance";
private static final String TYPE_STRING = "type";
private static final String DEGENERATED_POLYGON_TOLERANCE = "degeneratedPolygonTolerance";
private static final String LOWER_ANGLE_NAME = "lowerAngle";
private static final String UPPER_ANGLE_NAME = "upperAngle";
private static final String MAX_ANGLE_DEVIATION = "maxAngleDeviation";
public static final Requirement R_GE_R_TOO_FEW_POINTS = new Requirement("R_GE_R_TOO_FEW_POINTS", RequirementType.GEOMETRY);
public static final Requirement R_GE_R_NOT_CLOSED = new Requirement("R_GE_R_NOT_CLOSED", RequirementType.GEOMETRY);
public static final Requirement R_GE_R_CONSECUTIVE_POINTS_SAME = new Requirement("R_GE_R_CONSECUTIVE_POINTS_SAME", RequirementType.GEOMETRY);
public static final Requirement R_GE_R_SELF_INTERSECTION = new Requirement("R_GE_R_SELF_INTERSECTION", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_NON_PLANAR = new Requirement("R_GE_P_NON_PLANAR", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_INTERIOR_DISCONNECTED = new Requirement("R_GE_P_INTERIOR_DISCONNECTED", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_INTERSECTING_RINGS = new Requirement("R_GE_P_INTERSECTING_RINGS", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_HOLE_OUTSIDE = new Requirement("R_GE_P_HOLE_OUTSIDE", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_ORIENTATION_RINGS_SAME = new Requirement("R_GE_P_ORIENTATION_RINGS_SAME", RequirementType.GEOMETRY);
public static final Requirement R_GE_P_INNER_RINGS_NESTED = new Requirement("R_GE_P_INNER_RINGS_NESTED", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_TOO_FEW_POLYGONS = new Requirement("R_GE_S_TOO_FEW_POLYGONS", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_NOT_CLOSED = new Requirement("R_GE_S_NOT_CLOSED", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_NON_MANIFOLD_EDGE = new Requirement("R_GE_S_NON_MANIFOLD_EDGE", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_POLYGON_WRONG_ORIENTATION = new Requirement(
"R_GE_S_POLYGON_WRONG_ORIENTATION", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_ALL_POLYGONS_WRONG_ORIENTATION = new Requirement(
"R_GE_S_ALL_POLYGONS_WRONG_ORIENTATION", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_NON_MANIFOLD_VERTEX = new Requirement("R_GE_S_NON_MANIFOLD_VERTEX", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_SELF_INTERSECTION = new Requirement("R_GE_S_SELF_INTERSECTION", RequirementType.GEOMETRY);
public static final Requirement R_GE_S_MULTIPLE_CONNECTED_COMPONENTS = new Requirement(
"R_GE_S_MULTIPLE_CONNECTED_COMPONENTS", RequirementType.GEOMETRY);
public static final Requirement R_SE_ATTRIBUTES_EXISTING = new Requirement("R_SE_ATTRIBUTES_EXISTING", RequirementType.SEMANTIC);
public static final Requirement R_SE_ATTRIBUTES_CORRECT = new Requirement("R_SE_ATTRIBUTES_CORRECT", RequirementType.SEMANTIC);
public static final Requirement R_GE_R_NULL_AREA = new Requirement("R_GE_R_NULL_AREA", RequirementType.GEOMETRY);
public static final Requirement R_SE_BS_GROUND_UNFRAGMENTED = new Requirement("R_SE_BS_GROUND_UNFRAGMENTED", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_ROOF_UNFRAGMENTED = new Requirement("R_SE_BS_GROUND_UNFRAGMENTED", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_CEILING = new Requirement("R_SE_BS_IS_CEILING", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_FLOOR = new Requirement("R_SE_BS_IS_FLOOR", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_WALL = new Requirement("R_SE_BS_IS_WALL", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_GROUND = new Requirement("R_SE_BS_IS_GROUND", RequirementType.SEMANTIC);
static {
// fill requirements with default parameters
ArrayList<DefaultParameter> defaultParameters = new ArrayList<>();
defaultParameters.add(new DefaultParameter(TYPE_STRING, "distance", Unit.NONE));
defaultParameters.add(new DefaultParameter(DISTANCE_TOLERANCE, "0.01", Unit.METER));
defaultParameters.add(new DefaultParameter(ANGLE_TOLERANCE, "1", Unit.DEGREE));
defaultParameters.add(new DefaultParameter(DEGENERATED_POLYGON_TOLERANCE, "0.00000", Unit.METER));
R_GE_P_NON_PLANAR.parameters = Collections.unmodifiableList(defaultParameters);
defaultParameters = new ArrayList<>();
defaultParameters.add(new DefaultParameter(LOWER_ANGLE_NAME, "45", Unit.DEGREE));
defaultParameters.add(new DefaultParameter(UPPER_ANGLE_NAME, "135", Unit.DEGREE));
R_SE_BS_IS_WALL.parameters = Collections.unmodifiableList(defaultParameters);
defaultParameters.add(new DefaultParameter(MAX_ANGLE_DEVIATION, "1", Unit.DEGREE));
R_SE_BS_ROOF_UNFRAGMENTED.parameters = Collections.unmodifiableList(defaultParameters);
}
private String id;
private RequirementType type;
private List<DefaultParameter> parameters;
public Requirement(String id, RequirementType type) {
this.id = id;
this.type = type;
parameters = Collections.emptyList();
}
public String getId() {
return id;
}
public List<DefaultParameter> getDefaultParameter() {
return parameters;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Requirement other = (Requirement) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return id;
}
public RequirementType getType() {
return type;
}
}
......@@ -26,7 +26,7 @@ package de.hft.stuttgart.citydoctor2.check;
* @author Matthias Betz
*
*/
public enum CheckType {
public enum RequirementType {
GEOMETRY, SEMANTIC
......
......@@ -64,7 +64,7 @@ public class DegeneratedPolygonError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.GE_P_TINY_EDGE;
return ErrorId.GE_P_DEGENERATED_POLYGON;
}
@Override
......
......@@ -79,7 +79,7 @@ public class NotCeilingError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_BS_NOT_CEILING;
return ErrorId.SE_BS_NOT_CEILING;
}
@Override
......
......@@ -77,7 +77,7 @@ public class NotFloorError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_BS_NOT_FLOOR;
return ErrorId.SE_BS_NOT_FLOOR;
}
@Override
......
......@@ -79,7 +79,7 @@ public class NotGroundError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_BS_NOT_GROUND;
return ErrorId.SE_BS_NOT_GROUND;
}
@Override
......
......@@ -82,7 +82,7 @@ public class NotWallError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_BS_NOT_WALL;
return ErrorId.SE_BS_NOT_WALL;
}
@Override
......
......@@ -94,7 +94,7 @@ public class SchematronError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_SCHEMATRON_ERROR;
return ErrorId.SE_SCHEMATRON_ERROR;
}
@Override
......
......@@ -77,7 +77,7 @@ public class SurfaceUnfragmentedError implements CheckError {
@Override
public ErrorId getErrorId() {
return ErrorId.SEM_BS_UNFRAGMENTED;
return ErrorId.SE_BS_UNFRAGMENTED;
}
@Override
......
......@@ -42,6 +42,10 @@ public class BoundingBox {
public static BoundingBox of(List<Polygon> polygons) {
return BoundingBoxCalculator.calculateBoundingBox(polygons);
}
public static BoundingBox ofPoints(List<? extends Vector3d> points) {
return BoundingBoxCalculator.calculateBoundingBoxFromPoints(points);
}
/**
* Creates an axis aligned bounding box of the whole model.
......
......@@ -56,6 +56,12 @@ public class CovarianceMatrix {
covValues[1][2] += ydiff * zdiff;
covValues[2][2] += zdiff * zdiff;
}
covValues[0][0] /= vertices.size();
covValues[0][1] /= vertices.size();
covValues[0][2] /= vertices.size();
covValues[1][1] /= vertices.size();
covValues[1][2] /= vertices.size();
covValues[2][2] /= vertices.size();
// the covariance matrix is symmetric, so we can fill in the remaining values
covValues[1][0] = covValues[0][1];
covValues[2][0] = covValues[0][2];
......
......@@ -135,7 +135,7 @@ public class Matrix3x3d {
/**
* Multiplies this matrix with a 3-dim vector.
*
* @param other the vector
* @param the multiplied vector
* @return the result stored in a new vector
*/
public Vector3d mult(Vector3d other) {
......
......@@ -55,10 +55,10 @@ public class OrthogonalRegressionPlane {
double eig2 = eigenvalues.getZ();
Matrix v = ed.getV();
Matrix eigenVector;
if (eig0 < eig1 && eig0 < eig2) {
if (eig0 <= eig1 && eig0 <= eig2) {
// the first eigenvalue is the lowest
eigenVector = v.getMatrix(0, 2, 0, 0);
} else if (eig1 < eig0 && eig1 < eig2) {
} else if (eig1 <= eig0 && eig1 <= eig2) {
// the second eigenvalue is the lowest
eigenVector = v.getMatrix(0, 2, 1, 1);
} else {
......
......@@ -63,17 +63,20 @@ public class BoundingBoxCalculator {
for (Vertex v : p.getExteriorRing().getVertices()) {
if (v.getX() < lowX) {
lowX = v.getX();
} else if (v.getX() > highX) {
}
if (v.getX() > highX) {
highX = v.getX();
}
if (v.getY() < lowY) {
lowY = v.getY();
} else if (v.getY() > highY) {
}
if (v.getY() > highY) {
highY = v.getY();
}
if (v.getZ() < lowZ) {
lowZ = v.getZ();
} else if (v.getZ() > highZ) {
}
if (v.getZ() > highZ) {
highZ = v.getZ();
}
}
......@@ -92,7 +95,7 @@ public class BoundingBoxCalculator {
*/
public static BoundingBox calculateBoundingBox(CityDoctorModel model) {
Vector3d low = new Vector3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
Vector3d high = new Vector3d(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE);
Vector3d high = new Vector3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
findMinMax(low, high, model.getBuildings());
findMinMax(low, high, model.getBridges());
......@@ -107,6 +110,40 @@ public class BoundingBoxCalculator {
return BoundingBox.of(result);
}
public static BoundingBox calculateBoundingBoxFromPoints(List<? extends Vector3d> points) {
double lowX = Double.MAX_VALUE;
double highX = Double.NEGATIVE_INFINITY;
double lowY = Double.MAX_VALUE;
double highY = Double.NEGATIVE_INFINITY;
double lowZ = Double.MAX_VALUE;
double highZ = Double.NEGATIVE_INFINITY;
// only need to check exterior rings
for (Vector3d v : points) {
if (v.getX() < lowX) {
lowX = v.getX();
}
if (v.getX() > highX) {
highX = v.getX();
}
if (v.getY() < lowY) {
lowY = v.getY();
}
if (v.getY() > highY) {
highY = v.getY();
}
if (v.getZ() < lowZ) {
lowZ = v.getZ();
}
if (v.getZ() > highZ) {
highZ = v.getZ();
}
}
Vector3d[] result = new Vector3d[2];
result[0] = new Vector3d(lowX, lowY, lowZ);
result[1] = new Vector3d(highX, highY, highZ);
return BoundingBox.of(result);