Skip to content

Commit

Permalink
refactor(ios): use CLGeocoder APIs to reverse geocode and forward geo…
Browse files Browse the repository at this point in the history
…code (#12619)

Fixes TIMOB-28396
  • Loading branch information
build committed Mar 18, 2021
1 parent ff629ad commit 943b93b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 115 deletions.
6 changes: 6 additions & 0 deletions apidoc/Titanium/Geolocation/Geolocation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,12 @@ properties:
summary: Postal code
type: String

- name: state
summary: State name.
description: |
This property may return the full name or abbreviated name.
type: String

- name: zipcode
summary: Postal code. To be replaced by `postalCode`
type: String
Expand Down
184 changes: 71 additions & 113 deletions iphone/Classes/GeolocationModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
#ifdef USE_TI_GEOLOCATION

#import "GeolocationModule.h"
#import <Contacts/CNPostalAddress.h>
#import <Contacts/CNPostalAddressFormatter.h>
#import <TitaniumKit/APSHTTPClient.h>
#import <TitaniumKit/KrollPromise.h>
#import <TitaniumKit/NSData+Additions.h>
#import <TitaniumKit/TiApp.h>

#import <sys/utsname.h>

extern NSString *const TI_APPLICATION_GUID;
Expand Down Expand Up @@ -45,35 +46,7 @@ - (void)dealloc
[super dealloc];
}

- (void)start:(NSDictionary *)params
{
// https://api.appcelerator.net/p/v1/geo
NSString *kGeolocationURL = stringWithHexString(@"68747470733a2f2f6170692e61707063656c657261746f722e636f6d2f702f76312f67656f");

NSMutableString *url = [[[NSMutableString alloc] init] autorelease];
[url appendString:kGeolocationURL];
[url appendString:@"?"];
for (id key in params) {
NSString *value = [TiUtils stringValue:[params objectForKey:key]];
[url appendFormat:@"%@=%@&", key, [value stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]];
}

APSHTTPRequest *req = [[APSHTTPRequest alloc] init];
[req addRequestHeader:@"User-Agent" value:[[TiApp app] systemUserAgent]];
[req setUrl:[NSURL URLWithString:url]];
[req setDelegate:self];
[req setMethod:@"GET"];
// Place it in the main thread since we're not using a queue and yet we need the
// delegate methods to be called...
TiThreadPerformOnMainThread(
^{
[req send];
[req autorelease];
},
NO);
}

- (void)requestSuccess:(NSString *)data
- (void)requestSuccess:(NSDictionary *)data
{
}

Expand All @@ -87,27 +60,6 @@ - (void)requestError:(NSError *)error
[promise rejectWithErrorMessage:message];
}

- (void)request:(APSHTTPRequest *)request onLoad:(APSHTTPResponse *)response
{
[[TiApp app] stopNetwork];

if (request != nil && [response error] == nil) {
NSString *data = [response responseString];
[self requestSuccess:data];
} else {
[self requestError:[response error]];
}

[self autorelease];
}

- (void)request:(APSHTTPRequest *)request onError:(APSHTTPResponse *)response
{
[[TiApp app] stopNetwork];
[self requestError:[response error]];
[self autorelease];
}

@end

@interface HeadingCallback : GeolocationCallback
Expand Down Expand Up @@ -152,24 +104,8 @@ - (void)fireEvent:(id)event withProxy:(ObjcProxy *)proxy

@implementation ForwardGeoCallback

