Swift Google Maps Smoothly Rounded Polylines

Show Path on GoogleMaps in Swift4

Preconditions

You need to get a Google directions api Key following this link
How to get a Google Directions API key and you also need to add this line

GMSPlacesClient.provideAPIKey("Your API KEY")

in your AppDelegate didFinishLaunchingWithOptions method

Now our issue

To find a path you need to use a method like this one, using googleapis.directions request, passing two CLLocationCoordinate2D then in the closure you will get an array of CLLocationCoordinate2D which are the waypoints of your path

public func getWaypointsAsArrayOfCoordinates(startLocation: CLLocationCoordinate2D, endLocation: CLLocationCoordinate2D, mode:String? = "walking", lang:String? = "en", finishedClosure:@escaping (([CLLocationCoordinate2D])->Void)){

var resultedArray:[CLLocationCoordinate2D] = []

let urlWithParams = "https://maps.googleapis.com/maps/api/directions/json" + self.customEncodedParameters(parametersDict: ["origin":"\(startLocation.latitude),\(startLocation.longitude)", "destination": "\(endLocation.latitude),\(endLocation.longitude)", "mode": mode!, "key":googleDirectionsApiKey, "language" : lang!])

var urlRequest = URLRequest(url: URL(string: urlWithParams)!)
urlRequest.httpMethod = "GET"

URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in

if let _ = error {
} else {
do {
if let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
let status = jsonData["status"] as! String
if(status == "OK") {

for routeDict in jsonData["routes"] as! Array<Dictionary<String, AnyObject>>
{
let legs = routeDict["legs"] as! Array<Dictionary<String, AnyObject>>
for leg in legs
{
let steps = leg["steps"] as! Array<Dictionary<String, AnyObject>>
for (index,step) in steps.enumerated(){
let start = step["start_location"] as! Dictionary<String,Any>
let end = step["end_location"] as! Dictionary<String,Any>
resultedArray.append(CLLocationCoordinate2D(latitude: start["lat"] as! CLLocationDegrees, longitude: start["lng"] as! CLLocationDegrees))
if(index == steps.count - 1) {
resultedArray.append(CLLocationCoordinate2D(latitude: end["lat"] as! CLLocationDegrees, longitude: end["lng"] as! CLLocationDegrees))
}
}
}
}
finishedClosure(resultedArray)
}
else {
print("not found")
finishedClosure([])
}
}
} catch {
print(error)
finishedClosure([])
}
}

}.resume()

}

Edit (Added missing function)

private func customEncodedParameters(parametersDict:[String:String]) ->String
{
let charactersAllowed = CharacterSet.urlQueryAllowed
var returnStr = ""
for key in parametersDict.keys {
if(returnStr.count == 0)
{
returnStr += "?"
returnStr += key.addingPercentEncoding(withAllowedCharacters: charactersAllowed)!
returnStr += "="
returnStr += parametersDict[key]!.addingPercentEncoding(withAllowedCharacters: charactersAllowed)!
}else{
returnStr += "&"
returnStr += key.addingPercentEncoding(withAllowedCharacters: charactersAllowed)!
returnStr += "="
returnStr += parametersDict[key]!.addingPercentEncoding(withAllowedCharacters: charactersAllowed)!
}
}
return returnStr
}

Then you can use it in your code like this

extension HomeViewController: DropLocationDelegate {
func didSelectDrop(location: GooglePlaceModel?) {
guard let location = location else {return}
dropLocationLbl?.text = location.name

let position = CLLocationCoordinate2D(latitude: location.latitude , longitude: location.longitude)
print(position)
let marker = GMSMarker(position: position)
marker.icon = #imageLiteral(resourceName: "icon-drop-location")
marker.map = mapView

mapView.camera = GMSCameraPosition.camera(withTarget: position, zoom: 14)
pickupMarkers.append(marker)
self.getWaypointsAsArrayOfCoordinates(startLocation: pickupMarkers.first.position , endLocation: pickupMarkers.last.position) { [weak self] (arrayOfCoordinates) in

DispatchQueue.main.async {
let path = GMSMutablePath()

for coordinate in arrayOfCoordinates {
path.add(coordinate)
}

let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 2
polyline.strokeColor = UIColor.red
polyline.map = self?.mapView

let bounds = GMSCoordinateBounds(path: path)
self?.mapView?.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 50.0))
}
}
}
}

Can I draw a curved dashed line in Google Maps Android?

You can implement the curved dashed polyline between two points. For this purpose you can use Google Maps Android API Utility Library that has SphericalUtil class and apply some math in your code to create a polyline.

You have to include the utility library in your gradle as

compile 'com.google.maps.android:android-maps-utils:0.5'.

Please have a look at my sample Activity and function showCurvedPolyline (LatLng p1, LatLng p2, double k) that constructs dashed curved polyline between two points. The last parameter k defines curvature of the polyline, it can be >0 and <=1. In my example I used k=0.5

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

private GoogleMap mMap;
private LatLng sydney1;
private LatLng sydney2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;

mMap.getUiSettings().setZoomControlsEnabled(true);

// Add a marker in Sydney and move the camera
sydney1 = new LatLng(-33.904438,151.249852);
sydney2 = new LatLng(-33.905823,151.252422);

mMap.addMarker(new MarkerOptions().position(sydney1)
.draggable(false).visible(true).title("Marker in Sydney 1"));
mMap.addMarker(new MarkerOptions().position(sydney2)
.draggable(false).visible(true).title("Marker in Sydney 2"));

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney1, 16F));

this.showCurvedPolyline(sydney1,sydney2, 0.5);
}

private void showCurvedPolyline (LatLng p1, LatLng p2, double k) {
//Calculate distance and heading between two points
double d = SphericalUtil.computeDistanceBetween(p1,p2);
double h = SphericalUtil.computeHeading(p1, p2);

//Midpoint position
LatLng p = SphericalUtil.computeOffset(p1, d*0.5, h);

//Apply some mathematics to calculate position of the circle center
double x = (1-k*k)*d*0.5/(2*k);
double r = (1+k*k)*d*0.5/(2*k);

LatLng c = SphericalUtil.computeOffset(p, x, h + 90.0);

//Polyline options
PolylineOptions options = new PolylineOptions();
List<PatternItem> pattern = Arrays.<PatternItem>asList(new Dash(30), new Gap(20));

//Calculate heading between circle center and two points
double h1 = SphericalUtil.computeHeading(c, p1);
double h2 = SphericalUtil.computeHeading(c, p2);

//Calculate positions of points on circle border and add them to polyline options
int numpoints = 100;
double step = (h2 -h1) / numpoints;

for (int i=0; i < numpoints; i++) {
LatLng pi = SphericalUtil.computeOffset(c, r, h1 + i * step);
options.add(pi);
}

//Draw polyline
mMap.addPolyline(options.width(10).color(Color.MAGENTA).geodesic(false).pattern(pattern));
}

}

You can download a sample project with complete code from GitHub

https://github.com/xomena-so/so43305664

Just replace my API key with yours in the app/src/debug/res/values/google_maps_api.xml

Sample Image



Related Topics



Leave a reply



Submit