Iterate through JSON array and add coordinates to the map - ios

I'm using an API to get latitude and longitude coordinates and place them on a map with the name of the place it corresponds to. I'm able to put one place's lat and long coordinates but I'm not too sure how to add all of them to a map. I can't get my head around how to do it. I've tried to use a for loop to do it but I'm too sure on how I would implement it. This is what I've got so far:
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
guard let data = data, error == nil else {
print("Wrong")
return
}
var result: [Info]?
do {
result = try JSONDecoder().decode([Info].self, from: data)
}
catch {
print("Failed to convert: \(error.localizedDescription)")
}
guard let json = result else {
return
}
for each in json {
var each = 0
each += 1
let comp = json[each].lat_lng?.components(separatedBy: ",")
let latString = comp![each]
let lonString = comp![each]
let lat = Double(latString)
let lon = Double(lonString)
let locationPin: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat!, lon!)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(51.55573, -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
let myAn1 = MapPin(title: json[each].name!, locationName: json[each].name!, coordinate: locationPin)
mapView.addAnnotations([myAn1])
}
})
task.resume()
}

Your loop is wrong, each after for is one Info item. The Int index each is pointless and you set it in each iteration to zero so you get always the same coordinate (at index 1).
First of all declare name and lat_lng as non-optional. All records contain both fields.
struct Info : Decodable {
let lat_lng : String
let name : String
}
Second of all for convenience reasons extend CLLocationCoordinate2D to create a coordinate from a string
extension CLLocationCoordinate2D {
init?(string: String) {
let comp = string.components(separatedBy: ",")
guard comp.count == 2, let lat = Double(comp[0]), let lon = Double(comp[1]) else { return nil }
self.init(latitude: lat, longitude: lon )
}
}
Third of all put all good code into the do scope instead of dealing with optionals and set the region once before the loop
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode([Info].self, from: data!)
let location = CLLocationCoordinate2D(latitude: 51.55573, longitude: -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
var pins = [MapPin]()
for info in result {
if let coordinate = CLLocationCoordinate2D(string: info.lat_lng) {
pins.append(MapPin(title: info.name, locationName: info.name, coordinate: coordinate))
}
}
DispatchQueue.main.async {
self.mapView.addAnnotations(pins)
}
}
catch {
print("Failed to convert: \(error)")
}
})
task.resume()
}

Related

How to draw a polyline between multiple markers?

Want to draw a PolyLine from userLocation to multiple marker. In my code already added markers coordinates in a array then added userLocation into 0th position of that array. Now I want to draw a route polyLine between array elements. My code is given below...
self.coods.append(self.currentLocation)
let jsonResponse = response.data
do{
let json = try JSON(data: jsonResponse!)
self.dictXYZ = [json]
print("JsonResponse printed \(json["data"][0]["lattitude"])")
if let array = json["data"].array{
for i in 0..<array.count{
var coordinate = CLLocationCoordinate2D()
coordinate.latitude = array[i]["lattitude"].doubleValue
coordinate.longitude = array[i]["longitude"].doubleValue
self.coods.append(coordinate)
}
for j in self.coods {
let marker = GMSMarker()
marker.position = j
let camera = GMSCameraPosition.camera(withLatitude: j.latitude, longitude: j.longitude, zoom: 12)
self.mapView.camera = camera
marker.map = self.mapView
}
let path = GMSMutablePath()
for j in self.coods {
path.add(j)
}
let polyline = GMSPolyline(path: path)
polyline.map = mapView
In the Google Developer Docs.
Waypoints - Specifies an array of intermediate locations to include along the route between the origin and destination points as
pass through or stopover locations. Waypoints alter a route by
directing it through the specified location(s). The API supports
waypoints for these travel modes: driving, walking and bicycling; not
transit.
First you need to create a waypoints for all intermediate locations to add the route between the source and destination. With that polyline you can create a GMSPath and then draw the route by using GMSPolyline. I hope below solution can help you to draw a route for multiple locations.
func getPolylineRoute(from source: CLLocationCoordinate2D, to destinations: [CLLocationCoordinate2D], completionHandler: #escaping (Bool, String) -> ()) {
guard let destination = destinations.last else {
return
}
var wayPoints = ""
for (index, point) in destinations.enumerated() {
if index == 0 { // Skipping first location that is current location.
continue.
}
wayPoints = wayPoints.count == 0 ? "\(point.latitude),\(point.longitude)" : "\(wayPoints)%7C\(point.latitude),\(point.longitude)"
}
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=true&mode=driving&waypoints=\(wayPoints)&key=\(GOOGLE_API_KEY)")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed : \(String(describing: error?.localizedDescription))")
return
} else {
do {
if let json: [String: Any] = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] {
guard let routes = json["routes"] as? [[String: Any]] else { return }
if (routes.count > 0) {
let overview_polyline = routes[0]
let dictPolyline = overview_polyline["overview_polyline"] as? NSDictionary
let points = dictPolyline?.object(forKey: "points") as? String
completionHandler(true, points!)
} else {
completionHandler(false, "")
}
}
} catch {
print("Error : \(error)")
}
}
}
task.resume()
}
Pass the current location and destination array of locations to getPolylineRoute method. Then call the drawPolyline method with polyline points from main thread.
getPolylineRoute(from: coods[0], to: coods) { (isSuccess, polylinePoints) in
if isSuccess {
DispatchQueue.main.async {
self.drawPolyline(withMapView: self.mapView, withPolylinePoints: polylinePoints)
}
} else {
print("Falied to draw polyline")
}
}
func drawPolyline(withMapView googleMapView: GMSMapView, withPolylinePoints polylinePoints: String){
path = GMSPath(fromEncodedPath: polylinePoints)!
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 3.0
polyline.strokeColor = .lightGray
polyline.map = googleMapView
}
First create GMSPath object
let path = GMSMutablePath()
self.coods.forEach {
path.add(coordinate: $0)
}
https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_mutable_path.html#af62038ea1a9da3faa7807b8d22e72ffb
Then Create GMSPolyline object using path
let pathLine = GMSPolyline.with(path: path)
pathLine.map = self.mapView
https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_polyline.html#ace1dd6e6bab9295b3423712d2eed90a4

