简体   繁体   中英

Android Google Maps: Cut hole in polygon using polyline

Is it possible to cut holes (or create a sort of mask image) using Polyline ?

Similar to simply drawing Polylines over the top of a Polygon except the lines serve as masks/holes that 'erase' the polygon area. On the left is what I can currently achieve with a Polygon object that spans the entire map and a few Polyline objects over the top. On the right is the goal:

在此处输入图片说明

Below is the code I currently use to create the simple lines over the map:

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)
}

I understand that you are able to create holes in Polygon objects using a list of LatLng objects but this requires you to provide 5 coordinates to create a single hole shape. I've also had issues with these hole objects where the Polygon fill disappears if the holes overlap each other or aren't valid in some way. Therefore I'm looking for an alternate approach to creating complex holes.

Anyway you can implement custom view, which extends MapView (or MapFragment ) class for full control of drawing on view canvas in dispatchDraw() :

...
@Override
public void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    canvas.save();
    drawMaskAndPolygonOverTheMap(canvas);
    canvas.restore();
}
...

For converting polygon geographic LatLng coordinates into screen coordinates you can use toScreenLocation() method of Projection class:

Projection projection = mapView.getProjection();
Point pointScreenCoords = projection.toScreenLocation(pointLatLngCoordinates);

UPDATE: *

For example, with custom MapView like this:

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;
    }
}

and its usage this way:

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();
    }

}

you should get as result something like this:

透明路径示例

Path width depends on zoom level (it set in meters, not in pixals) and you can control it by with LINE_WIDTH_IN_METERS constant ( 45 meters set now). Intensity of path you can control by values in .setAlpha(); calls.

NB! It's just example, not perfect solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM