Drawing a circle Google Static Maps
Asked Answered
F

7

19

I have a Google Maps Circle drawn on v3 api. When the user has plotted there circle (or polygon if they choose), they can save the data to the server. If the user has picked a radial search, the Centre coordinates and the radius in feet is stored to the database. This means when the user reloads his search, it can pull through the circle again (like below).

Example of load

I'm having 1 problem, however, which is when the user selects what search they would like to use. It loads the polygon fine, if they drew a polygon, and if it's a circle it pulls through the marker on the center. However what I need is a function in static maps to draw a circle.

Fishery answered 6/9, 2011 at 8:28 Comment(1)
possible duplicate of Is it possible to draw a circle on a Google static map?Sawmill
P
29

A bit late in the game, but nothing I found solved my issue (serverside php only, no javascript). I ended up getting there in the end and have detailed my method here: http://jomacinc.com/map-radius/ and the short version is below.

This PHP function will return an encoded polyline string of lat/lng points in a circle around the specified point, and at the specified radius. The function requires Gabriel Svennerberg’s PHP polyline encoding class available here (http://www.svennerberg.com/examples/polylines/PolylineEncoder.php.txt).

function GMapCircle($Lat,$Lng,$Rad,$Detail=8){
 $R    = 6371;

 $pi   = pi();

 $Lat  = ($Lat * $pi) / 180;
 $Lng  = ($Lng * $pi) / 180;
 $d    = $Rad / $R;

 $points = array();
 $i = 0;

 for($i = 0; $i <= 360; $i+=$Detail):
   $brng = $i * $pi / 180;

   $pLat = asin(sin($Lat)*cos($d) + cos($Lat)*sin($d)*cos($brng));
   $pLng = (($Lng + atan2(sin($brng)*sin($d)*cos($Lat), cos($d)-sin($Lat)*sin($pLat))) * 180) / $pi;
   $pLat = ($pLat * 180) /$pi;

   $points[] = array($pLat,$pLng);
 endfor;

 require_once('PolylineEncoder.php');
 $PolyEnc   = new PolylineEncoder($points);
 $EncString = $PolyEnc->dpEncode();

 return $EncString['Points'];
}

You can now the use the above function to create a static map.

/* set some options */
$MapLat    = '-42.88188'; // latitude for map and circle center
$MapLng    = '147.32427'; // longitude as above
$MapRadius = 100;         // the radius of our circle (in Kilometres)
$MapFill   = 'E85F0E';    // fill colour of our circle
$MapBorder = '91A93A';    // border colour of our circle
$MapWidth  = 640;         // map image width (max 640px)
$MapHeight = 480;         // map image height (max 640px)

/* create our encoded polyline string */
$EncString = GMapCircle($MapLat,$MapLng, $MapRadius);

/* put together the static map URL */
$MapAPI = 'http://maps.google.com.au/maps/api/staticmap?';
$MapURL = $MapAPI.'center='.$MapLat.','.$MapLng.'&size='.$MapWidth.'x'.$MapHeight.'&maptype=roadmap&path=fillcolor:0x'.$MapFill.'33%7Ccolor:0x'.$MapBorder.'00%7Cenc:'.$EncString.'&sensor=false';

/* output an image tag with our map as the source */
echo '<img src="'.$MapURL.'" />'
Pyelonephritis answered 23/5, 2012 at 8:11 Comment(4)
Welcome to Stack Overflow! Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.Bracteate
@jomac Must admit, a great alternative, please follow Shawn Chin's advice, and post the PHP code and explanation on your answer for future reference!Fishery
Thanks guys, I have expanded my answer.Pyelonephritis
@Pyelonephritis Your code is soo soo useful and i implement it but i also. is there any other example for Polygon as same as your above ?Hydrometer
S
24
function GMapCircle(lat,lng,rad,detail=8){

var uri = 'https://maps.googleapis.com/maps/api/staticmap?';
var staticMapSrc = 'center=' + lat + ',' + lng;
staticMapSrc += '&size=100x100';
staticMapSrc += '&path=color:0xff0000ff:weight:1';

var r    = 6371;

var pi   = Math.PI;

var _lat  = (lat * pi) / 180;
var _lng  = (lng * pi) / 180;
var d    = (rad/1000) / r;

var i = 0;

for(i = 0; i <= 360; i+=detail) {
    var brng = i * pi / 180;

    var pLat = Math.asin(Math.sin(_lat) * Math.cos(d) + Math.cos(_lat) * Math.sin(d) * Math.cos(brng));
    var pLng = ((_lng + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(_lat), Math.cos(d) - Math.sin(_lat) * Math.sin(pLat))) * 180) / pi;
    pLat = (pLat * 180) / pi;

   staticMapSrc += "|" + pLat + "," + pLng;
}

return uri + encodeURI(staticMapSrc);}

Javascript version

