【问题标题】:Fill Polygon with icons用图标填充多边形
【发布时间】:2019-02-15 12:51:29
【问题描述】:

我可以在谷歌地图中绘制多边形。现在,当我在地图上绘制完该区域的多边形时,我想用相同的图标填充该多边形。我可以用颜色填充多边形,但我想用图标填充它,在边界处有一些填充,在图标之间有一些指定的空间。我该怎么做?

【问题讨论】:

  • 如果您在地图上处理多个多边形,这样做您的地图将开始滞后。
  • @V-rundPuro-hit 我想填写用户在地图上绘制的区域。当它将在数组中添加折线 latlong 但随后它将填充边框而不是内部时,我可以这样做。

标签: android google-maps


【解决方案1】:

Google Maps Android SDK Polygon 没有“开箱即用”的解决方案,用图案填充(仅实心填充),但您可以使用一些变通方法。

TLDR;

假设您需要填充公园多边形:

final List<LatLng> polygonPoints = new ArrayList<>();
polygonPoints.add(new LatLng(50.444477, 30.498976));
polygonPoints.add(new LatLng(50.445290, 30.500864));
polygonPoints.add(new LatLng(50.443958, 30.508921));
polygonPoints.add(new LatLng(50.443247, 30.508642));
polygonPoints.add(new LatLng(50.442830, 30.508878));
polygonPoints.add(new LatLng(50.440965, 30.508256));
polygonPoints.add(new LatLng(50.441949, 30.501443));

final Polygon polygon = mGoogleMap.addPolygon(new PolygonOptions()
        .addAll(polygonPoints)
        .strokeColor(Color.BLUE)
        .fillColor(Color.argb(20, 0, 255, 0)));

带有树木图标 (R.drawable.ic_forest):

你可以通过:

1) 通过带有自定义(树)图标的标记“填充”。为此,您需要找到多边形边界矩形并在循环中添加标记作为方形贪婪。要仅在多边形内添加标记,您可以使用来自Google Maps Android API Utility LibraryPolyUtil.containsLocation()

LatLngBounds bounds = getPolygonBounds(polygon.getPoints());

BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.ic_forest);

double boundHeight = bounds.northeast.latitude - bounds.southwest.latitude;
double boundWidth = bounds.northeast.longitude - bounds.southwest.longitude;

double gridCntLat = 5;  // 5 icons vertically
double gridCntLon = 5;  // 5 icons horizontally

double dLat = (bounds.northeast.latitude - bounds.southwest.latitude) / gridCntLat;
double dLng = (bounds.northeast.longitude - bounds.southwest.longitude) / gridCntLon;

double lat = dLat + bounds.southwest.latitude;

while (lat < bounds.northeast.latitude) {
    double lon = dLng + bounds.southwest.longitude;
    while (lon < bounds.northeast.longitude) {

        LatLng iconPos = new LatLng(lat, lon);

        if (PolyUtil.containsLocation(iconPos, polygonPoints, true)) {
            MarkerOptions markerOptions = new MarkerOptions().position(iconPos)
                    .draggable(false)
                    .flat(true)
                    .icon(icon);
            mGoogleMap.addMarker(markerOptions);
        }
        lon += dLng;
    }
    lat += dLat;
}

getPolygonBounds() 在哪里:

private LatLngBounds getPolygonBounds(List<LatLng> polygon) {
    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    for(int i = 0 ; i < polygon.size() ; i++) {
        builder.include(polygon.get(i));
    }
    LatLngBounds bounds = builder.build();
    return  bounds;
}

和 ... 私人谷歌地图 mGoogleMap; ...

@Override
public void onMapReady(GoogleMap googleMap) {
    mGoogleMap = googleMap;
    ...
}

结果你得到了类似的东西:

这是最简单的解决方案,但某些图标可能部分位于多边形边框之外(您可以通过MarkerOptions.anchor() 方法稍微调整它们,如果您希望标记图标随多边形旋转,您也可以设置MarkerOptions.flat(true))并且对于大量标记地图开始滞后。

2) 使用“动态”(以编程方式为每个多边形创建)GroundOverlay。为此,您还需要确定多边形矩形边界,然后创建具有相同像素比例的覆盖位图(还需要将其限制在较大的一侧以避免内存不足 (OOM) 错误)。注意,您需要使用Projection 来计算矩形边界,因为不同纬度和经度的 LatLng 度数以米为单位的大小与以像素为单位的大小不同。

然后,您需要在创建的位图画布上绘制多边形。为此,您可以将 Path 对象与重新计算的 LatLng 多边形点一起使用到以像素为单位的局部覆盖位图坐标中。你应该使用简单的比例,而不是Projection.toScreenLocation()

screenPoint.x = (int) (width * (latLng.longitude - bounds.southwest.longitude) / boundWidth);
screenPoint.y = height - (int) (height * (latLng.latitude - bounds.southwest.latitude) / boundHeight);  // y-axis of screen is inverted to Lat

