Java tutorial
/** This file is part of kalypso/deegree. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * history: * * Files in this package are originally taken from deegree and modified here * to fit in kalypso. As goals of kalypso differ from that one in deegree * interface-compatibility to deegree is wanted but not retained always. * * If you intend to use this software in other ways than in kalypso * (e.g. OGC-web services), you should consider the latest version of deegree, * see http://www.deegree.org . * * all modifications are licensed as deegree, * original copyright: * * Copyright (C) 2001 by: * EXSE, Department of Geography, University of Bonn * http://www.giub.uni-bonn.de/exse/ * lat/lon GmbH * http://www.lat-lon.de */ package org.kalypsodeegree_impl.tools; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.apache.commons.lang3.ArrayUtils; import org.j3d.geom.TriangulationUtils; import org.kalypso.commons.java.lang.MathUtils; import org.kalypso.commons.xml.NS; import org.kalypso.gmlschema.GMLSchemaUtilities; import org.kalypso.gmlschema.feature.IFeatureType; import org.kalypso.gmlschema.property.IPropertyType; import org.kalypso.gmlschema.property.IValuePropertyType; import org.kalypso.transformation.transformer.IGeoTransformer; import org.kalypsodeegree.model.feature.Feature; import org.kalypsodeegree.model.feature.FeatureList; import org.kalypsodeegree.model.feature.GMLWorkspace; import org.kalypsodeegree.model.geometry.GM_AbstractGeometry; import org.kalypsodeegree.model.geometry.GM_Curve; import org.kalypsodeegree.model.geometry.GM_Envelope; import org.kalypsodeegree.model.geometry.GM_Exception; import org.kalypsodeegree.model.geometry.GM_LineString; import org.kalypsodeegree.model.geometry.GM_MultiCurve; import org.kalypsodeegree.model.geometry.GM_MultiPoint; import org.kalypsodeegree.model.geometry.GM_MultiPrimitive; import org.kalypsodeegree.model.geometry.GM_MultiSurface; import org.kalypsodeegree.model.geometry.GM_Object; import org.kalypsodeegree.model.geometry.GM_Point; import org.kalypsodeegree.model.geometry.GM_Polygon; import org.kalypsodeegree.model.geometry.GM_Position; import org.kalypsodeegree.model.geometry.GM_Triangle; import org.kalypsodeegree_impl.model.feature.FeatureHelper; import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPath; import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPathException; import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPathUtilities; import org.kalypsodeegree_impl.model.geometry.GeometryFactory; import org.kalypsodeegree_impl.model.geometry.JTSAdapter; import com.infomatiq.jsi.Rectangle; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier; /** * @author doemming */ public final class GeometryUtilities { private GeometryUtilities() { throw new UnsupportedOperationException("Do not instantiate this helper class"); } /** * creates a new GM_Position that is on the straight line defined with the positions and has a special distance from * basePoint in the direction towards the directionPoint */ public static GM_Position createGM_PositionAt(final GM_Position basePoint, final GM_Position directionPoint, final double distanceFromBasePoint) { final double[] p1 = basePoint.getAsArray(); final double distance = basePoint.getDistance(directionPoint); if (distance == 0) return (GM_Position) basePoint.clone(); final double[] p2 = directionPoint.getAsArray(); final double factor = distanceFromBasePoint / distance; final double[] newPos = new double[p1.length]; // for( int i = 0; i < newPos.length; i++ ) for (int i = 0; i < 2; i++) newPos[i] = p1[i] + (p2[i] - p1[i]) * factor; return GeometryFactory.createGM_Position(newPos); } public static GM_Position getGM_PositionBetweenAtLevel(final GM_Position p1, final GM_Position p2, final double iso) { final double dz1 = iso - p1.getZ(); final double dz2 = p2.getZ() - p1.getZ(); final double dx = p2.getX() - p1.getX(); final double c = dz1 / dz2; // check between if (c < -0.01d || c > 1.01d) return null; return GeometryFactory.createGM_Position(p1.getX() + c * dx, p1.getY() + c * (p2.getY() - p1.getY()), iso); } public static GM_Position createGM_PositionAtCenter(final GM_Position p1, final GM_Position p2) { final double[] asArray1 = p1.getAsArray(); final double[] asArray2 = p2.getAsArray(); final int length = Math.min(asArray1.length, asArray2.length); final double[] newArray = new double[length]; for (int i = 0; i < length; i++) newArray[i] = (asArray1[i] + asArray2[i]) / 2d; return GeometryFactory.createGM_Position(newArray); } /** * assuming p1 and p2 have same coordinate system */ public static GM_Point createGM_PositionAtCenter(final GM_Point p1, final GM_Point p2) { final GM_Position newPos = createGM_PositionAtCenter(p1.getPosition(), p2.getPosition()); return GeometryFactory.createGM_Point(newPos, p1.getCoordinateSystem()); } public static double calcAngleToSurface(final GM_Polygon surface, final GM_Point point) { final double r = surface.distance(point); double min = r; double resultAngle = 0; final double n = 8; for (double angle = 0; angle < 2d * Math.PI; angle += 2d * Math.PI / n) { final GM_Point p = createPointFrom(point, angle, r / 2); final double distance = surface.distance(p); if (distance < min) { min = distance; resultAngle = angle; } } return resultAngle; } /** * guess point that is on the surface * * @param surface * surface that should contain the result point * @param pointGuess * @param tries * numer of maximal interations * @return point that is somewhere on the surface (e.g. can act as label point) */ public static GM_Point guessPointOnSurface(final GM_Polygon surface, GM_Point pointGuess, int tries) { if (surface == null) return null; if (pointGuess == null) pointGuess = surface.getCentroid(); if (tries <= 0) return pointGuess; tries--; if (surface.contains(pointGuess)) return pointGuess; // // pointGuess1 // | // |radius1 // | // --p1--- at border // ---------- // ------------ // ------------ // --result---- // ----------- // ---------- // ----------- // -surface--- // --p2------- at border // | // | // | // | // |radius2 // | // | // | // | // pointGuess2 // // 1. find point at surface on one side final double angle1 = calcAngleToSurface(surface, pointGuess); final double r1 = surface.distance(pointGuess); final GM_Point p1 = createPointFrom(pointGuess, angle1, r1); final GM_Point p2 = calcFarestPointOnSurfaceInDirection(surface, p1, angle1, Math.sqrt( Math.pow(surface.getEnvelope().getHeight(), 2) * Math.pow(surface.getEnvelope().getWidth(), 2)), 8); return guessPointOnSurface(surface, createGM_PositionAtCenter(p1, p2), tries); } private static GM_Point calcFarestPointOnSurfaceInDirection(final GM_Polygon surface, final GM_Point pOnSurface, final double angle, final double max, int tries) { final GM_Point point = createPointFrom(pOnSurface, angle, max); if (surface.contains(point)) return point; if (tries <= 0) return point;// return the best try tries--; final double distance = surface.distance(point); return calcFarestPointOnSurfaceInDirection(surface, pOnSurface, angle, max - distance, tries); } // public static GM_Point guessPointOnSurface( final GM_Surface surface, // // // // GM_Point firstGuessPoint, int tries ) // { // if( surface == null ) // return null; // if( firstGuessPoint == null ) // firstGuessPoint = surface.getCentroid(); // if( tries <= 0 ) // return firstGuessPoint; // tries--; // // // // guessPoint // // | // // |radius // // | // // --p1-- at border // // --|----- // // --p3----- middle of p1 and p2 // // --|------ // // --p2---- guesspoint mirror at p2 // // -------- // // --------- // // -surface- // // --------- // // // // 1. find direction to surface // double n = 8; // number of directions to test // final double r = surface.distance( firstGuessPoint ); // double min = r; // double resultAngle = 0; // for( double angle = 0; angle < 2d * Math.PI; angle += Math.PI / n ) // { // GM_Point p = createPointFrom( firstGuessPoint, angle, r / 2 ); // double distance = surface.distance( p ); // if( distance < min ) // { // min = distance; // resultAngle = angle; // } // } // // calc point at border // final GM_Point p1 = createPointFrom( firstGuessPoint, resultAngle, r ); // // mirror at p1 // final GM_Point p2 = createPointFrom( firstGuessPoint, resultAngle, 2 * r ); // // center of p1 and p2 // final GM_Point p3 = createGM_PositionAtCenter( p1, p2 ); // // // if(!surface.contains( p2 ) ) // { // if( surface.contains( p3 ) ) // return p3; // return p2; // } // return guessPointOnSurface( surface, p3, tries ); // // if( surface.contains( p2 ) ) // // { // // if( surface.contains( p3 ) ) // // return p3; // // return p2; // // } // // return guessPointOnSurface( surface, p3, tries ); // } private static GM_Point createPointFrom(final GM_Point centroid, final double angle, final double radius) { final double x = centroid.getX() + Math.cos(angle) * radius; final double y = centroid.getY() + Math.sin(angle) * radius; return GeometryFactory.createGM_Point(x, y, centroid.getCoordinateSystem()); } public static double calcArea(final GM_Object geom) { if (geom instanceof GM_Polygon) return ((GM_Polygon) geom).getArea(); else if (geom instanceof GM_MultiSurface) { double area = 0; final GM_Polygon[] allSurfaces = ((GM_MultiSurface) geom).getAllSurfaces(); for (final GM_Polygon element : allSurfaces) area += calcArea(element); return area; } else if (geom instanceof GM_MultiPrimitive) { double area = 0; final GM_AbstractGeometry[] allPrimitives = ((GM_MultiPrimitive) geom).getAllPrimitives(); for (final GM_AbstractGeometry element : allPrimitives) area += calcArea(element); return area; } return 0d; } public static boolean isInside(final GM_Object a, final GM_Object b) { if (a instanceof GM_Polygon && b instanceof GM_Polygon) return a.contains(guessPointOnSurface((GM_Polygon) b, b.getCentroid(), 3)); // return a.contains(b); if (a instanceof GM_MultiSurface) return isInside(((GM_MultiSurface) a).getAllSurfaces()[0], b); if (b instanceof GM_MultiSurface) return isInside(a, ((GM_MultiSurface) b).getAllSurfaces()[0]); return false; } public static double calcArea(final GM_Envelope env) { return env.getHeight() * env.getHeight(); } public static GM_Position createGM_PositionAverage(final GM_Position[] positions) { double x = 0d, y = 0d; for (final GM_Position position : positions) { x += position.getX(); y += position.getY(); } return GeometryFactory.createGM_Position(x / positions.length, y / positions.length); } /** * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isPointGeometry(final IValuePropertyType ftp) { // remember to use the same classes as used by the marshalling type handlers !! return ftp.getValueClass().equals(getPointClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isPointGeometry(final Object o) { return o.getClass().equals(getPointClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isMultiPointGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getMultiPointClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isMultiPointGeometry(final Object o) { return o.getClass().equals(getMultiPointClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isCurveGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getCurveClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isLineStringGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getLineStringClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isLineStringGeometry(final Object o) { return o.getClass().equals(getLineStringClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isMultiLineStringGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getMultiLineStringClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isSurfaceGeometry(final IValuePropertyType ftp) { return getSurfaceClass().isAssignableFrom(ftp.getValueClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isMultiLineStringGeometry(final Object o) { return o.getClass().equals(getMultiLineStringClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isPolygonGeometry(final IValuePropertyType ftp) { return getPolygonClass().isAssignableFrom(ftp.getValueClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isPolygonGeometry(final Object o) { final Class<? extends Object> class1 = o.getClass(); return getPolygonClass().isAssignableFrom(class1); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isMultiPolygonGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getMultiPolygonClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isMultiPolygonGeometry(final Object o) { return o.getClass().equals(getMultiPolygonClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isUndefinedGeometry(final IValuePropertyType ftp) { return ftp.getValueClass().equals(getUndefinedGeometryClass()); } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isUndefinedGeometry(final Object o) { return o.getClass().equals(getUndefinedGeometryClass()); } /** * @param ftp * @return <code>true</code> if feature property type equals this type of geometry */ public static boolean isAnyMultiGeometry(final IPropertyType ftp) { ftp.getClass(); // no yellow things return false; // not supported TODO support it } public static boolean isEnvelopeGeometry(final IValuePropertyType ftp) { return getEnvelopeClass().equals(ftp.getValueClass()); } /** * Classifies the property as a geometry. * * @return <code>null</code>, if the property is not a geometry property. */ public static GeometryType classifyGeometry(final IPropertyType pt) { if (!isGeometry(pt)) return null; final IValuePropertyType vpt = (IValuePropertyType) pt; if (isPointGeometry(vpt)) return GeometryType.POINT; if (isCurveGeometry(vpt)) return GeometryType.CURVE; if (isSurfaceGeometry(vpt)) return GeometryType.SURFACE; return GeometryType.UNKNOWN; } /** * @param o * @return <code>true</code> if object type equals this type of geometry */ public static boolean isEnvelopeGeometry(final Object o) { return getEnvelopeClass().equals(o.getClass()); } public static Class<? extends Object> getEnvelopeClass() { return GM_Envelope.class; } public static boolean isGeometry(final IPropertyType pt) { if (!(pt instanceof IValuePropertyType)) return false; final IValuePropertyType gpt = (IValuePropertyType) pt; return gpt.isGeometry(); } public static Class<? extends GM_Object> getPointClass() { return GM_Point.class; } public static Class<? extends GM_Object> getMultiPointClass() { return GM_MultiPoint.class; } public static Class<? extends GM_Object> getLineStringClass() { return GM_Curve.class; } public static Class<? extends GM_Object> getCurveClass() { return GM_Curve.class; } public static Class<? extends GM_Object> getMultiLineStringClass() { return GM_MultiCurve.class; } public static Class<? extends GM_Object> getSurfaceClass() { return GM_Polygon.class; } public static Class<? extends GM_Object> getPolygonClass() { return GM_Polygon.class; } public static Class<? extends GM_Object> getMultiPolygonClass() { return GM_MultiSurface.class; } public static Class<? extends GM_Object> getUndefinedGeometryClass() { return GM_Object.class; } public static boolean isGeometry(final Object o) { final Class<? extends Object> class1 = o.getClass(); if (getUndefinedGeometryClass().isAssignableFrom(class1)) return true; else if (getPointClass().isAssignableFrom(class1)) return true; else if (getMultiPointClass().isAssignableFrom(class1)) return true; else if (getLineStringClass().isAssignableFrom(class1)) return true; else if (getMultiLineStringClass().isAssignableFrom(class1)) return true; else if (getPolygonClass().isAssignableFrom(class1)) return true; else if (getMultiPolygonClass().isAssignableFrom(class1)) return true; return false; } /** * This method ensure to return a multi polygon (GM_MultiSurface ). the geomToCheck is a polygon ( GM_Surface) the * polygon is wrapped to a multi polygon. * * @param geomToCheck * geometry object to check * @return multi polygon, if geomToCheck is null, null is returned, if the geomToCheck is a multi polygon it returns * itself * @exception a * GM_Exception is thrown when a the geomToCheck can not be wrapped in a multi polygon. */ public static GM_MultiSurface ensureIsMultiPolygon(final GM_Object geomToCheck) throws GM_Exception { if (geomToCheck == null) return null; final Class<? extends GM_Object> class1 = geomToCheck.getClass(); if (getMultiPolygonClass().isAssignableFrom(class1)) return (GM_MultiSurface) geomToCheck; else if (getPolygonClass().isAssignableFrom(class1)) return GeometryFactory.createGM_MultiSurface(new GM_Polygon[] { (GM_Polygon) geomToCheck }, ((GM_Polygon) geomToCheck).getCoordinateSystem()); else throw new GM_Exception("This geometry can not be a MultiPolygon..."); } /** * @param positions * array of ordered {@link GM_Position}, last must equal first one * @return signed area, area >= 0 means points are counter clockwise defined (mathematic positive) */ public static double calcSignedAreaOfRing(final GM_Position[] positions) { if (positions.length < 4) // 3 points and 4. is repetition of first point throw new UnsupportedOperationException("can not calculate area of < 3 points"); final GM_Position a = positions[0]; // base double area = 0; for (int i = 1; i < positions.length - 2; i++) { final GM_Position b = positions[i]; final GM_Position c = positions[i + 1]; area += (b.getY() - a.getY()) * (a.getX() - c.getX()) // bounding rectangle - ((a.getX() - b.getX()) * (b.getY() - a.getY())// + (b.getX() - c.getX()) * (b.getY() - c.getY())// + (a.getX() - c.getX()) * (c.getY() - a.getY())// ) / 2d; } return area; } /** * Finds the first geometry property of the given feature type. * * @param aPreferedGeometryClass * If non null, the first property of this type is returned. */ public static IValuePropertyType findGeometryProperty(final IFeatureType featureType, final Class<?> aPreferedGeometryClass) { final QName defaultGeometryPropertyQName = new QName(NS.GML3, "location"); final IValuePropertyType[] allGeomteryProperties = featureType.getAllGeometryProperties(); IValuePropertyType geometryProperty = null; for (final IValuePropertyType property : allGeomteryProperties) { if (aPreferedGeometryClass == null || property.getValueClass().isAssignableFrom(aPreferedGeometryClass)) { geometryProperty = property; if (!geometryProperty.getQName().equals(defaultGeometryPropertyQName)) break; } } return geometryProperty; } /** * clones a GM_Linestring as GM_Curve and sets its z-value to a given value. * * @param newLine * the input linestring * @param value * the new z-value */ public static GM_Curve setValueZ(final GM_LineString newLine, final double value) throws GM_Exception { final GM_Position[] positions = newLine.getPositions(); final String crs = newLine.getCoordinateSystem(); final GM_Position[] newPositions = new GM_Position[positions.length]; for (int i = 0; i < positions.length; i++) { final GM_Position position = positions[i]; newPositions[i] = GeometryFactory.createGM_Position(position.getX(), position.getY(), value); } return GeometryFactory.createGM_Curve(newPositions, crs); } /** * creates a new curve by simplifying a given curve by using Douglas-Peucker Algorithm. * * @param curve * input curve to be simplified * @param epsThinning * max. distance value for Douglas-Peucker-Algorithm */ public static GM_Curve getThinnedCurve(final GM_Curve curve, final Double epsThinning) throws GM_Exception { final LineString line = (LineString) JTSAdapter.export(curve); final LineString simplifiedLine = (LineString) DouglasPeuckerSimplifier.simplify(line, epsThinning); final GM_Curve thinnedCurve = (GM_Curve) JTSAdapter.wrap(simplifiedLine); return thinnedCurve; } public static GM_Envelope grabEnvelopeFromDistance(final GM_Point point, final double grabDistance) { final GM_Position position = point.getPosition(); final GM_Envelope posEnvelope = GeometryFactory.createGM_Envelope(position, position, point.getCoordinateSystem()); final double grabDistanceHalf = grabDistance / 2; return posEnvelope.getBuffer(grabDistanceHalf); } public static Feature findNearestFeature(final GM_Point point, final double grabDistance, final FeatureList modelList, final QName geoQName) { final GM_Envelope reqEnvelope = GeometryUtilities.grabEnvelopeFromDistance(point, grabDistance); final List<?> foundElements = modelList.query(reqEnvelope, null); double min = Double.MAX_VALUE; Feature nearest = null; for (final Object feature : foundElements) { final GM_Object geom = (GM_Object) ((Feature) feature).getProperty(geoQName); if (geom != null) { final double curDist = point.distance(geom); if (min > curDist && curDist <= grabDistance) { nearest = (Feature) feature; min = curDist; } } } return nearest; } /** * Same as {@link #findNearestFeature(GM_Point, double, FeatureList, QName)}, but only regards features of certain * qnames. * * @param allowedQNames * Only features that substitute one of these qnames are considered. */ public static Feature findNearestFeature(final GM_Point point, final double grabDistance, final FeatureList modelList, final GMLXPath[] geometryPathes, final QName[] allowedQNames) { if (geometryPathes == null) return null; final GM_Envelope reqEnvelope = GeometryUtilities.grabEnvelopeFromDistance(point, grabDistance); final List<?> foundElements = modelList.query(reqEnvelope, null); double min = Double.MAX_VALUE; Feature nearest = null; final Feature parentFeature = modelList.getOwner(); final GMLWorkspace workspace = parentFeature == null ? null : parentFeature.getWorkspace(); for (final Object object : foundElements) { final Feature feature = FeatureHelper.getFeature(workspace, object); final IFeatureType featureType = feature.getFeatureType(); if (GMLSchemaUtilities.substitutes(featureType, allowedQNames)) { for (final GMLXPath geometryPath : geometryPathes) { try { final Object geomOrList = GMLXPathUtilities.query(geometryPath, feature); final GM_Object[] geometries = findGeometries(geomOrList, GM_Object.class); for (final GM_Object geometry : geometries) { final double curDist = point.distance(geometry); if (min > curDist && curDist <= grabDistance) { nearest = feature; min = curDist; } } } catch (final GMLXPathException e) { e.printStackTrace(); } } } } return nearest; } /** * Returns either the given qnames or all geometry qname's of the given feature. */ public static QName[] getGeometryQNames(final Feature feature, final QName[] geomQNames) { if (feature == null) return geomQNames; if (geomQNames == null) { final IValuePropertyType[] properties = feature.getFeatureType().getAllGeometryProperties(); return toQNames(properties); } return geomQNames; } private static QName[] toQNames(final IValuePropertyType[] properties) { final QName[] result = new QName[properties.length]; for (int i = 0; i < properties.length; i++) result[i] = properties[i].getQName(); return result; } /** * Same as * {@link #findNearestFeature(GM_Point, double, FeatureList, QName, QName[]), but with an array of Featurelists. * * @param allowedQNames * Only features that substitute one of these qnames are considered. */ public static Feature findNearestFeature(final GM_Point point, final double grabDistance, final FeatureList[] modelLists, final QName geoQName, final QName[] allowedQNames) { Feature nearest = null; for (final FeatureList modelList : modelLists) { if (modelList == null) continue; final GM_Envelope reqEnvelope = GeometryUtilities.grabEnvelopeFromDistance(point, grabDistance); final List<Object> foundElements = modelList.query(reqEnvelope, null); double min = Double.MAX_VALUE; for (final Object element : foundElements) { final Feature feature = (Feature) element; if (GMLSchemaUtilities.substitutes(feature.getFeatureType(), allowedQNames)) { final GM_Object geom = (GM_Object) feature.getProperty(geoQName); if (geom != null) { final double curDist = point.distance(geom); if (min > curDist && curDist <= grabDistance) { nearest = feature; min = curDist; } } } } } return nearest; } /** * Calculates the direction (in degrees) from one position to another. * * @return The angle in degree or {@link Double#NaN} if the points coincide. */ public static double directionFromPositions(final GM_Position from, final GM_Position to) { final double vx = to.getX() - from.getX(); final double vy = to.getY() - from.getY(); return directionFromVector(vx, vy); } /** * Calculates the 'direction' of a vector in degrees. The degree value represents the angle between the vector and the * x-Axis in coordinate space. * <p> * Orientation is anti.clockwise (i.e. positive). * </p> * * @return The angle in degree or {@link Double#NaN} if the given vector has length 0. */ public static double directionFromVector(final double vx, final double vy) { final double length = Math.sqrt(vx * vx + vy * vy); if (length == 0.0) // double comparison problems? return Double.NaN; final double alpha = Math.acos(vx / length); if (vy < 0) return Math.toDegrees(2 * Math.PI - alpha); return Math.toDegrees(alpha); } /** * Scales an envelope by the given factor (1 means no scaling) while maintaining the position of its center-point. */ public static GM_Envelope scaleEnvelope(final GM_Envelope zoomBox, final double factor) { final GM_Position zoomMax = zoomBox.getMax(); final GM_Position zoomMin = zoomBox.getMin(); final double newMaxX = zoomMin.getX() + (zoomMax.getX() - zoomMin.getX()) * factor; final double newMinX = zoomMax.getX() - (zoomMax.getX() - zoomMin.getX()) * factor; final double newMaxY = zoomMin.getY() + (zoomMax.getY() - zoomMin.getY()) * factor; final double newMinY = zoomMax.getY() - (zoomMax.getY() - zoomMin.getY()) * factor; final GM_Position newMin = GeometryFactory.createGM_Position(newMinX, newMinY); final GM_Position newMax = GeometryFactory.createGM_Position(newMaxX, newMaxY); return GeometryFactory.createGM_Envelope(newMin, newMax, zoomBox.getCoordinateSystem()); } /** * checks, if a position lies inside or outside of an polygon defined by a position array * * @param pos * position array of the polygon object * @param position * position to be checked * @return 0 - if position lies outside of the polygon<BR> * 1 - if position lies inside of the polygon<BR> * 2 - if position lies on polygon's border. */ public static int pointInsideOrOutside(final GM_Position[] pos, final GM_Position position) { int hits = 0; for (int i = 0; i < pos.length - 1; i++) { /* check, if position lies on ring's border */ final double sC = pos[i].getDistance(pos[i + 1]); final double sA = pos[i].getDistance(position); final double sB = pos[i + 1].getDistance(position); if (Math.abs(sC - sA - sB) < 0.001) return 2; /* calculate determinant */ final double a00 = 2181.2838; final double a10 = 0.31415926; // = PI/10 (??) final double a01 = pos[i].getX() - pos[i + 1].getX(); final double a11 = pos[i].getY() - pos[i + 1].getY(); final double b0 = pos[i].getX() - position.getX(); final double b1 = pos[i].getY() - position.getY(); final double det = a00 * a11 - a10 * a01; if (det == 0.0) { System.out.println("Indefinite problem in pointInsideOrOutside"); } final double x0 = (a11 * b0 - a01 * b1) / det; final double x1 = (-a10 * b0 + a00 * b1) / det; if (x0 > 0. && x1 >= 0. && x1 <= 1.) hits++; } final int check = hits % 2; if (check == 1) return 1; return 0; } /** * Convert the given bounding box into a {@link GM_Curve} */ public static GM_Curve toGM_Curve(final GM_Envelope bBox, final String crs) { try { final GM_Position min = bBox.getMin(); final GM_Position max = bBox.getMax(); final double minx = min.getX(); final double miny = min.getY(); final double maxx = max.getX(); final double maxy = max.getY(); final double[] coords = new double[] { minx, miny, maxx, miny, maxx, maxy, minx, maxy, minx, miny, }; final GM_Curve curve = GeometryFactory.createGM_Curve(coords, 2, crs); return curve; } catch (final Throwable e) { throw new RuntimeException("error while creating a curve", e); //$NON-NLS-1$ } } /** * Tests whether a ring (defined by its positions) is self-intersecting. */ public static boolean isSelfIntersecting(final GM_Position[] ring) { final LineString ls = JTSAdapter.exportAsLineString(ring); return !ls.isSimple(); } protected static GM_Position[] getPolygonPositions(final GM_Curve[] curves, final boolean selfIntersected) throws GM_Exception { /* test for self-intersection */ final List<GM_Position> posList = new ArrayList<>(); /* - add first curve's positions to positions list */ final GM_Position[] positions1 = curves[1].getAsLineString().getPositions(); for (final GM_Position element : positions1) { posList.add(element); } /* - add second curve's positions to positions list */ final GM_Position[] positions2 = curves[0].getAsLineString().getPositions(); if (!selfIntersected) { // not twisted: curves are oriented in the same direction, so we add the second curve's positions in the // opposite direction in order to get a non-self-intersected polygon. for (int i = 0; i < positions2.length; i++) { posList.add(positions2[positions2.length - 1 - i]); } } else { // twisted: curves are oriented in different directions, so we add the second curve's positions // from start to end in order to get a non-self-intersected polygon. for (final GM_Position element : positions2) { posList.add(element); } } /* close polygon position list */ posList.add(positions1[0]); return posList.toArray(new GM_Position[posList.size()]); } /** * converts two given curves into a position array of a ccw oriented polygon.<br> * The ring is simply produced by adding all positions of the first curve and the positions of the second curve in * inverse order.<br> * Produces a closed ring. * * @param curves * the curves as {@link GM_Curve} */ public static GM_Position[] getPolygonfromCurves(final GM_Curve firstCurve, final GM_Curve secondCurve) throws GM_Exception { /* get the positions of the curves */ // as a first guess, we assume that the curves build a non-intersecting polygon final GM_Position[] firstPoses = firstCurve.getAsLineString().getPositions(); final GM_Position[] secondPoses = secondCurve.getAsLineString().getPositions(); final GM_Position[] polygonPositions = new GM_Position[firstPoses.length + secondPoses.length + 1]; for (int i = 0; i < firstPoses.length; i++) polygonPositions[i] = firstPoses[i]; for (int i = 0; i < secondPoses.length; i++) polygonPositions[i + firstPoses.length] = secondPoses[secondPoses.length - i - 1]; polygonPositions[polygonPositions.length - 1] = polygonPositions[0]; return orientateRing(polygonPositions); } /** * converts two given curves into a position array of a non-self-intersecting, ccw oriented, closed polygon * * @param curves * the curves as {@link GM_Curve} */ public static GM_Position[] getPolygonfromCurves(final GM_Curve[] curves) throws GM_Exception { /* get the positions of the curves */ // as a first guess, we assume that the curves build a non-intersecting polygon GM_Position[] polygonPositions = getPolygonPositions(curves, false); // then we check this assumption if (isSelfIntersecting(polygonPositions)) polygonPositions = getPolygonPositions(curves, true); return orientateRing(polygonPositions); } /** * Orientates a ring counter clock wise. * * @return The inverted list of position, or the original list, if the ring was already oriented in the right way. */ public static GM_Position[] orientateRing(final GM_Position[] polygonPositions) { // check orientation if (calcSignedAreaOfRing(polygonPositions) < 0) ArrayUtils.reverse(polygonPositions); return polygonPositions; } /** * Triangulates a closed ring (must be oriented counter-clock-wise). <br> * <b>It uses floats, so there can occur rounding problems!</b><br> * To avoid this, we substract all values with its minimum value. And add it later. * * @return An array of triangles: GM_Position[numberOfTriangles][3] */ public static GM_Position[][] triangulateRing(final GM_Position[] ring) { final float[] posArray = new float[ring.length * 3]; double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double minZ = Double.MAX_VALUE; // find minimum values for (final GM_Position element : ring) { minX = Math.min(minX, element.getX()); minY = Math.min(minY, element.getY()); minZ = Math.min(minZ, element.getZ()); } // if we have no z-value we fake one. if (Double.isNaN(minZ)) { for (int i = 0; i < ring.length; i++) { posArray[i * 3] = (float) (ring[i].getX() - minX); posArray[i * 3 + 1] = (float) (ring[i].getY() - minY); posArray[i * 3 + 2] = new Float(0.0); } } else { for (int i = 0; i < ring.length; i++) { posArray[i * 3] = (float) (ring[i].getX() - minX); posArray[i * 3 + 1] = (float) (ring[i].getY() - minY); posArray[i * 3 + 2] = (float) (ring[i].getZ() - minZ); } } final float[] normal = { 0, 0, 1 }; final int[] output = new int[posArray.length]; final int numVertices = posArray.length / 3; final TriangulationUtils triangulator = new TriangulationUtils(); final int num = triangulator.triangulateConcavePolygon(posArray, 0, numVertices, output, normal); if (num < 0) return new GM_Position[0][0]; final GM_Position[][] triangles = new GM_Position[num][3]; if (Double.isNaN(minZ)) { for (int i = 0; i < num; i++) { triangles[i] = new GM_Position[3]; final double x1 = posArray[output[i * 3]] + minX; final double y1 = posArray[output[i * 3] + 1] + minY; final double z1 = Double.NaN; final double x2 = posArray[output[i * 3 + 1]] + minX; final double y2 = posArray[output[i * 3 + 1] + 1] + minY; final double z2 = Double.NaN; final double x3 = posArray[output[i * 3 + 2]] + minX; final double y3 = posArray[output[i * 3 + 2] + 1] + minY; final double z3 = Double.NaN; triangles[i][0] = GeometryFactory.createGM_Position(x1, y1, z1); triangles[i][1] = GeometryFactory.createGM_Position(x2, y2, z2); triangles[i][2] = GeometryFactory.createGM_Position(x3, y3, z3); } } else { for (int i = 0; i < num; i++) { triangles[i] = new GM_Position[3]; final double x1 = posArray[output[i * 3]] + minX; final double y1 = posArray[output[i * 3] + 1] + minY; final double z1 = posArray[output[i * 3] + 2] + minZ; final double x2 = posArray[output[i * 3 + 1]] + minX; final double y2 = posArray[output[i * 3 + 1] + 1] + minY; final double z2 = posArray[output[i * 3 + 1] + 2] + minZ; final double x3 = posArray[output[i * 3 + 2]] + minX; final double y3 = posArray[output[i * 3 + 2] + 1] + minY; final double z3 = posArray[output[i * 3 + 2] + 2] + minZ; triangles[i][0] = GeometryFactory.createGM_Position(x1, y1, z1); triangles[i][1] = GeometryFactory.createGM_Position(x2, y2, z2); triangles[i][2] = GeometryFactory.createGM_Position(x3, y3, z3); } } return triangles; } public static GM_Envelope envelopeFromRing(final GM_Position[] poses, final String crs) { double minX = poses[0].getX(); double maxX = poses[0].getX(); double minY = poses[0].getY(); double maxY = poses[0].getY(); for (int i = 1; i < poses.length; i++) { minX = Math.min(minX, poses[i].getX()); minY = Math.min(minY, poses[i].getY()); maxX = Math.max(maxX, poses[i].getX()); maxY = Math.max(maxY, poses[i].getY()); } return GeometryFactory.createGM_Envelope(minX, minY, maxX, maxY, crs); } /** * calculates the centroid of a ring of points * <p> * taken from gems iv (modified) * <p> * </p> * this method is only valid for the two-dimensional case. */ public static GM_Position centroidFromRing(final GM_Position[] ring) { double ai; double x; double y; double atmp = 0; double xtmp = 0; double ytmp = 0; // move points to the origin of the coordinate space // (to solve precision issues) final double transX = ring[0].getX(); final double transY = ring[0].getY(); int i; int j; for (i = ring.length - 1, j = 0; j < ring.length; i = j, j++) { final double x1 = ring[i].getX() - transX; final double y1 = ring[i].getY() - transY; final double x2 = ring[j].getX() - transX; final double y2 = ring[j].getY() - transY; ai = x1 * y2 - x2 * y1; atmp += ai; xtmp += (x2 + x1) * ai; ytmp += (y2 + y1) * ai; } if (atmp != 0) { x = xtmp / (3 * atmp) + transX; y = ytmp / (3 * atmp) + transY; } else { x = ring[0].getX(); y = ring[0].getY(); } return GeometryFactory.createGM_Position(x, y); } public static GM_Point centroidFromRing(final GM_Position[] poses, final String crs) { return GeometryFactory.createGM_Point(centroidFromRing(poses), crs); } /** * creates {@link GM_Triangle} with from given positions with Z value from according map field on error returns null */ public static GM_Triangle createTriangleForBilinearInterpolation( final Map<GM_Point, Double> mapPositionsValues) { if (mapPositionsValues.size() > 3) { return null; } try { final List<GM_Position> lListPositionWithValues = new ArrayList<>(); final Set<GM_Point> lSetKeys = mapPositionsValues.keySet(); GM_Point gmPoint = null; for (final Iterator<GM_Point> iterator = lSetKeys.iterator(); iterator.hasNext();) { gmPoint = iterator.next(); lListPositionWithValues.add(GeometryFactory.createGM_Position(gmPoint.getX(), gmPoint.getY(), mapPositionsValues.get(gmPoint))); } return GeometryFactory.createGM_Triangle(lListPositionWithValues.get(0), lListPositionWithValues.get(1), lListPositionWithValues.get(2), gmPoint.getCoordinateSystem()); } catch (final Exception e) { return null; } } /** * Adapts a given object to one or more GM_Objects of a given type. */ public static <T extends GM_Object> T[] findGeometries(final Object geomOrList, final Class<T> type) { final T[] emptyArray = (T[]) Array.newInstance(type, 0); if (geomOrList == null) return emptyArray; final Class<? extends GM_Object[]> arrayType = emptyArray.getClass(); if (geomOrList instanceof GM_Object) { final T[] adapter = (T[]) ((GM_Object) geomOrList).getAdapter(arrayType); if (adapter == null) return emptyArray; return adapter; } if (geomOrList instanceof List) return findGeometries((List<?>) geomOrList, arrayType); return emptyArray; } private static <T extends GM_Object> T[] findGeometries(final List<?> geomList, final Class<? extends GM_Object[]> arrayType) { final List<T> result = new ArrayList<>(); for (final Object geom : geomList) { if (geom instanceof GM_Object) { final T[] geometries = (T[]) ((GM_Object) geom).getAdapter(arrayType); result.addAll(Arrays.asList(geometries)); } } final T[] store = (T[]) Array.newInstance(arrayType.getComponentType(), result.size()); return result.toArray(store); } public static <G extends GM_Object> G[] transform(final G[] input, final IGeoTransformer transformer) throws Exception { final G[] result = (G[]) Array.newInstance(input.getClass().getComponentType(), input.length); for (int i = 0; i < result.length; i++) { if (input[i] != null) result[i] = (G) transformer.transform(input[i]); } return result; } public static final Rectangle toRectangle(final GM_Envelope envelope) { if (envelope == null) return null; // FIXME/HOTFIX: rounding double to float looses decimal places, so we get problems when comparing envelopes later // We now always create a slightly bigger envelope that is guarantueed to contain the original envelope final float x1 = MathUtils.floorFloat(envelope.getMinX()); final float y1 = MathUtils.floorFloat(envelope.getMinY()); final float x2 = MathUtils.ceilFloat(envelope.getMaxX()); final float y2 = MathUtils.ceilFloat(envelope.getMaxY()); return new Rectangle(x1, y1, x2, y2); } public static GM_Envelope toEnvelope(final Rectangle bounds, final String crs) { if (bounds == null) return null; return GeometryFactory.createGM_Envelope(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY, crs); } /** * Create the union of two or more envelopes. Handles <code>null</code> envelopes. * * @return The smallest envelope that covers all the given envelopes. <code>null</code>, if all given envelopes are <code>null</code>. * @see GM_Envelope#getMerged(GM_Envelope) */ public static GM_Envelope mergeEnvelopes(final GM_Envelope... envelopes) { GM_Envelope union = null; for (final GM_Envelope envelope : envelopes) { if (union == null) union = envelope; else union = union.getMerged(envelope); } return union; } }