/* 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.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import net.maritimecloud.message.MessageReader;
import net.maritimecloud.message.MessageSerializer;
import net.maritimecloud.message.MessageWriter;
/**
* A polygon consisting of multiple points.
*
* @author Kasper Nielsen
*/
public class Polygon extends Area implements Iterable<Position> {
/** The serializer of the polygon. */
public static final MessageSerializer<Polygon> SERIALIZER = new MessageSerializer<Polygon>() {
/** {@inheritDoc} */
@Override
public Polygon read(MessageReader reader) throws IOException {
List<Position> positions = reader.readList(1, "points", Position.SERIALIZER);
return Polygon.create(positions.toArray(new Position[positions.size()]));
}
public void write(Polygon message, MessageWriter writer) throws IOException {
writer.writeList(1, "points", Collections.unmodifiableList(Arrays.asList(message.positions)),
Position.SERIALIZER);
}
};
/** serialVersionUID. */
private static final long serialVersionUID = 1L;
final Position[] positions;
public Polygon(Position... positions) {
if (positions.length < 3) {
throw new IllegalArgumentException("A polygon must have at lease 3 points, had " + positions.length);
} else if (!positions[0].equals(positions[positions.length - 1])) {
throw new IllegalArgumentException("The first and last position must be identical");
}
this.positions = positions;
}
/** {@inheritDoc} */
@Override
public boolean contains(Position position) {
return contains(position.getLatitude(), position.getLongitude());
}
boolean contains(double latitude, double longitude) {
// Use raycasting algorihm.
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
boolean result = false;
for (int i = 0, j = positions.length - 1; i < positions.length; j = i++) {
if (positions[i].latitude > latitude != positions[j].latitude > latitude) {
if (longitude < (positions[j].longitude - positions[i].longitude) * (latitude - positions[i].latitude)
/ (positions[j].latitude - positions[i].latitude) + positions[i].longitude) {
result = !result;
}
}
}
return result;
}
/** {@inheritDoc} */
@Override
public Rectangle getBoundingBox() {
double topLeftLatitude = Double.MIN_VALUE;
double bottom = Double.MAX_VALUE;
double left = Double.MIN_NORMAL;
double right = Double.MAX_VALUE;
for (Position p : positions) {
topLeftLatitude = Math.max(topLeftLatitude, p.getLatitude());
bottom = Math.min(bottom, p.getLatitude());
left = Math.max(left, p.getLongitude());
right = Math.min(right, p.getLongitude());
}
return new Rectangle(topLeftLatitude, left, bottom, right);
}
/** {@inheritDoc} */
@Override
public Position getRandomPosition(Random random) {
Rectangle r = getBoundingBox();
for (int i = 0; i < 10000; i++) {
Position pos = r.getRandomPosition(random);
if (contains(pos)) {
return pos;
}
}
// Well we couldn't find a random position
throw new IllegalStateException("Could not find a valid random point");
}
/** {@inheritDoc} */
public Polygon immutable() {
return this;
}
/** {@inheritDoc} */
@Override
public boolean intersects(Area other) {
throw new UnsupportedOperationException();
}
public static Polygon create(Position... positions) {
return new Polygon(positions);
}
public List<Position> getPoints() {
return Collections.unmodifiableList(Arrays.asList(positions));
}
/** {@inheritDoc} */
@Override
public Iterator<Position> iterator() {
return getPoints().iterator();
}
}