Java tutorial
/** * Copyright 2010-2016 interactive instruments GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.interactive_instruments.etf.bsxm; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import nl.vrom.roo.validator.core.ValidatorContext; import nl.vrom.roo.validator.core.ValidatorMessage; import nl.vrom.roo.validator.core.dom4j.handlers.GeometryElementHandler; import com.github.davidmoten.rtree.geometry.Geometries; import com.google.common.base.Joiner; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.util.GeometryExtracter; import com.vividsolutions.jts.operation.union.CascadedPolygonUnion; import org.basex.api.dom.BXElem; import org.basex.api.dom.BXNode; import org.basex.data.Data; import org.basex.query.QueryException; import org.basex.query.QueryModule; import org.basex.query.value.Value; import org.basex.query.value.node.ANode; import org.basex.query.value.node.DBNode; import org.basex.query.value.seq.Empty; import org.basex.util.InputInfo; import org.deegree.geometry.Geometry; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; /** * This module supports the validation of geometries as well as computing the * spatial relationship between geometries. * <p> * NOTE 1: the validation and spatial relationship methods only support specific * sets of geometry types - please see the documentation of the respective * methods for details on which geometry types are supported. * * @author Johannes Echterhoff (echterhoff <at> interactive-instruments * <dot> de) * */ public class GmlGeoX extends QueryModule { public enum SpatialRelOp { CONTAINS, CROSSES, EQUALS, INTERSECTS, ISDISJOINT, ISWITHIN, OVERLAPS, TOUCHES } private static final Logger LOGGER = LoggerFactory.getLogger(GmlGeoX.class); private static final Pattern INTERSECTIONPATTERN = Pattern.compile("[0-2\\*TF]{9}"); protected final GmlGeoXUtils geoutils = new GmlGeoXUtils(); private final Set<String> gmlGeometries = new TreeSet<String>(); private static final boolean debug = LOGGER.isDebugEnabled(); private GeometryManager mgr = null; private int count = 0; private int count2 = 0; public GmlGeoX() throws QueryException { logMemUsage("GmlGeoX#init"); // default geometry types for which validation is performed registerGmlGeometry("Point"); registerGmlGeometry("Polygon"); registerGmlGeometry("Surface"); registerGmlGeometry("Curve"); registerGmlGeometry("LinearRing"); registerGmlGeometry("MultiPoint"); registerGmlGeometry("MultiPolygon"); registerGmlGeometry("MultiGeometry"); registerGmlGeometry("MultiSurface"); registerGmlGeometry("MultiCurve"); registerGmlGeometry("Ring"); registerGmlGeometry("LineString"); } /** * Calls the {@link #validate(Object, String)} method, with <code>null</code> * as the bitmask, resulting in a validation with all tests enabled. * <p> * See the documentation of the {@link #validate(Object, String)} method for * a description of the supported geometry types. * * @param o * @return * @throws QueryException */ @Requires(Permission.NONE) public String validate(Object o) throws QueryException { return this.validate(o, null); } /** * Validates the given (GML geometry) node. * <p> * By default validation is only performed for the following GML geometry * elements: Point, Polygon, Surface, Curve, LinearRing, MultiPolygon, * MultiGeometry, MultiSurface, MultiCurve, Ring, and LineString. The set of * GML elements to validate can be modified via the following methods: * {@link #registerGmlGeometry(String)}, * {@link #unregisterGmlGeometry(String)}, and * {@link #unregisterAllGmlGeometries()}. These methods are also available * for XQueries. * <p> * The validation tasks to perform can be specified via the given mask. The * mask is a simple string, where the character '1' at the position of a * specific test (assuming a 1-based index) specifies that the test shall be * performed. If the mask does not contain a character at the position of a * specific test (because the mask is empty or the length is smaller than * the position), then the test will be executed. * <p> * The following tests are available: * <p> * <table> * <tr> * <th>Position</th> * <th>Test Name</th> * <th>Description</th> * </tr> * <tr> * <td>1</td> * <td>General Validation</td> * <td>This test validates the given geometry using the validation * functionality of both deegree and JTS.</td> * </tr> * <tr> * <td>2</td> * <td>Polygon Patch Connectivity</td> * <td>Checks that multiple polygon patches within a single surface are * connected.</td> * </tr> * <tr> * <td>3</td> * <td>Repetition of Position in CurveSegments</td> * <td>Checks that consecutive positions within a CurveSegment are not * equal.</td> * </tr> * </table> * <p> * Examples: * <ul> * <li>The mask '010' indicates that only the 'Polygon Patch Connectivity' * test shall be performed.</li> * <li>The mask '1' indicates that all tests shall be performed (because the * first one is set to true/'1' and nothing is said for the other tests). * </li> * <li>The mask '0' indicates that all except the first test shall be * performed. * </ul> * * @param o * the GML geometry to validate * @return a mask with the test results, encoded as characters - one at each * position (1-based index) of the available tests. 'V' indicates * that the test passed, i.e. that the geometry is valid according * to that test. 'F' indicates that the test failed. 'S' indicates * that the test was skipped. Example: the string 'SVF' shows that * the first test was skipped, while the second test passed and the * third failed. * @throws QueryException */ @Requires(Permission.NONE) public String validate(Object o, String testMask) throws QueryException { try { // determine which tests to execute boolean isTestGeonovum, isTestPolygonPatchConnectivity, isTestRepetitionInCurveSegments; if (testMask == null) { isTestGeonovum = true; isTestPolygonPatchConnectivity = true; isTestRepetitionInCurveSegments = true; } else { isTestGeonovum = (testMask.length() >= 1 && testMask.charAt(0) == '1') ? true : false; isTestPolygonPatchConnectivity = (testMask.length() >= 2 && testMask.charAt(1) == '1') ? true : false; isTestRepetitionInCurveSegments = (testMask.length() >= 3 && testMask.charAt(2) == '1') ? true : false; } boolean isValidGeonovum = false; boolean polygonPatchesAreConnected = false; boolean noRepetitionInCurveSegment = false; BXNode elem; if (o instanceof ANode) { elem = ((ANode) o).toJava(); } else if (o instanceof BXNode) { elem = (BXNode) o; } else { // unknown type encountered throw new IllegalArgumentException( "Object type '" + o.getClass().getName() + "' is not supported for this method."); } // ================ // Geonovum validation (deegree and JTS validation) if (isTestGeonovum) { ValidatorContext ctx = new ValidatorContext(); GeometryElementHandler handler = new GeometryElementHandler(ctx, null); /* * configure handler with GML geometries specified through this * class */ handler.unregisterAllGmlGeometries(); for (String additionalGmlElementName : gmlGeometries) { handler.registerGmlGeometry(additionalGmlElementName); } SAXReader saxReader = new SAXReader(); saxReader.setDefaultHandler(handler); final InputStream stream = geoutils.nodeToInputStream(elem); saxReader.read(stream); isValidGeonovum = ctx.isSuccessful(); if (!isValidGeonovum) { List<ValidatorMessage> vmsgs = ctx.getMessages(); for (ValidatorMessage msg : vmsgs) { LOGGER.error(msg.toString()); } } } if (isTestPolygonPatchConnectivity || isTestRepetitionInCurveSegments) { ValidatorContext ctx = new ValidatorContext(); SecondaryGeometryElementValidationHandler handler = new SecondaryGeometryElementValidationHandler( isTestPolygonPatchConnectivity, isTestRepetitionInCurveSegments, ctx); /* * configure handler with GML geometries specified through this * class */ handler.unregisterAllGmlGeometries(); for (String additionalGmlElementName : gmlGeometries) { handler.registerGmlGeometry(additionalGmlElementName); } SAXReader saxReader = new SAXReader(); saxReader.setDefaultHandler(handler); final InputStream stream = geoutils.nodeToInputStream(elem); saxReader.read(stream); // ================ // Test: polygon patches of a surface are connected if (isTestPolygonPatchConnectivity) { polygonPatchesAreConnected = handler.arePolygonPatchesConnected(); } // ================ // Test: point repetition in curve segment if (isTestRepetitionInCurveSegments) { noRepetitionInCurveSegment = handler.isNoRepetitionInCurveSegments(); } } // combine results StringBuilder sb = new StringBuilder(); if (!isTestGeonovum) { sb.append("S"); } else if (isValidGeonovum) { sb.append("V"); } else { sb.append("F"); } if (!isTestPolygonPatchConnectivity) { sb.append("S"); } else if (polygonPatchesAreConnected) { sb.append("V"); } else { sb.append("F"); } if (!isTestRepetitionInCurveSegments) { sb.append("S"); } else if (noRepetitionInCurveSegment) { sb.append("V"); } else { sb.append("F"); } return sb.toString(); } catch (Exception e) { throw new QueryException(e); } } /** * Tests if the first geometry contains the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry contains the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean contains(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.CONTAINS); } /** * Tests if one geometry contains a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean contains(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.CONTAINS, matchAll); } /** * Tests if the first geometry crosses the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry crosses the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean crosses(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.CROSSES); } /** * Tests if one geometry crosses a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean crosses(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.CROSSES, matchAll); } /** * Tests if the first geometry equals the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry equals the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean equals(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.EQUALS); } /** * Tests if one geometry equals a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean equals(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.EQUALS, matchAll); } /** * Tests if the first geometry intersects the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry intersects the second * one, else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean intersects(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.INTERSECTS); } public com.vividsolutions.jts.geom.Geometry parseGeometry(Object arg) throws QueryException { try { if (arg instanceof ANode) { return geoutils.toJTSGeometry((ANode) arg); } else if (arg instanceof BXElem) { return geoutils.toJTSGeometry((Element) arg); } else if (arg instanceof com.vividsolutions.jts.geom.Geometry) { return (com.vividsolutions.jts.geom.Geometry) arg; } else { throw new IllegalArgumentException("First argument is neither a single node nor a JTS geometry."); } } catch (Exception e) { throw new QueryException(e); } } private boolean performSpatialRelationshipOperation(Object arg1, Object arg2, SpatialRelOp op) throws QueryException { try { com.vividsolutions.jts.geom.Geometry geom1, geom2; /* * We require that no basex value with multiple items is provided, * because the developer must explicitly state the desired match * semantics for cases in which one or both arguments is a * collection of items. */ geom1 = geoutils.singleObjectToJTSGeometry(arg1); geom2 = geoutils.singleObjectToJTSGeometry(arg2); return applySpatialRelationshipOperator(geom1, geom2, op); } catch (Exception e) { throw new QueryException(e); } } private boolean applySpatialRelationshipOperator(com.vividsolutions.jts.geom.Geometry geom1, com.vividsolutions.jts.geom.Geometry geom2, SpatialRelOp op) { switch (op) { case CONTAINS: return geom1.contains(geom2); case CROSSES: return geom1.crosses(geom2); case EQUALS: return geom1.equals(geom2); case INTERSECTS: return geom1.intersects(geom2); case ISDISJOINT: return geom1.disjoint(geom2); case ISWITHIN: return geom1.within(geom2); case OVERLAPS: return geom1.overlaps(geom2); case TOUCHES: return geom1.touches(geom2); default: throw new IllegalArgumentException("Unknown spatial relationship operator: " + op.toString()); } } private boolean performSpatialRelationshipOperation(Object arg1, Object arg2, SpatialRelOp op, boolean matchAll) throws QueryException { try { if (arg1 instanceof Empty || arg2 instanceof Empty) { return false; } else { com.vividsolutions.jts.geom.Geometry geom1, geom2; geom1 = geoutils.toJTSGeometry(arg1); geom2 = geoutils.toJTSGeometry(arg2); List<com.vividsolutions.jts.geom.Geometry> gc1, gc2; gc1 = geoutils.toFlattenedJTSGeometryCollection(geom1); gc2 = geoutils.toFlattenedJTSGeometryCollection(geom2); boolean allMatch = true; outer: for (com.vividsolutions.jts.geom.Geometry g1 : gc1) { for (com.vividsolutions.jts.geom.Geometry g2 : gc2) { if (matchAll) { if (applySpatialRelationshipOperator(g1, g2, op)) { /* * check the next geometry pair to see if it * also satisfies the spatial relationship */ } else { allMatch = false; break outer; } } else { if (applySpatialRelationshipOperator(g1, g2, op)) { return true; } else { /* * check the next geometry pair to see if it * satisfies the spatial relationship */ } } } } if (matchAll) { return allMatch; } else { return false; } } } catch (Exception e) { throw new QueryException(e); } } /** * Tests if one geometry intersects a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean intersects(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.INTERSECTS, matchAll); } /** * Tests if the first geometry is disjoint from the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry is disjoint from the * second one, else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean isDisjoint(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.ISDISJOINT); } /** * Tests if one geometry is disjoint to a list of geometries. Whether a * match is required for all or just one of these is controlled via * parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean isDisjoint(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.ISDISJOINT, matchAll); } /** * Tests if the first geometry is within the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry is within the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean isWithin(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.ISWITHIN); } /** * Tests if one geometry is within a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean isWithin(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.ISWITHIN, matchAll); } /** * Tests if the first geometry overlaps the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry overlaps the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean overlaps(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.OVERLAPS); } /** * Tests if one geometry overlaps a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean overlaps(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.OVERLAPS, matchAll); } /** * Tests if the first geometry touches the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @return <code>true</code> if the first geometry touches the second one, * else <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean touches(Object arg1, Object arg2) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.TOUCHES); } /** * Tests if one geometry touches a list of geometries. Whether a match is * required for all or just one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * element * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship for all geometries in arg2, else * <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean touches(Object arg1, Object arg2, boolean matchAll) throws QueryException { return performSpatialRelationshipOperation(arg1, arg2, SpatialRelOp.TOUCHES, matchAll); } /** * Adds the name of a GML geometry element to the set of elements for which * validation is performed. * * @param nodeName * name (simple, i.e. without namespace (prefix)) of a GML * geometry element to validate. */ @Requires(Permission.NONE) public void registerGmlGeometry(String nodeName) { gmlGeometries.add(nodeName); } /** * Removes the name of a GML geometry element from the set of elements for * which validation is performed. * * @param nodeName * name (simple, i.e. without namespace (prefix)) of a GML * geometry element to remove from validation. */ @Requires(Permission.NONE) public void unregisterGmlGeometry(String nodeName) { gmlGeometries.remove(nodeName); } /** * Removes all names of GML geometry elements that are currently registered * for validation. */ @Requires(Permission.NONE) public void unregisterAllGmlGeometries() { gmlGeometries.clear(); } /** * @return the currently registered GML geometry element names (comma * separated) */ @Requires(Permission.NONE) public String registeredGmlGeometries() { if (gmlGeometries.isEmpty()) { return ""; } else { Joiner joiner = Joiner.on(", ").skipNulls(); return joiner.join(gmlGeometries); } } @Requires(Permission.NONE) @Deterministic public com.vividsolutions.jts.geom.Geometry union(Object arg1, Object arg2) throws QueryException { try { List<com.vividsolutions.jts.geom.Geometry> geoms = new ArrayList<com.vividsolutions.jts.geom.Geometry>(); com.vividsolutions.jts.geom.Geometry geom1 = geoutils.toJTSGeometry(arg1); geoms.add(geom1); com.vividsolutions.jts.geom.Geometry geom2 = geoutils.toJTSGeometry(arg2); geoms.add(geom2); com.vividsolutions.jts.geom.GeometryCollection gc = geoutils.toJTSGeometryCollection(geoms, true); return gc.union(); } catch (Exception e) { throw new QueryException(e); } } @Requires(Permission.NONE) @Deterministic public com.vividsolutions.jts.geom.Geometry union(Object arg) throws QueryException { try { List<com.vividsolutions.jts.geom.Geometry> geoms = new ArrayList<com.vividsolutions.jts.geom.Geometry>(); com.vividsolutions.jts.geom.Geometry geom = geoutils.toJTSGeometry(arg); geoms.add(geom); com.vividsolutions.jts.geom.GeometryCollection gc = geoutils.toJTSGeometryCollection(geoms, true); return gc.union(); } catch (Exception e) { throw new QueryException(e); } } @Requires(Permission.NONE) @Deterministic public boolean isEmpty(com.vividsolutions.jts.geom.Geometry geom) { if (geom == null || geom.isEmpty()) { return true; } else { return false; } } /** * Identifies the holes contained in the given geometry (can be a Polygon, * MultiPolygon, or any other JTS geometry) and returns them as a JTS * geometry. If holes were found a union is built, to ensure that the result * is a valid JTS Polygon or JTS MultiPolygon. If no holes were found an * empty JTS GeometryCollection is returned. * * @param geom * potentially existing holes will be extracted from this * geometry * @return A geometry with the holes contained in the given geometry. Can be * empty but not <code>null</code>; */ @Requires(Permission.NONE) @Deterministic public com.vividsolutions.jts.geom.Geometry holes(com.vividsolutions.jts.geom.Geometry geom) { if (isEmpty(geom)) { return geoutils.emptyJTSGeometry(); } else { List<com.vividsolutions.jts.geom.Polygon> extractedPolygons = new ArrayList<com.vividsolutions.jts.geom.Polygon>(); GeometryExtracter.extract(geom, com.vividsolutions.jts.geom.Polygon.class, extractedPolygons); if (extractedPolygons.isEmpty()) { return geoutils.emptyJTSGeometry(); } else { // get holes as polygons List<com.vividsolutions.jts.geom.Polygon> holes = new ArrayList<com.vividsolutions.jts.geom.Polygon>(); for (com.vividsolutions.jts.geom.Polygon polygon : extractedPolygons) { // check that polygon has holes if (polygon.getNumInteriorRing() > 0) { // for each hole, convert it to a polygon for (int i = 0; i < polygon.getNumInteriorRing(); i++) { com.vividsolutions.jts.geom.LineString ls = polygon.getInteriorRingN(i); com.vividsolutions.jts.geom.Polygon holeAsPolygon = geoutils.toJTSPolygon(ls); holes.add(holeAsPolygon); } } } if (holes.isEmpty()) { return geoutils.emptyJTSGeometry(); } else { // create union of holes and return it return CascadedPolygonUnion.union(holes); } } } } @Requires(Permission.NONE) public boolean isValid(Object o) throws QueryException { if (o == null || o instanceof Empty) { return false; } else if (o instanceof BXElem || o instanceof ANode) { String validationResult = validate(o); if (validationResult.toLowerCase().indexOf('f') > -1) { return false; } else { return true; } } else if (o instanceof Value) { Value v = (Value) o; if (v.size() > 1) { throw new IllegalArgumentException("Single value expected where multiple were provided."); } } else if (o instanceof Object[]) { throw new IllegalArgumentException("Single object expected where multiple were provided."); } // unknown type encountered throw new IllegalArgumentException( "Object type '" + o.getClass().getName() + "' is not supported for this method."); } /** * Tests if the first geometry relates to the second geometry as defined by * the given intersection pattern. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents the second geometry, encoded as a GML geometry * element * @param intersectionPattern * the pattern against which to check the intersection matrix for * the two geometries (IxI,IxB,IxE,BxI,BxB,BxE,ExI,ExB,ExE) * @return <code>true</code> if the DE-9IM intersection matrix for the two * geometries matches the <code>intersectionPattern</code>, else * <code>false</code>. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean relate(Object arg1, Object arg2, String intersectionPattern) throws QueryException { checkIntersectionPattern(intersectionPattern); try { com.vividsolutions.jts.geom.Geometry geom1, geom2; /* * We require that no basex value with multiple items is provided, * because the developer must explicitly state the desired match * semantics for cases in which one or both arguments is a * collection of items. */ geom1 = geoutils.singleObjectToJTSGeometry(arg1); geom2 = geoutils.singleObjectToJTSGeometry(arg2); return geom1.relate(geom2, intersectionPattern); } catch (Exception e) { throw new QueryException(e); } } /** * Tests if one geometry relates to a list of geometries as defined by the * given intersection pattern. Whether a match is required for all or just * one of these is controlled via parameter. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry, encoded as a GML geometry * element * @param arg2 * represents a list of geometries, encoded as a GML geometry * @param intersectionPattern * the pattern against which to check the intersection matrix for * the geometries (IxI,IxB,IxE,BxI,BxB,BxE,ExI,ExB,ExE) * @param matchAll * <code>true</code> if arg1 must fulfill the spatial * relationship defined by the <code>intersectionPattern</code> * for all geometries in arg2, else <code>false</code> * @return <code>true</code> if the conditions are met, else * <code>false</code>. <code>false</code> will also be returned if * arg2 is empty. * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public boolean relate(Object arg1, Object arg2, String intersectionPattern, boolean matchAll) throws QueryException { checkIntersectionPattern(intersectionPattern); try { if (arg1 instanceof Empty || arg2 instanceof Empty) { return false; } else { com.vividsolutions.jts.geom.Geometry geom1, geom2; geom1 = geoutils.toJTSGeometry(arg1); geom2 = geoutils.toJTSGeometry(arg2); List<com.vividsolutions.jts.geom.Geometry> gc1, gc2; gc1 = geoutils.toFlattenedJTSGeometryCollection(geom1); gc2 = geoutils.toFlattenedJTSGeometryCollection(geom2); boolean allMatch = true; outer: for (com.vividsolutions.jts.geom.Geometry g1 : gc1) { for (com.vividsolutions.jts.geom.Geometry g2 : gc2) { if (matchAll) { if (g1.relate(g2, intersectionPattern)) { /* * check the next geometry pair to see if it * also satisfies the spatial relationship */ } else { allMatch = false; break outer; } } else { if (g1.relate(g2, intersectionPattern)) { return true; } else { /* * check the next geometry pair to see if it * satisfies the spatial relationship */ } } } } if (matchAll) { return allMatch; } else { return false; } } } catch (Exception e) { throw new QueryException(e); } } private void checkIntersectionPattern(String intersectionPattern) throws QueryException { if (intersectionPattern == null) { throw new QueryException("intersectionPattern is null."); } else { Matcher m = INTERSECTIONPATTERN.matcher(intersectionPattern.trim()); if (!m.matches()) { throw new QueryException( "intersectionPattern does not match the regular expression, which is: [0-2\\\\*TF]{9}"); } } } /** * Computes the intersection between the first and the second geometry. * <p> * See {{@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg1 * represents the first geometry * @param arg2 * represents the second geometry * @return the point-set common to the two geometries * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public com.vividsolutions.jts.geom.Geometry intersection(Object arg1, Object arg2) throws QueryException { try { if (arg1 instanceof Empty || arg2 instanceof Empty) { return geoutils.emptyJTSGeometry(); } else { com.vividsolutions.jts.geom.Geometry geom1, geom2; geom1 = geoutils.toJTSGeometry(arg1); geom2 = geoutils.toJTSGeometry(arg2); return geom1.intersection(geom2); } } catch (Exception e) { throw new QueryException(e); } } /** * Computes the envelope of a geometry. * <p> * See {@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param arg * represents the geometry * @return The bounding box, an array { x1, y1, x2, y2 } * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public Object[] envelope(Object arg) throws QueryException { try { Envelope env; if (arg instanceof Empty) { env = geoutils.emptyJTSGeometry().getEnvelopeInternal(); } else if (arg instanceof com.vividsolutions.jts.geom.Geometry) { env = ((com.vividsolutions.jts.geom.Geometry) arg).getEnvelopeInternal(); } else { env = geoutils.toJTSGeometry(arg).getEnvelopeInternal(); } Object[] res = { env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY() }; return res; } catch (Exception e) { throw new QueryException(e); } } @Requires(Permission.NONE) @Deterministic public int pre(Object entry) { if (entry instanceof IndexEntry) return ((IndexEntry) entry).pre; return -1; } @Requires(Permission.NONE) @Deterministic public String dbname(Object entry) { if (entry instanceof IndexEntry) return ((IndexEntry) entry).dbname; return null; } /** * Searches the spatial r-tree index for items in the envelope. * * @param minx * represents the minimum value on the first coordinate axis; a number * @param miny * represents the minimum value on the second coordinate axis; a number * @param maxx * represents the maximum value on the first coordinate axis; a number * @param maxy * represents the maximum value on the second coordinate axis; a number * @return the node set of all items in the envelope * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public Object[] search(Object minx, Object miny, Object maxx, Object maxy) throws QueryException { try { double x1; double x2; double y1; double y2; if (minx instanceof Number) x1 = ((Number) minx).doubleValue(); else x1 = 0.0; if (miny instanceof Number) y1 = ((Number) miny).doubleValue(); else y1 = 0.0; if (maxx instanceof Number) x2 = ((Number) maxx).doubleValue(); else x2 = 0.0; if (maxy instanceof Number) y2 = ((Number) maxy).doubleValue(); else y2 = 0.0; if (mgr == null) mgr = new GeometryManager(); Iterable<IndexEntry> iter = mgr.search(Geometries.rectangle(x1, y1, x2, y2)); List<DBNode> nodelist = new ArrayList<DBNode>(); for (IndexEntry entry : iter) { Data d = queryContext.resource.database(entry.dbname, new InputInfo("xpath", 0, 0)); DBNode n = new DBNode(d, entry.pre); if (n != null) nodelist.add(n); } if (++count % 5000 == 0) { logMemUsage("GmlGeoX#search " + count + ". Box: (" + x1 + ", " + y1 + ") (" + x2 + ", " + y2 + ")" + ". Hits: " + nodelist.size()); } return nodelist.toArray(); } catch (Exception e) { throw new QueryException(e); } } /** * Returns all items in the spatial r-tree index. * * @return the node set of all items in the index * @throws QueryException */ public Object[] search() throws QueryException { try { logMemUsage("GmlGeoX#search.start " + count + "."); if (mgr == null) mgr = new GeometryManager(); Iterable<IndexEntry> iter = mgr.search(); List<DBNode> nodelist = new ArrayList<DBNode>(); for (IndexEntry entry : iter) { Data d = queryContext.resource.database(entry.dbname, new InputInfo("xpath", 0, 0)); DBNode n = new DBNode(d, entry.pre); if (n != null) nodelist.add(n); } logMemUsage("GmlGeoX#search " + count + ". Hits: " + nodelist.size()); return nodelist.toArray(); } catch (Exception e) { throw new QueryException(e); } } /** * Logs memory information if Logger is enabled for the DEBUG level * * @param progress status string */ private void logMemUsage(final String progress) { if (debug) { final MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); memory.gc(); LOGGER.debug(progress + ". Memory: " + Math.round(memory.getHeapMemoryUsage().getUsed() / 1048576) + " MB of " + Math.round(memory.getHeapMemoryUsage().getMax() / 1048576) + " MB."); } } /** * Set cache size for geometries * * @param size * the size of the geometry cache; default is 100000 * @throws QueryException */ @Requires(Permission.NONE) public void cacheSize(Object size) throws QueryException { if (size instanceof BigInteger) { mgr = new GeometryManager(((BigInteger) size).intValue()); } } /** * Indexes a list of id nodes (gml:id attribute of features) with their GML geometries * <p> * See {@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * <p> * Both lists must have equal length. * * @param pre * represents the pre value of the indexed item node (typically the gml:id of GML feature elements) * @param dbname * represents the name of the database that contains the indexed item node (typically the gml:id of GML feature elements) * @param id * represents the id string of the item that should be indexed, typically the gml:id of GML feature elements; must be String instances * @param geom * represents the GML geometry to index; must be an BXElem instance * @throws QueryException */ @Requires(Permission.NONE) public void index(Object pre, Object dbname, Object id, Object geom) throws QueryException { if (mgr == null) mgr = new GeometryManager(); if (pre instanceof BigInteger && dbname instanceof String && (id instanceof BXNode || id instanceof String) && (geom instanceof BXElem || geom instanceof com.vividsolutions.jts.geom.Geometry)) try { IndexEntry entry = new IndexEntry((String) dbname, ((BigInteger) pre).intValue()); String _id = id instanceof String ? (String) id : ((BXNode) id).getNodeValue(); com.vividsolutions.jts.geom.Geometry _geom = geom instanceof BXElem ? geoutils.singleObjectToJTSGeometry(geom) : ((com.vividsolutions.jts.geom.Geometry) geom); Envelope env = _geom.getEnvelopeInternal(); if (!env.isNull()) { if (env.getHeight() == 0.0 && env.getWidth() == 0.0) mgr.index(entry, Geometries.point(env.getMinX(), env.getMinY())); else mgr.index(entry, Geometries.rectangle(env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY())); // add to geometry cache if (_id != null) mgr.put(_id, _geom); } int size = mgr.indexSize(); if (size % 5000 == 0) logMemUsage("GmlGeoX#index progress: " + size); } catch (Exception e) { throw new QueryException(e); } } /** * Retrieve the geometry of an item as a JTS geometry. First try the cache and if it is not in the cache * construct it from the XML. * <p> * See {@link GmlGeoXUtils#toJTSGeometry(Geometry)} for a list of supported * and unsupported geometry types. * * @param id * the id for which the geometry should be retrieved, typically a gml:id of a GML feature element; must be a String or BXNode instance * @param defgeom * represents the default GML geometry, if the geometry is not cached; must be a BXElem instance * @return * the geometry of the indexed node, or null if no geometry was found * @throws QueryException */ @Requires(Permission.NONE) @Deterministic public com.vividsolutions.jts.geom.Geometry getGeometry(Object id, Object defgeom) throws QueryException { if (++count2 % 5000 == 0) { logMemUsage("GmlGeoX#getGeometry.start " + count2); } if (mgr == null) mgr = new GeometryManager(); String idx; if (id instanceof String) { idx = (String) id; } else if (id instanceof BXNode) { idx = ((BXNode) id).getNodeValue(); } else throw new QueryException( "Failure to get geometry. An id uses an incorrect type: " + id.getClass().getCanonicalName()); com.vividsolutions.jts.geom.Geometry geom = mgr.get(idx); if (geom == null) { if (!(defgeom instanceof BXElem || defgeom instanceof com.vividsolutions.jts.geom.Geometry)) { throw new QueryException("Failure to parse geometry. A geometry uses an incorrect type: " + defgeom.getClass().getCanonicalName()); } try { geom = defgeom instanceof BXElem ? geoutils.singleObjectToJTSGeometry(defgeom) : ((com.vividsolutions.jts.geom.Geometry) defgeom); if (geom != null) mgr.put(idx, geom); } catch (Exception e) { throw new QueryException(e); } if (debug) { long missCount = mgr.getMissCount(); if (missCount % 10000 == 0) { LOGGER.debug("Cache misses: " + missCount + " of " + mgr.getCount()); } } } if (geom == null) { geom = geoutils.emptyJTSGeometry(); } if (count2 % 5000 == 0) { logMemUsage("GmlGeoX#getGeometry.end " + count2); } return geom; } }