我能在Google Maps Android中画一条曲线虚线吗?

9
在浏览器中的Google地图上,曲线虚线看起来像这样:enter image description here。但是当我在自己的Android项目中实现Google地图时,它没有显示这条线。

enter image description here

我该如何绘制这条线?


我认为这是可能的,我在这里找到了一个jsfiddle可以做到这一点。但问题是它使用Google Maps Javascript API使其工作。如需更多信息,请尝试查看此SO问题是否能帮助您。 - KENdi
@KENdi 非常感谢,直线虚线在Google Maps Android API中可以实现,但是曲线虚线只出现在Javascript API中。我已经检查了iOS、Android上的Google Maps应用程序,它们没有任何曲线,我猜这是不可能的(或者太难画了)。 - Tấn Nguyên
4个回答

34

你可以在两个点之间实现弯曲虚线折线。为此,您可以使用Google Maps Android API Utility Library,该库具有SphericalUtil类,并在代码中应用一些数学来创建折线。

您必须在gradle中包含实用程序库:

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

请查看我的示例Activity和函数showCurvedPolyline(LatLng p1,LatLng p2,double k),该函数构建两点之间的弯曲虚线折线。最后一个参数k定义折线的曲率,它可以是> 0且<= 1。在我的示例中,我使用了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));
}

}
你可以从GitHub下载一个具有完整代码的示例项目。 https://github.com/xomena-so/so43305664 只需在app/src/debug/res/values/google_maps_api.xml中用你自己的API密钥替换我的即可。 enter image description here

1
非常感谢,它解决了我的问题。我很抱歉晚评论,因为这个问题的任务在长时间等待后重新开始,转而开始另一个任务。 - Tấn Nguyên
1
这段代码存在一个错误,如果使用两个非常远的位置,曲线将会命中错误的中心位置。 - Lalle
对于非常偏远的地点,只需使用大圆弧折线。 - xomena
我试图去掉破折号使其变得坚实,但这样做时圆圈就不光滑。有什么想法吗?我甚至添加了更多点,但是还是同样的情况。 - j2emanue
1
这个能在JavaScript和React Native中实现吗? - vincent O

7

感谢 @xomena 提供的优秀回答。但是它有一个小问题,有时候圆弧会变成圆形。我进行了一些调试并发现,在该方法的第12行中,我们总是使用 h + 90.0 作为航向值。我们可以通过将该行更改为以下内容来解决这个问题:

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

从现在开始,您很可能不再遇到这个问题。


6
我在画实线曲线时遇到了弯曲的问题。在互联网上搜索并尝试不同的解决方案几个小时之后,最终我想出了一个解决方案(不是一个正式的解决方案,但可以达到目标),即使用Polygon代替Polyline。我修改了上述方法showCurvedPolyline()以绘制平滑曲线,并且曲线方向始终朝上。下面的屏幕截图是我的修改版本的最终结果。

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
}

为什么我们需要在PolygonOptions中再次以相反的顺序添加临时列表?

如果我们不以相反的顺序在PolygonOptions中再次添加LatLng,那么googleMap.addPolygon()将关闭路径,最终结果将如下所示。

enter image description here

提示:

如果您想让曲线更接近圆形,请增加k的值,例如k = 0.75


2

感谢 @xomena 提供的解决方案。它在大多数情况下都能很好地工作。但还有一些需要改进:

  • k == 1 时,x 将为0且中间点(p)将与中间曲线点(c)相同。这意味着它应该是一条直线,但当你计算步长时,结果不是零,因此最终结果是一个半圆曲线,这与上述条件模糊不清。

  • 当曲线足够长时,比如 LIMIT = 1000km,循环内的每个 h1 + i * step 计算都会对正确值产生微小误差(我猜是由于 Java 的 double 计算误差造成的)。然后,折线的起点和终点与起始坐标和结束坐标不完全匹配。此外,折线的曲率是不可预测的,根据我的研究,原因可能是地球表面的曲率使得基于航向的计算不正确。

我的快速修复方法是,如果 k == 1,则将 step 重置为0,以使其成为一条直线。对于第二个问题,如果两点之间的距离大于1000km的限制,则使用 k = 1 来绘制一条直线会是更安全的选择。


谢谢分享。请看这个链接:stackoverflow.com/questions/46411266/draw-arc-polyline-in-google-map 你能给我最终编写的showCurvedPolyline(..)方法的更新解决方案吗? - Sonali

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接