Android Maps API v2 draw circle
Asked Answered
S

6

16

Having the following code to draw circle (taken from Google Play Services "maps" sample):

    PolylineOptions options = new PolylineOptions();
    int radius = 5; //What is that?
    int numPoints = 100;
    double phase = 2 * Math.PI / numPoints;
    for (int i = 0; i <= numPoints; i++) {
        options.add(new LatLng(SYDNEY.latitude + radius * Math.sin(i * phase),
                SYDNEY.longitude + radius * Math.cos(i * phase)));
    }
    int color = Color.RED;
    mMap.addPolyline(options
            .color(color)
            .width(2));

This is what gets drawn on different part of the world:

Sydney Scandic

As you see circles are not really circles and even second one is ellipse basically.

I guess that "anti-aliasing" of circle depending on number of points in int numPoints variable.

  1. What is int radius = 5 variable in example code? I mean what measure it is?
  2. And main question what would be correct way of drawing nice circle with given radius in meters? Something smiliar to what we had in api v1 with canvas.drawCircle()

UPDATE --------------------

OK after improving math I was able to draw "right" circle:

private void addCircle(LatLng latLng, double radius)
    {
        double R = 6371d; // earth's mean radius in km
        double d = radius/R; //radius given in km
        double lat1 = Math.toRadians(latLng.latitude);
        double lon1 = Math.toRadians(latLng.longitude);         
        PolylineOptions options = new PolylineOptions();
        for (int x = 0; x <= 360; x++)
        {                      
            double brng = Math.toRadians(x);
            double latitudeRad = Math.asin(Math.sin(lat1)*Math.cos(d) + Math.cos(lat1)*Math.sin(d)*Math.cos(brng));
            double longitudeRad = (lon1 + Math.atan2(Math.sin(brng)*Math.sin(d)*Math.cos(lat1), Math.cos(d)-Math.sin(lat1)*Math.sin(latitudeRad)));             
            options.add(new LatLng(Math.toDegrees(latitudeRad), Math.toDegrees(longitudeRad)));
        }           
        mMap.addPolyline(options.color(Color.BLACK).width(2));          
    }

However anti-aliasing of circle I guess is somewhat beyond control, and on some zoom levels circle might get ugly:

enter image description here

Stepmother answered 21/12, 2012 at 13:45 Comment(4)
the reason you're getting ellipse it's because the math on the options.add( is very basic and pretends the earth is a flat surface, just like before Copernicus. If you draw it on smaller areas you'll get a rounder circle. If you want a precise circle you'll have extend that math to proper take into account the shape of Earth.Carnivore
@lija, Any specific reason of using a Polyline instead of Polygon to draw the circle? I tried using Polygon and i got the same output. So, you have any idea which one of them is best to use? Thank You.Longdrawnout
@Longdrawnout not really, you could use polygons if you need to have a fill option for instance. Otherwise in this case not a big difference.Stepmother
Checkout this library: github.com/i-schuetz/map_areasPathic
P
21

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

Poppied answered 20/1, 2013 at 19:15 Comment(3)
Despite your solution got minor bugs, I got an idea. Anyways with v2 API it seems like trivial tasks much harder to implement than before. For instance if I would need to resize this circle when user drags progress bar, I would have to remove marker and create new one which is slow, also I would need to manage lifecycle of bitmap. All this was very simple with Overlay in V1.Stepmother
just to add Marker marker = map.addMarker(options.position(latLng)); it working fineChristean
With circle, I want dim background on the map. Is this possible?Staley
R
23

Google made is simple in maps v2. The snippet below demonstrates both drawing markers and circles along with updating their positions together.

private Circle mCircle;
private Marker mMarker;
private GoogleMap mGoogleMap;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mGoogleMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.mapFragment)).getMap();
    mGoogleMap.setMyLocationEnabled(true);
    mGoogleMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
        @Override
        public void onMyLocationChange(Location location) {
            LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
            if(mCircle == null || mMarker == null){
                drawMarkerWithCircle(latLng);
            }else{
                updateMarkerWithCircle(latLng);
            }
        }
    });
}

private void updateMarkerWithCircle(LatLng position) {
    mCircle.setCenter(position);
    mMarker.setPosition(position);
}

