Android Google Maps:使用折线在多边形中切孔

Posted

技术标签:

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

【问题讨论】:

请看答案更新。 【参考方案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。

以上是关于Android Google Maps:使用折线在多边形中切孔的主要内容,如果未能解决你的问题,请参考以下文章

Google Maps Android API v2中折线的触摸检测

如何自定义折线看起来像 Google Maps Android 应用程序?

如何根据 rgw 距离在 android 中沿一条线在 Google Maps 折线上添加标记?

在 Android Google Maps API v2 中,交通数据显示在绘制的形状(折线)上方

折线在模拟器上崩溃

Google Maps v2 - 用不同颜色绘制路线 - Android