* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Rapsys\PackBundle\Util; use Symfony\Component\Routing\RouterInterface; /** * Helps manage map */ class MapUtil { /** * The cycle tile server * * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers */ const cycle = 'http://a.tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'; /** * The fill color */ const fill = '#cff'; /** * The font size */ const fontSize = 20; /** * The high fill color */ const highFill = '#c3c3f9'; /** * The high font size */ const highFontSize = 30; /** * The high radius size */ const highRadius = 6; /** * The high stroke color */ const highStroke = '#3333c3'; /** * The high stroke size */ const highStrokeWidth = 4; /** * The map length */ const length = 640; /** * The osm tile server * * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers */ const osm = 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'; /** * The radius size */ const radius = 5; /** * The stroke color */ const stroke = '#00c3f9'; /** * The stroke size */ const strokeWidth = 2; /** * The transport tile server * * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers */ const transport = 'http://a.tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'; /** * The tile size */ const tz = 256; /** * The map zoom */ const zoom = 17; /** * The RouterInterface instance */ protected RouterInterface $router; /** * The SluggerUtil instance */ protected SluggerUtil $slugger; /** * The fill color */ public string $fill; /** * The font size */ public int $fontSize; /** * The high fill color */ public string $highFill; /** * The font size */ public int $highFontSize; /** * The radius size */ public int $highRadius; /** * The high stroke color */ public string $highStroke; /** * The stroke size */ public int $highStrokeWidth; /** * The stroke color */ public string $stroke; /** * The stroke size */ public int $strokeWidth; /** * The radius size */ public int $radius; /** * Creates a new map util * * @param RouterInterface $router The RouterInterface instance * @param SluggerUtil $slugger The SluggerUtil instance */ 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) { //Set router $this->router = $router; //Set slugger $this->slugger = $slugger; //Set fill $this->fill = $fill; //Set font size $this->fontSize = $fontSize; //Set highFill $this->highFill = $highFill; //Set high font size $this->highFontSize = $highFontSize; //Set high radius size $this->highRadius = $highRadius; //Set highStroke $this->highStroke = $highStroke; //Set high stroke size $this->highStrokeWidth = $highStrokeWidth; //Set radius size $this->radius = $radius; //Set stroke $this->stroke = $stroke; //Set stroke size $this->strokeWidth = $strokeWidth; } /** * Return map url * * @param string $caption The caption * @param int $updated The updated timestamp * @param float $latitude The latitude * @param float $longitude The longitude * @param int $zoom The zoom * @param int $width The width * @param int $height The height * @return int The zoom */ public function mapUrl(string $caption, int $updated, float $latitude, float $longitude, int $zoom = self::zoom, int $width = self::length, int $height = self::length): array { //Set link hash $link = $this->slugger->serialize([$updated, $latitude, $longitude, $zoom + 1, $width * 2, $height * 2]); //Set src hash $src = $this->slugger->serialize([$updated, $latitude, $longitude, $zoom, $width, $height]); //Return array return [ 'caption' => $caption, 'link' => $this->router->generate('rapsys_pack_map', ['hash' => $link, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom + 1, 'width' => $width * 2, 'height' => $height * 2]), 'src' => $this->router->generate('rapsys_pack_map', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'zoom' => $zoom, 'width' => $width, 'height' => $height]), 'width' => $width, 'height' => $height ]; } /** * Return multi map url * * @param string $caption The caption * @param int $updated The updated timestamp * @param float $latitude The latitude * @param float $longitude The longitude * @param array $coordinates The coordinates array * @param int $zoom The zoom * @param int $width The width * @param int $height The height * @return int The zoom */ public function multiMapUrl(string $caption, int $updated, float $latitude, float $longitude, $coordinates = [], int $zoom = self::zoom, int $width = self::length, int $height = self::length): array { //Set coordinate $coordinate = implode('-', array_map(function ($v) { return $v['latitude'].','.$v['longitude']; }, $coordinates)); //Set coordinate hash $hash = $this->slugger->hash($coordinate); //Set link hash $link = $this->slugger->serialize([$updated, $latitude, $longitude, $hash, $zoom + 1, $width * 2, $height * 2]); //Set src hash $src = $this->slugger->serialize([$updated, $latitude, $longitude, $hash, $zoom, $width, $height]); //Return array return [ 'caption' => $caption, '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]), 'src' => $this->router->generate('rapsys_pack_multimap', ['hash' => $src, 'updated' => $updated, 'latitude' => $latitude, 'longitude' => $longitude, 'coordinates' => $coordinate, 'zoom' => $zoom, 'width' => $width, 'height' => $height]), 'width' => $width, 'height' => $height ]; } /** * Return multi map zoom * * Compute a zoom to have all coordinates on multi map * Multi map visible only from -($width / 2) until ($width / 2) and from -($height / 2) until ($height / 2) * * @see Wether we need to take in consideration circle radius in coordinates comparisons, likely +/-(radius / self::tz) * * @param float $latitude The latitude * @param float $longitude The longitude * @param array $coordinates The coordinates array * @param int $zoom The zoom * @param int $width The width * @param int $height The height * @return int The zoom */ public function multiMapZoom(float $latitude, float $longitude, array $coordinates = [], int $zoom = self::zoom, int $width = self::length, int $height = self::length): int { //Iterate on each zoom for ($i = $zoom; $i >= 1; $i--) { //Get tile xy $centerX = self::longitudeToX($longitude, $i); $centerY = self::latitudeToY($latitude, $i); //Calculate start xy $startX = floor($centerX - $width / 2 / self::tz); $startY = floor($centerY - $height / 2 / self::tz); //Calculate end xy $endX = ceil($centerX + $width / 2 / self::tz); $endY = ceil($centerY + $height / 2 / self::tz); //Iterate on each coordinates foreach($coordinates as $k => $coordinate) { //Set dest x $destX = self::longitudeToX($coordinate['longitude'], $i); //With outside point if ($startX >= $destX || $endX <= $destX) { //Skip zoom continue(2); } //Set dest y $destY = self::latitudeToY($coordinate['latitude'], $i); //With outside point if ($startY >= $destY || $endY <= $destY) { //Skip zoom continue(2); } } //Found zoom break; } //Return zoom return $i; } /** * Convert longitude to tile x number * * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5 * * @param float $longitude The longitude * @param int $zoom The zoom * * @return float The tile x */ public static function longitudeToX(float $longitude, int $zoom): float { return (($longitude + 180) / 360) * pow(2, $zoom); } /** * Convert latitude to tile y number * * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_5 * * @param $latitude The latitude * @param $zoom The zoom * * @return float The tile y */ public static function latitudeToY(float $latitude, int $zoom): float { return (1 - log(tan(deg2rad($latitude)) + 1 / cos(deg2rad($latitude))) / pi()) / 2 * pow(2, $zoom); } /** * Convert tile x to longitude * * @param float $x The tile x * @param int $zoom The zoom * * @return float The longitude */ public static function xToLongitude(float $x, int $zoom): float { return $x / pow(2, $zoom) * 360.0 - 180.0; } /** * Convert tile y to latitude * * @param float $y The tile y * @param int $zoom The zoom * * @return float The latitude */ public static function yToLatitude(float $y, int $zoom): float { return rad2deg(atan(sinh(pi() * (1 - 2 * $y / pow(2, $zoom))))); } /** * Convert decimal latitude to sexagesimal * * @param float $latitude The decimal latitude * * @return string The sexagesimal longitude */ public static function latitudeToSexagesimal(float $latitude): string { //Set degree $degree = $latitude % 60; //Set minute $minute = ($latitude - $degree) * 60 % 60; //Set second $second = ($latitude - $degree - $minute / 60) * 3600 % 3600; //Return sexagesimal longitude return $degree.'°'.$minute.'\''.$second.'"'.($latitude >= 0 ? 'N' : 'S'); } /** * Convert decimal longitude to sexagesimal * * @param float $longitude The decimal longitude * * @return string The sexagesimal longitude */ public static function longitudeToSexagesimal(float $longitude): string { //Set degree $degree = $longitude % 60; //Set minute $minute = ($longitude - $degree) * 60 % 60; //Set second $second = ($longitude - $degree - $minute / 60) * 3600 % 3600; //Return sexagesimal longitude return $degree.'°'.$minute.'\''.$second.'"'.($longitude >= 0 ? 'E' : 'W'); } }