I'm working on iOS app that draw polyline on the map to highlight the route user has taken. I can draw the poly line with no problem. But, the line is not a clean line that you see on Googlemap. It's a bit squiggly like handdrawn as you can see in the pic. Apology for big images as I don't know how to resize them in SO.
How can I acheive the result like this one in the picture :
This is my code that is responsible for drawing poly line :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = manager.location {
if (firstLoad) {
firstLoad = false
recentCoordinate = location.coordinate
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: Constants.MapView.Zoom, bearing: 0, viewingAngle: Constants.MapView.ViewingAngle)
}
else {
mapView.animateToLocation(CLLocationCoordinate2D(latitude: location.coordinate.latitude,longitude: location.coordinate.longitude))
}
// this is suppose to filter out noise. I don't think it's working
let age = 0 - location.timestamp.timeIntervalSinceNow
if (age > 120) {
return; // ignore old (cached) updates
}
if (location.horizontalAccuracy < 0) {
return; // ignore invalid updates
}
if (location.horizontalAccuracy <= 10){
// this is a valid update
// Draw route as user moves along
path.addCoordinate(CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude))
let polyline = GMSPolyline(path: path)
polyline.strokeColor = UIColor.redColor()
polyline.strokeWidth = 3
polyline.map = mapView
// Save the coordinates to array
coordinateArray.append([location.coordinate.latitude, location.coordinate.longitude])
}
}
}
Related
I have a problem drawing a Polyline on my MapKit map, I am supposed to be trying to draw a line where the user is going, the problem is that when I move the user's position it only draws a point in the new position but does not trace the complete line
I have already tried to paint the line with a starting point and a starting point, but it does not work for me, it is assumed that if the user walks his position is updated and he should go drawing the line
This is my method didUpdateLocations:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last{
self.currentLocation = location
mapKit.centerCoordinate = location.coordinate
//Validamos si el usuario quiere recordar su ruta
if SettingsViewController.recordRutaOn{
points.append(location)
print("Estamos registrando la ruta del usuario")
}
//Prueba para pintar la ruta
print((locations.last?.coordinate.latitude)!,(locations.last?.coordinate.longitude)!)
var rutaPoly = CLLocationCoordinate2D(latitude: (locations.last?.coordinate.latitude)!, longitude: (locations.last?.coordinate.longitude)!)
let polyline = MKPolyline(coordinates: &rutaPoly, count: locations.count)
mapKit.add(polyline)
if self.currentLocation == nil{
self.currentAnnotation = MKPointAnnotation()
self.currentAnnotation?.coordinate = location.coordinate
self.mapKit.addAnnotation(self.currentAnnotation!)
}
}
}
And this is my renderFor method:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolyline{
let polyline = overlay
let polyLineRender = MKPolylineRenderer(overlay: polyline)
polyLineRender.strokeColor = UIColor.blue
polyLineRender.lineWidth = 2.0
return polyLineRender
}
return MKPolylineRenderer()
}
I don't understand what I'm doing wrong that only one point is drawn
You have defined rutaPoly as a single CLLocationCoordinate2D. Thus, when you create the MKPolyline from that, there is only one coordinate and thus the rendered polyline will be a point. Worse, the count parameter you’re supplying to MKPolyline has nothing to do with the first parameter and you can get some undefined behaviors that way.
You want to keep an array of coordinates and append your new coordinate to that. Something like:
var coordinates: [CLLocationCoordinate2D] = []
var currentPolyline: MKPolyline?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
coordinates.append(location.coordinate)
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline)
if let currentPolyline = currentPolyline {
mapView.removeOverlay(currentPolyline)
}
currentPolyline = polyline
}
Or, alternatively, you can add each new location update as a separate polyline:
var previousCoordinate: CLLocationCoordinate2D?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
if let previousCoordinate = previousCoordinate {
let coordinates = [previousCoordinate, location.coordinate]
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline)
}
previousCoordinate = location.coordinate
}
But whatever you do, make sure that the MKPolyline initializer is supplied an array of CLLocationCoordinate2D and its respective count.
I was wondering if was possible to check if a marker is outside of the region.
I can check if the user left the region, but I want to also check if any marker on the map to the same thing as well, I want to check the geofence region between to markers.
func setUpGeofence() {
let geofenceRegionCenter = CLLocationCoordinate2DMake(getLatitude(),getLongitude());
let geofenceRegion = CLCircularRegion(center: geofenceRegionCenter, radius: 400, identifier: "Geofence");
geofenceRegion.notifyOnExit = true;
geofenceRegion.notifyOnEntry = true;
self.locationManager.startMonitoring(for: geofenceRegion)
}
}
You can use CLLocation.distance(from:) to calculate each marker's distance from your geofence center.
let center = CLLocation(latitude: getLatitude(), longitude: getLongitude())
for marker in markers {
if marker.location.distance(from: center) > radius {
// outside
} else {
// inside
}
}
I am using google distance api ["https://maps.googleapis.com/maps/api/directions/json?origin=" +start.latitude + "," + start.longitude +"&destination=" + end.latitude +"," + end.longitude + "&alternatives=false" +"&mode=driving&key=" + key;] to get route from start location to end location.
I am using the following code to draw route between my start and destination location
func drawPath()
{
if polylines != nil {
polylines?.map = nil
polylines = nil
}
if animationPolyline != nil {
self.animationIndex = 0
self.animationPath = GMSMutablePath()
self.animationPolyline.map = nil
if self.timer != nil {
self.timer.invalidate()
}
}
setupStartRideLocationMarkup(CLLocationCoordinate2D(latitude: (currentLocation?.coordinate.latitude)!, longitude: (currentLocation?.coordinate.longitude)!))
if currentLocation != nil && destinationLocation != nil {
let origin = "\((currentLocation?.coordinate.latitude)!),\((currentLocation?.coordinate.longitude)!)"
let destination = "\((destinationLocation?.latitude)!),\((destinationLocation?.longitude)!)"
let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving&key=MY_API_KEY"
Alamofire.request(url).responseJSON { response in
let json = JSON(data: response.data!)
self.jsonRoute = json
let routes = json["routes"].arrayValue
for route in routes
{
let routeOverviewPolyline = route["overview_polyline"].dictionary
let points = routeOverviewPolyline?["points"]?.stringValue
self.path = GMSPath.init(fromEncodedPath: points!)!
self.polylines = GMSPolyline.init(path: self.path)
self.polylines?.geodesic = true
self.polylines?.strokeWidth = 5
self.polylines?.strokeColor = UIColor.black
self.polylines?.map = self.mapView
}
self.shouldDrawPathToStartLocation()
self.shouldDrawPathToEndLocation()
if routes.count > 0 {
self.startAnimatingMap()
}
}
}
}
As you can see I am initialising path with encoded path from the api. Now I want to remove travelled GMSPolyline from the overall path How can I do that? My current intiuation is that it will be from didUpdateLocations Here's my code of didUpdateLocations method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currentLocation = locations.last!
let camera = GMSCameraPosition.camera(withLatitude: (currentLocation?.coordinate.latitude)!,
longitude: (currentLocation?.coordinate.longitude)!,
zoom: zoomLevel)
if (mapView?.isHidden)! {
mapView?.isHidden = false
mapView?.camera = camera
} else {
mapView?.animate(to: camera)
}
updatePolyLineIfRequired()
}
And in updatePolyLineIfRequired I want to remove travelled poly lines
func updatePolyLineIfRequired(){
if GMSGeometryIsLocationOnPath((currentLocation?.coordinate)!, path, true) {
if startPolyline != nil {
startPolyline?.map = nil
startPolyline = nil
}
}
}
I want to implement solution like Uber or Careem where travelled drawn GMSPolyline gets removed till user current location.
Thanks in Advance
P.S I am using Alamofire SwiftyJSON
There are two solutions for this:-
Calling Directions Api each time didUpdateLocations function is called.(Not efficient)
Removing the travelled coordinates from the GMSPath.
Calling Directions api will be not useful unless your request limit for Direction api is less.
For removing the travelled coordinates from the path:-
//Call this function in didUpdateLocations
func updateTravelledPath(currentLoc: CLLocationCoordinate2D){
var index = 0
for i in 0..<self.path.count(){
let pathLat = Double(self.path.coordinate(at: i).latitude).rounded(toPlaces: 3)
let pathLong = Double(self.path.coordinate(at: i).longitude).rounded(toPlaces: 3)
let currentLaenter code heret = Double(currentLoc.latitude).rounded(toPlaces: 3)
let currentLong = Double(currentLoc.longitude).rounded(toPlaces: 3)
if currentLat == pathLat && currentLong == pathLong{
index = Int(i)
break //Breaking the loop when the index found
}
}
//Creating new path from the current location to the destination
let newPath = GMSMutablePath()
for i in index..<Int(self.path.count()){
newPath.add(self.path.coordinate(at: UInt(i)))
}
self.path = newPath
self.polyline.map = nil
self.polyline = GMSPolyline(path: self.path)
self.polyline.strokeColor = UIColor.darkGray
self.polyline.strokeWidth = 2.0
self.polyline.map = self.mapView
}
The lat and longs are rounded of so that if the user is nearby the travelled location. Use the following extension to round of upto 3 decimal places or more according to requirement.
extension Double {
// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}
I am trying to show three things on map :
GPS (the current location) , Marker 1 , Marker 2, but I am not sure I am doing it right or not ! Here is my code :
self.startMarker.position = CLLocationCoordinate2D(latitude: self.startLatitude, longitude: self.startLongitude)
self.startMarker.icon = #imageLiteral(resourceName: "Pin Start")
self.startMarker.map = self.mapView
self.endMarker.position = CLLocationCoordinate2D(latitude: self.endLatitude, longitude: self.endLongitude)
self.endMarker.icon = #imageLiteral(resourceName: "Pin End")
self.endMarker.map = self.mapView
let southWest = CLLocationCoordinate2DMake(self.startLatitude,self.startLongitude)
let northEast = CLLocationCoordinate2DMake(self.endLatitude,self.endLongitude)
let bounds = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
let camera = self.mapView.camera(for: bounds, insets:.zero)
self.mapView.camera = camera!
More Code :
// MARK: - Google Map
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedWhenInUse {
mapView.isMyLocationEnabled = true
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last
mapView.camera = GMSCameraPosition.camera(withTarget: newLocation!.coordinate, zoom: 14)
mapView.isMyLocationEnabled = true
}
The result is like this :
What I need :
EDITED
if let myLocation = self.mapView.myLocation {
let path = GMSMutablePath()
path.add(myLocation.coordinate)
path.add(self.startMarker.position)
path.add(self.endMarker.position)
let bounds = GMSCoordinateBounds(path: path)
self.mapView.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 40))
}
Showing All Markers with screen bound:
Here I am trying to provide you one simple example, hope this will help you.
Using this, you can show any number of markers on screen.
Example:
let path = GMSMutablePath()
for var i in 0 ..< array.count //Here take your "array" which contains lat and long.
{
let marker = GMSMarker()
let lat = Double(array.objectAtIndex(i).valueForKey(lattitude) as! String)
let long = Double(arrayr.objectAtIndex(i).valueForKey(longitude) as! String)
marker.position = CLLocationCoordinate2DMake(lat!,long!)
path.addCoordinate(marker.position)
marker.map = self.mapView
}
let bounds = GMSCoordinateBounds(path: path)
self.mapView!.animateWithCameraUpdate(GMSCameraUpdate.fitBounds(bounds, withPadding: 30.0))
Can you try the below code, this is converted from ObjC code here is the documentation of includingCoordinate
let bounds = GMSCoordinateBounds()
bounds.includingCoordinate(self.startMarker.position)
bounds.includingCoordinate(self.endMarker.position)
bounds.includingCoordinate(yourCurrentLocationPosition)
self.mapView.animateWithCameraUpdate(GMSCameraUpdate(fitBounds:bounds, padding:20.0f))
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
showMarkers(locations)
//stop updating location when you find suitable
}
func showMarkers(userLocation: [CLLocation]){
let location1: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: self.startLatitude, longitude: self.startLongitude)
let location2: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: self.endLatitude, longitude: self.endLongitude)
let location3: CLLocationCoordinate2D = userLocation[0].coordinate
var locationArray = []
locationArray.append(location1)
locationArray.append(location2)
locationArray.append(location3)
var bounds = GMSCoordinateBounds()
for location in locationArray
{
let latitude = location.valueForKey("latitude")
let longitude = location.valueForKey("longitude")
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
marker.map = self.mapView
bounds = bounds.includingCoordinate(marker.position)
}
let update = GMSCameraUpdate.fitBounds(bounds, withPadding: 100)
mapView.animateWithCameraUpdate(update)
}
Note:
locationArray contains three locations ie. Marker 1, Marker 2, user location.
For someone who is looking for solution with Objective-C, This might help
//Locations
CLLocationCoordinate2D source = CLLocationCoordinate2DMake(19.2880, 72.1587);
CLLocationCoordinate2D userLocation = CLLocationCoordinate2DMake(19.1780, 72.9577);
CLLocationCoordinate2D destination = CLLocationCoordinate2DMake(19.0760, 72.8777);
//Create a GMSMutablePath
GMSMutablePath *path = [GMSMutablePath new];
[path addCoordinate:source];
[path addCoordinate:userLocation];
[path addCoordinate:destination];
//Create bounds
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc]initWithPath:path];
//Update the camera position
[mapview animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds]];
How do you center the google map as user is moving, like driving? The code I have does not center the user, and the user just goes toward the border and disapper. How do you keep user in center all the time?
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = manager.location {
if (firstLoad) {
firstLoad = false
recentCoordinate = location.coordinate
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: Constants.MapView.Zoom, bearing: 0, viewingAngle: Constants.MapView.ViewingAngle)
}
else {
// This line of code suppose to center the user as user moves along while keeping the zoom and angle state user has chosen
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: mapView.camera.zoom, bearing: 0, viewingAngle: mapView.camera.viewingAngle)
}
// Filter out noise. Currently not working
let age = 0 - location.timestamp.timeIntervalSinceNow
if (age > 120) {
return; // ignore old (cached) updates
}
if (location.horizontalAccuracy < 0) {
return; // ignore invalid updates
}
if (location.horizontalAccuracy <= 10){
// this is a valid update
// Draw route as user moves along
path.addCoordinate(CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude))
let polyline = GMSPolyline(path: path)
polyline.strokeColor = UIColor.redColor()
polyline.strokeWidth = 3
polyline.map = mapView
// Save the coordinates to array
coordinateArray.append([location.coordinate.latitude, location.coordinate.longitude])
}
}
There are couple other ways you can try.
let update = GMSCameraUpdate.setTarget(location.coordinate)
mapView.moveCamera(update)
or
mapView.camera = GMSCameraPosition.camera(target: location.coordinate, zoom: 15.0)
or
let update = GMSCameraUpdate.setTarget(location.coordinate)
mapView.animate(with: update)
If anybody looking for swift 2.3 solution like myself, here is the solution :
mapView.animateToLocation(CLLocationCoordinate2D(latitude: location.coordinate.latitude,longitude: location.coordinate.longitude))