前文
現在、地図上のデータを視覚化することは非常に人気があります。 ところで、視覚化だけでなく、ゲーム、ジオサービス、視覚化、統計など、多くのアプリケーションがあります。 一方で、キャンバスの使用は良好で現代的であり、他方では、オブジェクトの数が考えられない想像を超えるすべての制限を超える可能性があり、そのようなサービスではユーザーの速度が低下します大量など まれではありますが、canvas / html5をサポートしない「古い」ブラウザーのサポートが必要であることは言うまでもありません。
簡単な例
この写真のようなものを想像して、縮尺を縮小し、それによって「フレーム」内のポリゴンの数を5,000に増やします。 これに対処するには、タイルを使用してマップにオーバーレイレイヤーを追加するだけです。
ソースデータ
MySQLデータベースに、ポリゴンの頂点の座標で表されるいくつかのブロックを記述するテーブルがあるとします。 上記の写真の例では、これらはエカテリンブルクの街区の手描きの輪郭です。 各埋め立て地には、市の中心からの距離があり、いくつかのデータの視覚化の例として、ブロックの色付けに使用します(質量オプション:人口密度、環境汚染など)
コード
初心者にはわかりやすいように、できるだけ詳細にコードを文書化しようとしました。 コードは完璧にはほど遠い、1時間で書かれており、これがどのように機能するかを説明するためだけに見せかけています。
<?php // , mysql.php // - mysql, // $db. require('mysql.php'); // $tiles_path = '/some/path/to/web/site/root/poly-tiles/'; // , . if (!file_exists($tiles_path)) { mkdir($tiles_path, 0755); } // , // $zooms = array(12,13,14,15,16); // , // 'vertices', '|' $query = 'SELECT * FROM map_blocks'; // , $result = $db->query($query); // while ($block = $db->fetch_array($result,1)) { // id $blocks[$block['blockid']] = $block; // $verticles = explode('|',$block['vertices']); // : foreach ($verticles as $verticle) { // $v_coord = explode(',',$verticle); // , , // // $lats[] = $v_coord[0]; $long[] = $v_coord[1]; } } // , // : foreach ($zooms as $zoom) { // ( ) make_zoom_dir ($zoom); // ( ) $bigimg = gen_map ($zoom,$blocks,$lats,$long); // imagepng($bigimg,$tiles_path.$zoom.'/all.png'); // , ( ) tile_map ($zoom,$bigimg,$blocks,$lats,$long); } // , exit; /** * gen_map * * . * * @param integer $zoom * @param array $blocks * @param array $lats * @param array $long * @return gd_image $image */ function gen_map ($zoom,$blocks,$lats,$long) { // $x['min'] = min($long); $y['min'] = max($lats); $x['max'] = max($long); $y['max'] = min($lats); // (getTile x & y) $tiles['tl'] = getTile ($zoom,$y['min'],$x['min']); $tiles['rb'] = getTile ($zoom,$y['max'],$x['max']); // +1 ( ) $picsize_blocks['x'] = $tiles['rb']['x'] - $tiles['tl']['x'] + 1; $picsize_blocks['y'] = $tiles['rb']['y'] - $tiles['tl']['y'] + 1; // $pict_w = $picsize_blocks['x'] * 256; $pict_h = $picsize_blocks['y'] * 256; // 180/85 , // $world_shift['x'] = $tiles['tl']['x'] * 256; $world_shift['y'] = $tiles['tl']['y'] * 256; // GD-image $image = imagecreatetruecolor($pict_w, $pict_h); // $bg = imagecolorallocatealpha($image, 255, 255, 255, 0); // imagecolortransparent($image, $bg); // $black = imagecolorallocate($image, 0, 0, 0); // , $color1 = imagecolorallocatealpha($image, 255, 0, 0, 50); $color2 = imagecolorallocatealpha($image, 204, 0, 51, 50); $color3 = imagecolorallocatealpha($image, 153, 0, 102, 50); $color4 = imagecolorallocatealpha($image, 102, 0, 153, 50); $color5 = imagecolorallocatealpha($image, 51, 0, 204, 50); $color6 = imagecolorallocatealpha($image, 0, 0, 255, 50); // imagefilledrectangle($image, 0, 0, $pict_w-1, $pict_h-1, $bg); // : foreach ($blocks as $block_id=>$block_data) { // $vertices = $block_data['vertices']; // $verticles_data = explode('|',$vertices); // : foreach ($verticles_data as $vert) { // $b_coord = explode(',',$vert); // , $vx = lonToX($b_coord[1], $zoom); $vy = latToY($b_coord[0], $zoom); // 'verts' $vershiny[$block_id]['verts'][] = $vx - $world_shift['x']; $vershiny[$block_id]['verts'][] = $vy - $world_shift['y']; } // 'vcount' // ( - , // , ). , // . $vershiny[$block_id]['vcount'] = intval(count($vershiny[$block_id]['verts'])/2); } // // - .. foreach ($vershiny as $block_id=>$b_data) { // if, . $block_dist = $blocks[$block_id]['distance']; if ( $block_dist >= 0 && $block_dist < 1000 ) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color1); } if ( $block_dist >= 1000 && $block_dist < 2000 ) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color2); } if ( $block_dist >= 2000 && $block_dist < 4000 ) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color3); } if ( $block_dist >= 4000 && $block_dist < 7000 ) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color4); } if ( $block_dist >= 7000 && $block_dist < 10000 ) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color5); } if ( $block_dist >= 10000 && $block_dist < 15000) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $color6); } if ( $block_dist >= 15000) { imagefilledpolygon($image, $b_data['verts'], $b_data['vcount'], $black); } } // return $image; } /** * tile_map * * * * @param integer $zoom * @param gd_image $img , * @param array $blocks * @param array $lats * @param array $long */ function tile_map ($zoom,$img,$blocks,$lats,$long) { global $tiles_path; // $x['min'] = min($long); $y['min'] = max($lats); $x['max'] = max($long); $y['max'] = min($lats); // (getTile x & y) $tiles['tl'] = getTile ($zoom,$y['min'],$x['min']); $tiles['rb'] = getTile ($zoom,$y['max'],$x['max']); // : // for($x = $tiles['tl']['x']; $x<=$tiles['rb']['x']; $x++) { // for($y = $tiles['tl']['y']; $y <= $tiles['rb']['y']; $y++) { // $from_position_x = $x - $tiles['tl']['x']; $from_position_y = $y - $tiles['tl']['y']; // $from_x = $from_position_x * 256; $from_y = $from_position_y * 256; // GD-image $tile = imagecreatetruecolor(256, 256); // $bg = imagecolorallocatealpha($tile, 255, 255, 255, 0); // // ( ) imagecopymerge($tile,$img,0,0,$from_x,$from_y,256,256,100); // $white = imagecolorclosest ($tile, 255,255,255); // $black = imagecolorclosest ($tile, 0,0,0); // imagecolortransparent($tile, $bg); // , imagecolortransparent($tile, $white); imagecolortransparent($tile, $black); // X make_zoom_x_dir ($zoom,$x); // // - : {$tiles_path}/{$zoom_dir}/{$x}/{$x}x{$t}.png $tile_name = make_tile_name ($zoom,$x,$y); // , echo "Zoom: $zoom, $xx $y -> $tile_name\n"; // imagepng($tile,$tile_name); // GD-image , :-) imagedestroy($tile); } } // GD-image , :-) imagedestroy($img); } /** * make_tile_name * * * x & y * * @param integer $zoom * @param integer $x X * @param integer $y Y * @return string */ function make_tile_name ($zoom,$x,$y) { global $tiles_path; return $tiles_path.$zoom.'/'.$x.'/'.$y.'.png'; } /** * make_zoom_dir * * * * @param integer $zoom */ function make_zoom_dir ($zoom) { global $tiles_path; if (!file_exists($tiles_path.$zoom)) { mkdir($tiles_path.$zoom, 0755); } } /** * make_zoom_x_dir * * X * * @param integer $zoom * @param integer $x X */ function make_zoom_x_dir ($zoom,$x) { global $tiles_path; if (!file_exists($tiles_path.$zoom.'/'.$x.'/')) { mkdir($tiles_path.$zoom.'/'.$x.'/', 0755); } } /** * lonToX * * Returns longitude in pixels at a certain zoom level * * @param float $lon longitude * @param integer $zoom */ function lonToX($lon, $zoom) { $offset = 256 << ($zoom-1); $x = round($offset + ($offset * $lon / 180)); return $x; } /** * lonToX * * Returns latitude in pixels at a certain zoom level * * @param float $lat latitude * @param integer $zoom */ function latToY($lat, $zoom) { $offset = 256 << ($zoom-1); $y = round($offset - $offset/pi() * log((1 + sin($lat * pi() / 180)) / (1 - sin($lat * pi() / 180))) / 2); return $y; } /** * getTile * * Returns tile x & y numbers at a certain zoom level, latitude & longitude * * @param integer $zoom * @param float $lat latitude * @param float $lon longitude */ function getTile ($zoom,$lat,$lon) { $tile['x'] = floor((($lon + 180) / 360) * pow(2, $zoom)); $tile['y'] = floor((1 - log(tan(deg2rad($lat)) + 1 / cos(deg2rad($lat))) / pi()) /2 * pow(2, $zoom)); return $tile; } /** * tilenums2latlon * * Convert tile coordinates pair to latitude, longitude. * * @param int $_xtile X coordinate of the tile. * @param int $_ytile Y coordinate of the tile. * @param itn $_zoom Zoom level. * @return Point Returns latitude and longitude as a {@link Point} object. */ function tilenums2latlon($_xtile, $_ytile, $_zoom) { $factor = pow(2.0, floatval($_zoom)); $coord['lon'] = ($_xtile * 360 / $factor) - 180.0; $lat = atan(sinh(M_PI * (1 - 2 * $_ytile / $factor))); $coord['lat'] = degrees($lat); return $coord; } /** * Utility function. Transforms degree value to radian one. * * @param float $_degrees Degree value. * @return float Radian value. */ function radians($_degrees) { return M_PI * $_degrees / 180; } /** * Utility function. Converts radians to degrees. * * @param float $_radians Radian value. * @return float Degree value. */ function degrees($_radians) { return $_radians * 180 / M_PI; } ?>
仕組み
- すべてのデータが収まるゾーンの境界を計算します
- ズームレベルごとに大きな画像を生成します。
- データを描画します
- 256x256の小さな断片に切り取ります
- パパに入れます
その後、すべてが簡単です。GoogleMap APIで追加のマップタイプを作成します
var BWPolygonsOptions = { getTileUrl: function(ll, z) { var X = ll.x % (1 << z); // wrap return "http://some.host.com/poly-tiles/" + z + "/" + X + "/" + ll.y + ".png"; }, tileSize: new google.maps.Size(256, 256), isPng: true, minZoom: 12, maxZoom: 16, name: "BWPolygons", alt: "BWPolygons" }; var BWPolygonsMapType = new google.maps.ImageMapType(BWPolygonsOptions);
オーバーレイレイヤーとして埋め込む
map.overlayMapTypes.insertAt(0, BWPolygonsMapType);
Google Map APIに精通しているため、スイッチやその他の装飾でこのレイヤーを自由に明るくすることができます。これに焦点を当てます。
デモ
仕事の結果はこちらです。
スピード
たとえば、エカテリンブルク市の境界内にある2,873ブロックが使用されました。
12から16までのズームのタイル数は5,118です。
このスクリプトの実行時間は1分11秒です。
生成は、HP Proliant DL 360 G5サーバー(2.50 GHzで1つのIntel Xeon E5420、4 Gb RAM)で実行されました。
ブログで決定するのが難しいと感じたので、それをPHPに入れました。