📜 ⬆️ ⬇️

We draw tiles with data for GoogleMap for PHP

Preamble

Currently, it is very popular to visualize any data on maps. Yes, and other things, and not only visualization, many applications: games, geo-services, visualization, statistics, and much, much more. On the one hand, the use of canvas is good and modern, on the other hand - the number of objects can exceed all imaginable and inconceivable limits, which leads to a decrease in the speed of the user with such services, thousands of polygons on the canvas “slow down the client”, browsers “eat” memory in huge quantities, etc. This is not to mention the fact that although rare, support of “old” browsers that do not support canvas / html5 is needed.

Simple example


Imagine something similar to this picture, reduce the scale and thereby increase the number of polygons in the “frame” to 5,000. An office computer two or three years old may die on the drawing of such a map. You can fight this by simply adding an overlay layer to the map with your own tiles.


')
Initial data

Suppose that we have a table in a MySQL database, in which certain blocks are described, represented by the coordinates of the vertices of the polygon. In our example, in the picture above, these are hand-drawn contours of the blocks of the city of Yekaterinburg. For each landfill there is a distance from the city center, we will use it for coloring blocks as an example of visualizing some data (mass options: population density, environmental pollution, etc.)

Code

I tried to document the code in as much detail as possible so that it was understandable to beginners. The code is far from perfect, written in an hour, and claims only to illustrate how this can be made to work.
<?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; } ?> 


How it works

  1. We calculate the boundaries of the zone in which all our data will fit.
  2. Generate a large image for each zoom level.
  3. We draw in our data
  4. Cut it into small pieces 256x256
  5. We put them on daddy


Then everything is simple, create an additional Map Type in the Google Map 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); 

And we implement as an overlay layer
  map.overlayMapTypes.insertAt(0, BWPolygonsMapType); 

Well versed in the Google Map API, they may hang up this layer at will with switches and other decorations, but we’ll stop there.

Demo

The result of the work here .

Speed

For example, 2,873 blocks were used, located within the boundaries of the city of Yekaterinburg.
The number of tiles for zooms from 12 to 16 is 5,118 .
The running time of this script is 1 minute 11 seconds .
Generation was performed on the HP Proliant DL 360 G5 server (1 Intel Xeon E5420 @ 2.50GHz, 4 Gb RAM)

I found it difficult to decide on a blog, put it in PHP, and those who want to transfer it to a more suitable one - well.

Source: https://habr.com/ru/post/146107/


All Articles