/* Copyright (c) 2011 Danish Maritime Authority. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.maritimecloud.util.geometry; import java.io.IOException; import java.util.Random; import net.maritimecloud.message.MessageReader; import net.maritimecloud.message.MessageSerializer; import net.maritimecloud.message.MessageWriter; public final class Rectangle extends Area { /** A bounding box encompassing all coordinates. */ public static final Rectangle ALL = new Rectangle(90, -180, -90, 180); public static final MessageSerializer<Rectangle> SERIALIZER = new MessageSerializer<Rectangle>() { /** {@inheritDoc} */ @Override public Rectangle read(MessageReader reader) throws IOException { double topLeftLatitude = reader.readDouble(1, "topLeftLatitude"); double topLeftLongitude = reader.readDouble(2, "topLeftLongitude"); double bottomRightLatitude = reader.readDouble(3, "bottomRightLatitude"); double bottomRightLongitude = reader.readDouble(4, "bottomRightLongitude"); return Rectangle.create(topLeftLatitude, topLeftLongitude, bottomRightLatitude, bottomRightLongitude); } public void write(Rectangle message, MessageWriter w) throws IOException { w.writeDouble(1, "topLeftLatitude", message.getTopLeftLatitude()); w.writeDouble(2, "topLeftLongitude", message.getTopLeftLongitude()); w.writeDouble(3, "bottomRightLatitude", message.getBottomRightLatitude()); w.writeDouble(4, "bottomRightLongitude", message.getBottomRightLongitude()); } }; /** serialVersionUID */ private static final long serialVersionUID = 1L; final double bottomRightLatitude; final double bottomRightLongitude; final double topLeftLatitude; final double topLeftLongitude; Rectangle(double topLeftLatitude, double topLeftLongitude, double bottomRightLatitude, double bottomRightLongitude) { this.topLeftLatitude = Position.verifyLatitude(topLeftLatitude); this.topLeftLongitude = Position.verifyLongitude(topLeftLongitude); this.bottomRightLatitude = Position.verifyLatitude(bottomRightLatitude); this.bottomRightLongitude = Position.verifyLongitude(bottomRightLongitude); } public boolean contains(double latitude, double longitude) { return containsLatitude(latitude) && containsLongitude(longitude); } public boolean contains(Position position) { return contains(position.getLatitude(), position.getLongitude()); } private boolean containsLatitude(double latitude) { if (topLeftLatitude >= bottomRightLatitude) { return bottomRightLatitude <= latitude && latitude <= topLeftLatitude; } else { // crosses date line throw new UnsupportedOperationException("Not implemented yet"); } } private boolean containsLongitude(double longitude) { if (topLeftLongitude <= bottomRightLongitude) { return topLeftLongitude <= longitude && longitude <= bottomRightLongitude; } else { // crosses date line throw new UnsupportedOperationException("Not implemented yet"); } } @Override public boolean equals(Object obj) { return this == obj || obj instanceof Rectangle && equals((Rectangle) obj); } private boolean equals(Rectangle that) { return topLeftLatitude == that.topLeftLatitude && topLeftLongitude == that.topLeftLongitude && bottomRightLatitude == that.bottomRightLatitude && bottomRightLongitude == that.bottomRightLongitude; } public Position getBottomRight() { return Position.create(getBottomRightLatitude(), getBottomRightLongitude()); } public Position getBottomLeft() { return Position.create(getBottomRightLatitude(), getTopLeftLongitude()); } /** * @return the bottomRightLatitude */ public double getBottomRightLatitude() { return bottomRightLatitude; } /** * @return the bottomRightLongitude */ public double getBottomRightLongitude() { return bottomRightLongitude; } /** {@inheritDoc} */ @Override public Rectangle getBoundingBox() { return this; } // // public Position getCenterPoint() { // return Position.create((bottomLatitude + topLatitude) / 2, (leftLongitude + rightLongitude) / 2); // } /** {@inheritDoc} */ @Override public Position getRandomPosition(Random r) { double latitude; if (topLeftLatitude >= bottomRightLatitude) { latitude = Area.nextDouble(r, bottomRightLatitude, topLeftLatitude); } else { // crosses date line throw new UnsupportedOperationException("Not implemented yet"); } double longitude; if (topLeftLongitude <= bottomRightLongitude) { longitude = Area.nextDouble(r, topLeftLongitude, bottomRightLongitude); } else { // crosses date line throw new UnsupportedOperationException("Not implemented yet"); } return Position.create(latitude, longitude); } public Position getTopLeft() { return Position.create(getTopLeftLatitude(), getTopLeftLongitude()); } public Position getTopRight() { return Position.create(getTopLeftLatitude(), getBottomRightLongitude()); } /** * @return the topLeftLatitude */ public double getTopLeftLatitude() { return topLeftLatitude; } /** * @return the topLeftLongitude */ public double getTopLeftLongitude() { return topLeftLongitude; } @Override public int hashCode() { int result = 17; result = 37 * result + Double.hashCode(topLeftLatitude); result = 37 * result + Double.hashCode(topLeftLongitude); result = 37 * result + Double.hashCode(bottomRightLatitude); result = 37 * result + Double.hashCode(bottomRightLongitude); return result; } /** {@inheritDoc} */ public Rectangle immutable() { return this; } // /** // * Returns a rectangle bounding box that includes the specified bounding box. // * // * @param other // * the bounding box to include // * @return a bounding box // */ // public Rectangle union(Rectangle other) { // // // double minLon = this.leftLongitude; // // double maxLon = this.rightLongitude; // // double minLat = this.bottomLatitude; // // double maxLat = this.topLatitude; // // boolean changed = false; // // if (other.leftLongitude < minLon) { // // minLon = other.leftLongitude; // // changed = true; // // } // // if (other.rightLongitude > maxLon) { // // maxLon = other.rightLongitude; // // changed = true; // // } // // if (other.bottomLatitude < minLat) { // // minLat = other.bottomLatitude; // // changed = true; // // } // // if (other.topLatitude > maxLat) { // // maxLat = other.topLatitude; // // changed = true; // // } // // return changed ? new Rectangle(minLat, maxLat, minLon, maxLon) : this; // } /** {@inheritDoc} */ @Override public boolean intersects(Area other) { if (other instanceof Circle) { return intersects((Circle) other); } else if (other instanceof Rectangle) { return intersects((Rectangle) other); } else { throw new UnsupportedOperationException("Only circles and Rectangle supported"); } } public boolean intersects(Circle other) { // Either the circle's centre lies inside the rectangle, or // One of the edges of the rectangle intersects the circle. // See more here http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection if (contains(other.getCenter())) { return true; } Position topLeft = getTopLeft(); Position topRight = getTopRight(); Position bottomLeft = getBottomLeft(); Position bottomRight = getBottomRight(); return other.intersects(topLeft, topRight) || other.intersects(topRight, bottomLeft) || other.intersects(topLeft, bottomLeft) || other.intersects(bottomLeft, bottomRight); } public boolean intersects(Rectangle other) { // If the bounding box of this and other is shorter than the sum of the heights AND skinnier than the sum of the // widths, they must intersect // Rectangle common = union(other); // return common.getLatitudeSize() < getLatitudeSize() + other.getLatitudeSize() // && common.getLongitudeSize() < getLongitudeSize() + other.getLongitudeSize(); throw new UnsupportedOperationException(); } @Override public String toString() { return "(" + topLeftLatitude + "," + topLeftLongitude + "), (" + bottomRightLatitude + "," + bottomRightLongitude + ")"; } boolean wrapsDateLine() { return topLeftLongitude > bottomRightLongitude; } static Rectangle create(double y1, double y2, double x1, double x2) { return new Rectangle(Math.min(y1, y2), Math.max(y1, y2), Math.min(x1, x2), Math.max(x1, x2)); } public static Rectangle create(Position topLeft, Position bottomRight) { return new Rectangle(topLeft.getLatitude(), topLeft.getLongitude(), bottomRight.getLatitude(), bottomRight.getLongitude()); } // @Override // public boolean contains(Element element) { // if (element instanceof Position) { // return contains((Position) element); // } else { // return super.contains(element); // } // } /** * Creates a message of this type from a JSON throwing a runtime exception if the format of the message does not * match. * * @param string * the JSON string to parse * @return the parsed rectangle */ public static Rectangle fromJSON(CharSequence string) { return MessageSerializer.readFromJSON(SERIALIZER, string); } } // // public double getArea(/* DistanceUnit unit */) { // // CoordinateSystem.CARTESIAN.distanceBetween(minLatitude, minLongitude, maxLatitude, maxLongitude) // final Position a = new Position(topLatitude, leftLongitude); // final Position b = new Position(topLatitude, rightLongitude); // final Position c = new Position(bottomLatitude, leftLongitude); // final double ab = a.rhumbLineDistanceTo(b); // meters // final double ac = a.rhumbLineDistanceTo(c); // meters // return ab * ac; // }