してログ

XYZ 方式のタイルレイヤで、用意されていないズームレベルのタイルを、それより低い解像度のタイルで代用したいときがあります。地理院地図で細かい地図を表示したくないときや、容量やレンダリング負荷を減らしたい場合に有効なテクニックです。OpenLayers2 は不可能だと思って PHP で対応していたのですが、今回可能だということが分かったので紹介します。

OpenLayers2

serverResolutions にサーバーでサポートする解像度を配列で指定しておくと、それ以外のズームレベルで拡大表示が行われます。解像度は1ピクセル当たりのメートル単位になっており、下記ソースコードの配列で0~18の順で並んでいます。拡大表示を確認するには、下側の解像度をいくつかコメントアウトしてみてください。

var map = new OpenLayers.Map({
	div: "map",
	projection: "EPSG:3857",
	displayProjection: "EPSG:4326",
	numZoomLevels: 19,
	layers: [
		new OpenLayers.Layer.XYZ(
			"GSI Maps", 
			"https://cyberjapandata.gsi.go.jp/xyz/std/${z}/${x}/${y}.png",
			{
				serverResolutions: [
					156543.03390625,
					78271.516953125,
					39135.7584765625,
					19567.87923828125,
					9783.939619140625,
					4891.9698095703125,
					2445.9849047851562,
					1222.9924523925781,
					611.4962261962891,
					305.74811309814453,
					152.87405654907226,
					76.43702827453613,
					38.218514137268066
					19.109257068634033,
					9.554628534317017,
					4.777314267158508,
					2.388657133579254
					1.194328566789627,
					0.5971642833948135
				]
			}
		)
	],
	center: new OpenLayers.LonLat(139, 37).transform("EPSG:4326", "EPSG:3857"),
	zoom: 5
});
OpenLayers3

maxZoom にサーバーでサポートするズームレベルを指定すると、それ以外のズームレベルで拡大表示が行われます。下記ソースコードでは、ズームレベル11以上は10の拡大表示になります。

var map = new ol.Map({
	target: 'map',
	layers: [
		new ol.layer.Tile({
			source: new ol.source.XYZ({
				url: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
				maxZoom: 10
			})
		})
	],
	view: new ol.View({
		center: ol.proj.fromLonLat([139, 37]),
		zoom: 5
	})
});
Leaflet

Leaflet では maxNativeZoom を指定することで、同様の拡大表示を実現可能です。

PHP で無理やり対応(おまけ)

OpenLayers2 にはそういった機能が無いと思って、PHP で動的に拡大カットするラッパーを組んでしまいました。必要性が無くなったものの、他にも応用が効くコードかと思いますので公開しておきます。

  • シンプルにするために、以下のコードでは入力パラメータをチェックしていませんのでご注意ください
  • $max_zoom にはタイルレイヤで用意されている最大ズームレベルを入れてください
$z = $_REQUEST['z'];
$x = $_REQUEST['x'];
$y = $_REQUEST['y'];

$max_zoom = 12;

if ($z > $max_zoom) {

	// $max_zoom のタイルを切り取って拡大する

	$pw = pow(2,$z - $max_zoom);

	$zz = $max_zoom;
	$xx = floor($x / $pw);
	$yy = floor($y / $pw);

	$dw = 256;
	$sw = floor($dw / $pw);
	$sx = $x % $pw * $sw;
	$sy = $y % $pw * $sw;

	$path = "./layers/tile/{$zz}/{$xx}/{$yy}.png";

	if (file_exists($path)) {

		$src = imagecreatefrompng($path);

		$dst = imagecreatetruecolor($dw,$dw);
		imageAlphaBlending($dst,false);
		imageSaveAlpha($dst,true);
		$transparent = imageColorAllocateAlpha($dst, 0,0,0, 0);
		imageFill($dst, 0,0, $transparent);

		imagecopyresampled($dst, $src, 0,0, $sx,$sy, $dw,$dw, $sw,$sw);

		imagepng($dst);
		exit;

	} else {

		header("HTTP/1.1 404 Not Found");
		exit;
	}

} else {

	// 実ファイルにリダイレクト

	$url = "./layers/tile/{$z}/{$x}/{$y}.png";

	header('Location: '.$url);
	exit;

}
関連ページ

PostGIS で遊んでみたくなったので、CentOS7 で環境を作ってみた。

yum install postgresql-server
postgresql-setup initdb

yum install epel-release

yum install proj proj-devel proj-epsg
yum install gdal
yum install postgis

firewall-cmd --add-service=postgresql --permanent
firewall-cmd --reload

systemctl start postgresql
systemctl enable postgresql

表示して意味があるのかという問題は置いておいて、そういった要求でもネガティブな対応しないために目安の数値を計算してみました。目安というのは、同じズームレベルでも緯度によって縮尺が変わるし、実表示サイズが OS やディスプレイによって変わるためです。

計算結果は、ズームレベル0のタイルで横幅を 40,000km とし、表示サイズは96dpiで一致するようにしました。計算した緯度は、確認のための赤道と、キリがいい北緯37度、日本経緯度原点です。最終的な縮尺目安はキリのいい数字に丸めていますが、北海道と沖縄県ではだいぶ変わってくるはずですから注意してください。

Excel の計算式を下記に示しますので、システム内で表示緯度に応じて計算させる場合に使用してください。

=40000000*SIN(RADIANS(90-C$2))/2^$A3/256*$B$25/$B$26
C$2
緯度
$A3
ズームレベル
$B$25
表示デバイスのDPI(Windows なら 96)
$B$26
インチからメートルへの換算係数(0.0254)
計算結果
Zoom
Level
原図の縮尺縮尺(赤道)縮尺(北緯37度)縮尺(緯度原点)目安の縮尺
03735.65809922
0標高データ590,551,181-1471,635,144-1479,828,774-11/5億
1295,275,591-1235,817,572-1239,914,387-11/2億5千万
2147,637,795-1117,908,786-1119,957,193-11/1億2千万
373,818,898-158,954,393-159,978,597-11/6千万
436,909,449-129,477,196-129,989,298-11/3千万
51 : 5,000,00018,454,724-114,738,598-114,994,649-11/1,500万
69,227,362-17,369,299-17,497,325-11/750万
74,613,681-13,684,650-13,748,662-11/380万
82,306,841-11,842,325-11,874,331-11/180万
91 : 1,000,0001,153,420-1921,162-1937,166-11/100万
10576,710-1460,581-1468,583-11/50万
11288,355-1230,291-1234,291-11/20万
121 : 200,000144,178-1115,145-1117,146-11/10万
1372,089-157,573-158,573-11/5万
1436,044-128,786-129,286-11/3万
151 : 25,00018,022-114,393-114,643-11/1.5万
169,011-17,197-17,322-11/7,500
174,506-13,598-13,661-11/3,500
181 : 2,5001,799-11,845-11,830-11/1,800