Android Google 地图:使用折线在多边形中切洞
Android Google Maps: Cut hole in polygon using polyline
是否可以使用 Polyline
来切孔(或创建某种蒙版图像)?
类似于在 Polygon
的顶部简单地绘制 Polylines
,只是线条用作 masks/holes,'erase' 多边形区域。左边是我目前可以使用跨越整个地图的 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
填充就会消失。因此,我正在寻找创建复杂孔的替代方法。
无论如何你都可以实现自定义视图,扩展 MapView
(或 MapFragment
)class 以完全控制在 dispatchDraw()
视图 canvas 上绘图:
...
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawMaskAndPolygonOverTheMap(canvas);
canvas.restore();
}
...
要将多边形地理 LatLng
坐标转换为屏幕坐标,您可以使用 Projection
的 toScreenLocation()
方法 class:
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();
调用中的值控制的路径强度。
注意!这只是示例,不是完美的解决方案。
是否可以使用 Polyline
来切孔(或创建某种蒙版图像)?
类似于在 Polygon
的顶部简单地绘制 Polylines
,只是线条用作 masks/holes,'erase' 多边形区域。左边是我目前可以使用跨越整个地图的 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
填充就会消失。因此,我正在寻找创建复杂孔的替代方法。
无论如何你都可以实现自定义视图,扩展 MapView
(或 MapFragment
)class 以完全控制在 dispatchDraw()
视图 canvas 上绘图:
...
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawMaskAndPolygonOverTheMap(canvas);
canvas.restore();
}
...
要将多边形地理 LatLng
坐标转换为屏幕坐标,您可以使用 Projection
的 toScreenLocation()
方法 class:
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();
调用中的值控制的路径强度。
注意!这只是示例,不是完美的解决方案。