Android Maps API V2 Draw Circle

Android Maps API v2 draw circle

How to draw circle in Google Maps v2 (bitmap)

// 1. some variables:

private static final double EARTH_RADIUS = 6378100.0;
private int offset;

// 2. convert meters to pixels between 2 points in current zoom:

private int convertMetersToPixels(double lat, double lng, double radiusInMeters) {

double lat1 = radiusInMeters / EARTH_RADIUS;
double lng1 = radiusInMeters / (EARTH_RADIUS * Math.cos((Math.PI * lat / 180)));

double lat2 = lat + lat1 * 180 / Math.PI;
double lng2 = lng + lng1 * 180 / Math.PI;

Point p1 = YourActivity.getMap().getProjection().toScreenLocation(new LatLng(lat, lng));
Point p2 = YourActivity.getMap().getProjection().toScreenLocation(new LatLng(lat2, lng2));

return Math.abs(p1.x - p2.x);
}

// 3. bitmap creation:

private Bitmap getBitmap() {

// fill color
Paint paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint1.setColor(0x110000FF);
paint1.setStyle(Style.FILL);

// stroke color
Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint2.setColor(0xFF0000FF);
paint2.setStyle(Style.STROKE);

// icon
Bitmap icon = BitmapFactory.decodeResource(YourActivity.getResources(), R.drawable.blue);

// circle radius - 200 meters
int radius = offset = convertMetersToPixels(lat, lng, 200);

// if zoom too small
if (radius < icon.getWidth() / 2) {

radius = icon.getWidth() / 2;
}

// create empty bitmap
Bitmap b = Bitmap.createBitmap(radius * 2, radius * 2, Config.ARGB_8888);
Canvas c = new Canvas(b);

// draw blue area if area > icon size
if (radius != icon.getWidth() / 2) {

c.drawCircle(radius, radius, radius, paint1);
c.drawCircle(radius, radius, radius, paint2);
}

// draw icon
c.drawBitmap(icon, radius - icon.getWidth() / 2, radius - icon.getHeight() / 2, new Paint());

return b;
}

// 4. calculate image offset:

private LatLng getCoords(double lat, double lng) {

LatLng latLng = new LatLng(lat, lng);

Projection proj = YourActivity.getMap().getProjection();
Point p = proj.toScreenLocation(latLng);
p.set(p.x, p.y + offset);

return proj.fromScreenLocation(p);
}

// 5. draw:

MarkerOptions options = new MarkerOptions();
options.position(getCoords(lat, lng));
options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap()));

marker = YourActivity.getMap().addMarker(options);

and result:

google maps v2 draw circle

Google Maps Api V2 draw circle around current position

Circle circle = map.addCircle(new CircleOptions()
.center(new LatLng(loc.latitude, loc.longitude))
.radius(1000)
.strokeColor(Color.RED)
.fillColor(Color.BLUE));

More info https://developers.google.com/android/reference/com/google/android/gms/maps/model/Circle

You should subscribe to updates of user location and reset the circle center with

circle.setCenter(newLocation);

Draw circles of constant size on screen in Google Maps API v2

Use a custom icon for Marker instead. You can create Bitmap and Canvas, draw on the latter and use it as a Marker icon:

new MarkerOptions().icon(BitmapDescriptorFactory.fromBitmap(bitmap))...

Google Maps API v2 draw part of circle on MapFragment

For drawing the circle segments, I would register a TileProvider, if the segments are mainly static. (Tiles are typically loaded only once and then cached.) For checking for click events, you can register an onMapClickListener and loop over your segments to check whether the clicked LatLng is inside one of your segments. (see below for more details.)

Here is a TileProvider example, which you could subclass and just implement the onDraw method.

One important note: The subclass must be thread safe! The onDraw method will be called by multiple threads simultaneously. So avoid any globals which are changed inside onDraw!