How to get accurate route for map

I'm trying to build a route using Google Directions API. It returns an array of waypoints, however, the number of waypoints is not enough to build a smoothed route, and as the result I receive quite inaccurate route what is super crucial and inappropriate for transport application. I also tried HERE maps, almost the same result.
Is there any other services that may build more accurate route, or any solution applicable to the Google Directions API?
Here is the function:
public func getWaypoints(startLocation: [Double]!, endLocation: [Double]!, mode:String? = "walking", lang:String? = "en") -> [Dictionary<String,Any>] {
var resultedArray:[Dictionary<String,Any>] = []
let routeGM = Just.get("https://maps.googleapis.com/maps/api/directions/json?", params: ["origin":"\(startLocation[0]),\(startLocation[1])", "destination": "\(endLocation[0]),\(endLocation[1])", "mode": mode!, "key":self.KEY, "language": lang!])
if let _ = routeGM.error {
} else {
do {
if let data = jsonToNSData(routeGM.json! as AnyObject), let jsonData = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary {
let status = jsonData["status"] as! String
if(status == "OK") {
let results = jsonData["routes"] as! Array<Dictionary<String, AnyObject>>
let legs = results[0]["legs"] as! Array<Dictionary<String, AnyObject>>
let steps = legs[0]["steps"] as! Array<Dictionary<String, AnyObject>>
for i in 0...steps.count-1 {
let item = steps[i]
let start = item["start_location"] as! Dictionary<String,Any>
let end = item["end_location"] as! Dictionary<String,Any>
resultedArray.append(["start_location_lat": start["lat"]!,"start_location_lng": start["lng"]!,"end_location_lat": end["lat"]!,"end_location_lng": end["lng"]!])
}
}
else {
print("not found")
}
}
} catch {
print(error)
}
}
return resultedArray
}
Calling function:
func buildRoute(startCoord:[Double]!, endCoord:[Double]!) {
DispatchQueue.global(qos: .background).async {
let route:[Dictionary<String,Any>] = self.GMSRequest.getWaypoints(startLocation: startCoord,endLocation: endCoord, mode: "driving")
DispatchQueue.main.async {
let path = GMSMutablePath()
for item in route {
path.add(CLLocationCoordinate2D(latitude: item["start_location_lat"]! as! CLLocationDegrees, longitude: item["start_location_lng"]! as! CLLocationDegrees))
path.add(CLLocationCoordinate2D(latitude: item["end_location_lat"]! as! CLLocationDegrees, longitude: item["end_location_lng"]! as! CLLocationDegrees))
}
// Create the polyline, and assign it to the map.
self.polyline.path = path
self.polyline.strokeColor = Styles.colorWithHexString("#3768CD")
self.polyline.strokeWidth = 3.0
self.polyline.geodesic = true
self.polyline.map = self.mapView
}
}
}

