/* 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 static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import net.maritimecloud.message.MessageReader;
import net.maritimecloud.message.MessageSerializer;
import net.maritimecloud.message.MessageWriter;
/**
* A circle
*
*/
public class Circle extends Area {
public static final MessageSerializer<Circle> SERIALIZER = new MessageSerializer<Circle>() {
private static final String NAME_CENTER_LATITIUDE = "center-latitude";
private static final String NAME_CENTER_LONGITUDE = "center-longitude";
private static final String NAME_RADIUS = "radius";
/** {@inheritDoc} */
@Override
public Circle read(MessageReader reader) throws IOException {
double lat = reader.readDouble(1, NAME_CENTER_LATITIUDE);
double lon = reader.readDouble(2, NAME_CENTER_LONGITUDE);
double radius = reader.readDouble(3, NAME_RADIUS);
return new Circle(Position.create(lat, lon), radius);
}
/** {@inheritDoc} */
@Override
public void write(Circle message, MessageWriter w) throws IOException {
w.writeDouble(1, NAME_CENTER_LATITIUDE, message.center.latitude);
w.writeDouble(2, NAME_CENTER_LONGITUDE, message.center.longitude);
w.writeDouble(3, NAME_RADIUS, message.radius);
}
};
/** serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The center of the circle. */
final Position center;
/** The radius of the circle. */
final double radius;
Circle(Position center, double radius) {
this.center = requireNonNull(center, "center is null");
if (radius <= 0) {
throw new IllegalArgumentException("Radius must be positive, was " + radius);
}
this.radius = radius;
}
public boolean contains(Circle c) {
return center.rhumbLineDistanceTo(c.center) <= radius + c.radius;
}
/** {@inheritDoc} */
@Override
public boolean contains(Position position) {
return center.rhumbLineDistanceTo(position) <= radius;
}
public boolean equals(Circle other) {
return other == this || other != null && center.equals(other.center) && radius == other.radius;
}
/**
* Equals method
*/
@Override
public boolean equals(Object other) {
return other instanceof Circle && equals((Circle) other);
}
public double geodesicDistanceTo(Position other) {
return Math.max(0, center.geodesicDistanceTo(other) - radius);
}
/** {@inheritDoc} */
@Override
public Rectangle getBoundingBox() {
double top = CoordinateSystem.CARTESIAN.pointOnBearing(center, radius, 0).latitude;
double right = CoordinateSystem.CARTESIAN.pointOnBearing(center, radius, 90).longitude;
double bottom = CoordinateSystem.CARTESIAN.pointOnBearing(center, radius, 180).latitude;
double left = CoordinateSystem.CARTESIAN.pointOnBearing(center, radius, 270).longitude;
Position topLeft = Position.create(top, left);
Position buttomRight = Position.create(bottom, right);
return Rectangle.create(topLeft, buttomRight);
}
/**
* Return the center of the circle.
*
* @return the center of the circle
*/
public Position getCenter() {
return center;
}
/**
* Returns the radius of the circle.
*
* @return the radius of the circle
*/
public double getRadius() {
return radius;
}
/** {@inheritDoc} */
@Override
public Position getRandomPosition(Random r) {
Rectangle bb = getBoundingBox();
for (int i = 0; i < 10000; i++) {
Position p = bb.getRandomPosition(r);
if (contains(p)) {
return p;
}
}
throw new RuntimeException("Inifinite loop");
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return center.hashCode() ^ new Double(radius).hashCode();
}
/** {@inheritDoc} */
public Circle immutable() {
return 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 BoundingBoxes supported");
}
}
public boolean intersects(Circle other) {
double centerDistance = CoordinateSystem.CARTESIAN.distanceBetween(center, other.center);
return radius + other.radius >= centerDistance;
}
public boolean intersects(Line line) {
return intersects(line.getStart(), line.getEnd());
}
boolean intersects(Position p1, Position p2) {
double baX = p2.getLongitude() - p1.getLongitude();
double baY = p2.getLatitude() - p1.getLatitude();
double caX = center.getLongitude() - p2.getLongitude();
double caY = center.getLatitude() - p2.getLatitude();
double a = baX * baX + baY * baY;
double bBy2 = baX * caX + baY * caY;
double c = caX * caX + caY * caY - radius * radius;
double pBy2 = bBy2 / a;
double q = c / a;
double disc = pBy2 * pBy2 - q;
return disc >= 0;
}
public boolean intersects(Rectangle other) {
return other.intersects(this);
}
public double rhumbLineDistanceTo(Position position) {
return Math.max(0, center.rhumbLineDistanceTo(position) - radius);
}
public String toString() {
return "Circle: center = " + center + ", radius = " + radius;
}
/**
* Returns a new circle with the same radius as this circle but with the new position as the center
*
* @param center
* the new center of the circle
* @return a new circle
*/
public Circle withCenter(Position center) {
return new Circle(center, radius);
}
/**
* Returns a new circle with the same center as this circle but with the new radius.
*
* @param radius
* the new radius of the circle
* @return a new circle
*/
public Circle withRadius(double radius) {
return new Circle(center, radius);
}
public static Circle create(double latitude, double longitude, double radius) {
return new Circle(Position.create(latitude, longitude), radius);
}
/**
* Creates a new circle with the specified center and radius.
*
* @param center
* the center of the circle
* @param radius
* the radius in meters of the circle
* @return the new circle
* @throws NullPointerException
* if the specified center is null
* @throws IllegalArgumentException
* if the specified is not a positive number
*/
public static Circle create(Position center, double radius) {
return new Circle(center, radius);
}
/**
* 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 area
*/
public static Circle fromJSON(CharSequence string) {
return MessageSerializer.readFromJSON(SERIALIZER, string);
}
/**
* Returns a random valid circle.
*
* @return the random circle
*/
public static Circle random() {
return random(ThreadLocalRandom.current());
}
/**
* Returns a random valid circle.
*
* @param rnd
* the source of randomness
* @return the random circle
*/
public static Circle random(Random rnd) {
return new Circle(Position.random(), (1 - rnd.nextDouble()) * 10000);
}
}