/* imports should be obvious */ 
public abstract class CanvasTileProvider implements TileProvider {
private static int TILE_SIZE = 256;

private BitMapThreadLocal tlBitmap;

@SuppressWarnings("unused")
private static final String TAG = CanvasTileProvider.class.getSimpleName();

public CanvasTileProvider() {
super();
tlBitmap = new BitMapThreadLocal();
}

@Override
// Warning: Must be threadsafe. To still avoid creation of lot of bitmaps,
// I use a subclass of ThreadLocal !!!
public Tile getTile(int x, int y, int zoom) {
TileProjection projection = new TileProjection(TILE_SIZE,
x, y, zoom);

byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
onDraw(canvas, projection);
data = bitmapToByteArray(image);
Tile tile = new Tile(TILE_SIZE, TILE_SIZE, data);
return tile;
}

/** Must be implemented by a concrete TileProvider */
abstract void onDraw(Canvas canvas, TileProjection projection);

/**
* Get an empty bitmap, which may however be reused from a previous call in
* the same thread.
*
* @return
*/
private Bitmap getNewBitmap() {
Bitmap bitmap = tlBitmap.get();
// Clear the previous bitmap
bitmap.eraseColor(Color.TRANSPARENT);
return bitmap;
}

private static byte[] bitmapToByteArray(Bitmap bm) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] data = bos.toByteArray();
return data;
}

class BitMapThreadLocal extends ThreadLocal<Bitmap> {
@Override
protected Bitmap initialValue() {
Bitmap image = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE,
Config.ARGB_8888);
return image;
}
}
}

Use the projection, which is passed into the onDraw method, to get at first the bounds of the tile. If no segment is inside the bounds, just return. Otherwise draw your seqment into the canvas. The method projection.latLngToPoint helps you to convert from LatLng to the pixels of the canvas.

/** Converts between LatLng coordinates and the pixels inside a tile. */
public class TileProjection {

private int x;
private int y;
private int zoom;
private int TILE_SIZE;

private DoublePoint pixelOrigin_;
private double pixelsPerLonDegree_;
private double pixelsPerLonRadian_;

TileProjection(int tileSize, int x, int y, int zoom) {
this.TILE_SIZE = tileSize;
this.x = x;
this.y = y;
this.zoom = zoom;
pixelOrigin_ = new DoublePoint(TILE_SIZE / 2, TILE_SIZE / 2);
pixelsPerLonDegree_ = TILE_SIZE / 360d;
pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}

/** Get the dimensions of the Tile in LatLng coordinates */
public LatLngBounds getTileBounds() {
DoublePoint tileSW = new DoublePoint(x * TILE_SIZE, (y + 1) * TILE_SIZE);
DoublePoint worldSW = pixelToWorldCoordinates(tileSW);
LatLng SW = worldCoordToLatLng(worldSW);
DoublePoint tileNE = new DoublePoint((x + 1) * TILE_SIZE, y * TILE_SIZE);
DoublePoint worldNE = pixelToWorldCoordinates(tileNE);
LatLng NE = worldCoordToLatLng(worldNE);
return new LatLngBounds(SW, NE);
}

/**
* Calculate the pixel coordinates inside a tile, relative to the left upper
* corner (origin) of the tile.
*/
public void latLngToPoint(LatLng latLng, DoublePoint result) {
latLngToWorldCoordinates(latLng, result);
worldToPixelCoordinates(result, result);
result.x -= x * TILE_SIZE;
result.y -= y * TILE_SIZE;
}

private DoublePoint pixelToWorldCoordinates(DoublePoint pixelCoord) {
int numTiles = 1 << zoom;
DoublePoint worldCoordinate = new DoublePoint(pixelCoord.x / numTiles,
pixelCoord.y / numTiles);
return worldCoordinate;
}

/**
* Transform the world coordinates into pixel-coordinates relative to the
* whole tile-area. (i.e. the coordinate system that spans all tiles.)
*
*
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void worldToPixelCoordinates(DoublePoint worldCoord, DoublePoint result) {
int numTiles = 1 << zoom;
result.x = worldCoord.x * numTiles;
result.y = worldCoord.y * numTiles;
}

private LatLng worldCoordToLatLng(DoublePoint worldCoordinate) {
DoublePoint origin = pixelOrigin_;
double lng = (worldCoordinate.x - origin.x) / pixelsPerLonDegree_;
double latRadians = (worldCoordinate.y - origin.y)
/ -pixelsPerLonRadian_;
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians))
- Math.PI / 2);
return new LatLng(lat, lng);
}

/**
* Get the coordinates in a system describing the whole globe in a
* coordinate range from 0 to TILE_SIZE (type double).
*
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void latLngToWorldCoordinates(LatLng latLng, DoublePoint result) {
DoublePoint origin = pixelOrigin_;

result.x = origin.x + latLng.longitude * pixelsPerLonDegree_;

// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
double siny = bound(Math.sin(Math.toRadians(latLng.latitude)), -0.9999,
0.9999);
result.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny))
* -pixelsPerLonRadian_;
};

/** Return value reduced to min and max if outside one of these bounds. */
private double bound(double value, double min, double max) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;
}

