FlexからGoogle Mapsの地図上に円を表示

本文では、FlexからGoogle Mapsの地図上に円を表示する方法を記す。

まずは単純に円を表示

Google Mapsの地図上に直線や多角形を表示するには、Google MapsのライブラリのPolylineクラスとPolygonクラスを使用する。

しかし、地図上に円を表示するためのクラスやAPIはない。

そこで円を表示する代わりに、Polygonクラスで24角形、32角形といった多角形を表示する。

円のように見える多角形を表すPolygonクラスを生成する関数のコードを下に示す。

//ある点を中心にした多角形を生成する関数
//latLng:円の中心点
//radius:円の半径(単位は10進法の角度)
//options:多角形の表示方法
//pointNum:多角形の頂点の数
private function createCirclePolygon(latLng : LatLng, radius : Number, options : PolygonOptions, pointNum : int = 24) : Polygon {
	//多角形の各頂点の座標の配列を生成
	var points : Array = [];
	for(var i : int = 0; i <= pointNum; i++) {
		var radian : Number = 2.0 * Math.PI * (Number(i) / Number(pointNum)); 
		var x : Number = Math.cos(radian) * radius;
		var y : Number = Math.sin(radian) * radius;
		var lat : Number = latLng.lat() + y;
		var lng : Number = latLng.lng() + x;
		points.push(new LatLng(lat, lng, true));
	}
	
	var polygon : Polygon = new Polygon(points, options);
	return polygon;
}

この関数で緯度=0.0, 経度=0.0, 半径=10.0,点の数=24とすると、ほぼ円とみなせる多角形が表示できる。


しかし、これには問題がある。下に緯度=75.0, 経度=0.0, 半径=10.0,点の数=24としたときの表示を示す。


これを見ると、縦方向に大きく伸びた円といえない代物が表示されている。

この原因は、デフォルトではGoogle Mapsの地図の投影法として高緯度の地方ほど拡大して表示するメルカトル図法が使用されるためだ。

この原因に対処するためには、地図の投影方法に正距円筒図法を使用する。これは、緯度、経度をそれぞれ地図の縦と横にそのまま読みかえる投影方法だ。

正距円筒図法の実装

デフォルトのメルカトル図法以外の投影方法を使用するためには、Google Mapsのライブラリ中のProjectionBaseクラスを継承し、必要な処理を実装する。

その際には、ProjectionBaseクラスのメッソドの内、fromLatLngToPixel、fromPixelToLatLng、getWrapWidthの3つは必ずオーバライドする必要がある。

正距円筒図法を表すEquirectangularProjectionクラスとして実装例を示す。

public class EquirectangularProjection extends ProjectionBase
{
	public function EquirectangularProjection()
	{
		super();
	}
	
	override public function fromLatLngToPixel(arg0:LatLng, arg1:Number):Point {
		var x : Number = (arg0.lng() + 180.0) * getPixelsPerLng(arg1);
		var y : Number = (90.0 - arg0.lat()) * getPixelsPerLat(arg1);
		return new Point(x, y); 
	}
	
	override public function fromPixelToLatLng(arg0:Point, arg1:Number, arg2:Boolean=false):LatLng {
		var lng : Number = arg0.x / getPixelsPerLng(arg1) - 180.0;
		var lat : Number = 90.0 - arg0.y / getPixelsPerLat(arg1);
		return new LatLng(lat, lng, arg2);
	}
	
	override public function getWrapWidth(arg0:Number):Number {
		return Math.pow(2.0, arg0) * 256;
	}
	
	//1ピクセルに対する経度を取得
	private function getPixelsPerLng(zoom : Number) : Number {
		return 256 * Math.pow(2.0, zoom) / 360;
	}
	
	//1ピクセルに対する緯度を取得
	private function getPixelsPerLat(zoom : Number) : Number {
		return 256 * Math.pow(2.0, zoom) / 180;
	}
}

なお、この実装例では地図のタイル1枚の大きさを256×256とし、ズームレベルが0、1、2、3・・・のとき、地図を構成するタイルの数は1、4、8、16・・・となるものとしている。
そのため、画面上の緯度1の長さは、経度が2の長さと等しくなる。
これに対応させるために、先ほどのcreateCirclePolygon関数内での多角形の頂点のx座標の算出方法の部分を次のように書き直す。

//var x : Number = Math.cos(radian) * radius;
var x : Number = Math.cos(radian) * radius * 2


最後に、作成したEquirectangularProjectionをGoogle Mapsのデフォルトで用意されている通常タイプの世界地図の投影方法として適用する。それには、下のようなコードを記述する(mapはMapクラスのインスタンス)。

//正距円筒図法を通常タイプの世界地図に適用
var normalMapType : IMapType = MapType.NORMAL_MAP_TYPE;
var mapType : MapType = new MapType(normalMapType.getTileLayers(), new EquirectangularProjection(), "tasukete");
map.addMapType(mapType);
map.setMapType(mapType);

先ほどの関数で緯度=75.0, 経度=0.0, 半径=10.0,点の数=24として円を表示させると、正しく円が表示される。

参考リンク

その他

Google Maps上のマーカーのアイコンに円を描画した画像を設定すれば正確な円を簡単に表示できるかも。