GoogleMAPライク 画像をズームアップさせてみたり

概要

巨大な画像をWeb上で伸縮自在に動かしてみたい!
とゆーモノだと思い浮かぶのがGoogleMAPなワケでして。
Google Maps APIとかそんなのでも実現可能っぽいですが、今回はLeaflet.jsを使います。
「Download」から「Leaflet 0.7.3」をダウンロード。
画像は256×256ピクセルのJPEGを準備すれば良いようです。
一番小さくした時(256×256)は
0-0-0.jpg
という画像。
そこから1段階ズームした場合(512×512)は
1-0-0.jpg1-1-0.jpg
1-0-1.jpg1-1-1.jpg
という画像。
さらにもう1段階ズーム(1024×1024)すると
2-0-0.jpg2-1-0.jpg2-2-0.jpg2-3-0.jpg
2-0-1.jpg2-1-1.jpg2-2-1.jpg2-3-1.jpg
2-0-2.jpg2-1-2.jpg2-2-2.jpg2-3-2.jpg
2-0-3.jpg2-1-3.jpg2-2-3.jpg2-3-3.jpg
といった画像を準備すれば良いようです。
ズームレベル{z}、X軸指定{x}、Y軸指定{y}の変数さえ指定できればフォルダ名でもファイル名でもOKです。
手動でフォルダを作ると非常に面倒なので、ファイル名で作ります。

画像分割

ただこの分割作業とリネームが非常に面倒臭い。
ダウンロード時にイロイロ必要なGMap Image Cutterってのがありました。(ver 1.43で確認)
Google Maps APIを使って自動的に画像とHTMLを作成してくれるので、ただそれっぽいのを作るならこれでも良いかも。
でもLeaflet.js用にするならリネーム作業が必要になってきます。
さらに縦横のサイズが同じでなければムダな画像まで生成してくれます。
悪くは無いんですが、今回コレは使用しません。
分割結合「あ」で縦に切って、その後横に切りました。
自動で連番を振ってくれるので、上手くやればリネーム作業もほとんど必要ありません。
それで完成したのがDQ1(GB版)マップになります。
ぴしゃり2048×2048のマップだったので、上手い具合にできました。

ImageMagick

今回はこの1枚だけで作ってみましたが…何枚かやってるとやはり面倒。
もちっと簡単に行けそうなのを探して…ImageMagickが良いとゆー話が。
コマンドラインになるのでダメな人はダメそうな感もありますが、
convert.exe -crop 256x2048 3.jpg 3.jpg
のコマンドで2048×2048の画像(3.jpg)を256×2048の画像(3-0〜3-7.jpg)に切り出し、
convert.exe -crop 256x256 3-0.jpg 3-0.jpg
convert.exe -crop 256x256 3-1.jpg 3-1.jpg
convert.exe -crop 256x256 3-2.jpg 3-2.jpg
convert.exe -crop 256x256 3-3.jpg 3-3.jpg
convert.exe -crop 256x256 3-4.jpg 3-4.jpg
convert.exe -crop 256x256 3-5.jpg 3-5.jpg
convert.exe -crop 256x256 3-6.jpg 3-6.jpg
convert.exe -crop 256x256 3-7.jpg 3-7.jpg
のコマンドで256×2048の画像(3-0〜3-7.jpg)を256×256の画像(3-0-0〜3-7-7.jpg)に切り出してくれます。
さらに楽なコマンドもできそうな気もしますが、とりあえずはこんな感じで。
縦横比が違ってもタイル画像を作れますし。
ちなみに縦横比が違う場合の最小画像は
0-0-0.jpg
でいいとして、そこから1段階ズームした場合は
1-0-0.jpg1-1-0.jpg
にして、もう1段階ズームすると
2-0-0.jpg2-1-0.jpg2-2-0.jpg2-3-0.jpg
2-0-1.jpg2-1-1.jpg2-2-1.jpg2-3-1.jpg
といった感じにしないといけません。
ま、こうしても0-0-0.jpgはちょっと妙なコトになるんですけどね。

