Adding geojson layer to google map in iOS - ios

I am writing my first iOS native app. I am trying to load a GeoJSON layer onto a google map in the app (map comes from the google maps sdk) but I can't find any way to do it. I'm proficient in the google maps javascript API but I'm sensing things in Swift are very different.
How can I load a GeoJSON layer onto a map in a native iOS app?

First add your geoJSON file to your project. And if you have google maps set up than you can use the following:
let path = Bundle.main.path(forResource: "GeoJSON_sample", ofType: "json")
let url = URL(fileURLWithPath: path!)
geoJsonParser = GMUGeoJSONParser(url: url)
geoJsonParser.parse()
let renderer = GMUGeometryRenderer(map: mapView, geometries: geoJsonParser.features)
renderer.render()

As of today, I have not see any api can parse geojson into google map shapes on ios. Therefor you have to parse it on your own, parse it into array, then loop through array get each feature, get each geometry, properties, and create shape based on feature type(point, line, polygon)
Here is sample from mapbox to draw a line, you have to extend it to point and polygon.
// Perform GeoJSON parsing on a background thread
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^(void)
{
// Get the path for example.geojson in the app's bundle
NSString *jsonPath = [[NSBundle mainBundle] pathForResource:#"example" ofType:#"geojson"];
// Load and serialize the GeoJSON into a dictionary filled with properly-typed objects
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[[NSData alloc] initWithContentsOfFile:jsonPath] options:0 error:nil];
// Load the `features` dictionary for iteration
for (NSDictionary *feature in jsonDict[#"features"])
{
// Our GeoJSON only has one feature: a line string
if ([feature[#"geometry"][#"type"] isEqualToString:#"LineString"])
{
// Get the raw array of coordinates for our line
NSArray *rawCoordinates = feature[#"geometry"][#"coordinates"];
NSUInteger coordinatesCount = rawCoordinates.count;
// Create a coordinates array, sized to fit all of the coordinates in the line.
// This array will hold the properly formatted coordinates for our MGLPolyline.
CLLocationCoordinate2D coordinates[coordinatesCount];
// Iterate over `rawCoordinates` once for each coordinate on the line
for (NSUInteger index = 0; index < coordinatesCount; index++)
{
// Get the individual coordinate for this index
NSArray *point = [rawCoordinates objectAtIndex:index];
// GeoJSON is "longitude, latitude" order, but we need the opposite
CLLocationDegrees lat = [[point objectAtIndex:1] doubleValue];
CLLocationDegrees lng = [[point objectAtIndex:0] doubleValue];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(lat, lng);
// Add this formatted coordinate to the final coordinates array at the same index
coordinates[index] = coordinate;
}
// Create our polyline with the formatted coordinates array
MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:coordinatesCount];
// Optionally set the title of the polyline, which can be used for:
// - Callout view
// - Object identification
// In this case, set it to the name included in the GeoJSON
polyline.title = feature[#"properties"][#"name"]; // "Crema to Council Crest"
// Add the polyline to the map, back on the main thread
// Use weak reference to self to prevent retain cycle
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[weakSelf.mapView addAnnotation:polyline];
});
}
}
});
here is swift code for line, you have to expend it to point and polygon
// Parsing GeoJSON can be CPU intensive, do it on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
// Get the path for example.geojson in the app's bundle
let jsonPath = NSBundle.mainBundle().pathForResource("example", ofType: "geojson")
let jsonData = NSData(contentsOfFile: jsonPath!)
do {
// Load and serialize the GeoJSON into a dictionary filled with properly-typed objects
if let jsonDict = try NSJSONSerialization.JSONObjectWithData(jsonData!, options: []) as? NSDictionary {
// Load the `features` array for iteration
if let features = jsonDict["features"] as? NSArray {
for feature in features {
if let feature = feature as? NSDictionary {
if let geometry = feature["geometry"] as? NSDictionary {
if geometry["type"] as? String == "LineString" {
// Create an array to hold the formatted coordinates for our line
var coordinates: [CLLocationCoordinate2D] = []
if let locations = geometry["coordinates"] as? NSArray {
// Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
for location in locations {
// Make a CLLocationCoordinate2D with the lat, lng
let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
// Add coordinate to coordinates array
coordinates.append(coordinate)
}
}
let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
// Optionally set the title of the polyline, which can be used for:
// - Callout view
// - Object identification
line.title = "Crema to Council Crest"
// Add the annotation on the main thread
dispatch_async(dispatch_get_main_queue(), {
// Unowned reference to self to prevent retain cycle
[unowned self] in
self.mapView.addAnnotation(line)
})
}
}
}
}
}
}
}
catch
{
print("GeoJSON parsing failed")
}
})

Related

Adding null values to array in Objective-C

In a swift project I was able to do this easily. I have an array of CLLocationCoordinate2D's and CLLocationDistances
Here is the swift code (inside a PFQuery) It works just fine
if let returnedLocation = object["location"] as? PFGeoPoint
{
let requestLocation = CLLocationCoordinate2DMake(returnedLocation.latitude, returnedLocation.longitude)
self.locations.append(requestLocation)
let requestCLLocation = CLLocation(latitude: requestLocation.latitude, longitude: requestLocation.longitude)
let driverCLLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let distance = driverCLLocation.distanceFromLocation(requestCLLocation)
self.distances.append(distance/1000)
}
When I try to add them in Objective C to the locations and distance arrays I get an error because I'm adding an object without a pointer. What would be the best way to get around this? Thank you
Objective C code (they're both NSMutableArrays)
if (object[#"driverResponded"] == nil) {
NSString *username = object[#"username"];
[self.usernames addObject:username];
PFGeoPoint *returnedLocation = object[#"location"];
CLLocationCoordinate2D requestLocation = CLLocationCoordinate2DMake(returnedLocation.latitude, returnedLocation.longitude);
//FIX!
[self.locations addObject:requestLocation];
CLLocation *requestCLLocation = [[CLLocation alloc]initWithLatitude:requestLocation.latitude longitude:requestLocation.longitude];
CLLocation *driverCLLocation = [[CLLocation alloc]initWithLatitude:location.latitude longitude:location.longitude];
CLLocationDistance distance = [driverCLLocation distanceFromLocation:requestCLLocation];
[self.distances addObject:distance/1000];
}
You can do this to store it as a CLLocationCoordinate doing this:
CLLocationCoordinate2D new_coordinate = CLLocationCoordinate2DMake(returnedLocation.latitude, returnedLocation.longitude);
[self.locations addObject:[NSValue valueWithMKCoordinate:new_coordinate]];
Pull it back out like this:
CLLocationCoordinate2D coordinate = [[self.locations objectAtIndex:0] MKCoordinateValue];

Google Maps iOS SDK, Getting Directions between 2 locations

While I am using Google Maps SDK, I am trying to get driving direction between two locations on iOS. I know we can do this using two methods:-
1.) Using URL Scheme, for which it is necessary that Google Maps App is installed on your device.
2.) Using Directions API, via Request-Response and then parsing the JSON. Displaying markers to show the direction.
Now, my question is there any other way by which I can do this on iOS? I need to show the direction from my current location to a particular location of which i have the Lat/Long.
I mean is it really not possible to simply pass 2 location as parameter and Google Maps SDK, will give me the directions?
Thanks,
NSString *urlString = [NSString stringWithFormat:
#"%#?origin=%f,%f&destination=%f,%f&sensor=true&key=%#",
#"https://maps.googleapis.com/maps/api/directions/json",
mapView.myLocation.coordinate.latitude,
mapView.myLocation.coordinate.longitude,
destLatitude,
destLongitude,
#"Your Google Api Key String"];
NSURL *directionsURL = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:directionsURL];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
NSLog(#"%#",response);
NSDictionary *json =[NSJSONSerialization JSONObjectWithData:[request responseData] options:NSJSONReadingMutableContainers error:&error];
GMSPath *path =[GMSPath pathFromEncodedPath:json[#"routes"][0][#"overview_polyline"][#"points"]];
GMSPolyline *singleLine = [GMSPolyline polylineWithPath:path];
singleLine.strokeWidth = 7;
singleLine.strokeColor = [UIColor greenColor];
singleLine.map = self.mapView;
}
else NSLog(#"%#",[request error]);
Note: make Sure Your Google Direction API Sdk Is Enable in Your google developer Console.
It sounds like you are looking for UI Chrome like the Google Maps app has for showing directions. Google Maps SDK for iOS will paint you a map, but you are responsible for the additional navigation chrome.
You can use the Google Directions API to request directions, and then use the encoded path returned from the service to draw a GMSPolyline using GMSPath's pathFromEncodedPath: method.
These lines shows location between a given latitude / longitude and user location;
NSString *googleMapUrlString = [NSString stringWithFormat:#"http://maps.google.com/?saddr=%f,%f&daddr=%#,%#", mapView.userLocation.coordinate.latitude, mapView.userLocation.coordinate.longitude, destinationLatitude, destinationLongtitude];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:googleMapUrlString]];
Swift 3.0 & XCode 8.0
Using AFNetworking & SwiftJson
let destLatitude="26.9124"
let destLongitude="75.7873"
mapView.isMyLocationEnabled = true
var urlString = "\("https://maps.googleapis.com/maps/api/directions/json")?origin=\("28.7041"),\("77.1025")&destination=\(destLatitude),\(destLongitude)&sensor=true&key=\("Your-Api-key")"
urlString = urlString.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)!
let manager=AFHTTPRequestOperationManager()
manager.responseSerializer = AFJSONResponseSerializer(readingOptions: JSONSerialization.ReadingOptions.allowFragments) as AFJSONResponseSerializer
manager.requestSerializer = AFJSONRequestSerializer() as AFJSONRequestSerializer
manager.responseSerializer.acceptableContentTypes = NSSet(objects:"application/json", "text/html", "text/plain", "text/json", "text/javascript", "audio/wav") as Set<NSObject>
manager.post(urlString, parameters: nil, constructingBodyWith: { (formdata:AFMultipartFormData!) -> Void in
}, success: { operation, response -> Void in
//{"responseString" : "Success","result" : {"userId" : "4"},"errorCode" : 1}
//if(response != nil){
let parsedData = JSON(response)
print_debug("parsedData : \(parsedData)")
var path = GMSPath.init(fromEncodedPath: parsedData["routes"][0]["overview_polyline"]["points"].string!)
//GMSPath.fromEncodedPath(parsedData["routes"][0]["overview_polyline"]["points"].string!)
var singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 7
singleLine.strokeColor = UIColor.green
singleLine.map = self.mapView
//let loginResponeObj=LoginRespone.init(fromJson: parsedData)
// }
}, failure: { operation, error -> Void in
print_debug(error)
let errorDict = NSMutableDictionary()
errorDict.setObject(ErrorCodes.errorCodeFailed.rawValue, forKey: ServiceKeys.keyErrorCode.rawValue as NSCopying)
errorDict.setObject(ErrorMessages.errorTryAgain.rawValue, forKey: ServiceKeys.keyErrorMessage.rawValue as NSCopying)
})
Swift 4.1, Xcode 9.4.1
//Here you need to set your origin and destination points and mode
let url = NSURL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=Machilipatnam&destination=Vijayawada&mode=driving")
//OR if you want to use latitude and longitude for source and destination
//let url = NSURL(string: "\("https://maps.googleapis.com/maps/api/directions/json")?origin=\("17.521100"),\("78.452854")&destination=\("15.1393932"),\("76.9214428")")
let task = URLSession.shared.dataTask(with: url! as URL) { (data, response, error) -> Void in
do {
if data != nil {
let dic = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableLeaves) as! [String:AnyObject]
// print(dic)
let status = dic["status"] as! String
var routesArray:String!
if status == "OK" {
routesArray = (((dic["routes"]!as! [Any])[0] as! [String:Any])["overview_polyline"] as! [String:Any])["points"] as! String
// print("routesArray: \(String(describing: routesArray))")
}
DispatchQueue.main.async {
let path = GMSPath.init(fromEncodedPath: routesArray!)
let singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 6.0
singleLine.strokeColor = .blue
singleLine.map = mapView
}
}
} catch {
print("Error")
}
}
task.resume()
Here, you need to add your key (google api key) to the above API.
I had done it as it also shows PINS DISTANCE AND DURATION on map with DIRECTION ROUTE. But dont forget to set your GOOGLE DIRECTION API TO ENABLED in your GOOGLE DEVELOPER CONSOLE
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
NSString *urlString =#"https://maps.googleapis.com/maps/api/directions/json";
NSDictionary *dictParameters = #{#"origin" : [NSString stringWithFormat:#"%#",_sourceAdd], #"destination" : [NSString stringWithFormat:#"%#",_destinationAdd], #"mode" : #"driving", #"key":#"AIzaSyD9cWTQkAxemELVXTNUCALOmzlDv5b9Dhg"};
[manager GET:urlString parameters:dictParameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
GMSPath *path =[GMSPath pathFromEncodedPath:responseObject[#"routes"][0][#"overview_polyline"][#"points"]];
NSDictionary *arr=responseObject[#"routes"][0][#"legs"];
NSMutableArray *loc=[[NSMutableArray alloc]init];
loc=[[arr valueForKey:#"start_location"]valueForKey:#"lat"];
_sourceloc.latitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"start_location"]valueForKey:#"lng"];
_sourceloc.longitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"end_location"]valueForKey:#"lat"];
_destinationloc.latitude=[loc[0] doubleValue];
loc=[[arr valueForKey:#"end_location"]valueForKey:#"lng"];
_destinationloc.longitude=[loc[0] doubleValue];
NSString *dis,*dur;
loc=[[arr valueForKey:#"distance"]valueForKey:#"text"];
dis=loc[0];
loc=[[arr valueForKey:#"duration"]valueForKey:#"text"];
dur=loc[0];
NSString *sa,*da;
loc=[arr valueForKey:#"start_address"];
sa=loc[0];
loc=[arr valueForKey:#"end_address"];
da=loc[0];
UIAlertView *av=[[UIAlertView alloc]initWithTitle:#"Route Info" message:[NSString stringWithFormat:#"Distance:%# \nDuration:%#",dis,dur] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil, nil];
[av show];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:_sourceloc.latitude longitude:_sourceloc.longitude zoom:10];
mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
GMSMarker *marker = [GMSMarker markerWithPosition:_sourceloc];
marker.title=#"Source";
marker.snippet =sa;
marker.appearAnimation = kGMSMarkerAnimationPop;
marker.map = mapView;
GMSMarker *marker2 = [GMSMarker markerWithPosition:_destinationloc];
marker2.title=#"Destination";
marker2.snippet =da;
marker2.appearAnimation = kGMSMarkerAnimationPop;
marker2.map = mapView;
GMSPolyline *singleLine = [GMSPolyline polylineWithPath:path];
singleLine.strokeWidth = 4;
singleLine.strokeColor = [UIColor blueColor];
singleLine.map = mapView;
self.view = mapView;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Using Swift I definitely solved in this way.
My purpose was finding distance between two coordinates:
import AFNetworking
/**
Calculate distance between two valid coordinates
- parameter origin: origin coordinates
- parameter destination: destination coordinates
- parameter completion: completion callback
*/
func calculateDistance(origin origin: CLLocation, destination: CLLocation, completion: (distance: Double?) -> Void) {
let service = "https://maps.googleapis.com/maps/api/directions/json"
let originLat = origin.coordinate.latitude
let originLong = origin.coordinate.longitude
let destLat = destination.coordinate.latitude
let destLong = destination.coordinate.longitude
let urlString = "\(service)?origin=\(originLat),\(originLong)&destination=\(destLat),\(destLong)&mode=driving&units=metric&sensor=true&key=<YOUR_KEY>"
let directionsURL = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: directionsURL!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let operation = AFHTTPRequestOperation(request: request)
operation.responseSerializer = AFJSONResponseSerializer()
operation.setCompletionBlockWithSuccess({ (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) -> Void in
if let result = responseObject as? NSDictionary {
if let routes = result["routes"] as? [NSDictionary] {
if let lines = routes[0]["overview_polyline"] as? NSDictionary {
if let points = lines["points"] as? String {
let path = GMSPath(fromEncodedPath: points)
let distance = GMSGeometryLength(path)
print("wow \(distance / 1000) KM")
}
}
}
}
}) { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
print("\(error)")
}
operation.start()
}
(void)viewDidLoad {
[super viewDidLoad];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:30.692408
longitude:76.767556
zoom:14];
GMSMapView *mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
mapView.myLocationEnabled = YES;
// Creates markers in the center of the map.
GMSMarker *marker = [[GMSMarker alloc] init];
marker.position = CLLocationCoordinate2DMake(30.6936659, 76.77201819999999);
marker.title = #"Chandigarh 47c";
marker.snippet = #"Hello World";
marker.map = mapView;
GMSMarker *marker1 = [[GMSMarker alloc] init];
marker1.position = CLLocationCoordinate2DMake(30.742138, 76.818756);
marker1.title = #"Sukhna Lake";
marker1.map = mapView;
//creating a path
GMSMutablePath *path = [GMSMutablePath path];
[path addCoordinate:CLLocationCoordinate2DMake(#(30.6936659).doubleValue,#(76.77201819999999).doubleValue)];
[path addCoordinate:CLLocationCoordinate2DMake(#(30.742138).doubleValue,#(76.818756).doubleValue)];
GMSPolyline *rectangle = [GMSPolyline polylineWithPath:path];
rectangle.strokeWidth = 2.f;
rectangle.map = mapView;
self.view=mapView;
}
If someone is looking to parse the distance from routes array following is the way to get the distance in swift 4/5
let distance = responseJSON["routes"][0]["legs"][0]["distance"]["text"]
As #iOS suggested I'm also posting my answer to show you a way how to accomplish it using Codable and Alamofire/Moya.
To do so, you'd have to remodel the GoogleMaps Response entities, like so:
/// Struct for modelling a response from the Google Maps Directions API. This is the "root level"
struct GMSDirectionsResponse: Codable {
/// The suggested routes
let routes: [GMSRoute]
/// Status telling if request was okay
let status: String
}
/// Struct for modelling a Route suggested by GoogleMaps Directions API.
struct GMSRoute: Codable {
/// Represents an area in the map
struct Bounds: Codable {
// You can omit these structs for your case.
// Just to give an idea how it would look like if you model the entire response
// ...
}
struct Leg: Codable {
// ...
}
/// A single step of a route
struct Step: Codable {
// ...
}
/// Structure around the coded representation of the GMSPath
struct OverviewPolyline: Codable {
/// A coded representation of the GMSPath
let points: String
}
/// The bounds to show on the map to include the whole route
let bounds: Bounds?
/// All legs of this route, including the single steps
let legs: [Leg]?
/// The path to walk/drive/etc. this route
let overview_polyline: OverviewPolyline?
/// A textual summary of the most important roads to take
let summary: String?
}
You can see how a response object consists of an array of routes and a status string (e.g. "OK"). Each route has a few properties again, including the overview_polyline field. For being able to have that object decoded by a JSONDecoder you also need to model that class (it simply contains a string value for the key points.
Now if you only need the overview_polyline it's perfectly fine to omit all other unneeded properties and structs as long as you still model the hierarchical structure of the response (e.g. GMSDirectionsResponse > GMSRoute > OverviewPolyline > points).
What you can do now is to ask a JSONDecoder to decode a GMSDirectionsResponse from the body data with a single line! In my project I used Moya but I'm sure you can also do it with URLSession's data object.
// let moya do request
let moya = MoyaProvider<YourGMSService>()
moya.request(.getDirections(origin: origin, destination: destination)) { (result) in
switch result {
case .success(let response):
// check if request was successful
if
// makes sure status is code is 2xx
(try? response.filterSuccessfulStatusCodes()) != nil,
// this line tries to decode a GMSDirectionsResponse object from response.data
let directions = try? JSONDecoder().decode(GMSDirectionsResponse.self, from: response.data)
{
// successful request, converted response to JSON Dictionary
NSLog("GET DIRECTIONS: Success")
// here we can check for the directions properites already!
NSLog("GoogleMaps Directions request finished with status %#", directions.status)
// check if we have at least one GMSRoute with an overview_polyline
guard let encodedPath = directions.routes.first?.overview_polyline else { return }
// now let's use the encoded path:
DispatchQueue.main.async {
let path = GMSPath.init(fromEncodedPath: encodedPath.points)
// continue as in other answers (Y)
let singleLine = GMSPolyline.init(path: path)
singleLine.strokeWidth = 6.0
singleLine.strokeColor = .blue
singleLine.map = mapView
}
return
}
// some error handling if we couldn't parse the data to a GMSDirectionsResponse object
NSLog("Could not parse JSON from Directions API Response:\n%#", response.debugDescription)
case .failure(let error):
// we had an error
NSLog(error.localizedDescription)
}
// log and complete with nil
NSLog("GET DIRECTIONS: Failed")
}
This might look like a huge load of code but it's totally convenient and it keeps you from messing around with JSON subscript members and lots of [] braces. 😊
If you have questions, I'm happy to help!
If you are using a restricted key, you have to add X-Ios-Bundle-Identifier header with the bundle you restricted the key to. With that header it works also from Postman.
Create a key in google developer console make sure your project is created with App bundleID after that add the following code
NSString *KEY=#"";
NSString *Origin=#"";
NSString *Destination=#"";
NSString *str_maps=[NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/directions/json?origin=%#&destination=%#&key=%#",Origin,Destination,KEY];
NSURL *url=[NSURL URLWithString:str_maps];
NSData *dta=[NSData dataWithContentsOfURL:url];
NSDictionary *dict=(NSDictionary *)[NSJSONSerialization JSONObjectWithData:dta options:kNilOptions error:nil];
NSLog(#"%#",dict);

How to track a user's location and display the path travelled using Google Maps ios SDK

I'm currently building an ios app and I'm hoping to implement a function where the user's location is displayed on a Google Map view and when they move a polyline show's the path that has been travelled by the user so far. This obviously needs to happen in realtime.
So far I've initialised a Google Maps view and can display the user's current location using the observeValueForKeyPath function. The view and location marker update as the user moves.
I figured the best way to do this would be to create a GMSMutablePath object that adds the user's current location every time the map camera updates to the new location and then create a polyline from that path. The code I've used for this is below:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"myLocation"] && [object isKindOfClass:[GMSMapView class]])
{
[self.myMapView animateToCameraPosition:[GMSCameraPosition cameraWithLatitude:self.myMapView.myLocation.coordinate.latitude
longitude:self.myMapView.myLocation.coordinate.longitude
zoom:18]];
[trackedPath addCoordinate:CLLocationCoordinate2DMake(self.myMapView.myLocation.coordinate.latitude, self.myMapView.myLocation.coordinate.longitude)];
GMSPolyline *testPoly = [GMSPolyline polylineWithPath:trackedPath];
testPoly.strokeWidth = 8;
testPoly.strokeColor = [UIColor redColor];
testPoly.map = myMapView;
}
}
This doesn't produce any polyline on the map in practice so any help/advice would be much appreciated!
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{ NSString *pointString=[NSString stringWithFormat:#"%f,%f",newLocation.coordinate.latitude,newLocation.coordinate.longitude];
[self.points addObject:pointString];
GMSMutablePath *path = [GMSMutablePath path];
for (int i=0; i<self.points.count; i++)
{
NSArray *latlongArray = [[self.points objectAtIndex:i]componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#","]];
[path addLatitude:[[latlongArray objectAtIndex:0] doubleValue] longitude:[[latlongArray objectAtIndex:1] doubleValue]];
}
if (self.points.count>2)
{
GMSPolyline *polyline = [GMSPolyline polylineWithPath:path];
polyline.strokeColor = [UIColor blueColor];
polyline.strokeWidth = 5.f;
polyline.map = mapView_;
self.view = mapView_;
}}
If you want to draw two path BTW two place use this method. its demo swift methods to find rout of two places.
//MARK: API CALL
func GetRoughtofTwoLocation(){
let originString: String = "\(23.5800),\(72.5853)"
let destinationString: String = "\(24.5836),\(72.5853)"
let directionsAPI: String = "https://maps.googleapis.com/maps/api/directions/json?"
let directionsUrlString: String = "\(directionsAPI)&origin=\(originString)&destination=\(destinationString)&mode=driving"
APICall().callApiUsingWithFixURLGET(directionsUrlString, withLoader: true) { (responceData) -> Void in
let json = responceData as! NSDictionary
let routesArray: [AnyObject] = (json["routes"] as! [AnyObject])
var polyline: GMSPolyline? = nil
if routesArray.count > 0 {
let routeDict: [NSObject : AnyObject] = routesArray[0] as! [NSObject : AnyObject]
var routeOverviewPolyline: [NSObject : AnyObject] = (routeDict["overview_polyline"] as! [NSObject : AnyObject])
let points: String = (routeOverviewPolyline["points"] as! String)
let path: GMSPath = GMSPath(fromEncodedPath: points)!
polyline = GMSPolyline(path: path)
polyline!.strokeWidth = 2.0
polyline!.map = self.mapView
}
}
}

How can I improve this performance?

I have a UITabBarController based app that:
1) Fetches data from web and parses it into CD (this works fine). [In Tab 1]
2) Then when second tab [In Tab 2] is selected, it runs this method:
{ viewDidLoad > loadRecordsFromCD (performBlockAndWait) > populateLocationsToSort }
- (void)populateLocationsToSort {
//1. Get UserLocation based on mapview
self.userLocation = [[CLLocation alloc] initWithLatitude:self.userLocation.coordinate.latitude longitude:self.userLocation.coordinate.longitude];
// Loop thru dictionary-->Create locations
// 2. Loop thru dictionary to get Custom Objects
for (Location * locationObject in self.farSiman) {
// 3. Unload objects values into locals
//PARSE ALL DATA
NSString *coordenadas = locationObject.coordenadas;
NSArray *coordinatesArray = [coordenadas componentsSeparatedByString:#","];
NSString * latitude = [coordinatesArray objectAtIndex:0];
NSString * longitude = [coordinatesArray objectAtIndex:1];
NSString * storeDescription = locationObject.nombrePublico;
NSString * address = locationObject.direccion;
NSString * ciudad = locationObject.ciudad;
NSString * horario = locationObject.horario;
NSString * hor_LV = locationObject.hor_LV;
NSString * hor_S = locationObject.hor_S;
NSString * hor_D = locationObject.hor_D;
NSString * telefono = locationObject.telefono;
NSString * celular_TA = locationObject.celular_TA;
NSString * celular_TB = locationObject.celular_TB;
NSString * hrs24 = locationObject.hrs24;
NSString * driveThru = locationObject.driveThru;
//NSString * estado = locationObject.estado;
NSString * estado;
// IF self.open24hrs SELECTED
if (self.open24hrs) {
// Set it based on TimeComparator
if ([TimeComparator dealWithTimeStrings2:locationObject.hor_LV]) {
estado = #"Abierta";
} else {
estado = #"Cerrada";
}
} else {
estado = locationObject.estado;
}
// 4. Create MyLocation object based on locals gotten from Custom Object
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0 ciudad:ciudad horario:horario telefono:telefono hrs24:hrs24 driveThru:driveThru hor_LV:hor_LV hor_D:hor_D hor_S:hor_S celular_TA:celular_TA celular_TB:celular_TB estado:estado];
// 5. Calculate distance between locations & uL
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];
annotation.distance = calculatedDistance/1000;
//Add annotation to local NSMArray
[self.annotationsToSort addObject:annotation];
} //ENDS FOR LOOP
//SORT the created annotationsToSort
[self sort];
}
It takes the array populated from the CD fetch and creates objects out of them. It creates new objects because it must take the hor_LV field, parse it into dates and compare them to now in order to determine the location.estado.
Currently there are 84 records being fetched and I can already notice a lag from the time i tap on the second Tab, (this tableviewcontroller), and the time it actually displays onscreen.
I can't pre-parse this array because the user sets some filters on Tab 1 which are passed in to Tab 2 before the data is fetched from the database. So I know that the fetch must occur as Tab 2 loads. My question is, what could I do to speed this up or not let the lag be so obvious?
The best would be to use threads to deal with the data faster. Also, it would give you the opportunity to give some feedback to the user making the lag almost unnoticeable or at least less annoying - especially if you consider sending data even if all is not processed yet.
If you're looking for code optimizations, I would suggest that you use a profiler and modify the code accordingly.
Look at using a batched fetch request. You can't display all of the 84+ items to the user at the same time so you don't need to pull them all out of the data store at the same time. The fetch request can be configured to sort the items and to return pages suitable for the number of items that can be seen at any one time. Then each time a page is loaded there will be a very small amount of processing / conversion but the overall cost will be distributed. Also, if the user never 'scrolls' to see data then it won't be loaded.

iOS : MapKit place an annotation relative to the map, and not the bounds of the screen

I am writing an application with a mapview inside.
I download coordinates for some annotations from my server via a HTTP request.
I am supposed to decode the JsonResponse and add the annotations to my map.
Excerpt from my JSON response
...{
"id": 1,
"lat": 20.34226,
"long": 19.988363
},..
I add my annotations within this method, which is called when the download of the above JSON is complete. (i have a custom annotation class.)
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
NSLog(#"Succeeded! Received %d bytes of data",[self.responseData length]);
// convert to JSON
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
// show all values
for(NSDictionary *dict in res) {
CGFloat a = [[dict objectForKey:#"lat"] floatValue];
CGFloat b = [[dict objectForKey:#"long"] floatValue];
// CLLocationCoordinate2D location;
// location.latitude = a;
// location.longitude = b;
//
CLLocationCoordinate2D touchMapCoordinate
= [self.mapView convertPoint:CGPointMake(b,a) toCoordinateFromView:self.mapView];
MapViewAnnotation *myPin = [[MapViewAnnotation alloc] initWithCoordinate:touchMapCoordinate]; // Or whatever coordinates...
[self.mapView addAnnotation:myPin];
}
}
When the annotations are added, they are added relative to my screens bounds , and not to the whole mapview.
I do understand that the method call [self.mapView convertPoint:CGPointMake(a,b) toCoordinateFromView:self.mapView] is the cause of this.
I would like to perform the code that is now in comments, and skip the
CLLocationCoordinate2D touchMapCoordinate
= [self.mapView convertPoint:CGPointMake(b,a) toCoordinateFromView:self.mapView];
and go directly for MapViewAnnotation *myPin = [[MapViewAnnotation alloc] initWithCoordinate:location];
However, when i do this i get the following error:
An instance 0x858cc30 of class MapViewAnnotation was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x1a000890> (
<NSKeyValueObservance 0x1a000850: Observer: 0xa117890, Key path: coordinate, Options: <New: NO, Old: NO, Prior: YES> Context: 0x0, Property: 0x1a0008d0>
)
What i wonder is: How can i add these annotations relative to my map, with long/latitude obtained from JSON (and not relative to the view i currently see).

Resources