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 | |||||
13 | class WebMapTileService extends AbstractGISWebServiceController |
||||
14 | { |
||||
15 | private static $url_handlers = array( |
||||
16 | '$Model//$z/$x/$y' => 'handleAction', |
||||
17 | ); |
||||
18 | |||||
19 | /** |
||||
20 | * Buffer in pixel by wich the tile box is enlarged, which is used for |
||||
21 | * filtering the data. That's in orderer to include points from a bordering |
||||
22 | * tile that was cut and that wouldn't be rendered in this tile in order to |
||||
23 | * show a complete point. Use int as a uniform buffer around the current tile |
||||
24 | * or a two values for left/right and top/bottom or four values for left, top, |
||||
25 | * right and bottom. |
||||
26 | * |
||||
27 | * @var mixed |
||||
28 | */ |
||||
29 | private static $tile_buffer = 5; |
||||
30 | |||||
31 | private static $tile_size = 256; |
||||
32 | |||||
33 | private static $wrap_date = true; |
||||
34 | |||||
35 | private static $cache_path = 'tile-cache'; |
||||
36 | |||||
37 | private static $cache_ttl = 0; |
||||
38 | |||||
39 | private static $default_style = [ |
||||
40 | 'gd' => [ |
||||
41 | 'backgroundcolor' => [0, 0, 0, 127], |
||||
42 | 'strokecolor' => [60, 60, 210, 0], |
||||
43 | 'fillcolor' => [60, 60, 210, 80], |
||||
44 | 'setthickness' => [2], |
||||
45 | 'pointradius' => 5, |
||||
46 | ], |
||||
47 | 'imagick' => [ |
||||
48 | 'StrokeOpacity' => 1, |
||||
49 | 'StrokeWidth' => 2, |
||||
50 | 'StrokeColor' => 'rgb(60,60,210)', |
||||
51 | 'FillColor' => 'rgba(60,60,210,.25)', |
||||
52 | 'PointRadius' => 5, |
||||
53 | ], |
||||
54 | ]; |
||||
55 | |||||
56 | 2 | public function index($request) |
|||
57 | { |
||||
58 | 2 | $model = $this->model = $this->getModel($request); |
|||
59 | 2 | $config = $this->getConfig($model); |
|||
60 | |||||
61 | if ( |
||||
62 | 2 | ($cache = $config['cache_ttl'] ? sha1(json_encode($request->getVars())) : false) |
|||
63 | 1 | && ($age = $this->cacheAge($cache)) !== false |
|||
64 | && $config['cache_ttl'] > $age |
||||
65 | ) { |
||||
66 | $response = $this->getResponse(); |
||||
67 | $response->addHeader('Content-Type', 'image/png'); |
||||
68 | $response->setBody($this->readCache($cache)); |
||||
69 | return $response; |
||||
70 | } |
||||
71 | |||||
72 | 2 | $renderer = Config::inst()->get($this->getModel($request), 'tile_renderer'); |
|||
73 | 2 | $response = $this->$renderer($request); |
|||
74 | |||||
75 | 2 | if ($cache && $response->getStatusCode() == 200) { |
|||
76 | 1 | $this->writeCache($cache, $response->getBody()); |
|||
77 | } |
||||
78 | |||||
79 | 2 | return $response; |
|||
80 | } |
||||
81 | |||||
82 | 2 | public function vector_renderer($request) |
|||
83 | { |
||||
84 | 2 | $model = $this->model = $this->getModel($request); |
|||
85 | 2 | $config = $this->getConfig($model); |
|||
86 | 2 | $list = $this->getRecords($request); |
|||
87 | |||||
88 | 2 | $z = $request->param('z'); |
|||
89 | 2 | $x = $request->param('x'); |
|||
90 | 2 | $y = $request->param('y'); |
|||
91 | |||||
92 | 2 | $tileSize = $config['tile_size']; |
|||
93 | 2 | $tile = Tile::create($z, $x, $y, $config['default_style'], $config['wrap_date'], $tileSize); |
|||
94 | |||||
95 | 2 | list($lon1, $lat1) = Tile::zxy2lonlat($z, $x, $y); |
|||
96 | 2 | list($lon2, $lat2) = Tile::zxy2lonlat($z, $x + 1, $y + 1); |
|||
97 | |||||
98 | 2 | $geometryField = $config['geometry_field']; |
|||
99 | |||||
100 | 2 | $bufferSize = $config['tile_buffer']; |
|||
101 | 2 | if (!is_array($bufferSize)) { |
|||
102 | 2 | $bufferSize = array_fill(0, 4, $bufferSize); |
|||
103 | } elseif (count($bufferSize) == 2) { |
||||
104 | $bufferSize += $bufferSize; |
||||
105 | } |
||||
106 | |||||
107 | $buffer = [ |
||||
108 | 2 | ($lon2 - $lon1) / $tileSize * $bufferSize[0], |
|||
109 | 2 | ($lat2 - $lat1) / $tileSize * $bufferSize[1], |
|||
110 | 2 | ($lon2 - $lon1) / $tileSize * $bufferSize[2], |
|||
111 | 2 | ($lat2 - $lat1) / $tileSize * $bufferSize[3], |
|||
112 | ]; |
||||
113 | |||||
114 | $boxes = [[ |
||||
115 | 2 | [$lon1 - $buffer[0], $lat1 - $buffer[1]], |
|||
116 | 2 | [$lon2 + $buffer[2], $lat1 - $buffer[1]], |
|||
117 | 2 | [$lon2 + $buffer[2], $lat2 + $buffer[3]], |
|||
118 | 2 | [$lon1 - $buffer[0], $lat2 + $buffer[3]], |
|||
119 | 2 | [$lon1 - $buffer[0], $lat1 - $buffer[1]], |
|||
120 | ]]; |
||||
121 | |||||
122 | $bounds = [ |
||||
123 | 2 | 'type' => 'Polygon', |
|||
124 | 2 | 'srid' => 4326, |
|||
125 | 2 | 'coordinates' => $boxes, |
|||
126 | ]; |
||||
127 | |||||
128 | 2 | $list = $list->filter( |
|||
129 | 2 | $geometryField . ':ST_Intersects', |
|||
130 | 2 | GIS::array_to_ewkt( |
|||
131 | 2 | GIS::reproject_array( |
|||
132 | 2 | $bounds, |
|||
133 | 2 | Config::inst()->get(GIS::class, 'default_srid') |
|||
134 | ) |
||||
135 | ) |
||||
136 | ); |
||||
137 | |||||
138 | 2 | if ($request->requestVar('debug')) { |
|||
139 | $tile->debug("$z, $x, $y, " . $list->count()); |
||||
140 | } |
||||
141 | |||||
142 | 2 | $response = $this->getResponse(); |
|||
143 | 2 | $response->addHeader('Content-Type', $tile->getContentType()); |
|||
144 | 2 | $response->setBody($tile->render($list)); |
|||
145 | |||||
146 | 2 | return $response; |
|||
147 | } |
||||
148 | |||||
149 | public function raster_renderer($request) |
||||
150 | { |
||||
151 | $model = $this->model = $this->getModel($request); |
||||
152 | $config = $this->getConfig($model); |
||||
153 | $raster = singleton($model); |
||||
154 | |||||
155 | $z = $request->param('z'); |
||||
156 | $x = $request->param('x'); |
||||
157 | $y = $request->param('y'); |
||||
158 | |||||
159 | list($lon1, $lat1) = Tile::zxy2lonlat($z, $x, $y); |
||||
160 | list($lon2, $lat2) = Tile::zxy2lonlat($z, $x + 1, $y + 1); |
||||
161 | |||||
162 | list($x1, $y1) = ($srid = $raster->getSrid()) == 4326 ? [$lon1, $lat1] : GIS::reproject_array(['srid' => 4326, 'type' => 'Point', 'coordinates' => [$lon1, $lat1]], $srid)['coordinates']; |
||||
163 | list($x2, $y2) = ($srid = $raster->getSrid()) == 4326 ? [$lon2, $lat2] : GIS::reproject_array(['srid' => 4326, 'type' => 'Point', 'coordinates' => [$lon2, $lat2]], $srid)['coordinates']; |
||||
164 | |||||
165 | $sfx = ($x2 - $x1) / $config['tile_size']; |
||||
166 | $sfy = ($y2 - $y1) / $config['tile_size']; |
||||
167 | |||||
168 | DB::query("set bytea_output='escape'"); |
||||
169 | |||||
170 | $rasterDef = ($colormap = $raster->getColorMap()) |
||||
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||||
171 | ? sprintf('ST_ColorMap(%1$s, \'%2$s\')', $raster->getRasterColumn(), $raster->getColorMap()) |
||||
172 | : $raster->getRasterColumn(); |
||||
173 | |||||
174 | $sql = sprintf(' |
||||
175 | SELECT |
||||
176 | ST_AsPNG(%3$s) pngbinary |
||||
177 | FROM |
||||
178 | ST_Retile( |
||||
179 | \'%1$s\'::regclass, |
||||
180 | \'%2$s\', |
||||
181 | ST_MakeEnvelope(%4$f, %5$f, %6$f, %7$f, %8$d), |
||||
182 | %9$f, %10$f, |
||||
183 | %11$d, %11$d |
||||
184 | ) %2$s |
||||
185 | LIMIT 1; |
||||
186 | ', |
||||
187 | $raster->getTableName(), |
||||
188 | $raster->getRasterColumn(), |
||||
189 | $rasterDef, |
||||
190 | $x1, $y1, $x2, $y2, |
||||
191 | $srid, |
||||
192 | $sfx, $sfy, |
||||
193 | $config['tile_size'] |
||||
194 | ); |
||||
195 | |||||
196 | $query = DB::query($sql); |
||||
197 | |||||
198 | $raw = $query->value(); |
||||
199 | $response = $this->getResponse(); |
||||
200 | |||||
201 | $dimensions = $raster->getDimensions(); |
||||
202 | |||||
203 | $padding[0] = ($dimensions[0] - $x1) / $sfx; |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
204 | $padding[1] = ($dimensions[1] - $y1) / $sfy; |
||||
205 | $padding[2] = ($x2 - $dimensions[2]) / $sfx; |
||||
206 | $padding[3] = ($y2 - $dimensions[3]) / $sfy; |
||||
207 | |||||
208 | $response->addHeader('Content-Type', 'image/png'); |
||||
209 | if (!$raw) { |
||||
210 | $response->setBody(base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=')); |
||||
211 | } else if (max(...$padding) > 0) { |
||||
212 | |||||
213 | $imgout = imagecreatetruecolor($config['tile_size'], $config['tile_size']); |
||||
214 | imagecolortransparent($imgout, imagecolorallocatealpha($imgout, 0, 0, 0, 0)); |
||||
215 | imagealphablending($imgout, true); |
||||
216 | |||||
217 | imagecopyresampled( |
||||
218 | $imgout, |
||||
219 | imagecreatefromstring(pg_unescape_bytea($raw)), |
||||
220 | $padding[0] > 0 ? $padding[0] : 0, |
||||
221 | $padding[1] > 0 ? $padding[1] : 0, |
||||
222 | 0, |
||||
223 | 0, |
||||
224 | $config['tile_size'], |
||||
225 | $config['tile_size'], |
||||
226 | (max($x1, $x2) - min($x1, $x2)) / abs($sfx), |
||||
0 ignored issues
–
show
max($x1, $x2) - min($x1, $x2) / abs($sfx) of type double is incompatible with the type integer expected by parameter $src_w of imagecopyresampled() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
227 | (max($y1, $y2) - min($y1, $y2)) / abs($sfy) |
||||
0 ignored issues
–
show
max($y1, $y2) - min($y1, $y2) / abs($sfy) of type double is incompatible with the type integer expected by parameter $src_h of imagecopyresampled() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
228 | ); |
||||
229 | |||||
230 | ob_start(); |
||||
231 | imagepng($imgout); |
||||
232 | |||||
233 | $response->setBody(ob_get_clean()); |
||||
234 | } else { |
||||
235 | $response->setBody(pg_unescape_bytea($raw)); |
||||
236 | } |
||||
237 | |||||
238 | DB::query("set bytea_output='hex'"); |
||||
239 | |||||
240 | return $response; |
||||
241 | } |
||||
242 | |||||
243 | 1 | protected function cacheFile($cache) |
|||
244 | { |
||||
245 | 1 | $dir = $this->getConfig($this->model)['cache_path'] . DIRECTORY_SEPARATOR; |
|||
246 | 1 | $dir = $dir[0] != DIRECTORY_SEPARATOR |
|||
247 | 1 | ? TEMP_PATH . DIRECTORY_SEPARATOR . $dir |
|||
248 | 1 | : $dir; |
|||
249 | |||||
250 | 1 | if (!file_exists($dir)) { |
|||
251 | 1 | mkdir($dir, fileperms(TEMP_PATH), true); |
|||
252 | } |
||||
253 | |||||
254 | 1 | return $dir . $cache; |
|||
255 | } |
||||
256 | |||||
257 | 1 | protected function cacheAge($cache) |
|||
258 | { |
||||
259 | 1 | return is_readable($file = $this->cacheFile($cache)) |
|||
260 | ? time() - filemtime($file) |
||||
261 | 1 | : false; |
|||
262 | } |
||||
263 | |||||
264 | protected function readCache($cache) |
||||
265 | { |
||||
266 | return file_get_contents($this->cacheFile($cache)); |
||||
267 | } |
||||
268 | |||||
269 | 1 | protected function writeCache($cache, $data) |
|||
270 | { |
||||
271 | 1 | file_put_contents($this->cacheFile($cache), $data); |
|||
272 | 1 | } |
|||
273 | } |
||||
274 |