Is it possible to not take over screen? Google Places IOS swift

Below codes from Google only return a place if I pick one from the list like the one I attached.
My question:
Is there any function available for me to store all the place's detail in a given coordinate? For example, if I have a coordinate of (51.5108396, -0.0922251), how can I get all the information of nearby places? I am not familiar with Json. Is there any example close to what I want? Thanks a lot.
This function placesClient.currentPlaceWithCallback is somehow close to what I want but it cannot use custom coordinate because it uses user's current coordinate.
//https://developers.google.com/places/ios-api/placepicker
let center = CLLocationCoordinate2DMake(51.5108396, -0.0922251)
let northEast = CLLocationCoordinate2DMake(center.latitude + 0.001, center.longitude + 0.001)
let southWest = CLLocationCoordinate2DMake(center.latitude - 0.001, center.longitude - 0.001)
let viewport = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
let config = GMSPlacePickerConfig(viewport: viewport)
let placePicker = GMSPlacePicker(config: config)
placePicker?.pickPlaceWithCallback({ (place: GMSPlace?, error: NSError?) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
if let place = place {
print("Place name \(place.name)")
print("Place address \(place.formattedAddress)")
print("Place attributions \(place.attributions)")
} else {
print("No place selected")
}
})
Fetching nearby places using google maps
Something is changed due to upgraded iOS version.
complete changed code
func fetchPlacesNearCoordinate(coordinate: CLLocationCoordinate2D, radius: Double, types:[String]) {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=\("your api key")&location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(radius)&rankby=prominence&sensor=true"
let typesString = types.count > 0 ? types.joinWithSeparator("|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let session = NSURLSession.sharedSession()
let placesTask = session.dataTaskWithURL(NSURL(string: urlString)!) {data, response, error in
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
if let jsonResult = (try? NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as? NSDictionary {
let returnedPlaces: NSArray? = jsonResult["results"] as? NSArray
if returnedPlaces != nil {
for index in 0..<returnedPlaces!.count {
if let returnedPlace = returnedPlaces?[index] as? NSDictionary {
var placeName = ""
var latitude = 0.0
var longitude = 0.0
if let name = returnedPlace["name"] as? NSString {
placeName = name as String
}
if let geometry = returnedPlace["geometry"] as? NSDictionary {
if let location = geometry["location"] as? NSDictionary {
if let lat = location["lat"] as? Double {
latitude = lat
}
if let lng = location["lng"] as? Double {
longitude = lng
}
}
}
print("index", index, placeName, latitude, longitude)
}
}
}
}
}
placesTask.resume()
}

iOS Reverse geocoding (getting the NAME of a location from its Coordinates, not just the address)

I'm building a small little app for myself– right now, it has a function called performSearch that does a search within your local area of all nearby coffee places, gyms, and restaurants then drops pins at each of those locations. However, I'm confused as to how I can get the annotation to display the name of the location as shown on the actual map view. Anyone have any experience?
Basically, instead of displaying the address only, I want the annotation to say "Starbucks
Address…"
Sample code:
This does a search with any given Search field and drops pins on a map view of all locations in the given area with that search field.
var matchingItems: [MKMapItem] = [MKMapItem]()
#IBOutlet weak var map: MKMapView!
func performSearch(searchField: String) {
matchingItems.removeAll()
//search request
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchField
request.region = self.map.region
// process the request
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
for item in response.mapItems {
// customize your annotations here, if you want
var annotation = item.placemark
self.reverseGeocoding(annotation.coordinate.latitude, longitude: allData.coordinate.longitude)
self.map.addAnnotation(annotation)
self.matchingItems.append(item)
}
}
}
func reverseGeocoding(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print(error)
return
}
else if placemarks?.count > 0 {
let pm = placemarks![0]
let address = ABCreateStringWithAddressDictionary(pm.addressDictionary!, false)
print("\n\(address)")
if pm.areasOfInterest?.count > 0 {
let areaOfInterest = pm.areasOfInterest?[0]
print(areaOfInterest!)
} else {
print("No area of interest found.")
}
}
})
}
First, reverseGeocodeLocation runs asynchronously, so you'd have to use completion handler pattern.
But, second, the reverse geocoding is unnecessary, as the item in the response.mapItems has a name property. So get use that as the title of your annotation.
For example:
func performSearch(searchField: String) {
matchingItems.removeAll()
//search request
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchField
request.region = map.region
// process the request
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
for item in response.mapItems {
let annotation = MKPointAnnotation()
annotation.title = item.name
annotation.subtitle = item.placemark.title
annotation.coordinate = item.placemark.coordinate
self.map.addAnnotation(annotation)
self.matchingItems.append(item)
}
}
}

