17
votes

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:

SydneyScandic

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

6
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.Budius
@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.Archie.bpgc
@Archie.bpgc not really, you could use polygons if you need to have a fill option for instance. Otherwise in this case not a big difference.Ilja S.
Checkout this library: github.com/i-schuetz/map_areasUser

6 Answers

21
votes

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

23
votes

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

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());
4
votes

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.

1
votes

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