/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2012, Geomatys * * 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; * version 2.1 of the License. * * 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. */ package org.geotoolkit.internal.tree; import org.apache.sis.math.MathFunctions; import org.apache.sis.util.ArgumentChecks; import org.opengis.geometry.Envelope; /** * Some utilities methods. * * @author Rémi Maréchal (Geomatys). */ public class TreeUtilities { /** * Properties which define if a {@link Node} is a leaf.<br/> * See {@link Node#isLeaf()}. */ public final static byte IS_LEAF = 1; /** * Properties which define if a {@link Node} is a data, which mean its child id represent tree identifier of a data.<br/> * See {@link Node#isData()}. */ public final static byte IS_DATA = 2; /** * Properties which define if a {@link Node} is a Hilbert Cell.<br/> * See {@link HilbertNode}.<br/> * See {@link HilbertNode#isCell() }. */ public final static byte IS_CELL = 4; /** * Properties which define if a {@link Node} is a "tree branch" * which mean Node with no particularity properties. */ public final static byte IS_OTHER = 8; /** * Numbers to identify tree type in file. */ public final static int BASIC_NUMBER = 188047901; public final static int HILBERT_NUMBER = 69669745; public final static int STAR_NUMBER = 23107209; public final static double VERSION_NUMBER = 0.1; private TreeUtilities() { } /** * Compute "envelope" bulk from its double coordinates table. * * @param envelope coordinates. * @throws IllegalArgumentException if envelope is null. * @throws IllegalArgumentException if envelope dimension < 3. * @return bulk value. */ public static double getBulk(final double[] envelope) { ArgumentChecks.ensureNonNull("getBulk : envelope", envelope); final int dim = envelope.length >> 1; if (dim < 3) throw new IllegalArgumentException("getGeneralEnvelopBulk : compute envelop bulk with lesser than three dimensions have no sens"); double bulk = 1; for (int i = 0; i < dim; i++) bulk *= getSpan(envelope, i); return bulk; } /** * Compute "Envelope" perimeter from its double coordinates table. * * @param envelope coordinates. * @throws IllegalArgumentException if envelope is null. * @throws IllegalArgumentException if envelope dimension > 2. * @return perimeter value. */ public static double getPerimeter(final double[] envelope) { ArgumentChecks.ensureNonNull("getPerimeter : envelope", envelope); final int dim = envelope.length >> 1; if (dim != 2) throw new IllegalArgumentException("getGeneralEnvelopPerimeter : compute envelop perimeter with more or lesser than two dimension have no sens"); double perim = 0; for (int i = 0, l = dim; i < l; i++) perim += getSpan(envelope, i); return 2 * perim; } /** * Compute overlaps between two {@code Envelop}. * * <blockquote><font size=-1> * <strong>NOTE: In first time : compute intersection {@code Envelope} between envelopA and envelopB. * - If intersection dimension is 2 compute its area. * - If intersection dimension is 3 compute its bulk.</strong> * </font></blockquote> * * @param envelopA * @param envelopB * @return intersection between envelopA, envelopB bulk, or area from area dimension. */ public static double getOverlapValue(final double[] envelopA, final double[] envelopB) { ArgumentChecks.ensureNonNull("getOverlapValue : envelopA", envelopA); ArgumentChecks.ensureNonNull("getOverlapValue : envelopB", envelopB); if (!intersects(envelopA, envelopB, true)) return 0; final double[] intersectionGN = intersect(envelopA, envelopB); return ((intersectionGN.length >> 1) == 2) ? getArea(intersectionGN) : getBulk(intersectionGN); } /** * Compute Euclidean distance between two {@code double[]} in dimension n. * * @param positionA : coordinate double table of point A. * @param positionB : coordinate double table of point B. * @throws IllegalArgumentException if positionA or positionB are null. * @throws IllegalArgumentException if positionA or positionB are not in same dimension. * @return distance between positionA and positionB. */ public static double getDistanceBetween2Positions(final double[] positionA, final double[] positionB) { ArgumentChecks.ensureNonNull("getDistanceBetween2Positions : positionA", positionA); ArgumentChecks.ensureNonNull("getDistanceBetween2Positions : positionB", positionB); final int length = positionA.length; if (length != positionB.length) throw new IllegalArgumentException("getDistanceBetween2Positions : positionA and positionB are not in same dimension"); final double[] tab = new double[length]; for (int i = 0; i < length; i++) { tab[i] = positionA[i] - positionB[i]; } return MathFunctions.magnitude(tab); } /** * Compute Euclidean distance between two {@code Envelope} in dimension n. * * @param envelopA * @param envelopB * @throws IllegalArgumentException if envelopA or envelopB are null. * @throws IllegalArgumentException if envelopA or envelopB are not in same dimension. * @return distance between envelopA and envelopB centroids. */ public static double getDistanceBetween2Envelopes(final double[] envelopA, final double[] envelopB){ ArgumentChecks.ensureNonNull("getDistanceBetween2Envelopes : envelopA", envelopA); ArgumentChecks.ensureNonNull("getDistanceBetween2Envelopes : envelopB", envelopB); if(envelopA.length != envelopB.length) throw new IllegalArgumentException("getDistanceBetween2Envelopes : envelopA and envelopB are not in same dimension"); assert (envelopA.length % 2 == 0) :"envelope coordinates length should be modulo 2"; return getDistanceBetween2Positions(getMedian(envelopA), getMedian(envelopB)); } /** * Compute general boundary of all {@code Envelope} passed in parameter. * * @param lS GeneralEnvelope List. * @throws IllegalArgumentException if {@code Envelope} list lS is null. * @throws IllegalArgumentException if {@code Envelope} list lS is empty. * @return GeneralEnvelope which is general boundary. */ public static double[] getEnvelopeMin(final double[][] coordinates){ ArgumentChecks.ensureNonNull("getEnveloppeMin : coordinates", coordinates); if(coordinates == null || coordinates.length == 0){ throw new IllegalArgumentException("impossible to get Envelope min from null or empty table."); } final double[] envelope = coordinates[0].clone(); for (int i = 1, s = coordinates.length; i < s; i++) { add(envelope, coordinates[i]); } return envelope; } public static boolean arrayEquals(final double[] expected, final double[] value, final double epsilon) { ArgumentChecks.ensureNonNull("arrayEquals : expected : ", expected); ArgumentChecks.ensureNonNull("arrayEquals : value : ", value); ArgumentChecks.ensurePositive("arrayEquals : epsilon", epsilon); final int l = expected.length; if (l != value.length) return false; for (int i = 0; i < l; i++) if (Math.abs(expected[i] - value[i]) > epsilon) return false; return true; } /** * Return double coordinate table which contain Envelope coordinate. * Table result length is 2*envelope dimension. * First table part contain envelope lower corner coordinates and second part, upper corner coordinates. * * @param envelope * @param coords table where is store coordinate. if null a new table is create. * @return double coordinate table which contain Envelope coordinate. */ public static double[] getCoords(final Envelope envelope) { ArgumentChecks.ensureNonNull("getCoords : envelope", envelope); final int dim = envelope.getDimension(); final double[] coords = new double[dim <<1]; for (int i = 0, d = dim; i < dim; i++, d++) { coords[i] = envelope.getMinimum(i); coords[d] = envelope.getMaximum(i); } return coords; } /** * Compute union between two "envelope" coordinate double tables. * Result is set in envelopeA. * * @return envelopeA which contain result of union. */ public static double[] add(final double[] envelopeA, final double[] envelopeB) { ArgumentChecks.ensureNonNull("EnvelopeA", envelopeA); ArgumentChecks.ensureNonNull("EnvelopeB", envelopeB); assert (envelopeA.length == envelopeB.length) :"getUnion : envelope should have same dimension number."; final int dim = envelopeA.length >> 1; for (int i = 0, d = dim; i < dim; i++, d++) { envelopeA[i] = Math.min(envelopeA[i], envelopeB[i]); envelopeA[d] = Math.max(envelopeA[d], envelopeB[d]); } return envelopeA; } /** * Compute intersection between two "envelope" coordinate double tables. * * @return double table which contain result of intersection or null if none. */ public static double[] intersect(final double[] envelopeA, final double[] envelopeB) { ArgumentChecks.ensureNonNull("EnvelopeA", envelopeA); ArgumentChecks.ensureNonNull("EnvelopeB", envelopeB); assert (envelopeA.length == envelopeB.length) :"intersect : envelope should have same dimension number."; final double[] intersect = envelopeA.clone(); final int dim = intersect.length >> 1; for (int i = 0, d = dim; i < dim; i++, d++) { intersect[i] = Math.max(intersect[i], envelopeB[i]); intersect[d] = Math.min(intersect[d], envelopeB[d]); if (intersect[i] > intersect[d]) return null; } return intersect; } /** * Return true if there is intersection between two "envelope" coordinate double tables. * * @param envelopeA first envelope coordinates. * @param envelopeB second envelope coordinates. * @param edgeInclusive if true return true if the 2 "envelope" just touches else false. * @return true if there is intersection else false. */ public static boolean intersects(final double[] envelopeA, final double[] envelopeB, final boolean edgeInclusive) { ArgumentChecks.ensureNonNull("EnvelopeA", envelopeA); ArgumentChecks.ensureNonNull("EnvelopeB", envelopeB); assert (envelopeA.length == envelopeB.length) :"intersects : envelope should have same dimension number."; final int dim = envelopeA.length >> 1; double low, upp; for (int i = 0, d = dim; i < dim; i++, d++) { low = Math.max(envelopeA[i], envelopeB[i]); upp = Math.min(envelopeA[d], envelopeB[d]); if (edgeInclusive && low > upp || !edgeInclusive && low >= upp) return false; } return true; } /** * Return true if the 2 "envelopes" touches them else false. * * @param envelopeA first envelope coordinates. * @param envelopeB second envelope coordinates. * @return true if the 2 "envelopes" touches them else false. */ public static boolean touches(final double[] envelopeA, final double[] envelopeB){ final double epsilon = 1E-15; if (!intersects(envelopeA, envelopeB, true)) return false; if (intersects(envelopeA, envelopeB, false)) return false; final double[] intersection = intersect(envelopeA, envelopeB); /** * If one dimension from intersection, equals to envelopeA and envelopeB boundary touches is true. */ final int dim = intersection.length >> 1; for (int i = 0; i < dim; i++) { if (getSpan(intersection, i) < epsilon) { final double val = intersection[i]; final double minA = Math.abs(val - getMinimum(envelopeA, i)); final double minB = Math.abs(val - getMinimum(envelopeB, i)); final double maxA = Math.abs(val - getMaximum(envelopeA, i)); final double maxB = Math.abs(val - getMaximum(envelopeB, i)); if (((minA < epsilon && maxB < epsilon) || (maxA < epsilon && minB < epsilon))) return true; } } return false; } /** * Return true if envelopeA contain envelopeB or envelope is within envelopeA. * * @param envelopeA first envelope coordinates. * @param envelopeB second envelope coordinates. * @param edgeInclusive * @return true if envelopeA contain envelopeB or envelope is within envelopeA. */ public static boolean contains(final double[] envelopeA, final double[] envelopeB, final boolean edgeInclusive) { ArgumentChecks.ensureNonNull("EnvelopeA", envelopeA); ArgumentChecks.ensureNonNull("EnvelopeB", envelopeB); assert (envelopeA.length == envelopeB.length) :"contains : envelope should have same dimension number."; final int dim = envelopeA.length >> 1;//decalbit for (int i = 0, d = dim; i < dim; i++, d++) { if ((edgeInclusive && (envelopeB[i] < envelopeA[i] || envelopeB[d] > envelopeA[d])) || (!edgeInclusive && (envelopeB[i] <= envelopeA[i] || envelopeB[d] >= envelopeA[d]))) return false; } return true; } /** * Return true if envelopeA contain envelopeB or envelope is within envelopeA. * * @param envelopeA first envelope coordinates. * @param envelopeB second envelope coordinates. * @param edgeInclusive * @return true if envelopeA contain envelopeB or envelope is within envelopeA. */ public static boolean contains(final double[] envelope, final double[] point) { ArgumentChecks.ensureNonNull("Envelope", envelope); ArgumentChecks.ensureNonNull("Point", point); final int dim = envelope.length >> 1; assert (dim == point.length) :"contains : envelope should have same dimension number."; for (int i = 0, d = dim; i < dim; i++, d++) { if (point[i] < envelope[i] || point[i] > envelope[d]) return false; } return true; } /**Compute {@code Envelope} area in euclidean cartesian space. * * @param envelope * @return candidate area. */ public static double getArea(final double[] envelope){ ArgumentChecks.ensureNonNull("getArea : envelop", envelope); double area = 0; final int dim = envelope.length >> 1; for(int i = 0; i < dim-1; i++) { for(int j = i+1; j < dim; j++) { area += getSpan(envelope, i) * getSpan(envelope, j); } } return (dim-1) * (area); } /** * Return span value at index i from envelope table coordinates. * * @param envelope * @return span value at index i from envelope table coordinates. */ public static double getSpan(final double[] envelope, final int i){ ArgumentChecks.ensureNonNull("getSpan : envelop", envelope); int dim = envelope.length; assert (dim % 2 == 0) : "envelope dimension invalide. It should be modulo 2"; dim = dim >> 1; ArgumentChecks.ensureBetween("dimension : envelop", 0, dim, i); return envelope[dim + i] - envelope[i];//maybe math.abs } /** * Return minimum value at index i from envelope table coordinates. * * @param envelope * @return minimum value at index i from envelope table coordinates. */ public static double getMinimum(final double[] envelope, int i){ ArgumentChecks.ensureNonNull("getSpan : envelop", envelope); int dim = envelope.length;//decal bit assert (dim % 2 == 0) : "envelope dimension invalide. It should be modulo 2"; dim = dim >> 1; ArgumentChecks.ensureBetween("dimension : envelop", 0, dim, i); return envelope[i]; } /** * Return maximum value at index i from envelope table coordinates. * * @param envelope * @return maximum value at index i from envelope table coordinates. */ public static double getMaximum(final double[] envelope, int i){ ArgumentChecks.ensureNonNull("getSpan : envelop", envelope); int dim = envelope.length;//decal bit assert (dim % 2 == 0) : "envelope dimension invalide. It should be modulo 2"; dim = dim >> 1; ArgumentChecks.ensureBetween("dimension : envelop", 0, dim, i); return envelope[dim + i]; } /** * A coordinate position consisting of all the {@linkplain #getMedian(int) middle ordinates} * for each dimension for all points within the {@code Envelope}. * * @return The median coordinates. * * @param env */ public static double[] getMedian(final double[] envelope) { ArgumentChecks.ensureNonNull("getMedian : envelop", envelope); final int dim = envelope.length >> 1;//decal bit final double[] median = new double[dim]; for (int i = 0, d = dim; i < dim; i++, d++) { median[i] = (envelope[i] + envelope[d]) / 2;//decal bit; } return median; } /** * A coordinate position consisting of all the {@linkplain #getMinimum(double[], int) minimum ordinates} * for each dimension from the {@code Envelope}. * * @return The lower corner coordinates. * * @param env */ public static double[] getLowerCorner(final double[] envelope) { ArgumentChecks.ensureNonNull("getLowerCorner : envelop", envelope); int dim = envelope.length;//decal bit assert (dim % 2 == 0) : "envelope dimension invalide. It should be modulo 2"; dim = dim >> 1; final double[] lowerCorner = new double[dim]; System.arraycopy(envelope, 0, lowerCorner, 0, dim); return lowerCorner; } /** * A coordinate position consisting of all the {@linkplain #getMaximum(double[],int) maximum ordinates} * for each dimension from the {@code Envelope}. * * @return The upper coordinates. * * @param env */ public static double[] getUpperCorner(final double[] envelope) { ArgumentChecks.ensureNonNull("getLowerCorner : envelop", envelope); int dim = envelope.length;//decal bit assert (dim % 2 == 0) : "envelope dimension invalide. It should be modulo 2"; dim = dim >> 1; final double[] upperCorner = new double[dim]; System.arraycopy(envelope, dim, upperCorner, 0, dim); return upperCorner; } }