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 坐标转换为屏幕坐标,您可以使用 ProjectiontoScreenLocation() 方法 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(); 调用中的值控制的路径强度。

注意!这只是示例,不是完美的解决方案。