Scarabaeoid answered 26/2, 2016 at 19:58 Comment(8)
Incredible useful!Maltese
In this version, rad is in meters.Cabernet
possible to fill the circle?Sanguinary
To fill: just add |fillcolor:${circleHexColor} (note the | separator instead of :)Sanguinary
@MadeInMoon add it where? How? I can't get it to fill.Availability
@Availability you have to concatenate to the staticMapSrc string, like: staticMapSrc += string Sanguinary
@MadeInMoon Do I do that within the for loop or outside of it?Availability
@Availability from my memory I would say outsideSanguinary
O
6

Based on the answer from Jomac, Here is a Java/Android version of the same code.

It uses the PolyUtil class from Google Maps Android API Utility Library to encode the path.

import android.location.Location;

import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.PolyUtil;

import java.util.ArrayList;

public class GoogleStaticMapsAPIServices
{
    private static final double EARTH_RADIUS_KM = 6371;

    private static String GOOGLE_STATIC_MAPS_API_KEY = "XXXXXXXXXXXXX";

    public static String getStaticMapURL(Location location, int radiusMeters)
    {
        String pathString = "";
        if (radiusMeters > 0)
        {
            // Add radius path
            ArrayList<LatLng> circlePoints = getCircleAsPolyline(location, radiusMeters);

            if (circlePoints.size() > 0)
            {
                String encodedPathLocations = PolyUtil.encode(circlePoints);
                pathString = "&path=color:0x0000ffff%7Cweight:1%7Cfillcolor:0x0000ff80%7Cenc:" + encodedPathLocations;
            }
        }

        String staticMapURL = "https://maps.googleapis.com/maps/api/staticmap?size=640x320&markers=color:red%7C" +
                location.getLatitude() + "," + location.getLongitude() +
                pathString +
                "&key=" + GOOGLE_STATIC_MAPS_API_KEY;

        return staticMapURL;
    }

    private static ArrayList<LatLng> getCircleAsPolyline(Location center, int radiusMeters)
    {
        ArrayList<LatLng> path = new ArrayList<>();

        double latitudeRadians = center.getLatitude() * Math.PI / 180.0;
        double longitudeRadians = center.getLongitude() * Math.PI / 180.0;
        double radiusRadians = radiusMeters / 1000.0 / EARTH_RADIUS_KM;

        double calcLatPrefix = Math.sin(latitudeRadians) * Math.cos(radiusRadians);
        double calcLatSuffix = Math.cos(latitudeRadians) * Math.sin(radiusRadians);

        for (int angle = 0; angle < 361; angle += 10)
        {
            double angleRadians = angle * Math.PI / 180.0;

            double latitude = Math.asin(calcLatPrefix + calcLatSuffix * Math.cos(angleRadians));
            double longitude = ((longitudeRadians + Math.atan2(Math.sin(angleRadians) * Math.sin(radiusRadians) * Math.cos(latitudeRadians), Math.cos(radiusRadians) - Math.sin(latitudeRadians) * Math.sin(latitude))) * 180) / Math.PI;
            latitude = latitude * 180.0 / Math.PI;

            path.add(new LatLng(latitude, longitude));
        }

        return path;
    }
}
Orthocephalic answered 29/6, 2016 at 13:5 Comment(1)
This answer deserves more up-votes because it uses clear names for all the variables and it helps a lot in understanding what those intial r, d and brng variables actually represent.Volkan
E
2

I think it is not possible to draw a circle on a static Google map. You would need to approximate the circle by a polyline (best in encoded format). This has already been mentioned in Stackoverflow and it is demonstrated e.g. by Free Map Tools.

Extrude answered 6/9, 2011 at 9:14 Comment(3)
Thanks for the links, I was about to go down the route of drawing a circle with a polygon, but thinking about it, maybe it's overkill. The second link is unfortunately for API2. Thanks againFishery
"Google Static Maps API v2" is not part of "Google Maps API v2/v3", it's a separate API. So it is used also with Google Maps API v3. In Free Map Tools you can generate a static map with a approximated circle. You would generate the same format from your Google Maps API v3.Extrude
Ah sorry my bad, I'll have a look at what I can do and post the code once finished.Fishery
R
0

Sharing my C# version

private string GMapCircle(double lat, double lng, double rad, int detail = 8)
{
    const string uri = "https://maps.googleapis.com/maps/api/staticmap?";
    var staticMapSrc = "center=" + lat + "," + lng;
    staticMapSrc += "&zoom=16";
    staticMapSrc += "&maptype=satellite";
    staticMapSrc += "&key=[YOURKEYHERE]";
    staticMapSrc += "&size=640x426";
    staticMapSrc += "&path=color:0xff0000ff:weight:1";

    const int r = 6371;
    const double pi = Math.PI;

    var latAux = (lat * pi) / 180;
    var longAux = (lng * pi) / 180;
    var d = (rad / 1000) / r;

    var i = 0;

    if (rad > 0)
    {
        for (i = 0; i <= 360; i += detail)
        {
            var brng = i * pi / 180;

            var pLat = Math.Asin(Math.Sin(latAux) * Math.Cos(d) + Math.Cos(latAux) * Math.Sin(d) * Math.Cos(brng));
            var pLng = ((longAux + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(latAux), Math.Cos(d) - Math.Sin(latAux) * Math.Sin(pLat))) * 180) / pi;
            pLat = (pLat * 180) / pi;

            staticMapSrc += "|" + pLat + "," + pLng;
        }
    }
    else
    {
        //TODO - Add marker
    }

    return uri + staticMapSrc;
}
Reisfield answered 8/3, 2018 at 19:3 Comment(0)
T
0

