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];
}
}
Related
I need to convert the results of calculations performed in a double, but I cannot use decimalNumberByMultiplyingBy or any other NSDecimalNumber function. I've tried to get an accurate result in the following ways:
double calc1 = 23.5 * 45.6 * 52.7; // <-- Correct answer is 56473.32
NSLog(#"calc1 = %.20f", calc1);
-> calc1 = 56473.32000000000698491931
NSDecimalNumber *calcDN = (NSDecimalNumber *)[NSDecimalNumber numberWithDouble:calc1];
NSLog(#"calcDN = %#", [calcDN stringValue]);
-> calcDN = 56473.32000000001024
NSDecimalNumber *testDN = [[[NSDecimalNumber decimalNumberWithString:#"23.5"] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithString:#"45.6"]] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithString:#"52.7"]];
NSLog(#"testDN = %#", [testDN stringValue]);
-> testDN = 56473.32
I understand that this difference is related to the respective accuracies.
But here's my question: How can I round this number in the most accurate way possible regardless of what the initial value of double may be? And if a more accurate method exists to do the initial calculation, what is that method?
Well, you can either use double to represent the numbers and embrace inaccuracies or use some different number representation, such as NSDecimalNumber. It all depends on what are the expected values and business requirements concerning accuracy.
If it is really crucial not to use arithmetic methods provided by NSDecimalNumber, than the rounding behaviour is best controlled using NSDecimalNumberHandler, which is a concrete implementation of NSDecimalNumberBehaviors protocol. The actual rounding is performed using decimalNumberByRoundingAccordingToBehavior: method.
Here comes the snippet - it's in Swift, but it should be readable:
let behavior = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundPlain,
scale: 2,
raiseOnExactness: false,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: false)
let calcDN : NSDecimalNumber = NSDecimalNumber(double: calc1)
.decimalNumberByRoundingAccordingToBehavior(behavior)
calcDN.stringValue // "56473.32"
I do not know of any method of improving the accuracy of the actual computations when using double representation.
I'd recommend rounding the number based on the number of digits in your double so that the NSDecimalNumber is truncated to only show the appropriate number of digits, thus eliminating the digits formed by potential error, ex:
// Get the number of decimal digits in the double
int digits = [self countDigits:calc1];
// Round based on the number of decimal digits in the double
NSDecimalNumberHandler *behavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundDown scale:digits raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO];
NSDecimalNumber *calcDN = (NSDecimalNumber *)[NSDecimalNumber numberWithDouble:calc1];
calcDN = [calcDN decimalNumberByRoundingAccordingToBehavior:behavior];
I've adapted the countDigits: method from this answer:
- (int)countDigits:(double)num {
int rv = 0;
const double insignificantDigit = 18; // <-- since you want 18 significant digits
double intpart, fracpart;
fracpart = modf(num, &intpart); // <-- Breaks num into an integral and a fractional part.
// While the fractional part is greater than 0.0000001f,
// multiply it by 10 and count each iteration
while ((fabs(fracpart) > 0.0000001f) && (rv < insignificantDigit)) {
num *= 10;
fracpart = modf(num, &intpart);
rv++;
}
return rv;
}
Is there a good solution to reverse geocode considering the horizontal accuracy of the location?
For instance this is the result for the location (and is regardless of horizontal accuracy):
{
City = "San Francisco";
Country = "United States";
CountryCode = US;
FormattedAddressLines = (
"246 Powell St",
"San Francisco, CA 94102-2206",
"United States"
);
Name = "246 Powell St";
PostCodeExtension = 2206;
State = CA;
Street = "246 Powell St";
SubAdministrativeArea = "San Francisco";
SubLocality = "Union Square";
SubThoroughfare = 246;
Thoroughfare = "Powell St";
ZIP = 94102;
}
I would like to get the result considering accuracy. E.g.:
accuracy: 10m; result: 246 Powell St
accuracy: 100m; result: Union Square
...
accuracy: 100km; result: San Francisco
I have figured I could probably do this by requesting reverse geocoding for multiple coordinates that are roughly on the edges of the provided horizontal accuracy and then intersecting the results. But is there a more clean way?
Did you mean CLGeocoder?
I think that requesting multiple reverse geocoding will probably do more harm then good.
From Apple's CLGeocoder documentation (which can be found here):
Applications should be conscious of how they use geocoding.
Geocoding requests are rate-limited for each app,
so making too many requests in a short period of time
may cause some of the requests to fail.
So I suggest not going this way.
Could be better ways, but just at the top of my head, you could probably use some sort of if-else method that uses the horizontalAccuracy property.
It's been a while since I've used MapKit so could be that my code below isn't 100% accurate but I can't test its functionality at the moment (it should compile though), but it'll give you an idea of how this can be done.
For example:
// here geocoder is your CLGeocoder object
// and location is the CLLocation you try to reverse geocode
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if(error) {
NSLog(#"Error occurred: %#", [error localizedDescription]);
} else { // No error has occurred
if([placemarks count]) { // Just another step of precaution
CLPlacemark *placemark = placemarks[0]; // assume the first object is our correct place mark
if(location.horizontalAccuracy <= 10) { // equal or less than 10 meters
NSLog(#"Result: %# %#", placemark.subThoroughfare, placemark.thoroughfare); // 246 Powell St
} else if (location.horizontalAccuracy <= 100) { // equal or less than 100 meters
NSLog(#"Result: %#", placemark.subLocality); // Union Square
} else if (location.horizontalAccuracy <= 100000) { // equal or less than 100km
NSLog(#"Result: %#", placemark.subAdministrativeArea); // San Francisco
}
}
}
}];
When I would be able to test its functionality myself (could be a couple of hours, or a couple of days) I would edit if changes needed to be made.
Let me know if you are having issues or you have more questions.
Good luck mate.
I am looking to write my own power function to work with NSDecimalNumbers and exponents that are not whole numbers. I first tried to use a combination of newtons method and the built in integer power method, but due to newtons method i am getting overflow errors when I have exponents with more than 2 decimals. So I thought maybe the float value pow function might serve as a good model for my own function. So I was wondering if anyone knows where I can fond some sort of documentation on the inner workings of the pow function?
Edit:
#wombat57, those links look like they could be what I am looking for however I have no idea to read them. The algorithm you suggest is in fact what I am using. the overflow comes from newtons method due to very large exponents. Because I am getting exponents in decimal form I have to convert it to a fraction first. the only way of ding this in code, as far as I know, multiplying the decimal by ten until you have a whole number, and using that as the numerator. Doing this you get exponents of 100+ for numbers with 3 or more decimals. this causes an overflow error.
EDIT 1: Here are links to the actual source
http://opensource.apple.com/source/Libm/Libm-2026/Source/Intel/expf_logf_powf.c
http://opensource.apple.com/source/Libm/Libm-315/Source/ARM/powf.c
I got the links from this question, which has a bunch of relevant discussion
self made pow() c++
This page describes an algorithm: Link.
x^(1/n) = the nth root of x, and x^mn = (x^m)^n. Thus, x^(m/n) = (the nth root of x)^m. Arbitrary roots can be calculated with Newton's method. Integer powers can be calculated with Exponentiation by squaring. For irrational exponents, you can use increasingly accurate rational approximations until you get the desired number of significant digits.
EDIT 2:
Newton's method involves raising your current guess to the power of the root that you're trying to find. If that power is large, and the guess is even a little too high, this can result in overflow. One solution here is to identify this case. If overflow ever occurs, this means that the guess was too high. You can solve the problem by (whenever a guess results in overflow), setting the current guess to a value between the last guess that did not overflow and the current guess (you may have to do this several times). That is, whenever Newton's method overflows, do a binary search down toward the last guess that did not overflow. Here's some python that implements all of this:
def nroot(n, b, sig_figs = 10):
g1 = 1.0
g2 = 1.0
while True:
done = False
while not done:
try:
g3 = g2 - ((g2**b) - n) / (b * (g2**(b-1)))
done = True
except OverflowError:
g2 = (g1 + g2) / 2.0
if abs(g2 - g3) < 1.0 / (10**sig_figs):
return g3
g1 = g2
g2 = g3
def npowbysqr(n, p):
if p == 0:
return 1.0
if p % 2 == 0:
v = npowbysqr(n, p/2)
return v*v
else:
return n*npowbysqr(n, p-1)
def npow(n, p):
return npowbysqr(nroot(n, 1000000), int(p*1000000))
print npow(5, 4.3467)
print 5**4.3467
I should add that there are probably much better solutions. This does seem to work, however
I happened to need something like this a while ago. Thankfully, Dave DeLong had been tinkering with this in his DDMathParser, so I built off of that. He yanked his implementation from his code in this commit, but I took that and modified it. This is my version of his NSDecimal power function:
extern NSDecimal DDDecimalPower(NSDecimal d, NSDecimal power) {
NSDecimal r = DDDecimalOne();
NSDecimal zero = DDDecimalZero();
NSComparisonResult compareToZero = NSDecimalCompare(&zero, &power);
if (compareToZero == NSOrderedSame) {
return r;
}
if (DDDecimalIsInteger(power))
{
if (compareToZero == NSOrderedAscending)
{
// we can only use the NSDecimal function for positive integers
NSUInteger p = DDUIntegerFromDecimal(power);
NSDecimalPower(&r, &d, p, NSRoundBankers);
}
else
{
// For negative integers, we can take the inverse of the positive root
NSUInteger p = DDUIntegerFromDecimal(power);
p = -p;
NSDecimalPower(&r, &d, p, NSRoundBankers);
r = DDDecimalInverse(r);
}
} else {
// Check whether this is the inverse of an integer
NSDecimal inversePower = DDDecimalInverse(power);
NSDecimalRound(&inversePower, &inversePower, 34, NSRoundBankers); // Round to 34 digits to deal with cases like 1/3
if (DDDecimalIsInteger(inversePower))
{
r = DDDecimalNthRoot(d, inversePower);
}
else
{
double base = DDDoubleFromDecimal(d);
double p = DDDoubleFromDecimal(power);
double result = pow(base, p);
r = DDDecimalFromDouble(result);
}
}
return r;
}
It tries to identify common cases and use more precise calculations for those. It does fall back on pow() for things that don't fit in these cases, though.
The rest of the NSDecimal functions I use can be found here and here.
I have come up with a function that suits my needs and will hopefully suit the needs of many others. the following method is fully annotated and works for any power function that has a real value. This method also only uses NSDecimalNumbers meaning you will not loose any precision due to float rounding error. This method takes two arguments one for the base and one for the power, and both are NSDecimalNumbers. So here it is:
//these are constants that will be used
NSDecimalNumber *ten = [NSDecimalNumber decimalNumberWithString:#"10"];
NSDecimalNumber *one = NSDecimalNumber.one;
//these will together hold the power in fractional form
NSDecimalNumber *numerator = power, *denominator = one;
//this will hold the final answer and all previous guesses the first guess is set to be the base
NSDecimalNumber *powAns = base;
//this will hold the change in your guess, also serves as an idea of how large the error is
NSDecimalNumber *error = one;
//part1 holds f(x) and part2 holds f'(x)
NSDecimalNumber *part1, *part2;
//if the base is < 0 and the power is not whole, answer is not real
if ([base doubleValue] < 0 && [[power stringValue] rangeOfString:#"."].location != NSNotFound)
return NSDecimalNumber.notANumber;
//converts power to a fractional value
while ([[numerator stringValue] rangeOfString:#"."].location != NSNotFound) {
numerator = [numerator decimalNumberByMultiplyingBy:ten];
denominator = [denominator decimalNumberByMultiplyingBy:ten];
}
//conditions here are the precision you wish to get
while ([error compare:[NSDecimalNumber decimalNumberWithString:#"1e-20"]] == NSOrderedDescending ||
[error compare:[NSDecimalNumber decimalNumberWithString:#"-1e-20"]] == NSOrderedAscending) {
//if this catches an overflow error it is set to be a very large number otherwise the value cannot be a number, however no other error should be returned.
#try {
part1 = [powAns decimalNumberByRaisingToPower:[denominator intValue]];
}
#catch (NSException *exception) {
if ([exception.name isEqual: NSDecimalNumberOverflowException])
part1 = [NSDecimalNumber decimalNumberWithString:#"10e127"];
else
return NSDecimalNumber.notANumber;
}
part1 = [part1 decimalNumberBySubtracting:base];
//if this catches an overflow error it is set to be a very large number otherwise the value cannot be a number, however no other error should be returned.
#try {
part2 = [powAns decimalNumberByRaisingToPower:[denominator intValue]-1];
part2 = [part2 decimalNumberByMultiplyingBy:denominator];
}
#catch (NSException *exception) {
if ([exception.name isEqual: NSDecimalNumberOverflowException])
part2 = [NSDecimalNumber decimalNumberWithString:#"10e127"];
else
return NSDecimalNumber.notANumber;
}
//error is the change in the estimated value or y - f(x)/f'(x)
error = [part1 decimalNumberByDividingBy:part2];
powAns = [powAns decimalNumberBySubtracting: error];
}
//if the numerator value is negative it must be made positive and the answer is then inverted
if ([numerator intValue] < 0) {
powAns = [powAns decimalNumberByRaisingToPower:abs([numerator intValue])];
powAns = [one decimalNumberByDividingBy:powAns];
}
else
powAns = [powAns decimalNumberByRaisingToPower:[numerator intValue]];
return powAns;
If anyone has any questions about my code I am happy to answer them.
I am taking masses (as g, mg, µg, ng & kg) and volumes (as ml, µl & l) as input to a chemistry app.
Currently I convert all masses to grams and volumes to litres, save in core data and perform any calculations as doubles.
Finally results are then converted back into a meaningful unit
ie 0.000034litres is more useful expressed as 34µl for my customers
What is best practice for working between different units?
There may be a few libraries out there but what you are doing is specific so I doubt there is a specific "best practice".
You may want to investigate the properties of NSDouble, CGFloat however since they might be more suited across devices and give you more options them the primitive double.
There are no unit data types or many built in native converter functions. A number is a number its up to the programmer to give that number meaning in the app's context.
I found a suitable engineering formatter at https://github.com/dhoerl/EngineeringNotationFormatter
In addition I created a simple version:
-(NSString *)engineeringFormat:(double)value digits:(int)digits {
//calculate exponent in step of 3
int perMill = trunc(log10(value)/3);
if (value<1) {
perMill -= 1;
}
//calculate mantissa format range of 1 to 1000
double corrected = value;
while (corrected<1) {
corrected = corrected*1000;
}
while (corrected>=1000) {
corrected=corrected/1000;
}
//format number of significant digits
NSNumberFormatter *numberFormatDigits = [[NSNumberFormatter alloc] init];
numberFormatDigits.usesSignificantDigits = YES;
numberFormatDigits.maximumSignificantDigits = digits;
NSString *mantissa = [numberFormatDigits stringFromNumber:[NSNumber numberWithDouble:corrected]];
//select engineering notation prefix
NSArray *engSuffix = #[#"T",#"G",#"M",#"k",#"",#"m",#"µ",#"n",#"p"];
int index = 4 - perMill;
NSString *result;
if ((index > engSuffix.count-1) || (index<0)) {
result = #"Out of range";
} else {
result = [NSString stringWithFormat:#"%# %#",mantissa,[engSuffix objectAtIndex:index]];
}
return result;
}
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.