]> Raphaël G. Git Repositories - packbundle/blob - Util/MapUtil.php
Cleanup trailing extra equals from hash method result
[packbundle] / Util / MapUtil.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys PackBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\PackBundle\Util;
13
14 use Symfony\Component\Routing\RouterInterface;
15
16 /**
17 * Manages map
18 */
19 class MapUtil {
20 /**
21 * The cycle tile server
22 *
23 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
24 */
25 const cycle = 'http://a.tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png';
26
27 /**
28 * The fill color
29 */
30 const fill = '#cff';
31
32 /**
33 * The font size
34 */
35 const fontSize = 20;
36
37 /**
38 * The high fill color
39 */
40 const highFill = '#c3c3f9';
41
42 /**
43 * The high font size
44 */
45 const highFontSize = 30;
46
47 /**
48 * The high radius size
49 */
50 const highRadius = 6;
51
52 /**
53 * The high stroke color
54 */
55 const highStroke = '#3333c3';
56
57 /**
58 * The high stroke width
59 */
60 const highStrokeWidth = 4;
61
62 /**
63 * The map width
64 */
65 const width = 640;
66
67 /**
68 * The map height
69 */
70 const height = 640;
71
72 /**
73 * The osm tile server
74 *
75 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
76 */
77 const osm = 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png';
78
79 /**
80 * The radius size
81 */
82 const radius = 5;
83
84 /**
85 * The stroke color
86 */
87 const stroke = '#00c3f9';
88
89 /**
90 * The stroke width
91 */
92 const strokeWidth = 2;
93
94 /**
95 * The transport tile server
96 *
97 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
98 */
99 const transport = 'http://a.tile.thunderforest.com/transport/{Z}/{X}/{Y}.png';
100
101 /**
102 * The tile size
103 */
104 const tz = 256;
105
106 /**
107 * The map zoom
108 */
109 const zoom = 17;
110
111 /**
112 * Creates a new map util
113 *
114 * @param RouterInterface $router The RouterInterface instance
115 * @param SluggerUtil $slugger The SluggerUtil instance
116 */
117 function __construct(protected RouterInterface $router, protected SluggerUtil $slugger, protected string $fill = self::fill, protected int $fontSize = self::fontSize, protected string $highFill = self::highFill, protected int $highFontSize = self::highFontSize, protected int $highRadius = self::highRadius, protected string $highStroke = self::highStroke, protected int $highStrokeWidth = self::highStrokeWidth, protected int $radius = self::radius, protected string $stroke = self::stroke, protected int $strokeWidth = self::strokeWidth) {
118 }
119
120 /**
121 * Get fill color
122 */
123 function getFill() {
124 return $this->fill;
125 }
126
127 /**
128 * Get font size
129 */
130 function getFontSize() {
131 return $this->fontSize;
132 }
133
134 /**
135 * Get high fill color
136 */
137 function getHighFill() {
138 return $this->highFill;
139 }
140
141 /**
142 * Get high font size
143 */
144 function getHighFontSize() {
145 return $this->highFontSize;
146 }
147
148 /**
149 * Get high radius size
150 */
151 function getHighRadius() {
152 return $this->highRadius;
153 }
154
155 /**
156 * Get high stroke color
157 */
158 function getHighStroke() {
159 return $this->highStroke;
160 }
161
162 /**
163 * Get high stroke width
164 */
165 function getHighStrokeWidth() {
166 return $this->highStrokeWidth;
167 }
168
169 /**
170 * Get radius size
171 */
172 function getRadius() {
173 return $this->radius;
174 }
175
176 /**
177 * Get stroke color
178 */
179 function getStroke() {
180 return $this->stroke;
181 }
182
183 /**
184 * Get stroke width
185 */
186 function getStrokeWidth() {
187 return $this->strokeWidth;
188 }
189
190 /**
191 * Get map data
192 *
193 * @param string $caption The caption
194 * @param int $updated The updated timestamp
195 * @param float $latitude The latitude
196 * @param float $longitude The longitude
197 * @param int $zoom The zoom
198 * @param int $width The width
199 * @param int $height The height
200 * @return array The map data
201 */
202 public function getMap(string $caption, int $updated, float $latitude, float $longitude, int $zoom = self::zoom, int $width = self::width, int $height = self::height): array {
203 //Set link hash
204 $link = $this->slugger->hash([$updated, $latitude, $longitude, $zoom + 1, $width * 2, $height * 2]);
205
206 //Set src hash
207 $src = $this->slugger->hash([$updated, $latitude, $longitude, $zoom, $width, $height]);
208
209 //Return array
210 return [
211 'caption' => $caption,
212 'link' => $this->router->generate('rapsyspack_map', ['hash' => $link, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom + 1, 'width' => $width * 2, 'height' => $height * 2]),
213 'src' => $this->router->generate('rapsyspack_map', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom, 'width' => $width, 'height' => $height]),
214 'width' => $width,
215 'height' => $height
216 ];
217 }
218
219 /**
220 * Get multi map data
221 *
222 * @param string $caption The caption
223 * @param int $updated The updated timestamp
224 * @param array $coordinates The coordinates array
225 * @param int $width The width
226 * @param int $height The height
227 * @return array The multi map data
228 */
229 public function getMultiMap(string $caption, int $updated, array $coordinates, int $width = self::width, int $height = self::height): array {
230 //Without coordinates
231 if (empty($coordinates)) {
232 //Return empty array
233 return [];
234 }
235
236 //Set latitudes
237 $latitudes = array_map(function ($v) { return $v['latitude']; }, $coordinates);
238
239 //Set longitudes
240 $longitudes = array_map(function ($v) { return $v['longitude']; }, $coordinates);
241
242 //Set latitude
243 $latitude = round((min($latitudes)+max($latitudes))/2, 6);
244
245 //Set longitude
246 $longitude = round((min($longitudes)+max($longitudes))/2, 6);
247
248 //Set zoom
249 $zoom = $this->getMultiZoom($latitude, $longitude, $coordinates, $width, $height);
250
251 //Set coordinate
252 $coordinate = implode('-', array_map(function ($v) { return $v['latitude'].','.$v['longitude']; }, $coordinates));
253
254 //Set coordinate hash
255 $hash = $this->slugger->hash($coordinate);
256
257 //Set link hash
258 $link = $this->slugger->hash([$updated, $latitude, $longitude, $hash, $zoom + 1, $width * 2, $height * 2]);
259
260 //Set src hash
261 $src = $this->slugger->hash([$updated, $latitude, $longitude, $hash, $zoom, $width, $height]);
262
263 //Return array
264 return [
265 'caption' => $caption,
266 'link' => $this->router->generate('rapsyspack_multimap', ['hash' => $link, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'coordinates' => $coordinate, 'zoom' => $zoom + 1, 'width' => $width * 2, 'height' => $height * 2]),
267 'src' => $this->router->generate('rapsyspack_multimap', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'coordinates' => $coordinate, 'zoom' => $zoom, 'width' => $width, 'height' => $height]),
268 'width' => $width,
269 'height' => $height
270 ];
271 }
272
273 /**
274 * Get multi zoom
275 *
276 * Compute a zoom to have all coordinates on multi map
277 * Multi map visible only from -($width / 2) until ($width / 2) and from -($height / 2) until ($height / 2)
278 *
279 * @see Wether we need to take in consideration circle radius in coordinates comparisons, likely +/-(radius / self::tz)
280 *
281 * @param float $latitude The latitude
282 * @param float $longitude The longitude
283 * @param array $coordinates The coordinates array
284 * @param int $width The width
285 * @param int $height The height
286 * @param int $zoom The zoom
287 * @return int The zoom
288 */
289 public function getMultiZoom(float $latitude, float $longitude, array $coordinates, int $width, int $height, int $zoom = self::zoom): int {
290 //Iterate on each zoom
291 for ($i = $zoom; $i >= 1; $i--) {
292 //Get tile xy
293 $centerX = self::longitudeToX($longitude, $i);
294 $centerY = self::latitudeToY($latitude, $i);
295
296 //Calculate start xy
297 $startX = floor($centerX - $width / 2 / self::tz);
298 $startY = floor($centerY - $height / 2 / self::tz);
299
300 //Calculate end xy
301 $endX = ceil($centerX + $width / 2 / self::tz);
302 $endY = ceil($centerY + $height / 2 / self::tz);
303
304 //Iterate on each coordinates
305 foreach($coordinates as $k => $coordinate) {
306 //Set dest x
307 $destX = self::longitudeToX($coordinate['longitude'], $i);
308
309 //With outside point
310 if ($startX >= $destX || $endX <= $destX) {
311 //Skip zoom
312 continue(2);
313 }
314
315 //Set dest y
316 $destY = self::latitudeToY($coordinate['latitude'], $i);
317
318 //With outside point
319 if ($startY >= $destY || $endY <= $destY) {
320 //Skip zoom
321 continue(2);
322 }
323 }
324
325 //Found zoom
326 break;
327 }
328
329 //Return zoom
330 return $i;
331 }
332
333 /**
334 * Convert longitude to tile x number
335 *
336 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5
337 *
338 * @param float $longitude The longitude
339 * @param int $zoom The zoom
340 *
341 * @return float The tile x
342 */
343 public static function longitudeToX(float $longitude, int $zoom): float {
344 return (($longitude + 180) / 360) * pow(2, $zoom);
345 }
346
347 /**
348 * Convert latitude to tile y number
349 *
350 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5
351 *
352 * @param $latitude The latitude
353 * @param $zoom The zoom
354 *
355 * @return float The tile y
356 */
357 public static function latitudeToY(float $latitude, int $zoom): float {
358 return (1 - log(tan(deg2rad($latitude)) + 1 / cos(deg2rad($latitude))) / pi()) / 2 * pow(2, $zoom);
359 }
360
361 /**
362 * Convert tile x to longitude
363 *
364 * @param float $x The tile x
365 * @param int $zoom The zoom
366 *
367 * @return float The longitude
368 */
369 public static function xToLongitude(float $x, int $zoom): float {
370 return $x / pow(2, $zoom) * 360.0 - 180.0;
371 }
372
373 /**
374 * Convert tile y to latitude
375 *
376 * @param float $y The tile y
377 * @param int $zoom The zoom
378 *
379 * @return float The latitude
380 */
381 public static function yToLatitude(float $y, int $zoom): float {
382 return rad2deg(atan(sinh(pi() * (1 - 2 * $y / pow(2, $zoom)))));
383 }
384
385 /**
386 * Convert decimal latitude to sexagesimal
387 *
388 * @param float $latitude The decimal latitude
389 *
390 * @return string The sexagesimal longitude
391 */
392 public static function latitudeToSexagesimal(float $latitude): string {
393 //Set degree
394 //TODO: see if round or intval is better suited to fix the Deprecated: Implicit conversion from float to int loses precision
395 $degree = round($latitude) % 60;
396
397 //Set minute
398 $minute = round(($latitude - $degree) * 60) % 60;
399
400 //Set second
401 $second = round(($latitude - $degree - $minute / 60) * 3600) % 3600;
402
403 //Return sexagesimal longitude
404 return $degree.'°'.$minute.'\''.$second.'"'.($latitude >= 0 ? 'N' : 'S');
405 }
406
407 /**
408 * Convert decimal longitude to sexagesimal
409 *
410 * @param float $longitude The decimal longitude
411 *
412 * @return string The sexagesimal longitude
413 */
414 public static function longitudeToSexagesimal(float $longitude): string {
415 //Set degree
416 //TODO: see if round or intval is better suited to fix the Deprecated: Implicit conversion from float to int loses precision
417 $degree = round($longitude) % 60;
418
419 //Set minute
420 $minute = round(($longitude - $degree) * 60) % 60;
421
422 //Set second
423 $second = round(($longitude - $degree - $minute / 60) * 3600) % 3600;
424
425 //Return sexagesimal longitude
426 return $degree.'°'.$minute.'\''.$second.'"'.($longitude >= 0 ? 'E' : 'W');
427 }
428 }