Leaflet.jsもImageMagickもまだまだ機能があるので、イロイロ試してみたいと思います。

位置指定サンプル

…というワケで、一番基本的な感じ↓で作ってみました。
<!DOCTYPE html>
<html>
<head>
	<title>DQ1 GB Map</title>
	<meta charset="utf-8" />
	<link rel="stylesheet" href="leaflet.css" />
	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>

	<script src="leaflet.js"></script>
	<script type="text/javascript" src="leaflet-hash.js"></script>  
	<script>

		var map = L.map('map').setView([0, 0], 0);
		var hash = new L.Hash(map);

		L.tileLayer('img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg', {
			maxZoom: 3,
			minZoom: 0,
			noWrap: true
		}).addTo(map);

	</script>
</body>
</html>
ついでに15、19行目に追加した「leaflet-hash」で座標指定のリンクもできるようにしてみました。
メルキド@DQ1(GB版)マップみたいな感じでね。
なお、24行目の「noWrap: true」を取ると左右(東西)のループになります。
今回はDQ1のマップなのでループしないようにしています。

グリッド表示サンプル

さらに「leaflet-virtual-grid」を使って16×16のグリッドを表示させてみました。
全画面にするとシャレにならないくらい重くなるのでINFRAMEです。


16行目で「virtual-grid.js」を読み込んで、28〜59行目でグリッドの設定&表示だと思います。
<!DOCTYPE html>
<html>
<head>
	<title>DQ1 GB Map</title>
	<meta charset="utf-8" />
	<link rel="stylesheet" href="leaflet.css" />
	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>

	<script src="leaflet.js"></script>
	<script type="text/javascript" src="leaflet-hash.js"></script>
	<script src="virtual-grid.js"></script>
	<script>

		var map = L.map('map').setView([0, 0], 0);
		var hash = new L.Hash(map);

		L.tileLayer('img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg', {
			maxZoom: 3,
			minZoom: 0,
			noWrap: true,
		}).addTo(map);

      var rects = {};

      function coordsToKey(coords){
        return coords.x + ':' + coords.y + ':' +coords.z;
      }

      var grid = L.virtualGrid({
        cellSize: 16
      });

      // when new cells come into view...
      grid.on("cellcreate", function(e) {
        console.log(e.type, e);
        rects[coordsToKey(e.coords)] = L.rectangle(e.bounds, {
          color: '#333333',
          weight: 1,
          opacity: 0.5,
          fillOpacity: 0
        }).addTo(map);
      });

      grid.on("cellenter", function(e) {
        console.log(e.type, e);
        map.addLayer(rects[coordsToKey(e.coords)]);
      });

      grid.on("cellleave", function(e) {
        console.log(e.type, e);
        map.removeLayer(rects[coordsToKey(e.coords)]);
      });

      grid.addTo(map);

	</script>
</body>
</html>

ミニマップ表示サンプル

8、11行目に追加した「MiniMap」でミニマップを表示できるようにしてみました。↓
<!DOCTYPE html>
<html>
<head>
	<title>DQ1(GB版)マップ</title>
	<meta charset="utf-8" />
	<META NAME="Description" content="GB版DQ1のマップをGoogleMAP風にしてみました。">
	<link rel="stylesheet" href="../sc/leaflet.css" />
	<link rel="stylesheet" href="../sc/Control.MiniMap.css" />
	<script src="../sc/leaflet.js"></script>
	<script type="text/javascript" src="../sc/leaflet-hash.js"></script>
	<script src="../sc/Control.MiniMap.js" type="text/javascript"></script>

	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>
	<script type="text/javascript">

		// マップ本体表示部分
		var map = new L.Map('map');
		var dq1Url='img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg';
		var dq1m = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 3, noWrap: true});

		map.addLayer(dq1m);
		map.setView(new L.LatLng(0, 0),1);
		
		//ハッシュ表示
		var hash = new L.Hash(map);

		//ミニマップ表示部
		var dq1m2 = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 0, noWrap: true });
		var miniMap = new L.Control.MiniMap(dq1m2, { toggleDisplay: true }).addTo(map);


	</script>