- (void)requestSuccess:(NSString *)locationString
- (void)requestSuccess:(NSDictionary *)event
{
NSMutableDictionary *event = nil;

NSArray *listItems = [locationString componentsSeparatedByString:@","];
if ([listItems count] == 4 && [[listItems objectAtIndex:0] isEqualToString:@"200"]) {
id accuracy = [listItems objectAtIndex:1];
id latitude = [listItems objectAtIndex:2];
id longitude = [listItems objectAtIndex:3];
event = [TiUtils dictionaryWithCode:0 message:nil];
[event setObject:accuracy forKey:@"accuracy"];
[event setObject:latitude forKey:@"latitude"];
[event setObject:longitude forKey:@"longitude"];
} else {
//TODO: better error handling
event = [TiUtils dictionaryWithCode:-1 message:@"error obtaining geolocation"];
}

if (callback != nil) {
[callback callWithArguments:@[ event ]];
}
Expand All @@ -180,28 +116,12 @@ - (void)requestSuccess:(NSString *)locationString

@implementation ReverseGeoCallback

- (void)requestSuccess:(NSString *)locationString
- (void)requestSuccess:(NSDictionary *)event
{
NSError *error = nil;
id event = [TiUtils jsonParse:locationString error:&error];
if (error != nil) {
[self requestError:error];
} else {
BOOL success = [TiUtils boolValue:@"success" properties:event def:YES];
NSMutableDictionary *revisedEvent = [TiUtils dictionaryWithCode:success ? 0 : -1 message:success ? nil : @"error reverse geocoding"];
[revisedEvent setValuesForKeysWithDictionary:event];
NSArray<NSMutableDictionary *> *places = (NSArray<NSMutableDictionary *> *)revisedEvent[@"places"];
for (NSMutableDictionary *dict in places) {
dict[@"postalCode"] = dict[@"zipcode"];
[dict removeObjectForKey:@"zipcode"];
dict[@"countryCode"] = dict[@"country_code"];
[dict removeObjectForKey:@"country_code"];
}
if (callback != nil) {
[callback callWithArguments:@[ revisedEvent ]];
}
[promise resolve:@[ revisedEvent ]];
if (callback != nil) {
[callback callWithArguments:@[ event ]];
}
[promise resolve:@[ event ]];
}

@end
Expand Down Expand Up @@ -502,35 +422,57 @@ - (BOOL)hasCompass

GETTER_IMPL(BOOL, hasCompass, HasCompass);

- (void)performGeo:(NSString *)direction address:(NSString *)address callback:(GeolocationCallback *)callback
{
[[TiApp app] startNetwork];

id aguid = TI_APPLICATION_GUID;
id sid = [[TiApp app] sessionId];

NSMutableDictionary *params = [@{
@"d" : direction,
@"aguid" : aguid,
@"mid" : [TiUtils appIdentifier],
@"sid" : sid,
@"q" : address,
} mutableCopy];

NSString *countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
if (countryCode) {
[params setValue:countryCode forKey:@"c"];
}
[callback start:params];
RELEASE_TO_NIL(params);
}

- (JSValue *)reverseGeocoder:(double)latitude longitude:(double)longitude withCallback:(JSValue *)callback
{
#ifndef __clang_analyzer__ // Ignore static analyzer error here, memory will be released. See TIMOB-19444
KrollPromise *promise = [[[KrollPromise alloc] initInContext:[self currentContext]] autorelease];
ReverseGeoCallback *rcb = [[ReverseGeoCallback alloc] initWithCallback:callback andPromise:promise];
[self performGeo:@"r" address:[NSString stringWithFormat:@"%f,%f", latitude, longitude] callback:rcb];

CLGeocoder *geoCoder = [[[CLGeocoder alloc] init] autorelease];
CLLocation *clLocation = [[[CLLocation alloc] initWithLatitude:latitude longitude:longitude] autorelease];
[geoCoder reverseGeocodeLocation:clLocation
preferredLocale:[NSLocale currentLocale]
completionHandler:^(NSArray<CLPlacemark *> *_Nullable placemarks, NSError *_Nullable error) {
if (error != nil) {
[rcb requestError:error];
} else {
NSMutableDictionary *events = [TiUtils dictionaryWithCode:0 message:nil];

NSMutableArray<NSMutableDictionary *> *places = [NSMutableArray array];
for (CLPlacemark *placemark in placemarks) {
NSMutableDictionary *place = [NSMutableDictionary dictionary];

if (placemark.thoroughfare) {
place[@"street"] = placemark.thoroughfare;
}
if (placemark.locality) {
place[@"city"] = placemark.locality;
}
if (placemark.administrativeArea) {
place[@"state"] = placemark.administrativeArea;
}
if (placemark.country) {
place[@"country"] = placemark.country;
}
if (placemark.postalCode) {
place[@"postalCode"] = placemark.postalCode;
}
if (placemark.ISOcountryCode) {
place[@"countryCode"] = placemark.ISOcountryCode;
}
if (placemark.location) {
place[@"latitude"] = @(placemark.location.coordinate.latitude);
place[@"longitude"] = @(placemark.location.coordinate.longitude);
}
if (placemark.postalAddress) {
place[@"address"] = [CNPostalAddressFormatter stringFromPostalAddress:placemark.postalAddress style:CNPostalAddressFormatterStyleMailingAddress];
}
[places addObject:place];
}
events[@"places"] = places;
[rcb requestSuccess:events];
}
}];
return promise.JSValue;
#endif
}
Expand All @@ -540,7 +482,23 @@ - (JSValue *)forwardGeocoder:(NSString *)address withCallback:(JSValue *)callbac
#ifndef __clang_analyzer__ // Ignore static analyzer error here, memory will be released. See TIMOB-19444
KrollPromise *promise = [[[KrollPromise alloc] initInContext:[self currentContext]] autorelease];
ForwardGeoCallback *fcb = [[ForwardGeoCallback alloc] initWithCallback:callback andPromise:promise];
[self performGeo:@"f" address:address callback:fcb];
CLGeocoder *geoCoder = [[[CLGeocoder alloc] init] autorelease];
[geoCoder geocodeAddressString:address
inRegion:nil
preferredLocale:[NSLocale currentLocale]
completionHandler:^(NSArray<CLPlacemark *> *_Nullable placemarks, NSError *_Nullable error) {
if (error != nil) {
[fcb requestError:error];
} else {
NSMutableDictionary *events = [TiUtils dictionaryWithCode:0 message:nil];
CLPlacemark *placemark = [placemarks firstObject]; // For forward geocode, take first object only
if (placemark.location) {
events[@"latitude"] = @(placemark.location.coordinate.latitude);
events[@"longitude"] = @(placemark.location.coordinate.longitude);
}
[fcb requestSuccess:events];
}
}];
return promise.JSValue;
#endif
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Resources/ti.geolocation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ describe('Titanium.Geolocation', () => {
should(data.places[0]).have.property('latitude').which.is.a.Number();
should(data.places[0]).have.property('longitude').which.is.a.Number();
should(data.places[0].country).be.oneOf('USA', 'United States of America', 'United States');
should(data.places[0].state).be.eql('California');
should(data.places[0].state).be.oneOf('California', 'CA');
should(data.places[0].countryCode).be.eql('US');
should(data.places[0]).have.property('city').which.is.a.String();
should(data.places[0]).have.property('address').which.is.a.String();
Expand All @@ -680,7 +680,7 @@ describe('Titanium.Geolocation', () => {
should(data.places[0]).have.property('latitude').which.is.a.Number();
should(data.places[0]).have.property('longitude').which.is.a.Number();
should(data.places[0].country).be.oneOf('USA', 'United States of America', 'United States');
should(data.places[0].state).be.eql('California');
should(data.places[0].state).be.oneOf('California', 'CA');
should(data.places[0].countryCode).be.eql('US');
should(data.places[0]).have.property('city').which.is.a.String();
should(data.places[0]).have.property('address').which.is.a.String();
Expand Down

0 comments on commit 943b93b

Please sign in to comment.