Convert a place String to a CLLocation - ios

I'm having a bug with the convertion of an NSString (which contains a place from a UITextField input) to a CLLocation.
Here is the method supposed to process the convertion:
- (void)geoCodeForAddress:(NSString *)address {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray* placemarks, NSError* error){
dispatch_async(dispatch_get_main_queue(), ^{
CLPlacemark *placeMark = [placemarks lastObject];
if (placeMark.location != nil) {
self.actionLocation = placeMark.location;
} else {
self.actionLocation = nil;
}
if(self.event)
[self.tableView reloadData];
});
}];
}
My problem is, this method returns the wrong location for some specific strings. For instance, whenever I try to use "la patache paris" as an input, it returns a location in the wrong city though when I try the same exact string in Maps, it's finding the right place.
The method geocodeAddressString:completionHandler: is a CoreLocation method from the CLGeocoder class so there is no way I can see what's going on in it.
Would someone have some lead to find out why this is behaving weirdly ?
Thank you.

Related

PHAsset CLLocation is not giving location same as photos app

We are fetching the latitude and longitude from PHAsset properties we are using CLGeocoder to reverseGeocodeLocation using following method.
CLGeocoder * geoCoder = [CLGeocoder new];
[geoCoder reverseGeocodeLocation:loc completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
CLPlacemark *placeInfo = placemarks[0];
}
we are getting placeInfo object but thoroughfare and subThoroughfare are always coming nil.
For the same photo photosapp is able to fetch thoroughfare and subThoroughfare.
Thank You in advance.
thoroughfare and subThoroughfare property that might not contain data for some places. So instead of creatying address string using thoroghfare and subThoroghfare, use addressdictionary as follow:
NSString *address = ABCreateStringWithAddressDictionary(placeInfo .addressDictionary, NO);

How to convert PFGeoPoint to CLLocation

Is it possible to convert the PFGeoPoint with Parse to a CLLocation?
Reason being I'm trying to get the names of places from PFGeoPoint like so:
CLGeocoder *geocoder;
CLPlacemark *placemark;
PFGeoPoint *point = [object objectForKey:#"location"];
[geocoder reverseGeocodeLocation:point completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0)
{
placemark = [placemarks lastObject];
NSLog(#"%#", placemarks);
}
}];
Error I get obviously is:
Incompatiable pointer types sending 'PFGeoPoint *' to parameter of type 'CLLocation *'
As Wain said there is no convenient method to convert from PFGeoPoint to a CLLocation however you can make your own and extend PFGeoPoint using the following
import Parse
extension PFGeoPoint {
func location() -> CLLocation {
return CLLocation(latitude: self.latitude, longitude: self.longitude)
}
}
now you can easily return a CLLocation object by doing
let aLocation: CLLocation = yourGeoPointObject.location()
You need to explicitly create the CLLocation instance using initWithLatitude:longitude: with the latitude and longitude from the PFGeoPoint. There is only a convenience method for creating a PFGeoPoint from a CLLocation, not the other way round.
Based on Justin Oroz's answer (see the answer below):
Also a great idea could be to create the extension in the CLLocation side so it can be created using a convenience init which receives the PFGeoPoint:
extension CLLocation {
convenience init(geoPoint: PFGeoPoint) {
self.init(latitude: geoPoint.latitude, longitude: geoPoint.longitude)
}
}
You could always get the PFGeoPoint and then add it to a CLLocationCoordinate2D like this:
//get all PFObjects
let parseLocation = ParseObject["location"]
let location:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: parseLocation.latitude, longitude: parseLocation.longitude)

How to get directions to a MKPointAnnotation