</body>
</html>
実は「<script src="../sc/leaflet.js"></script>」をヘッダに入れないと上手く動きませんでした。
あとはミニマップの表示/非表示切り替え画像も場所を指定する必要があるので、「Control.MiniMap.css」を一部書き換えています。
CSSなんでHTMLに直接書いても良いんですけどね。
この分に関してはコメントアウト部分も日本語なので、あまり解説は無しです。(オイ)

表示範囲指定サンプル

37、38行目に追加した部分で表示範囲を指定してみました。↓
<!DOCTYPE html>
<html>
<head>
	<title>DQ1(GB版)マップ</title>
	<meta charset="utf-8" />
	<META NAME="Description" content="GB版DQ1のマップをGoogleMAP風にしてみました。">
	<link rel="stylesheet" href="../sc/leaflet.css" />
	<link rel="stylesheet" href="../sc/Control.MiniMap.css" />
	<script src="../sc/leaflet.js"></script>
	<script type="text/javascript" src="../sc/leaflet-hash.js"></script>
	<script src="../sc/Control.MiniMap.js" type="text/javascript"></script>

	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>
	<script type="text/javascript">

		// マップ本体表示部分
		var map = new L.Map('map');
		var dq1Url='img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg';
		var dq1m = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 3, noWrap: true});

		map.addLayer(dq1m);
		map.setView(new L.LatLng(0, 0),1);
		
		//ハッシュ表示
		var hash = new L.Hash(map);

		//ミニマップ表示部
		var dq1m2 = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 0, noWrap: true });
		var miniMap = new L.Control.MiniMap(dq1m2, { toggleDisplay: true }).addTo(map);
		
		//表示範囲指定
		var bounds = new L.LatLngBounds([[-90, -180], [90, 180]]);
		map.setMaxBounds(bounds);


	</script>
</body>
</html>
ホントは端まで行くとしっかり止まってほしいけど、そういう動きはできないっぽい?
DQ1(GB版)マップ(表示範囲指定版)DQ1(GB版)マップ(通常版)で比べると動作がわかりやすいと思います。

マーカー表示サンプル

40〜61行目でマーカーを表示させてみました。↓
<!DOCTYPE html>
<html>
<head>
	<title>DQ1(GB版)マップ</title>
	<meta charset="utf-8" />
	<META NAME="Description" content="GB版DQ1のマップをGoogleMAP風にしてみました。">
	<link rel="stylesheet" href="../sc/leaflet.css" />
	<link rel="stylesheet" href="../sc/Control.MiniMap.css" />
	<script src="../sc/leaflet.js"></script>
	<script type="text/javascript" src="../sc/leaflet-hash.js"></script>
	<script src="../sc/Control.MiniMap.js" type="text/javascript"></script>

	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>
	<script type="text/javascript">

		// マップ本体表示部分
		var map = new L.Map('map');
		var dq1Url='img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg';
		var dq1m = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 3, noWrap: true});

		map.addLayer(dq1m);
		map.setView(new L.LatLng(0, 0),1);
		
		//ハッシュ表示
		var hash = new L.Hash(map);

		//ミニマップ表示部
		var dq1m2 = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 0, noWrap: true });
		var miniMap = new L.Control.MiniMap(dq1m2, { toggleDisplay: true }).addTo(map);
		
		//表示範囲指定
		var bounds = new L.LatLngBounds([[-90, -180], [90, 180]]);
		map.setMaxBounds(bounds);

		//マーカー位置
		var cities = new L.LayerGroup();
		L.marker([48.10743, -53.4375]).bindPopup('ラダトーム').addTo(cities),
		L.marker([84.08888, -170.33203]).bindPopup('ガライ').addTo(cities),
		L.marker([-59.88894, -107.22656]).bindPopup('ドムドーラ').addTo(cities),
		L.marker([-73.82482, 27.94922]).bindPopup('メルキド').addTo(cities),
		L.marker([81.25503, 116.89453]).bindPopup('マイラ').addTo(cities);
		L.marker([-26.11599, 111.09375]).bindPopup('リムルダール').addTo(cities);

		var castle = new L.LayerGroup();
		L.marker([40.58058, -36.73828]).bindPopup('竜王の城').addTo(castle);

		var overlays = {
			"Cities": cities
		};

		var overlays2 = {
			"町": cities,
			"竜王の城": castle
		};

		L.control.layers(overlays,overlays2).addTo(map);

	</script>
