Google Maps APIで円を描く。

google.maps.Circle とか google.maps.Oval...なんてクラスがなかった時代に、google.maps.Polylineで多角形を描くという道をたどった備忘録のページ。

日本へそ公園を中心とする、半径R kmの円で考えてみます。
各頂点の座標は極座標形式の円の方程式を用いて・・・
x = R * cos(θ)
y = R * sin(θ)
で、θを0~360まで1周させればいいかと思ったけど、
経度1度と緯度1度での長さ(r)が異なるから、このままではダメ。

楕円の方程式を持ってきて
lat = (R / a) * sin(θ) + lat_center
lng = (R / b) * cos(θ) + lng_center
aやbには経度・緯度1度あたりの長さをあてはめればいいわけです。
理科年表に掲載されている「地球楕円体に関する計算式」を用いて、
緯度をθ、地球の赤道半径をER、地球の離心率をeとすれば、
経度1度あたりの長さ(m) = (π * ER * (1 - e2)) / (180 * (1 - e2 sin2(θ))3/2)
緯度1度あたりの長さ(m) = (π * ER * cos(θ)) / (180 * (1 - e2 sin2(θ))1/2)
WGS84測地系では、赤道半径は 6378137m。
離心率(e)は扁平率(f)との関係から、
e2 = 2f - f2
WGS84では 1/f = 298.257223 なので、F = 1/f とすれば
e2 = 2f - f2 = (2F - 1) / F2
これらを用いて、
// 中心地(日本へそ公園)
var point = new google.maps.LatLng(35, 135);

// 描く円の半径(km)
var radius = 1;

// 赤道半径(m) (WGS-84)
var EquatorialRadius = 6378137;

// 扁平率の逆数 : 1/f (WGS-84)
var F = 298.257223;            

// 離心率の2乗
var E = ((2 * F) -1) / Math.pow(F, 2);

// 赤道半径 × π
var PI_ER = Math.PI * EquatorialRadius;

// 1 - e^2 sin^2 (θ)
var TMP = 1 - E * Math.pow(Math.sin(point.lat() * Math.PI / 180), 2);

// 経度1度あたりの長さ(m)
var arc_lat = (PI_ER * (1 - E)) / (180 * Math.pow(TMP, 3/2));

// 緯度1度あたりの長さ(m)
var arc_lng = (PI_ER * Math.cos(point.lat() * Math.PI / 180)) / (180 * Math.pow(TMP, 1/2));

// 半径をm単位に
var R = radius * 1000;

// 0~360°で1°ずつ頂点を求めることで360角形を生成
var points = new Array(360);
for (i = 0; i <= 360; i++) {
  var rad = i / 180 * Math.PI;
  var lat = (R / arc_lat) * Math.sin(rad) + point.lat();
  var lng = (R / arc_lng) * Math.cos(rad) + point.lng();
  points[i] = new google.maps.LatLng(lat, lng);
}

var circle = new google.maps.Polyline({
  path: points,
  strokeColor: '#ff0000',
  strokeOpacity: 1.0,
  strokeWeight: 1,
  map: map
});
ところで、何角形になれば円とみなせますか?

頂点の数
3600 3240 2880 2520 2160 1800
1440 1280 720 360 180 90
72 60 45 40 36 30
24 20 18 15 12 10
9 8 6 5 4 3
あまり多くしすぎるとブラウザが固まったかと思っちゃうので、
zoomlevelとクライアントPCの性能で調整を。
以上を踏まえて、functionにしてみます。
頂点の数はvertexで定義しているので、適宜変更のこと。
/**
 * pseudoGCircle (擬似-円描画)
 * @param point   中心地 google.maps.LatLng
 * @param radius  半径 km
 * @param color   google.maps.Polylineに渡される
 * @param weight  google.maps.Polylineに渡される
 * @param opacity google.maps.Polylineに渡される
 * @return google.maps.Polyline
 */
function pseudoGCircle(point, radius, color, weight, opacity) {
  var vertex = 360;               // 頂点の数
  var EquatorialRadius = 6378137; // 赤道半径 (WGS-84)
  var F = 298.257223563;          // 扁平率の逆数 (WGS-84)

  // 離心率の2乗
  var E = ((2 * F) -1) / Math.pow(F, 2);

  // π × 赤道半径
  var PI_ER = Math.PI * EquatorialRadius;

  // 1 - e^2 sin^2 (θ)
  var TMP = 1 - E * Math.pow(Math.sin(point.lat() * Math.PI / 180), 2);

  // 経度1度あたりの長さ(m)
  var arc_lat = (PI_ER * (1 - E)) / (180 * Math.pow(TMP, 3/2)); 

  // 緯度1度あたりの長さ(m)
  var arc_lng = (PI_ER * Math.cos(point.lat() * Math.PI / 180)) / (180 * Math.pow(TMP, 1/2));

  // 半径をm単位に
  var R = radius * 1000;

  var points = new Array(vertex);
  for (i = 0; i <= vertex; i++) {
    var rad = (i / (vertex / 2)) * Math.PI;
    var lat = (R / arc_lat) * Math.sin(rad) + point.lat();
    var lng = (R / arc_lng) * Math.cos(rad) + point.lng();
    points[i] = new google.maps.LatLng(lat, lng);
  }
  return new google.maps.Polyline({
    path: points,
    strokeColor: color,
    strokeOpacity: opacity,
    strokeWeight: weight
  });

}
定数を計算済みにしてみる?
/**
 * pseudoGCircle (擬似-円描画)
 * @param point   中心地 google.maps.LatLng
 * @param radius  半径 km
 * @param color   google.maps.Polylineに渡される
 * @param weight  google.maps.Polylineに渡される
 * @param opacity google.maps.Polylineに渡される
 * @return google.maps.Polyline
 */
function pseudoGCircle(point, radius, color, weight, opacity) {
  var vertex = 360;
  var TMP = 1 - 0.00669437999014132 * Math.pow(Math.sin(point.lat() * Math.PI / 180), 2);

  var arc_lat = 110574.2758215944444 / Math.pow(TMP, 3/2);
  var arc_lng = (111319.490793273333 * Math.cos(point.lat() * Math.PI / 180)) / Math.pow(TMP, 1/2);

  var R = radius * 1000;
  var points = new Array(vertex);
  for (i = 0; i <= vertex; i++) {
    var rad = (i / (vertex / 2)) * Math.PI;
    var lat = (R / arc_lat) * Math.sin(rad) + point.lat();
    var lng = (R / arc_lng) * Math.cos(rad) + point.lng();
    points[i] = new google.maps.LatLng(lat, lng);
  }
  return new google.maps.Polyline({
    path: points,
    strokeColor: color,
    strokeOpacity: opacity,
    strokeWeight: weight
  }
}

追記 2008/07/21 Google AJAX API ローダー対応。(G名前空間からgoogle.maps.* 名前空間へ)
追記 2018/10/08 V3に書き換え