I am able to draw polygon inside Google map. Now when I finished drawing a polygon of the area on the map, I want to fill that polygon with the same icons. I can fill the polygon with color, but I want to fill it with icons with some padding at the border and some specified space between the icons. How can i do it?
-
if you are dealing with multiple polygons on map, by doing so your map will start lagging. – V-rund Puro-hit Sep 11 '18 at 05:25
-
@V-rundPuro-hit i want to fill out the area that the user draws on map. I can do this when it will add polyline latlong inside array but then it will fill border and not the inner part. – Avi Patel Sep 11 '18 at 05:40
1 Answers
There is no "from the box" solution for Google Maps Android SDK Polygon
fill with pattern (only solid fill), but you can use some workarounds.
TLDR;
Assume you need fill park polygon:
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)));
with trees icons (R.drawable.ic_forest
):
You can do it via:
1) "fill" by markers with customized (tree) icons. For do that you need to find polygon bounds rectangle and add markers in loop as square greed. For adding markers only inside polygon you can use PolyUtil.containsLocation()
from Google Maps Android API Utility Library:
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;
}
where getPolygonBounds()
is:
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;
}
and ... private GoogleMap mGoogleMap; ...
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
...
}
As a result you got something like that:
It's easiest solution, but some icons may be partially outside polygon border (you can slightly adjust them by MarkerOptions.anchor()
method and also you can set MarkerOptions.flat(true)
if you want marker icons rotated with the polygon) and for large amount of markers map starts lagging.
2) use "dynamic" (programmatically created for each polygon) GroundOverlay
. For do that you also need to determine polygon rectangle bounds and than create overlay bitmap with same proportions in pixels (also you need to limit it by the larger side to avoid Out of Memory (OOM) Error). NB you need to use Projection
to calculate rectangle bounds because LatLng degrees at different latitudes and longitudes has different size in meters and than in pixels.
Than, you need draw polygon at created bitmap canvas. For do that you can use Path
object with recalculated LatLng polygon points into local overlay bitmap coordinates in pixels. You should use simple proportion for that, not 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
For filling polygon on overlay bitmap with bitmaps of your icons you can use Shader
and set it to Paint
object with Paint.setShader()
. Than, at last, you can add created bitmap as Ground Overlay on your GoogleMap object. Full source code of fillPoly()
method, that fills polygon
with 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);
}
where createOverlayBitmap()
is:
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;
}
And you can use it that way:
Bitmap tileBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_forest); // create bitmap of fill icon
fillPoly(mGoogleMap.getProjection(), polygon, tileBitmap, 0.5f);
mGoogleMap.getProjection()
needed for correct calculation of bitmap proportion. If you need use to use mGoogleMap.getProjection()
before map rendered you should use this solution of andr.
As a result you got something like this:
If you need "some specified space between the icons" that spaces should be inside icon itself:
(h - horizontal space, v - vertical space).
Ground overlays always scaled and rotated with map and if you need zoom-independent icon you should use other approach. Also for large overlay bitmaps device may not have enough memory.
3) use Tile Overlays
. Actually it's like p.2 but overlay bitmap generated for each zoom level and splitted into small (e.g. 256x256 pixels) tiles for avoid high memory consumption. You can store generated tiles in device file system and implement TileProvider
like in 'this' answer of Alex Vasilkov
;
4) use custom
MapView
-based view with overridden dispatchDraw()
where you can take control of everything of drawing on view canvas. It's the best, but complex solution.
NB! It's only demonstration - not the full solution (e.g. it didn't tested for negative LatLng etc.).

- 13,183
- 12
- 43
- 79