001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.geometry.jts;
017:
018: import java.awt.geom.Rectangle2D;
019: import com.vividsolutions.jts.geom.Envelope;
020: import org.opengis.geometry.BoundingBox;
021: import org.opengis.geometry.DirectPosition;
022: import org.opengis.geometry.MismatchedDimensionException;
023: import org.opengis.geometry.MismatchedReferenceSystemException;
024: import org.opengis.referencing.FactoryException;
025: import org.opengis.referencing.crs.CoordinateReferenceSystem;
026: import org.opengis.referencing.operation.CoordinateOperation;
027: import org.opengis.referencing.operation.MathTransform;
028: import org.opengis.referencing.operation.TransformException;
029: import org.geotools.geometry.DirectPosition2D;
030: import org.geotools.geometry.Envelope2D;
031: import org.geotools.geometry.GeneralDirectPosition;
032: import org.geotools.geometry.GeneralEnvelope;
033: import org.geotools.referencing.CRS;
034: import org.geotools.resources.Utilities;
035: import org.geotools.resources.i18n.ErrorKeys;
036: import org.geotools.resources.i18n.Errors;
037:
038: /**
039: * A JTS envelope associated with a
040: * {@linkplain CoordinateReferenceSystem coordinate reference system}. In
041: * addition, this JTS envelope also implements the GeoAPI
042: * {@linkplain org.opengis.geometry.coordinate.Envelope envelope} interface
043: * for interoperability with GeoAPI.
044: *
045: * @since 2.2
046: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/api/src/main/java/org/geotools/geometry/jts/ReferencedEnvelope.java $
047: * @version $Id: ReferencedEnvelope.java 26186 2007-07-10 02:18:59Z jdeolive $
048: * @author Jody Garnett
049: * @author Martin Desruisseaux
050: * @author Simone Giannecchini
051: *
052: * @see org.geotools.geometry.Envelope2D
053: * @see org.geotools.geometry.GeneralEnvelope
054: * @see org.opengis.metadata.extent.GeographicBoundingBox
055: */
056: public class ReferencedEnvelope extends Envelope implements
057: org.opengis.geometry.Envelope, BoundingBox {
058: /**
059: * Serial number for compatibility with different versions.
060: */
061: private static final long serialVersionUID = -3188702602373537163L;
062:
063: /**
064: * The coordinate reference system, or {@code null}.
065: */
066: private final CoordinateReferenceSystem crs;
067:
068: /**
069: * Creates a null envelope with the specified coordinate reference system.
070: *
071: * @param crs The coordinate reference system.
072: * @throws MismatchedDimensionException if the CRS dimension is not valid.
073: */
074: public ReferencedEnvelope(final CoordinateReferenceSystem crs)
075: throws MismatchedDimensionException {
076: this .crs = crs;
077: checkCoordinateReferenceSystemDimension();
078: }
079:
080: /**
081: * Creates an envelope for a region defined by maximum and minimum values.
082: *
083: * @param x1 The first x-value.
084: * @param x2 The second x-value.
085: * @param y1 The first y-value.
086: * @param y2 The second y-value.
087: * @param crs The coordinate reference system.
088: *
089: * @throws MismatchedDimensionException if the CRS dimension is not valid.
090: */
091: public ReferencedEnvelope(final double x1, final double x2,
092: final double y1, final double y2,
093: final CoordinateReferenceSystem crs)
094: throws MismatchedDimensionException {
095: super (x1, x2, y1, y2);
096: this .crs = crs;
097: checkCoordinateReferenceSystemDimension();
098: }
099:
100: /**
101: * Creates an envelope for a Java2D rectangle.
102: *
103: * @param rectangle The rectangle.
104: * @param crs The coordinate reference system.
105: *
106: * @throws MismatchedDimensionException if the CRS dimension is not valid.
107: *
108: * @since 2.4
109: */
110: public ReferencedEnvelope(final Rectangle2D rectangle,
111: final CoordinateReferenceSystem crs)
112: throws MismatchedDimensionException {
113: this (rectangle.getMinX(), rectangle.getMaxX(), rectangle
114: .getMinY(), rectangle.getMaxY(), crs);
115: }
116:
117: /**
118: * Creates a new envelope from an existing envelope.
119: *
120: * @param envelope The envelope to initialize from
121: * @throws MismatchedDimensionException if the CRS dimension is not valid.
122: *
123: * @since 2.3
124: */
125: public ReferencedEnvelope(final ReferencedEnvelope envelope)
126: throws MismatchedDimensionException {
127: super (envelope);
128: crs = envelope.getCoordinateReferenceSystem();
129: checkCoordinateReferenceSystemDimension();
130: }
131:
132: /**
133: * Creates a new envelope from an existing bounding box.
134: *
135: * @param bbox The bounding box to initialize from.
136: * @throws MismatchedDimensionException if the CRS dimension is not valid.
137: *
138: * @since 2.4
139: */
140: public ReferencedEnvelope(final BoundingBox bbox)
141: throws MismatchedDimensionException {
142: this (bbox.getMinX(), bbox.getMaxX(), bbox.getMinY(), bbox
143: .getMaxY(), bbox.getCoordinateReferenceSystem());
144: }
145:
146: /**
147: * Creates a new envelope from an existing OGC envelope.
148: *
149: * @param envelope The envelope to initialize from.
150: * @throws MismatchedDimensionException if the CRS dimension is not valid.
151: *
152: * @since 2.4
153: */
154: public ReferencedEnvelope(
155: final org.opengis.geometry.Envelope envelope)
156: throws MismatchedDimensionException {
157: super (envelope.getMinimum(0), envelope.getMaximum(0), envelope
158: .getMinimum(1), envelope.getMaximum(1));
159: this .crs = envelope.getCoordinateReferenceSystem();
160: checkCoordinateReferenceSystemDimension();
161: }
162:
163: /**
164: * Creates a new envelope from an existing JTS envelope.
165: *
166: * @param envelope The envelope to initialize from.
167: * @param crs The coordinate reference system.
168: * @throws MismatchedDimensionExceptionif the CRS dimension is not valid.
169: */
170: public ReferencedEnvelope(final Envelope envelope,
171: final CoordinateReferenceSystem crs)
172: throws MismatchedDimensionException {
173: super (envelope);
174: this .crs = crs;
175: checkCoordinateReferenceSystemDimension();
176: }
177:
178: /**
179: * Creates a new envelope from an existing OGC envelope.
180: *
181: * @param envelope The envelope to initialize from.
182: * @param crs The coordinate reference system.
183: * @throws MismatchedDimensionException if the CRS dimension is not valid.
184: *
185: * @since 2.3
186: *
187: * @deprecated The {@code crs} argument duplicate the information already provided
188: * in the OGC envelope.
189: */
190:
191: /**
192: * Returns the specified bounding box as a JTS envelope.
193: */
194: private static Envelope getJTSEnvelope(final BoundingBox bbox) {
195: if (bbox instanceof Envelope) {
196: return (Envelope) bbox;
197: }
198:
199: return new ReferencedEnvelope(bbox);
200: }
201:
202: /**
203: * Convenience method for checking coordinate reference system validity.
204: *
205: * @throws IllegalArgumentException if the CRS dimension is not valid.
206: */
207: private void checkCoordinateReferenceSystemDimension()
208: throws MismatchedDimensionException {
209: if (crs != null) {
210: final int expected = getDimension();
211: final int dimension = crs.getCoordinateSystem()
212: .getDimension();
213:
214: if (dimension != expected) {
215: throw new MismatchedDimensionException(Errors.format(
216: ErrorKeys.MISMATCHED_DIMENSION_$3, crs
217: .getName().getCode(), new Integer(
218: dimension), new Integer(expected)));
219: }
220: }
221: }
222:
223: /**
224: * Make sure that the specified bounding box uses the same CRS than this one.
225: *
226: * @param bbox The other bounding box to test for compatibility.
227: * @throws MismatchedReferenceSystemException if the CRS are incompatibles.
228: */
229: private void ensureCompatibleReferenceSystem(final BoundingBox bbox)
230: throws MismatchedReferenceSystemException {
231: if (crs != null) {
232: final CoordinateReferenceSystem other = bbox
233: .getCoordinateReferenceSystem();
234:
235: if (other != null) {
236: if (!CRS.equalsIgnoreMetadata(crs, other)) {
237: throw new MismatchedReferenceSystemException(
238: Errors
239: .format(ErrorKeys.MISMATCHED_COORDINATE_REFERENCE_SYSTEM));
240: }
241: }
242: }
243: }
244:
245: /**
246: * Returns the coordinate reference system associated with this envelope.
247: */
248: public CoordinateReferenceSystem getCoordinateReferenceSystem() {
249: return crs;
250: }
251:
252: /**
253: * Returns the number of dimensions.
254: */
255: public int getDimension() {
256: return 2;
257: }
258:
259: /**
260: * Returns the minimal ordinate along the specified dimension.
261: */
262: public double getMinimum(final int dimension) {
263: switch (dimension) {
264: case 0:
265: return getMinX();
266:
267: case 1:
268: return getMinY();
269:
270: default:
271: throw new IndexOutOfBoundsException(String
272: .valueOf(dimension));
273: }
274: }
275:
276: /**
277: * Returns the maximal ordinate along the specified dimension.
278: */
279: public double getMaximum(final int dimension) {
280: switch (dimension) {
281: case 0:
282: return getMaxX();
283:
284: case 1:
285: return getMaxY();
286:
287: default:
288: throw new IndexOutOfBoundsException(String
289: .valueOf(dimension));
290: }
291: }
292:
293: /**
294: * Returns the center ordinate along the specified dimension.
295: */
296: public double getCenter(final int dimension) {
297: switch (dimension) {
298: case 0:
299: return 0.5 * (getMinX() + getMaxX());
300:
301: case 1:
302: return 0.5 * (getMinY() + getMaxY());
303:
304: default:
305: throw new IndexOutOfBoundsException(String
306: .valueOf(dimension));
307: }
308: }
309:
310: /**
311: * Returns the envelope length along the specified dimension. This length is
312: * equals to the maximum ordinate minus the minimal ordinate.
313: */
314: public double getLength(final int dimension) {
315: switch (dimension) {
316: case 0:
317: return getWidth();
318:
319: case 1:
320: return getHeight();
321:
322: default:
323: throw new IndexOutOfBoundsException(String
324: .valueOf(dimension));
325: }
326: }
327:
328: /**
329: * A coordinate position consisting of all the minimal ordinates for each
330: * dimension for all points within the {@code Envelope}.
331: */
332: public DirectPosition getLowerCorner() {
333: return new DirectPosition2D(crs, getMinX(), getMinY());
334: }
335:
336: /**
337: * A coordinate position consisting of all the maximal ordinates for each
338: * dimension for all points within the {@code Envelope}.
339: */
340: public DirectPosition getUpperCorner() {
341: return new DirectPosition2D(crs, getMaxX(), getMaxY());
342: }
343:
344: /**
345: * Returns {@code true} if lengths along all dimension are zero.
346: *
347: * @since 2.4
348: */
349: public boolean isEmpty() {
350: return super .isNull();
351: }
352:
353: /**
354: * Returns {@code true} if the provided location is contained by this bounding box.
355: *
356: * @since 2.4
357: */
358: public boolean contains(DirectPosition pos) {
359: return super .contains(pos.getOrdinate(0), pos.getOrdinate(1));
360: }
361:
362: /**
363: * Returns {@code true} if the provided bounds are contained by this bounding box.
364: *
365: * @since 2.4
366: */
367: public boolean contains(final BoundingBox bbox) {
368: ensureCompatibleReferenceSystem(bbox);
369:
370: return super .contains(getJTSEnvelope(bbox));
371: }
372:
373: /**
374: * Check if this bounding box intersects the provided bounds.
375: *
376: * @since 2.4
377: */
378: public boolean intersects(final BoundingBox bbox) {
379: ensureCompatibleReferenceSystem(bbox);
380:
381: return super .intersects(getJTSEnvelope(bbox));
382: }
383:
384: /**
385: * Include the provided bounding box, expanding as necessary.
386: *
387: * @since 2.4
388: */
389: public void include(final BoundingBox bbox) {
390: ensureCompatibleReferenceSystem(bbox);
391: super .expandToInclude(getJTSEnvelope(bbox));
392: }
393:
394: /**
395: * Include the provided coordinates, expanding as necessary.
396: *
397: * @since 2.4
398: */
399: public void include(double x, double y) {
400: super .expandToInclude(x, y);
401: }
402:
403: /**
404: * Initialize the bounding box with another bounding box.
405: *
406: * @since 2.4
407: */
408: public void setBounds(final BoundingBox bbox) {
409: ensureCompatibleReferenceSystem(bbox);
410: super .init(getJTSEnvelope(bbox));
411: }
412:
413: /**
414: * Returns a new bounding box which contains the transformed shape of this bounding box.
415: * This is a convenience method that delegate its work to the {@link #transform transform}
416: * method.
417: *
418: * @since 2.4
419: */
420: public BoundingBox toBounds(
421: final CoordinateReferenceSystem targetCRS)
422: throws TransformException {
423: try {
424: return transform(targetCRS, true);
425: } catch (FactoryException e) {
426: throw new TransformException(e.getLocalizedMessage(), e);
427: }
428: }
429:
430: /**
431: * Transforms the referenced envelope to the specified coordinate reference system.
432: * <p>
433: * This method can handle the case where the envelope contains the North or South pole,
434: * or when it cross the ±180� longitude.
435: *
436: * @param targetCRS The target coordinate reference system.
437: * @param lenient {@code true} if datum shift should be applied even if there is
438: * insuffisient information. Otherwise (if {@code false}), an
439: * exception is thrown in such case.
440: * @return The transformed envelope.
441: * @throws FactoryException if the math transform can't be determined.
442: * @throws TransformException if at least one coordinate can't be transformed.
443: *
444: * @see CRS#transform(CoordinateOperation, org.opengis.geometry.Envelope)
445: */
446: public ReferencedEnvelope transform(
447: CoordinateReferenceSystem targetCRS, boolean lenient)
448: throws TransformException, FactoryException {
449: return transform(targetCRS, lenient, 5);
450: }
451:
452: /**
453: * Transforms the referenced envelope to the specified coordinate reference system
454: * using the specified amount of points.
455: * <p>
456: * This method can handle the case where the envelope contains the North or South pole,
457: * or when it cross the ±180� longitude.
458: *
459: * @param targetCRS The target coordinate reference system.
460: * @param lenient {@code true} if datum shift should be applied even if there is
461: * insuffisient information. Otherwise (if {@code false}), an
462: * exception is thrown in such case.
463: * @param numPointsForTransformation The number of points to use for sampling the envelope.
464: * @return The transformed envelope.
465: * @throws FactoryException if the math transform can't be determined.
466: * @throws TransformException if at least one coordinate can't be transformed.
467: *
468: * @see CRS#transform(CoordinateOperation, org.opengis.geometry.Envelope)
469: *
470: * @since 2.3
471: */
472: public ReferencedEnvelope transform(
473: final CoordinateReferenceSystem targetCRS,
474: final boolean lenient, final int numPointsForTransformation)
475: throws TransformException, FactoryException {
476: /*
477: * Gets a first estimation using an algorithm capable to take singularity in account
478: * (North pole, South pole, 180� longitude). We will expand this initial box later.
479: */
480: final CoordinateOperation operation = CRS
481: .getCoordinateOperationFactory(lenient)
482: .createOperation(crs, targetCRS);
483: final GeneralEnvelope transformed = CRS.transform(operation,
484: this );
485: transformed.setCoordinateReferenceSystem(targetCRS);
486:
487: /*
488: * Now expands the box using the usual utility methods.
489: */
490: final ReferencedEnvelope target = new ReferencedEnvelope(
491: transformed);
492: final MathTransform transform = operation.getMathTransform();
493: JTS.transform(this , target, transform,
494: numPointsForTransformation);
495:
496: return target;
497: }
498:
499: /**
500: * Returns a hash value for this envelope. This value need not remain
501: * consistent between different implementations of the same class.
502: */
503: public int hashCode() {
504: int code = super .hashCode() ^ (int) serialVersionUID;
505:
506: if (crs != null) {
507: code ^= crs.hashCode();
508: }
509:
510: return code;
511: }
512:
513: /**
514: * Compares the specified object with this envelope for equality.
515: */
516: public boolean equals(final Object object) {
517: if (super .equals(object)) {
518: final CoordinateReferenceSystem otherCRS = (object instanceof ReferencedEnvelope) ? ((ReferencedEnvelope) object).crs
519: : null;
520:
521: return Utilities.equals(crs, otherCRS);
522: }
523:
524: return false;
525: }
526:
527: /**
528: * Returns a string representation of this envelope. The default implementation
529: * is okay for occasional formatting (for example for debugging purpose).
530: */
531: public String toString() {
532: final StringBuffer buffer = new StringBuffer(Utilities
533: .getShortClassName(this )).append('[');
534: final int dimension = getDimension();
535:
536: for (int i = 0; i < dimension; i++) {
537: if (i != 0) {
538: buffer.append(", ");
539: }
540:
541: buffer.append(getMinimum(i)).append(" : ").append(
542: getMaximum(i));
543: }
544:
545: return buffer.append(']').toString();
546: }
547:
548: /**
549: * Utility method to ensure that an Envelope if a ReferencedEnvelope.
550: * <p>
551: * This method first checks if <tt>e</tt> is an instanceof {@link ReferencedEnvelope},
552: * if it is, itself is returned. If not <code>new ReferencedEnvelpe(e,null)</code>
553: * is returned.
554: * </p>
555: * @param e The envelope.
556: * @return
557: */
558: public static ReferencedEnvelope reference(Envelope e) {
559: if (e instanceof ReferencedEnvelope) {
560: return (ReferencedEnvelope) e;
561: }
562:
563: return new ReferencedEnvelope(e, null);
564: }
565: }
|