Display route on map in Swift

I am trying to draw the route between two points on Apple map (Swift code).
The following structure is used to store the coordinates
struct GeoLocation {
var latitude: Double
var longitude: Double
func distanceBetween(other: GeoLocation) -> Double {
let locationA = CLLocation(latitude: self.latitude, longitude: self.longitude)
let locationB = CLLocation(latitude: other.latitude, longitude: other.longitude)
return locationA.distanceFromLocation(locationB)
}
}
self.foundLocations - is an array of these structures
In the custom class I recieve the coordinates of the points on the map.
var coordinates = self.foundLocations.map{$0.coordinate}
Then I draw the route on the map
self.polyline = MKPolyline(coordinates: &coordinates, count: coordinates.count)
self.mapView.addOverlay(self.polyline, level: MKOverlayLevel.AboveRoads)
To draw the route I use the following method from MKMapViewDelegate
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if let polylineOverlay = overlay as? MKPolyline {
let render = MKPolylineRenderer(polyline: polylineOverlay)
render.strokeColor = UIColor.blueColor()
return render
}
return nil
}
Instead of the actual route laying on roads I get just a straight line between two points.
How can I display the actual route?
You actually have to fetch the route from Apple's maps' server using calculateDirectionsWithCompletionHandler.
First create the relevant MKMapItems for both the source and destination, ex:
let geocoder = CLGeocoder()
let location = CLLocation(latitude: sourceLatitude, longitude: sourceLongitude)
geocoder.reverseGeocodeLocation(location, completionHandler: {
(placemarks:[AnyObject]?, error:NSError?) -> Void in
if placemarks?.count > 0 {
if let placemark: MKPlacemark = placemarks![0] as? MKPlacemark {
self.source = MKMapItem(placemark: placemark)
}
}
})
(Repeat for destination.)
Then fetch the MKRoute, ex:
let request:MKDirectionsRequest = MKDirectionsRequest()
// source and destination are the relevant MKMapItems
request.setSource(source)
request.setDestination(destination)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.Automobile;
// If you're open to getting more than one route,
// requestsAlternateRoutes = true; else requestsAlternateRoutes = false;
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler ({
(response: MKDirectionsResponse?, error: NSError?) in
if error == nil {
self.directionsResponse = response
// Get whichever currentRoute you'd like, ex. 0
self.route = directionsResponse.routes[currentRoute] as MKRoute
}
})
Then after retrieving the MKRoute, you can add the polyline to the map like so:
mapView.addOverlay(route.polyline, level: MKOverlayLevel.AboveRoads)
Swift 3 and reusable conversion of Lyndsey Scott's answer:
final class Route {
static func getRouteFor(
source: CLLocationCoordinate2D,
destination: CLLocationCoordinate2D,
completion: #escaping (
_ route: MKRoute?,
_ error: String?)->()
) {
let sourceLocation = CLLocation(
latitude: source.latitude,
longitude: source.longitude
)
let destinationLocation = CLLocation(
latitude: destination.latitude,
longitude: destination.longitude
)
let request = MKDirectionsRequest()
self.getMapItemFor(location: sourceLocation) { sourceItem, error in
if let e = error {
completion(nil, e)
}
if let s = sourceItem {
self.getMapItemFor(location: destinationLocation) { destinationItem, error in
if let e = error {
completion(nil, e)
}
if let d = destinationItem {
request.source = s
request.destination = d
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate(completionHandler: { response, error in
if let r = response {
let route = r.routes[0]
completion(route, nil)
}
})
}
}
}
}
}
static func getMapItemFor(
location: CLLocation,
completion: #escaping (
_ placemark: MKMapItem?,
_ error: String?)->()
) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemark, error in
if let e = error {
completion(nil, e.localizedDescription)
}
if let p = placemark {
if p.count < 1 {
completion(nil, "placemark count = 0")
} else {
if let mark = p[0] as? MKPlacemark {
completion(MKMapItem(placemark: mark), nil)
}
}
}
}
}
}
Usage:
Route.getRouteFor(source: CLLocationCoordinate2D, destination: CLLocationCoordinate2D) { (MKRoute?, String?) in
<#code#>
}

Resources