I'm working on an iPhone app that is using GPS coordinates for leaderboards. I don't need the coordinates to be exact --- actually I don't ever want the coordinates to be exact, to protect user privacy.
I am specifying kCLLocationAccuracyThreeKilometers for desiredAccuracy, but when the GPS is active it seems it can also pick up the exact location when the device has it.
QUESTION: Is there any easy algorithm I can use to make the GPS data more coarse? Say, make it granular to 3km.
If I just scale the numbers up and remove decimal points and scale them down again it will make it more coarse in some parts of the world than others.
Thanks!
While Mark's answer above was a useful, it still did not yield a formula with consistent results because it relied on a random number generator.
My buddy provided the best answer for this:
Round the lat,lon to the nearest significant figure depending on the granularity, but this would result in all the lat/lons near a certain location, wind up in the same location. This method would use the distance between two points in lat/lon to calculate the rounding of the lat lon. Use the same formula below and set the course to 0, then the distance is your distance granularity. Calculate the resulting new lat/lon subtract the two lat/lons to get the rounding amount for lat. Then set the heading to 90 and recalculate and subtract the new lat/lon from the old to get the rounding amount for lon.
And here's the C++ code:
class LocationUtility
{
public: static Location getLocationNow()
{
Location location;
if(context != null)
{
double latitude = 0;
double longitude = 0;
::of_getCurrentLocation(&latitude, &longitude);
location.setLatitude(latitude);
location.setLongitude(longitude);
location = makeLocationCoarse(location);
}
return location;
}
public: static Location makeLocationCoarse(const Location& location)
{
double granularityInMeters = 3 * 1000;
return makeLocationCoarse(location, granularityInMeters);
}
public: static Location makeLocationCoarse(const Location& location,
double granularityInMeters)
{
Location courseLocation;
if(location.getLatitude() == (double)0 &&
location.getLongitude() == (double)0)
{
// Special marker, don't bother.
}
else
{
double granularityLat = 0;
double granularityLon = 0;
{
// Calculate granularityLat
{
double angleUpInRadians = 0;
Location newLocationUp = getLocationOffsetBy(location,
granularityInMeters, angleUpInRadians);
granularityLat = location.getLatitude() -
newLocationUp.getLatitude();
if(granularityLat < (double)0)
{
granularityLat = -granularityLat;
}
}
// Calculate granularityLon
{
double angleRightInRadians = 1.57079633;
Location newLocationRight = getLocationOffsetBy(location,
granularityInMeters, angleRightInRadians);
granularityLon = location.getLongitude() -
newLocationRight.getLongitude();
if(granularityLon < (double)0)
{
granularityLon = -granularityLon;
}
}
}
double courseLatitude = location.getLatitude();
double courseLongitude = location.getLongitude();
{
if(granularityLon == (double)0 || granularityLat == (double)0)
{
courseLatitude = 0;
courseLongitude = 0;
}
else
{
courseLatitude = (int)(courseLatitude / granularityLat) *
granularityLat;
courseLongitude = (int)(courseLongitude / granularityLon) *
granularityLon;
}
}
courseLocation.setLatitude(courseLatitude);
courseLocation.setLongitude(courseLongitude);
}
return courseLocation;
}
// http://www.movable-type.co.uk/scripts/latlong.html
private: static Location getLocationOffsetBy(const Location& location,
double offsetInMeters, double angleInRadians)
{
Location newLocation;
double lat1 = location.getLatitude();
double lon1 = location.getLongitude();
lat1 = deg2rad(lat1);
lon1 = deg2rad(lon1);
double distanceKm = offsetInMeters / (double)1000;
const double earthRadiusKm = 6371;
double lat2 = asin( sin(lat1)*cos(distanceKm/earthRadiusKm) +
cos(lat1)*sin(distanceKm/earthRadiusKm)*cos(angleInRadians) );
double lon2 = lon1 +
atan2(sin(angleInRadians)*sin(distanceKm/earthRadiusKm)*cos(lat1),
cos(distanceKm/earthRadiusKm)-sin(lat1)*sin(lat2));
lat2 = rad2deg(lat2);
lon2 = rad2deg(lon2);
newLocation.setLatitude(lat2);
newLocation.setLongitude(lon2);
return newLocation;
}
private: static double rad2deg(double radians)
{
static double ratio = (double)(180.0 / 3.141592653589793238);
return radians * ratio;
}
private: static double deg2rad(double radians)
{
static double ratio = (double)(180.0 / 3.141592653589793238);
return radians / ratio;
}
/*
public: static void testCoarse()
{
Location vancouver(49.2445, -123.099146);
Location vancouver2 = makeLocationCoarse(vancouver);
Location korea(37.423938, 126.692488);
Location korea2 = makeLocationCoarse(korea);
Location hiroshima(34.3937, 132.464);
Location hiroshima2 = makeLocationCoarse(hiroshima);
Location zagreb(45.791958, 15.935786);
Location zagreb2 = makeLocationCoarse(zagreb);
Location anchorage(61.367778, -149.900208);
Location anchorage2 = makeLocationCoarse(anchorage);
}*/
};
This is very similar to a previous question
Rounding Lat and Long to Show Approximate Location in Google Maps
If you assume the earth is a sphere (probably adequate for this problem), then you just need to calculate a location which is a certain angular distance from the given latitude and longitude. Pick a distance and a (random) direction, and calculate the new location by using the distance formula.
There's good discussion of the opposite problem (distance between two latitude/longitude points) here: Link
It ought to be relatively straightforward to go from there to finding a point a specified distance away from the given point.
The answer from swinefeaster is ok, but there is no need for such complex maths. If you're rounding to a grid, then the latitude changes by constant amounts at all points on the planet. Longitude changes by different amounts according to how far you are from the equator.
The following code snaps latitude and longitude to an arbitrary grid size
double EARTH_RADIUS_KM = 6371;
double GRID_SIZE_KM = 1.6; // <----- Our grid size in km..
double DEGREES_LAT_GRID = Math.toDegrees(GRID_SIZE_KM / EARTH_RADIUS_KM);
// ^^^^^^ This is constant for a given grid size.
public Location snapToGrid(Location myLoc) {
double cos = Math.cos(Math.toRadians(myLoc.latitude));
double degreesLonGrid = DEGREES_LAT_GRID / cos;
return new Location (
Math.round(myLoc.longitude / degreesLonGrid) * degreesLonGrid,
Math.round(myLoc.latitude / DEGREES_LAT_GRID) * DEGREES_LAT_GRID);
}
Note that this will fail in the case where you are at the Pole (when the cos function approaches zero). Depending on your grid size, the results become unpredictable as you approach a latitude of +/- 90 degrees. Handling this is an exercise left for the reader :)
I try to implemente the solution in Ruby but in my case, the coarse coordinate vs real have a huge difference. the coarse coordinate only change when the lat change but when lat stay the same and long move, coarse remain the same. In case someone can check the code below, perhaps I made a bad coding.
class CoarseLocation
AREA_LIMIT = 1000
class << self
def make_location_coarse(lat, lon)
if lat.nil? && lon.nil?
raise InvalidParamsError
end
location = [lat.to_f, lat.to_f]
new_location_up = get_location_by_offset(location, AREA_LIMIT, 0)
granularityLat = location[0] - new_location_up[0]
if granularityLat < 0
granularityLat = -granularityLat
end
new_location_right = get_location_by_offset(location, AREA_LIMIT, 1.57079633)
granularityLon = location[1] - new_location_right[1]
if(granularityLon < 0)
granularityLon = -granularityLon
end
course_lat = location[0]
course_lon = location[1]
if(granularityLat == 0.0) || (granularityLon == 0.0)
course_lat = 0
course_lon = 0
else
course_lat = (course_lat / granularityLat).to_i * granularityLat
course_lon = (course_lon / granularityLon).to_i * granularityLon
end
[course_lat, course_lon]
end
def get_location_by_offset(location, offset, angle)
lat_radius = location[0] * Math::PI / 180
lon_radius = location[1] * Math::PI / 180
distance = (offset / 1000).to_f
earth_radius = 6371
lat_radius_1 = (Math::asin( Math::sin(lat_radius) * Math::cos(distance/earth_radius) + Math::cos(lat_radius) * Math::sin(distance/earth_radius) * Math::cos(angle))).to_f
lon_radius_1 = (lon_radius + Math::atan2(Math::sin(angle)*Math::sin(distance/earth_radius)*Math::cos(lat_radius), Math::cos(distance/earth_radius) - Math::sin(lat_radius)*Math::sin(lat_radius_1))).to_f
new_lat = lat_radius_1 * 180 / Math::PI
new_lon = lon_radius_1 * 180 / Math::PI
return [new_lat.to_f, new_lon.to_f]
end
end
end
Location field is always ab array of 2 elements in which [0] is lat and [1] is long.
Related
I want to check if anyone enter in allocated boundary then i have to alert that user like "You are entered" and when user leaves then "You left". I am using .KML file for draw boundary in which there are more than latitude and longitude. Here i attached screenshot for the same.So, my concern is that how can i detect that anyone is entered within this boundary and left from that boundary. Thank you in advance
Boundary looks like this.Red color line is boundary.
Use map rects. Here's an example using the map's current visible rect. With regards to your question, you could use convertRegion:toRectToView: to first convert your region to a MKMapRect beforehand.
MKMapPoint userPoint = MKMapPointForCoordinate(mapView.userLocation.location.coordinate);
MKMapRect mapRect = mapView.visibleMapRect; // find visible map rect
//MKMapRect mapRect = [self getMapRectUsingAnnotations:arrCordinate];//find custom map rect
BOOL inside = MKMapRectContainsPoint(mapRect, userPoint);
MKMapRect mapRect = mapView.visibleMapRect;
Create your custom mapRect using your boundary region from multiple latitude and longitude
- (MKMapRect) getMapRectUsingAnnotations:(NSArray*)arrCordinate {
MKMapPoint points[[arrCordinate count]];
for (int i = 0; i < [arrCordinate count]; i++) {
points[i] = MKMapPointForCoordinate([arrCordinate[i] MKCoordinateValue]);
}
MKPolygon *poly = [MKPolygon polygonWithPoints:points count:[arrCordinate count]];
return [poly boundingMapRect];
}
Geofencing will not work on complex polygon shaped regions. May be you can solve the problem with some other approach. For instance divide the region into smaller CLCircularRegion and then develop aggregate logic for the case where you have to show notification for all those locationManager:didEnterRegion: and locationManager:didExitRegion: callbacks. But keep in mind that only a max of 20 simultaneous monitored regions per app are allowed.
Refer https://forums.developer.apple.com/thread/21323 phillippk1 suggestion for other possible approach.
Try this code. This is based on Winding Number Algorithm. This works for complex shapes such as your red line.
typedef struct {
double lon;
double lat;
} LATLON;
// returns true if w/in region
bool chkInRegion(LATLON poi, int npoi, LATLON *latlon)
{
int wn = 0;
for (int i = 0 ; i < npoi-1 ; i++) {
if (latlon[i].lat <= poi.lat && latlon[i+1].lat > poi.lat) {
double vt = (poi.lat - latlon[i].lat)/(latlon[i+1].lat - latlon[i].lat);
if (poi.lon < (latlon[i].lon + (vt * (latlon[i+1].lon - latlon[i].lon)))) {
wn++;
}
} else if (latlon[i].lat > poi.lat && latlon[i+1].lat <= poi.lat) {
double vt = (poi.lat - latlon[i].lat)/(latlon[i+1].lat - latlon[i].lat);
if (poi.lon < (latlon[i].lon + (vt * (latlon[i+1].lon - latlon[i].lon)))) {
wn--;
}
}
}
return wn < 0;
}
// test data
LATLON llval[] = {
{100,100},
{200,500},
{600,500},
{700,100},
{400,300},
{100,100}
};
#define NLATLON (sizeof(llval)/sizeof(LATLON))
int main(int argc, char **argv) {
while (1) {
char buf[1024];
fprintf(stderr, "lon = ");
fgets(buf, sizeof(buf), stdin);
double lon = atof(buf);
fprintf(stderr, "lat = ");
fgets(buf, sizeof(buf), stdin);
double lat = atof(buf);
LATLON ltest;
ltest.lat = lat;
ltest.lon = lon;
if (chkInRegion(ltest, NLATLON, llval)) {
fprintf(stderr, "\n*** in region ***\n\n");
} else {
fprintf(stderr, "\n=== outside ===\n\n");
}
}
return 0;
}
I am re-writing the particle filter library of iOS in Swift from Objective C which is available on Bitbucket and I have a question regarding a syntax of Objective C which I cannot understand.
The code goes as follows:
- (void)setRssi:(NSInteger)rssi {
_rssi = rssi;
// Ignore zeros in average, StdDev -- we clear the value before setting it to
// prevent old values from hanging around if there's no reading
if (rssi == 0) {
self.meters = 0;
return;
}
self.meters = [self metersFromRssi:rssi];
NSInteger* pidx = self.rssiBuffer;
*(pidx+self.bufferIndex++) = rssi;
if (self.bufferIndex >= RSSIBUFFERSIZE) {
self.bufferIndex %= RSSIBUFFERSIZE;
self.bufferFull = YES;
}
if (self.bufferFull) {
// Only calculate trailing mean and Std Dev when we have enough data
double accumulator = 0;
for (NSInteger i = 0; i < RSSIBUFFERSIZE; i++) {
accumulator += *(pidx+i);
}
self.meanRssi = accumulator / RSSIBUFFERSIZE;
self.meanMeters = [self metersFromRssi:self.meanRssi];
accumulator = 0;
for (NSInteger i = 0; i < RSSIBUFFERSIZE; i++) {
NSInteger difference = *(pidx+i) - self.meanRssi;
accumulator += difference*difference;
}
self.stdDeviationRssi = sqrt( accumulator / RSSIBUFFERSIZE);
self.meanMetersVariance = ABS(
[self metersFromRssi:self.meanRssi]
- [self metersFromRssi:self.meanRssi+self.stdDeviationRssi]
);
}
}
The class continues with more code and functions which are not important and what I do not understand are these two lines
NSInteger* pidx = self.rssiBuffer;
*(pidx+self.bufferIndex++) = rssi;
Variable pidx is initialized to the size of a buffer which was previously defined and then in the next line the size of that buffer and buffer plus one is equal to the RSSI variable which is passed as a parameter in the function.
I assume that * has something to do with reference but I just can't figure out the purpose of this line. Variable pidx is used only in this function for calculating trailing mean and standard deviation.
Let explain those code:
NSInteger* pidx = self.rssiBuffer; means that you are getting pointer of the first value of the buffer.
*(pidx+self.bufferIndex++) = rssi; means that you are setting the value of the buffer at index 0+self.bufferIndex to rssiand then increase bufferIndex by 1. Thanks to #Jakub Vano point it out.
In C++, it will look like that
int self.rssiBuffer[1000]; // I assume we have buffer like that
self.rssiBuffer[self.bufferIndex++] = rssi
I am using the NSLengthFormatter class to format the distance between the user and some destination.
CLLocation *userLocation; //<- Coordinates fetched from CLLocationManager
CLLocation *targetLocation; //<- Some location retrieved from server data
CLLocationDistance distance = [userLocation distanceFromLocation:targetLocation];
NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
NSString *formattedLength = [lengthFormatter stringFromMeters:distance];
Now, if the length is less than 1000 meters, the formatted distance is always shown in yards or meters (depending on the locale).
Eg. if distance = 450.0, the formatted string will be 492.7 yd or 450 m.
How can I tweak NSLengthFormatter to return the distance strings in miles/kilometers only?
This is what I have ended up using:
-(NSString *)formattedDistanceForMeters:(CLLocationDistance)distance
{
NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
[lengthFormatter.numberFormatter setMaximumFractionDigits:1];
if ([[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue])
{
return [lengthFormatter stringFromValue:distance / 1000 unit:NSLengthFormatterUnitKilometer];
}
else
{
return [lengthFormatter stringFromValue:distance / 1609.34 unit:NSLengthFormatterUnitMile];
}
}
EDIT:
The same in Swift would look like:
func formattedDistanceForMeters(distance:CLLocationDistance) -> String {
let lengthFormatter:NSLengthFormatter! = NSLengthFormatter()
lengthFormatter.numberFormatter.maximumFractionDigits = 1
if NSLocale.currentLocale().objectForKey(NSLocaleUsesMetricSystem).boolValue()
{
return lengthFormatter.stringFromValue(distance / 1000, unit:NSLengthFormatterUnitKilometer)
}
else
{
return lengthFormatter.stringFromValue(distance / 1609.34, unit:NSLengthFormatterUnitMile)
}
}
There doesn't seem a way to opt out of this behaviour. To be honest, your requirement is not very common from UX perspective.
Note that meter is the base unit, not a kilometer (a thousand of meters). Usually, displaying 10 meters is preferred over displaying 0.01 kilometers. It's just more friendly for the users.
It would be actually very hard to design an API that would enforce a specific unit considering that the base unit depends on current locale.
You can enforce a specific unit using:
- (NSString *)unitStringFromValue:(double)value unit:(NSLengthFormatterUnit)unit;
but you will have to handle the locale and scaling & unit conversion by yourself (see Objective c string formatter for distances)
It's actually a very common requirement for people not using metric system (Yes I know...).
In metric system it just makes sense to go from Kilometers to Meters, etc. If you follow the same logic with the imperial system you'll go from Miles to Yards to Foot.
Usually you don't want to use Yards for road distances for example and you don't want to display 5,000 ft but 0.9 mi (Actually Google Maps display in feet up to 0.1 miles or 528 feet, and then in miles).
let distanceAsDouble = 415.0
let lengthFormatter = LengthFormatter()
if Locale.current.usesMetricSystem {
return distanceFormatter.string(fromMeters: distanceAsDouble)
} else {
let metersInOneMile = Measurement<UnitLength>(value: 1.0, unit: .miles).converted(to: .meters).value
if distanceAsDouble < metersInOneMile / 10 { // Display feets from 0 to 0.1 mi, then miles
let distanceInFeets = Measurement<UnitLength>(value: distanceAsDouble, unit: .meters).converted(to: .feet).value
return distanceFormatter.string(fromValue: distanceInFeets, unit: .foot)
} else {
return distanceFormatter.string(fromValue: distanceAsDouble / metersInOneMile, unit: .mile)
}
}
-(NSString *)formattedDistance {
CLLocationDistance distance = _distance;
NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
[lengthFormatter.numberFormatter setMaximumFractionDigits:1];
if ([[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue])
{
return [lengthFormatter stringFromValue:distance / 1000 unit:NSLengthFormatterUnitKilometer];
}
else
{
return [lengthFormatter stringFromValue:distance / 1609.34 unit:NSLengthFormatterUnitMile];
}
}
Hi aim using following method to find out the distance between my current location and the locations(lat and longitude) stored in NSMutableArray value.
But am getting wrong distance..Please help..
My code
-(void)getDistancetoShow:(NSMutableArray *)newArray{
CLLocationCoordinate2D coordinateUser = [self getLocation];
float _lat,_long;
first_Loc = [[CLLocation alloc] initWithLatitude:coordinateUser.latitude longitude:coordinateUser.longitude];
for(int p=0;p<[[newArray1 valueForKey:#"Name"] count];p++){
_lat=[[[newArray1 valueForKey:#"Latitude"] objectAtIndex:p]floatValue];
_long=[[[newArray1 valueForKey:#"Longitude"] objectAtIndex:p]floatValue];
second_loc=[[CLLocation alloc] initWithLatitude:_lat longitude:_long];
showDistance=[second_loc distanceFromLocation:first_Loc]/1000;
[distanceToDispaly addObject:[NSString stringWithFormat:#"%.2f KM",showDistance]];
}
NSLog(#"first=%#, second=%#", first_Loc, second_loc);
}
Latitudes in array
(
"47.0735010448824",
"47.0564688100431",
" 47.0582514311038",
"47.0587640538326",
"47.0569233603454",
"47.0541853132569",
"47.0542029215138",
"47.0544259594592",
"47.0560264547367",
" 47.0576532159776",
" 47.0550023679218",
"47.0342030007379",
"47.0746263896213",
" 47.0740256635512",
"47.0524765957921",
"47.0606287049051",
"47.0539691521825",
"47.0542799159057",
"47.0651001682846",
"47.0536948902097",
"47.0525973335309",
"47.0389265414812",
"47.0761811267051",
"47.0668801601942",
"47.0614859079241",
"47.0579433468181",
"47.0718998779465"
)
and longitude in array
(
"21.9154175327011",
"21.9312065669748",
"21.9337414545594",
" 21.9346772505188",
" 21.9300587945685",
"21.9363460105132",
"21.9362081709222",
"21.9343042603097",
"21.939485335992",
"21.9320057169724",
"21.9300799002643",
"21.9485373571669",
"21.9310667367526",
"21.9318507902135",
"21.9192195298473",
"21.9195273899529",
"21.9329595191441",
"21.9292015418841",
"21.9219452321208",
"21.9098849252041",
"21.9074768948561",
"21.9424499491422",
"21.9151458954504",
"21.9304346568769",
"21.9305973807911",
"21.9331511189507",
"21.9159872752442"
)
but the real distance in something like staring with 9**** but am getiing now 5***
CLLocation gives you crow(straight) distance between two places. I thinks you are getting crow distance.
First take coordinate of two places and find distance between them.
then search crow distance between those two coordinate.
hope this will help
Once you get the two coordinates you can calculate the distance between them using this piece of code (taken from here):
- (NSNumber*)calculateDistanceInMetersBetweenCoord:(CLLocationCoordinate2D)coord1 coord:(CLLocationCoordinate2D)coord2 {
NSInteger nRadius = 6371; // Earth's radius in Kilometers
double latDiff = (coord2.latitude - coord1.latitude) * (M_PI/180);
double lonDiff = (coord2.longitude - coord1.longitude) * (M_PI/180);
double lat1InRadians = coord1.latitude * (M_PI/180);
double lat2InRadians = coord2.latitude * (M_PI/180);
double nA = pow ( sin(latDiff/2), 2 ) + cos(lat1InRadians) * cos(lat2InRadians) * pow ( sin(lonDiff/2), 2 );
double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA ));
double nD = nRadius * nC;
// convert to meters
return #(nD*1000);
}
Hope this helps!
To get distance from array of points in Swift use below reduce method.
Here locations is array of type CLLocation.
let calculatedDistance = locations.reduce((0, locations[0])) { ($0.0 + $0.1.distance(from: $1), $1)}.0
I have coordinates like this:
<googlemapsX>32.733611</googlemapsX>
<googlemapsY>-117.189722</googlemapsY>
And i want to show them like this:
String document = "<lbs>
<location lat='LAT' lon='LON'/>
</lbs>";
But LAT and LON must be integers, how to convert them?
To convert them into the lat/lon that BlackBerry uses in its document, you can just multiply them by 100000 to get the correct value.
int lat = 32.733611 * 100000;
int lon = -117.189772 * 100000;
And use those values as your LAT and LON
lat, long can be values like 32.733611, -117.189772 (double values)
int latitude = (int) (lat * 1e6);
int longitude = (int) (long * 1e6);