MapKit - Make route line follow streets when map zoomed in - iphone

I wrote simple application which draws route between two locations on MapKit. I am using Google Map API. I used resources I found online and here's the code I am using to make request to Google:
_httpClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://maps.googleapis.com/"]];
[_httpClient registerHTTPOperationClass: [AFJSONRequestOperation class]];
[_httpClient setDefaultHeader:#"Accept" value:#"application/json"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:[NSString stringWithFormat:#"%f,%f", coordinate.latitude, coordinate.longitude] forKey:#"origin"];
[parameters setObject:[NSString stringWithFormat:#"%f,%f", endCoordinate.latitude, endCoordinate.longitude] forKey:#"destination"];
[parameters setObject:#"true" forKey:#"sensor"];
NSMutableURLRequest *request = [_httpClient requestWithMethod:#"GET" path: #"maps/api/directions/json" parameters:parameters];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
NSInteger statusCode = operation.response.statusCode;
if (statusCode == 200)
{
NSLog(#"Success: %#", operation.responseString);
}
else
{
NSLog(#"Status code = %d", statusCode);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", operation.responseString);
}
];
[_httpClient enqueueHTTPRequestOperation:operation];
This works flawlessly. When I run this and try to show route between LA and Chicago, here's how it looks like:
BUT. When I zoom map to street level, here's how route looks like:
Does anyone know how can I achieve that route I am drawing follows streets when map is zoomed? I'd like route to show exact path through the streets. I don't know if some additional parameter needs to be added to my request to Google.
Any help or advice would be great. Many thanks in advance!
[edit #1: Adding request URL and response from Google]
My request URL after creating operation object from code above looks like this:
http://maps.googleapis.com/maps/api/directions/json?sensor=true&destination=34%2E052360,-118%2E243560&origin=41%2E903630,-87%2E629790
Just paste that URL to your browser and you will see the JSON data Google sends as the response which I get in my code also.
[edit #2: Parsing answer from Google and building the path]
- (void)parseResponse:(NSData *)response
{
NSDictionary *dictResponse = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableContainers error:nil];
NSArray *routes = [dictResponse objectForKey:#"routes"];
NSDictionary *route = [routes lastObject];
if (route)
{
NSString *overviewPolyline = [[route objectForKey: #"overview_polyline"] objectForKey:#"points"];
_path = [self decodePolyLine:overviewPolyline];
}
}
- (NSMutableArray *)decodePolyLine:(NSString *)encodedStr
{
NSMutableString *encoded = [[NSMutableString alloc] initWithCapacity:[encodedStr length]];
[encoded appendString:encodedStr];
[encoded replaceOccurrencesOfString:#"\\\\" withString:#"\\"
options:NSLiteralSearch
range:NSMakeRange(0, [encoded length])];
NSInteger len = [encoded length];
NSInteger index = 0;
NSMutableArray *array = [[NSMutableArray alloc] init];
NSInteger lat=0;
NSInteger lng=0;
while (index < len)
{
NSInteger b;
NSInteger shift = 0;
NSInteger result = 0;
do
{
b = [encoded characterAtIndex:index++] - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
NSInteger dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do
{
b = [encoded characterAtIndex:index++] - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
NSInteger dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
lng += dlng;
NSNumber *latitude = [[NSNumber alloc] initWithFloat:lat * 1e-5];
NSNumber *longitude = [[NSNumber alloc] initWithFloat:lng * 1e-5];
CLLocation *location = [[CLLocation alloc] initWithLatitude:[latitude floatValue] longitude:[longitude floatValue]];
[array addObject:location];
}
return array;
}

It looks like Google is not giving you all the points, or you are not looking at all the points. Actually, I'd expect polylines between placemarks, not only placemarks like you seem to have (with a straight line).
Check DirectionsStatus in the response to see if you are limited
Provide the json data that Google sends back.
I'm not so sure they use a radically different Mercator projection from the one used by Google.

I believe that the projection used by MapKit is different than that used by Google Maps.
MapKit uses Cylindrical Mercator, while Google uses a variant of the Mercator Projection.
Converting Between Coordinate Systems
Although you normally specify
points on the map using latitude and longitude values, there may be
times when you need to convert to and from other coordinate systems.
For example, you typically use map points when specifying the shape of
overlays.
Quoted from Apple:

Related

App crash on creating NSdictionary for Json in IOS

i am working on in app purchase app in which i want to verify purchase transaction receipt with my developer server along with other information like Device VendorID, software version , device name and product id wish to buy.
I am using NSDictionary to create Json but it crash when i try to add
NSMutableArray *MainOBJ = [NSMutableArray arrayWithObjects:IDdict,deviceData,kMyFeatureIdentifier,jsonObjectString,nil];
in which IDdict is device id string , deviceData is dictionary which content device information like name , software version and kMyFeatureIdentifier is product id NSstring wish to buy. and jsonObjectString is encoded transaction receipt string.
here is my code
- (void)verifyReceipt:(SKPaymentTransaction *)transaction {
//TODO
// currently working on JSON to send to server .
NSLog(#"In verifyReceipt method");
jsonObjectString = [self encode:(uint8_t*)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
// jsonObjectString=#"TESTING";
NSLog(#"Json Object encoded receipt is %#",jsonObjectString);
NSString *IDdict = [[NSString alloc ]initWithString:[UIDevice currentDevice].identifierForVendor.UUIDString]; // Device UDID
NSArray *objects = [NSArray arrayWithObjects:#"NULL",[[UIDevice currentDevice] model],[[UIDevice currentDevice] name],nil];
NSArray *keys = [NSArray arrayWithObjects:#"serial",#"constructor",#"name",nil];
NSDictionary *deviceData = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; // Device information like name , device model , serial number
NSLog(#"Json question dict created");
//TODO: **It crash here**
NSMutableArray *MainOBJ = [NSMutableArray arrayWithObjects:IDdict,deviceData,kMyFeatureIdentifier,jsonObjectString,nil]; // purchased Item ID of previous item
NSMutableArray *MainKeys = [NSMutableArray arrayWithObjects:#"ID",#"device",#"video","#receiptData", nil];
NSMutableDictionary *MainDict = [NSMutableDictionary dictionaryWithObjects:MainOBJ forKeys:MainKeys]; // final string of data
NSLog(#"Json Main dict created");
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:MainDict options:NSJSONWritingPrettyPrinted error:&error];
NSString *resultAsString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"Purchase product Json string:\n%#", resultAsString);
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[[NSURL alloc] initWithString:#"http://xyz/dev.php/video/verifyReceipt"]];
[request setPostValue:resultAsString forKey:#"verify"];
[request setDidFinishSelector:#selector(requestDone:)];
[request setTimeOutSeconds:120];
[request setDelegate:self];
[request setNumberOfTimesToRetryOnTimeout:2];
[request setDownloadProgressDelegate:self];
request.showAccurateProgress = YES;
i got "NSLog(#"Json question dict created");" in my log after it crashes.
and my expected json format is like this
{
"ID" : "E6E95901-006B-4569-8D2B-FA29A0307F80",
"device" : {
"name" : "iPad Simulator",
"constructor" : "iPad Simulator",
"serial" : "NULL"
},
"video" : "com.amm.happyclip.4445Video",
"receiptData":"DSKLFKSGERPOKFLJGMZEKLEMSERLKEMZTRKGDGFLefklezkgem"
}
Screenshot of error
Any suggestion , help appreciated Thank you
Encoding method which return string and i just assign that string to "jsonObjectString"
- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *output = (uint8_t *)data.mutableBytes;
for (NSInteger i = 0; i < length; i += 3) {
NSInteger value = 0;
for (NSInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger index = (i / 3) * 4;
output[index + 0] = table[(value >> 18) & 0x3F];
output[index + 1] = table[(value >> 12) & 0x3F];
output[index + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[index + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}
Maybe each object in MainOBJ is nil and if you create dictionary with nil object it crashes your app, or they are already released.
try to log objects from MainOBJ array.

Add annotations to a MapView in a loop

I am trying to add some annotations to my mkMapView in this loop:
for (PFObject *fetchedCompany in companyArray)
{
Store *store = [[Store alloc] initWithObject:fetchedCompany];
[self loadStore:store];
[store release];
}
- (void) loadStore:(Store *) store {
CLLocationCoordinate2D location = [self getLocationFromAddressString:store.address];
MapViewAnnotation *mapAnnotation = [[MapViewAnnotation alloc] initWithTitle:store.name coordinate:location];
[self.indexMapView addAnnotation:mapAnnotation];
}
And this the getLocationFromAddressString method that convert the address to a location using google api:
-(CLLocationCoordinate2D) getLocationFromAddressString:(NSString*) addressStr {
NSString *urlStr = [NSString stringWithFormat:#"http://maps.google.com/maps/geo?q=%#&output=csv", [addressStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *error = nil;
NSString *locationStr = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlStr] encoding:NSUTF8StringEncoding error:&error];
NSArray *items = [locationStr componentsSeparatedByString:#","];
double lat = 0.0;
double lon = 0.0;
if([items count] >= 4 && [[items objectAtIndex:0] isEqualToString:#"200"]) {
lat = [[items objectAtIndex:2] doubleValue];
lon = [[items objectAtIndex:3] doubleValue];
}
else {
NSLog(#"Address, %# not found: Error %#",addressStr, [items objectAtIndex:0]);
}
CLLocationCoordinate2D location;
location.latitude = lat;
location.longitude = lon;
return location;
}
When I send just one location always it works perfectly, but when I add annotations in my loop sometimes some locations could not be recognized and I got the error from my NSLog:
Address, Vallgatan 3 GĂ–TEBORG not found: Error 620
The thing is I am pretty sure about the addresses because when I tried them without loop they worked. Do you know how can I solve the problem?
You've submitted too many queries and your request got rejected. See Geocoding API V2 docs.
620: G_GEO_TOO_MANY_QUERIES
"The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly"

How can I parse this xml using GDataXML parser?

<list>
<OrderData HASH="1408108039"></OrderData>
<OrderData HASH="208524692">
<id>97</id>
<customer>
<CustomerData HASH="2128670187"></CustomerData></customer>
<billingAddress></billingAddress><deliveryAddress></deliveryAddress>
<orderDetail>
<list>
<OrderDetailData HASH="516790072"></OrderDetailData>
<OrderDetailData HASH="11226247"></OrderDetailData>
<OrderDetailData HASH="11226247"></OrderDetailData>
</list>
</orderDetail>
<log/>
</OrderData>
<OrderData HASH="1502226778"></OrderData>
</list>
I cannot find a solution to find the number of OrderDetailData elements? I also read http://iphonebyradix.blogspot.com/2011/03/using-gdata-to-parse-xml-file.html this url.
Thanks in advance.
Edit:
I am explaining my requirement again. In this xml there will be multiple OrderData element. Now I have to count the number of OrderDetailData elemnts from a particular OrderData element. Suppose that, according to my xml, the current parsed xml has one OrderData element, named id and its value is 97. Now, I have to count how many OrderDetailData elements are contained in the OrderData(whichid` is 97).
This is a simple example how to retrieve some data. This example is very simple and not use XPath expression. I suggest you first understand how it works and then use XPath expression. In my opinion it is not useful to use XPath expression if you cannot understand how the parser works.
NSString* path = [[NSBundle mainBundle] pathForResource:#"test2" ofType:#"xml"];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:path];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData
options:0 error:&error];
//NSLog(#"%#", doc.rootElement); // print the whole xml
NSArray *orderDataArray = [doc.rootElement elementsForName:#"OrderData"];
for (GDataXMLElement *orderDataElement in orderDataArray) {
if([orderDataElement childCount] > 0)
{
NSString *attributeForOrderDataElement = [(GDataXMLElement *) [orderDataElement attributeForName:#"HASH"] stringValue];
NSLog(#"attributeForOrderDataElement has value %#", attributeForOrderDataElement);
GDataXMLElement* idElement = (GDataXMLElement*)[[orderDataElement elementsForName:#"id"] objectAtIndex:0];
NSLog(#"id has value %#", idElement.stringValue);
GDataXMLElement* orderDetailElement = (GDataXMLElement*)[[orderDataElement elementsForName:#"orderDetail"] objectAtIndex:0];
GDataXMLElement* listElement = (GDataXMLElement*)[[orderDetailElement elementsForName:#"list"] objectAtIndex:0];
NSArray* orderDetailDataArray = [listElement elementsForName:#"OrderDetailData"];
int count = 0;
for (GDataXMLElement *orderDetailDataElement in orderDetailDataArray) {
NSString *attributeForOrderDetailDataElement = [(GDataXMLElement *) [orderDetailDataElement attributeForName:#"HASH"] stringValue];
NSLog(#"attributeForOrderDetailDataElement has value %#", attributeForOrderDetailDataElement);
count++;
}
NSLog(#"%d", count);
}
}
[doc release];
[xmlData release];
This is the output console:
attributeForOrderDataElement has value 208524692 <-- HASH value
id has value 97 <-- id value
attributeForOrderDetailDataElement has value 516790072 <-- HASH value
attributeForOrderDetailDataElement has value 11226247
attributeForOrderDetailDataElement has value 11226247
3 <-- the count
Hope it helps.
Edit
test2.xml contains your file but you could pass it as a string. You can also pass as parameters as string like the following:
NSString* xmlString = #"<list>"
"<OrderData HASH=\"1408108039\"></OrderData>"
"<OrderData HASH=\"208524692\">"
"<id>97</id>"
"<customer>"
"<CustomerData HASH=\"2128670187\"></CustomerData>"
"</customer>"
"<billingAddress></billingAddress>"
"<deliveryAddress></deliveryAddress>"
"<orderDetail>"
"<list>"
"<OrderDetailData HASH=\"516790072\"></OrderDetailData>"
"<OrderDetailData HASH=\"11226247\"></OrderDetailData>"
"<OrderDetailData HASH=\"11226247\"></OrderDetailData>"
"</list>"
"</orderDetail>"
"<log/>"
"</OrderData>"
"<OrderData HASH=\"1502226778\"></OrderData>"
"</list>";
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithXMLString:xmlString options:0 error:&error];
I found TouchXML easy in parsing and you can directly access data needed from xml
First of all Download TouchXML and add libxml2.dylib framework to your project.
change buildsetting for "Header Search Path" and add "/usr/include/libxml2"
Import TouchXML.h to your file
//Access document
CXMLDocument *parserDoc = [[CXMLDocument alloc] initWithContentsOfURL:url options:0 error:&err];
//Access root element and access children in heirarchy
CXMLElement *root = [parserDoc rootElement];
NSArray *places = [[[root children] objectAtIndex:0] children];
Else
//Access node by node
NSString *location =[[[[[parserDoc nodesForXPath:#"/xml_api_reply/weather/forecast_information/city" error:nil] objectAtIndex:0] attributeForName:#"data"] stringValue] retain];
If the OrderDetailData is present in 2nd object of array all the time, u can use the below
NSArray* arr = [[XMLelement elementForName:#"list"] elementsForName:#"OrderData"];
NSXMLElement* listElement = [[[arr objectAtIndex:1] elementForName:#"orderDetail"] elementForName:#"list"];
NSArray* orderDetailArray* = [listElement elementsForName:#"OrderDetailData"];
In this u can get the OrderDetailData elements in the array, and u can parse the data in loop. to get HASH value use.
NSString* hash = [[orderDetailArray objectAtIndex:0] stringValueForAttribute:#"HASH"];
This is the below code, i have tried. I got the right output.. check out
NSString* xmlString = #"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<list>"
"<OrderData HASH=\"1408108039\"></OrderData>"
"<OrderData HASH=\"208524692\">"
"<id>97</id>"
"<customer>"
"<CustomerData HASH=\"2128670187\"></CustomerData></customer>"
"<billingAddress></billingAddress><deliveryAddress></deliveryAddress>"
"<orderDetail>"
"<list>"
"<OrderDetailData HASH=\"516790072\"></OrderDetailData>"
"<OrderDetailData HASH=\"11226247\"></OrderDetailData>"
"<OrderDetailData HASH=\"11226247\"></OrderDetailData>"
"</list>"
"</orderDetail>"
"<log/></OrderData>"
"<OrderData HASH=\"1502226778\"></OrderData>"
"</list>";
NSXMLDocument* doc = [[NSXMLDocument alloc] initWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSXMLElement* rootElement = [doc rootElement];
NSArray* arr = [rootElement elementsForName:#"OrderData"];
NSXMLElement* orderDataElement;
for (int i = 0; i < [arr count]; i++)
{
int Id = 0;
Id = [[arr objectAtIndex:i] integerValueForNode:#"id"];
if (Id != 0)
{
orderDataElement = [arr objectAtIndex:i];
break;
}
}
NSArray* orderDetailArray = [[[orderDataElement elementForName:#"orderDetail"]
elementForName:#"list"]
elementsForName:#"OrderDetailData"];
for (int j = 0; j < [orderDetailArray count]; j++)
{
NSLog(#"%#", [[orderDetailArray objectAtIndex:j] stringValueForAttribute:#"HASH"]);
}

How to deal with different types of information in a text file

I'm downloading a text file and using the information from it to create several objects.
I'm using the following code to achieve this:
NSString *fileContents = [NSString stringWithContentsOfURL: readerView.url
encoding: NSUTF8StringEncoding
error: NULL];
NSArray *lines = [fileContents componentsSeparatedByString:#"\n"];
for(NSString *line in lines)
{
NSArray *params = [line componentsSeparatedByString:#","];
NSString *label1 = [params objectAtIndex:0];
NSString *label2 = [params objectAtIndex:1];
float weight = [[params objectAtIndex:2] floatValue];
int x1 = [[params objectAtIndex:3] intValue];
int y1 = [[params objectAtIndex:4] intValue];
int x2 = [[params objectAtIndex:5] intValue];
int y2 = [[params objectAtIndex:6] intValue];
int type = [[params objectAtIndex:7] intValue];
[graph addComponents:label1:label2 :weight :x1 :y1 :x2 :y2 :type];
}
An example of a line in the text file is like so:
A,B,6.0,270,190,150,190,1
So it's pretty basic. What I wanted to do though was for either the first or last line of the text file, have a URL which would trigger another download for an image. I can't think of what would be the best way to achieve this. In my mind I'm thinking something like this in pseudo code:
If(line = first line)
trigger download
else
go through params.
KennyTM is right, but I would do it the other way around because , is a valid URL character for some schemes and invalid for others, so it's possible (if unlikely) to get a false positive. Parsing the line as a URL will return nil if the line is not a valid URL and then you can parse the line as the formatted data you expect in absence of a URL.
for(NSString *line in lines) {
NSURL* url = [NSURL URLWithString:line];
if (url) {
// trigger download...
} else {
NSArray *params = [line componentsSeparatedByString:#","];
// Do stuff with params
}
}
I would try to check if the line is in the valid params format. If not, try to parse as URL.
Assuming the URL doesn't contain exactly 7 commas,
for(NSString *line in lines) {
NSArray *params = [line componentsSeparatedByString:#","];
if ([params count] == 8) {
// go through params
} else {
NSURL* url = [NSURL URLWithString:line];
if (url) {
// trigger download...
}
}
}
This way the URL can be placed anywhere in the file.

NSString: fastes way of splitting

my question is a performance question.
I need to split a huge NSString into coordinates. The string has this format:
#"coordx,coordy coordx,coordy coordx,coordy (...)"
My method to parse this is:
-(NSMutableArray*) parsePath:(NSString*) pathString
{
// first split between the coordinates
NSArray* path = [pathString componentsSeparatedByString:#" "];
NSMutableArray* coords = [[NSMutableArray alloc] init];
static NSString *seperator = #",";
NSArray *coord;
for(NSString *coordString in path)
{
coord = [coordString componentsSeparatedByString:seperator];
Coordinate *c = [[Coordinate alloc]init];
c.x = [[coord objectAtIndex:0] intValue];
c.y = [[coord objectAtIndex:1] intValue];
[coords addObject:c];
[c release];
}
return coords;
}
So, is there any way to make this faster?
Thanks!
Sebastian
Using NSScanner is probably faster.
http://developer.apple.com/iphone/library/documentation/cocoa/reference/Foundation/Classes/NSScanner_Class/Reference/Reference.html