iOS : Access JPEG metadata at Marker APP3 - iphone

I have been using the Exif tags to store some data on jpeg files in the following manner:
CGImageSourceRef source = CGImageSourceCreateWithURL(baseURL, NULL);
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
[EXIFDictionary setObject:[NSString stringWithFormat:#"%d",tag] forKey:(NSString *)kCGImagePropertyExifUserComment];
Now, I would like to use a custom Application Marker (APP3 at 0xFFE3) instead of Exif Marker.
(Refer - http://www.ozhiker.com/electronics/pjmt/jpeg_info/app_segments.html)
Could someone point me in the right direction.
PS: I am using a jailbroken iPad for this app.

Okay, it seems we have to go via file handler way for this. Here is what I did, though there may be a better way to do this.
Create a File handle:
NSString *filePath = currentImageObject.myFilePath;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
if(!fileHandle){
return;
}
[fileHandle seekToEndOfFile];
unsigned long long eofOffset = [fileHandle offsetInFile];
Then iterate the file contents till you find a desired tag:
BOOL markerFound = NO;
BOOL dqtFound = NO;
while ((!markerFound) && (!dqtFound) && ([fileHandle offsetInFile] < eofOffset)) {
currentOffset += 1;
[fileHandle seekToFileOffset:currentOffset];
NSData *markerData = [fileHandle readDataOfLength:1];
currentOffset += 1;
NSInteger markerValue = (unsigned char)*(unsigned char *)[markerData bytes];
if (0xe0 == markerValue) {
currentOffset += 14;
[fileHandle seekToFileOffset:currentOffset];
NSData *xThumbnailData = [fileHandle readDataOfLength:1];
currentOffset += 1;
NSData *yThumbnailData = [fileHandle readDataOfLength:1];
currentOffset += 1;
NSInteger xThumbnail = (unsigned char)*(unsigned char *)[xThumbnailData bytes];
NSInteger yThumbnail = (unsigned char)*(unsigned char *)[yThumbnailData bytes];
NSInteger thumbnailSize = 3 * xThumbnail * yThumbnail;
currentOffset += thumbnailSize;
[fileHandle seekToFileOffset:currentOffset];
} else if (0xe3 == markerValue) {
markerFound = YES;
break;
} else if (0xdb == markerValue) {
dqtFound = YES;
break;
} else {
NSData *lengthData = [fileHandle readDataOfLength:2];
currentOffset += 2;
NSInteger length = (unsigned short)*(unsigned short *)[lengthData bytes];
length = NSSwapBigShortToHost(length);
length -= 2;
currentOffset += length;
[fileHandle seekToFileOffset:currentOffset];
}
}
This gives you the offset to the APP3 marker, incase you need to add your own app3 marker you can use a similar approach.

Related

How to decode the Google Directions API polylines field into lat long points in Objective-C for iPhone?

I want to draw routes on a map corresponding to directions JSON which I am getting through the Google Directions API: https://developers.google.com/maps/documentation/directions/start
I have figured out how to extract the latitude and longitude from the steps field, however this doesn't follow curvy roads very well. I think what I need is to decode the polyline information, I found Googles instructions on how to encode polylines: https://developers.google.com/maps/documentation/utilities/polylinealgorithm
I did find some code here for Android and also Javascript on decoding the polylines, for example:
Map View draw directions using google Directions API - decoding polylines
android get and parse Google Directions
But I can't find same for Objective-C iPhone code, can anybody help me with this? I'm sure I can do it myself if I have to, but it sure would save me some time if it's already available somewhere.
EDIT: the key here is being able to decode the base64 encoding on a character by character basis. To be more specific, I get something like this in JSON from Google which is encoded using base64 encoding among other things:
... "overview_polyline" : {
"points" : "ydelDz~vpN_#NO#QEKWIYIIO?YCS#WFGBEBICCAE?G#y#RKBEBEBAD?HTpB#LALALCNEJEFSP_#LyDv#aB\\GBMB"
},
...
Note: I should mention that this question refers to Google Maps API v1, it is much easier to do this in v2 using GMSPolyLine polyLineWithPath as many answers below will tell you (thanks to #cdescours).
I hope it's not against the rules to link to my own blog post if it's relevant to the question, but I've solved this problem in the past. Stand-alone answer from linked post:
#implementation MKPolyline (MKPolyline_EncodedString)
+ (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
const char *bytes = [encodedString UTF8String];
NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger idx = 0;
NSUInteger count = length / 4;
CLLocationCoordinate2D *coords = calloc(count, sizeof(CLLocationCoordinate2D));
NSUInteger coordIdx = 0;
float latitude = 0;
float longitude = 0;
while (idx < length) {
char byte = 0;
int res = 0;
char shift = 0;
do {
byte = bytes[idx++] - 63;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1));
latitude += deltaLat;
shift = 0;
res = 0;
do {
byte = bytes[idx++] - 0x3F;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1));
longitude += deltaLon;
float finalLat = latitude * 1E-5;
float finalLon = longitude * 1E-5;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(finalLat, finalLon);
coords[coordIdx++] = coord;
if (coordIdx == count) {
NSUInteger newCount = count + 10;
coords = realloc(coords, newCount * sizeof(CLLocationCoordinate2D));
count = newCount;
}
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:coordIdx];
free(coords);
return polyline;
}
#end
The best and lightest answer should be to use the method provided by Google in the framework :
[GMSPolyline polylineWithPath:[GMSPath pathFromEncodedPath:encodedPath]]
If you are working with Google Map on iOS and want to draw the route including the polylines, google itself provides an easier way to get the GMSPath from polyline as,
GMSPath *pathFromPolyline = [GMSPath pathFromEncodedPath:polyLinePoints];
Here is the complete code:
+ (void)callGoogleServiceToGetRouteDataFromSource:(CLLocation *)sourceLocation toDestination:(CLLocation *)destinationLocation onMap:(GMSMapView *)mapView_{
NSString *baseUrl = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=false", sourceLocation.coordinate.latitude, sourceLocation.coordinate.longitude, destinationLocation.coordinate.latitude, destinationLocation.coordinate.longitude];
NSURL *url = [NSURL URLWithString:[baseUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"Url: %#", url);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
GMSMutablePath *path = [GMSMutablePath path];
NSError *error = nil;
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSArray *routes = [result objectForKey:#"routes"];
NSDictionary *firstRoute = [routes objectAtIndex:0];
NSDictionary *leg = [[firstRoute objectForKey:#"legs"] objectAtIndex:0];
NSArray *steps = [leg objectForKey:#"steps"];
int stepIndex = 0;
CLLocationCoordinate2D stepCoordinates[1 + [steps count] + 1];
for (NSDictionary *step in steps) {
NSDictionary *start_location = [step objectForKey:#"start_location"];
stepCoordinates[++stepIndex] = [self coordinateWithLocation:start_location];
[path addCoordinate:[self coordinateWithLocation:start_location]];
NSString *polyLinePoints = [[step objectForKey:#"polyline"] objectForKey:#"points"];
GMSPath *polyLinePath = [GMSPath pathFromEncodedPath:polyLinePoints];
for (int p=0; p<polyLinePath.count; p++) {
[path addCoordinate:[polyLinePath coordinateAtIndex:p]];
}
if ([steps count] == stepIndex){
NSDictionary *end_location = [step objectForKey:#"end_location"];
stepCoordinates[++stepIndex] = [self coordinateWithLocation:end_location];
[path addCoordinate:[self coordinateWithLocation:end_location]];
}
}
GMSPolyline *polyline = nil;
polyline = [GMSPolyline polylineWithPath:path];
polyline.strokeColor = [UIColor grayColor];
polyline.strokeWidth = 3.f;
polyline.map = mapView_;
}];
}
+ (CLLocationCoordinate2D)coordinateWithLocation:(NSDictionary*)location
{
double latitude = [[location objectForKey:#"lat"] doubleValue];
double longitude = [[location objectForKey:#"lng"] doubleValue];
return CLLocationCoordinate2DMake(latitude, longitude);
}
Swift 3.0
let polyline = GMSPolyline(path: GMSPath.init(fromEncodedPath: encodedPolyline))
Python Implementation
This isn't in Objective-C, but this thread is where Google drops you if you're looking to decode polyline strings from Google Maps. In case anyone else needs it (much like I did), here's a Python implementation for decoding polyline strings. This is ported from the Mapbox JavaScript version; more info found on my repo page.
def decode_polyline(polyline_str):
index, lat, lng = 0, 0, 0
coordinates = []
changes = {'latitude': 0, 'longitude': 0}
# Coordinates have variable length when encoded, so just keep
# track of whether we've hit the end of the string. In each
# while loop iteration, a single coordinate is decoded.
while index < len(polyline_str):
# Gather lat/lon changes, store them in a dictionary to apply them later
for unit in ['latitude', 'longitude']:
shift, result = 0, 0
while True:
byte = ord(polyline_str[index]) - 63
index+=1
result |= (byte & 0x1f) << shift
shift += 5
if not byte >= 0x20:
break
if (result & 1):
changes[unit] = ~(result >> 1)
else:
changes[unit] = (result >> 1)
lat += changes['latitude']
lng += changes['longitude']
coordinates.append((lat / 100000.0, lng / 100000.0))
return coordinates
- (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
const char *bytes = [encodedString UTF8String];
NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger idx = 0;
NSUInteger count = length / 4;
CLLocationCoordinate2D *coords = calloc(count, sizeof(CLLocationCoordinate2D));
NSUInteger coordIdx = 0;
float latitude = 0;
float longitude = 0;
while (idx < length) {
char byte = 0;
int res = 0;
char shift = 0;
do {
byte = bytes[idx++] - 63;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1));
latitude += deltaLat;
shift = 0;
res = 0;
do {
byte = bytes[idx++] - 0x3F;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1));
longitude += deltaLon;
float finalLat = latitude * 1E-5;
float finalLon = longitude * 1E-5;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(finalLat, finalLon);
coords[coordIdx++] = coord;
if (coordIdx == count) {
NSUInteger newCount = count + 10;
coords = realloc(coords, newCount * sizeof(CLLocationCoordinate2D));
count = newCount;
}
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:coordIdx];
free(coords);
return polyline;
}
- (MKPolygonRenderer *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
// MKPolygonRenderer *polylineView = [[MKPolygonRenderer alloc] initWithOverlay:overlay];
MKPolylineView *polylineView = [[MKPolylineView alloc] initWithPolyline:overlay];
polylineView.strokeColor = [UIColor redColor];
polylineView.lineWidth = 4.0;
[self zoomToPolyLine:mapview polyline:overlay animated:YES];
return polylineView;
}
-(void)zoomToPolyLine: (MKMapView*)map polyline: (MKPolyline*)polyline animated: (BOOL)animated
{
[map setVisibleMapRect:[polyline boundingMapRect] edgePadding:UIEdgeInsetsMake(25.0, 25.0, 25.0, 25.0) animated:animated];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
currlong = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
currlt = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
}
NSString *origin = [NSString stringWithFormat:#"%#%#%#",currlt,#",",currlong];
//I have just mention static location
NSString *drivein = #"23.0472963,72.52757040000006";
NSString *apikey = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/directions/json?origin=%#&destination=%#",origin,drivein];
NSURL *url = [NSURL URLWithString:apikey];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if(!error)
{
NSData *data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
NSArray *routesArray = [jsonResponse objectForKey:#"routes"];
NSLog(#"route array %#",routesArray);
if ([routesArray count] > 0)
{
NSDictionary *routeDict = [routesArray objectAtIndex:0];
NSDictionary *routeOverviewPolyline = [routeDict objectForKey:#"overview_polyline"];
NSString *points = [routeOverviewPolyline objectForKey:#"points"];
MKPolyline *line = [self polylineWithEncodedString:points];
[mapview addOverlay:line];
}
}
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(currentLocation.coordinate, 500, 500);
MKCoordinateRegion adjustedRegion = [mapview regionThatFits:viewRegion];
[mapview setRegion:adjustedRegion animated:YES];
mapview.showsUserLocation = YES;
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = currentLocation.coordinate;
point.title = #"Your current Locations";
point.subtitle = #"You are here!";
[mapview addAnnotation:point];
[locationmanger stopUpdatingLocation];
}
Here's how I do it in my directions app. keyPlace is your destination object
- (void)getDirections {
CLLocation *newLocation;// = currentUserLocation;
MKPointAnnotation *annotation = [[[MKPointAnnotation alloc] init] autorelease];
annotation.coordinate = CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude);
annotation.title = #"You";
[mapView addAnnotation:annotation];
CLLocationCoordinate2D endCoordinate;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=false&mode=walking", newLocation.coordinate.latitude, newLocation.coordinate.longitude, keyPlace.lat, keyPlace.lon]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
if ([[request.responseString.JSONValue valueForKey:#"status"] isEqualToString:#"ZERO_RESULTS"]) {
[[[[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not route path from your current location"
delegate:nil
cancelButtonTitle:#"Close"
otherButtonTitles:nil, nil] autorelease] show];
self.navigationController.navigationBar.userInteractionEnabled = YES;
return;
}
int points_count = 0;
if ([[request.responseString.JSONValue objectForKey:#"routes"] count])
points_count = [[[[[[request.responseString.JSONValue objectForKey:#"routes"] objectAtIndex:0] objectForKey:#"legs"] objectAtIndex:0] objectForKey:#"steps"] count];
if (!points_count) {
[[[[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not route path from your current location"
delegate:nil
cancelButtonTitle:#"Close"
otherButtonTitles:nil, nil] autorelease] show];
self.navigationController.navigationBar.userInteractionEnabled = YES;
return;
}
CLLocationCoordinate2D points[points_count * 2];
int j = 0;
NSArray *steps = nil;
if (points_count && [[[[request.responseString.JSONValue objectForKey:#"routes"] objectAtIndex:0] objectForKey:#"legs"] count])
steps = [[[[[request.responseString.JSONValue objectForKey:#"routes"] objectAtIndex:0] objectForKey:#"legs"] objectAtIndex:0] objectForKey:#"steps"];
for (int i = 0; i < points_count; i++) {
double st_lat = [[[[steps objectAtIndex:i] objectForKey:#"start_location"] valueForKey:#"lat"] doubleValue];
double st_lon = [[[[steps objectAtIndex:i] objectForKey:#"start_location"] valueForKey:#"lng"] doubleValue];
//NSLog(#"lat lon: %f %f", st_lat, st_lon);
if (st_lat > 0.0f && st_lon > 0.0f) {
points[j] = CLLocationCoordinate2DMake(st_lat, st_lon);
j++;
}
double end_lat = [[[[steps objectAtIndex:i] objectForKey:#"end_location"] valueForKey:#"lat"] doubleValue];
double end_lon = [[[[steps objectAtIndex:i] objectForKey:#"end_location"] valueForKey:#"lng"] doubleValue];
if (end_lat > 0.0f && end_lon > 0.0f) {
points[j] = CLLocationCoordinate2DMake(end_lat, end_lon);
endCoordinate = CLLocationCoordinate2DMake(end_lat, end_lon);
j++;
}
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:points count:points_count * 2];
[mapView addOverlay:polyline];
}
#pragma mark - MapKit
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *annView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"] autorelease];
annView.canShowCallout = YES;
annView.animatesDrop = YES;
return annView;
}
- (MKOverlayView *)mapView:(MKMapView *)mapView
viewForOverlay:(id<MKOverlay>)overlay {
MKPolylineView *overlayView = [[[MKPolylineView alloc] initWithOverlay:overlay] autorelease];
overlayView.lineWidth = 5;
overlayView.strokeColor = [UIColor purpleColor];
overlayView.fillColor = [[UIColor purpleColor] colorWithAlphaComponent:0.5f];
return overlayView;
}
In case anyone would need the decoding-code in VBA, here is a (working) port:
Function decodeGeopoints(encoded)
decodeGeopoints = ""
' This code is a port to VBA from code published here:
' http://blog.synyx.de/2010/06/routing-driving-directions-on-android-part-1-get-the-route/
'//decoding
'List poly = new ArrayList();
'// replace two backslashes by one (some error from the transmission)
'encoded = encoded.replace("\\", "\");
encoded = Replace(encoded, "\\", "\")
'int index = 0, len = encoded.length();
Dim index As Long
index = 0
Dim leng As Long
leng = Len(encoded)
'int lat = 0, lng = 0;
Dim lat As Long
lat = 0
Dim lng As Long
lng = 0
'while (index < len) {
While (index < leng)
'int b, shift = 0, result = 0;
Dim b, shift, result As Long
b = 0
shift = 0
result = 0
'do {
Do
'b = encoded.charAt(index++) - 63;
index = index + 1
b = Asc(Mid(encoded, index, 1)) - 63
'result |= (b & 0x1f) << shift;
result = result Or ((b And 31) * (2 ^ shift))
'shift += 5;
shift = shift + 5
'} while (b >= 0x20);
Loop While (b >= 32)
'int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
Dim dlat As Long
If (result And 1) <> 0 Then
dlat = Not Int(result / 2)
Else
dlat = Int(result / 2)
End If
'lat += dlat;
lat = lat + dlat
'shift = 0;
shift = 0
'result = 0;
result = 0
'do {
Do
'b = encoded.charAt(index++) - 63;
index = index + 1
b = Asc(Mid(encoded, index, 1)) - 63
'result |= (b & 0x1f) << shift;
result = result Or ((b And 31) * (2 ^ shift))
'shift += 5;
shift = shift + 5
'} while (b >= 0x20);
Loop While (b >= 32)
'int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
Dim dlng As Long
If (result And 1) <> 0 Then
dlng = Not Int(result / 2)
Else
dlng = Int(result / 2)
End If
'lng += dlng;
lng = lng + dlng
'GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));
Dim myLat, myLng As Double
myLat = (lat / 100000)
'myLat = myLat * 1000000
myLng = (lng / 100000)
'myLng = myLng * 1000000
'poly.add(p);
decodeGeopoints = decodeGeopoints & Comma2Dot(myLng) & "," & Comma2Dot(myLat) & ",0 "
'}
Wend
End Function
For Google maps it already have a straight forward method , polylineWithPath, so I prefer this snippet.
-(void)drawPathFrom:(CLLocation*)source toDestination:(CLLocation*)destination{
NSString *baseUrl = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=true", source.coordinate.latitude, source.coordinate.longitude, destination.coordinate.latitude, destination.coordinate.longitude];
NSURL *url = [NSURL URLWithString:[baseUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"Url: %#", url);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if(!connectionError){
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSArray *routes = [result objectForKey:#"routes"];
NSDictionary *firstRoute = [routes objectAtIndex:0];
NSString *encodedPath = [firstRoute[#"overview_polyline"] objectForKey:#"points"];
GMSPolyline *polyPath = [GMSPolyline polylineWithPath:[GMSPath pathFromEncodedPath:encodedPath]];
polyPath.strokeColor = [UIColor redColor];
polyPath.strokeWidth = 3.5f;
polyPath.map = _mapView;
}
}];
}
Swift 4.2 / Swift 5
let gmsPolyline = GMSPolyline(path: GMSPath(fromEncodedPath: encodedPolyline))
gmsPolyline.map = map
This is my own revisitation of Sedate Alien's answer.
It is the same implementation save for removing duplicated code and using NSMutableData instead of manually allocating stuff.
#implementation MKPolyline (EncodedString)
+ (float)decodeBytes:(const char *)bytes atPos:(NSUInteger *)idx toValue:(float *)value {
char byte = 0;
int res = 0;
char shift = 0;
do {
byte = bytes[(*idx)++] - 0x3F;
res |= (byte & 0x1F) << shift;
shift += 5;
}
while (byte >= 0x20);
(*value) += ((res & 1) ? ~(res >> 1) : (res >> 1));
return (*value) * 1E-5;
}
+ (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
const char *bytes = [encodedString UTF8String];
NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger idx = 0;
NSMutableData *data = [NSMutableData data];
float lat = 0;
float lon = 0;
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(0, 0);
while (idx < length) {
coords.latitude = [self decodeBytes:bytes atPos:&idx toValue:&lat];
coords.longitude = [self decodeBytes:bytes atPos:&idx toValue:&lon];
[data appendBytes:&coords length:sizeof(CLLocationCoordinate2D)];
}
return [MKPolyline polylineWithCoordinates:(CLLocationCoordinate2D *)data.bytes count:data.length / sizeof(CLLocationCoordinate2D)];
}
#end
The other answers here seem to be about using Apple Maps, for using Google Maps I found I had to make some modifications to #SedateAlien's great category.
MODIFIED CATEGORY
+ (GMSPolyline *)polylineWithEncodedString:(NSString *)encodedString {
const char *bytes = [encodedString UTF8String];
NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger idx = 0;
NSUInteger count = length / 4;
CLLocationCoordinate2D *coords = calloc(count, sizeof(CLLocationCoordinate2D));
NSUInteger coordIdx = 0;
float latitude = 0;
float longitude = 0;
while (idx < length) {
char byte = 0;
int res = 0;
char shift = 0;
do {
byte = bytes[idx++] - 63;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1));
latitude += deltaLat;
shift = 0;
res = 0;
do {
byte = bytes[idx++] - 0x3F;
res |= (byte & 0x1F) << shift;
shift += 5;
} while (byte >= 0x20);
float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1));
longitude += deltaLon;
float finalLat = latitude * 1E-5;
float finalLon = longitude * 1E-5;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(finalLat, finalLon);
coords[coordIdx++] = coord;
if (coordIdx == count) {
NSUInteger newCount = count + 10;
coords = realloc(coords, newCount * sizeof(CLLocationCoordinate2D));
count = newCount;
}
}
GMSMutablePath *path = [[GMSMutablePath alloc] init];
int i;
for (i = 0; i < coordIdx; i++)
{
[path addCoordinate:coords[i]];
}
GMSPolyline *polyline = [GMSPolyline polylineWithPath:path];
free(coords);
return polyline;
}
USAGE
// Here I make the call to the Google Maps API to get the routes between two points...
....
// Get the encoded array of points.
NSString *points = routes[#"routes"][0][#"overview_polyline"][#"points"];
// Use the modified category to get a polyline from the points.
GMSPolyline *polyline = [GMSPolyline polylineWithEncodedString:points];
// Add the polyline to the map.
polyline.strokeColor = [UIColor redColor];
polyline.strokeWidth = 10.f;
polyline.map = theMapView;
}
If anybody else is trying to do this in swift, here's #RootCode's answer adapted to swift (2.3):
let path = GMSMutablePath()
let steps = directionsToShowOnMap.steps
for (idx, step) in steps.enumerate() {
path.addCoordinate(coordinateFromJson(step["start_location"]))
if let polylinePoints = step["polyline"].string, subpath = GMSPath(fromEncodedPath: polylinePoints) {
for c in 0 ..< subpath.count() {
path.addCoordinate(subpath.coordinateAtIndex(c))
}
}
if idx == steps.count - 1 {
path.addCoordinate(coordinateFromJson(step["end_location"]))
}
}
let polyline = GMSPolyline(path: path)
polyline.strokeColor = UIColor.blueColor()
polyline.strokeWidth = 3
polyline.map = mapView
and then:
private func coordinateFromJson(location: JSON) -> CLLocationCoordinate2D {
return CLLocationCoordinate2DMake(location["lat"].double!, location["lng"].double!)
}

Hyphenation library doesn't work with iOS 5

I just tried the hyphenate library of Tupil.
It was mentioned here http://blog.tupil.com/adding-hyphenation-to-nsstring/.
But while it is working perfectly under iOS 4.3, I did not get it to work with iOS 5.
Are there any other frameworks I could use? I heard of CoreText, but I don't know where to start.
Thanks in advance
Martin
I realize it's been a few years, but I just found that there's a Core Foundation function that suggests hyphenation points: CFStringGetHyphenationLocationBeforeIndex. It only works for a few languages, but it looks like it might be really helpful for the narrow label problem.
Update:
Here is some example code. It's a CLI program that shows where to hyphenate a word:
#include <Cocoa/Cocoa.h>
int main(int ac, char *av[])
{
#autoreleasepool {
if(ac < 2) {
fprintf(stderr, "usage: hyph word\n");
exit(1);
}
NSString *word = [NSString stringWithUTF8String: av[1]];
unsigned char hyspots[word.length];
memset(hyspots, 0, word.length);
CFRange range = CFRangeMake(0, word.length);
CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US"));
for(int i = 0; i < word.length; i++) {
int x = CFStringGetHyphenationLocationBeforeIndex(
(CFStringRef) word, i, range,
0, locale, NULL);
if(x >= 0 && x < word.length)
hyspots[x] = 1;
}
for(int i = 0; i < word.length; i++) {
if(hyspots[i]) putchar('-');
printf("%s", [[word substringWithRange: NSMakeRange(i, 1)] UTF8String]);
}
putchar('\n');
}
exit(0);
}
Here's how it looks when you build and run it:
$ cc -o hyph hyph.m -framework Cocoa
$ hyph accessibility
ac-ces-si-bil-i-ty
$ hyph hypothesis
hy-poth-e-sis
These hyphenations agree exactly with the OS X dictionary. I am using this for a narrow label problem in iOS, and it's working well for me.
I wrote a category based Jeffrey's answer for adding "soft hyphenation" to any string. These are "-" which is not visible when rendered, but instead merely queues for CoreText or UITextKit to know how to break up words.
NSString *string = #"accessibility tests and frameworks checking";
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:#"en_US"];
NSString *hyphenatedString = [string softHyphenatedStringWithLocale:locale error:nil];
NSLog(#"%#", hyphenatedString);
Outputs ac-ces-si-bil-i-ty tests and frame-works check-ing
NSString+SoftHyphenation.h
typedef enum {
NSStringSoftHyphenationErrorNotAvailableForLocale
} NSStringSoftHyphenationError;
extern NSString * const NSStringSoftHyphenationErrorDomain;
extern NSString * const NSStringSoftHyphenationToken;
#interface NSString (SoftHyphenation)
- (BOOL)canSoftHyphenateStringWithLocale:(NSLocale *)locale;
- (NSString *)softHyphenatedStringWithLocale:(NSLocale *)locale error:(out NSError **)error;
#end
NSString+SoftHyphenation.m
#import "NSString+SoftHyphenation.h"
NSString * const NSStringSoftHyphenationErrorDomain = #"NSStringSoftHyphenationErrorDomain";
NSString * const NSStringSoftHyphenationToken = #"­"; // NOTE: UTF-8 soft hyphen!
#implementation NSString (SoftHyphenation)
- (BOOL)canSoftHyphenateStringWithLocale:(NSLocale *)locale
{
CFLocaleRef localeRef = (__bridge CFLocaleRef)(locale);
return CFStringIsHyphenationAvailableForLocale(localeRef);
}
- (NSString *)softHyphenatedStringWithLocale:(NSLocale *)locale error:(out NSError **)error
{
if(![self canSoftHyphenateStringWithLocale:locale])
{
if(error != NULL)
{
*error = [self hyphen_createOnlyError];
}
return [self copy];
}
else
{
NSMutableString *string = [self mutableCopy];
unsigned char hyphenationLocations[string.length];
memset(hyphenationLocations, 0, string.length);
CFRange range = CFRangeMake(0, string.length);
CFLocaleRef localeRef = (__bridge CFLocaleRef)(locale);
for(int i = 0; i < string.length; i++)
{
CFIndex location = CFStringGetHyphenationLocationBeforeIndex((CFStringRef)string, i, range, 0, localeRef, NULL);
if(location >= 0 && location < string.length)
{
hyphenationLocations[location] = 1;
}
}
for(int i = string.length - 1; i > 0; i--)
{
if(hyphenationLocations[i])
{
[string insertString:NSStringSoftHyphenationToken atIndex:i];
}
}
if(error != NULL) { *error = nil; }
// Left here in case you want to test with visible results
// return [string stringByReplacingOccurrencesOfString:NSStringSoftHyphenationToken withString:#"-"];
return string;
}
}
- (NSError *)hyphen_createOnlyError
{
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: #"Hyphenation is not available for given locale",
NSLocalizedFailureReasonErrorKey: #"Hyphenation is not available for given locale",
NSLocalizedRecoverySuggestionErrorKey: #"You could try using a different locale even though it might not be 100% correct"
};
return [NSError errorWithDomain:NSStringSoftHyphenationErrorDomain code:NSStringSoftHyphenationErrorNotAvailableForLocale userInfo:userInfo];
}
#end
:)

-[NSCFDictionary length]: unrecognized selector

I have the next problem with this code:
NSDictionary * imagen = [[NSDictionary alloc] initWithDictionary:[envio resultValue]];
NSString *imagenS = [imagen valueForKey:#"/Result"];
[Base64 initialize];
NSData * imagenDecode = [Base64 decode:imagenS];
NSLog(#"%#", [imagenS length]);
//SAVE IMAGE
NSArray *sysPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *docDirectory = [sysPaths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#david.png",docDirectory];
[imagenDecode writeToFile:filePath atomically:YES];
[envio resultValue] --> return a NSDictionary with one image in Base 64 codification.
I want decoder and save this image but in my console I have showed this message:
2011-08-23 20:15:36.539 WSStub[39226:a0f] -[NSCFDictionary length]: unrecognized selector sent to instance 0xd00ee0
The Base 64 code is:
//
// Base64.m
// CryptTest
//
// Created by Kiichi Takeuchi on 4/20/10.
// Copyright 2010 ObjectGraph LLC. All rights reserved.
//
#import "Base64.h"
#implementation Base64
#define ArrayLength(x) (sizeof(x)/sizeof(*(x)))
static char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static char decodingTable[128];
+ (void) initialize
{
if (self == [Base64 class])
{
memset(decodingTable, 0, ArrayLength(decodingTable));
for (NSInteger i = 0; i < ArrayLength(encodingTable); i++) {
decodingTable[encodingTable[i]] = i;
}
}
}
+ (NSData*) decode:(const char*) string length:(NSInteger) inputLength
{
if ((string == NULL) || (inputLength % 4 != 0))
{
return nil;
}
while (inputLength > 0 && string[inputLength - 1] == '=')
{
inputLength--;
}
NSInteger outputLength = inputLength * 3 / 4;
NSMutableData* data = [NSMutableData dataWithLength:outputLength];
uint8_t* output = data.mutableBytes;
NSInteger inputPoint = 0;
NSInteger outputPoint = 0;
while (inputPoint < inputLength)
{
char i0 = string[inputPoint++];
char i1 = string[inputPoint++];
char i2 = inputPoint < inputLength ? string[inputPoint++] : 'A'; /* 'A' will decode to \0 */
char i3 = inputPoint < inputLength ? string[inputPoint++] : 'A';
output[outputPoint++] = (decodingTable[i0] << 2) | (decodingTable[i1] >> 4);
if (outputPoint < outputLength)
{
output[outputPoint++] = ((decodingTable[i1] & 0xf) << 4) | (decodingTable[i2] >> 2);
}
if (outputPoint < outputLength)
{
output[outputPoint++] = ((decodingTable[i2] & 0x3) << 6) | decodingTable[i3];
}
}
return data;
}
+ (NSData*) decode:(NSString*) string
{
return [self decode:[string cStringUsingEncoding:NSASCIIStringEncoding] length:[string length]];
}
#end
The line
NSString *imagenS = [imagen valueForKey:#"/Result"];
is returning a dictionary, not a string. You'll have to inspect your data source to determine whether this is correct or not.
Your NSLog() call is wrong. To show a length, it should be:
NSLog(#"%lu", [imagenS length]);
But that is probably not the problem.
You seem to be invoking length on an NSDictionary. Hard to tell where you do that, since you don't show the piece of code where that happens. It could be that imagenS is not an NSString, but an NSDictionary instead.
Try to do:
NSLog(#"%#", [imagenS class]);
and see what is displayed. If probably tells you that imagenS is not a string, but an NSCFDictionary or similar instead.
FWIW, NSCFDictionary is one of the subclasses of NSDictionary that actually implement the different versions of the main class. This is called a class cluster.

How to send integer through NSOutputStream?

So I'm making an iOS app that constantly sends and array of points through NSStreams, but because sometimes the sender writes two arrays before the receiver receives one, I decided to first write the length, then the array data itself, so that the receiver knows how many bytes to process.
sender:
if (_outStream && [_outStream hasSpaceAvailable]){
const uint8_t *buffer = [data bytes];
uint64_t length = CFSwapInt64HostToLittle([data length]);
NSLog(#"Sending bytes with length: %llu", length);
int er1 = [_outStream write:(const uint8_t *)&length maxLength:sizeof(length)];
int er2 = [_outStream write:buffer maxLength:length];
if (er1 < 0 || er2 < 0) {
[self _showAlert:#"Failed sending data to peer"];
}
}
receiver:
case NSStreamEventHasBytesAvailable:
{
if (stream == _inStream) {
uint8_t *b;
int len = 0;
len = [_inStream read:b maxLength:8];
uint64_t dataLength = CFSwapInt64LittleToHost(*b);
NSLog(#"Received bytes with length: %llu", dataLength);
if(len < 0) {
if ([stream streamStatus] != NSStreamStatusAtEnd)
[self _showAlert:#"Failed reading data from peer"];
} else if (len > 0){
uint8_t bytes[dataLength];
int length = [_inStream read:bytes maxLength:dataLength];
[currentDownload appendBytes:bytes length:length];
id pointsArray = [NSKeyedUnarchiver unarchiveObjectWithData:currentDownload];
[currentDownload release];
currentDownload = [[NSMutableData alloc] init];
if ([pointsArray isKindOfClass:[NSArray class]]) {
[drawScene addNewPoint:[[pointsArray objectAtIndex:0] CGPointValue] previousPoint:[[pointsArray objectAtIndex:1] CGPointValue]];
}
}
}
break;
}
The problem is that the receiver receives an incorrect integer, hence it reads an incorrect amount of bytes.
Can anyone help me with this?
Could it be that when you read the data, you're starting with the introductory bits intended to convey length, rather than starting with the actual data?
For:
[currentDownload appendBytes:bytes length:length];
try substituting something like this:
NSRange rangeLeftover = NSMakeRange(sizeof(uint8_t), [currentDownload length] - sizeof(uint8_t));
NSData *dataLeftover = [currentDownload subdataWithRange:rangeLeftover];
uint8_t bytesLeftover[[dataLeftover length]];
[dataLeftover getBytes:&bytesLeftover length:[dataLeftover length]];
currentDownload = [NSMutableData data]; // clear
[currentDownload appendBytes:bytesLeftover length:[dataLeftover length]];

NSString range of string at occurrence

i'm trying to build a function that will tell me the range of a string at an occurrence.
For example if I had the string "hello, hello, hello", I want to know the range of hello at it's, lets say, third occurrence.
I've tried building this simple function, but it doesn't work.
Note - the top functions were constructed at an earlier date and work fine.
Any help appreciated.
- (NSString *)stringByTrimmingString:(NSString *)stringToTrim toChar:(NSUInteger)toCharacterIndex {
if (toCharacterIndex > [stringToTrim length]) return #"";
NSString *devString = [[[NSString alloc] init] autorelease];
for (int i = 0; i <= toCharacterIndex; i++) {
devString = [NSString stringWithFormat:#"%#%#", devString, [NSString stringWithFormat:#"%c", [stringToTrim characterAtIndex:(i-1)]]];
}
return devString;
[devString release];
}
- (NSString *)stringByTrimmingString:(NSString *)stringToTrim fromChar:(NSUInteger)fromCharacterIndex {
if (fromCharacterIndex > [stringToTrim length]) return #"";
NSString *devString = [[[NSString alloc] init] autorelease];
for (int i = (fromCharacterIndex+1); i <= [stringToTrim length]; i++) {
devString = [NSString stringWithFormat:#"%#%#", devString, [NSString stringWithFormat:#"%c", [stringToTrim characterAtIndex:(i-1)]]];
}
return devString;
[devString release];
}
- (NSRange)rangeOfString:(NSString *)substring inString:(NSString *)string atOccurence:(int)occurence {
NSString *trimmedString = [inString copy]; //We start with the whole string.
NSUInteger len, loc, oldLength;
len = 0;
loc = 0;
NSRange tempRange = [string rangeOfString:substring];
len = tempRange.length;
loc = tempRange.location;
for (int i = 0; i != occurence; i++) {
NSUInteger endOfWord = len+loc;
trimmedString = [self stringByTrimmingString:trimmedString fromChar:endOfWord];
oldLength += [[self stringByTrimmingString:trimmedString toChar:endOfWord] length];
NSRange tmp = [trimmedString rangeOfString:substring];
len = tmp.length;
loc = tmp.location + oldLength;
}
NSRange returnRange = NSMakeRange(loc, len);
return returnRange;
}
Instead of trimming the string a bunch of times (slow), just use rangeOfString:options:range:, which searches only within the range passed as its third argument. See Apple's documentation.
So try:
- (NSRange)rangeOfString:(NSString *)substring
inString:(NSString *)string
atOccurence:(int)occurence
{
int currentOccurence = 0;
NSRange rangeToSearchWithin = NSMakeRange(0, string.length);
while (YES)
{
currentOccurence++;
NSRange searchResult = [string rangeOfString: substring
options: NULL
range: rangeToSearchWithin];
if (searchResult.location == NSNotFound)
{
return searchResult;
}
if (currentOccurence == occurence)
{
return searchResult;
}
int newLocationToStartAt = searchResult.location + searchResult.length;
rangeToSearchWithin = NSMakeRange(newLocationToStartAt, string.length - newLocationToStartAt);
}
}
You need to rework the whole code. While it may seem to work, it's poor coding and plain wrong, like permanently reassigning the same variable, initializing but reassigning one line later, releasing after returning (which will never work).
For your question: Just use rangeOfString:options:range:, and do this the appropriate number of times while just incrementing the starting point.