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.ByteArrayInputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import nl.vrom.roo.validator.core.ValidatorContext; import nl.vrom.roo.validator.core.ValidatorMessageBundle; import nl.vrom.roo.validator.core.dom4j.Dom4JHelper; import nl.vrom.roo.validator.core.errorlocation.IdErrorLocation; import org.deegree.commons.xml.XMLParsingException; import org.deegree.cs.exceptions.UnknownCRSException; import org.deegree.geometry.Geometry; import org.deegree.geometry.composite.CompositeGeometry; import org.deegree.geometry.composite.CompositeSolid; import org.deegree.geometry.multi.MultiGeometry; import org.deegree.geometry.multi.MultiSolid; import org.deegree.geometry.points.Points; import org.deegree.geometry.primitive.Curve; import org.deegree.geometry.primitive.Point; import org.deegree.geometry.primitive.Ring; import org.deegree.geometry.primitive.Solid; import org.deegree.geometry.primitive.Surface; import org.deegree.geometry.primitive.patches.PolygonPatch; import org.deegree.geometry.primitive.patches.SurfacePatch; import org.deegree.geometry.primitive.segments.Arc; import org.deegree.geometry.primitive.segments.ArcString; import org.deegree.geometry.primitive.segments.CubicSpline; import org.deegree.geometry.primitive.segments.CurveSegment; import org.deegree.geometry.primitive.segments.GeodesicString; import org.deegree.geometry.primitive.segments.LineStringSegment; import org.deegree.geometry.standard.points.PointsList; import org.deegree.gml.GMLInputFactory; import org.deegree.gml.GMLStreamReader; import org.deegree.gml.GMLVersion; import org.dom4j.Element; import org.dom4j.ElementHandler; import org.dom4j.ElementPath; import org.dom4j.Namespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * NOTE: Implementation is based on Geonovum's GeometryElementHandler. * * @author Johannes Echterhoff (echterhoff <at> interactive-instruments * <dot> de) * */ public class SecondaryGeometryElementValidationHandler implements ElementHandler { private static final Logger LOGGER = LoggerFactory.getLogger(SecondaryGeometryElementValidationHandler.class); private static final String NODE_NAME_FEATURE_MEMBER = "featureMember"; protected GmlGeoXUtils geoutils = new GmlGeoXUtils(); private final ValidatorContext validatorContext; private final Set<String> gmlGeometries = new HashSet<String>(); private final Map<String, Integer> currentGmlGeometryCounters = new HashMap<String, Integer>(); private String currentFeatureMember; private boolean isGMLVersionReported; private boolean isTestRepetitionInCurveSegments; private boolean isTestPolygonPatchConnectivity; private boolean noRepetitionInCurveSegments = true; private boolean polygonPatchesAreConnected = true; /** * @param isTestPolygonPatchConnectivity * - <code>true</code> if polygon patch connectivity shall be * tested * @param isTestRepetitionInCurveSegments * - <code>true</code> if repetition of consecutive points in * curve segments shall be checked * @param validatorContext */ public SecondaryGeometryElementValidationHandler(boolean isTestPolygonPatchConnectivity, boolean isTestRepetitionInCurveSegments, ValidatorContext validatorContext) { this.isTestPolygonPatchConnectivity = isTestPolygonPatchConnectivity; this.isTestRepetitionInCurveSegments = isTestRepetitionInCurveSegments; this.validatorContext = validatorContext; registerGmlGeometry("Point"); registerGmlGeometry("Polygon"); registerGmlGeometry("Surface"); registerGmlGeometry("Curve"); registerGmlGeometry("LinearRing"); registerGmlGeometry("MultiPoint"); registerGmlGeometry("MultiPolygon"); registerGmlGeometry("MultiGeometry"); registerGmlGeometry("MultiSurface"); registerGmlGeometry("MultiCurve"); registerGmlGeometry("Ring"); registerGmlGeometry("LineString"); } /** * {@inheritDoc} * <p> * When this is called on a new feature member, the geometry counters are * reset. * </p> */ public void onStart(ElementPath elementPath) { String currentPath = elementPath.getPath(); if (NODE_NAME_FEATURE_MEMBER.equals(Dom4JHelper.getNodeFromPath(Dom4JHelper.getParentPath(currentPath)))) { currentFeatureMember = Dom4JHelper.getNodeFromPath(currentPath); resetGmlGeometryCounters(); } } /** {@inheritDoc} */ public void onEnd(ElementPath elementPath) { String nodeName = Dom4JHelper.getNodeFromPath(elementPath.getPath()); if (gmlGeometries.contains(nodeName)) { // Check if this element is a main geometry if (isMainGeometry(elementPath)) { raiseGmlGeometryCounter(nodeName); try { validate(validatorContext, elementPath.getCurrent()); } catch (XMLParsingException e) { LOGGER.error("Unexpected error detected while validating geometry", e); } catch (UnknownCRSException e) { LOGGER.error("Unexpected error detected while validating geometry", e); } } else { LOGGER.trace("Element {} is part of another geometry", nodeName); } } } private void validate(ValidatorContext validatorContext, Element element) throws XMLParsingException, UnknownCRSException { Namespace namespace = element.getNamespace(); String namespaceURI = namespace == null ? null : namespace.getURI(); if (namespace == null || (!geoutils.isGML32Namespace(namespaceURI) && !geoutils.isGML31Namespace(namespaceURI))) { LOGGER.error("Unable to determine GML version. Namespace= {}", namespaceURI); String errMessage = ValidatorMessageBundle.getMessage("validator.core.validation.geometry.no-gml", namespaceURI); validatorContext.addError(errMessage); return; } if (!isGMLVersionReported) { if (geoutils.isGML32Namespace(namespaceURI)) { validatorContext.addNotice( ValidatorMessageBundle.getMessage("validator.core.validation.geometry.gmlversion", "3.2")); } else if (geoutils.isGML31Namespace(namespaceURI)) { validatorContext.addNotice( ValidatorMessageBundle.getMessage("validator.core.validation.geometry.gmlversion", "3.1")); } isGMLVersionReported = true; } try { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(element.asXML().getBytes()); XMLStreamReader xmlStream = XMLInputFactory.newInstance().createXMLStreamReader(byteArrayInputStream); GMLVersion gmlVersion = null; if (geoutils.isGML32Namespace(namespaceURI)) { // GML 3.2 gmlVersion = GMLVersion.GML_32; } else if (geoutils.isGML31Namespace(namespaceURI)) { gmlVersion = GMLVersion.GML_31; } else { throw new Exception("Cannot determine GML version"); } GMLStreamReader gmlStream = GMLInputFactory.createGMLStreamReader(gmlVersion, xmlStream); Geometry geom = gmlStream.readGeometry(); // ================ // Test: polygon patches of a surface are connected if (isTestPolygonPatchConnectivity) { boolean isValid = checkConnectivityOfPolygonPatches(geom); if (!isValid) { polygonPatchesAreConnected = false; } } // ================ // Test: point repetition in curve segment if (isTestRepetitionInCurveSegments) { boolean isValid = checkNoRepetitionInCurveSegment(geom); if (!isValid) { noRepetitionInCurveSegments = false; } } } catch (XMLStreamException e) { String currentGmlId = Dom4JHelper.findGmlId(element); String message = getLocationDescription(element, currentGmlId) + ": " + e.getMessage(); validatorContext.addError(message, new IdErrorLocation(currentGmlId)); LOGGER.error(e.getMessage(), e); } catch (FactoryConfigurationError e) { LOGGER.error(e.getMessage(), e); validatorContext.addError( ValidatorMessageBundle.getMessage("validator.core.validation.geometry.unknown-exception")); } catch (Exception e) { String currentGmlId = Dom4JHelper.findGmlId(element); String message = getLocationDescription(element, currentGmlId) + ": " + e.getMessage(); validatorContext.addError(message, new IdErrorLocation(currentGmlId)); LOGGER.error(e.getMessage(), e); } // finally { // // Only permitted if this is a main geometry.... // if (isMainGeometry(element)) { // element.detach(); // } // } } private String getLocationDescription(Element element, String gmlId) { return ValidatorMessageBundle.getMessage("validator.core.validation.geometry.coordinates-position", new Object[] { element.getName(), currentGmlGeometryCounters.get(element.getName()), currentFeatureMember, gmlId }); } /** * Check if this is a main geometry by making sure that the parent element * is <code>null</code> or in a non-GML namespace. * * @param elementPath * @return true is this is a main geometry */ private boolean isMainGeometry(ElementPath elementPath) { // if(elementPath.getCurrent().getParent()==null) { // return false; // } // String namespaceURI = // elementPath.getCurrent().getParent().getNamespaceURI(); // return (!isGML32Namespace(namespaceURI) && // !isGML31Namespace(namespaceURI)); /* * 2015-12-08 Johannes Echterhoff: the previous logic is not suitable * for us, in case that a single geometry (node) is validated by a * (x)query. Because then the geometry has no parent, which would cause * this method to evaluate to false - which is not what we want. */ if (elementPath.getCurrent().getParent() == null) { // the geometry is the only XML that is validated String namespaceURI = elementPath.getCurrent().getNamespaceURI(); return (geoutils.isGML32Namespace(namespaceURI) || geoutils.isGML31Namespace(namespaceURI)); } else { // if the parent namespace is not a GML one, we have a main geometry String namespaceURI = elementPath.getCurrent().getParent().getNamespaceURI(); return (!geoutils.isGML32Namespace(namespaceURI) && !geoutils.isGML31Namespace(namespaceURI)); } } public void registerGmlGeometry(String nodeName) { gmlGeometries.add(nodeName); currentGmlGeometryCounters.put(nodeName, 0); } public void unregisterGmlGeometry(String nodeName) { gmlGeometries.remove(nodeName); currentGmlGeometryCounters.remove(nodeName); } public void unregisterAllGmlGeometries() { gmlGeometries.clear(); currentGmlGeometryCounters.clear(); } private void raiseGmlGeometryCounter(String nodeName) { Integer newCounter = currentGmlGeometryCounters.get(nodeName) + 1; currentGmlGeometryCounters.put(nodeName, newCounter); } private void resetGmlGeometryCounters() { for (String key : currentGmlGeometryCounters.keySet()) { currentGmlGeometryCounters.put(key, 0); } LOGGER.trace("Counters reset"); } /** * Checks that multiple polygon patches within a surface are connected. * <p> * The test is implemented as follows: Each polygon patch is converted into * a JTS Polygon. Then the union of all polygons is created. If the union * geometry is a JTS Polygon then the surface is connected - otherwise it is * not. * <p> * Checks: * <ul> * <li>Surface (including PolyhedralSurface, CompositeSurface, and * OrientableSurface)</li> * <ul><li>Only PolygonPatch is allowed as surface patch - all surfaces that * contain a different type of surface patch are ignored.</li></ul> * <li>The elements of multi and composite geometries (except Multi- and * CompositeSolids).</li> * </ul> * Does NOT check the surfaces within solids! * * @param geom * @return <code>true</code> if the given geometry is a connected surface, a * point, a curve, multi- or composite geometry that only consists * of these geometry types, else <code>false</code>. Thus, * <code>false</code> will be returned whenever a solid is * encountered and if a surface is not connected. * @throws Exception */ protected boolean checkConnectivityOfPolygonPatches(Geometry geom) throws Exception { if (geom instanceof Surface) { Surface s = (Surface) geom; List<? extends SurfacePatch> sps = s.getPatches(); if (sps.size() <= 1) { // not multiple patches -> nothing to check return true; } else { /* * Compute JTS geometry from the surface with multiple patches. * An exception will be thrown if the surface does not consist * of polygon patches. If the surface is connected then the * resulting geometry is a JTS Polygon (because a union of all * patches has been created). Otherwise the surface is not * connected. */ com.vividsolutions.jts.geom.Geometry g = geoutils.toJTSGeometry(geom); if (g instanceof com.vividsolutions.jts.geom.Polygon) { return true; } else { return false; } } } else if (geom instanceof MultiSolid || geom instanceof CompositeSolid || geom instanceof Solid) { return false; } else if (geom instanceof MultiGeometry || geom instanceof CompositeGeometry) { // MultiSurface extends MultiGeometry // CompositeSurface extends CompositeGeometry @SuppressWarnings("rawtypes") List l = (List) geom; for (Object o : l) { Geometry g = (Geometry) o; if (!checkConnectivityOfPolygonPatches(g)) { return false; } } return true; } else { return true; } } /** * Checks that two consecutive points in a posList - within a CurveSegment - * are not equal. * <p> * Checks: * <ul> * <li>Curve (including CompositeCurve, LineString, OrientableCurve, Ring) * </li> * <li>The following CurveSegment types: Arc, ArcString, CubicSpline, * GeodesicString, LineStringSegment</li> * <li>The exterior and interior rings of polygon patches (contained within * Surface, Polygon, PolyhedralSurface, TriangulatedSurface, Tin, * CompositeSurface, or OrientableSurface) - NOTE: all other types of * surface patches are currently ignored!</li> * <li>The elements of multi and composite geometries</li> * </ul> * Does NOT check curve segments within solids! * * @param geom * the geometry that shall be tested * @return <code>true</code> if no repetition was detected (or if the * geometry is a point, a solid, or consists of solids), else * <code>false</code> */ protected boolean checkNoRepetitionInCurveSegment(Geometry geom) { if (geom instanceof Curve) { /* * includes CompositeCurve, LineString, OrientableCurve, Ring */ Curve curve = (Curve) geom; for (CurveSegment segment : curve.getCurveSegments()) { Points points = null; if (segment instanceof ArcString) { ArcString as = (ArcString) segment; points = as.getControlPoints(); } else if (segment instanceof Arc) { Arc arc = (Arc) segment; List<Point> lp = new ArrayList<Point>(); lp.add(arc.getPoint1()); lp.add(arc.getPoint2()); lp.add(arc.getPoint3()); points = new PointsList(lp); } else if (segment instanceof CubicSpline) { CubicSpline cs = (CubicSpline) segment; points = cs.getControlPoints(); } else if (segment instanceof GeodesicString) { GeodesicString gs = (GeodesicString) segment; points = gs.getControlPoints(); } else if (segment instanceof LineStringSegment) { LineStringSegment lss = (LineStringSegment) segment; points = lss.getControlPoints(); } Point lastPoint = null; for (Point point : points) { if (lastPoint != null) { if (point.equals(lastPoint)) { return false; } } lastPoint = point; } } return true; } else if (geom instanceof Surface) { Surface s = (Surface) geom; List<? extends SurfacePatch> patches = s.getPatches(); for (SurfacePatch sp : patches) { if (sp instanceof PolygonPatch) { PolygonPatch pp = (PolygonPatch) sp; Ring exterior = pp.getExteriorRing(); if (!checkNoRepetitionInCurveSegment(exterior)) { return false; } List<Ring> interiorRings = pp.getInteriorRings(); for (Ring interiorRing : interiorRings) { if (!checkNoRepetitionInCurveSegment(interiorRing)) { return false; } } } else { // TODO is another type of surface patch relevant? } } return true; } else if (geom instanceof MultiGeometry || geom instanceof CompositeGeometry) { @SuppressWarnings("rawtypes") List l = (List) geom; for (Object o : l) { Geometry g = (Geometry) o; if (!checkNoRepetitionInCurveSegment(g)) { return false; } } return true; } else { return true; } } /** * @return <code>true</code> if there is no repetition in curve segments */ public boolean isNoRepetitionInCurveSegments() { return noRepetitionInCurveSegments; } /** * @return <code>true</code> if the polygon patches are connected */ public boolean arePolygonPatchesConnected() { return polygonPatchesAreConnected; } }