Hatena::ブログ(Diary)

カストリブログ

2009-07-31

Flex+Google Mapsで独自地図の表示

| 02:00

本記事では、Flex+Google Mapsでデフォルトの世界地図ではなく、独自の地図を表示する方法について説明します。


地図画像を準備

Google Mapsの地図は一枚の画像ではなく複数のタイル画像から構成されます。

地図のズームレベルを0, 1, 2, 3・・・とすると、対応するタイル画像の枚数は、それぞれ、1, 4, 8, 16・・・です。

したがって、Google Mapsにて独自地図画像を表示させるためには、複数のタイル画像を用意する必要があります。

もし、独自地図画像のズームレベルを0, 1, 2の範囲で表示したいなら、必要な画像の枚数は1+4+8=13枚です。

こんな枚数の画像の用意を手作業で行うのは非常に酷なことです。

Google Maps用の地図画像を生成するツールは多数あるので大丈夫です。

ここでは、GMapImageCutter1.4を使用します。

Google Map Image Cutter

f:id:hippu:20090801015729j:image

その使用手順は以下の通りです。

  1. GMapImageCutter1.4をダウンロードして解凍
  2. 解凍したフォルダ内の「GMapImageCutter.jar」を実行
  3. 表示したい地図画像を読み込む
  4. ズームレベルの最大値を指定
  5. 「Create」ボタンを押す

すると、地図画像と同一フォルダ内に「tiles」というフォルダが作成され、その中に各ズームレベルに応じた複数のタイル画像が生成されます。

生成されるタイル一枚の大きさは256×256です。

「tiles」フォルダを見ると、jpg形式の画像ファイルがt, tq, tr, ts, tt, tqt, tqr, tqs, tqt・・・という名前で作成されています。

ここで、ファイル名の長さはズームレベルを表します。

例えば、ファイル名をtならズームレベル0のタイル画像であり、ttqrstならズームレベル5のタイル画像の一枚であることが分かります。

また、ファイル名に使用される文字は、地図上での位置を表します。ファイル名に使用される文字は、q, r, s, tであり、左上、右上、右下、左下を意味しています。

qrstの解釈

|f:id:hippu:20090801011149p:image:h150:w150|

ズームレベル0-2までのタイルの構成を下に示す。

レベル0レベル1レベル2
f:id:hippu:20090801011148p:image:h150:w150f:id:hippu:20090801011147p:image:h150:w150f:id:hippu:20090801011146p:image:h150:w150

Flexで独自地図画像を表示

Flexから上で用意したタイル画像から独自地図画像を表示する方法を説明します。

Flex+Google Mapsで独自地図画像を表示するためには、TileLayerBaseクラスの継承クラスを作成し、loadTileメッソドをオーバライドする必要があります。

TileLayerBaseクラスを継承したクラスの名前をMyTileLayerとし、その実装コードを下に示します。

public class MyTileLayer extends TileLayerBase {
	//画像ファイルへのパス
	private var path : String;
	//画像形式
	private var format : String;
	
	public function MyTileLayer(path : Strings, format : String)
	{
		this.path = path;
		this.format = format;
		super(new CopyrightCollection(), 0, 4);
	}
	
	//tilePosはtile座標
	//zoom=1なら、tilePosは(0,0)(1,0)(0,1)(1,1)
	override public function loadTile(tilePos:Point, zoom:Number) : DisplayObject {
		var loader : Loader = new Loader();
		var tileUrl : String = path + "/" + getTileName(tilePos, zoom) + format;
		trace(tilePos, zoom, tileUrl);
		loader.load(new URLRequest(tileUrl));
		return loader;
	}
	
	//タイルの位置とzoomレベルから適切なファイル名を生成する関数
	//例:tilePos=(0, 0) zoom=1のときの返り値はtq
	private function getTileName(tilePos : Point, zoom : Number) : String {
		var tileChar : Object = {0 : {0 : "q", 1 : "t"}, 1 : {0 : "r", 1 : "s"}}
		var imageName : String = "t";
		
		for(var i : int = int(zoom); i >= 1; i--) {
			var den : Number = Math.pow(2, i - 1);
			imageName += tileChar[int(tilePos.x / den) % 2][int(tilePos.y / den) % 2];
		}
		return imageName;
	}
}

MyTileLayerクラスの使用例(mapはMapクラスのインスタンス)

//タイル画像の配置パスはtile、画像形式はjpgとする
var myTileLayer : MyTileLayer = new MyTileLayer("tile", "jpg");
var tileLayers : Array = new Array(myTileLayer);

var mapType : MapType = new MapType(tileLayers, map.MERCATOR_PROJECTION, "original map");
myTileLayer.setMapType(mapType);
map.addMapType(mapType);
map.setMapType(mapType);

2009-07-30

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

| 23:33

本文では、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とすると、ほぼ円とみなせる多角形が表示できる。

f:id:hippu:20090730225923j:image


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

f:id:hippu:20090730230054j:image


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

この原因は、デフォルトでは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として円を表示させると、正しく円が表示される。

f:id:hippu:20090730232915j:image

参考リンク

その他

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