【问题标题】:Android Google Maps: Cut hole in polygon using polylineAndroid Google Maps:使用折线在多边形中切孔
【发布时间】: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 填充就会消失。因此,我正在寻找一种替代方法来创建复杂的孔。

【问题讨论】:

  • 请看答案更新。

标签: android google-maps google-maps-android-api-2


【解决方案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
猜你喜欢
  • 1970-01-01
  • 2011-06-11
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-25
  • 1970-01-01
相关资源
最近更新 更多