]> Raphaël G. Git Repositories - packbundle/blob - Util/MapUtil.php
Fix deprecated implicit conversion from float to int loses precision message
[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 * Helps manage 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 size
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 size
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 * The RouterInterface instance
113 */
114 protected RouterInterface $router;
115
116 /**
117 * The SluggerUtil instance
118 */
119 protected SluggerUtil $slugger;
120
121 /**
122 * The fill color
123 */
124 public string $fill;
125
126 /**
127 * The font size
128 */
129 public int $fontSize;
130
131 /**
132 * The high fill color
133 */
134 public string $highFill;
135
136 /**
137 * The font size
138 */
139 public int $highFontSize;
140
141 /**
142 * The radius size
143 */
144 public int $highRadius;
145
146 /**
147 * The high stroke color
148 */
149 public string $highStroke;
150
151 /**
152 * The stroke size
153 */
154 public int $highStrokeWidth;
155
156 /**
157 * The stroke color
158 */
159 public string $stroke;
160
161 /**
162 * The stroke size
163 */
164 public int $strokeWidth;
165
166 /**
167 * The radius size
168 */
169 public int $radius;
170
171 /**
172 * Creates a new map util
173 *
174 * @param RouterInterface $router The RouterInterface instance
175 * @param SluggerUtil $slugger The SluggerUtil instance
176 */
177 function __construct(RouterInterface $router, SluggerUtil $slugger, string $fill = self::fill, int $fontSize = self::fontSize, string $highFill = self::highFill, int $highFontSize = self::highFontSize, int $highRadius = self::highRadius, string $highStroke = self::highStroke, int $highStrokeWidth = self::highStrokeWidth, int $radius = self::radius, string $stroke = self::stroke, int $strokeWidth = self::strokeWidth) {
178 //Set router
179 $this->router = $router;
180
181 //Set slugger
182 $this->slugger = $slugger;
183
184 //Set fill
185 $this->fill = $fill;
186
187 //Set font size
188 $this->fontSize = $fontSize;
189
190 //Set highFill
191 $this->highFill = $highFill;
192
193 //Set high font size
194 $this->highFontSize = $highFontSize;
195
196 //Set high radius size
197 $this->highRadius = $highRadius;
198
199 //Set highStroke
200 $this->highStroke = $highStroke;
201
202 //Set high stroke size
203 $this->highStrokeWidth = $highStrokeWidth;
204
205 //Set radius size
206 $this->radius = $radius;
207
208 //Set stroke
209 $this->stroke = $stroke;
210
211 //Set stroke size
212 $this->strokeWidth = $strokeWidth;
213 }
214
215 /**
216 * Get map data
217 *
218 * @param string $caption The caption
219 * @param int $updated The updated timestamp
220 * @param float $latitude The latitude
221 * @param float $longitude The longitude
222 * @param int $zoom The zoom
223 * @param int $width The width
224 * @param int $height The height
225 * @return array The map data
226 */
227 public function getMap(string $caption, int $updated, float $latitude, float $longitude, int $zoom = self::zoom, int $width = self::width, int $height = self::height): array {
228 //Set link hash
229 $link = $this->slugger->hash([$updated, $latitude, $longitude, $zoom + 1, $width * 2, $height * 2]);
230
231 //Set src hash
232 $src = $this->slugger->hash([$updated, $latitude, $longitude, $zoom, $width, $height]);
233
234 //Return array
235 return [
236 'caption' => $caption,
237 'link' => $this->router->generate('rapsys_pack_map', ['hash' => $link, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom + 1, 'width' => $width * 2, 'height' => $height * 2]),
238 'src' => $this->router->generate('rapsys_pack_map', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom, 'width' => $width, 'height' => $height]),
239 'width' => $width,
240 'height' => $height
241 ];
242 }
243
244 /**
245 * Get multi map data
246 *
247 * @param string $caption The caption
248 * @param int $updated The updated timestamp
249 * @param array $coordinates The coordinates array
250 * @param int $width The width
251 * @param int $height The height
252 * @return array The multi map data
253 */
254 public function getMultiMap(string $caption, int $updated, array $coordinates, int $width = self::width, int $height = self::height): array {
255 //Set latitudes
256 $latitudes = array_map(function ($v) { return $v['latitude']; }, $coordinates);
257
258 //Set longitudes
259 $longitudes = array_map(function ($v) { return $v['longitude']; }, $coordinates);
260
261 //Set latitude
262 $latitude = round((min($latitudes)+max($latitudes))/2, 6);
263
264 //Set longitude
265 $longitude = round((min($longitudes)+max($longitudes))/2, 6);
266
267 //Set zoom
268 $zoom = $this->getMultiZoom($latitude, $longitude, $coordinates, $width, $height);
269
270 //Set coordinate
271 $coordinate = implode('-', array_map(function ($v) { return $v['latitude'].','.$v['longitude']; }, $coordinates));
272
273 //Set coordinate hash
274 $hash = $this->slugger->hash($coordinate);
275
276 //Set link hash
277 $link = $this->slugger->hash([$updated, $latitude, $longitude, $hash, $zoom + 1, $width * 2, $height * 2]);
278
279 //Set src hash
280 $src = $this->slugger->hash([$updated, $latitude, $longitude, $hash, $zoom, $width, $height]);
281
282 //Return array
283 return [
284 'caption' => $caption,
285 'link' => $this->router->generate('rapsys_pack_multimap', ['hash' => $link, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'coordinates' => $coordinate, 'zoom' => $zoom + 1, 'width' => $width * 2, 'height' => $height * 2]),
286 'src' => $this->router->generate('rapsys_pack_multimap', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'coordinates' => $coordinate, 'zoom' => $zoom, 'width' => $width, 'height' => $height]),
287 'width' => $width,
288 'height' => $height
289 ];
290 }
291
292 /**
293 * Get multi zoom
294 *
295 * Compute a zoom to have all coordinates on multi map
296 * Multi map visible only from -($width / 2) until ($width / 2) and from -($height / 2) until ($height / 2)
297 *
298 * @see Wether we need to take in consideration circle radius in coordinates comparisons, likely +/-(radius / self::tz)
299 *
300 * @param float $latitude The latitude
301 * @param float $longitude The longitude
302 * @param array $coordinates The coordinates array
303 * @param int $width The width
304 * @param int $height The height
305 * @param int $zoom The zoom
306 * @return int The zoom
307 */
308 public function getMultiZoom(float $latitude, float $longitude, array $coordinates, int $width, int $height, int $zoom = self::zoom): int {
309 //Iterate on each zoom
310 for ($i = $zoom; $i >= 1; $i--) {
311 //Get tile xy
312 $centerX = self::longitudeToX($longitude, $i);
313 $centerY = self::latitudeToY($latitude, $i);
314
315 //Calculate start xy
316 $startX = floor($centerX - $width / 2 / self::tz);
317 $startY = floor($centerY - $height / 2 / self::tz);
318
319 //Calculate end xy
320 $endX = ceil($centerX + $width / 2 / self::tz);
321 $endY = ceil($centerY + $height / 2 / self::tz);
322
323 //Iterate on each coordinates
324 foreach($coordinates as $k => $coordinate) {
325 //Set dest x
326 $destX = self::longitudeToX($coordinate['longitude'], $i);
327
328 //With outside point
329 if ($startX >= $destX || $endX <= $destX) {
330 //Skip zoom
331 continue(2);
332 }
333
334 //Set dest y
335 $destY = self::latitudeToY($coordinate['latitude'], $i);
336
337 //With outside point
338 if ($startY >= $destY || $endY <= $destY) {
339 //Skip zoom
340 continue(2);
341 }
342 }
343
344 //Found zoom
345 break;
346 }
347
348 //Return zoom
349 return $i;
350 }
351
352 /**
353 * Convert longitude to tile x number
354 *
355 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5
356 *
357 * @param float $longitude The longitude
358 * @param int $zoom The zoom
359 *
360 * @return float The tile x
361 */
362 public static function longitudeToX(float $longitude, int $zoom): float {
363 return (($longitude + 180) / 360) * pow(2, $zoom);
364 }
365
366 /**
367 * Convert latitude to tile y number
368 *
369 * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5
370 *
371 * @param $latitude The latitude
372 * @param $zoom The zoom
373 *
374 * @return float The tile y
375 */
376 public static function latitudeToY(float $latitude, int $zoom): float {
377 return (1 - log(tan(deg2rad($latitude)) + 1 / cos(deg2rad($latitude))) / pi()) / 2 * pow(2, $zoom);
378 }
379
380 /**
381 * Convert tile x to longitude
382 *
383 * @param float $x The tile x
384 * @param int $zoom The zoom
385 *
386 * @return float The longitude
387 */
388 public static function xToLongitude(float $x, int $zoom): float {
389 return $x / pow(2, $zoom) * 360.0 - 180.0;
390 }
391
392 /**
393 * Convert tile y to latitude
394 *
395 * @param float $y The tile y
396 * @param int $zoom The zoom
397 *
398 * @return float The latitude
399 */
400 public static function yToLatitude(float $y, int $zoom): float {
401 return rad2deg(atan(sinh(pi() * (1 - 2 * $y / pow(2, $zoom)))));
402 }
403
404 /**
405 * Convert decimal latitude to sexagesimal
406 *
407 * @param float $latitude The decimal latitude
408 *
409 * @return string The sexagesimal longitude
410 */
411 public static function latitudeToSexagesimal(float $latitude): string {
412 //Set degree
413 //TODO: see if round or intval is better suited to fix the Deprecated: Implicit conversion from float to int loses precision
414 $degree = round($latitude) % 60;
415
416 //Set minute
417 $minute = round(($latitude - $degree) * 60) % 60;
418
419 //Set second
420 $second = round(($latitude - $degree - $minute / 60) * 3600) % 3600;
421
422 //Return sexagesimal longitude
423 return $degree.'°'.$minute.'\''.$second.'"'.($latitude >= 0 ? 'N' : 'S');
424 }
425
426 /**
427 * Convert decimal longitude to sexagesimal
428 *
429 * @param float $longitude The decimal longitude
430 *
431 * @return string The sexagesimal longitude
432 */
433 public static function longitudeToSexagesimal(float $longitude): string {
434 //Set degree
435 //TODO: see if round or intval is better suited to fix the Deprecated: Implicit conversion from float to int loses precision
436 $degree = round($longitude) % 60;
437
438 //Set minute
439 $minute = round(($longitude - $degree) * 60) % 60;
440
441 //Set second
442 $second = round(($longitude - $degree - $minute / 60) * 3600) % 3600;
443
444 //Return sexagesimal longitude
445 return $degree.'°'.$minute.'\''.$second.'"'.($longitude >= 0 ? 'E' : 'W');
446 }
447 }