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
![]() |
|||
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 |