package aimax.osm.data;
import java.util.Collection;
import java.util.List;
import aimax.osm.data.entities.MapEntity;
import aimax.osm.data.entities.MapNode;
import aimax.osm.data.entities.MapWay;
import aimax.osm.data.entities.WayRef;
/**
* Maintains a latitude longitude pair and provides some useful methods for
* distance calculations.
*
* @author Ruediger Lunde
*/
public class Position {
/** Earth's mean radius in km. */
public static double EARTH_RADIUS = 6371.0;
protected float lat;
protected float lon;
public Position(float lat, float lon) {
this.lat = lat;
this.lon = lon;
}
public Position(MapNode node) {
lat = node.getLat();
lon = node.getLon();
}
public float getLat() {
return lat;
}
public float getLon() {
return lon;
}
/**
* Computes the distance in kilometer from this position to a specified map
* node.
*/
public double getDistKM(MapEntity entity) {
if (entity instanceof MapNode) {
return getDistKM(lat, lon, ((MapNode) entity).getLat(),
((MapNode) entity).getLon());
} else if (entity instanceof MapWay) {
MapWay way = (MapWay) entity;
BoundingBox bb = way.computeBoundingBox();
float bbLat = (Math.abs(lat - bb.getLatMin()) < Math.abs(lat
- bb.getLatMax())) ? bb.getLatMin() : bb.getLatMax();
float bbLon = (Math.abs(lon - bb.getLonMin()) < Math.abs(lon
- bb.getLonMax())) ? bb.getLonMin() : bb.getLonMax();
return getDistKM(lat, lon, bbLat, bbLon);
}
return Double.NaN;
}
public boolean insertInAscendingDistanceOrder(List<MapEntity> nodes,
MapNode node) {
int pos = getInsertPosition(nodes, node);
if (pos != -1)
nodes.add(pos, node);
return pos != -1;
}
public boolean insertInAscendingDistanceOrder(List<MapEntity> ways,
MapWay way) {
int pos = getInsertPosition(ways, way);
if (pos != -1)
ways.add(pos, way);
return pos != -1;
}
private int getInsertPosition(List<?> entities, MapEntity entity) {
int pos1 = 0;
int pos2 = entities.size();
double newDistance = getDistKM(entity);
while (pos1 < pos2) {
int pos3 = (pos1 + pos2) / 2;
double dist3 = getDistKM((MapEntity) entities.get(pos3));
if (newDistance < dist3)
pos2 = pos3;
else if (newDistance > dist3)
pos1 = pos3 + 1;
else
pos1 = pos2 = pos3;
}
if (pos1 < entities.size()) {
for (int i = pos1; i >= 0
&& getDistKM((MapEntity) entities.get(i)) == newDistance; i--)
if (entities.get(i) == entity)
return -1;
for (int i = pos1 + 1; i < entities.size()
&& getDistKM((MapEntity) entities.get(i)) == newDistance; i++)
if (entities.get(i) == entity)
return -1;
}
return pos1;
}
/**
* Returns the node from <code>nodes</code> which is nearest to this
* position. If a filter is given, only those nodes are inspected, which are
* part of a way accepted by the filter.
*
* @param nodes
* @param filter
* possibly null
* @return A node or null
*/
public MapNode selectNearest(Collection<MapNode> nodes, MapWayFilter filter) {
MapNode result = null;
double dist = Double.MAX_VALUE;
double newDist;
for (MapNode node : nodes) {
newDist = getDistKM(node);
boolean found = (newDist < dist);
if (found && filter != null) {
found = false;
for (WayRef ref : node.getWayRefs()) {
if (filter.isAccepted(ref.getWay()))
found = true;
}
}
if (found) {
result = node;
dist = newDist;
}
}
return result;
}
/**
* Computes a simple approximation of the compass course from this position
* to the specified node.
*
* @param node
* @return Number between 1 and 360
*/
public int getCourseTo(MapNode node) {
double lonCorr = Math.cos(Math.PI / 360.0 * (lat + node.getLat()));
double latDist = node.getLat() - lat;
double lonDist = lonCorr * (node.getLon() - lon);
int course = (int) (180.0 / Math.PI * Math.atan2(lonDist, latDist));
if (course <= 0)
course += 360;
return course;
}
/** Computes the total length of a track in kilometers. */
public static double getTrackLengthKM(List<MapNode> nodes) {
double result = 0.0;
for (int i = 1; i < nodes.size(); i++) {
MapNode n1 = nodes.get(i - 1);
MapNode n2 = nodes.get(i);
result += getDistKM(n1.getLat(), n1.getLon(), n2.getLat(), n2
.getLon());
}
return result;
}
/**
* Computes the distance between two positions on the earth surface using
* the haversine formula.
*/
public static double getDistKM(float lat1, float lon1, float lat2,
float lon2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2)
* Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c;
}
}