package net.gcdc.geonetworking; import java.nio.ByteBuffer; /** * Area according to ETSI EN 302 931 V1.1.1 "Geographical Area Definition". * * The class is declared as final, just because there was no need for subclasses yet. * If you remove final, make sure to take good care of {@link #equals(Object)} and * {@link #hashCode()}. */ public final class Area { private final Position center; private final double distanceAmeters; private final double distanceBmeters; private final double angleDegreesFromNorth; private final Type type; private Area(Position center, double distanceA, double distanceB, double angleDegreesFromNorth, Type type) { this.center = center; this.distanceAmeters = distanceA; this.distanceBmeters = distanceB; this.angleDegreesFromNorth = angleDegreesFromNorth; this.type = type; } @Override public String toString() { return "Area [center=" + center + ", distanceAmeters=" + distanceAmeters + ", distanceBmeters=" + distanceBmeters + ", angleDegreesFromNorth=" + angleDegreesFromNorth + ", type=" + type + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (short)angleDegreesFromNorth; result = prime * result + (short)distanceAmeters; result = prime * result + (short)distanceBmeters; result = prime * result + ((center == null) ? 0 : center.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Area other = (Area) obj; if ((short)angleDegreesFromNorth != (short)other.angleDegreesFromNorth) return false; if ((short)distanceAmeters != (short)other.distanceAmeters) return false; if ((short)distanceBmeters != (short)other.distanceBmeters) return false; if (center == null) { if (other.center != null) return false; } else if (!center.equals(other.center)) return false; if (type != other.type) return false; return true; } public static enum Type { CIRCLE (0), RECTANGLE (1), ELLIPSE (2); private final int code; private Type(int code) { this.code = code; } public int code() { return code; } public static Area.Type fromCode(int code) { for (Area.Type h: Area.Type.values()) { if (h.code() == code) { return h; } } throw new IllegalArgumentException("Can't recognize area type: " + code); } } public static Area getFrom(ByteBuffer buffer, Area.Type type) { Position center = Position.getFrom(buffer); double distanceA = buffer.getShort() & 0xffff; // Convert to int to remove sign from short. double distanceB = buffer.getShort() & 0xffff; double angleDegreesFromNorth = buffer.getShort() & 0xffff; return new Area(center, distanceA, distanceB, angleDegreesFromNorth, type); } public ByteBuffer putTo(ByteBuffer buffer) { center.putTo(buffer); buffer.putShort((short) distanceAmeters); buffer.putShort((short) distanceBmeters); buffer.putShort((short) angleDegreesFromNorth); return buffer; } public Area.Type type() { return type; } public Position center() { return center; } public boolean contains(Position position) { return f(position) >= 0; } /** Characteristic function of a geographical area. * * The function has the following properties: * <pre> * = 1 for x = 0 and y = 0 (at the center point) * > 0 inside the geographical area * = 0 at the border of the geographical area * < 0 outside the geographical area * </pre> * where x, y are the geographical coordinates of a position P in a Cartesian coordinate system * with origin in the center of the shape and abscissa parallel to the long side of the shape. */ public double f(Position position) { double distance = this.center.distanceInMetersTo(position); double bearing = this.center.bearingInDegreesTowards(position); double relativeAngle = bearing - angleDegreesFromNorth; double x = distance * Math.cos(Math.toRadians(relativeAngle)); double y = distance * Math.sin(Math.toRadians(relativeAngle)); double a = distanceAmeters; double b = distanceBmeters; switch (type) { case CIRCLE: return 1 - Math.pow(x/a, 2) - Math.pow(x/a, 2); // distanceB is 0 for circle. case ELLIPSE: return 1 - Math.pow(x/a, 2) - Math.pow(y/b, 2); case RECTANGLE: return Math.min(1 - Math.pow(x/a, 2), 1 - Math.pow(y/b, 2)); default: return 0; // At a border of an unknown shape... } } public static Area circle(Position center, double radius) { return new Area(center, radius, 0, 0, Type.CIRCLE); } /** * * @param center position of the center point * @param longDistanceMeters half the long side -- the (longer) distance between the center point and the short side of the rectangle (perpendicular bisector of the short side); * @param shortDistanceMeters half the short side -- the (shorter) distance between the center point and the long side of the rectangle (perpendicular bisector of the long side); * @param azimuthAngleDegreesFromNorth azimuth angle of the long side of the rectangle. * @return new rectangular area */ public static Area rectangle( Position center, double longDistanceMeters, double shortDistanceMeters, double azimuthAngleDegreesFromNorth) { return new Area(center, longDistanceMeters, shortDistanceMeters, azimuthAngleDegreesFromNorth, Type.RECTANGLE); } public static Area ellipse( Position center, double longSemiAxisMeters, double shortSemiAxisMeters, double azimuthAngleDegreesFromNorth) { return new Area(center, longSemiAxisMeters, shortSemiAxisMeters, azimuthAngleDegreesFromNorth, Type.ELLIPSE); } }