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

4

9

In Google Maps from browser which has the curved dashed line look like this: enter image description here

But when I implement Google Maps in my own Android project, it didn't show this line

enter image description here

How can I draw this line?

Matri answered 9/4, 2017 at 10:28 Comment(2)
I think this is possible, I found here a jsfiddle that can do it. But the problem is it uses Google Maps Javascript API to make it work. For more information, try to check this SO question if it can help you.Calcutta
@Calcutta thanks a lot, straight dashed polyline in Google Maps Android API can be implemented, but the curved dashed line just appear in Javascript API. I has checked Google Maps application on iOS, Android, its didn't have any curved line, I guess this impossible (or too hard to draw)Tanyatanzania
W
35

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

enter image description here

Whitsunday answered 27/4, 2017 at 18:29 Comment(5)
Thank a lot, It solved my problem. I apologize for comment late, because my task for this problem has started again after a long time pending to start another.Tanyatanzania
There is a bug in this code where if you use two very remote locations the curve will hit the wrong center locationValery
For very remote locations just use geodesic polylines.Whitsunday
i tried getting rid of the dashes to make it solid but when i do that the circle is not smooth . any ideas ? i even added more points but same thingGoodfellowship
is there anyway this can be achieved in javascript and react native?Flatboat
E
7

Thanks to @xomena for the great answer. But it has just one little bug. Sometimes, it's arc becoming like a circle. I made a few debugging and see that, we are always using h + 90.0 for heading value at the 12. line of the method. We can solve this by changing that line like this:

LatLng c = SphericalUtil.computeOffset(p, x, h > 40 ? h + 90.0 : h - 90.0);

From now, you probably not encounter this problem again.

Earpiercing answered 5/5, 2020 at 7:40 Comment(0)
R
7

I had the same problem of crooked curved line when I am drawing curve in solid line. After few hours of searching on the internet and trying the different solution. Finally, I came up with the solution (NOT a proper solution but target can be achieved) by using Polygon instead of Polyline. I have modified the above method showCurvedPolyline() to draw a smooth curve and the curve direction will always be in upward direction. Below screenshots are the final result of my modified version.

enter image description here

enter image description here

fun drawCurveOnMap(googleMap: GoogleMap, latLng1: LatLng, latLng2: LatLng) {

    //Adding marker is optional here, you can move out from here.
    googleMap.addMarker(
       MarkerOptions().position(latLng1).icon(BitmapDescriptorFactory.defaultMarker()))
    googleMap.addMarker(
       MarkerOptions().position(latLng2).icon(BitmapDescriptorFactory.defaultMarker()))
    
    val k = 0.5 //curve radius
    var h = SphericalUtil.computeHeading(latLng1, latLng2)
    var d = 0.0
    val p: LatLng?

    //The if..else block is for swapping the heading, offset and distance 
    //to draw curve always in the upward direction
    if (h < 0) {
        d = SphericalUtil.computeDistanceBetween(latLng2, latLng1)
        h = SphericalUtil.computeHeading(latLng2, latLng1)
        //Midpoint position
        p = SphericalUtil.computeOffset(latLng2, d * 0.5, h)
    } else {
        d = SphericalUtil.computeDistanceBetween(latLng1, latLng2)

        //Midpoint position
        p = SphericalUtil.computeOffset(latLng1, d * 0.5, h)
    }

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

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

    //Calculate heading between circle center and two points
    val h1 = SphericalUtil.computeHeading(c, latLng1)
    val h2 = SphericalUtil.computeHeading(c, latLng2)

    //Calculate positions of points on circle border and add them to polyline options
    val numberOfPoints = 1000 //more numberOfPoints more smooth curve you will get
    val step = (h2 - h1) / numberOfPoints

    //Create PolygonOptions object to draw on map
    val polygon = PolygonOptions()

    //Create a temporary list of LatLng to store the points that's being drawn on map for curve 
    val temp = arrayListOf<LatLng>()

    //iterate the numberOfPoints and add the LatLng to PolygonOptions to draw curve
    //and save in temp list to add again reversely in PolygonOptions
    for (i in 0 until numberOfPoints) {
        val latlng = SphericalUtil.computeOffset(c, r, h1 + i * step)
        polygon.add(latlng) //Adding in PolygonOptions
        temp.add(latlng)    //Storing in temp list to add again in reverse order
    }

    //iterate the temp list in reverse order and add in PolygonOptions
    for (i in (temp.size - 1) downTo 1) {
        polygon.add(temp[i])
    }

    polygon.strokeColor(Color.BLUE)
    polygon.strokeWidth(12f)
    polygon.strokePattern(listOf(Dash(30f), Gap(50f))) //Skip if you want solid line
    googleMap.addPolygon(polygon)

    temp.clear() //clear the temp list
}

Why are we adding temp list again in reverse order in PolygonOptions?

If we do not add LatLng again in PolygonOptions in reverse order, the googleMap.addPolygon() will close the path and the final result will be look like below.

enter image description here

TIPS:

If you want the curve is more in circular shape, increase the value of k. like k = 0.75

Repertoire answered 24/7, 2020 at 17:40 Comment(0)
D
2

Thanks @xomena for the solution above. It works beautifully in most cases. But there needs some improvement:

  • When k == 1, x will be 0 and midpoint (p) will be the same as mid curve point (c). That means it should be a straight line, but then when you calculate the step, it's not Zero so the final result is a half-circle curve, which is ambiguous with the above condition.

  • When the curve is long enough, let say LIMIT = 1000km, each calculation in h1 + i * step inside the loop make a tiny error to the correct value (due to java double calculation error I guess). Then the start and end points of the polyline not exactly match with start and end coordinations. Moreover, the curvature of the polyline is unpredictable, base on my research, the reason can be the curvature of the Earth's surface that can make your calculation base on heading not correct.

My quick fix is to reset the step to 0 if k == 1 to make it a straight line. For the second problem, if the distance between 2 points is greater than a LIMIT of 1000km, drawing a straight line with k = 1 will be a safer choice to me.

Discontinue answered 25/9, 2017 at 6:11 Comment(1)
Thanks for sharing this. Please have a look at this: https://mcmap.net/q/473829/-draw-arc-polyline-in-google-map Can I have the updated solution you finally wrote for showCurvedPolyline(..) methodWellthoughtof

© 2022 - 2024 — McMap. All rights reserved.