</body>
</html>
せっかくなので表示・非表示も切り替えられるようにしてます。
DQ1(GB版)マップ(マーカーレイヤー切り替え版)で試すコトができます。
52〜54行目は無くても良さそうなんですが、削ると動作おかしくなりました。
あとはマーカー画像のファイル指定がよくわかりませんでした。(爆)

CSV読み込みサンプル

マーカーを直接記述せずに、CSV読み込みで表示させてみました。↓
<!DOCTYPE html>
<html>
<head>
	<title>DQ1(GB版)マップ</title>
	<meta charset="utf-8" />
	<META NAME="Description" content="GB版DQ1のマップをGoogleMAP風にしてみました。">
	<link rel="stylesheet" href="../sc/leaflet.css" />
	<script src="../sc/leaflet.js"></script>
	<script type="text/javascript" src="../sc/leaflet-hash.js"></script>
	<link rel="stylesheet" href="../sc/Control.MiniMap.css" />
	<script src="../sc/Control.MiniMap.js" type="text/javascript"></script>
	<script src="../sc/leaflet-omnivore.min.js" type="text/javascript"></script>

	<style type="text/css">
	html, body, #map { width: 100%; height: 100%; padding: 0; margin: 0; }
	</style>
</head>
<body>
	<div id="map"></div>
	<script type="text/javascript">

		// マップ本体表示部分
		var map = new L.Map('map');
		var dq1Url='img/dq1gb/TileGroup0/{z}-{x}-{y}.jpg';
		var dq1m = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 3, noWrap: true});

		map.addLayer(dq1m);
		map.setView(new L.LatLng(0, 0),1);
		
		//ハッシュ表示
		var hash = new L.Hash(map);

		//ミニマップ表示部
		var dq1m2 = new L.TileLayer(dq1Url, {minZoom: 0, maxZoom: 0, noWrap: true });
		var miniMap = new L.Control.MiniMap(dq1m2, { toggleDisplay: true }).addTo(map);
		
		//表示範囲指定
		var bounds = new L.LatLngBounds([[-90, -180], [90, 180]]);
		map.setMaxBounds(bounds);

		//マーカー位置
		var cities = new L.LayerGroup();
		//CSV読み込み&アイコン表示
		omnivore.csv('dq1g_map.csv',{latfield: '緯度', lonfield: '経度', delimiter: ','},
		  L.geoJson(null, {pointToLayer: function(feature, latlng) {return L.marker(latlng);},
		    onEachFeature: function(feature, layer) {layer.bindPopup(feature.properties['名称']);}
		  })
		).addTo(cities);

		var castle = new L.LayerGroup();
		L.marker([40.58058, -36.73828]).bindPopup('竜王の城').addTo(castle);

		var overlays = {
			"Cities": cities
		};

		var overlays2 = {
			"町": cities,
			"竜王の城": castle
		};

		L.control.layers(overlays,overlays2).addTo(map);

	</script>
</body>
</html>
DQ1(GB版)マップ(マーカーレイヤー切り替え版)から単純にCSV読み込みに変更しています。
DQ1(GB版)マップ(マーカーCSV読み込み&レイヤー切り替え版)で試して…って、挙動は基本変わっていません。
このCSV読み込みに関しては世界の測量(leaflet-omnivore テスト)を参考にさせていただきました。
12行目で「leaflet-omnivore.min.js」を読み込み、44〜48行目でCSV読み込み&データ表示を行っています。
CSVファイルは日本語表記ですが、UTF-8で改行コードがLFじゃないと表示されないってのにハマりました。

また何かあれば追記していきたいと思います。


[スットン共和国] / [ワンダープロジェクトJ] / GoogleMAPライク