This solution uses the significantly more versatile Canvas API to draw over the map image . All code is in Typescript, so simply remove type declarations if you're using Javascript.

ADVANGES OF USING CANVAS:

  • Its easier to draw shapes on.
  • Those shapes can also be revised without re-requesting the map image from Google.
  • The drawing 'layer' can be serialized independently of the underlying map image.

USAGE:

// DEFINE BASIC VARIABLES
const latitude: -34.3566871,
const longitude: 18.4967666
const mapZoom = 12;
const imageWidth: 100;
const imageHeight: 100;

// INVOKE UTILITY FUNCTION
savePlaceImage({

  // SET BASIC PROPS
  latitude,
  longitude,
  mapZoom,
  imageWidth,
  imageHeight,
  fileName: 'Cape Point',

  // DRAW IMAGE USING CANVAS API
  draw: ctx => {

    // draw location as dot
    ctx.fillStyle = '#FF3366';
    ctx.beginPath();
    ctx.arc(imageWidth / 2, imageHeight / 2, 10, 0, 2 * Math.PI);
    ctx.fill();

    // draw circle around location with 1 kilometer radius
    ctx.strokeStyle = '#0000FF';
    ctx.beginPath();
    ctx.arc(imageWidth / 2, imageHeight / 2, pixelsPerMeter(latitude) * 1000, 0, 2 * Math.PI);
    ctx.stroke();
  }
})

UTILITIES:

function savePlaceImage(
  config: {
    latitude: number,
    longitude: number,
    mapZoom: number,
    imageWidth: number,
    imageHeight: number,
    fileName: string,
    draw: (ctx: CanvasRenderingContext2D) => void,
  },
) {

  // DOWNLOAD MAP IMAGE FROM GOOGLE'S STATIC MAPS API AS A BLOB
  return from(axios.get<Blob>(`https://maps.googleapis.com/maps/api/staticmap`, {
    params: {
      key: GOOGLE_MAPS_API_KEY,
      size: `${config.imageWidth}x${config.imageHeight}`,
      zoom: `${config.mapZoom}`,
      center: `${config.latitude},${config.longitude}`,
      style: 'feature:all|element:labels|visibility:off',
    },
    responseType: 'blob',

  // CONVERT BLOB TO BASE64 ENCODED STRING
  }).then(response => {
    const reader = new FileReader();
    reader.readAsDataURL(response.data);
    return new Promise<string>(resolve => reader.onloadend = () => resolve(reader.result as string));

  // CREATE HTML IMG ELEMENT, SET IT'S SRC TO MAP IMAGE, AND WAIT FOR IT TO LOAD
  }).then(response => {
    const image = document.createElement('img');
    image.src = response;
    return new Promise<HTMLImageElement>(resolve => image.onload = () => resolve(image));

  // CREATE HTML CANVAS ELEMENT, THEN DRAW ON TOP OF CANVAS USING CANVAS API, THEN CONVERT TO BLOB
  }).then(image => {
    const canvas = document.createElement('canvas');
    canvas.width = config.imageWidth;
    canvas.height = config.imageHeight;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0);
    config.draw(ctx);
    return new Promise<Blob>(resolve => canvas.toBlob(blob => resolve(blob)));

  // ATTACH BLOB TO HTML FORM WHICH CONVERTS IT TO A FILE TO BE POSTED, THEN SEND FILE TO SERVER
  }).then(blob => {
    const form = new FormData();
    form.append('blob', blob, `${config.fileName}.png`);
    const file = form.get('blob') as File;
    return axios.post<{ file }>('https://www.my-api.com/save-image', form);
  }));
}

function pixelsPerMeter(latitude: number) {
  const radiusOfEarthInKilometers = 6371;
  return Math.cos(latitude * Math.PI / 180) * 2 * Math.PI * radiusOfEarthInKilometers / (256 * Math.pow(2, 12));
}
Tomekatomes answered 23/10, 2020 at 12:8 Comment(0)
T
0

python version, polyline library used for encoding the polygon

import math, polyline

def g_map_circle(lat,lng,radius,detail=8):
  points = []
  r = 6371
  pi = math.pi

  _lat = (lat * pi) /180
  _lng = (lng * pi) /180
  d = radius / r

  i = 0
  while i <= 360:
    i = i + detail
    brng = i * pi /180

    p_lat = math.asin(math.sin(_lat) * math.cos(d) + math.cos(_lat) * math.sin(d) * math.cos(brng));
    p_lng = (_lng + math.atan2(math.sin(brng) * math.sin(d) * math.cos(_lat), math.cos(d) - math.sin(_lat) * math.sin(p_lat))) * 180 / pi
    p_lat = (p_lat * 180) /pi
    points.append((p_lat,p_lng))

  return polyline.encode(points)
Torto answered 14/11, 2020 at 1:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.