CLGeocoder ever return one placemark - ios

I want to revive this and this question because the problem still persists for me, so I'm writing a new question.
This is my code:
- (SVGeocoder*)initWithParameters:(NSMutableDictionary*)parameters completion:(SVGeocoderCompletionHandler)block {
self = [super init];
self.operationCompletionBlock = block;
Class cl = NSClassFromString(#"CLGeocoder");
if (cl != nil)
{
if (self.geocoder_5_1 == nil) {
self.geocoder_5_1 = [[cl alloc] init];
}
NSString *address = [parameters objectForKey:kGeocoderAddress];
[self.geocoder_5_1 geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
NSMutableArray *svplacemarks = [NSMutableArray arrayWithCapacity:1];
SVPlacemark *placemark;
NSLog(#"placemarks[count] = %i", [placemarks count]);
for (CLPlacemark *mark in placemarks) {
placemark = [[SVPlacemark alloc] initWithPlacemark:mark];
[svplacemarks addObject:placemark];
}
self.operationCompletionBlock([NSArray arrayWithArray:svplacemarks],nil,error);
}];
}
else
{
self.operationRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://maps.googleapis.com/maps/api/geocode/json"]];
[self.operationRequest setTimeoutInterval:kSVGeocoderTimeoutInterval];
[parameters setValue:#"true" forKey:kGeocoderSensor];
[parameters setValue:[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode] forKey:kGeocoderLanguage];
[self addParametersToRequest:parameters];
self.state = SVGeocoderStateReady;
}
return self;
}
It is my personal version (quite rough) of SVGeocoder using CLGeocoder for forward geocoding with retrocompatibility for iOS < 5.1
I use this solution because of the Google terms which prevent the use of the maps API without showing the result on a Google map.
The problem is the same one from the previously mentioned questions: CLGeocoder returns only one placemark and the log prints a nice
"placemarks[count] = 1".
My question is, does anyone know if there is another way to retrieve forward geocoding, or some other magic thing (the Apple map app shows multiple markers for the same query I do, "via roma", for example) ?
EDIT FOR ROB'S SOLUTION
Class mkLocalSearch = NSClassFromString(#"MKLocalSearch");
if (mkLocalSearch != nil)
{
NSString *address = [parameters objectForKey:kGeocoderAddress];
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.region = MKCoordinateRegionForMapRect(MKMapRectWorld);
request.naturalLanguageQuery = address;
MKLocalSearch *localsearch = [[MKLocalSearch alloc] initWithRequest:request];
[localsearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
NSMutableArray *svplacemarks = [NSMutableArray arrayWithCapacity:1];
SVPlacemark *placemark;
NSLog(#"response.mapItems[count] = %i", [response.mapItems count]);
for (MKMapItem *item in response.mapItems)
{
placemark = [[SVPlacemark alloc] initWithPlacemark:item.placemark];
[svplacemarks addObject:placemark];
}
self.operationCompletionBlock([NSArray arrayWithArray:svplacemarks],nil,error);
}];
}
This is an interesting solution that gives another point of view. Unfortunately, even if I set the region to worldwide, I still get a nice log
response.mapItems[count] = 1
The query was "via roma", which is a very common street name in Italy, so much so that I think we can find it in practically any Italian city.
Maybe I'm doing something wrong?
EDIT 2 - New Test:
convert World Rect to CLRegion, code from here
NSString *address = [parameters objectForKey:kGeocoderAddress];
// make a conversion from MKMapRectWorld to a regular CLRegion
MKMapRect mRect = MKMapRectWorld;
MKMapPoint neMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), mRect.origin.y);
MKMapPoint swMapPoint = MKMapPointMake(mRect.origin.x, MKMapRectGetMaxY(mRect));
float ewDelta= neMapPoint.x - swMapPoint.x;
float nsDelta= swMapPoint.y - neMapPoint.y;
MKMapPoint cMapPoint = MKMapPointMake(ewDelta / 2 + swMapPoint.x, nsDelta / 2 + neMapPoint.y);
CLLocationCoordinate2D neCoord = MKCoordinateForMapPoint(neMapPoint);
CLLocationCoordinate2D swCoord = MKCoordinateForMapPoint(swMapPoint);
CLLocationCoordinate2D centerCoord = MKCoordinateForMapPoint(cMapPoint);
CLLocationDistance diameter = [self getDistanceFrom:neCoord to:swCoord];
// i don't have the map like showed in the example so i'm trying to center the search area to the hypothetical center of the world
CLRegion *clRegion = [[CLRegion alloc] initCircularRegionWithCenter:centerCoord radius:(diameter/2) identifier:#"worldwide"];
[self.geocoder_5_1 geocodeAddressString:address inRegion: clRegion completionHandler:^(NSArray *placemarks, NSError *error) {
NSMutableArray *svplacemarks = [NSMutableArray arrayWithCapacity:1];
SVPlacemark *placemark;
NSLog(#"placemarks[count] = %i", [placemarks count]);
for (CLPlacemark *mark in placemarks) {
placemark = [[SVPlacemark alloc] initWithPlacemark:mark];
[svplacemarks addObject:placemark];
}
self.operationCompletionBlock([NSArray arrayWithArray:svplacemarks],nil,error);
}];
... and I get the usual "placemark [count] = 1"

Obviously, CLGeocoder will return multiple placemarks if the address gets multiple hits (i.e. the region is large enough such that the simple street address is ambiguous), but frequently it will find just the one match if the region is small enough or if the supplied address is unique enough.
While it's not a general purpose solution, effective iOS 6.1, you have MKLocalSearch, which does a more general lookup (including names of businesses, etc.):
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.region = self.mapView.region;
request.naturalLanguageQuery = textField.text;
MKLocalSearch *localsearch = [[MKLocalSearch alloc] initWithRequest:request];
[localsearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
for (MKMapItem *item in response.mapItems)
{
Annotation *annotation = [[Annotation alloc] initWithPlacemark:item.placemark];
annotation.title = item.name;
annotation.phone = item.phoneNumber;
annotation.subtitle = item.placemark.addressDictionary[(NSString *)kABPersonAddressStreetKey];
[self.mapView addAnnotation:annotation];
}
}];
I guess it all depends upon what sort of multiple hits you're expecting to receive.

There are some addresses for which CLGeocoder does return multiple placemarks. One example I've found is "Herzel 13, Haifa, Israel". I use the geocodeAddressDictionary:completionHandler: method, and get the same 2 results for the address (it can be set either as street/city/country, or just as a street - the results are the same).
It's just pretty hard to find such examples, and they may change in the future of course. For some reason, the Apple maps app shows the "Did you mean..." dialog for many more addresses.

Related

Estimated Time Between two Locations in iOS [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
What is proper way to get ETA (estimated time arrival) from AnyLocation to MyCurrentLocation?
I want, when use tap on any annotation, then I have to set details on the Footer according to that annotation and user's current location.
Please see below image for better understanding.
I saw this What is proper way to get ETA (estimated time arrival) from any location to my current location and this calculate time taken to cover a journey in Apple Maps But they didn't solved my problem using this. I am getting always Null response using this. I put my code below:-
double latitude = -0.075410;
double longitude = 51.512520;
MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake(latitude, longitude) addressDictionary:nil] ;
MKMapItem *mapItemSource = [[MKMapItem alloc] initWithPlacemark:placemark];
double latitude1 = -0.132128;
double longitude1 = 51.464138;
MKPlacemark *placemark1 = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake(latitude1, longitude1) addressDictionary:nil] ;
MKMapItem *mapItemDestination = [[MKMapItem alloc] initWithPlacemark:placemark1];
MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];
[directionsRequest setSource:mapItemSource];
[directionsRequest setDestination:mapItemDestination];
directionsRequest.transportType = MKDirectionsTransportTypeAutomobile;
MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
if (error) {
NSLog(#"Error %#", error.description);
} else {
NSLog(#"expectedTravelTime %f", response.expectedTravelTime);
}
}];
//I am not sure why I am getting Null response into calculateETAWithCompletionHandler this method..
//Also tried following......
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (error) {
NSLog(#"Error %#", error.description);
} else {
MKRoute * routeDetails = response.routes.lastObject;
//[self.mapView addOverlay:routeDetails.polyline];
NSLog(#"Street %#",[placemark.addressDictionary objectForKey:#"Street"]);
NSLog(#"distance %#",[NSString stringWithFormat:#"%0.1f Miles", routeDetails.distance/1609.344]);
NSLog(#"expectedTravelTime %#",[NSString stringWithFormat:#"%0.1f minutes",routeDetails.expectedTravelTime/60]);
}
}];
//I am not sure why I am getting Null response into calculateDirectionsWithCompletionHandler this method..
Also I saw this Is there any way to determine the driving time between two locations using Apple's Maps API? and this How to calculate time required to complete the path in between two locations?. But RightNow, I don't want to use Google Maps Directions API. Because my client does not want to use that. If Google APIs are free, then Definitely I preferred that.
Also, I tried distanceFromLocation method. But, I think, This solution assumes straight-line distance between two points, which is almost never useful.
Is there any other solution for doing this..??
Any help would be appreciated.
Try this method. Here you have to pass source and destination latitude and longitude information.
NSString *strUrl = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=false&mode=%#", lat, longi, userLat, userLong, #"DRIVING"];
NSURL *url = [NSURL URLWithString:[strUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
if(jsonData != nil)
{
NSError *error = nil;
id result = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
NSMutableArray *arrDistance=[result objectForKey:#"routes"];
if ([arrDistance count]==0) {
NSLog(#"N.A.");
}
else{
NSMutableArray *arrLeg=[[arrDistance objectAtIndex:0]objectForKey:#"legs"];
NSMutableDictionary *dictleg=[arrLeg objectAtIndex:0];
NSLog(#"%#",[NSString stringWithFormat:#"Estimated Time %#",[[dictleg objectForKey:#"duration"] objectForKey:#"text"]]);
}
}
else{
NSLog(#"N.A.");
}
If you want to calculate the travel time between points you first need to specify if you want driving or walking directions, and then generate directions. The result of the directions request will include a travel time estimate.
You'll need to use MapKit, MKDirectionsRequest, and MKDirections classes, among others. You'll also need to configure your app to ask Apple for permission to generate driving directions. I found this link online that explains how to calculate driving directions.
you can use google api for that and one more thing by default there is method
locA = [[CLLocation alloc] initWithLatitude:[[latsArray objectAtIndex:0] floatValue] longitude:[[longArray objectAtIndex:0] floatValue]];
locB = [[CLLocation alloc] initWithLatitude:[[latsArray objectAtIndex:1] floatValue] longitude:[[longArray objectAtIndex:1] floatValue]];
distance = [locA distanceFromLocation:locB] * 0.000621371;
and time for walk
float time = distance / 3.1; //divide by 3.1 for by walk

Error trying to grab coordinates from MKMapItem

Some quick background, I perform a search location search request. The code for the search request is given below:
Search
-(void)performSearch {
// activate search inidicator
[self.completingSearchIndicator startAnimating];
self.completingSearchIndicator.hidesWhenStopped = YES;
self.doneButton.enabled = NO;
self.cancelButton.enabled = NO;
self.searchButton.enabled = NO;
// Create a search request
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = self.searchText.text;
// adjust the region of search so it is about 5000m x 5000m
// about 2.5x bigger than viewing region
// we should allow users to adjust this ****
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.mapHandle.userLocation.location.coordinate, 5000, 5000);;
request.region = region;
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
[search startWithCompletionHandler:^ (MKLocalSearchResponse *response, NSError *error)
{ // a block which loads each item into an array for us to use
// an array of MKMapItem objects
NSMutableArray *placemarks = [NSMutableArray array];
for (MKMapItem *item in response.mapItems) {
[placemarks addObject:item.placemark];
}
// save results in an instance variable
self.searchResults = placemarks;
// reactivate everything!
[self.completingSearchIndicator stopAnimating];
self.doneButton.enabled = YES;
self.cancelButton.enabled = YES;
self.searchButton.enabled = YES;
}];
}
From that I receive an array of MKMapItems, I choose the ones I want to save, and store them in another array. From here I would like to print out their coordinates, however, I keep getting an error while trying to access them. The code I'm using to access them is:
for (MKMapItem *item in self.selectedPlaces) {
MKPlacemark *temp = item.placemark;
CLLocationCoordinate2D coords = temp.coordinate;
NSString *coordinateString = [NSString stringWithFormat:#"%f", coords.latitude];
NSLog(#"%#",coordinateString);
}
And the error I'm getting is:
2014-04-21 20:15:30.931 iTasks[2759:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MKPlacemark placemark]: unrecognized selector sent to instance 0x193a5610'
Any resolution or alternate stategy would be appreciated! Many thanks in advance!
Maybe you want this instead?
for (MKPlacemark *item in self.selectedPlaces) {
CLLocationCoordinate2D coords = item.location.coordinate;
NSString *coordinateString = [NSString stringWithFormat:#"%f", coords.latitude];
NSLog(#"%#",coordinateString);
}

MKLocalSearch not returning correct cities

I am using MKLocalSearch to find locations in US.
The search is not returning correct locations. When I searched for San Francisco, I got the location (I was near to San Francisco). But, when I searched for Newyork, it showed a street near by. London, Paris, Tokyo etc not working.
Below is my code.
MKLocalSearchRequest *searchRequest = [[MKLocalSearchRequest alloc]init];
searchRequest.naturalLanguageQuery = searchQuery;
searchRequest.region = self.mapView.region;
MKLocalSearch *search = [[MKLocalSearch alloc]initWithRequest:searchRequest];
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error)
{
if (error || response.mapItems.count == 0)
{
NSLog(#"Map Search Error");
return ;
}
MKMapItem *topItem = [response.mapItems firstObject];
[self.mapView addAnnotation:topItem.placemark];
[self goToCoordinate:topItem.placemark.coordinate];
Your suggestions are welcome.

Forward geocoding did not work with CLGeocoder but works as expected on Apple Native Map

I tried to use the Apple GeoCoderDemo to do the forward geocoding. I tried with "Walmart Michigan", and results returned back are totally different by comparing with apple's native map app on the device.
After searching stackOverflow, I know that CLGeocoder can only do address search instead of address/business name search, which meaning it is looking for street name contains with Walmart in Michigan in my case.
But I am curious to know why the apple's native map can do the work perfect. Does anyone know the secret for that?
Thanks for all helps.
In iOS 6.1, Apple exposed us to MKLocalSearch, which is a true search function, akin to what the Maps app does. For example:
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = #"restaurant";
request.region = mapView.region;
MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:request];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
NSMutableArray *annotations = [NSMutableArray array];
[response.mapItems enumerateObjectsUsingBlock:^(MKMapItem *item, NSUInteger idx, BOOL *stop) {
CustomAnnotation *annotation = [[CustomAnnotation alloc] initWithPlacemark:item.placemark];
annotation.title = item.name;
annotation.phone = item.phoneNumber;
annotation.subtitle = item.placemark.addressDictionary[(NSString *)kABPersonAddressStreetKey];
[annotations addObject:annotation];
}];
[self.mapView addAnnotations:annotations];
}];

Reverse Geocoding issues for City name in iOS

I'm able to retrieve the current location in my iPad application using,
CLLocation *location = [[CLLocation alloc] initWithLatitude:[latitude floatValue] longitude:[longitude floatValue]];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error)
{
NSLog(#"-----------------Placemark is %#-----------------", placemarks);
locationLabel.text = placemarks;
}];
and the output is,
-----------------Placemark is ("South Atlantic Ocean, South Atlantic Ocean #<-42.60533670,-21.93128480> +/- 100.00m, region (identifier <-41.51023865,-31.60774370> radius 4954476.31) <-41.51023865,-31.60774370> radius 4954476.31m"
)-----------------
Can I use the same information to just get the city and the country name? instead of the long list of information?
also, the 'locationLabel.text = placemarks' gives a warning, "Incompatible pointer types assigning to 'NSString*' from 'NSArray*_strong', which I'm unable to resolve.
Yes you can.
But you doing it a little it wrong. First of all, placemarks is an array and not a string. That's why locationLabel.text = placemarks gives a warning.
Placemarks is an array of CLPlacemarks. This is because the geocoder could return multiple results for a coordinate. In the simplest condition the first item in it should be okay.
A CLPlacemark has the property addressDictionary which contains the data of this location.
You can access this data with the address property constans defined by the ABPerson header file.
For example:
Get the first placemark from the array:
CLPlacemark *place = [placemarks objectAtIndex:0];
then get the city from this placemark:
NSString *cityName = [place objectForKey: kABPersonAddressCityKey];
Don't forget to import the AVPerson header!
your can get all following place details
placeNameLabel.text = [placemarks[0] name];
addressNumberLabel.text = [placemarks[0] subThoroughfare];
addressLabel.text = [placemarks[0] thoroughfare];
neighborhoodLabel.text = [placemarks[0] subLocality];
cityLabel.text = [placemarks[0] locality];
countyLabel.text = [placemarks[0] subAdministrativeArea];
stateLabel.text = [placemarks[0] administrativeArea];
zipCodeLabel.text = [placemarks[0] postalCode];
countryLabel.text = [placemarks[0] country];
countryCodeLabel.text = [placemarks[0] ISOcountryCode];
inlandWaterLabel.text = [placemarks[0] inlandWater];
oceanLabel.text = [placemarks[0] ocean];

Resources