我可以在 Google 地图 Android 中画一条弯曲的虚线吗?
Can I draw a curved dashed line in Google Maps Android?
在 Google 中,来自浏览器的具有弯曲虚线的地图如下所示:
但是当我在我自己的 Android 项目中实现 Google 地图时,它没有显示这一行
这条线怎么画?
您可以在两点之间实现曲线虚折线。为此,您可以使用具有 SphericalUtil class 的 Google Maps Android API Utility Library 并在您的代码中应用一些数学来创建折线。
您必须将实用程序库包含在您的 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 密钥替换为您的
感谢@xomena 提供上述解决方案。在大多数情况下,它工作得很好。但还需要改进:
当k == 1
时,x 将为 0,中点 (p) 将与中间曲线点 (c) 相同。也就是说它应该是一条直线,但是当你计算步长时,它不是零所以最后的结果是一个半圆曲线,这与上面的条件有歧义。
当曲线足够长时,假设 LIMIT = 1000km
,循环内 h1 + i * step
中的每个计算都会对正确值产生微小的错误(由于 java double
我猜是计算错误)。然后折线的起点和终点与起点和终点坐标不完全匹配。而且,折线的曲率是不可预测的,根据我的研究,原因可能是地球表面的曲率使你根据航向计算不正确。
我的快速修复是将 step
重置为 0 if k == 1
使其成为一条直线。对于第二个问题,如果2点之间的距离大于1000km的LIMIT,我画直线k = 1
会是比较安全的选择。
感谢@xomena 的精彩回答。但它只有一个小错误。有时,它的圆弧变得像一个圆圈。我进行了一些调试,发现我们总是在方法的第 12 行使用 h + 90.0
作为标题值。我们可以通过像这样更改该行来解决此问题:
LatLng c = SphericalUtil.computeOffset(p, x, h > 40 ? h + 90.0 : h - 90.0);
从现在开始,您可能不会再遇到这个问题了。
我在画实线曲线时遇到了同样的曲线弯曲的问题。在互联网上搜索了几个小时并尝试了不同的解决方案之后。最后,我通过使用 Polygon
而不是 Polyline
想出了解决方案( 不是 合适的解决方案,但可以实现目标)。我修改了上面的方法showCurvedPolyline()
画了一条平滑的曲线,曲线的方向永远是向上的。下面截图是我修改后的最终效果。
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()
将关闭路径,最终结果如下所示。
提示:
如果你想要曲线更圆,增加k
的值。喜欢 k = 0.75
在 Google 中,来自浏览器的具有弯曲虚线的地图如下所示:
但是当我在我自己的 Android 项目中实现 Google 地图时,它没有显示这一行
这条线怎么画?
您可以在两点之间实现曲线虚折线。为此,您可以使用具有 SphericalUtil class 的 Google Maps Android API Utility Library 并在您的代码中应用一些数学来创建折线。
您必须将实用程序库包含在您的 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
感谢@xomena 提供上述解决方案。在大多数情况下,它工作得很好。但还需要改进:
当
k == 1
时,x 将为 0,中点 (p) 将与中间曲线点 (c) 相同。也就是说它应该是一条直线,但是当你计算步长时,它不是零所以最后的结果是一个半圆曲线,这与上面的条件有歧义。当曲线足够长时,假设
LIMIT = 1000km
,循环内h1 + i * step
中的每个计算都会对正确值产生微小的错误(由于 javadouble
我猜是计算错误)。然后折线的起点和终点与起点和终点坐标不完全匹配。而且,折线的曲率是不可预测的,根据我的研究,原因可能是地球表面的曲率使你根据航向计算不正确。
我的快速修复是将 step
重置为 0 if k == 1
使其成为一条直线。对于第二个问题,如果2点之间的距离大于1000km的LIMIT,我画直线k = 1
会是比较安全的选择。
感谢@xomena 的精彩回答。但它只有一个小错误。有时,它的圆弧变得像一个圆圈。我进行了一些调试,发现我们总是在方法的第 12 行使用 h + 90.0
作为标题值。我们可以通过像这样更改该行来解决此问题:
LatLng c = SphericalUtil.computeOffset(p, x, h > 40 ? h + 90.0 : h - 90.0);
从现在开始,您可能不会再遇到这个问题了。
我在画实线曲线时遇到了同样的曲线弯曲的问题。在互联网上搜索了几个小时并尝试了不同的解决方案之后。最后,我通过使用 Polygon
而不是 Polyline
想出了解决方案( 不是 合适的解决方案,但可以实现目标)。我修改了上面的方法showCurvedPolyline()
画了一条平滑的曲线,曲线的方向永远是向上的。下面截图是我修改后的最终效果。
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()
将关闭路径,最终结果如下所示。
提示:
如果你想要曲线更圆,增加k
的值。喜欢 k = 0.75