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.