Заполните многоугольник иконками - PullRequest
0 голосов
/ 11 сентября 2018

Я могу нарисовать многоугольник внутри карты Google. Теперь, когда я закончил рисовать многоугольник области на карте, я хочу заполнить этот многоугольник теми же значками. Я могу заполнить многоугольник цветом, но я хочу заполнить его иконками с отступами на границе и определенным пространством между иконками. Как я могу это сделать?

1 Ответ

0 голосов
/ 14 сентября 2018

Для 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)));

Source polygon

с иконками деревьев (R.drawable.ic_forest):

Tile icon

Вы можете сделать это через:

1) «заполнить» маркерами с помощью пользовательских (древовидных) значков. Для этого вам нужно найти прямоугольники границ полигонов и добавить маркеры в цикле в виде квадратной жадности. Для добавления маркеров только внутри полигона вы можете использовать PolyUtil.containsLocation() из Библиотека утилит Google Maps Android API :

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

и ... частный GoogleMap mGoogleMap; ...

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

В результате вы получили что-то вроде этого:

Filed by markers

Это самое простое решение, но некоторые значки могут быть частично за пределами границы многоугольника (вы можете немного отрегулировать их с помощью метода MarkerOptions.anchor(), а также можете установить MarkerOptions.flat(true), если хотите значки маркеров повернуты вместе с многоугольником) и при большом количестве маркеров карта начинает отставать.

2) использовать «динамический» (программно созданный для каждого многоугольника) GroundOverlay. Для этого вам также необходимо определить границы многоугольника и затем создать наложение растрового изображения с такими же пропорциями в пикселях (также вам нужно ограничить его большей стороной, чтобы избежать ошибки нехватки памяти (OOM)). Обратите внимание, что вам нужно использовать Projection для вычисления границ прямоугольника, потому что градусы широты на разных широтах и ​​долготах имеют разный размер в метрах и в пикселях.

Тогда вам нужно нарисовать многоугольник на созданном растровом холсте. Для этого вы можете использовать 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(). Затем, наконец, вы можете добавить созданное растровое изображение как Ground Overlay на ваш объект GoogleMap. Полный исходный код метода fillPoly(), который заполняет polygon tileBitmap:

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() до отображения карты, вы должны использовать это решение andr .

В результате вы получили что-то вроде этого:

Polygon with Ground overlay

Если вам нужно «определенное расстояние между значками», то пробелы должны быть внутри самого значка:

Tile icon with spaces

(h - горизонтальное пространство, v - вертикальное пространство).

Наземные наложения всегда масштабируются и поворачиваются вместе с картой, и если вам нужен значок, не зависящий от масштаба, вам следует использовать другой подход. Также для больших оверлеев растровых изображений может не хватать памяти.

3) использовать Tile Overlays. На самом деле это похоже на стр.2, но наложение растрового изображения генерируется для каждого уровня масштабирования и разбивается на маленькие (например, 256x256 пикселей) плитки, чтобы избежать высокого потребления памяти. Вы можете сохранить сгенерированные тайлы в файловой системе устройства и реализовать TileProvider как в 'this' ответ Alex Vasilkov;

4) использовать custom MapView на основе вида с переопределением dispatchDraw(), где вы можете управлять всем рисованием на холсте вида , Это лучшее, но сложное решение.

Внимание! Это только демонстрация, а не полное решение (например, оно не проверялось на отрицательный LatLng и т. Д.).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...