Android Google Maps:使用折线在多边形中切孔
Posted
技术标签:
【中文标题】Android Google Maps:使用折线在多边形中切孔【英文标题】:Android Google Maps: Cut hole in polygon using polyline 【发布时间】:2021-09-16 23:09:08 【问题描述】:是否可以使用Polyline
切孔(或创建一种蒙版图像)?
类似于在Polygon
的顶部简单地绘制Polylines
,除了线条用作“擦除”多边形区域的掩码/孔。左边是我目前可以使用跨越整个地图的Polygon
对象和顶部的几个Polyline
对象来实现的。右边是目标:
以下是我目前用来在地图上创建简单线条的代码:
override fun onLocationResult(locationResult: LocationResult?)
locationResult ?: return
locationResult.lastLocation.let
if (it.accuracy > 0.5)
lastLocation = it
drawTrail(LatLng(it.latitude, it.longitude))
private fun drawTrail(newLocation: LatLng)
oldLine?.remove()
currentTrail.add(newLocation)
oldLine = map.addPolyline(currentTrail)
我了解您可以使用 LatLng
对象列表在 Polygon
对象中创建孔,但这需要您提供 5 个坐标才能创建单个孔形状。我也遇到了这些孔对象的问题,如果孔相互重叠或以某种方式无效,Polygon
填充就会消失。因此,我正在寻找一种替代方法来创建复杂的孔。
【问题讨论】:
请看答案更新。 【参考方案1】:无论如何,您都可以实现自定义视图,它扩展了MapView
(或MapFragment
)类,以便完全控制dispatchDraw()
中的视图画布上的绘图:
...
@Override
public void dispatchDraw(Canvas canvas)
super.dispatchDraw(canvas);
canvas.save();
drawMaskAndPolygonOverTheMap(canvas);
canvas.restore();
...
要将多边形地理LatLng
坐标转换为屏幕坐标,您可以使用Projection
类的toScreenLocation()
方法:
Projection projection = mapView.getProjection();
Point pointScreenCoords = projection.toScreenLocation(pointLatLngCoordinates);
更新:*
例如,像这样自定义MapView
:
public class PathMapView extends MapView implements OnMapReadyCallback
private static final float LINE_WIDTH_IN_METERS = 45;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private Paint mPaintPath;
private Paint mPaintBackground;
private Paint mPaintBitmap;
private ArrayList<LatLng> mPathPoints;
private Bitmap mBitmap;
private Canvas mBitmapCanvas;
public PathMapView(@NonNull Context context)
super(context);
init();
public PathMapView(@NonNull Context context, @Nullable AttributeSet attrs)
super(context, attrs);
init();
public PathMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
public PathMapView(@NonNull Context context, @Nullable GoogleMapOptions options)
super(context, options);
init();
@Override
public void dispatchDraw(Canvas canvas)
super.dispatchDraw(canvas);
canvas.save();
drawPolylineOverTheMap(canvas);
canvas.restore();
private void drawPolylineOverTheMap(Canvas canvas)
if (mGoogleMap == null || mPathPoints == null || mPathPoints.size() < 2)
return;
if (mBitmap == null)
mBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
mBitmapCanvas = new Canvas(mBitmap);
mBitmapCanvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaintBackground);
double metersPerPixel = (Math.cos(mGoogleMap.getCameraPosition().target.latitude * Math.PI / 180) * 2 * Math.PI * 6378137) / (256 * Math.pow(2, mGoogleMap.getCameraPosition().zoom));
float lineWidth = (float) (LINE_WIDTH_IN_METERS / metersPerPixel);
mPaintPath.setStrokeWidth(lineWidth);
Projection projection = mGoogleMap.getProjection();
for (int i = 1; i < mPathPoints.size(); i++)
final Point point1 = projection.toScreenLocation(mPathPoints.get(i-1));
final Point point2 = projection.toScreenLocation(mPathPoints.get(i));
mBitmapCanvas.drawLine(point1.x, point1.y, point2.x, point2.y, mPaintPath);
canvas.drawBitmap(mBitmap, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), mPaintBitmap);
private void init()
setWillNotDraw(false);
mPaintPath = new Paint();
mPaintPath.setColor(Color.WHITE);
mPaintPath.setStrokeWidth(25);
mPaintPath.setAlpha(255);
mPaintPath.setStrokeCap(Paint.Cap.ROUND);
mPaintBackground = new Paint();
mPaintBackground.setColor(Color.BLACK);
mPaintBackground.setAlpha(155);
mPaintBackground.setStrokeWidth(15);
mPaintBitmap = new Paint();
mPaintBitmap.setAlpha(50);
@Override
public void getMapAsync(OnMapReadyCallback callback)
mMapReadyCallback = callback;
super.getMapAsync(this);
@Override
public void onMapReady(GoogleMap googleMap)
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener()
@Override
public void onCameraMove()
invalidate();
);
if (mMapReadyCallback != null)
mMapReadyCallback.onMapReady(googleMap);
public void setPathPoints(final ArrayList<LatLng> pathPoints)
mPathPoints = pathPoints;
及其用法:
public class MainActivity extends AppCompatActivity
private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
static final LatLng V_ICE_SCREAMS = new LatLng(52.99728959196756, -1.1899122739632702);
private GoogleMap mGoogleMap;
private PathMapView mMapView;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle mapViewBundle = null;
if (savedInstanceState != null)
mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
mMapView = (PathMapView) findViewById(R.id.mapview);
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(new OnMapReadyCallback()
@Override
public void onMapReady(GoogleMap googleMap)
mGoogleMap = googleMap;
final ArrayList<LatLng> pathPoints = new ArrayList<>();
pathPoints.add(new LatLng(52.99728191304702, -1.1898995151709677));
pathPoints.add(new LatLng(52.99729343143402, -1.1915964365223883));
pathPoints.add(new LatLng(52.997462367423275, -1.1892870924275978));
pathPoints.add(new LatLng(52.99732798657649, -1.1888979488094147));
pathPoints.add(new LatLng(52.99848364819607, -1.1887576019291894));
pathPoints.add(new LatLng(52.99842989717891, -1.1903460734198053));
pathPoints.add(new LatLng(52.99632203666474, -1.1900781384562662));
pathPoints.add(new LatLng(52.99728959196756, -1.1898484799275026));
mMapView.setPathPoints(pathPoints);
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(V_ICE_SCREAMS,15));
);
@Override
public void onSaveInstanceState(Bundle outState)
super.onSaveInstanceState(outState);
Bundle mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY);
if (mapViewBundle == null)
mapViewBundle = new Bundle();
outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle);
mMapView.onSaveInstanceState(mapViewBundle);
@Override
protected void onResume()
super.onResume();
mMapView.onResume();
@Override
protected void onStart()
super.onStart();
mMapView.onStart();
@Override
protected void onStop()
super.onStop();
mMapView.onStop();
@Override
protected void onPause()
mMapView.onPause();
super.onPause();
@Override
protected void onDestroy()
mMapView.onDestroy();
super.onDestroy();
@Override
public void onLowMemory()
super.onLowMemory();
mMapView.onLowMemory();
你应该得到这样的结果:
路径宽度取决于缩放级别(它以米为单位,而不是以像素为单位),您可以使用LINE_WIDTH_IN_METERS
常量(现在设置45
米)来控制它。您可以通过.setAlpha();
调用中的值控制路径的强度。
注意!这只是示例,不是完美的解决方案。
【讨论】:
很棒的解决方案。现在我只需要能够处理地图的缩放和移动!但这非常解决了挖洞露出背后地图的问题。projection.toScreenLocation()
很神奇。感谢您花时间回答这个问题!
另外,Math.cos(mGoogleMap.getCameraPosition().target.latitude * Math.PI / 180) * 2 * Math.PI * 6378137) / (256 * Math.pow(2, mGoogleMap.getCameraPosition().zoom)
是一个疯狂的数学题,请问您在哪里找到它以及6378137
代表什么?
@KesWalker 它是Earth radius,以米为单位(大约)。这种“疯狂的数学”(不完全是)需要,因为赤道附近的一度纬度和两极附近的一度纬度以米为单位不相等(由于地球形状) - 两极附近的一度转换为更少的米(如this image) .
@KesWalker “现在我只需要能够处理地图的缩放和移动!” - 该解决方案应该适用于这种情况。
@KesWalker 还有关于math。以上是关于Android Google Maps:使用折线在多边形中切孔的主要内容,如果未能解决你的问题,请参考以下文章
Google Maps Android API v2中折线的触摸检测
如何自定义折线看起来像 Google Maps Android 应用程序?
如何根据 rgw 距离在 android 中沿一条线在 Google Maps 折线上添加标记?