private void drawMarkerWithCircle(LatLng position){
    double radiusInMeters = 100.0;
    int strokeColor = 0xffff0000; //red outline
    int shadeColor = 0x44ff0000; //opaque red fill

    CircleOptions circleOptions = new CircleOptions().center(position).radius(radiusInMeters).fillColor(shadeColor).strokeColor(strokeColor).strokeWidth(8);
    mCircle = mGoogleMap.addCircle(circleOptions);

    MarkerOptions markerOptions = new MarkerOptions().position(position);
    mMarker = mGoogleMap.addMarker(markerOptions);
}
Rueful answered 31/8, 2013 at 8:36 Comment(0)
P
21

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

Poppied answered 20/1, 2013 at 19:15 Comment(3)
Despite your solution got minor bugs, I got an idea. Anyways with v2 API it seems like trivial tasks much harder to implement than before. For instance if I would need to resize this circle when user drags progress bar, I would have to remove marker and create new one which is slow, also I would need to manage lifecycle of bitmap. All this was very simple with Overlay in V1.Stepmother
just to add Marker marker = map.addMarker(options.position(latLng)); it working fineChristean
With circle, I want dim background on the map. Is this possible?Staley
A
7

Android 2.0 API you can use CircleOption to draw a circle, works really well, no messy conversions of pixels to meters.

public CircleOptions getCircleOptions() {
    CircleOptions co = new CircleOptions();
    co.center(latlng);
    co.radius(getCircleSize());
    co.fillColor(getCircleColor());
    co.strokeColor(getStrokeColor());
    co.strokeWidth(2.0f);
    return co;
}

    Circle circle = mapView.getMap().addCircle(munzeeMarker.getCircleOptions());
Annitaanniversary answered 27/5, 2013 at 21:10 Comment(0)
H
4

Here's my code, just for added variation. I haven't found a way to solve the problems with antialiasing or shape simplification:

// Make a circle of latitude and longitude points.
// Radius is in metres.
// Probably won't work if the circle is large or near the poles.
private ArrayList<LatLng> makeCircle(LatLng centre, double radius)
{
    ArrayList<LatLng> points = new ArrayList<LatLng>();

    double EARTH_RADIUS = 6378100.0;
    // Convert to radians.
    double lat = centre.latitude * Math.PI / 180.0;
    double lon = centre.longitude * Math.PI / 180.0;

    for (double t = 0; t <= Math.PI * 2; t += 0.1)
    {
        // y
        double latPoint = lat + (radius / EARTH_RADIUS) * Math.sin(t);
        // x
        double lonPoint = lon + (radius / EARTH_RADIUS) * Math.cos(t) / Math.cos(lat);
        points.add(new LatLng(latPoint * 180.0 / Math.PI, lonPoint * 180.0 / Math.PI));
    }

    return points;
}

Explanation of maths

Google Earth uses the Mercator projection which means that physical circles do not appear as circles on the map - instead they are distorted as shown in the question. The closer to the poles you go the more distorted they are. To draw a circle on the map but with coordinates given in latitude/longitude we need to invert the distortion.

First we find the centre of the circle in radians - that is pretty simple and is stored in lat and lon.

Then we go around the circle finding points on its edge. If we were making a physical circle, the latitude and longitude of each point is just:

        double latPoint = lat + (radius / EARTH_RADIUS) * Math.sin(t);
        double lonPoint = lon + (radius / EARTH_RADIUS) * Math.cos(t);

That's pretty simple trigonometry. The (radius / EARTH_RADIUS) is the angular radius of the circle in radians (e.g. a circle with a radius of 100 m would have an angular radius of 0.000015 rad).

If we used that simple code we would find that the circle shown on the map was squished by cos(lat) horizontally, so we simply divide by cos(lat) to unsquish it.

We could equally have done this:

        double latPoint = lat + (radius / EARTH_RADIUS) * Math.sin(t) * Math.cos(lat);
        double lonPoint = lon + (radius / EARTH_RADIUS) * Math.cos(t);

Resulting in a large circle - it depends whether the radius function parameter refers to latitude or longitude.

This explanation could use some diagrams but I can't be bothered, sorry.

Hesitancy answered 10/1, 2013 at 13:34 Comment(2)
Can you explain the math here?Usury
I have added a bit of explanation.Hesitancy
D
2

Google today, Feb 26, just released Circle as an overlay type.

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

Daguerre answered 27/2, 2013 at 0:35 Comment(0)
B
1

Yesterday Android Developer team intoduced Google Play service v3. Abuse it! ;) https://developers.google.com/maps/documentation/android/shapes#circles

Bicorn answered 27/2, 2013 at 9:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.