I am fairly new to Android programming, but I am getting pretty good at it (I think: ))
What I am doing is building a situated stories app. It is an app that places audio files at certain GPS markers and enables the user to listen to them at specific locations.
The next step is moving audio files. What I want to do is set a marker at a specific position in a city. (done). Next I want to check the location of a second marker that moves in a circle around it.
What I have so far is this:
public void checkCircularPosition(){
/*
* could be a solution?
*
radius = 250; //offset in meters
gpsLatCenter = 5.1164; //?how can i make this accurate in meters?
gpsLonCenter = 52.0963; //??how can i make this accurate in meters?
degree = 0; //should be variable over time (full circle in 1Hr, 3600sec --> 360/3600 = 0,1 deg/s)
radian;
radian = (degree/180)*Math.PI;
gpsCircleLat = gpsLatCenter+Math.cos(radian)*radius;
gpsCircleLon = gpsLonCenter-Math.sin(radian)*radius;
*/
}
Now, I have checked this code in adobe flash, which made a movie clip move around in a circle. So I know the calculations are somewhat right. But, I have no way of calculating the latitude and longitude of the resulting coordinates.
EDIT!!
i found the solution with the help posted below. still a lot of work to figure out how to use the results. anyway, i posted the resulting function below.
to make this work, you need _radius wich is 6371 (earth's radius), a bearing, a distance, and a start location.
thanks a lot guys!
public static void destinationPoint(double brng, double dist) {
dist = dist/_radius; // convert dist to angular distance in radians
brng = Math.toRadians(brng); //
double lat1 = Math.toRadians(_lat);
double lon1 = Math.toRadians(_lon);
double lat2 = Math.asin( Math.sin(lat1)*Math.cos(dist) + Math.cos(lat1)*Math.sin(dist)*Math.cos(brng) );
double lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos(lat1), Math.cos(dist)-Math.sin(lat1)*Math.sin(lat2));
lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180º
Log.i(APPTAG, ""+Math.toDegrees(lat2));
Log.i(APPTAG, ""+Math.toDegrees(lon2));
Location movLoc = new Location("");
movLoc.setLatitude(Math.toDegrees(lat2));
movLoc.setLongitude(Math.toDegrees(lon2));
Log.i(APPTAG, ""+movLoc);
}
You should check the section Destination point given distance and bearing from start point at this website: http://www.movable-type.co.uk/scripts/latlong.html
That website has the proper formula for using your start point (gpsLatCenter/gpsLonCenter) and bearing (degree in you code) to compute the final lat/lon (gpsCircleLat/gpsCircleLon).
Related
I am working coordinates system as shown below:
x and y are in meters. I am interested in only positive x,y. I want to convert (x,y) to (lat,lon) and vise versa.
I thought it is simple and I dont see a problem with my solutions below. But I am not getting very correct results.
My Solution:
As we see in the image below, I considered the Latitude and Longitude as angles of 2 circles:
1. (x,y) to (lat,lon)
I applied the Arc Length formula (here), shown below, on both; x and y
Hence, my functions are:
private static double calculateLat(float x) {
int earthRadius = 6371000;
return REF_LOC.getLatitude() + x*360/(2*PI*earthRadius);
}
private static double calculateLong(float y) {
int earthRadius = 6371000;
return REF_LOC.getLongitude() + y*360/(2*PI*earthRadius);
}
REF_LOC is the reference Geo Location for which (x,y) are (0,0). It can be any point on earth.
2. (lat,lon) to (x,y)
For this I am simply using this:
int calculateX(double longitude){
Location.distanceBetween(REF_LOC.getLatitude(), REF_LOC.getLongitude(),
REF_LOC.getLatitude(), lonDeg, results);
return results[0];
}
int calculateY(double latitude){
Location.distanceBetween(REF_LOC.getLatitude(), REF_LOC.getLongitude(),
latDeg, REF_LOC.getLongitude(), results);
return results[0];
}
But I am getting inconsistent results. First I use solution 1 and convert some value (x,y) to (lat,long). But when I use the same (lat,long) back to (x,y) using solution 2, I get about 2 meters difference in x and 10 meters in y. Can anyone help me identify the problem please?
Alluding to my comment on spherical vs elliptical calculations, another way to look at difference in distance calculations for spherical vs elliptical distance is to use the 2 available utilities:
// For REF_LOC = (70, 20)
// Compute a lat/lng due east of a REF_LOC and compute distance using both
// available methods.
LatLng P = SphericalUtil.computeOffsetOrigin(REF_LOC, 20000, 90.0);
// And then compute a distance
d = SphericalUtil.computeDistanceBetween(REF_LOC, P);
Location.distanceBetween(REF_LOC.latitude, REF_LOC.longitude, P.latitude, P.longitude, results);
// d = 20000.000000000036
// results[0] = 20081.818
// and for a REF_LOC = (0, 20)
// d = 20000.000000000127
// results[0] = 20022.377
What's also interesting is the SphericalUtil.computeOffsetOrigin() produces error
in latitude increasing from equator to pole making it non-symmetrical. Yet the resulting distances are essentially exact.
I'd recommend using SphericalUtil.computeOffsetOrigin to compute the X/Y breaking
it into the latitude and longitude offsets as you are doing.
Finally demonstrating the SphericalUtil solution:
// Start with some arbitrary x/y relative to REF_LOC
double Rx = 125.0;
double Ry = 73.0;
// Compute a lat/lon from REF_LOC to the point
LatLng Rll = new LatLng(SphericalUtil.computeOffsetOrigin(REF_LOC, Ry, 180).latitude,
SphericalUtil.computeOffsetOrigin(REF_LOC, Rx, 270).longitude);
// And recompute the x/y components of the lat/lon
double Rxx = SphericalUtil.computeDistanceBetween(REF_LOC, new LatLng(REF_LOC.latitude, Rll.longitude));
double Ryy = SphericalUtil.computeDistanceBetween(REF_LOC, new LatLng(Rll.latitude, REF_LOC.longitude));
Resulting in:
Rx/Ry = (125.0, 73.0)
Rxx/Ryy = (125.00000004545973, 73.00000000137051)
Acceptable error I assume.
So the parting question is - what does the x/y really represent?
Reference the source for both utilities for more information:
Location
SphericalUtil
I'm adding a PNG file as an own floorplan on top of Google Maps with the Google Maps Android API with the following code:
GroundOverlayOptions groundOverlayOptions = new GroundOverlayOptions();
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromAsset("building-d.png");
groundOverlayOptions.image(bitmapDescriptor);
groundOverlayOptions.anchor(0, 1);
LatLng buildingSW = new LatLng(47.014815, 8.305098);
LatLng buildingNE = new LatLng(47.015148, 8.305440);
LatLng buildingNW = new LatLng(47.015168, 8.305144);
LatLng buildingSE = new LatLng(47.014792, 8.305385);
Location swLoc = locationFromLatLng(buildingSW);
Location seLoc = locationFromLatLng(buildingSE);
Location nwLoc = locationFromLatLng(buildingNW);
Location neLoc = locationFromLatLng(buildingNE);
float angle = swLoc.bearingTo(nwLoc);
groundOverlayOptions.bearing(angle);
float width = swLoc.distanceTo(seLoc);
groundOverlayOptions.position(buildingSW, width);
mMap.addGroundOverlay(groundOverlayOptions);
Now I know that in the PNG there is a room at pixel coordinates 422/301, 708/301, 422/10 and 708/10 (those are the corners). I'd like to draw a polygon over the GroundOverlay covering that room. How should I do that? Do I need to convert my pixel-coordinates to LatLng and if so, how?
And by the way: Do I really have to use PNGs for GroundOverlays and is there no other supported vector-format like eps, pdf, ...?
Having seen your comment to the other answer, let me complete with some code:
Having set the "origin" in latlng 47.014816, 8.305098, you have to convert those coordinates to mercator and you can do something similar to the below:
public boolean initializeByTwoCouplesOfCooordsAndScale(double[] coordAreal, double[] coordBreal, double[] coordAvirtual, double[] coordBvirtual, double scalingFactor) {
if (coordAreal[0] == coordBreal[0] && coordAvirtual[1] == coordBvirtual[1] && coordAreal[1] == coordBreal[1] && coordAvirtual[0] == coordBvirtual[0]) {
System.err.println("Coordinates must not be the same!");
return false;
}
// aPoint is considered the "origin" point (0,0)
aPoint = coordAreal;
bPoint = coordAvirtual;
// now calculate the angle of the Real world coordinate for the points
double deltaRy = coordBreal[1] - coordAreal[1];
double deltaRx = coordBreal[0] - coordAreal[0];
double aR = Math.atan2(deltaRy, deltaRx);
// Now calculate the angle of the virtual world coordinates
double deltaVy = coordBvirtual[1] - coordAvirtual[1];
double deltaVx = coordBvirtual[0] - coordAvirtual[0];
double aV = Math.atan2(deltaVy, deltaVx);
// Set the transformation angle as the difference between the real and the virtual angles.
mPhi= (aR - aV);
// Set the scaling factor as the provided one
mScale = (scalingFactor);//scaling factor is in function below
// Calculate the scaling factor error correction using the distances of the two systems.
return true;
}
public static double getScalingFactor(double latitude) {
return 1 / (Math.cos(Math.toRadians(latitude)));
}
So you can call the method:
initializeByTwoCouplesOfCooordsAndScale(new double[]{MERCATOR_LNG,MERCATOR_LAT},//real coordinates for point A REMEMBER: LNG,LAT = x,y!
new double[]{0d,0d}, //Virual coordinates for point A
new double[]{MERCATOR_POINT_B_LNG, MERCATOR_POINT_B_LAT},//real point B
new double[]{X_METERS,Y_METERS},//coordinates in meters of point B in virtual map
getScalingFactor(47.014816));
then you can transform with this function:
public double[] transform(double[] coord) {
double[] transCoord = new double[2];
double xscaled = (coord[0] - bPoint[0]) * mScale; // XXX bPoint is the position of origin point in the "VIRTUAL" world. [0] is the x coordinate
double yscaled = (coord[1] - bPoint[1]) * mScale;
transCoord[0] = (xscaled * Math.cos(mPhi)) - (yscaled * Math.sin(mPhi)) + aPoint[0]; //aPoint is the point with real coordinates of origin!
transCoord[1] = (xscaled * Math.sin(mPhi)) + (yscaled * Math.cos(mPhi)) + aPoint[1];
return transCoord;
}
you can find online a way to convert latlng to mercator, it just a bunch of math ;)
You should work in this way:
Your indoor map positions should be relative to a specific point (BOTTOM-LEFT is 0,0 let's say), then all the other positions will be relative to that point in meters, so you will endup in values under 100meters usually.
Having this you have to "move, rotate and scale" the indoor map with respect to the world.
Just take a map on a desktop which is not LAT/LNG and find the coordinates for the same indoor points you have (usually we get real and indoor position for bottom-left and top-right positions) so you can find where it should be in the world.
Take a look also at the scaling factor (depending on the latitude, the map must be scaled)
https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor
We calculate that value by doing something like 1/cos(latitudeINradians)
public static double getScalingFactor(double latitude) {
return 1 / (Math.cos(Math.toRadians(latitude)));
}
Let me know if you can find a way, otherwise i will search and try to strip our code
I'm drawing a point which reflects my current location. I've got some shapes on the map as well. However, due to changing GPS accuracy I need to show that I've crossed the line on map even if a LatLng point is still before it but the accuracy is ~20(m).
What I have:
- line's start and end points
- circle's center
- circle's radius (in meters)
I'm able to calculate the distance between line and circle's center point but that gives me the value like: 0.00987506668990474 which gives me really nothing because this value does not reflect the distance in meters and I can't really convert that to meters and compare with accuracy radius.
I'm not even sure if I'm on good target to get that information. Maybe there's some another method to get the information if there's an intersection or not.
thanks for any hints
update:
using the distance * 110km I'm getting much better results.
Happy face = intersection not detected - distance < radius, Sad face = intersection detected - distance > radius.
Yellow zone edge is mine line segment. As you can see it works when the intersection is on the top(/bottom), but not on the left(/right) side.
That's my calculation algorithm calculating distance from line segment to point. Forgive the mess... I'm still struggling with that, so it's not optimized yet:
public static double pointLineSegmentDistance(final List<Double> point, final List<List<Double>> line)
{
List<Double> v = line.get(0);
List<Double> w = line.get(1);
double d = pointPointSquaredDistance(v, w);
double t;
List<Double> calculateThis;
if (d > 0)
{
boolean test = (t = ((point.get(0) - v.get(0)) * (w.get(0) - v.get(0)) + (point.get(1) - v.get(1)) * (w.get(1) - v.get(1))) / d) < 0;
if (test)
{
calculateThis = v;
}
else
{
if (t > 1)
{
calculateThis = w;
}
else
{
calculateThis = new ArrayList<Double>();
calculateThis.add(v.get(0) + t * (w.get(0) - v.get(0)));
calculateThis.add(v.get(1) + t * (w.get(1) - v.get(1)));
}
}
}
else
{
calculateThis = v;
}
return Math.sqrt(pointPointSquaredDistance(point, calculateThis));
}
public static double pointPointSquaredDistance(final List<Double> v, final List<Double> w)
{
double dx = v.get(0) - w.get(0);
double dy = v.get(1) - w.get(1);
return dx * dx + dy * dy;
}
final List point - contains 2 elements - (0) latitude (1) longitude
List> line - contains 2 lists - (0) line segment start point (0/0)lat/(0/1)long (1) line segment end point (1/0)lat/(1/1)long
Finally I've solved my problem. It comes the solution is really very simple. Much simpler than I thought. There's maputil library available here:
https://github.com/googlemaps/android-maps-utils
That library privides a function named "isLocationOnPath":
boolean isLocationOnPath(LatLng point, List<LatLng> polyline, boolean geodesic, double tolerance)
Point - is a center or my point.
Polyline - for my needs is an edge of a polygon,
Geodesic - true (of course)
Tolerance - (in meters) is my accuracy taken from GPS accuracy - basicaly is a circle radius.
Details about the method:
http://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/PolyUtil.html#isLocationOnPath-LatLng-java.util.List-boolean-
I hope that helps.
You have two possibilities:
1) The cheap way:
You calcualte the distances between line start/end point and circle center with the android api (distanceBetween()). The result will be in meters, and correctby means of point to point distance calculation. Take the minimum of both start / end point to center circle distances.
If the line segment is very long related to the circle radius, the normal distance of the line to center, using this kind of caluclation is wrong
2) The better way, but more complex:
You transform the line start and end and the circle center to cartesian x,y space with unit = meter and calculate the distance to the line
segment using cartesian math. (see distance to line segment calculation)
First check option1 because it is just one line of code.
Your result of 0.00987506668990474 this looks like a distance in degrees, instead of meters. multiply it with 111km gives a value of about 1km. (at equator)
I am using the new Android place Api to get autocomplete predictions while the user type.
From what I saw so far the API takes a LatLngBounds object which is created using two locations.
Is there a way to generate a LatLngBounds object using one LatLng as a center point and a radius?
Thank you.
As promised Distwo, here is how I am creating a viewport based on a single latitude and longitude. The reason for me wanting to do this is that my store locator script searches from the viewport returned by the Places API, and so if a viewport isn't returned (rare but it happens) I go ahead and create one from the lat and lng returned by places. Please note that I am not using the android app, this is for regular web viewing.
console.log("no viewport found so lets spherically compute the bounds");
var sw = google.maps.geometry.spherical.computeOffset(place.geometry.location, 1609, 225);
var ne = google.maps.geometry.spherical.computeOffset(place.geometry.location, 1609, 45);
swlat = sw.lat().toFixed(6);
swlng = sw.lng().toFixed(6);
nelat = ne.lat().toFixed(6);
nelng = ne.lng().toFixed(6);
Not sure this is of use to you. In my case the radius is fixed, but in your situation it sounds though it's a variable and hence you'll have to refer to it as a variable.
OK so a little edit: I've changed 1423 to 1000, which represents a lilometer radius. If you're using kilometers then 1000 is the number to use, but if you're using miles then use "1609.3440006" instead.
Thanks to #luke_mclachlan, I found the method I was looking for on the google map github.
https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/SphericalUtil.java
Here is the method I was looking for:
public static LatLng computeOffset(LatLng from, double distance, double heading) {
distance /= EARTH_RADIUS;
heading = toRadians(heading);
// http://williams.best.vwh.net/avform.htm#LL
double fromLat = toRadians(from.latitude);
double fromLng = toRadians(from.longitude);
double cosDistance = cos(distance);
double sinDistance = sin(distance);
double sinFromLat = sin(fromLat);
double cosFromLat = cos(fromLat);
double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading);
double dLng = atan2(
sinDistance * cosFromLat * sin(heading),
cosDistance - sinFromLat * sinLat);
return new LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng));
}
Note that Heart radius here is expected in meters.
Using this method, I find the southwest (heading = 225) and northeast (heading = 45) coordinates and then create my LatLongBounds object.
I’d like to take a series of samples of coordinates returned by GPS and calculate the (straight line) distance between them so I can graph the distances via Excel. I see the method distanceBetween and distanceTo of the Location class, but I’m concerned these don’t return the straight line distance.
Does anyone know what distance is returned by these methods or if there are any ways to calculate straight line distance based on latitude/longitude values returned by the Location class?
A Google search will offer solutions should you somehow desire to do this calculation yourself. For example the Haversine approach:
var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
is reported here. Note that this is straight line and does not account for irregularities in elevation, etc.
How can I measure distance and create a bounding box based on two latitude+longitude points in Java?
provides a Java implementation of the Haversine approach.
Here my code
float[] result=new float[1];
if(cordenatalar.size()>1)
{
LatLong add_kor=(LatLong)cordenatalar.get(cordenatalar.size()-1);
Location.distanceBetween(add_kor.getLat(), add_kor.getLongg(), location.getLatitude(), location.getLongitude(), result);
kilometr+=result[0];
//KMTextView.setText(String.valueOf(kilometr));
}
KMTextView.setText("sss: "+String.valueOf(cordenatalar.size()+" res: "+kilometr+" metr"));
cordenatalar.add(new LatLong(location.getLatitude(), location.getLongitude()));
source = starting location;
destination = current location;
/* this method will fetch you the distance between two geo points. */
source.distanceTo(destination);