smindel /
silverstripe-gis
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Smindel\GIS\Control; |
||
| 4 | |||
| 5 | use SilverStripe\Control\Controller; |
||
| 6 | use SilverStripe\Core\Config\Config; |
||
| 7 | use SilverStripe\Security\Security; |
||
| 8 | use SilverStripe\Security\Permission; |
||
| 9 | use SilverStripe\ORM\DB; |
||
| 10 | use Smindel\GIS\GIS; |
||
| 11 | use Smindel\GIS\Service\Tile; |
||
| 12 | use Smindel\GIS\Model\Raster; |
||
| 13 | use SilverStripe\Assets\File; |
||
| 14 | use Exception; |
||
| 15 | |||
| 16 | class WebMapTileService extends AbstractGISWebServiceController |
||
| 17 | { |
||
| 18 | private static $url_handlers = array( |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 19 | '$Model//$ID!/$z!/$x!/$y!' => 'handleAction', |
||
| 20 | '$Model//$z!/$x!/$y!' => 'handleAction', |
||
| 21 | ); |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Buffer in pixel by wich the tile box is enlarged, which is used for |
||
| 25 | * filtering the data. That's in orderer to include points from a bordering |
||
| 26 | * tile that was cut and that wouldn't be rendered in this tile in order to |
||
| 27 | * show a complete point. Use int as a uniform buffer around the current tile |
||
| 28 | * or a two values for left/right and top/bottom or four values for left, top, |
||
| 29 | * right and bottom. |
||
| 30 | * |
||
| 31 | * @var mixed |
||
| 32 | */ |
||
| 33 | private static $tile_buffer = 5; |
||
|
0 ignored issues
–
show
|
|||
| 34 | |||
| 35 | private static $tile_size = 256; |
||
|
0 ignored issues
–
show
|
|||
| 36 | |||
| 37 | private static $wrap_date = true; |
||
|
0 ignored issues
–
show
|
|||
| 38 | |||
| 39 | private static $cache_path = 'tile-cache'; |
||
|
0 ignored issues
–
show
|
|||
| 40 | |||
| 41 | private static $cache_ttl = 0; |
||
|
0 ignored issues
–
show
|
|||
| 42 | |||
| 43 | private static $default_style = [ |
||
|
0 ignored issues
–
show
|
|||
| 44 | 'gd' => [ |
||
| 45 | 'backgroundcolor' => [0, 0, 0, 127], |
||
| 46 | 'strokecolor' => [60, 60, 210, 0], |
||
| 47 | 'fillcolor' => [60, 60, 210, 80], |
||
| 48 | 'setthickness' => [2], |
||
| 49 | 'pointradius' => 5, |
||
| 50 | ], |
||
| 51 | 'imagick' => [ |
||
| 52 | 'StrokeOpacity' => 1, |
||
| 53 | 'StrokeWidth' => 2, |
||
| 54 | 'StrokeColor' => 'rgb(60,60,210)', |
||
| 55 | 'FillColor' => 'rgba(60,60,210,.25)', |
||
| 56 | 'PointRadius' => 5, |
||
| 57 | ], |
||
| 58 | ]; |
||
| 59 | |||
| 60 | 3 | public function index($request) |
|
| 61 | { |
||
| 62 | 3 | $model = $this->model = $this->getModel($request); |
|
| 63 | 3 | $config = $this->getConfig($model); |
|
| 64 | |||
| 65 | if ( |
||
| 66 | 3 | ($cache = $config['cache_ttl'] ? sha1(json_encode($request->getVars())) : false) |
|
| 67 | 1 | && ($age = $this->cacheAge($cache)) !== false |
|
| 68 | && $config['cache_ttl'] > $age |
||
| 69 | ) { |
||
| 70 | $response = $this->getResponse(); |
||
| 71 | $response->addHeader('Content-Type', 'image/png'); |
||
| 72 | $response->setBody($this->readCache($cache)); |
||
| 73 | return $response; |
||
| 74 | } |
||
| 75 | |||
| 76 | 3 | $renderer = Config::inst()->get($this->getModel($request), 'tile_renderer'); |
|
| 77 | 3 | $response = $this->$renderer($request); |
|
| 78 | |||
| 79 | 3 | if ($cache && $response->getStatusCode() == 200) { |
|
| 80 | 1 | $this->writeCache($cache, $response->getBody()); |
|
| 81 | } |
||
| 82 | |||
| 83 | 3 | return $response; |
|
| 84 | } |
||
| 85 | |||
| 86 | 2 | public function vector_renderer($request) |
|
| 87 | { |
||
| 88 | 2 | $model = $this->model = $this->getModel($request); |
|
| 89 | 2 | $config = $this->getConfig($model); |
|
| 90 | 2 | $list = $this->getRecords($request); |
|
| 91 | |||
| 92 | 2 | $z = $request->param('z'); |
|
| 93 | 2 | $x = $request->param('x'); |
|
| 94 | 2 | $y = $request->param('y'); |
|
| 95 | |||
| 96 | 2 | $tileSize = $config['tile_size']; |
|
| 97 | 2 | $tile = Tile::create($z, $x, $y, $config['default_style'], $config['wrap_date'], $tileSize); |
|
| 98 | |||
| 99 | 2 | list($lon1, $lat1) = Tile::zxy2lonlat($z, $x, $y); |
|
| 100 | 2 | list($lon2, $lat2) = Tile::zxy2lonlat($z, $x + 1, $y + 1); |
|
| 101 | |||
| 102 | 2 | $geometryField = $config['geometry_field']; |
|
| 103 | |||
| 104 | 2 | $bufferSize = $config['tile_buffer']; |
|
| 105 | 2 | if (!is_array($bufferSize)) { |
|
| 106 | 2 | $bufferSize = array_fill(0, 4, $bufferSize); |
|
| 107 | } elseif (count($bufferSize) == 2) { |
||
| 108 | $bufferSize += $bufferSize; |
||
| 109 | } |
||
| 110 | |||
| 111 | $buffer = [ |
||
| 112 | 2 | ($lon2 - $lon1) / $tileSize * $bufferSize[0], |
|
| 113 | 2 | ($lat2 - $lat1) / $tileSize * $bufferSize[1], |
|
| 114 | 2 | ($lon2 - $lon1) / $tileSize * $bufferSize[2], |
|
| 115 | 2 | ($lat2 - $lat1) / $tileSize * $bufferSize[3], |
|
| 116 | ]; |
||
| 117 | |||
| 118 | $boxes = [[ |
||
| 119 | 2 | [$lon1 - $buffer[0], $lat1 - $buffer[1]], |
|
| 120 | 2 | [$lon2 + $buffer[2], $lat1 - $buffer[1]], |
|
| 121 | 2 | [$lon2 + $buffer[2], $lat2 + $buffer[3]], |
|
| 122 | 2 | [$lon1 - $buffer[0], $lat2 + $buffer[3]], |
|
| 123 | 2 | [$lon1 - $buffer[0], $lat1 - $buffer[1]], |
|
| 124 | ]]; |
||
| 125 | |||
| 126 | $bounds = [ |
||
| 127 | 2 | 'type' => 'Polygon', |
|
| 128 | 2 | 'srid' => 4326, |
|
| 129 | 2 | 'coordinates' => $boxes, |
|
| 130 | ]; |
||
| 131 | |||
| 132 | 2 | $list = $list->filter( |
|
| 133 | 2 | $geometryField . ':ST_Intersects', |
|
| 134 | 2 | GIS::create($bounds)->reproject(GIS::config()->default_srid) |
|
| 135 | ); |
||
| 136 | |||
| 137 | 2 | if ($request->requestVar('debug')) { |
|
| 138 | $tile->debug("$z, $x, $y, " . $list->count()); |
||
| 139 | } |
||
| 140 | |||
| 141 | 2 | $response = $this->getResponse(); |
|
| 142 | 2 | $response->addHeader('Content-Type', $tile->getContentType()); |
|
| 143 | 2 | $response->setBody($tile->render($list)); |
|
| 144 | |||
| 145 | 2 | return $response; |
|
| 146 | } |
||
| 147 | |||
| 148 | 1 | public function raster_renderer($request) |
|
| 149 | { |
||
| 150 | 1 | $model = $this->model = $this->getModel($request); |
|
| 151 | 1 | if (is_a($model, File::class, true)) { |
|
| 152 | $file = $model::get()->byID($request->param('ID')); |
||
| 153 | if (!$file) { |
||
| 154 | return $this->getResponse()->setStatusCode(404); |
||
| 155 | } |
||
| 156 | if (!$file->canView()) { |
||
| 157 | return $this->getResponse()->setStatusCode(403); |
||
| 158 | } |
||
| 159 | $raster = new Raster(PUBLIC_PATH . $file->getURL()); |
||
| 160 | 1 | } else if (is_a($model, Raster::class, true)) { |
|
| 161 | 1 | $raster = singleton($model); |
|
| 162 | } else { |
||
| 163 | throw new Exception('Cannot render tile from ' . $model); |
||
| 164 | } |
||
| 165 | |||
| 166 | 1 | $z = $request->param('z'); |
|
| 167 | 1 | $x = $request->param('x'); |
|
| 168 | 1 | $y = $request->param('y'); |
|
| 169 | |||
| 170 | 1 | list($lon1, $lat1) = Tile::zxy2lonlat($z, $x, $y); |
|
| 171 | 1 | list($lon2, $lat2) = Tile::zxy2lonlat($z, $x + 1, $y + 1); |
|
| 172 | |||
| 173 | 1 | list($x1, $y1) = ($srid = $raster->getSrid()) == 4326 |
|
| 174 | 1 | ? [$lon1, $lat1] |
|
| 175 | : GIS::create(['srid' => 4326, 'type' => 'Point', 'coordinates' => [$lon1, $lat1]]) |
||
| 176 | 1 | ->reproject($srid)->coordinates; |
|
| 177 | 1 | list($x2, $y2) = ($srid = $raster->getSrid()) == 4326 |
|
| 178 | 1 | ? [$lon2, $lat2] |
|
| 179 | : GIS::create(['srid' => 4326, 'type' => 'Point', 'coordinates' => [$lon2, $lat2]]) |
||
| 180 | 1 | ->reproject($srid)->coordinates; |
|
| 181 | |||
| 182 | 1 | $tile_size_x = $tile_size_y = 256; |
|
| 183 | 1 | $response = $this->getResponse(); |
|
| 184 | |||
| 185 | return $response |
||
| 186 | 1 | ->addHeader('Content-Type', 'image/png') |
|
| 187 | 1 | ->setBody($raster->translateRaster( |
|
| 188 | 1 | [$x1, $y1], [$x2, $y2], |
|
| 189 | 1 | $tile_size_x, $tile_size_y |
|
| 190 | )); |
||
| 191 | } |
||
| 192 | |||
| 193 | 1 | protected function cacheFile($cache) |
|
| 194 | { |
||
| 195 | 1 | $dir = $this->getConfig($this->model)['cache_path'] . DIRECTORY_SEPARATOR; |
|
| 196 | 1 | $dir = $dir[0] != DIRECTORY_SEPARATOR |
|
| 197 | 1 | ? TEMP_PATH . DIRECTORY_SEPARATOR . $dir |
|
| 198 | 1 | : $dir; |
|
| 199 | |||
| 200 | 1 | if (!file_exists($dir)) { |
|
| 201 | 1 | mkdir($dir, fileperms(TEMP_PATH), true); |
|
| 202 | } |
||
| 203 | |||
| 204 | 1 | return $dir . $cache; |
|
| 205 | } |
||
| 206 | |||
| 207 | 1 | protected function cacheAge($cache) |
|
| 208 | { |
||
| 209 | 1 | return is_readable($file = $this->cacheFile($cache)) |
|
| 210 | ? time() - filemtime($file) |
||
| 211 | 1 | : false; |
|
| 212 | } |
||
| 213 | |||
| 214 | protected function readCache($cache) |
||
| 215 | { |
||
| 216 | return file_get_contents($this->cacheFile($cache)); |
||
| 217 | } |
||
| 218 | |||
| 219 | 1 | protected function writeCache($cache, $data) |
|
| 220 | { |
||
| 221 | 1 | file_put_contents($this->cacheFile($cache), $data); |
|
| 222 | 1 | } |
|
| 223 | } |
||
| 224 |