I have an application that uses a CLGeocoder to forwardGeocode a placemark from an address string. The CLPlacemark response contains a CLLocation which gives me GPS coordinates.
The only way to create an NSTimeZone seems to be by using the correct Time Zone Name. It is important to point out that I am not using the current location of the device, so [NSTimeZone localTimeZone] will not work for me.
Is there a way to get the timezone name for the CLLocation so that I can create an NSTimeZone correctly?
NOTE: I have been using timeZoneForSecondsFromGMT but that never contains correct DST data, so it is not helpful for me.
You should use https://github.com/Alterplay/APTimeZones to get NSTimeZone from CLLocation. It also works with CLGeocoder.
since iOS9 it should be possible direclty using CLGeocoder as specified here: https://developer.apple.com/library/prerelease/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html
Search results for MapKit and CLGeocoder can provide a time zone for the result.
I found an interesting approach using CLGeocoder, which I put into a category on CLLocation. The interesting part looks like this:
-(void)timeZoneWithBlock:(void (^)(NSTimeZone *timezone))block {
[[[CLGeocoder alloc] init] reverseGeocodeLocation:self completionHandler:^(NSArray *placemarks, NSError *error) {
NSTimeZone *timezone = nil;
if (error == nil && [placemarks count] > 0) {
CLPlacemark *placeMark = [placemarks firstObject];
NSString *desc = [placeMark description];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"identifier = \"([a-z]*\\/[a-z]*_*[a-z]*)\"" options:NSRegularExpressionCaseInsensitive error:nil];
NSTextCheckingResult *result = [regex firstMatchInString:desc options:0 range:NSMakeRange(0, [desc length])];
NSString *timezoneString = [desc substringWithRange:[result rangeAtIndex:1]];
timezone = [NSTimeZone timeZoneWithName:timezoneString];
}
block(timezone);
}];
}
Usage is like this:
CLLocation *myLocation = ...
[myLocation timeZoneWithBlock:^(NSTimeZone *timezone) {
if (timezone != nil) {
// do something with timezone
} else {
// error determining timezone
}
}];
Despite requiring a network connection and working asynchronously, I have found this to be the most reliable way of getting the time zone for a location.
EDITED YEARS LATER
This answer hasn't aged well since the timeZone property was added to CLPlacemark in iOS9 (thank you Ortwin Genz). Here's an updated category method:
-(void)timeZoneWithBlock:(void (^)(NSTimeZone *timezone))block {
[[[CLGeocoder alloc] init] reverseGeocodeLocation:self completionHandler:^(NSArray *placemarks, NSError *error) {
NSTimeZone *timeZone = nil;
if (error == nil && [placemarks count] > 0) {
CLPlacemark *placeMark = [placemarks firstObject];
timeZone = placeMark.timeZone;
}
block(timeZone);
}];
}
Related
I want to allow the users to search for a street name and have the results displayed in a UITableView.
For the moment the region is not important, it can be from any region.
I could not find any relevant example in my searches and I don't know if I should use CLLocation or MKLocalSearch.
Based on docs, I should use MKLocalSearch:
Although local search and geocoding are similar, they support
different use cases. Use geocoding when you want to convert between
map coordinates and a structured address, such as an Address Book
address. Use local search when you want to find a set of locations
that match the user’s input.
But I have tried both methods and it gives me only 1 result (even-though there is an NSArray returned.
This is the CLGeocoder approach:
CLGeocoder *geocoding = [[CLGeocoder alloc] init];
[geocoding geocodeAddressString:theTextField.text completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSLog(#"%i", [placemarks count]);
for(CLPlacemark *myStr in placemarks) {
NSLog(#"%#", myStr);
}
}
}];
And this is my MKLocalSearch try:
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = theTextField.text;
request.region = self.region;
localSearch = [[MKLocalSearch alloc] initWithRequest:request];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error){
if (error != nil) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Map Error",nil)
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK",nil) otherButtonTitles:nil] show];
return;
}
if ([response.mapItems count] == 0) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"No Results",nil)
message:nil
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK",nil) otherButtonTitles:nil] show];
return;
}
self.streets = response;
[self.streetsTableView reloadData];
}];
MKLocalSearch seems to return more than 1 response in some cases, but these are related to places not street names searches.
Thanks in advance.
This is the closest I could get. This involves using google places API Web Service.
Note: You could probably use their Google Maps API, etc. I am sure there are other ways to get this information from the various Google APIs.
NSURL *googlePlacesURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=%#&location=%f,%f&sensor=true&key=API_KEY", formattedSearchText, location.coordinate.latitude,
location.coordinate.longitude]];
The response is a JSON object. Convert it to a dictionary.
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:_googlePlacesResponse
options:NSJSONReadingMutableContainers error:&error];
if([[response objectForKey:#"status"] isEqualToString:#"OK"])
{
NSArray *predictions = [response objectForKey:#"predictions"];
for(NSDictionary *prediction in predictions)
{
NSArray *addressTypes = [prediction objectForKey:#"types"];
if([addressTypes containsObject:#"route"])
{
//This search result contains a street name.
//Now get the street name.
NSArray *terms = [prediction objectForKey:#"terms"];
NSDictionary *streetNameKeyValuePair = [terms objectAtIndex:0];
NSLog(#"%#",[streetNameKeyValuePair objectForKey#"value"]);
}
}
}
The possible types seem to be
Route -> Street name
Locality -> City/location name
Political -> State, etc
Geocode -> lat/long available
You could populate the table view with those predictions that ONLY contain route as an address type. This could work.
CLGeocoder simply returns address format.
Slap this in your code and play with the contents of mapitems
MKLocalSearchRequest *request = [MKLocalSearchRequest new];
request.naturalLanguageQuery = #"Pizza";
request.region = MKCoordinateRegionMake(location.coordinate, MKCoordinateSpanMake(.01, .01));
MKLocalSearch *search = [[MKLocalSearch alloc]initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
NSArray *mapItems = response.mapItems;
for (MKMapItem *mapItem in mapItems) {
MKPointAnnotation *point = [MKPointAnnotation new];
point.coordinate = mapItem.placemark.coordinate;`
}
}];
The array returned contains mapItems, you can iterate over the array to pull out all the mapItems like this:
myMatchingItems = [[NSMutableArray alloc] init];
for (MKMapItem *item in response.mapItems){
[myMatchingItems addObject:item];
}
Each mapItem.placemark.thoroughfare contains the Street of the location that was found.
I'm receiving a list of latitude and longitudes by the web service and geocoding then to get the full address.
The problem is that after some time, they become null, and the geocoder stop working!
Can somebody help me?
Here's the code that I'm using to get the address:
-(void)getAddressArray:(void (^)(id response))completion{
for (int i=0; i<[favoritos count]; i++) {
double latitude = [[[favoritos valueForKey:#"latitude"] objectAtIndex:i] doubleValue];
double longitude = [[[favoritos valueForKey:#"longitude"] objectAtIndex:i] doubleValue];
CLLocationCoordinate2D location = CLLocationCoordinate2DMake(latitude, longitude);
CLLocation * locationAtual = [[CLLocation alloc]initWithLatitude:location.latitude longitude:location.longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:locationAtual completionHandler:^(NSArray *placemarks, NSError *error) {
NSDictionary *dicAddress;
CLPlacemark *place = [placemarks firstObject];
if (placemarks) {
NSLog(#"%#",place.addressDictionary);
if ([place.addressDictionary valueForKey:#"Thoroughfare"] == [NSNull null] || ![place.addressDictionary valueForKey:#"Thoroughfare"]) {
dicAddress = #{#"id":[[favoritos objectAtIndex:i] valueForKey:#"id"],#"address":#"Endereço não encontrado!",#"complemento":[[favoritos objectAtIndex:i] valueForKey:#"references"],#"number":[[favoritos valueForKey:#"numero"] objectAtIndex:i],#"name":[[favoritos valueForKey:#"name"] objectAtIndex:i], #"latitude":[[favoritos valueForKey:#"latitude"] objectAtIndex:i], #"longitude":[[favoritos valueForKey:#"longitude"] objectAtIndex:i]};
}
[address addObject:dicAddress];
if ([address count] == [favoritos count])
completion(address);
}
}];
}
Thanks!
Check the error passed in to the block, probably you are sending to many request and the rate limit is reached.
There is rate limit for reverseGeocodeLocation:completionHandler:
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. When the maximum rate is exceeded, the geocoder passes an error
object with the value kCLErrorNetwork to your completion handler.
i write code of CLGecoder and the code are
-(NSString *)GetCurrentAddress:(CLLocation *)Location
{
__block NSString *locatedaddress;
CLGeocoder *Gecoder=[[CLGeocoder alloc]init];
[Gecoder reverseGeocodeLocation: Location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"Placemark array: %#",placemark.addressDictionary );
//String to address
locatedaddress = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
//Print the location in the console
NSLog(#"Currently address is: %#",locatedaddress);
}];
return locatedaddress;
}
Problem is locatedaddress inside The completionHandler Has Value But Outside Is Not ?
Any One Can Help Me ?
Thanks In Advance.
The geocoder is asynchronous -- which by definition means you cannot get the result immediately. You must use the completion handler to do whatever you want to do with the result.
I'd like to ask for a second opinion for my solution in reverse Geocoding in getting a user's current location:
- (void)reverseGeocodeLocation:(CLLocation *)location
{
CLGeocoder* reverseGeocoder = [[CLGeocoder alloc] init];
if (reverseGeocoder) {
[reverseGeocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark* placemark = [placemarks firstObject];
if (placemark && [placemark count] > 0) {
//Using blocks, get zip code
NSString *zipCode = [placemark.addressDictionary objectForKey:(NSString*)kABPersonAddressZIPKey];
}
}];
}
else{
MKReverseGeocoder* revGeo = [[MKReverseGeocoder alloc] initWithCoordinate:location.coordinate];
revGeo.delegate = self;//using delegate
[revGeo start];
[revGeo release];
}
[reverseGeocoder release];
}
however, there seemed to be a bit of a problem...I encountered an EXC_BAD_ACCESS error pointing at:
[reverseGeocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark* placemark = ...
}];
Could you please tell me what went wrong? I received an EXC_BAD_ACCESS error.
You're not checking the error sent to the block. You're presuming that you get at least one placemark returned, but the array may be empty for some reason. That certainly could be the source of your EXC_BAD_ACCESS error.
I am connecting to a remote web service which basically returns an XML back. I am then parsing that XML into a Property object (think real state sort of thing)
But now, the web service returns a postal code for each property alone. It does not provide a coordinate which is what I need to place an annotation in the map. I am able to geocode an address provided a postal code. However, my problem is it is not allowing me to do multiple requests
Here's my code
- (void)processProperties:(Property *)property {
[geocoder geocodeAddressString:property.postalCode
completionHandler:^(NSArray* placemarks, NSError* error){
placemark = [placemarks lastObject];
for (CLPlacemark* aPlacemark in placemarks)
{
[sublet setLatitude:aPlacemark.location.coordinate.latitude];
[sublet setLongitude:aPlacemark.location.coordinate.longitude];
}
}];
}
- (void)addAnnotations:(NSArray *)objects {
CLLocationDegrees lat;
CLLocationDegrees longitude;
CLLocationCoordinate2D mCoords;
NSString *fullAddress;
// Add the annotations found nearby
for (Property *property in objects) {
[self processProperties:property];
lat = property.latitude;
longitude = property.longitude;
fullAddress = [NSString stringWithFormat:#"%# %# %#", property.houseNumber, #" ", property.streetName];
[self createAnnotationWithCoords:mCoords :fullAddress :[NSString stringWithFormat:#"$%.2f", property.rent]];
}
zoomLevel = 0.1;
mCoords = CLLocationCoordinate2DMake(lat,longitude);
MKCoordinateRegion region = MKCoordinateRegionMake(mCoords,MKCoordinateSpanMake(zoomLevel,zoomLevel));
[self.mapView setRegion:region animated:YES];
}
For some reason it's just geocoding 1 property. Is not going through the loop accordingly.
Any ideas folks?
Use this on your Forward Geo Function. geocoder needs to be release and initialized again to start a new address, hope this helps.
- (void)processProperties:(Property *)property {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:property.postalCode
completionHandler:^(NSArray* placemarks, NSError* error){
placemark = [placemarks lastObject];
for (CLPlacemark* aPlacemark in placemarks)
{
[sublet setLatitude:aPlacemark.location.coordinate.latitude];
[sublet setLongitude:aPlacemark.location.coordinate.longitude];
}
[geocoder release];
}];
}