mirror of
https://github.com/wavelog/wavelog.git
synced 2026-03-22 10:24:14 +00:00
Static Map API (#1098)
Added new Static Map Feature --------- Co-authored-by: phl0 <github@florian-wolters.de>
This commit is contained in:
23
src/StaticMap/LICENSE
Normal file
23
src/StaticMap/LICENSE
Normal file
@@ -0,0 +1,23 @@
|
||||
https://github.com/DantSu/php-osm-static-api
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Franck ALARY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
116
src/StaticMap/README.md
Normal file
116
src/StaticMap/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
[](https://packagist.org/packages/DantSu/php-osm-static-api)
|
||||
[](https://packagist.org/packages/DantSu/php-osm-static-api)
|
||||
[](https://github.com/DantSu/php-osm-static-api/blob/master/LICENSE)
|
||||
|
||||
# PHP OpenStreetMap Static API
|
||||
https://github.com/DantSu/php-osm-static-api
|
||||
|
||||
PHP library to easily get static image from OpenStreetMap with markers, lines, circles and polygons.
|
||||
|
||||
This project uses the [Tile Server](https://wiki.openstreetmap.org/wiki/Tile_servers) of the OpenStreetMap Foundation which runs entirely on donated resources, see [Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/) for more information.
|
||||
|
||||
## ✨ Supporting
|
||||
|
||||
⭐ Star this repository to support this project. You will contribute to increase the visibility of this library 🙂
|
||||
|
||||
## Installation
|
||||
|
||||
Install this library easily with composer :
|
||||
|
||||
```cmd
|
||||
composer require dantsu/php-osm-static-api
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
### Generate OpenStreetMap static image with markers and polygon :
|
||||
|
||||
```php
|
||||
use \Wavelog\StaticMapImage\OpenStreetMap;
|
||||
use \Wavelog\StaticMapImage\LatLng;
|
||||
use \Wavelog\StaticMapImage\Polygon;
|
||||
use \Wavelog\StaticMapImage\Markers;
|
||||
|
||||
\header('Content-type: image/png');
|
||||
(new OpenStreetMap(new LatLng(44.351933, 2.568113), 17, 600, 400))
|
||||
->addMarkers(
|
||||
(new Markers(__DIR__ . '/resources/marker.png'))
|
||||
->setAnchor(Markers::ANCHOR_CENTER, Markers::ANCHOR_BOTTOM)
|
||||
->addMarker(new LatLng(44.351933, 2.568113))
|
||||
->addMarker(new LatLng(44.351510, 2.570020))
|
||||
->addMarker(new LatLng(44.351873, 2.566250))
|
||||
)
|
||||
->addDraw(
|
||||
(new Polygon('FF0000', 2, 'FF0000DD'))
|
||||
->addPoint(new LatLng(44.351172, 2.571092))
|
||||
->addPoint(new LatLng(44.352097, 2.570045))
|
||||
->addPoint(new LatLng(44.352665, 2.568107))
|
||||
->addPoint(new LatLng(44.352887, 2.566503))
|
||||
->addPoint(new LatLng(44.352806, 2.565972))
|
||||
->addPoint(new LatLng(44.351517, 2.565672))
|
||||
)
|
||||
->getImage()
|
||||
->displayPNG();
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Align and zoom the map to drawings and markers :
|
||||
|
||||
- `->fitToDraws(int $padding = 0)`
|
||||
- `->fitToMarkers(int $padding = 0)`
|
||||
- `->fitToDrawsAndMarkers(int $padding = 0)`
|
||||
- `->fitToPoints(LatLng[] $points, int $padding = 0)`
|
||||
|
||||
`$padding` sets the amount of padding in the borders of the map that shouldn't be accounted for when setting the view to fit bounds. This can be positive or negative according to your needs.
|
||||
|
||||
```php
|
||||
use \Wavelog\StaticMapImage\OpenStreetMap;
|
||||
use \Wavelog\StaticMapImage\LatLng;
|
||||
use \Wavelog\StaticMapImage\Polygon;
|
||||
use \Wavelog\StaticMapImage\Markers;
|
||||
|
||||
\header('Content-type: image/png');
|
||||
(new OpenStreetMap(new LatLng(0, 0), 0, 600, 400))
|
||||
->addMarkers(
|
||||
(new Markers(__DIR__ . '/resources/marker.png'))
|
||||
->setAnchor(Markers::ANCHOR_CENTER, Markers::ANCHOR_BOTTOM)
|
||||
->addMarker(new LatLng(44.351933, 2.568113))
|
||||
->addMarker(new LatLng(44.351510, 2.570020))
|
||||
->addMarker(new LatLng(44.351873, 2.566250))
|
||||
)
|
||||
->addDraw(
|
||||
(new Polygon('FF0000', 2, 'FF0000DD'))
|
||||
->addPoint(new LatLng(44.351172, 2.571092))
|
||||
->addPoint(new LatLng(44.352097, 2.570045))
|
||||
->addPoint(new LatLng(44.352665, 2.568107))
|
||||
->addPoint(new LatLng(44.352887, 2.566503))
|
||||
->addPoint(new LatLng(44.352806, 2.565972))
|
||||
->addPoint(new LatLng(44.351517, 2.565672))
|
||||
)
|
||||
->fitToDraws(10)
|
||||
->getImage()
|
||||
->displayPNG();
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
| Class | Description |
|
||||
|--- |--- |
|
||||
| [Circle](./docs/classes/DantSu/OpenStreetMapStaticAPI/Circle.md) | Wavelog\StaticMapImage\Circle draw circle on the map.|
|
||||
| [LatLng](./docs/classes/DantSu/OpenStreetMapStaticAPI/LatLng.md) | Wavelog\StaticMapImage\LatLng define latitude and longitude for map, lines, markers.|
|
||||
| [Line](./docs/classes/DantSu/OpenStreetMapStaticAPI/Line.md) | Wavelog\StaticMapImage\Line draw line on the map.|
|
||||
| [MapData](./docs/classes/DantSu/OpenStreetMapStaticAPI/MapData.md) | Wavelog\StaticMapImage\MapData convert latitude and longitude to image pixel position.|
|
||||
| [Markers](./docs/classes/DantSu/OpenStreetMapStaticAPI/Markers.md) | Wavelog\StaticMapImage\Markers display markers on the map.|
|
||||
| [OpenStreetMap](./docs/classes/DantSu/OpenStreetMapStaticAPI/OpenStreetMap.md) | Wavelog\StaticMapImage\OpenStreetMap is a PHP library created for easily get static image from OpenStreetMap with markers, lines, polygons and circles.|
|
||||
| [Polygon](./docs/classes/DantSu/OpenStreetMapStaticAPI/Polygon.md) | Wavelog\StaticMapImage\Polygon draw polygon on the map.|
|
||||
| [TileLayer](./docs/classes/DantSu/OpenStreetMapStaticAPI/TileLayer.md) | Wavelog\StaticMapImage\TileLayer define tile server url and related configuration|
|
||||
| [XY](./docs/classes/DantSu/OpenStreetMapStaticAPI/XY.md) | Wavelog\StaticMapImage\XY define X and Y pixel position for map, lines, markers.|
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Please fork this repository and contribute back using pull requests.
|
||||
|
||||
Any contributions, large or small, major features, bug fixes, are welcomed and appreciated but will be thoroughly reviewed.
|
||||
|
||||
132
src/StaticMap/src/Circle.php
Normal file
132
src/StaticMap/src/Circle.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
|
||||
use Wavelog\StaticMapImage\Interfaces\Draw;
|
||||
use Wavelog\StaticMapImage\Utils\GeographicConverter;
|
||||
use DantSu\PHPImageEditor\Geometry2D;
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\Circle draw circle on the map.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class Circle implements Draw
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $strokeColor = '000000';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $strokeWeight = 1;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fillColor = '000000';
|
||||
/**
|
||||
* @var LatLng
|
||||
*/
|
||||
private $center = null;
|
||||
|
||||
/**
|
||||
* @var LatLng
|
||||
*/
|
||||
private $edge = null;
|
||||
|
||||
/**
|
||||
* @var bool wrap around the world or not
|
||||
*/
|
||||
private $wrap;
|
||||
|
||||
/**
|
||||
* Circle constructor.
|
||||
*
|
||||
* @param LatLng $center Latitude and longitude of the circle center
|
||||
* @param string $strokeColor Hexadecimal string color
|
||||
* @param int $strokeWeight pixel weight of the line
|
||||
* @param string $fillColor Hexadecimal string color
|
||||
*/
|
||||
public function __construct(LatLng $center, string $strokeColor, int $strokeWeight, string $fillColor, bool $wrap = false)
|
||||
{
|
||||
$this->center = $center;
|
||||
$this->edge = $center;
|
||||
$this->strokeColor = \str_replace('#', '', $strokeColor);
|
||||
$this->strokeWeight = $strokeWeight > 0 ? $strokeWeight : 0;
|
||||
$this->fillColor = \str_replace('#', '', $fillColor);
|
||||
$this->wrap = $wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a latitude and longitude to define the radius of the circle.
|
||||
*
|
||||
* @param LatLng $edge Latitude and longitude of the edge point of a circle
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setEdgePoint(LatLng $edge): Circle
|
||||
{
|
||||
$this->edge = $edge;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the radius of the circle in meters.
|
||||
*
|
||||
* @param float $radius radius of a circle in meters
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setRadius(float $radius): Circle
|
||||
{
|
||||
$this->edge = GeographicConverter::metersToLatLng($this->center, $radius, 45);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the circle on the map image.
|
||||
*
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
*
|
||||
* @param Image $image The map image (An instance of DantSu\PHPImageEditor\Image)
|
||||
* @param MapData $mapData Bounding box of the map
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function draw(Image $image, MapData $mapData): Circle
|
||||
{
|
||||
$center = $mapData->convertLatLngToPxPosition($this->center, $this->wrap);
|
||||
$edge = $mapData->convertLatLngToPxPosition($this->edge, $this->wrap);
|
||||
|
||||
$angleAndLenght = Geometry2D::getAngleAndLengthFromPoints($center->getX(), $center->getY(), $edge->getX(), $edge->getY());
|
||||
$length = \round($angleAndLenght['length'] + $this->strokeWeight / 2);
|
||||
|
||||
$dImage = Image::newCanvas($image->getWidth(), $image->getHeight());
|
||||
|
||||
if ($this->strokeWeight > 0) {
|
||||
$dImage->drawCircle($center->getX(), $center->getY(), $length * 2, $this->strokeColor);
|
||||
}
|
||||
|
||||
$dImage->drawCircle($center->getX(), $center->getY(), ($length - $this->strokeWeight) * 2, $this->fillColor);
|
||||
|
||||
$image->pasteOn($dImage, 0, 0);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get bounding box of the shape
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public function getBoundingBox(): array
|
||||
{
|
||||
$distance = GeographicConverter::latLngToMeters($this->center, $this->edge) * 1.4142;
|
||||
return [
|
||||
GeographicConverter::metersToLatLng($this->center, $distance, 315),
|
||||
GeographicConverter::metersToLatLng($this->center, $distance, 135)
|
||||
];
|
||||
}
|
||||
}
|
||||
61
src/StaticMap/src/Geometry2D.php
Normal file
61
src/StaticMap/src/Geometry2D.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace DantSu\PHPImageEditor;
|
||||
|
||||
class Geometry2D
|
||||
{
|
||||
public static function degrees0to360(float $angle): float
|
||||
{
|
||||
while ($angle < 0 || $angle >= 360) {
|
||||
if ($angle < 0) $angle += 360;
|
||||
elseif ($angle >= 360) $angle -= 360;
|
||||
}
|
||||
return $angle;
|
||||
}
|
||||
|
||||
public static function getDstXY(float $originX, float $originY, float $angle, float $length): array
|
||||
{
|
||||
$angle = 360 - $angle;
|
||||
return [
|
||||
'x' => $originX + \cos($angle * M_PI / 180) * $length,
|
||||
'y' => $originY + \sin($angle * M_PI / 180) * $length
|
||||
];
|
||||
}
|
||||
|
||||
public static function getDstXYRounded(float $originX, float $originY, float $angle, float $length): array
|
||||
{
|
||||
$xy = Geometry2D::getDstXY($originX, $originY, $angle, $length);
|
||||
return [
|
||||
'x' => \round($xy['x']),
|
||||
'y' => \round($xy['y'])
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAngleAndLengthFromPoints(float $originX, float $originY, float $dstX, float $dstY): array
|
||||
{
|
||||
$width = $dstX - $originX;
|
||||
$height = $dstY - $originY;
|
||||
$diameter = \sqrt(\pow($width, 2) + \pow($height, 2));
|
||||
|
||||
if($width == 0) {
|
||||
$angle = 90;
|
||||
} elseif ($height == 0) {
|
||||
$angle = 0;
|
||||
} else {
|
||||
$angle = \atan2(\abs($height), \abs($width)) * 180.0 / M_PI;
|
||||
}
|
||||
|
||||
if($width < 0 && $height < 0) {
|
||||
$angle += 180;
|
||||
} elseif ($width < 0) {
|
||||
$angle = 180 - $angle;
|
||||
} elseif ($height < 0) {
|
||||
$angle = 360 - $angle;
|
||||
}
|
||||
|
||||
return [
|
||||
'angle' => 360 - $angle,
|
||||
'length' => $diameter
|
||||
];
|
||||
}
|
||||
}
|
||||
1369
src/StaticMap/src/Image.php
Normal file
1369
src/StaticMap/src/Image.php
Normal file
File diff suppressed because it is too large
Load Diff
14
src/StaticMap/src/Interfaces/Draw.php
Normal file
14
src/StaticMap/src/Interfaces/Draw.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage\Interfaces;
|
||||
|
||||
|
||||
use Wavelog\StaticMapImage\MapData;
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
interface Draw
|
||||
{
|
||||
public function getBoundingBox(): array;
|
||||
|
||||
public function draw(Image $image, MapData $mapData);
|
||||
}
|
||||
54
src/StaticMap/src/LatLng.php
Normal file
54
src/StaticMap/src/LatLng.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\LatLng define latitude and longitude for map, lines, markers...
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class LatLng
|
||||
{
|
||||
/**
|
||||
* @var float Latitude
|
||||
*/
|
||||
private $lat = 0.0;
|
||||
/**
|
||||
* @var float Longitude
|
||||
*/
|
||||
private $lng = 0.0;
|
||||
|
||||
|
||||
/**
|
||||
* LatLng constructor.
|
||||
* @param float $lat Latitude
|
||||
* @param float $lng Longitude
|
||||
*/
|
||||
public function __construct(float $lat, float $lng)
|
||||
{
|
||||
$this->lat = $lat;
|
||||
$this->lng = $lng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latitude
|
||||
* @return float Latitude
|
||||
*/
|
||||
public function getLat(): float
|
||||
{
|
||||
return $this->lat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get longitude
|
||||
* @return float Longitude
|
||||
*/
|
||||
public function getLng(): float
|
||||
{
|
||||
return $this->lng;
|
||||
}
|
||||
}
|
||||
135
src/StaticMap/src/Line.php
Normal file
135
src/StaticMap/src/Line.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
require('./src/StaticMap/src/Interfaces/Draw.php');
|
||||
require('./src/StaticMap/src/Utils/GeographicConverter.php');
|
||||
use Wavelog\StaticMapImage\Interfaces\Draw;
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
use Wavelog\StaticMapImage\LatLng;
|
||||
use Wavelog\StaticMapImage\MapData;
|
||||
use Wavelog\StaticMapImage\Utils\GeographicConverter;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\Line draw line on the map.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class Line implements Draw
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $color = '000000';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $weight = 1;
|
||||
/**
|
||||
* @var LatLng[]
|
||||
*/
|
||||
private $points = [];
|
||||
|
||||
/**
|
||||
* @var bool wrap around the world or not
|
||||
*/
|
||||
private $wrap;
|
||||
|
||||
/**
|
||||
* Line constructor.
|
||||
* @param string $color Hexadecimal string color
|
||||
* @param int $weight pixel weight of the line
|
||||
*/
|
||||
public function __construct(string $color, int $weight, bool $wrap = false)
|
||||
{
|
||||
$this->color = \str_replace('#', '', $color);
|
||||
$this->weight = $weight;
|
||||
$this->wrap = $wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a latitude and longitude to the multi-points line
|
||||
* @param LatLng $latLng Latitude and longitude to add
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addPoint(LatLng $latLng): Line
|
||||
{
|
||||
$this->points[] = $latLng;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the line on the map image.
|
||||
*
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
*
|
||||
* @param Image $image The map image (An instance of DantSu\PHPImageEditor\Image)
|
||||
* @param MapData $mapData Bounding box of the map
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function draw(Image $image, MapData $mapData): Line
|
||||
{
|
||||
/**
|
||||
* @var $cPoints XY[]
|
||||
*/
|
||||
$cPoints = \array_map(
|
||||
function (LatLng $p) use ($mapData) {
|
||||
return $mapData->convertLatLngToPxPosition($p, !$this->wrap);
|
||||
},
|
||||
$this->points
|
||||
);
|
||||
|
||||
foreach ((array) $cPoints as $k => $point) {
|
||||
if (isset($cPoints[$k - 1])) {
|
||||
$image->drawLine($cPoints[$k - 1]->getX(), $cPoints[$k - 1]->getY(), $point->getX(), $point->getY(), $this->weight, $this->color);
|
||||
|
||||
// do the same left and right if $wrap is disabled. 'Lines' are special here
|
||||
if ($this->wrap) {
|
||||
$image->drawLine($cPoints[$k - 1]->getX() + $image->getWidth(), $cPoints[$k - 1]->getY(), $point->getX() + $image->getWidth(), $point->getY(), $this->weight, $this->color);
|
||||
$image->drawLine($cPoints[$k - 1]->getX() - $image->getWidth(), $cPoints[$k - 1]->getY(), $point->getX() - $image->getWidth(), $point->getY(), $this->weight, $this->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounding box of the shape
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public function getBoundingBox(): array
|
||||
{
|
||||
return MapData::getBoundingBoxFromPoints($this->points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Geodesic points between two coordinates
|
||||
*
|
||||
* @param LatLng $start
|
||||
* @param LatLng $end
|
||||
*
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public function geodesicPoints(LatLng $start, LatLng $end): array {
|
||||
$points = [$start];
|
||||
$totalDistance = GeographicConverter::latLngToMeters($start, $end);
|
||||
$distanceInterval = 10000;
|
||||
$currentDistance = 0;
|
||||
|
||||
while ($currentDistance + $distanceInterval < $totalDistance) {
|
||||
$currentDistance += $distanceInterval;
|
||||
|
||||
$lastPoint = end($points);
|
||||
$angle = GeographicConverter::getBearing($lastPoint, $end);
|
||||
|
||||
$nextPoint = GeographicConverter::metersToLatLng($lastPoint, $distanceInterval, $angle);
|
||||
$points[] = $nextPoint;
|
||||
}
|
||||
|
||||
$points[] = $end;
|
||||
return $points;
|
||||
}
|
||||
}
|
||||
361
src/StaticMap/src/MapData.php
Normal file
361
src/StaticMap/src/MapData.php
Normal file
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
|
||||
use Wavelog\StaticMapImage\Utils\GeographicConverter;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\MapData convert latitude and longitude to image pixel position.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class MapData {
|
||||
|
||||
/**
|
||||
* @var LatLng Center of the map
|
||||
*/
|
||||
private $centerMap;
|
||||
|
||||
/**
|
||||
* Convert longitude and zoom to horizontal OpenStreetMap tile number and pixel position.
|
||||
* @param float $lon Longitude
|
||||
* @param int $zoom Zoom
|
||||
* @param int $tileSize Tile size
|
||||
* @param bool wrap around the globe
|
||||
* @return int[] OpenStreetMap tile id and pixel position of the given longitude and zoom
|
||||
*/
|
||||
public function lngToXTile(float $lon, int $zoom, int $tileSize, bool $wrap = false): array {
|
||||
// Adjust longitude based on the map's center longitude
|
||||
$centerLon = $this->centerMap->getLng();
|
||||
$wrappedLon = fmod(($lon - $centerLon + 180), 360) - 180 + $centerLon;
|
||||
if ($wrap) {
|
||||
$x = ($wrappedLon + 180) / 360 * \pow(2, $zoom);
|
||||
} else {
|
||||
$x = ($lon + 180) / 360 * \pow(2, $zoom);
|
||||
}
|
||||
// Continue with the existing calculation
|
||||
$tile = \floor($x);
|
||||
return [
|
||||
'id' => $tile,
|
||||
'position' => \round($tileSize * ($x - $tile))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert latitude and zoom to vertical OpenStreetMap tile number and pixel position.
|
||||
* @param float $lat Latitude
|
||||
* @param int $zoom Zoom
|
||||
* @param int $tileSize Tile size
|
||||
* @return int[] OpenStreetMap tile id and pixel position of the given latitude and zoom
|
||||
*/
|
||||
public static function latToYTile(float $lat, int $zoom, int $tileSize): array {
|
||||
$y = (1 - \log(\tan(\deg2rad($lat)) + 1 / \cos(\deg2rad($lat))) / M_PI) / 2 * \pow(2, $zoom);
|
||||
$tile = \floor($y);
|
||||
return [
|
||||
'id' => $tile,
|
||||
'position' => \round($tileSize * ($y - $tile))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert horizontal OpenStreetMap tile number ad zoom to longitude.
|
||||
* @param int $id Horizontal OpenStreetMap tile id
|
||||
* @param int $position Horizontal pixel position on tile
|
||||
* @param int $zoom Zoom
|
||||
* @param int $tileSize Tile size
|
||||
* @return float Longitude of the given OpenStreetMap tile id and zoom
|
||||
*/
|
||||
public static function xTileToLng(int $id, int $position, int $zoom, int $tileSize): float {
|
||||
return ($id + $position / $tileSize) / \pow(2, $zoom) * 360 - 180;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert vertical OpenStreetMap tile number and zoom to latitude.
|
||||
* @param int $id Vertical OpenStreetMap tile id
|
||||
* @param int $position Vertical pixel position on tile
|
||||
* @param int $zoom Zoom
|
||||
* @param int $tileSize Tile size
|
||||
* @return float Latitude of the given OpenStreetMap tile id and zoom
|
||||
*/
|
||||
public static function yTileToLat(int $id, int $position, int $zoom, int $tileSize): float {
|
||||
return \rad2deg(\atan(\sinh(M_PI * (1 - 2 * ($id + $position / $tileSize) / \pow(2, $zoom)))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform array of LatLng to bounding box
|
||||
*
|
||||
* @param LatLng[] $points
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public static function getBoundingBoxFromPoints(array $points): array {
|
||||
$minLat = 360;
|
||||
$maxLat = -360;
|
||||
$minLng = 360;
|
||||
$maxLng = -360;
|
||||
foreach ($points as $point) {
|
||||
if ($point->getLat() < $minLat) {
|
||||
$minLat = $point->getLat();
|
||||
}
|
||||
if ($point->getLat() > $maxLat) {
|
||||
$maxLat = $point->getLat();
|
||||
}
|
||||
if ($point->getLng() < $minLng) {
|
||||
$minLng = $point->getLng();
|
||||
}
|
||||
if ($point->getLng() > $maxLng) {
|
||||
$maxLng = $point->getLng();
|
||||
}
|
||||
}
|
||||
return [new LatLng($maxLat, $minLng), new LatLng($minLat, $maxLng)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get center and zoom from two points.
|
||||
*
|
||||
* @param LatLng $topLeft
|
||||
* @param LatLng $bottomRight
|
||||
* @param int $padding
|
||||
* @param int $imageWidth
|
||||
* @param int $imageHeight
|
||||
* @param int $tileSize
|
||||
* @return array center : LatLng, zoom : int
|
||||
*/
|
||||
public static function getCenterAndZoomFromBoundingBox(LatLng $topLeft, LatLng $bottomRight, int $padding, int $imageWidth, int $imageHeight, int $tileSize): array {
|
||||
$zoom = 20;
|
||||
$padding *= 2;
|
||||
$topTilePos = MapData::latToYTile($topLeft->getLat(), $zoom, $tileSize);
|
||||
$bottomTilePos = MapData::latToYTile($bottomRight->getLat(), $zoom, $tileSize);
|
||||
$leftTilePos = MapData::lngToXTile($topLeft->getLng(), $zoom, $tileSize);
|
||||
$rightTilePos = MapData::lngToXTile($bottomRight->getLng(), $zoom, $tileSize);
|
||||
$pxZoneWidth = ($rightTilePos['id'] - $leftTilePos['id']) * $tileSize + $rightTilePos['position'] - $leftTilePos['position'];
|
||||
$pxZoneHeight = ($bottomTilePos['id'] - $topTilePos['id']) * $tileSize + $bottomTilePos['position'] - $topTilePos['position'];
|
||||
|
||||
return [
|
||||
'center' => GeographicConverter::getCenter($topLeft, $bottomRight),
|
||||
'zoom' => \intval(
|
||||
\floor(
|
||||
\log(
|
||||
\min(
|
||||
1,
|
||||
($imageHeight - $padding) / $pxZoneHeight,
|
||||
($imageWidth - $padding) / $pxZoneWidth
|
||||
) * \pow(2, $zoom)
|
||||
) / 0.69314
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int zoom
|
||||
*/
|
||||
private $zoom;
|
||||
/**
|
||||
* @var int tile size
|
||||
*/
|
||||
private $tileSize;
|
||||
/**
|
||||
* @var XY Width and height of the image in pixel
|
||||
*/
|
||||
private $outputSize;
|
||||
/**
|
||||
* @var XY top left tile numbers
|
||||
*/
|
||||
private $tileTopLeft;
|
||||
/**
|
||||
* @var XY bottom right tile numbers
|
||||
*/
|
||||
private $tileBottomRight;
|
||||
/**
|
||||
* @var XY left and top pixels to crop to fit final image size
|
||||
*/
|
||||
private $mapCropTopLeft;
|
||||
/**
|
||||
* @var XY bottom and right pixels to crop to fit final image size
|
||||
*/
|
||||
private $mapCropBottomRight;
|
||||
/**
|
||||
* @var LatLng Latitude and longitude of top left image
|
||||
*/
|
||||
private $latLngTopLeft;
|
||||
/**
|
||||
* @var LatLng Latitude and longitude of top right image
|
||||
*/
|
||||
private $latLngTopRight;
|
||||
/**
|
||||
* @var LatLng Latitude and longitude of bottom left image
|
||||
*/
|
||||
private $latLngBottomLeft;
|
||||
/**
|
||||
* @var LatLng Latitude and longitude of bottom right image
|
||||
*/
|
||||
private $latLngBottomRight;
|
||||
|
||||
|
||||
/**
|
||||
* @param LatLng $centerMap
|
||||
* @param int $zoom
|
||||
* @param XY $outputSize
|
||||
* @param int $tileSize
|
||||
*/
|
||||
public function __construct(LatLng $centerMap, int $zoom, XY $outputSize, int $tileSize) {
|
||||
$this->centerMap = $centerMap;
|
||||
$this->zoom = $zoom;
|
||||
$this->outputSize = $outputSize;
|
||||
$this->tileSize = $tileSize;
|
||||
|
||||
$x = static::lngToXTile($centerMap->getLng(), $zoom, $this->tileSize);
|
||||
$y = static::latToYTile($centerMap->getLat(), $zoom, $this->tileSize);
|
||||
|
||||
$startX = \floor($outputSize->getX() / 2 - $x['position']);
|
||||
$startY = \floor($outputSize->getY() / 2 - $y['position']);
|
||||
|
||||
|
||||
$rightSize = $outputSize->getX() - $startX;
|
||||
$bottomSize = $outputSize->getY() - $startY;
|
||||
|
||||
$this->mapCropTopLeft = new XY(
|
||||
$startX < 0 ? \abs($startX) : ($startX % $this->tileSize == 0 ? 0 : $this->tileSize - $startX % $this->tileSize),
|
||||
$startY < 0 ? \abs($startY) : ($startY % $this->tileSize == 0 ? 0 : $this->tileSize - $startY % $this->tileSize)
|
||||
);
|
||||
$this->mapCropBottomRight = new XY(
|
||||
($rightSize % $this->tileSize == 0 ? 0 : $this->tileSize - $rightSize % $this->tileSize),
|
||||
($bottomSize % $this->tileSize == 0 ? 0 : $this->tileSize - $bottomSize % $this->tileSize)
|
||||
);
|
||||
$this->tileTopLeft = new XY(
|
||||
$x['id'] - \ceil($startX / $this->tileSize),
|
||||
$y['id'] - \ceil($startY / $this->tileSize)
|
||||
);
|
||||
$this->tileBottomRight = new XY(
|
||||
$x['id'] - 1 + \ceil($rightSize / $this->tileSize),
|
||||
$y['id'] - 1 + \ceil($bottomSize / $this->tileSize)
|
||||
);
|
||||
|
||||
$this->latLngTopLeft = new LatLng(
|
||||
static::yTileToLat($this->tileTopLeft->getY(), $this->mapCropTopLeft->getY(), $zoom, $this->tileSize),
|
||||
static::xTileToLng($this->tileTopLeft->getX(), $this->mapCropTopLeft->getX(), $zoom, $this->tileSize)
|
||||
);
|
||||
$this->latLngTopRight = new LatLng(
|
||||
static::yTileToLat($this->tileTopLeft->getY(), $this->mapCropTopLeft->getY(), $zoom, $this->tileSize),
|
||||
static::xTileToLng($this->tileBottomRight->getX(), $this->tileSize - $this->mapCropBottomRight->getX(), $zoom, $this->tileSize)
|
||||
);
|
||||
$this->latLngBottomLeft = new LatLng(
|
||||
static::yTileToLat($this->tileBottomRight->getY(), $this->tileSize - $this->mapCropBottomRight->getY(), $zoom, $this->tileSize),
|
||||
static::xTileToLng($this->tileTopLeft->getX(), $this->mapCropTopLeft->getX(), $zoom, $this->tileSize)
|
||||
);
|
||||
$this->latLngBottomRight = new LatLng(
|
||||
static::yTileToLat($this->tileBottomRight->getY(), $this->tileSize - $this->mapCropBottomRight->getY(), $zoom, $this->tileSize),
|
||||
static::xTileToLng($this->tileBottomRight->getX(), $this->tileSize - $this->mapCropBottomRight->getX(), $zoom, $this->tileSize)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get latitude and longitude of top left image
|
||||
* @return LatLng Latitude and longitude of top left image
|
||||
*/
|
||||
public function getLatLngTopLeft(): LatLng {
|
||||
return $this->latLngTopLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latitude and longitude of top right image
|
||||
* @return LatLng Latitude and longitude of top right image
|
||||
*/
|
||||
public function getLatLngTopRight(): LatLng {
|
||||
return $this->latLngTopRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latitude and longitude of bottom left image
|
||||
* @return LatLng Latitude and longitude of bottom left image
|
||||
*/
|
||||
public function getLatLngBottomLeft(): LatLng {
|
||||
return $this->latLngBottomLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latitude and longitude of bottom right image
|
||||
* @return LatLng Latitude and longitude of bottom right image
|
||||
*/
|
||||
public function getLatLngBottomRight(): LatLng {
|
||||
return $this->latLngBottomRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get width and height of the image in pixel
|
||||
* @return XY Width and height of the image in pixel
|
||||
*/
|
||||
public function getOutputSize(): XY {
|
||||
return $this->outputSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the zoom
|
||||
* @return int zoom
|
||||
*/
|
||||
public function getZoom(): int {
|
||||
return $this->zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tile size
|
||||
* @return int tile size
|
||||
*/
|
||||
public function getTileSize(): int {
|
||||
return $this->tileSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get top left tile numbers
|
||||
* @return XY top left tile numbers
|
||||
*/
|
||||
public function getTileTopLeft(): XY {
|
||||
return $this->tileTopLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bottom right tile numbers
|
||||
* @return XY bottom right tile numbers
|
||||
*/
|
||||
public function getTileBottomRight(): XY {
|
||||
return $this->tileBottomRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top left crop pixels
|
||||
* @return XY top left crop pixels
|
||||
*/
|
||||
public function getMapCropTopLeft(): XY {
|
||||
return $this->mapCropTopLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bottom right crop pixels
|
||||
* @return XY bottom right crop pixels
|
||||
*/
|
||||
public function getMapCropBottomRight(): XY {
|
||||
return $this->mapCropBottomRight;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a latitude and longitude to a XY pixel position in the image
|
||||
* @param LatLng $latLng Latitude and longitude to be converted
|
||||
* @return XY Pixel position of latitude and longitude in the image
|
||||
*/
|
||||
public function convertLatLngToPxPosition(LatLng $latLng, $wrap = false): XY {
|
||||
$x = static::lngToXTile($latLng->getLng(), $this->zoom, $this->tileSize, $wrap); // we only need to wrap X
|
||||
$y = static::latToYTile($latLng->getLat(), $this->zoom, $this->tileSize);
|
||||
|
||||
return new XY(
|
||||
(int)(($x['id'] - $this->tileTopLeft->getX()) * $this->tileSize - $this->mapCropTopLeft->getX() + $x['position']),
|
||||
(int)(($y['id'] - $this->tileTopLeft->getY()) * $this->tileSize - $this->mapCropTopLeft->getY() + $y['position'])
|
||||
);
|
||||
}
|
||||
}
|
||||
145
src/StaticMap/src/Markers.php
Normal file
145
src/StaticMap/src/Markers.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\Markers display markers on the map.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class Markers {
|
||||
const ANCHOR_LEFT = 'left';
|
||||
const ANCHOR_CENTER = 'center';
|
||||
const ANCHOR_RIGHT = 'right';
|
||||
const ANCHOR_TOP = 'top';
|
||||
const ANCHOR_MIDDLE = 'middle';
|
||||
const ANCHOR_BOTTOM = 'bottom';
|
||||
|
||||
/**
|
||||
* @var Image Image of the marker
|
||||
*/
|
||||
private $image;
|
||||
/**
|
||||
* @var bool Wrap around the globe or not
|
||||
*/
|
||||
private $wrap;
|
||||
/**
|
||||
* @var string|int Horizontal anchor of the marker image
|
||||
*/
|
||||
private $horizontalAnchor = Markers::ANCHOR_CENTER;
|
||||
/**
|
||||
* @var string|int Vertical anchor of the marker image
|
||||
*/
|
||||
private $verticalAnchor = Markers::ANCHOR_MIDDLE;
|
||||
/**
|
||||
* @var LatLng[] Latitudes and longitudes of the markers
|
||||
*/
|
||||
private $coordinates = [];
|
||||
|
||||
public function __construct($pathImage, $wrap = false) {
|
||||
$this->wrap = $wrap;
|
||||
$this->image = Image::fromPath($pathImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a marker on the map.
|
||||
* @param LatLng $coordinate Latitude and longitude of the marker
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addMarker(LatLng $coordinate): Markers {
|
||||
$this->coordinates[] = $coordinate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the marker image.
|
||||
*
|
||||
* @param int $width Width of the marker
|
||||
* @param int $height Height of the marker
|
||||
* @return $this Fluent interface
|
||||
*
|
||||
*/
|
||||
|
||||
public function resizeMarker(int $width, int $height): Markers {
|
||||
$this->image = $this->image->resize($width, $height);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the anchor point of the image marker.
|
||||
* @param int|string $horizontalAnchor Horizontal anchor in pixel or you can use `Markers::ANCHOR_LEFT`, `Markers::ANCHOR_CENTER`, `Markers::ANCHOR_RIGHT`
|
||||
* @param int|string $verticalAnchor Vertical anchor in pixel or you can use `Markers::ANCHOR_TOP`, `Markers::ANCHOR_MIDDLE`, `Markers::ANCHOR_BOTTOM`
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setAnchor($horizontalAnchor, $verticalAnchor): Markers {
|
||||
$this->horizontalAnchor = $horizontalAnchor;
|
||||
$this->verticalAnchor = $verticalAnchor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw markers on the image map.
|
||||
*
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
* @param Image $image The map image (An instance of DantSu\PHPImageEditor\Image)
|
||||
* @param MapData $mapData Bounding box of the map
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function draw(Image $image, MapData $mapData): Markers {
|
||||
$imageMarginLeft = $this->horizontalAnchor;
|
||||
$offsetX = 1;
|
||||
$offsetY = 6;
|
||||
|
||||
switch ($imageMarginLeft) {
|
||||
case Markers::ANCHOR_LEFT:
|
||||
$imageMarginLeft = 0;
|
||||
break;
|
||||
case Markers::ANCHOR_CENTER:
|
||||
$imageMarginLeft = $this->image->getWidth() / 2;
|
||||
break;
|
||||
case Markers::ANCHOR_RIGHT:
|
||||
$imageMarginLeft = $this->image->getWidth();
|
||||
break;
|
||||
}
|
||||
|
||||
$imageMarginTop = $this->verticalAnchor;
|
||||
switch ($imageMarginTop) {
|
||||
case Markers::ANCHOR_TOP:
|
||||
$imageMarginTop = 0;
|
||||
break;
|
||||
case Markers::ANCHOR_MIDDLE:
|
||||
$imageMarginTop = $this->image->getHeight() / 2;
|
||||
break;
|
||||
case Markers::ANCHOR_BOTTOM:
|
||||
$imageMarginTop = $this->image->getHeight();
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($this->coordinates as $coordinate) {
|
||||
$xy = $mapData->convertLatLngToPxPosition($coordinate);
|
||||
$image->pasteOn($this->image, $xy->getX() + $offsetX - $imageMarginLeft, $xy->getY() + $offsetY - $imageMarginTop);
|
||||
|
||||
// pasteOn again for wrapping left and right
|
||||
if ($this->wrap) {
|
||||
$image->pasteOn($this->image, $xy->getX() + $offsetX - $imageMarginLeft + $image->getWidth(), $xy->getY() + $offsetY - $imageMarginTop);
|
||||
$image->pasteOn($this->image, $xy->getX() + $offsetX - $imageMarginLeft - $image->getWidth(), $xy->getY() + $offsetY - $imageMarginTop);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounding box of markers
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public function getBoundingBox(): array {
|
||||
return MapData::getBoundingBoxFromPoints($this->coordinates);
|
||||
}
|
||||
}
|
||||
308
src/StaticMap/src/OpenStreetMap.php
Normal file
308
src/StaticMap/src/OpenStreetMap.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
use Wavelog\StaticMapImage\Interfaces\Draw;
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\OpenStreetMap is a PHP library created for easily get static image from OpenStreetMap with markers, lines, polygons and circles.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class OpenStreetMap
|
||||
{
|
||||
/**
|
||||
* Create new instance of OpenStreetMap.
|
||||
* @param LatLng $centerMap Latitude and longitude of the map center
|
||||
* @param int $zoom Zoom
|
||||
* @param int $imageWidth Width of the generated map image
|
||||
* @param int $imageHeight Height of the generated map image
|
||||
* @param TileLayer $tileLayer Tile server configuration, defaults to OpenStreetMaps tile server
|
||||
* @param int $tileSize Tile size in pixels
|
||||
*/
|
||||
public static function createFromLatLngZoom(LatLng $centerMap, int $zoom, int $imageWidth, int $imageHeight, TileLayer $tileLayer = null, int $tileSize = 256): OpenStreetMap
|
||||
{
|
||||
return new OpenStreetMap($centerMap, $zoom, $imageWidth, $imageHeight, $tileLayer, $tileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of OpenStreetMap.
|
||||
* @param LatLng $topLeft Latitude and longitude of the map top left
|
||||
* @param LatLng $bottomRight Latitude and longitude of the map bottom right
|
||||
* @param int $padding Padding to add before top left and after bottom right position.
|
||||
* @param int $imageWidth Width of the generated map image
|
||||
* @param int $imageHeight Height of the generated map image
|
||||
* @param TileLayer $tileLayer Tile server configuration, defaults to OpenStreetMaps tile server
|
||||
* @param int $tileSize Tile size in pixels
|
||||
* @return OpenStreetMap
|
||||
*/
|
||||
public static function createFromBoundingBox(LatLng $topLeft, LatLng $bottomRight, int $padding, int $imageWidth, int $imageHeight, TileLayer $tileLayer = null, int $tileSize = 256): OpenStreetMap
|
||||
{
|
||||
if ($tileLayer === null) {
|
||||
$tileLayer = TileLayer::defaultTileLayer();
|
||||
}
|
||||
|
||||
$latLngZoom = MapData::getCenterAndZoomFromBoundingBox($topLeft, $bottomRight, $padding, $imageWidth, $imageHeight, $tileSize);
|
||||
return new OpenStreetMap($latLngZoom['center'], $latLngZoom['zoom'], $imageWidth, $imageHeight, $tileLayer, $tileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var MapData Data about the generated map (bounding box, size, OSM tile ids...)
|
||||
*/
|
||||
protected $mapData;
|
||||
/**
|
||||
* @var TileLayer[] Array of TileLayer instances
|
||||
*/
|
||||
protected $layers = [];
|
||||
/**
|
||||
* @var Markers[] Array of Markers instances
|
||||
*/
|
||||
protected $markers = [];
|
||||
/**
|
||||
* @var Draw[] Array of Line instances
|
||||
*/
|
||||
protected $draws = [];
|
||||
|
||||
/**
|
||||
* OpenStreetMap constructor.
|
||||
* @param LatLng $centerMap Latitude and longitude of the map center
|
||||
* @param int $zoom Zoom
|
||||
* @param int $imageWidth Width of the generated map image
|
||||
* @param int $imageHeight Height of the generated map image
|
||||
* @param TileLayer $tileLayer Tile server configuration, defaults to OpenStreetMaps tile server
|
||||
* @param int $tileSize Tile size in pixels
|
||||
*/
|
||||
public function __construct(LatLng $centerMap, int $zoom, int $imageWidth, int $imageHeight, TileLayer $tileLayer = null, int $tileSize = 256)
|
||||
{
|
||||
if ($tileLayer === null) {
|
||||
$tileLayer = TileLayer::defaultTileLayer();
|
||||
}
|
||||
|
||||
$this->mapData = new MapData($centerMap, $tileLayer->checkZoom($zoom), new XY($imageWidth, $imageHeight), $tileSize);
|
||||
$this->layers = [$tileLayer];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tile layer to the map
|
||||
* @param TileLayer $layer An instance of TileLayer
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addLayer(TileLayer $layer)
|
||||
{
|
||||
$this->layers[] = $layer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add markers on the map
|
||||
* @param Markers $markers An instance of Markers
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addMarkers(Markers $markers)
|
||||
{
|
||||
$this->markers[] = $markers;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a line on the map
|
||||
* @param Draw $draw An instance of Line
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addDraw(Draw $draw)
|
||||
{
|
||||
$this->draws[] = $draw;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit map to draws.
|
||||
*
|
||||
* @param int $padding Padding in pixel
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function fitToDraws(int $padding = 0)
|
||||
{
|
||||
$points = [];
|
||||
foreach ($this->draws as $draw) {
|
||||
$points = \array_merge($points, $draw->getBoundingBox());
|
||||
}
|
||||
return $this->fitToPoints($points, $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit map to markers.
|
||||
*
|
||||
* @param int $padding Padding in pixel
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function fitToMarkers(int $padding = 0)
|
||||
{
|
||||
$points = [];
|
||||
foreach ($this->markers as $markers) {
|
||||
$points = \array_merge($points, $markers->getBoundingBox());
|
||||
}
|
||||
return $this->fitToPoints($points, $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit map to draws and markers.
|
||||
*
|
||||
* @param int $padding Padding in pixel
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function fitToMarkersAndDraws(int $padding = 0)
|
||||
{
|
||||
$points = [];
|
||||
foreach ($this->draws as $draw) {
|
||||
$points = \array_merge($points, $draw->getBoundingBox());
|
||||
}
|
||||
foreach ($this->markers as $markers) {
|
||||
$points = \array_merge($points, $markers->getBoundingBox());
|
||||
}
|
||||
return $this->fitToPoints($points, $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit map to an array of points.
|
||||
*
|
||||
* @param LatLng[] $points LatLng points
|
||||
* @param int $padding Padding in pixel
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function fitToPoints(array $points, int $padding = 0)
|
||||
{
|
||||
$outputSize = $this->mapData->getOutputSize();
|
||||
$tileSize = $this->mapData->getTileSize();
|
||||
$boundingBox = MapData::getBoundingBoxFromPoints($points);
|
||||
$latLngZoom = MapData::getCenterAndZoomFromBoundingBox($boundingBox[0], $boundingBox[1], $padding, $outputSize->getX(), $outputSize->getY(), $tileSize);
|
||||
$this->mapData = new MapData($latLngZoom['center'], $this->layers[0]->checkZoom($latLngZoom['zoom']), $outputSize, $tileSize);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data about the generated map (bounding box, size, OSM tile ids...)
|
||||
* @see https://github.com/DantSu/php-osm-static-api/blob/master/docs/classes/DantSu/OpenStreetMapStaticAPI/MapData.md See more about MapData
|
||||
* @return MapData data about the generated map (bounding box, size, OSM tile ids...)
|
||||
*/
|
||||
public function getMapData(): MapData
|
||||
{
|
||||
return $this->mapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only the map image.
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
* @return Image An instance of DantSu\PHPImageEditor\Image
|
||||
*/
|
||||
protected function getMapImage($centerMap): Image
|
||||
{
|
||||
$imgSize = $this->mapData->getOutputSize();
|
||||
$startX = $this->mapData->getMapCropTopLeft()->getX() * -1;
|
||||
$startY = $this->mapData->getMapCropTopLeft()->getY() * -1;
|
||||
|
||||
$image = Image::newCanvas($imgSize->getX(), $imgSize->getY());
|
||||
$tileSize = $this->mapData->getTileSize();
|
||||
|
||||
foreach ($this->layers as $tileLayer) {
|
||||
$yTile = $this->mapData->getTileTopLeft()->getY();
|
||||
for ($y = $startY; $y < $imgSize->getY(); $y += $tileSize) {
|
||||
$xTile = $this->mapData->getTileTopLeft()->getX();
|
||||
for ($x = $startX; $x < $imgSize->getX(); $x += $tileSize) {
|
||||
$xTileWrapped = $this->wrapLongitudeTile($xTile);
|
||||
$image->pasteOn(
|
||||
$tileLayer->getTile($xTileWrapped, $yTile, $this->mapData->getZoom(), $tileSize, $centerMap),
|
||||
$x,
|
||||
$y
|
||||
);
|
||||
++$xTile;
|
||||
}
|
||||
++$yTile;
|
||||
}
|
||||
}
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the longitude tile.
|
||||
* @param int $xTile The x tile
|
||||
* @return int The wrapped x tile
|
||||
*/
|
||||
protected function wrapLongitudeTile($xTile)
|
||||
{
|
||||
$maxTile = pow(2, $this->mapData->getZoom());
|
||||
return ($xTile % $maxTile + $maxTile) % $maxTile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw OpenStreetMap attribution at the right bottom of the image
|
||||
* @param Image $image The image of the map
|
||||
* @return Image The image of the map with attribution
|
||||
*/
|
||||
protected function drawAttribution(Image $image): Image
|
||||
{
|
||||
$margin = 5;
|
||||
|
||||
// Anonymous function to render the attribution text
|
||||
$attribution = function (Image $image, $margin): array {
|
||||
// Collect attribution text without HTML tags (strip tags from each layer)
|
||||
$attributionText = \implode(
|
||||
' - ',
|
||||
\array_map(function ($layer) {
|
||||
// Strip HTML tags (like <a>) from the attribution text
|
||||
return strip_tags($layer->getAttributionText());
|
||||
}, $this->layers)
|
||||
);
|
||||
|
||||
// Draw the plain text onto the image and get its bounding box
|
||||
return $image->writeTextAndGetBoundingBox(
|
||||
$attributionText,
|
||||
__DIR__ . '/resources/font.ttf', // Font path
|
||||
10, // Font size
|
||||
'0078A8', // Text color
|
||||
$margin, // X margin
|
||||
$margin, // Y margin
|
||||
Image::ALIGN_LEFT, // Horizontal alignment
|
||||
Image::ALIGN_TOP // Vertical alignment
|
||||
);
|
||||
};
|
||||
|
||||
// Calculate bounding box for the attribution text
|
||||
$bbox = $attribution(Image::newCanvas(1, 1), $margin);
|
||||
|
||||
// Create a new canvas for the attribution background
|
||||
$imageAttribution = Image::newCanvas($bbox['bottom-right']['x'] + $margin, $bbox['bottom-right']['y'] + $margin);
|
||||
// Draw a semi-transparent rectangle for the attribution background
|
||||
$imageAttribution->drawRectangle(0, 0, $imageAttribution->getWidth(), $imageAttribution->getHeight(), 'FFFFFF33');
|
||||
// Write the attribution text on the new canvas
|
||||
$attribution($imageAttribution, $margin);
|
||||
|
||||
// Paste the attribution canvas onto the main image (bottom-right corner)
|
||||
return $image->pasteOn($imageAttribution, Image::ALIGN_RIGHT, Image::ALIGN_BOTTOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map image with markers and lines.
|
||||
*
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
* @return Image An instance of DantSu\PHPImageEditor\Image
|
||||
*/
|
||||
public function getImage($centerMap = '00'): Image
|
||||
{
|
||||
$image = $this->getMapImage($centerMap);
|
||||
|
||||
foreach ($this->draws as $line) {
|
||||
$line->draw($image, $this->mapData);
|
||||
}
|
||||
|
||||
foreach ($this->markers as $markers) {
|
||||
$markers->draw($image, $this->mapData);
|
||||
}
|
||||
|
||||
return $this->drawAttribution($image);
|
||||
}
|
||||
}
|
||||
171
src/StaticMap/src/Polygon.php
Normal file
171
src/StaticMap/src/Polygon.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
|
||||
use Wavelog\StaticMapImage\Interfaces\Draw;
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\Polygon draw polygon on the map.
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class Polygon implements Draw
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $strokeColor = '000000';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $strokeWeight = 1;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fillColor = '000000';
|
||||
/**
|
||||
* @var LatLng[]
|
||||
*/
|
||||
private $points = [];
|
||||
|
||||
/**
|
||||
* @var bool wrap around the world or not
|
||||
*/
|
||||
private $wrap;
|
||||
|
||||
/**
|
||||
* Polygon constructor.
|
||||
* @param string $strokeColor Hexadecimal string color
|
||||
* @param int $strokeWeight pixel weight of the line
|
||||
* @param string $fillColor Hexadecimal string color
|
||||
*/
|
||||
public function __construct(string $strokeColor, int $strokeWeight, string $fillColor, bool $wrap = false)
|
||||
{
|
||||
$this->strokeColor = \str_replace('#', '', $strokeColor);
|
||||
$this->strokeWeight = $strokeWeight > 0 ? $strokeWeight : 0;
|
||||
$this->fillColor = \str_replace('#', '', $fillColor);
|
||||
$this->wrap = $wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a latitude and longitude to the polygon
|
||||
* @param LatLng $latLng Latitude and longitude to add
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function addPoint(LatLng $latLng): Polygon
|
||||
{
|
||||
$this->points[] = $latLng;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Points of the polygon
|
||||
* @return array of points
|
||||
*/
|
||||
|
||||
public function getPoints(): array
|
||||
{
|
||||
return $this->points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the polygon on the map image.
|
||||
*
|
||||
* @see https://github.com/DantSu/php-image-editor See more about DantSu\PHPImageEditor\Image
|
||||
*
|
||||
* @param Image $image The map image (An instance of DantSu\PHPImageEditor\Image)
|
||||
* @param MapData $mapData Bounding box of the map
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function draw(Image $image, MapData $mapData): Polygon
|
||||
{
|
||||
/**
|
||||
* @var $cPoints XY[]
|
||||
*/
|
||||
$cPoints = \array_map(
|
||||
function (LatLng $p) use ($mapData) {
|
||||
return $mapData->convertLatLngToPxPosition($p);
|
||||
},
|
||||
$this->points
|
||||
);
|
||||
|
||||
// Funktion zum Zeichnen des Polygons, um Wiederholungen zu reduzieren
|
||||
$drawPolygon = function (array $points) use ($image) {
|
||||
$image->pasteOn(
|
||||
Image::newCanvas($image->getWidth(), $image->getHeight())
|
||||
->drawPolygon(
|
||||
\array_reduce(
|
||||
$points,
|
||||
function (array $acc, XY $p) {
|
||||
$acc[] = $p->getX();
|
||||
$acc[] = $p->getY();
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
),
|
||||
$this->fillColor
|
||||
),
|
||||
0,
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
// Zeichne das ursprüngliche Polygon
|
||||
$drawPolygon($cPoints);
|
||||
|
||||
if ($this->wrap) {
|
||||
// Zeichne das Polygon links und rechts des Bildes
|
||||
$width = $image->getWidth();
|
||||
$shiftedLeft = \array_map(function (XY $p) use ($width) {
|
||||
return new XY($p->getX() - $width, $p->getY());
|
||||
}, $cPoints);
|
||||
|
||||
$shiftedRight = \array_map(function (XY $p) use ($width) {
|
||||
return new XY($p->getX() + $width, $p->getY());
|
||||
}, $cPoints);
|
||||
|
||||
$drawPolygon($shiftedLeft);
|
||||
$drawPolygon($shiftedRight);
|
||||
}
|
||||
|
||||
if ($this->strokeWeight > 0) {
|
||||
// Zeichne die Linien zwischen den Punkten für das Hauptpolygon
|
||||
foreach ($cPoints as $k => $point) {
|
||||
$pK = $k - 1;
|
||||
if (!isset($cPoints[$pK])) {
|
||||
$pK = \count($cPoints) - 1;
|
||||
}
|
||||
$image->drawLine($cPoints[$pK]->getX(), $cPoints[$pK]->getY(), $point->getX(), $point->getY(), $this->strokeWeight, $this->strokeColor);
|
||||
}
|
||||
|
||||
// Zeichne die Linien für die linken und rechten Kopien des Polygons, falls Wrap aktiv ist
|
||||
if ($this->wrap) {
|
||||
foreach ([$shiftedLeft, $shiftedRight] as $shiftedPoints) {
|
||||
foreach ($shiftedPoints as $k => $point) {
|
||||
$pK = $k - 1;
|
||||
if (!isset($shiftedPoints[$pK])) {
|
||||
$pK = \count($shiftedPoints) - 1;
|
||||
}
|
||||
$image->drawLine($shiftedPoints[$pK]->getX(), $shiftedPoints[$pK]->getY(), $point->getX(), $point->getY(), $this->strokeWeight, $this->strokeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounding box of the shape
|
||||
* @return LatLng[]
|
||||
*/
|
||||
public function getBoundingBox(): array
|
||||
{
|
||||
return MapData::getBoundingBoxFromPoints($this->points);
|
||||
}
|
||||
}
|
||||
224
src/StaticMap/src/TileLayer.php
Normal file
224
src/StaticMap/src/TileLayer.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
use DantSu\PHPImageEditor\Image;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\TileLayer define tile server url and related configuration
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Stephan Strate <hello@stephan.codes>
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class TileLayer {
|
||||
|
||||
/**
|
||||
* Default tile server. OpenStreetMaps with related attribution text
|
||||
* @return TileLayer default tile server
|
||||
*/
|
||||
public static function defaultTileLayer(): TileLayer {
|
||||
$CI = &get_instance();
|
||||
$CI->load->model('themes_model');
|
||||
$r = $CI->themes_model->get_theme_mode($CI->optionslib->get_option('option_theme'));
|
||||
if ($r == 'dark') {
|
||||
$server = 'https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png';
|
||||
} else {
|
||||
$server = $CI->optionslib->get_option('option_map_tile_server');
|
||||
}
|
||||
$attribution = $CI->optionslib->get_option('option_map_tile_server_copyright');
|
||||
return new TileLayer($server, $attribution, $r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string Tile server url, defaults to OpenStreetMap tile server
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var string Theme mode, defined light or dark mode
|
||||
*/
|
||||
protected $thememode;
|
||||
|
||||
/**
|
||||
* @var string Tile server attribution according to license
|
||||
*/
|
||||
protected $attributionText;
|
||||
|
||||
/**
|
||||
* @var string[] Tile server subdomains
|
||||
*/
|
||||
protected $subdomains;
|
||||
|
||||
/**
|
||||
* @var float Opacity
|
||||
*/
|
||||
protected $opacity = 1;
|
||||
|
||||
/*
|
||||
* @var int Max zoom value
|
||||
*/
|
||||
protected $maxZoom = 20;
|
||||
|
||||
/*
|
||||
* @var int Min zoom value
|
||||
*/
|
||||
protected $minZoom = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @array $curlOptions Array of curl options
|
||||
*/
|
||||
protected $curlOptions = [];
|
||||
|
||||
/**
|
||||
* @bool $failCurlOnError If true, curl will throw an exception on error.
|
||||
*/
|
||||
protected $failCurlOnError = false;
|
||||
|
||||
/**
|
||||
* TileLayer constructor
|
||||
* @param string $url tile server url with placeholders (`x`, `y`, `z`, `r`, `s`)
|
||||
* @param string $attributionText tile server attribution text
|
||||
* @param string $subdomains tile server subdomains
|
||||
* @param array $curlOptions Array of curl options
|
||||
* @param bool $failCurlOnError If true, curl will throw an exception on error.
|
||||
*/
|
||||
public function __construct(string $url, string $attributionText, string $thememode, string $subdomains = 'abc', array $curlOptions = [], bool $failCurlOnError = false) {
|
||||
$this->url = $url;
|
||||
$this->thememode = $thememode;
|
||||
$this->attributionText = $attributionText;
|
||||
$this->subdomains = \str_split($subdomains);
|
||||
$this->curlOptions = $curlOptions;
|
||||
$this->failCurlOnError = $failCurlOnError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set opacity of the layer
|
||||
* @param float $opacity Opacity value (0 to 1)
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setOpacity(float $opacity) {
|
||||
$this->opacity = $opacity;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a max zoom value
|
||||
* @param int $maxZoom
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setMaxZoom(int $maxZoom) {
|
||||
$this->maxZoom = $maxZoom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max zoom value
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxZoom(): int {
|
||||
return $this->maxZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a min zoom value
|
||||
* @param int $minZoom
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setMinZoom(int $minZoom) {
|
||||
$this->minZoom = $minZoom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get min zoom value
|
||||
* @return int
|
||||
*/
|
||||
public function getMinZoom(): int {
|
||||
return $this->minZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if zoom value is between min zoom and max zoom
|
||||
* @param int $zoom Zoom value to be checked
|
||||
* @return int
|
||||
*/
|
||||
public function checkZoom(int $zoom): int {
|
||||
return \min(\max($zoom, $this->minZoom), $this->maxZoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tile url for coordinates and zoom level
|
||||
* @param int $x x coordinate
|
||||
* @param int $y y coordinate
|
||||
* @param int $z zoom level
|
||||
* @return string tile url
|
||||
*/
|
||||
public function getTileUrl(int $x, int $y, int $z): string {
|
||||
return \str_replace(
|
||||
['{r}', '{s}', '{x}', '{y}', '{z}'],
|
||||
['', $this->getSubdomain($x, $y), $x, $y, $z],
|
||||
$this->url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select subdomain of tile server to prevent rate limiting on remote server
|
||||
* @param int $x x coordinate
|
||||
* @param int $y y coordinate
|
||||
* @return string selected subdomain
|
||||
* @see https://github.com/Leaflet/Leaflet/blob/main/src/layer/tile/TileLayer.js#L233 Leaflet implementation
|
||||
*/
|
||||
protected function getSubdomain(int $x, int $y): string {
|
||||
return $this->subdomains[\abs($x + $y) % \sizeof($this->subdomains)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribution text
|
||||
* @return string Attribution text
|
||||
*/
|
||||
public function getAttributionText(): string {
|
||||
return $this->attributionText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image tile
|
||||
* @param float $x
|
||||
* @param float $y
|
||||
* @param int $z
|
||||
* @param int $tileSize
|
||||
* @param string $centerMap
|
||||
* @return Image Image instance containing the tile
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getTile(float $x, float $y, int $z, int $tileSize, string $centerMap): Image {
|
||||
$CI = &get_instance();
|
||||
$namehash = substr(md5($x . $y . $z . $centerMap . $this->thememode), 0, 16);
|
||||
$cacheKey = $namehash . ".png";
|
||||
$cacheConfig = $CI->config->item('cache_path') == '' ? APPPATH . 'cache/' : $CI->config->item('cache_path');
|
||||
$cacheDir = $cacheConfig . "tilecache/" . $z . "/" . $y . "/" . $x . "/";
|
||||
$cachePath = $cacheDir . $cacheKey;
|
||||
|
||||
if (!is_dir($cacheDir)) {
|
||||
mkdir($cacheDir, 0755, true);
|
||||
}
|
||||
|
||||
if (file_exists($cachePath)) {
|
||||
$tile = Image::fromPath($cachePath);
|
||||
} else {
|
||||
$tile = Image::fromCurl($this->getTileUrl($x, $y, $z), $this->curlOptions, $this->failCurlOnError);
|
||||
$tile->savePNG($cachePath);
|
||||
}
|
||||
if ($this->opacity == 0) {
|
||||
return Image::newCanvas($tileSize, $tileSize);
|
||||
}
|
||||
|
||||
if ($this->opacity > 0 && $this->opacity < 1) {
|
||||
$tile->setOpacity($this->opacity);
|
||||
}
|
||||
|
||||
return $tile;
|
||||
}
|
||||
}
|
||||
119
src/StaticMap/src/Utils/GeographicConverter.php
Normal file
119
src/StaticMap/src/Utils/GeographicConverter.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage\Utils;
|
||||
|
||||
|
||||
use Wavelog\StaticMapImage\LatLng;
|
||||
|
||||
class GeographicConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* Calculate the earth radius at the given latitude
|
||||
*
|
||||
* @param float $lat
|
||||
* @return float
|
||||
*/
|
||||
public static function earthRadiusAtLatitude(float $lat): float
|
||||
{
|
||||
$equatorial = 6378137.0;
|
||||
$pole = 6356752.3;
|
||||
$lat = \deg2rad($lat);
|
||||
|
||||
return \sqrt(
|
||||
(\pow(\pow($equatorial, 2) * \cos($lat), 2) + \pow(\pow($pole, 2) * \sin($lat), 2)) /
|
||||
(\pow($equatorial * \cos($lat), 2) + \pow($pole * \sin($lat), 2))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert distance and angle from a point to latitude and longitude
|
||||
* 0 : top, 90 : right; 180 : bottom, 270 : left
|
||||
*
|
||||
* @param LatLng $from Starting coordinate
|
||||
* @param float $distance Distance in meters
|
||||
* @param float $angle Angle in degrees
|
||||
* @return LatLng
|
||||
*/
|
||||
public static function metersToLatLng(LatLng $from, float $distance, float $angle): LatLng
|
||||
{
|
||||
$earthRadius = self::earthRadiusAtLatitude($from->getLat());
|
||||
$lat = \deg2rad($from->getLat());
|
||||
$lng = \deg2rad($from->getLng());
|
||||
$angle = \deg2rad($angle);
|
||||
|
||||
return new LatLng(
|
||||
\rad2deg(
|
||||
\asin(
|
||||
\sin($lat) * \cos($distance / $earthRadius) +
|
||||
\cos($lat) * \sin($distance / $earthRadius) * \cos($angle)
|
||||
)
|
||||
),
|
||||
\rad2deg(
|
||||
$lng + \atan2(
|
||||
\sin($angle) * \sin($distance / $earthRadius) * \cos($lat),
|
||||
\cos($distance / $earthRadius) - \sin($lat) * \sin($lat)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distance in meters between two points.
|
||||
*
|
||||
* @param LatLng $from Starting coordinate
|
||||
* @param LatLng $end Ending coordinate
|
||||
* @return float
|
||||
*/
|
||||
public static function latLngToMeters(LatLng $from, LatLng $end): float
|
||||
{
|
||||
$earthRadius = self::earthRadiusAtLatitude($from->getLat());
|
||||
$lat1 = \deg2rad($from->getLat());
|
||||
$lat2 = \deg2rad($end->getLat());
|
||||
$lat = \deg2rad($end->getLat() - $from->getLat());
|
||||
$lng = \deg2rad($end->getLng() - $from->getLng());
|
||||
|
||||
$a = \pow(\sin($lat / 2), 2) + \cos($lat1) * \cos($lat2) * \pow(\sin($lng / 2), 2);
|
||||
return \abs($earthRadius * 2 * \atan2(\sqrt($a), \sqrt(1 - $a)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get center between two coordinates.
|
||||
* @param LatLng $point1 Vertical OpenStreetMap tile id
|
||||
* @param LatLng $point2 Vertical pixel position on tile
|
||||
* @return LatLng midpoint between the given coordinates
|
||||
*/
|
||||
public static function getCenter(LatLng $point1, LatLng $point2): LatLng
|
||||
{
|
||||
//return new LatLng(($point1->getLat() + $point2->getLat()) / 2, ($point1->getLng() + $point2->getLng()) / 2);
|
||||
$dLng = \deg2rad($point2->getLng() - $point1->getLng());
|
||||
$lat1 = \deg2rad($point1->getLat());
|
||||
$lat2 = \deg2rad($point2->getLat());
|
||||
$lng1 = \deg2rad($point1->getLng());
|
||||
$bx = \cos($lat2) * \cos($dLng);
|
||||
$by = \cos($lat2) * \sin($dLng);
|
||||
return new LatLng(
|
||||
\rad2deg(\atan2(\sin($lat1) + \sin($lat2), \sqrt(\pow(\cos($lat1) + $bx, 2) + \pow($by, 2)))),
|
||||
\rad2deg($lng1 + \atan2($by, \cos($lat1) + $bx))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bearing between two coordinates.
|
||||
* @param LatLng $from Starting coordinate
|
||||
* @param LatLng $to Ending coordinate
|
||||
*
|
||||
* @return float bearing in degrees
|
||||
*/
|
||||
public static function getBearing(LatLng $from, LatLng $to): float
|
||||
{
|
||||
$lat1 = deg2rad($from->getLat());
|
||||
$lat2 = deg2rad($to->getLat());
|
||||
$dLon = deg2rad($to->getLng() - $from->getLng());
|
||||
|
||||
$y = sin($dLon) * cos($lat2);
|
||||
$x = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($dLon);
|
||||
return fmod(rad2deg(atan2($y, $x)) + 360, 360);
|
||||
}
|
||||
}
|
||||
79
src/StaticMap/src/Utils/Terminator.php
Normal file
79
src/StaticMap/src/Utils/Terminator.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
class Terminator {
|
||||
private $resolution;
|
||||
|
||||
public function __construct($resolution = 2) {
|
||||
$this->resolution = $resolution;
|
||||
}
|
||||
|
||||
public function getTerminatorCoordinates($time = null) {
|
||||
$date = $time ? new DateTime($time) : new DateTime();
|
||||
$julianDay = $this->julian($date);
|
||||
$gst = $this->GMST($julianDay);
|
||||
|
||||
$sunEclPos = $this->sunEclipticPosition($julianDay);
|
||||
$eclObliq = $this->eclipticObliquity($julianDay);
|
||||
$sunEqPos = $this->sunEquatorialPosition($sunEclPos['lambda'], $eclObliq);
|
||||
|
||||
$latLng = [];
|
||||
for ($i = 0; $i <= 720 * $this->resolution; $i++) {
|
||||
$lng = -360 + $i / $this->resolution;
|
||||
$ha = $this->hourAngle($lng, $sunEqPos, $gst);
|
||||
$latLng[] = [$this->latitude($ha, $sunEqPos), $lng];
|
||||
}
|
||||
|
||||
if ($sunEqPos['delta'] < 0) {
|
||||
array_unshift($latLng, [90, -360]);
|
||||
array_push($latLng, [90, 360]);
|
||||
} else {
|
||||
array_unshift($latLng, [-90, -360]);
|
||||
array_push($latLng, [-90, 360]);
|
||||
}
|
||||
|
||||
return $latLng;
|
||||
}
|
||||
|
||||
private function julian($date) {
|
||||
// Julian date calculation
|
||||
return ($date->getTimestamp() / 86400) + 2440587.5;
|
||||
}
|
||||
|
||||
private function GMST($julianDay) {
|
||||
$d = $julianDay - 2451545.0;
|
||||
return fmod(18.697374558 + 24.06570982441908 * $d, 24);
|
||||
}
|
||||
|
||||
private function sunEclipticPosition($julianDay) {
|
||||
$n = $julianDay - 2451545.0;
|
||||
$L = fmod(280.460 + 0.9856474 * $n, 360);
|
||||
$g = fmod(357.528 + 0.9856003 * $n, 360);
|
||||
$lambda = $L + 1.915 * sin(deg2rad($g)) + 0.02 * sin(deg2rad(2 * $g));
|
||||
$R = 1.00014 - 0.01671 * cos(deg2rad($g)) - 0.0014 * cos(deg2rad(2 * $g));
|
||||
return ['lambda' => $lambda, 'R' => $R];
|
||||
}
|
||||
|
||||
private function eclipticObliquity($julianDay) {
|
||||
$n = $julianDay - 2451545.0;
|
||||
$T = $n / 36525;
|
||||
return 23.43929111 - $T * (46.836769 / 3600 - $T * (0.0001831 / 3600 + $T * (0.00200340 / 3600 - $T * (0.576e-6 / 3600 - $T * 4.34e-8 / 3600))));
|
||||
}
|
||||
|
||||
private function sunEquatorialPosition($sunEclLng, $eclObliq) {
|
||||
$alpha = rad2deg(atan(cos(deg2rad($eclObliq)) * tan(deg2rad($sunEclLng))));
|
||||
$delta = rad2deg(asin(sin(deg2rad($eclObliq)) * sin(deg2rad($sunEclLng))));
|
||||
$lQuadrant = floor($sunEclLng / 90) * 90;
|
||||
$raQuadrant = floor($alpha / 90) * 90;
|
||||
$alpha = $alpha + ($lQuadrant - $raQuadrant);
|
||||
return ['alpha' => $alpha, 'delta' => $delta];
|
||||
}
|
||||
|
||||
private function hourAngle($lng, $sunPos, $gst) {
|
||||
$lst = $gst + $lng / 15;
|
||||
return $lst * 15 - $sunPos['alpha'];
|
||||
}
|
||||
|
||||
private function latitude($ha, $sunPos) {
|
||||
return rad2deg(atan(-cos(deg2rad($ha)) / tan(deg2rad($sunPos['delta']))));
|
||||
}
|
||||
}
|
||||
52
src/StaticMap/src/XY.php
Normal file
52
src/StaticMap/src/XY.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Wavelog\StaticMapImage;
|
||||
|
||||
/**
|
||||
* Wavelog\StaticMapImage\XY define X and Y pixel position for map, lines, markers...
|
||||
*
|
||||
* @package Wavelog\StaticMapImage
|
||||
* @author Franck Alary
|
||||
* @access public
|
||||
* @see https://github.com/DantSu/php-osm-static-api Github page of this project
|
||||
*/
|
||||
class XY
|
||||
{
|
||||
/**
|
||||
* @var int X
|
||||
*/
|
||||
private $x = 0;
|
||||
/**
|
||||
* @var int Y
|
||||
*/
|
||||
private $y = 0;
|
||||
|
||||
/**
|
||||
* XY constructor.
|
||||
* @param int $x X
|
||||
* @param int $y Y
|
||||
*/
|
||||
public function __construct(int $x, int $y)
|
||||
{
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get X
|
||||
* @return int X
|
||||
*/
|
||||
public function getX(): int
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Y
|
||||
* @return int Y
|
||||
*/
|
||||
public function getY(): int
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
}
|
||||
BIN
src/StaticMap/src/resources/circle-dot-red.png
Normal file
BIN
src/StaticMap/src/resources/circle-dot-red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/StaticMap/src/resources/font.ttf
Normal file
BIN
src/StaticMap/src/resources/font.ttf
Normal file
Binary file not shown.
BIN
src/StaticMap/src/resources/watermark_static_map.png
Normal file
BIN
src/StaticMap/src/resources/watermark_static_map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Reference in New Issue
Block a user