/** A Point in an x/y coordinate system with coordinates of type double */
public static class DoublePoint {
double x;
double y;

public DoublePoint(double x, double y) {
this.x = x;
this.y = y;
}
}

}

Finally you need something to check, whether a click on a LatLng-Coordinate is inside of your segment.
I would therefore approximate the segment by a list of LatLng-Coordinates, where in your case a simple triangle may be sufficient. For each list of LatLng coordinates, i.e. for each segment, you may then call something like the following:

private static boolean isPointInsidePolygon(List<LatLng> vertices, LatLng point) {
/**
* Test is based on a horizontal ray, starting from point to the right.
* If the ray is crossed by an even number of polygon-sides, the point
* is inside the polygon, otherwise it is outside.
*/
int i, j;
boolean inside = false;
int size = vertices.size();
for (i = 0, j = size - 1; i < size; j = i++) {
LatLng vi = vertices.get(i);
LatLng vj = vertices.get(j);
if ((vi.latitude > point.latitude) != (vj.latitude > point.latitude)) {
/* The polygonside crosses the horizontal level of the ray. */
if (point.longitude <= vi.longitude
&& point.longitude <= vj.longitude) {
/*
* Start and end of the side is right to the point. Side
* crosses the ray.
*/
inside = !inside;
} else if (point.longitude >= vi.longitude
&& point.longitude >= vj.longitude) {
/*
* Start and end of the side is left of the point. No
* crossing of the ray.
*/
} else {
double crossingLongitude = (vj.longitude - vi.longitude)
* (point.latitude - vi.latitude)
/ (vj.latitude - vi.latitude) + vi.longitude;
if (point.longitude < crossingLongitude) {
inside = !inside;
}
}
}
}
return inside;
}

As you may see, I had a very similar task to solve :-)

Android- Google map v2 Draw circle

To update Google MAP V2:

Open Android SDK Manager -> Go to Extras -> Check for the update of Google Play Service. if it doesn't shows the update available, then go to Packages->Reload. update the google play service to Revision 5.

After that remove the old Google Play Service Library project from Eclipse Workspace. import it again. add this library project in your current project. now u will have the Circle class available under com.google.android.gms.maps.model package. and you can use that to add circle in your map.

To Add Circle:

mMap.addCircle(new CircleOptions()
.center(new LatLng(location.getLatitude(), location.getLongitude()))
.radius(100)
.strokeColor(Color.RED)
.fillColor(Color.BLUE));

And for your information, this is Release note for changes of February 2013.

EDIT : This is Release notes for June 2016. You can now make the circles clickable, which may be helpful if you want to do some action with circle.

android google map v2 drawing 1k circles performance issue

I finally found a fix. I checked about performances of polyline which is about 12x faster. So well, drawing circles is not possible if you have too many points.

So the idea is to draw a polyline and then to add a pattern with dots.

  PolylineOptions polylineOptions = new PolylineOptions();
polylineOptions.width(3);
polylineOptions.color(Color.RED);
for(MyPoint point : pointsToDraw){
polylineOptions.add(new LatLng(point.getLatitude(), point.getLongitude()));
}
Polyline polyline = googleMap.addPolyline(entry.getValue());
// the key is here with the pattern
List<PatternItem> pattern = Arrays.asList(new Dot());
polyline.setPattern(pattern);


Related Topics



Leave a reply



Submit