I have several thousand locations stored in CoreData and I would like to search for locations that are within a Google Maps visibleRegion. I was previously doing a search with a bounding box but the addition of the bearing feature breaks this type of query. I have several ideas but this must be a common problem with some well thought out solutions. I'd be interested to see if any solutions use geohashes.
This is my query that breaks when the bearing is not due north.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(lat > %f AND lat < %f AND lng > %f AND lng < %f)",
[self.googleMap.projection visibleRegion].nearLeft.latitude,
[self.googleMap.projection visibleRegion].farLeft.latitude,
[self.googleMap.projection visibleRegion].nearLeft.longitude,
[self.googleMap.projection visibleRegion].nearRight.longitude
];
You can calculate an axis-aligned bounding box of the visible region, and then use that to look up your locations. Some of them will still be outside of the actual visible area, but at least you'll filter out most of them.
The GMSCoordinateBounds class in GMSCoordinateBounds.h can be used to make this easier:
GMSMapView* _mapView = ...;
GMSCoordinateBounds* bounds =
[[GMSCoordinateBounds alloc]
initWithRegion: [_mapView.projection visibleRegion]];
CLLocationCoordinate2D northEast = bounds.northEast;
CLLocationCoordinate2D southWest = bounds.southWest;
Note also that there is currently a bug with visibleRegion being too large, see here:
https://code.google.com/p/gmaps-api-issues/issues/detail?id=5107
See here for a workaround to that problem:
Google Maps iOS SDK: How do I get accurate latitude and longitude coordinates from a camera's visibleRegion?
Related
I'm creating an iPhone app with Weather lookup for particular locations and I have the following problem that i'm not sure the best way to tackle.
I have the latitude and longitude of a location and want to find the closest lat/long match from a list of 5000+ locations
The 5000+ locations come from a JSON feed from Met Office Datapoint API and are in the form of a NSArray of NSDictionaries, the NSDictionary includes id, lat, long and name.
I want to match my location to the nearest location from the list from the Met Office and grab the id key value.
Many Thanks in advance
I'm assuming you're using CLLocation objects in this...
- (CLLocation*)closestLocationToLocation:(CLLocation*)currLocation
{
CLLocationDistance minDistance;
CLLocation *closestLocation = nil;
for (CLLocation *location in arrayOfLocations) {
CLLocationDistance distance = [location distanceFromLocation:currLocation];
if (distance <= minDistance
|| closestLocation == nil) {
minDistance = distance;
closestLocation = location;
}
}
//closestLocation is now the location from your array which is closest to the current location or nil if there are no locations in your array.
return closestLocation;
}
There may be a quicker way of doing this but this will get it done.
EDITED to use CLLocation functions
I did a similar thing once (finding all lat/lon Objects surrounding a point with a max. radius) and used the formula given here:
http://www.movable-type.co.uk/scripts/latlong.html
However, that was quite time consuming. So I sort of "boxed" the objects fist. Based on the calculation above (reverted of course) I calculated the latitude and longitude of those coordinates north, west, south and east that had exactly the maximum distance. With hose max and min values (for lat and lon) I queried all objects in question. And only for those I calculated the exact distance and included them in the list of results or excluded them.
However, so far that does not exactly match your problem. But I tried to fasten the calculations even further. For that I said to myself, that I do not need the exact distance from the searched object to mine but it is enogh to know wether it is closer than one of the boxes coordinates. And that part is the one that corresponds well to your question:
Your case could be much easier. Assuming that the locations in question (the shortest once) are close to the one location which you try to assign, all this complex math may not play a role. You do not need the exact distance. What you need is the cosest one. For that I would assume that the earth is flat and that the distances between longitudes (or latitude) are linear. That is not true of course but should be good enough to figure out, which of those is the closest.
Going from there you could use pythagoras.
Distance = sqrt(sqr(difference-in-lat) + sqr(difference-in-lon));
For the mere purpose of comparing the distances and finding the shortest, you could even replace the time consuming square route with a much faster sqare operation.
Square-Of-Distance = sqr(difference-in-lat) + sqr(difference-in-lon).
And then compare the various Square-Of-Distance rather than the Distance. The result will be the same but much faster.
BTW, that was a PHP projects. That's why I cannot provide sample code but just explain the algorihm.
I'd suggest something like this:
NSMutableArray *tempArray = [NSMutableArray new];
for (NSMutableDictionary *location in yourArrayOfLocations){
CLLocation coord;
coord.latitude = [location objectForKey:#"latitude"];
coord.longitude = [location objectForKey:#"longitude"];
[location setValue:[usersLocation distanceFromLocation:coord] forKey:#"distance"];
[tempArray addObject:location];
}
// Now sort the array
NSArray *sortedArray = [tempArray sortedArrayUsingComparator:^(id o1, id o2) {
NSDictionary *location1 = (NSDictionary *)o1;
NSDictionary *location2 = (NSDictionary *)o2;
return [[location1 objectForKey:#"distance"] compare:[location2 objectForKey:#"distance"]];
}];
[tempArray release];
Now you have an array ordered by distance. You can use the object at index 0, as it is the closest to the user's position.
Good Luck!
I have some locations ( in this case >3000 ) stored with Core Data. Once I open the map, I fetch the locations and store them in an array. Each time the mapview region is changed I call a function which will calculate which annotations are visible in the current visibleMaprect and filter them by pixel-distance. ( I know there would be more complex optimizations, like quadtrees, but I would not really implement it right now, if it's not extremely necessary ).
This is my code :
//locations is an array of NSManagedObjects
for (int i =0 ; i < [locations count]; i++)
{
// managed object class for faster access, valueforkey takes ages ...
LocationEntity * thisLocation = [locations objectAtIndex:i];
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake( [thisLocation.latitude doubleValue], [thisLocation.longitude doubleValue]) ;
// mapRect is mapView.visibleMapRect
BOOL isOnScreen = MKMapRectContainsPoint(mapRect, MKMapPointForCoordinate(coord));
if (isOnScreen)
{
CGPoint cgp = [mapView convertCoordinate:coord toPointToView:mapView];
// compare the distance to already existing annotations
for (int idx = 0; idx < [annotations count] && hasEnoughDistance; idx++)
{
CGPoint cgp_prev = [mapView convertCoordinate:[[annotations objectAtIndex:idx] coordinate] toPointToView:mapView];
if ( getDist(cgp, cgp_prev) < dist ) hasEnoughDistance = FALSE;
}
}
if (hasEnoughDistance)
// if it's ok, create the annotation, add to an array and after the for add all to the map
}
The map is freezing for a few seconds after each zoom/movement.
I checked with time profiler and the simple obtainment of coordinates is taking sometimes 1 whole second, sometimes just 0.1, even though the coordinates are indexed attributes in my model... Also these type of lines seem to take ages :
CGPoint cgp = [mapView convertCoordinate:coord toPointToView:mapView];
Any suggestions how could I calculate the pixel/point distance between two annotations/coordinates without going through this function ? Or any optimization suggestions for Core Data?
Thanks :)
Ok, I sort of missed the not having them too close bit from your explanation. The conversion between the coordinates is very slow. The way you can alleviate it is to precompute the coordinates into map points with MKMapPointForCoordinate and store them persistently - they only depend on the coordinates. Then you can quickly calculate the distance between the map points of two annotations, scale it depending on your current zoom level of the map and this will quite closely relate to the actual distance on the screen. It should be accurate enough and will be much faster.
I would recommend calculating the squared distance and comparing it to squared dist. You would be saving a lot on the sqrt().
If you still get boggled down on getDist() (or getSqDist()) you could either go for a kd tree or use the Accelerate Framework to do the calculations. I've done the latter when I needed to calculate distances between many points and the speedup was very good. But the details of this is an another cup of tea. Let me know if you need any help with that.
The fact that your coordinates are indexed would only help if you actually searched for annotations by the coordinates, so it won't help if you just look through all of them.
A way of dealing with long loading times from CoreData would be to try making your annotations as lightweight as possible, so only storing the coordinates and map points. Then you could have a way of getting the rest of the annotation data as needed. This could be done with the proxy pattern.
One more thing. Fast enumeration might be faster and is better practice as well, so
for(LocationEntity* thisLocation in locations)
instead of
for (int i =0 ; i < [locations count]; i++)
Say I have a square which consists of four CLLocationCoordinate2D points, which are in lat, lon, and I want to find the area of the square in meters. I convert the CLLocationCoordinate2D points into MKMapPoints, and I find the area in X-Y space. However, the area I find is in the units of MKMapPoint, which don't directly translate to meters. How can I translate this area in MKMapPoint-space back into meters?
The MapKit function MKMetersBetweenMapPoints makes this easier.
For example, if you wanted to get the area of the currently displayed region:
MKMapPoint mpTopLeft = mapView.visibleMapRect.origin;
MKMapPoint mpTopRight = MKMapPointMake(
mapView.visibleMapRect.origin.x + mapView.visibleMapRect.size.width,
mapView.visibleMapRect.origin.y);
MKMapPoint mpBottomRight = MKMapPointMake(
mapView.visibleMapRect.origin.x + mapView.visibleMapRect.size.width,
mapView.visibleMapRect.origin.y + mapView.visibleMapRect.size.height);
CLLocationDistance hDist = MKMetersBetweenMapPoints(mpTopLeft, mpTopRight);
CLLocationDistance vDist = MKMetersBetweenMapPoints(mpTopRight, mpBottomRight);
double vmrArea = hDist * vDist;
The documentation states that the function takes "into account the curvature of the Earth."
You can use the Haversine formula to calculate it, assuming that the earth is a perfect sphere.
To understand how lat/lon vs meters works in the context of the earth, you may find it interesting to read about Nautical miles.
You can find some more resources and some sample code by googling objective-c Haversine formula.
Enjoy!
When I debug code below, I see that span is changed by mapkit from what I have provided. span2 contains different numbers from what was provided. Why is this happening?
- (void) viewDidLoad
{
[super viewDidLoad];
CLLocationCoordinate2D loc;
loc.latitude = self.atm.lat;
loc.longitude = self.atm.lon;
MKCoordinateSpan span1 = MKCoordinateSpanMake(0.05f, 0.05f);
self.mapView.region = MKCoordinateRegionMake(loc, span1);
// at this point numbers are not 0.05 anymore
MKCoordinateSpan span2 = self.mapView.region.span;
// ... more code
}
Note that latitude and longitude change differently so square MapView will actually display region with different span values for its coordinates. This is likely to cause your mapview region to have span (slightly?) different from what you set.
In reference for MKMapView's region property there's somewhat relevant phrase:
Changing only the center coordinate of the region can still
cause the span to change implicitly.
This is due to the fact that the
distances represented by a span change
at different latitudes and longitudes
and the map view may need to adjust
the span to account for the new
location.
A square MapView with identical values for the latitudinal and longitudinal span will almost always experience the change in span indicated above as longitudinal arc-length varies significantly as you move from the equator to either pole. As the latitude approaches +/-90, the longitudinal arc-length approaches 0.
In addition, however, identical span values will also change if the MapView region is not square (as the span only refers to the region actually visible at the time).
as title how to? i have tried the code from google earth, but seem like the result is different with the google map calculation result. below provided the code i did
-(double)GetDistance:(double)lat1 long1:(double)lng1 la2:(double)lat2 long2:(double)lng2 {
//NSLog(#"latitude 1:%.7f,longitude1:%.7f,latitude2:%.7f,longtitude2:%.7f",lat1,lng1,lat2,lng2);
double radLat1 = [self rad:lat1];
double radLat2 = [self rad:lat2];
double a = radLat1 - radLat2;
double b = [self rad:lng1] -[self rad:lng2];
double s = 2 * asin(sqrt(pow(sin(a/2),2) + cos(radLat1)*cos(radLat2)*pow(sin(b/2),2)));
s = s * EARTH_RADIUS;
s = round(s * 10000) / 10000;
return s;
}
-(double)rad:(double)d
{
return d *3.14159265 / 180.0;
}
the EARTH_RADIUS value is 6378.138
by using this function by provided two coordinates the result come out is 4.5kM
but when i use google map get direction between two same coordinates, it show me the distance is about 8km
can anyone help to point out the problem of my code?
Since this is tagged iPhone, why not use the built-in distance function rather than rolling your own? location1 and location2 are CLLocation objects.
CLLocationDistance distance = [location1 getDistanceFrom:location2];
Here is a simple code (supposing you just have latitude and longitude of the two points)
CLLocation *startLocation = [[CLLocation alloc] initWithLatitude:startLatitude longitude:startLongitude];
CLLocation *endLocation = [[CLLocation alloc] initWithLatitude:endLatitude longitude:endLongitude];
CLLocationDistance distance = [startLocation distanceFromLocation:endLocation]; // aka double
Don't forget to add MapKit Framework to your project, and import MapKit in your file :
#import <MapKit/MapKit.h>
Google Maps is likely to be giving you the driving distance, whereas the great circle equation you have listed is going to be the straight line surface distance. If there was a straight line surface road directly from point A to point B, Google Maps would likely give you the same distance as the equation you have there.
Since
getDistanceFrom:
isDeprecated
Try use the
[newLocation distanceFromLocation:oldLocation
You should be able to use the google API directly to calculate either great circle distance or driving distance depending on your application needs.
See GLatLong::distanceFrom and GDirections::getDistance.