I have created a map that has an MKPointAnnotation which is the single point on the map (besides the users location). I am trying to work out how to amend some existing code I have to get the Driving directions to this point.
This is the code that I use early in the application. At this earlier point in the application I have the following which gives me a CLPlaceMark.
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
Collecting directions:
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
[request setSource:[MKMapItem mapItemForCurrentLocation]];
MKPlacemark *mkDest = [[MKPlacemark alloc] initWithPlacemark:topResult];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
[request setTransportType:MKDirectionsTransportTypeWalking]; // This can be limited to automobile and walking directions.
[request setRequestsAlternateRoutes:NO];
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (!error) {
for (MKRoute *route in [response routes]) {
[self.mapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads]; // Draws the route above roads, but below labels.
// You can also get turn-by-turn steps, distance, advisory notices, ETA, etc by accessing various route properties.
}
}
}];
Issue
The issue is that later on I seem to only be able to access the self.mapView.annotations. So I have access to the MKPointAnnotation, but I need access to a CLPlacemark for the setDestination on the MKDirectionsRequest.
So the question is how do I get a CLPacemark from a MKPointAnnotation, or is there a different approach to getting directions to a single point without that requirement? Thanks
For the directions request, the MKMapItem needs an MKPlacemark (not a CLPlacemark).
You can create an MKPlacemark directly from coordinates using its initWithCoordinate:addressDictionary: method.
For example:
MKPlacemark *mkDest = [[MKPlacemark alloc]
initWithCoordinate:pointAnnotation.coordinate
addressDictionary:nil];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
MKPointAnnotation will give you the coordinate, which you can put into CLPlacemark's location.coordinate. I don't think there will be any other readily-available information within an MKPointAnnotation that would be usable in a CLPlacemark.
MKPointAnnotation *annotation = ...;
CLPlacemark *placemark = ...;
placemark.location.coordinate = annotation.coordinate;
Edit: Apologies, I didn't realize that CLPlacemarks are largely read-only. That being said, you can use a reverse-geocode on the coordinate of your MKPointAnnotation in order to get a CLPlacemark. This link has information on how to reverse-geocode to get your CLPlacemark from a CLLocation (populate the location.coordinate with your annotation.coordinate) to look up directions.
You need to use the coordinate property of the MKPointAnnotation and then get the CLPlacemark via CLGeocoder using reverse geocode.
EDIT: Some sample code.
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemark, NSError *error){
// Grab the placemark
}];
Otherwise you need to cache the CLPlacemark which you can do on your annotation datasource if you like (remember MKAnnotation is a protocol, there is nothing saying you can't add a property to the backing model).

Geocoder throwing odd exception

I have a method that is triggered on the press of a button. This is most of the implmentation:
[self.placeDictionary setValue:#"166 Bovet Rd" forKey:#"Street"];
[self.placeDictionary setValue:#"San Mateo" forKey:#"City"];
[self.placeDictionary setValue:#"CA" forKey:#"State"];
[self.placeDictionary setValue:#"94402" forKey:#"ZIP"];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressDictionary:self.placeDictionary completionHandler:^(NSArray *placemarks, NSError *error) {
if([placemarks count]) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coordinate = location.coordinate;
PFGeoPoint* userLocation = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
NSLog(#"%f,%f", userLocation.latitude, userLocation.longitude);
} else {
NSLog(#"location error");
return;
}
}];
However, I am getting the following exception:
*** WebKit discarded an uncaught exception in the webView:shouldInsertText:replacingDOMRange:givenAction: delegate: <NSUnknownKeyException> [<__NSDictionaryI 0x873a3c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key Street.
I have absolutely no idea what this exception means. Could someone help me understand why it is producing this?
First off, you are trying to add objects to an immutable dictionary. The part of the exception beginning [<__NSDictionaryI 0x873a3c0> setValue:forUndefinedKey: gives a class name of __NSDictionaryI, which is an immutable member of the NSDictionary class cluster - so you cannot add any objects to it at run-time. You need to ensure that self.placeDictionary is assigned to an NSMutableDictionary instance before this code is called.
Unfortunately, you are also using the wrong method to add the objects - you're using setValue:forKey: instead of setObject:forKey:. Since this method is part of the NSKeyValueCoding informal protocol, you're not stopped from doing this at compile time. You should instead use setObject:forKey: which is the correct method to set key-value pairs on an NSMutableDictionary. After you correct the first issue, replace the setValue:forKey: calls with setObject:forKey:, for example:
[self.placeDictionary setObject:#"San Mateo" forKey:#"City"];

Checking Proximity of two Locations in IOS

I have an array of locations and when I add another I want to be able to check if the other locations in the array are within a block of the new one. This is the Code I have to find the current location:
//Geocoding Block
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
The array has yet to be created because I want to figure this out first, I dont know what should be held in the array (string...). I know how to do a search I just need to know how to compare the locations.
You can get the distance between two locations using the distanceFromLocation: method on CLLocation. (You can get a CLLocation out of a CLPlacemark with myPlacemark.location.) So if you have an array of CLLocation objects, and you want to find the ones that are within one block (1/20 mile, or about 80 meters), you can do this:
NSMutableArray *locationsWithinOneBlock = [NSMutableArray new];
for (CLLocation *location in myLocations) {
if ([location distanceFromLocation:targetLocation] <= 80.0)
[locationsWithinOneBlock addObject:location];
}
This assumes you have an array myLocations of CLLocation objects that you want to filter against a single CLLocation called targetLocation.

Resources