All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.geometry.AbstractEnvelope Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2004-2012, Open Source Geospatial Foundation (OSGeo)
 *    (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.geometry;

import java.awt.geom.Rectangle2D;
import javax.measure.unit.Unit;
import javax.measure.converter.ConversionException;

import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.RangeMeaning;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.display.shape.XRectangle2D;

import static org.geotoolkit.internal.InternalUtilities.epsilonEqual;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.util.Strings.trimFractionalPart;
import static org.geotoolkit.math.XMath.SIGN_BIT_MASK;
import static org.geotoolkit.math.XMath.isNegative;
import static org.geotoolkit.math.XMath.isPositive;


/**
 * Base class for {@linkplain Envelope envelope} implementations. This base class does not hold any
 * state and does not implement the {@link java.io.Serializable} or {@link org.geotoolkit.util.Cloneable}
 * interfaces. The internal representation, and the choice to be cloneable or serializable, is left
 * to implementors.
 * 

* Implementors needs to define at least the following methods: *

*

    *
  • {@link #getDimension()}
  • *
  • {@link #getCoordinateReferenceSystem()}
  • *
  • {@link #getLower(int)}
  • *
  • {@link #getUpper(int)}
  • *
*

* All other methods, including {@link #toString()}, {@link #equals(Object)} and {@link #hashCode()}, * are implemented on top of the above four methods. * * {@section Spanning the anti-meridian of a Geographic CRS} * The Web Coverage Service (WCS) specification authorizes (with special treatment) * cases where upper < lower at least in the longitude case. They are * envelopes crossing the anti-meridian, like the red box below (the green box is the usual case). * The default implementation of methods listed in the right column can handle such cases. * *

* * * Supported methods: *
    *
  • {@link #getMinimum(int)}
  • *
  • {@link #getMaximum(int)}
  • *
  • {@link #getMedian(int)}
  • *
  • {@link #getSpan(int)}
  • *
  • {@link #contains(DirectPosition)}
  • *
  • {@link #contains(Envelope, boolean)}
  • *
  • {@link #intersects(Envelope, boolean)}
  • *
*
* * {@section Note on positive and negative zeros} * The IEEE 754 standard defines two different values for positive zero and negative zero. * When used with Geotk envelopes and keeping in mind the above discussion, those zeros have * different meanings: *

*

    *
  • The [-0…0°] range is an empty envelope.
  • *
  • The [0…-0°] range makes a full turn around the globe, like the [-180…180°] * range except that the former range spans across the anti-meridian.
  • *
* * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @since 2.4 * @module */ public abstract class AbstractEnvelope implements Envelope { /** * Constructs an envelope. */ protected AbstractEnvelope() { } /** * Returns the given envelope as an {@code AbstractEnvelope} instance. If the given envelope * is already an instance of {@code AbstractEnvelope}, then it is returned unchanged. * Otherwise the coordinate values and the CRS of the given envelope are copied in a * new envelope. * * @param envelope The envelope to cast, or {@code null}. * @return The values of the given envelope as an {@code AbstractEnvelope} instance. * * @see GeneralEnvelope#castOrCopy(Envelope) * @see ImmutableEnvelope#castOrCopy(Envelope) * * @since 3.20 */ public static AbstractEnvelope castOrCopy(final Envelope envelope) { if (envelope == null || envelope instanceof AbstractEnvelope) { return (AbstractEnvelope) envelope; } return new GeneralEnvelope(envelope); } /** * Returns {@code true} if at least one of the specified CRS is null, or both CRS are equals. * This special processing for {@code null} values is different from the usual contract of an * {@code equals} method, but allow to handle the case where the CRS is unknown. *

* Note that in debug mode (to be used in assertions only), the comparison are actually a bit * more relax than just "ignoring metadata", since some rounding errors are tolerated. */ static boolean equalsIgnoreMetadata(final CoordinateReferenceSystem crs1, final CoordinateReferenceSystem crs2, final boolean debug) { return (crs1 == null) || (crs2 == null) || Utilities.deepEquals(crs1, crs2, debug ? ComparisonMode.DEBUG : ComparisonMode.IGNORE_METADATA); } /** * Returns the common CRS of specified points. * * @param lower The first position. * @param upper The second position. * @return Their common CRS, or {@code null} if none. * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS. */ static CoordinateReferenceSystem getCoordinateReferenceSystem(final DirectPosition lower, final DirectPosition upper) { final CoordinateReferenceSystem crs1 = lower.getCoordinateReferenceSystem(); final CoordinateReferenceSystem crs2 = upper.getCoordinateReferenceSystem(); if (crs1 == null) { return crs2; } else { if (crs2 != null && !crs1.equals(crs2)) { throw new IllegalArgumentException( Errors.format(Errors.Keys.MISMATCHED_COORDINATE_REFERENCE_SYSTEM)); } return crs1; } } /** * Returns the axis of the given coordinate reference system for the given dimension, * or {@code null} if none. * * @param crs The envelope CRS, or {@code null}. * @param dimension The dimension for which to get the axis. * @return The axis at the given dimension, or {@code null}. */ static CoordinateSystemAxis getAxis(final CoordinateReferenceSystem crs, final int dimension) { if (crs != null) { final CoordinateSystem cs = crs.getCoordinateSystem(); if (cs != null) { return cs.getAxis(dimension); } } return null; } /** * Returns {@code true} if the axis for the given dimension has the * {@link RangeMeaning#WRAPAROUND WRAPAROUND} range meaning. * * @param crs The envelope CRS, or {@code null}. * @param dimension The dimension for which to get the axis. * @return {@code true} if the range meaning is {@code WRAPAROUND}. */ static boolean isWrapAround(final CoordinateReferenceSystem crs, final int dimension) { final CoordinateSystemAxis axis = getAxis(crs, dimension); return (axis != null) && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning()); } /** * If the range meaning of the given axis is "wraparound", returns the spanning of that axis. * Otherwise returns {@link Double#NaN}. * * @param axis The axis for which to get the spanning. * @return The spanning of the given axis. */ static double getSpan(final CoordinateSystemAxis axis) { if (axis != null && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) { return axis.getMaximumValue() - axis.getMinimumValue(); } return Double.NaN; } /** * Returns {@code true} if the given value is negative, without checks for {@code NaN}. * This method should be invoked only when the number is known to not be {@code NaN}, * otherwise the safer {@link org.geotoolkit.math.XMath#isNegative(double)} method shall * be used instead. Note that the check for {@code NaN} doesn't need to be explicit. * For example in the following code, {@code NaN} values were implicitly checked by * the {@code (a < b)} comparison: * * {@preformat java * if (a < b && isNegativeUnsafe(a)) { * // ... do some stuff * } * } */ static boolean isNegativeUnsafe(final double value) { return (Double.doubleToRawLongBits(value) & SIGN_BIT_MASK) != 0; } /** * A coordinate position consisting of all the {@linkplain #getLower(int) lower ordinates}. * The default implementation returns a unmodifiable direct position backed by this envelope, * so changes in this envelope will be immediately reflected in the direct position. * * {@note The Web Coverage Service (WCS) 1.1 specification uses an extended * interpretation of the bounding box definition. In a WCS 1.1 data structure, the lower * corner defines the edges region in the directions of decreasing coordinate * values in the envelope CRS. This is usually the algebraic minimum coordinates, but not * always. For example, an envelope crossing the anti-meridian could have a lower corner * longitude greater than the upper corner longitude. Such extended interpretation applies * mostly to axes having WRAPAROUND range meaning.} * * @return The lower corner, typically (but not necessarily) containing minimal ordinate values. */ @Override public DirectPosition getLowerCorner() { return new LowerCorner(); } /** * A coordinate position consisting of all the {@linkplain #getUpper(int) upper ordinates}. * The default implementation returns a unmodifiable direct position backed by this envelope, * so changes in this envelope will be immediately reflected in the direct position. * * {@note The Web Coverage Service (WCS) 1.1 specification uses an extended * interpretation of the bounding box definition. In a WCS 1.1 data structure, the upper * corner defines the edges region in the directions of increasing coordinate * values in the envelope CRS. This is usually the algebraic maximum coordinates, but not * always. For example, an envelope crossing the anti-meridian could have an upper corner * longitude less than the lower corner longitude. Such extended interpretation applies * mostly to axes having WRAPAROUND range meaning.} * * @return The upper corner, typically (but not necessarily) containing maximal ordinate values. */ @Override public DirectPosition getUpperCorner() { return new UpperCorner(); } /** * Returns the limit in the direction of decreasing ordinate values in the specified dimension. * This is usually the algebraic {@linkplain #getMinimum(int) minimum}, except if this envelope * spans the anti-meridian. * * @param dimension The dimension for which to obtain the ordinate value. * @return The starting ordinate value at the given dimension. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ public abstract double getLower(int dimension) throws IndexOutOfBoundsException ; /** * Returns the limit in the direction of increasing ordinate values in the specified dimension. * This is usually the algebraic {@linkplain #getMaximum(int) maximum}, except if this envelope * spans the anti-meridian. * * @param dimension The dimension for which to obtain the ordinate value. * @return The starting ordinate value at the given dimension. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ public abstract double getUpper(int dimension) throws IndexOutOfBoundsException ; /** * Returns the minimal ordinate value for the specified dimension. In the typical case * of envelopes not spanning the anti-meridian, this method returns the * {@link #getLower(int)} value verbatim. In the case of envelope spanning the anti-meridian, * this method returns the {@linkplain CoordinateSystemAxis#getMinimumValue() axis minimum value}. * * @param dimension The dimension for which to obtain the ordinate value. * @return The minimal ordinate value at the given dimension. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ @Override public double getMinimum(final int dimension) throws IndexOutOfBoundsException { double lower = getLower(dimension); if (isNegative(getUpper(dimension) - lower)) { // Special handling for -0.0 final CoordinateSystemAxis axis = getAxis(getCoordinateReferenceSystem(), dimension); lower = (axis != null) ? axis.getMinimumValue() : Double.NEGATIVE_INFINITY; } return lower; } /** * Returns the maximal ordinate value for the specified dimension. In the typical case * of envelopes not spanning the anti-meridian, this method returns the * {@link #getUpper(int)} value verbatim. In the case of envelope spanning the anti-meridian, * this method returns the {@linkplain CoordinateSystemAxis#getMaximumValue() axis maximum value}. * * @param dimension The dimension for which to obtain the ordinate value. * @return The maximal ordinate value at the given dimension. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ @Override public double getMaximum(final int dimension) throws IndexOutOfBoundsException { double upper = getUpper(dimension); if (isNegative(upper - getLower(dimension))) { // Special handling for -0.0 final CoordinateSystemAxis axis = getAxis(getCoordinateReferenceSystem(), dimension); upper = (axis != null) ? axis.getMaximumValue() : Double.POSITIVE_INFINITY; } return upper; } /** * 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. * * @since 3.20 (derived from 2.5) */ public DirectPosition getMedian() { final GeneralDirectPosition position = new GeneralDirectPosition(getDimension()); for (int i=position.ordinates.length; --i>=0;) { position.ordinates[i] = getMedian(i); } position.setCoordinateReferenceSystem(getCoordinateReferenceSystem()); return position; } /** * Returns the median ordinate along the specified dimension. In most cases, the result is * equals (minus rounding error) to: * * {@preformat java * median = (getUpper(dimension) + getLower(dimension)) / 2; * } * * {@section Spanning the anti-meridian of a Geographic CRS} * If upper < lower and the * {@linkplain CoordinateSystemAxis#getRangeMeaning() range meaning} for the requested * dimension is {@linkplain RangeMeaning#WRAPAROUND wraparound}, then the median calculated * above is actually in the middle of the space outside the envelope. In such cases, * this method shifts the median value by half of the periodicity (180° in the * longitude case) in order to switch from outer space to inner * space. If the axis range meaning is not {@code WRAPAROUND}, then this method returns * {@link Double#NaN NaN}. * * @param dimension The dimension for which to obtain the ordinate value. * @return The median ordinate at the given dimension, or {@link Double#NaN}. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ @Override public double getMedian(final int dimension) throws IndexOutOfBoundsException { final double lower = getLower(dimension); final double upper = getUpper(dimension); double median = 0.5 * (lower + upper); if (isNegative(upper - lower)) { // Special handling for -0.0 median = fixMedian(getAxis(getCoordinateReferenceSystem(), dimension), median); } return median; } /** * Shifts the median value when the minimum is greater than the maximum. * If no shift can be applied, returns {@code NaN}. */ static double fixMedian(final CoordinateSystemAxis axis, final double median) { if (axis != null && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) { final double minimum = axis.getMinimumValue(); final double maximum = axis.getMaximumValue(); final double cycle = maximum - minimum; if (cycle > 0 && cycle != Double.POSITIVE_INFINITY) { // The copySign is for shifting in the direction of the valid range center. return median + 0.5 * Math.copySign(cycle, 0.5*(minimum + maximum) - median); } } return Double.NaN; } /** * Returns the envelope span (typically width or height) along the specified dimension. * In most cases, the result is equals (minus rounding error) to: * * {@preformat java * span = getUpper(dimension) - getLower(dimension); * } * * {@section Spanning the anti-meridian of a Geographic CRS} * If upper < lower and the * {@linkplain CoordinateSystemAxis#getRangeMeaning() range meaning} for the requested * dimension is {@linkplain RangeMeaning#WRAPAROUND wraparound}, then the span calculated * above is negative. In such cases, this method adds the periodicity (typically 360° of * longitude) to the span. If the result is a positive number, it is returned. Otherwise * this method returns {@link Double#NaN NaN}. * * @param dimension The dimension for which to obtain the span. * @return The span (typically width or height) at the given dimension, or {@link Double#NaN}. * @throws IndexOutOfBoundsException If the given index is negative or is equals or greater * than the {@linkplain #getDimension() envelope dimension}. * * @since 3.20 */ @Override public double getSpan(final int dimension) { double span = getUpper(dimension) - getLower(dimension); if (isNegative(span)) { // Special handling for -0.0 span = fixSpan(getAxis(getCoordinateReferenceSystem(), dimension), span); } return span; } /** * Transforms a negative span into a valid value if the axis range meaning is "wraparound". * Returns {@code NaN} otherwise. * * @param axis The axis for the span dimension, or {@code null}. * @param span The negative span. * @return A positive span, or NaN if the span can not be fixed. */ static double fixSpan(final CoordinateSystemAxis axis, double span) { if (axis != null && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) { final double cycle = axis.getMaximumValue() - axis.getMinimumValue(); if (cycle > 0 && cycle != Double.POSITIVE_INFINITY) { span += cycle; if (span >= 0) { return span; } } } return Double.NaN; } /** * Returns the envelope span along the specified dimension, in terms of the given units. * The default implementation invokes {@link #getSpan(int)} and converts the result. * * @param dimension The dimension to query. * @param unit The unit for the return value. * @return The span in terms of the given unit. * @throws IndexOutOfBoundsException If the given index is out of bounds. * @throws ConversionException if the length can't be converted to the specified units. * * @since 3.20 (derived from 2.5) */ public double getSpan(final int dimension, final Unit unit) throws IndexOutOfBoundsException, ConversionException { double value = getSpan(dimension); final CoordinateSystemAxis axis = getAxis(getCoordinateReferenceSystem(), dimension); if (axis != null) { final Unit source = axis.getUnit(); if (source != null) { value = source.getConverterToAny(unit).convert(value); } } return value; } /** * Determines whether or not this envelope is empty. An envelope is non-empty only if it has * at least one {@linkplain #getDimension() dimension}, and the {@linkplain #getSpan(int) span} * is greater than 0 along all dimensions. Note that a non-empty envelope is always * non-{@linkplain #isNull() null}, but the converse is not always true. * * @return {@code true} if this envelope is empty. * * @see org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox#isEmpty() * @see java.awt.geom.Rectangle2D#isEmpty() * * @since 3.20 (derived from 1.2) */ public boolean isEmpty() { final int dimension = getDimension(); if (dimension == 0) { return true; } for (int i=0; i 0)) { // Use '!' in order to catch NaN return true; } } assert !isNull() : this; return false; } /** * Returns {@code false} if at least one ordinate value is not {@linkplain Double#NaN NaN}. The * {@code isNull()} check is a little bit different than {@link #isEmpty()} since it returns * {@code false} for a partially initialized envelope, while {@code isEmpty()} returns * {@code false} only after all dimensions have been initialized. More specifically, the * following rules apply: *

*

    *
  • If isNull() == true, then {@linkplain #isEmpty()} == true
  • *
  • If {@linkplain #isEmpty()} == false, then isNull() == false
  • *
  • The converse of the above-cited rules are not always true.
  • *
* * @return {@code true} if this envelope has NaN values. * * @see GeneralEnvelope#setToNull() * * @since 3.20 (derived from 2.2) */ public boolean isNull() { final int dimension = getDimension(); for (int i=0; iupper < lower then this method uses an * algorithm which is the opposite of the usual one: rather than testing if the given point is * inside the envelope interior, this method tests if the given point is outside the * envelope exterior. * * @param position The point to text. * @return {@code true} if the specified coordinate is inside the boundary * of this envelope; {@code false} otherwise. * @throws MismatchedDimensionException if the specified point doesn't have * the expected dimension. * @throws AssertionError If assertions are enabled and the envelopes have mismatched CRS. * * @since 3.20 (derived from 3.00) */ public boolean contains(final DirectPosition position) throws MismatchedDimensionException, AssertionError { ensureNonNull("position", position); final int dimension = getDimension(); AbstractDirectPosition.ensureDimensionMatch("point", position.getDimension(), dimension); assert equalsIgnoreMetadata(getCoordinateReferenceSystem(), position.getCoordinateReferenceSystem(), true) : position; for (int i=0; i= lower); final boolean c2 = (value <= upper); if (c1 & c2) { continue; // Point inside the range, check other dimensions. } if (c1 | c2) { if (isNegative(upper - lower)) { /* * "Spanning the anti-meridian" case: if we reach this point, then the * [upper...lower] range (note the 'lower' and 'upper' interchanging) * is actually a space outside the envelope and we have checked that * the ordinate value is outside that space. */ continue; } } return false; } return true; } /** * Returns {@code true} if this envelope completely encloses the specified envelope. * If one or more edges from the specified envelope coincide with an edge from this * envelope, then this method returns {@code true} only if {@code edgesInclusive} * is {@code true}. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance raisons, it will no be verified unless Java assertions are enabled.} * * {@section Spanning the anti-meridian of a Geographic CRS} * For every cases illustrated below, the yellow box is considered completely enclosed * in the blue envelope: * *
* * @param envelope The envelope to test for inclusion. * @param edgesInclusive {@code true} if this envelope edges are inclusive. * @return {@code true} if this envelope completely encloses the specified one. * @throws MismatchedDimensionException if the specified envelope doesn't have * the expected dimension. * @throws AssertionError If assertions are enabled and the envelopes have mismatched CRS. * * @see #intersects(Envelope, boolean) * @see #equals(Envelope, double, boolean) * * @since 3.20 (derived from 2.2) */ public boolean contains(final Envelope envelope, final boolean edgesInclusive) throws MismatchedDimensionException, AssertionError { ensureNonNull("envelope", envelope); final int dimension = getDimension(); AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dimension); assert equalsIgnoreMetadata(getCoordinateReferenceSystem(), envelope.getCoordinateReferenceSystem(), true) : envelope; final DirectPosition lower = envelope.getLowerCorner(); final DirectPosition upper = envelope.getUpperCorner(); for (int i=0; i= min0); maxCondition = (max1 <= max0); } else { minCondition = (min1 > min0); maxCondition = (max1 < max0); } if (minCondition & maxCondition) { /* maxCnd maxCnd * ┌─────────────┐ ────┐ ┌──── ┌─┐ * │ ┌───────┐ │ or ──┐ │ │ ┌── excluding ───┐ │ │ ┌─── * │ └───────┘ │ ──┘ │ │ └── ───┘ │ │ └─── * └─────────────┘ ────┘ └──── └─┘ * minCnd minCnd */ // (max1-min1) is negative if the small rectangle in above pictures spans the anti-meridian. if (!isNegativeUnsafe(max1 - min1) || isNegativeUnsafe(max0 - min0)) { // Not the excluded case, go to next dimension. continue; } // If this envelope does not span the anti-meridian but the given envelope // does, we don't contain the given envelope except in the special case // where the envelope spanning is equals or greater than the axis spanning // (including the case where this envelope expands to infinities). if ((min0 == Double.NEGATIVE_INFINITY && max0 == Double.POSITIVE_INFINITY) || (max0 - min0 >= getSpan(getAxis(getCoordinateReferenceSystem(), i)))) { continue; } } else if (minCondition != maxCondition) { /* maxCnd !maxCnd * ──────────┐ ┌───── ─────┐ ┌───────── * ┌────┐ │ │ or │ │ ┌────┐ * └────┘ │ │ │ │ └────┘ * ──────────┘ └───── ─────┘ └───────── * !minCnd minCnd */ if (isNegative(max0 - min0)) { if (isPositive(max1 - min1)) { continue; } // Special case for the [0…-0] range, if inclusive. if (edgesInclusive && Double.doubleToRawLongBits(min0) == 0L && Double.doubleToRawLongBits(max0) == SIGN_BIT_MASK) { continue; } } } return false; } // The check for ArrayEnvelope.class is for avoiding never-ending callbacks. assert envelope.getClass() == ArrayEnvelope.class || intersects(new ArrayEnvelope(envelope), edgesInclusive) : envelope; return true; } /** * Returns {@code true} if this envelope intersects the specified envelope. * If one or more edges from the specified envelope coincide with an edge from this * envelope, then this method returns {@code true} only if {@code edgesInclusive} * is {@code true}. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance raisons, it will no be verified unless Java assertions are enabled.} * * {@section Spanning the anti-meridian of a Geographic CRS} * This method can handle envelopes spanning the anti-meridian. * * @param envelope The envelope to test for intersection. * @param edgesInclusive {@code true} if this envelope edges are inclusive. * @return {@code true} if this envelope intersects the specified one. * @throws MismatchedDimensionException if the specified envelope doesn't have * the expected dimension. * @throws AssertionError If assertions are enabled and the envelopes have mismatched CRS. * * @see #contains(Envelope, boolean) * @see #equals(Envelope, double, boolean) * * @since 3.20 (derived from 2.2) */ public boolean intersects(final Envelope envelope, final boolean edgesInclusive) throws MismatchedDimensionException, AssertionError { ensureNonNull("envelope", envelope); final int dimension = getDimension(); AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dimension); assert equalsIgnoreMetadata(getCoordinateReferenceSystem(), envelope.getCoordinateReferenceSystem(), true) : envelope; final DirectPosition lower = envelope.getLowerCorner(); final DirectPosition upper = envelope.getUpperCorner(); for (int i=0; i= min0); } else { minCondition = (min1 < max0); maxCondition = (max1 > min0); } if (maxCondition & minCondition) { /* ┌──────────┐ * │ ┌───────┼──┐ * │ └───────┼──┘ * └──────────┘ (this is the most standard case) */ continue; } final boolean sp0 = isNegative(max0 - min0); final boolean sp1 = isNegative(max1 - min1); if (sp0 | sp1) { /* * If both envelopes span the anti-meridian (sp0 & sp1), we have an unconditional * intersection (since both envelopes extend to infinities). Otherwise we have one * of the cases illustrated below. Note that the rectangle could also intersect on * only once side. * ┌──────────┐ ─────┐ ┌───── * ────┼───┐ ┌───┼──── or ┌─┼──────┼─┐ * ────┼───┘ └───┼──── └─┼──────┼─┘ * └──────────┘ ─────┘ └───── */ if ((sp0 & sp1) | (maxCondition | minCondition)) { continue; } } // The check for ArrayEnvelope.class is for avoiding never-ending callbacks. assert envelope.getClass() == ArrayEnvelope.class || hasNaN(envelope) || !contains(new ArrayEnvelope(envelope), edgesInclusive) : envelope; return false; } return true; } /** * Returns {@code true} if at least one ordinate in the given envelope * is {@link Double#NaN}. This is used for assertions only. */ static boolean hasNaN(final Envelope envelope) { return hasNaN(envelope.getLowerCorner()) || hasNaN(envelope.getUpperCorner()); } /** * Returns {@code true} if at least one ordinate in the given position * is {@link Double#NaN}. This is used for assertions only. */ static boolean hasNaN(final DirectPosition position) { for (int i=position.getDimension(); --i>=0;) { if (Double.isNaN(position.getOrdinate(i))) { return true; } } return false; } /** * Returns a {@link Rectangle2D} with the {@linkplain #getMinimum(int) minimum} * and {@linkplain #getMaximum(int) maximum} values of this {@code Envelope}. * This envelope must be two-dimensional before this method is invoked. * * {@section Spanning the anti-meridian of a Geographic CRS} * If this envelope spans the anti-meridian, then the longitude dimension will be * extended to full range of its coordinate system axis (typically [-180 … 180]°). * * @return This envelope as a two-dimensional rectangle. * @throws IllegalStateException if this envelope is not two-dimensional. * * @since 3.20 (derived from 3.00) */ public Rectangle2D toRectangle2D() throws IllegalStateException { final int dimension = getDimension(); if (dimension != 2) { throw new IllegalStateException(Errors.format( Errors.Keys.NOT_TWO_DIMENSIONAL_$1, dimension)); } return XRectangle2D.createFromExtremums( getMinimum(0), getMinimum(1), getMaximum(0), getMaximum(1)); } /** * Formats this envelope in the Well Known Text (WKT) format. The output is like * below, where n is the {@linkplain #getDimension() number of dimensions}: * *
{@code BOX}n{@code D(}{@linkplain #getLowerCorner() lower corner}{@code ,} * {@linkplain #getUpperCorner() upper corner}{@code )}
* * The output of this method can be {@linkplain GeneralEnvelope#GeneralEnvelope(String) parsed} * by the {@link GeneralEnvelope} constructor. */ @Override public String toString() { return toString(this); } /** * Implementation of {@link Envelopes#toWKT(Envelope)}. Formats a {@code BOX} element from an * envelope in Well Known Text (WKT) format. * * @param envelope The envelope to format. * @return The envelope as a {@code BOX2D} or {@code BOX3D} in WKT format. * * @see GeneralEnvelope#GeneralEnvelope(String) * @see org.geotoolkit.measure.CoordinateFormat * @see org.geotoolkit.io.wkt * * @since 3.09 */ static String toString(final Envelope envelope) { final int dimension = envelope.getDimension(); final DirectPosition lower = envelope.getLowerCorner(); final DirectPosition upper = envelope.getUpperCorner(); final StringBuilder buffer = new StringBuilder("BOX").append(dimension).append("D("); for (int i=0; iWell Known Text (WKT) format. This is provided as an * alternative to the {@code BOX} element formatted by {@link #toString(Envelope)}, because * the {@code BOX} element is usually not considered a geometry while {@code POLYGON} is. *

* The output of this method can be {@linkplain GeneralEnvelope#GeneralEnvelope(String) parsed} * by the {@link GeneralEnvelope} constructor. * * @param envelope The envelope to format. * @return The envelope as a {@code POLYGON} in WKT format. * * @see org.geotoolkit.io.wkt * * @since 3.09 * * @deprecated Moved to {@link Envelopes#toPolygonWKT(Envelope)}. */ @Deprecated public static String toPolygonString(final Envelope envelope) { return Envelopes.toPolygonWKT(envelope); } /** * Returns a hash value for this envelope. */ @Override public int hashCode() { final int dimension = getDimension(); int code = 1; boolean p = true; do { for (int i=0; i>> 32)); } } while ((p = !p) == false); final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); if (crs != null) { code += crs.hashCode(); } return code; } /** * Returns {@code true} if the specified object is an envelope of the same class * with equals coordinates and {@linkplain #getCoordinateReferenceSystem CRS}. * * {@note This implementation requires that the provided object argument * is of the same class than this envelope. We do not relax this rule since not every * implementations in the Geotk code base follow the same contract.} * * @param object The object to compare with this envelope. * @return {@code true} if the given object is equal to this envelope. */ @Override public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { final AbstractEnvelope that = (AbstractEnvelope) object; final int dimension = getDimension(); if (dimension == that.getDimension()) { for (int i=0; i *

    *
  • If {@code epsIsRelative} is set to {@code true}, the actual tolerance value for a * given dimension i is {@code eps}×{@code span} where {@code span} * is the maximum of {@linkplain #getSpan this envelope span} and the specified envelope * length along dimension i.
  • *
  • If {@code epsIsRelative} is set to {@code false}, the actual tolerance value for a * given dimension i is {@code eps}.
  • *
* * {@note Relative tolerance value (as opposed to absolute tolerance value) help to workaround * the fact that tolerance value are CRS dependent. For example the tolerance value need to be * smaller for geographic CRS than for UTM projections, because the former typically has a * range of -180 to 180° while the later can have a range of thousands of meters.} * * {@section Coordinate Reference System} * To be considered equal, the two envelopes must have the same {@linkplain #getDimension() dimension} * and their CRS must be {@linkplain org.geotoolkit.referencing.CRS#equalsIgnoreMetadata equals, * ignoring metadata}. If at least one envelope has a null CRS, then the CRS are ignored and the * ordinate values are compared as if the CRS were equal. * * @param envelope The envelope to compare with. * @param eps The tolerance value to use for numerical comparisons. * @param epsIsRelative {@code true} if the tolerance value should be relative to * axis length, or {@code false} if it is an absolute value. * @return {@code true} if the given object is equal to this envelope up to the given * tolerance value. * * @see GeneralEnvelope#contains(Envelope, boolean) * @see GeneralEnvelope#intersects(Envelope, boolean) * * @since 2.4 */ public boolean equals(final Envelope envelope, final double eps, final boolean epsIsRelative) { ensureNonNull("envelope", envelope); final int dimension = getDimension(); if (envelope.getDimension() != dimension) { return false; } if (!equalsIgnoreMetadata(getCoordinateReferenceSystem(), envelope.getCoordinateReferenceSystem(), false)) { return false; } final DirectPosition lower = envelope.getLowerCorner(); final DirectPosition upper = envelope.getUpperCorner(); for (int i=0; i 0 && span < Double.POSITIVE_INFINITY) { epsilon *= span; } } if (!epsilonEqual(getLower(i), lower.getOrdinate(i), epsilon) || !epsilonEqual(getUpper(i), upper.getOrdinate(i), epsilon)) { return false; } } return true; } /** * Base class for direct position from an envelope. * This class delegates its work to the enclosing envelope. */ private abstract class Corner extends AbstractDirectPosition { /** The coordinate reference system in which the coordinate is given. */ @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { return AbstractEnvelope.this.getCoordinateReferenceSystem(); } /** The length of coordinate sequence (the number of entries). */ @Override public int getDimension() { return AbstractEnvelope.this.getDimension(); } /** Sets the ordinate value along the specified dimension. */ @Override public void setOrdinate(int dimension, double value) { throw new UnsupportedOperationException(); } } /** * The corner returned by {@link AbstractEnvelope#getLowerCorner}. */ private final class LowerCorner extends Corner { @Override public double getOrdinate(final int dimension) throws IndexOutOfBoundsException { return getLower(dimension); } } /** * The corner returned by {@link AbstractEnvelope#getUpperCorner}. */ private final class UpperCorner extends Corner { @Override public double getOrdinate(final int dimension) throws IndexOutOfBoundsException { return getUpper(dimension); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy