package geo;
import gl.GLCamera;
import gl.scenegraph.MeshComponent;
import gl.scenegraph.Shape;
import gui.MetaInfos;
import system.EventManager;
import util.EfficientList;
import util.HasDebugInformation;
import util.Log;
import util.Vec;
import worldData.Entity;
import worldData.Obj;
import worldData.Visitor;
import actions.ActionCalcRelativePos;
import android.location.Address;
import android.location.Location;
/**
* This is a subclass of {@link Obj} which has a fixed GPS position in the
* virtual world. It is the default class to create any object with a location.
* Also check the {@link GeoUtils} methods for some useful things.
*
* @author Spobo
*
*/
public class GeoObj extends Obj implements HasDebugInformation {
@Deprecated
public interface GeoObjUpdateListener {
void updateToNewPosition(int i, int j);
}
/*
* TODO move somewhere else and dont use these to not create new
* dependencies:
*/
@Deprecated
public static final GeoObj a1 = new GeoObj(50.769118, 6.097568, 0, "A1");
@Deprecated
public static final GeoObj a2 = new GeoObj(50.769328, 6.097514, 0, "A2");
@Deprecated
public static final GeoObj a3 = new GeoObj(50.769159, 6.097986, 0, "A3");
@Deprecated
public static final GeoObj n1 = new GeoObj(50.769444, 6.095191, 0, "N1");
@Deprecated
public static final GeoObj n2 = new GeoObj(50.769617, 6.09481, 0, "N2");
@Deprecated
public static final GeoObj n3 = new GeoObj(50.769174, 6.095156, 0, "N3");
@Deprecated
public static final GeoObj rwthI9 = new GeoObj(50.778393, 6.060886, 0, "I9");
@Deprecated
public static final GeoObj iPark1 = new GeoObj(50.778771, 6.061074, 0, "P1");
@Deprecated
public static final GeoObj iPark2 = new GeoObj(50.778661, 6.060497, 0, "P2");
@Deprecated
public static final GeoObj iPark3 = new GeoObj(50.779134, 6.060202, 0, "P3");
@Deprecated
public static final GeoObj iPark4 = new GeoObj(50.779242, 6.060787, 0, "P4");
@Deprecated
public static final GeoObj p = new GeoObj(50.781161, 6.078752, 0, "Ponttor");
private static final String LOG_TAG = "GeoObj";
/**
* if this is true the {@link GeoObj} will try to calculate its virtual
* position in the world assuming the camera is moving relative to the
* 0-point in the world. whenever a new mesh is assigned to this object and
* therefor a new wrapper {@link RenderGroup} is created this flag is
* checked.
*/
private boolean autoCalcVirtualPos = true;
/**
* this is the average radius of the earth in meters. polar-radius is
* 6356800 meters and equatorial-radius is 6378100 meters
*/
private static final int EARTH_RADIUS = 6371000;
// MeshComponent myMesh;
private double myLatitude = 0;
private double myLongitude = 0;
private double myAltitude = 0;
/**
* is needed for the dijsktra algorithm, dont use it anywhere else, it will
* change when using dijsktra!
*/
protected int dijkstraId;
private MeshComponent mySurroundGroup;
@Deprecated
private GeoObjUpdateListener myUpdateListener;
/**
* this flag is used in the {@link CustomItemizedOverlay}-class to
* synchronize it with a parallel created {@link EfficientList} instance of
* {@link GeoObjWrapper}s
*/
private boolean isDeleted = false;
// Vec myPosition=new Vec();
/**
* @param lati
* the latitude value (e.g. 48.858306)
* @param longi
* the longitude value (e.g. 2.294496)
* @param alti
* the altitude value in meters (eg 20 for 20m)
* @param meshToSurround
* @param calcVirtulPos
*/
private GeoObj(double lati, double longi, double alti,
MeshComponent meshToSurround, boolean calcVirtulPos) {
// a geoObj itself should not have a color so null:
setMyLatitude(lati);
setMyLongitude(longi);
setMyAltitude(alti);
autoCalcVirtualPos = calcVirtulPos;
setComp(meshToSurround);
}
@Deprecated
public void setUpdateListener(GeoObjUpdateListener myUpdateListener) {
this.myUpdateListener = myUpdateListener;
}
/**
* Use this constructor if you want to position the GeoObject relatively to
* the users GPS position. Set a {@link MeshComponent} and the virtual
* postion and then extract the PGS coordinates if you need them:
*
* <br>
* <br>
*
* GeoObj x=new GeoObj(); <br>
*
* x.setComp(addMeshCompHere...); <br>
*
* //place it 10 meters north of the users position: <br>
*
* x.setVirtualPosition(new Vec(0,10,0)); <br>
* <br>
*
* x.getLatitude()<br>
*
* x.getLongitude()<br>
*/
public GeoObj() {
super();
setComp(loadDefaultMesh());
}
public GeoObj(boolean autoCalcVirtualPos) {
this.autoCalcVirtualPos = autoCalcVirtualPos;
setComp(loadDefaultMesh());
}
/**
* @return the {@link RenderGroup} where all {@link MeshComponent} will be
* inserted to and which will wrap these objects and recalculate the
* virtual position of the {@link GeoObj}
*/
public MeshComponent getMySurroundGroup() {
if (mySurroundGroup == null)
if (autoCalcVirtualPos) {
mySurroundGroup = new Shape(null, getVirtualPosition());
} else {
mySurroundGroup = new Shape();
}
return mySurroundGroup;
}
@Override
public MeshComponent getGraphicsComponent() {
// System.out.println("super.getGraphicsComponent()="
// + super.getGraphicsComponent());
// System.out.println("super.getGraphicsComponent().getChildren()="
// + super.getGraphicsComponent().getChildren());
return (MeshComponent) super.getGraphicsComponent().getChildren();
}
@SuppressWarnings("deprecation")
@Override
public void setComp(Entity comp) {
if (comp instanceof MeshComponent) {
MeshComponent g = getMySurroundGroup();
g.removeAllChildren();
g.addChild((MeshComponent) comp);
setMyGraphicsComponent(g);
/*
* if the surround-group was not jet added to the GeoObj it will be
* added now:
*/
if (getMyComponents().contains(g) == -1) {
getMyComponents().add(g);
}
} else
super.setComp(comp);
}
/*
* (non-Javadoc)
*
* @see worldData.Obj#getPosition()
*/
@Override
public Vec getPosition() {
Vec p = super.getPosition();
if (p != null) {
return getVirtualPosition().add(p);
}
return getVirtualPosition();
}
/**
* @param lati
* the latitude value (e.g. 48.858306)
* @param longi
* the longitude value (e.g. 2.294496)
* @param alti
* the altitude value in meters (eg 20 for 20m)
* @param meshToSurround
*/
public GeoObj(double lati, double longi, double alti,
MeshComponent meshToSurround) {
this(lati, longi, alti, meshToSurround, true);
}
public GeoObj(GeoObj positionSource, MeshComponent meshToSurround) {
this(positionSource.getLatitude(), positionSource.getLongitude(),
positionSource.getAltitude(), meshToSurround);
}
public GeoObj(GeoObj positionSource) {
this(positionSource.getLatitude(), positionSource.getLongitude(),
positionSource.getAltitude());
}
// public boolean addConnection(GeoObj connectedObject) {
// return true;
// }
/**
* To position a {@link GeoObj} at the current camera position this
* constructor could be used like this: <br>
* <br>
* Vec pos = glCameraInstance.getGPSPositionVec(); <br>
* GeoObj o = new GeoObj(pos.y, pos.x, pos.z);
*
* !Important
*
* @param lati
* the latitude value (e.g. 48.858306)
* @param longi
* the longitude value (e.g. 2.294496)
* @param alti
* the altitude value in meters (eg 20 for 20m)
*/
public GeoObj(double lati, double longi, double alti) {
this(lati, longi, alti, loadDefaultMesh());
}
public GeoObj(Address a) {
this(a.getLatitude(), a.getLongitude(), 0, loadDefaultMesh());
getInfoObject().extractInfos(a);
}
public GeoObj(Location l) {
this(l.getLatitude(), l.getLongitude(), l.getAltitude(),
loadDefaultMesh());
}
/**
* @param lati
* the latitude value (e.g. 48.858306)
* @param longi
* the longitude value (e.g. 2.294496)
* @param alti
* the altitude value in meters (eg 20 for 20m)
* @param name
*/
public GeoObj(double lati, double longi, int alti, String name) {
this(lati, longi, alti, loadDefaultMesh());
getInfoObject().setShortDescr(name);
}
/**
* @param l
* if l is null the GPS coordinates will be set to 0
* @param b
* if false the virtual position wont be calculated!
*/
public GeoObj(Location l, boolean b) {
this((l != null) ? l.getLatitude() : null, (l != null) ? l
.getLongitude() : null, (l != null) ? l.getAltitude() : null,
loadDefaultMesh(), false);
}
/**
* This constructor can be used if the altitude is unknown and should be the
* same as the current camera altitude (to be at the same height like the
* camera)
*
* @param latitude
* the latitude value (e.g. 48.858306)
* @param longitude
* the longitude value (e.g. 2.294496)
*/
public GeoObj(double latitude, double longitude) {
this(latitude, longitude, getDeviceAltidute());
}
private static double getDeviceAltidute() {
if (ActionCalcRelativePos.USE_ALTITUDE_VALUES) {
return EventManager.getInstance().getCurrentLocationObject()
.getAltitude();
}
return 0;
}
/**
* this can be overwritten by subclasses of {@link GeoObj} to set a default
* mesh to a newly created {@link GeoObj}
*
* @return
*/
public static MeshComponent loadDefaultMesh() {
return null;
}
public Location toLocation() {
Location x = new Location("customCreated");
x.setLatitude(getLatitude());
x.setLongitude(getLongitude());
x.setAltitude(getAltitude());
return x;
}
/**
* @return the latitude value of the GeoObj (e.g. 48.858306)
*/
public double getLatitude() {
return myLatitude;
}
/**
* @return the longitude value of the GeoObj(e.g. 2.294496)
*/
public double getLongitude() {
return myLongitude;
}
/**
* @return the altitude value in meters (e.g. 20 for 20m)
*/
public double getAltitude() {
return myAltitude;
}
/**
* dont forget to call {@link GeoObj#refreshVirtualPosition()} afterwards if
* the virtual position should be updated
*
* @param latitude
*
*/
public void setMyLatitude(double latitude) {
this.myLatitude = latitude;
if (myUpdateListener != null)
myUpdateListener.updateToNewPosition((int) (getLatitude() * 1E6),
(int) (getLongitude() * 1E6));
}
/**
* dont forget to call {@link GeoObj#refreshVirtualPosition()} afterwards if
* the virtual position should be updated
*
* @param longitude
*/
public void setMyLongitude(double longitude) {
this.myLongitude = longitude;
if (myUpdateListener != null)
myUpdateListener.updateToNewPosition((int) (getLatitude() * 1E6),
(int) (getLongitude() * 1E6));
}
/**
* dont forget to call {@link GeoObj#refreshVirtualPosition()} afterwards if
* the virtual position should be updated
*
* @param altitude
*/
public void setMyAltitude(double altitude) {
this.myAltitude = altitude;
if (myUpdateListener != null)
myUpdateListener.updateToNewPosition((int) (getLatitude() * 1E6),
(int) (getLongitude() * 1E6));
}
/**
* calculates the virtual position in the OpenGL-view relative to the
* relative zero point (normally the device position)
*
* @param zeroLatitude
* @param zeroLongitude
* @param zeroAltitude
* @return the target Vec or a new Vec with the correct virtual position if
* target vec was null (x=longitude, y=latitude, z=altitude)
*/
public Vec getVirtualPosition(double zeroLatitude, double zeroLongitude,
double zeroAltitude) {
/*
* The longitude calculation depends on current latitude: The
* circumference of a circle at a given latitude is proportional to the
* cosine, so the formula is:
*
* (myLongitude - zeroLongitude) * 40075017 / 360 * cos(zeroLatitude)
*
* earth circumfence through poles is 40008000
*
* earth circumfence at equator is 40075017
*
* degree to radians: PI/180=0.0174532925
*
* TODO check what happens when myLongi is positive but zeroLongi is
* negative for example. this can create problems! both have to be
* negative or positive otherwise delta value is wrong! this will nearly
* never happen, but for people in Greenwhich eg it might be a problem
* when living near the 0 latitude..
*/
Vec position = new Vec();
position.x = (float) ((myLongitude - zeroLongitude) * 111319.4917 * Math
.cos(zeroLatitude * 0.0174532925));
position.y = (float) ((myLatitude - zeroLatitude) * 111133.3333);
/*
* the altitude should be respected as well but altitude = 0 should by
* default mean the current device altitude is used:
*/
if (ActionCalcRelativePos.USE_ALTITUDE_VALUES) {
if (myAltitude == 0
&& ActionCalcRelativePos.USE_DEVICE_ALTI_FOR_ZERO) {
position.z = 0;
} else {
position.z = (float) (myAltitude - zeroAltitude);
}
} else {
/*
* else the altitude value is ignored but the height of the meshcomp
* can be still set of course
*/
}
return position;
}
/**
* This method will set the gps position according to the passed virtual
* position
*
* @param virtualPosition
* the virtual position of the object in meters
* @param zeroLocation
* normally this would be the device position, but you can
* specify anything else here too
* @return if the virtual position of the mesh could be updated too
*/
public boolean calcGPSPosition(Vec virtualPosition, GeoObj zeroLocation) {
Vec newGPSPos = GeoObj.calcGPSPosition(virtualPosition,
zeroLocation.getLatitude(), zeroLocation.getLongitude(),
zeroLocation.getAltitude());
if (newGPSPos != null) {
setMyLongitude(newGPSPos.x);
setMyLatitude(newGPSPos.y);
setMyAltitude(newGPSPos.z);
} else {
Log.i(LOG_TAG, "GeoObj " + this
+ " did not have a virtual position so it's assumed "
+ "it should be placed at (0,0,0).");
setMyLatitude(zeroLocation.getLatitude());
setMyLongitude(zeroLocation.getLongitude());
setMyAltitude(zeroLocation.getAltitude());
}
/*
* does it even make sense to update the virtual position here? has the
* virtual position passed as a parameter to this method to be the
* virtual position of the object?
*/
return refreshVirtualPosition();
}
/**
* define the virtual position and the GPS position will be calculated
* correctly.
*
* @param virtualPosition
* @return true if the virtual position was updated correctly
*/
public boolean setVirtualPosition(Vec virtualPosition) {
return calcGPSPosition(virtualPosition, EventManager.getInstance()
.getZeroPositionLocationObject());
}
/**
* @param zeroLatitude
* @param zeroLongitude
* @param zeroAltitude
* @return a Vector with x=Longitude, y=Latitude, z=Altitude
*/
public static Vec calcGPSPosition(Vec virtualPosition, double zeroLatitude,
double zeroLongitude, double zeroAltitude) {
if (virtualPosition != null) {
/*
* same formula as in calcVirtualPos() but resolved for latitude and
* longitude:
*/
Vec result = new Vec();
result.x = (float) (virtualPosition.x
/ (111319.889f * Math.cos(zeroLatitude * 0.0174532925f)) + zeroLongitude);
result.y = (float) (virtualPosition.y / 111133.3333f + zeroLatitude);
result.z = (float) (virtualPosition.z + zeroAltitude);
return result;
}
return null;
}
/**
* This sets all three position values at once (can be useful in combination
* with {@link GeoObj}.calcGPSPosition e.g.
*
* @param GPSPosition
* a {@link Vec} with x=Longitude, y=Latitude, z=Altitude
*/
public void setMyPosition(Vec GPSPosition) {
setMyLongitude(GPSPosition.x);
setMyLatitude(GPSPosition.y);
setMyAltitude(GPSPosition.z);
}
public int matchesSearchTerm(String searchTerm) {
return getInfoObject().matchesSearchTerm(searchTerm);
}
/**
* calculates the distance in meters to the {@link GeoObj} GPS-position
*
* @param otherGeoObj
* @return positive value in meters
*/
public double getDistance(GeoObj otherObj) {
return distFrom(getLatitude(), getLongitude(), otherObj.getLatitude(),
otherObj.getLongitude());
}
/**
* Implementation of the Harvesine function
*
* @param lat1
* value in degree
* @param lng1
* value in degree
* @param lat2
* value in degree
* @param lng2
* value in degree
* @return the distance in meters
*/
private static double distFrom(double lat1, double lng1, double lat2,
double lng2) {
final double dLat = Math.toRadians(lat2 - lat1);
final double dLng = Math.toRadians(lng2 - lng1);
final double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
* Math.sin(dLng / 2);
return EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
public boolean hasSameCoordsAs(GeoObj o) {
if (o.getLatitude() == getLatitude()) {
if (o.getLongitude() == getLongitude()) {
return true;
}
}
return false;
}
/**
* dont forget to call {@link GeoObj#refreshVirtualPosition()} afterwards if
* the virtual position should be updated
*
* @param l
*/
public void setLocation(Location l) {
if (l != null) {
setMyLatitude(l.getLatitude());
setMyLongitude(l.getLongitude());
setMyAltitude(l.getAltitude());
}
}
// public void setLocation(Location location) {
// setMyLatitude(location.getLatitude());
// setMyLongitude(location.getLongitude());
// setMyAltitude(location.getAltitude());
// }
/**
* This method can also be used to calculate the distance between
* {@link GeoObj} A and {@link GeoObj} B by doing this: <br>
* aToB_Distance=b.getVirtualPosition(a);
*
* @param relativeNullPoint
* @return (x=longitude, y=latitude, z=altitude)
*/
public Vec getVirtualPosition(GeoObj relativeNullPoint) {
if (relativeNullPoint == null) {
Log.e(LOG_TAG, "Virtual position can't be calculated if the "
+ "relative zero position is not known!");
return null;
}
return getVirtualPosition(relativeNullPoint.getLatitude(),
relativeNullPoint.getLongitude(),
relativeNullPoint.getAltitude());
}
/**
* This will use the current gps values and calculate the virtual position
* in account to the current device gps position
*
* @return the virtual position (x=rel. east distance (longitude axis),
* y=rel. north distance (latitude axis), z=rel. height (altitude
* axis))
*/
public Vec getVirtualPosition() {
return getVirtualPosition(EventManager.getInstance()
.getZeroPositionLocationObject());
}
/**
* refresh the virtual position of the object (does only work if the object
* has a {@link MeshComponent} jet)
*
* @return true if it worked
*/
public boolean refreshVirtualPosition() {
Vec pos = getVirtualPosition();
if (pos != null) {
MeshComponent m = getMySurroundGroup();
if (m != null) {
m.setPosition(pos);
return true;
}
}
return false;
}
// /**
// * this is called by {@link DefaultSelectionInterface} objects like the
// * {@link GMap} or the {@link GeoGraph} to load the default onClick
// command
// * and set it to this {@link GeoObj}
// *
// * @param s
// */
// public void setSelectionCommands(DefaultSelectionInterface s) {
// if (getOnClickCommand() == null)
// setOnClickCommand(s.getDefaultOnClickCommand());
// if (getOnLongClickCommand() == null)
// setOnLongClickCommand(s.getDefaultOnLongClickCommand());
// if (getOnMapClickCommand() == null)
// setOnMapClickCommand(s.getDefaultOnMapClickCommand());
// }
@Override
public boolean accept(Visitor visitor) {
return visitor.default_visit(this);
}
public void setRemoved() {
isDeleted = true;
}
public boolean isDeleted() {
return isDeleted;
}
/**
* @return a new object with the same location data and the same
* {@link MetaInfos} information
*/
public GeoObj copy() {
GeoObj o = new GeoObj(this);
o.autoCalcVirtualPos = this.autoCalcVirtualPos;
o.getInfoObject().setTo(this.getInfoObject());
return o;
}
@Override
public void showDebugInformation() {
Log.e(LOG_TAG, "Information about " + this);
Log.d(LOG_TAG, "mySurroundGroup=" + mySurroundGroup);
Log.d(LOG_TAG,
"mySurroundGroup.myPosition=" + mySurroundGroup.getPosition());
Log.d(LOG_TAG, "mySurroundGroup.myScale=" + mySurroundGroup.getScale());
Log.d(LOG_TAG,
"mySurroundGroup.myRotation=" + mySurroundGroup.getRotation());
Log.d(LOG_TAG, "mesh inside=" + mySurroundGroup.getChildren());
}
public static GeoObj newRandomGeoObjAroundCamera(GLCamera camera,
float minDist, float maxDist) {
GeoObj o = new GeoObj();
if (maxDist < minDist)
maxDist = minDist;
o.setVirtualPosition(Vec.getNewRandomPosInXYPlane(camera.getPosition(),
minDist, maxDist));
return o;
}
}