要使用图标位图填充覆盖位图上的多边形,您可以使用Shader 并将其设置为Paint 对象和Paint.setShader()。最后,您可以将创建的位图作为地面叠加层添加到您的 GoogleMap 对象上。 fillPoly() 方法的完整源代码,用tileBitmap 填充polygon

private GroundOverlay fillPoly(Projection projection, Polygon polygon, Bitmap tileBitmap, float transparency) {
    LatLngBounds bounds = getPolygonBounds(polygon.getPoints());

    double boundHeight = bounds.northeast.latitude - bounds.southwest.latitude;
    double boundWidth = bounds.northeast.longitude - bounds.southwest.longitude;

    Point northEast = projection.toScreenLocation(bounds.northeast);
    Point southWest = projection.toScreenLocation(bounds.southwest);

    int screenBoundHeight = northEast.y - southWest.y;
    int screenBoundWidth = northEast.x - southWest.x;

    // determine overlay bitmap size
    double k = Math.abs((double) screenBoundHeight / (double) screenBoundWidth);
    int overlayWidth;
    int overlayHeight;
    if (Math.abs(boundWidth) > Math.abs(boundHeight)) {
        overlayWidth = OVERLAY_MAX_SIZE;
        overlayHeight = (int) (overlayWidth * k);
    } else {
        overlayHeight = OVERLAY_MAX_SIZE;
        overlayWidth = (int) (overlayHeight * k);
    }

    // create overlay bitmap
    Bitmap overlayBitmap = createOverlayBitmap(overlayWidth, overlayHeight, bounds, polygon.getPoints(), tileBitmap);

    // create ground overlay
    GroundOverlayOptions overlayOptions = new GroundOverlayOptions()
            .image(BitmapDescriptorFactory.fromBitmap(overlayBitmap))
            .transparency(transparency)
            .positionFromBounds(bounds);

    return mGoogleMap.addGroundOverlay(overlayOptions);
}

createOverlayBitmap() 在哪里:

private Bitmap createOverlayBitmap(int width, int height, LatLngBounds bounds, List<LatLng> polygon, Bitmap tileBitmap) {
    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);

    Path path = new Path();
    BitmapShader shader = new BitmapShader(tileBitmap,
            Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);

    Paint paint = new Paint();
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(10);
    paint.setAntiAlias(true);
    paint.setShader(shader);

    List<Point> screenPoints = new ArrayList<>(polygon.size());

    double boundHeight = bounds.northeast.latitude - bounds.southwest.latitude;
    double boundWidth = bounds.northeast.longitude - bounds.southwest.longitude;

    for (int i = 0; i < polygon.size(); i++) {
        LatLng latLng = polygon.get(i);
        Point screenPoint = new Point();
        screenPoint.x = (int) (width * (latLng.longitude - bounds.southwest.longitude) / boundWidth);
        screenPoint.y = height - (int) (height * (latLng.latitude - bounds.southwest.latitude) / boundHeight);
        screenPoints.add(screenPoint);
    }

    path.moveTo(screenPoints.get(0).x, screenPoints.get(0).y);
    for (int i = 1; i < polygon.size(); i++) {
        path.lineTo(screenPoints.get(i).x, screenPoints.get(i).y);
    }
    path.close();

    canvas.drawPath(path, paint);

    return bitmap;
}

你可以这样使用它:

Bitmap tileBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_forest);   // create bitmap of fill icon
fillPoly(mGoogleMap.getProjection(), polygon, tileBitmap, 0.5f);

mGoogleMap.getProjection() 需要正确计算位图比例。如果您需要在地图渲染之前使用mGoogleMap.getProjection(),您应该使用andrthis 解决方案。

结果你得到了这样的结果:

如果您需要“图标之间的一些指定空间”,则该空间应位于图标本身内:

(h - 水平空间,v - 垂直空间)。

地面叠加层总是随地图缩放和旋转,如果您需要独立于缩放的图标,您应该使用其他方法。同样对于大型叠加位图设备可能没有足够的内存。

3) 使用Tile Overlays。实际上它就像 p.2 但为每个缩放级别生成叠加位图并分成小块(例如 256x256 像素)以避免高内存消耗。您可以将生成的图块存储在设备文件系统中,并在'this'Alex Vasilkov 答案中实现TileProvider

4) 使用基于custom MapView 的视图并覆盖dispatchDraw(),您可以在其中控制在视图画布上绘制的所有内容。这是最好但复杂的解决方案。

注意!这只是演示 - 不是完整的解决方案(例如,它没有测试负 LatLng 等)。

【讨论】:

  • 感谢 Andrii,它对参考有很大帮助。
猜你喜欢
  • 1970-01-01
  • 2016-07-16
  • 1970-01-01
  • 1970-01-01
  • 2014-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
  • 2018-01-01
相关资源
最近更新 更多