Google Maps API V2 Draw Part of Circle on Mapfragment

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

How to draw a shape on the map fragment by touching it using google map V2

GoogleMap doesn't have a touch listener, so you'll have to override onTouchEvent for the parent view of your MapFragment. Once you have the screen coordinates of your touch event, you can get the Lat/Long by using Projection (Doc here). Simply do

LatLng coords = mapFragment.getMap().getProjection().fromScreenLocation(point);

Where point is a Point describing the location of your touch event.

Once you have the LatLng describing your touch event, you can draw shapes using either Circle or Polygon. Google's tutorial on drawing shapes will do a better job of explaining it than I could: Shapes - Google Maps v2

Hope this helps!

android - draw resizable circle on google map

Based on your question, you want to overlay a "pinch listening" view that draws an oval shape based on the pinch. I made some poorly-tested code for this purpose, adapt it as you need:

MainLayout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<!-- Replace the ImageView with your MapView or whatever you are
overlaying with the oval shape -->
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#F00" />

<com.example.testapp.CircleTouchView
android:id="@+id/circle_drawer_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</FrameLayout>

CircleTouchView:

public class CircleTouchView extends View {
private static final int MODE_PINCH = 0;
private static final int MODE_DONT_CARE = 1;

ShapeDrawable mCircleDrawable;
int mTouchMode = MODE_DONT_CARE;

public CircleTouchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mCircleDrawable = new ShapeDrawable(new OvalShape());
mCircleDrawable.getPaint().setColor(0x66FFFFFF);
}

public CircleTouchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CircleTouchView(Context context) {
this(context, null, 0);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mCircleDrawable.setBounds(0, 0, 0, 0);
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN:
prepareCircleDrawing(event);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchMode == MODE_PINCH) {
prepareCircleDrawing(event);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (event.getActionIndex() <= 1) {
mTouchMode = MODE_DONT_CARE;
}
break;
default:
super.onTouchEvent(event);
}

return true;
}

private void prepareCircleDrawing(MotionEvent event) {
int top, right, bottom, left;
int index = event.getActionIndex();

if (index > 1) {
return;
}
mTouchMode = MODE_PINCH;
if (event.getX(0) < event.getX(1)) {
left = (int) event.getX(0);
right = (int) event.getX(1);
} else {
left = (int) event.getX(1);
right = (int) event.getX(0);
}

if (event.getY(0) < event.getY(1)) {
top = (int) event.getY(0);
bottom = (int) event.getY(1);
} else {
top = (int) event.getY(1);
bottom = (int) event.getY(0);
}

mCircleDrawable.setBounds(left, top, right, bottom);

invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
mCircleDrawable.draw(canvas);
}
}

If you want a perfect circle instead of an oval shape, change the prepareCircleDrawing() method so that it takes the smallest values for X and Y between event 0 and 1.

Edit: you can add the snippet below before calling mCircleDrawable.setBounds(left, top, right, bottom); to draw a perfect circle. There are other ways for drawing circles, it depends on how you want it to behave.

int height = bottom - top;
int width = right - left;

if (height > width) {
int delta = height - width;
top += delta / 2;
bottom -= delta / 2;
} else {
int delta = width - height;
left += delta / 2;
right -= delta / 2;
}

Hope I made myself clear, regards.

GroundOverlay made with a Canvas in Google Maps Android API v2

Starting from a LatLng point you can calculate another LatLng point in a given distance (radius) and a given angle as follows:

private static final double EARTHRADIUS = 6366198;

/**
* Move a LatLng-Point into a given distance and a given angle (0-360,
* 0=North).
*/
public static LatLng moveByDistance(LatLng startGp, double distance,
double angle) {
/*
* Calculate the part going to north and the part going to east.
*/
double arc = Math.toRadians(angle);
double toNorth = distance * Math.cos(arc);
double toEast = distance * Math.sin(arc);
double lonDiff = meterToLongitude(toEast, startGp.latitude);
double latDiff = meterToLatitude(toNorth);
return new LatLng(startGp.latitude + latDiff, startGp.longitude
+ lonDiff);
}

private static double meterToLongitude(double meterToEast, double latitude) {
double latArc = Math.toRadians(latitude);
double radius = Math.cos(latArc) * EARTHRADIUS;
double rad = meterToEast / radius;
double degrees = Math.toDegrees(rad);
return degrees;
}

private static double meterToLatitude(double meterToNorth) {
double rad = meterToNorth / EARTHRADIUS;
double degrees = Math.toDegrees(rad);
return degrees;
}


Related Topics



Leave a reply



Submit