You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
16 KiB
PHP
433 lines
16 KiB
PHP
<?php
|
|
|
|
/**
|
|
* staticMapLite 0.3.1
|
|
*
|
|
* Copyright 2009 Gerhard Koch
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* @author Gerhard Koch <gerhard.koch AT ymail.com>
|
|
*
|
|
* Adaptation pour SPIP
|
|
*
|
|
* @author b_b
|
|
*
|
|
*/
|
|
|
|
class staticMapGis
|
|
{
|
|
|
|
protected $maxWidth = _TAILLE_MAX_GIS_STATIC;
|
|
protected $maxHeight = _TAILLE_MAX_GIS_STATIC;
|
|
|
|
protected $tileSize = 256;
|
|
protected $tileSrcUrl = [
|
|
'openstreetmap_mapnik' => [
|
|
'url' => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors'
|
|
],
|
|
'openstreetmap_de' => [
|
|
'url' => 'https://tile.openstreetmap.de/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors'
|
|
],
|
|
'openstreetmap_fr' => [
|
|
'url' => 'https://a.tile.openstreetmap.fr/osmfr/{Z}/{X}/{Y}.png',
|
|
'credits' => '© Openstreetmap France | © OpenStreetMap contributors'
|
|
],
|
|
'openstreetmap_hot' => [
|
|
'url' => 'http://a.tile.openstreetmap.fr/hot/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors, Tiles style by Humanitarian OpenStreetMap Team hosted by OpenStreetMap France'
|
|
],
|
|
'opentopomap' => [
|
|
'url' => 'https://tile.opentopomap.org/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributorsn, SRTM | Map style: © OpenTopoMap (CC-BY-SA)'
|
|
],
|
|
'cartodb_positron' => [
|
|
'url' => 'https://basemaps.cartocdn.com/light_all/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'cartodb_positron_base' => [
|
|
'url' => 'https://basemaps.cartocdn.com/light_nolabels/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'cartodb_darkmatter' => [
|
|
'url' => 'https://basemaps.cartocdn.com/dark_all/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'cartodb_darkmatter_base' => [
|
|
'url' => 'https://basemaps.cartocdn.com/dark_nolabels/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'cartodb_voyager' => [
|
|
'url' => 'https://basemaps.cartocdn.com/rastertiles/voyager/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'cartodb_voyager_base' => [
|
|
'url' => 'https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{Z}/{X}/{Y}.png',
|
|
'credits' => '© OpenStreetMap contributors © CARTO'
|
|
],
|
|
'stamen_toner' => [
|
|
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/toner/{Z}/{X}/{Y}.png',
|
|
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors'
|
|
],
|
|
'stamen_tonerlite' => [
|
|
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/toner-lite/{Z}/{X}/{Y}.png',
|
|
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors'
|
|
],
|
|
'stamen_terrain' => [
|
|
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/terrain/{Z}/{X}/{Y}.png',
|
|
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors'
|
|
],
|
|
'stamen_watercolor' => [
|
|
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/watercolor/{Z}/{X}/{Y}.jpg',
|
|
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors'
|
|
],
|
|
'esri_worldstreetmap' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
|
|
],
|
|
'esri_delorme' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Copyright: ©2012 DeLorme'
|
|
],
|
|
'esri_worldtopomap' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
|
|
],
|
|
'esri_worldimagery' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
|
|
],
|
|
'esri_worldterrain' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Source: USGS, Esri, TANA, DeLorme, and NPS'
|
|
],
|
|
'esri_worldshadedrelief' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Source: Esri'
|
|
],
|
|
'esri_worldphysical' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Source: US National Park Service'
|
|
],
|
|
'esri_oceanbasemap' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
|
|
],
|
|
'esri_natgeoworldmap' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
|
|
],
|
|
'esri_worldgraycanvas' => [
|
|
'url' => 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{Z}/{Y}/{X}',
|
|
'credits' => 'Tiles © Esri — Esri, DeLorme, NAVTEQ'
|
|
]
|
|
];
|
|
|
|
protected $tileDefaultSrc = 'openstreetmap_mapnik';
|
|
|
|
protected $useTileCache = true;
|
|
protected $tileCacheBaseDir = '';
|
|
|
|
protected $useMapCache = true;
|
|
protected $mapCacheBaseDir = '';
|
|
protected $mapCacheID = '';
|
|
protected $mapCacheFile = '';
|
|
protected $mapCacheExtension = 'png';
|
|
|
|
protected $zoom, $lat, $lon, $width, $height, $markers, $image, $maptype;
|
|
protected $centerX, $centerY, $offsetX, $offsetY;
|
|
|
|
public function __construct($lat, $lon, $zoom, $width, $height, $maptype, $markers) {
|
|
$this->width = intval($width);
|
|
$this->height = intval($height);
|
|
if ($zoom) {
|
|
$this->zoom = intval($zoom);
|
|
}
|
|
$this->lat = floatval($lat);
|
|
$this->lon = floatval($lon);
|
|
$this->markers = $markers;
|
|
$this->maptype = $this->tileDefaultSrc;
|
|
$base = sous_repertoire(_DIR_VAR, 'cache-gis');
|
|
$this->tileCacheBaseDir = sous_repertoire($base, 'tiles');
|
|
$this->mapCacheBaseDir = sous_repertoire($base, 'maps');
|
|
if ($maptype and array_key_exists($maptype, $this->tileSrcUrl)) {
|
|
$this->maptype = $maptype;
|
|
}
|
|
}
|
|
|
|
public function lonToTile($long, $zoom) {
|
|
return (($long + 180) / 360) * 2 ** $zoom;
|
|
}
|
|
|
|
public function latToTile($lat, $zoom) {
|
|
return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * pi() / 180)) / pi()) / 2 * 2 ** $zoom;
|
|
}
|
|
|
|
public function latLngToPixels($lat, $lon, $zoom) {
|
|
return [
|
|
'x' => $this->lonToTile($lon, $zoom) * $this->tileSize,
|
|
'y' => $this->latToTile($lat, $zoom) * $this->tileSize
|
|
];
|
|
}
|
|
|
|
public function getBoundsZoom() {
|
|
// thx to https://github.com/esripdx/Static-Maps-API-PHP/blob/master/img.php#L166
|
|
$bounds = ['minLat' => 90, 'maxLat' => -90, 'minLon' => 180, 'maxLon' => -180];
|
|
foreach ($this->markers as $marker) {
|
|
if ($marker['lat'] < $bounds['minLat']) {
|
|
$bounds['minLat'] = $marker['lat'];
|
|
}
|
|
if ($marker['lat'] > $bounds['maxLat']) {
|
|
$bounds['maxLat'] = $marker['lat'];
|
|
}
|
|
if ($marker['lon'] < $bounds['minLon']) {
|
|
$bounds['minLon'] = $marker['lon'];
|
|
}
|
|
if ($marker['lon'] > $bounds['maxLon']) {
|
|
$bounds['maxLon'] = $marker['lon'];
|
|
}
|
|
}
|
|
$this->lat = $bounds['minLat'] + (($bounds['maxLat'] - $bounds['minLat']) / 2);
|
|
$this->lon = $bounds['minLon'] + (($bounds['maxLon'] - $bounds['minLon']) / 2);
|
|
if (!intval($this->zoom)) {
|
|
$fitZoom = 10;
|
|
$doesNotFit = true;
|
|
while ($fitZoom > 1 && $doesNotFit) {
|
|
$center = $this->latLngToPixels($this->lat, $this->lon, $fitZoom);
|
|
// check if the bounding rectangle fits within width/height
|
|
$sw = $this->latLngToPixels($bounds['minLat'], $bounds['minLon'], $fitZoom);
|
|
$ne = $this->latLngToPixels($bounds['maxLat'], $bounds['maxLon'], $fitZoom);
|
|
$fitHeight = abs($ne['y'] - $sw['y']);
|
|
$fitWidth = abs($ne['x'] - $sw['x']);
|
|
if ($fitHeight <= $this->height && $fitWidth <= $this->width) {
|
|
$doesNotFit = false;
|
|
}
|
|
$fitZoom--;
|
|
}
|
|
$this->zoom = $fitZoom;
|
|
}
|
|
}
|
|
|
|
public function initCoords() {
|
|
$this->centerX = $this->lonToTile($this->lon, $this->zoom);
|
|
$this->centerY = $this->latToTile($this->lat, $this->zoom);
|
|
$this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
|
|
$this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
|
|
}
|
|
|
|
public function createBaseMap() {
|
|
$this->image = imagecreatetruecolor($this->width, $this->height);
|
|
$startX = floor($this->centerX - ($this->width / $this->tileSize) / 2);
|
|
$startY = floor($this->centerY - ($this->height / $this->tileSize) / 2);
|
|
$endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
|
|
$endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
|
|
$this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
|
|
$this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
|
|
$this->offsetX += floor($this->width / 2);
|
|
$this->offsetY += floor($this->height / 2);
|
|
$this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
|
|
$this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
|
|
|
|
for ($x = $startX; $x <= $endX; $x++) {
|
|
// normalise x index for tile stitching at date crossing line
|
|
$xNorm = $x % (2 ** $this->zoom);
|
|
if ($xNorm < 0) {
|
|
$xNorm = $xNorm + (2 ** $this->zoom);
|
|
}
|
|
for ($y = $startY; $y <= $endY; $y++) {
|
|
$url = str_replace(['{Z}', '{X}', '{Y}'], [$this->zoom, $xNorm, $y], $this->tileSrcUrl[$this->maptype]['url']);
|
|
$tileData = $this->fetchTile($url);
|
|
if ($tileData) {
|
|
$tileImage = @imagecreatefromstring($tileData);
|
|
} else {
|
|
$tileImage = imagecreate($this->tileSize, $this->tileSize);
|
|
$color = imagecolorallocate($tileImage, 255, 255, 255);
|
|
@imagestring($tileImage, 1, 127, 127, 'err', $color);
|
|
}
|
|
$destX = ($x - $startX) * $this->tileSize + $this->offsetX;
|
|
$destY = ($y - $startY) * $this->tileSize + $this->offsetY;
|
|
@imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public function placeMarkers() {
|
|
// loop thru marker array
|
|
foreach ($this->markers as $marker) {
|
|
// set some local variables
|
|
$markerLat = $marker['lat'];
|
|
$markerLon = $marker['lon'];
|
|
$markerUrl = $marker['url'] ?? '';
|
|
// clear variables from previous loops
|
|
$markerShadow = false;
|
|
// marker perso ou par défaut ?
|
|
if ($markerUrl) {
|
|
include_spip('inc/distant');
|
|
$markerPath = copie_locale($markerUrl);
|
|
[$h, $w] = taille_image($markerPath);
|
|
$markerOffsetX = -$w / 2;
|
|
$markerOffsetY = -$h;
|
|
} else {
|
|
$markerPath = sinon(find_in_path('images/marker_defaut.png'), find_in_path('lib/leaflet/dist/images/marker-icon.png'));
|
|
$markerShadow = sinon(find_in_path('images/marker_defaut_shadow.png'), find_in_path('lib/leaflet/dist/images/marker-shadow.png'));
|
|
$markerOffsetX = -12;
|
|
$markerOffsetY = -41;
|
|
}
|
|
|
|
// create img + shadow resource
|
|
$markerImg = imagecreatefrompng($markerPath);
|
|
$markerShadowImg = null;
|
|
if ($markerShadow) {
|
|
$markerShadowImg = imagecreatefrompng($markerShadow);
|
|
}
|
|
|
|
// calc position
|
|
$destX = floor(($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)));
|
|
$destY = floor(($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)));
|
|
|
|
// copy shadow on basemap
|
|
if ($markerShadow && $markerShadowImg) {
|
|
imagecopy(
|
|
$this->image,
|
|
$markerShadowImg,
|
|
$destX + intval($markerOffsetX),
|
|
$destY + intval($markerOffsetY),
|
|
0,
|
|
0,
|
|
imagesx($markerShadowImg),
|
|
imagesy($markerShadowImg)
|
|
);
|
|
}
|
|
|
|
// copy marker on basemap above shadow
|
|
imagecopy(
|
|
$this->image,
|
|
$markerImg,
|
|
$destX + intval($markerOffsetX),
|
|
$destY + intval($markerOffsetY),
|
|
0,
|
|
0,
|
|
imagesx($markerImg),
|
|
imagesy($markerImg)
|
|
);
|
|
};
|
|
}
|
|
|
|
|
|
public function tileUrlToFilename($url) {
|
|
return $this->tileCacheBaseDir . '/' . str_replace(['http://','https://'], '', $url);
|
|
}
|
|
|
|
public function checkTileCache($url) {
|
|
$filename = $this->tileUrlToFilename($url);
|
|
if (file_exists($filename)) {
|
|
return file_get_contents($filename);
|
|
}
|
|
}
|
|
|
|
public function checkMapCache() {
|
|
$this->mapCacheID = md5($this->serializeParams());
|
|
$filename = $this->mapCacheIDToFilename();
|
|
if (file_exists($filename)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function serializeParams() {
|
|
return join('&', [$this->zoom, $this->lat, $this->lon, $this->width, $this->height, serialize($this->markers), $this->maptype]);
|
|
}
|
|
|
|
public function mapCacheIDToFilename() {
|
|
if (!$this->mapCacheFile) {
|
|
$this->mapCacheFile = $this->mapCacheBaseDir . $this->maptype . '/' . $this->zoom . '/cache_' . substr($this->mapCacheID, 0, 2) . '/' . substr($this->mapCacheID, 2, 2) . '/' . substr($this->mapCacheID, 4);
|
|
}
|
|
return $this->mapCacheFile . '.' . $this->mapCacheExtension;
|
|
}
|
|
|
|
|
|
public function mkdir_recursive($pathname, $mode) {
|
|
is_dir(dirname($pathname)) || $this->mkdir_recursive(dirname($pathname), $mode);
|
|
return is_dir($pathname) || @mkdir($pathname, $mode);
|
|
}
|
|
|
|
public function writeTileToCache($url, $data) {
|
|
$filename = $this->tileUrlToFilename($url);
|
|
$this->mkdir_recursive(dirname($filename), 0777);
|
|
file_put_contents($filename, $data);
|
|
}
|
|
|
|
public function fetchTile($url) {
|
|
if ($this->useTileCache && ($cached = $this->checkTileCache($url))) {
|
|
return $cached;
|
|
}
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_USERAGENT, _INC_DISTANT_USER_AGENT);
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
$tile = curl_exec($ch);
|
|
curl_close($ch);
|
|
if ($tile && $this->useTileCache) {
|
|
$this->writeTileToCache($url, $tile);
|
|
}
|
|
return $tile;
|
|
}
|
|
|
|
public function copyrightNotice() {
|
|
$credits = $this->tileSrcUrl[$this->maptype]['credits'];
|
|
$largeur = $this->width * 2;
|
|
include_spip('inc/filtres_images_mini');
|
|
$logoImg = imagecreatefrompng(extraire_attribut(image_reduire_par(image_typo($credits, 'taille=20', 'padding=8', 'align=right', "largeur=$largeur", 'police=dustismo_bold.ttf'), 2), 'src'));
|
|
imagecopy($this->image, $logoImg, imagesx($this->image) - imagesx($logoImg), imagesy($this->image) - imagesy($logoImg), 0, 0, imagesx($logoImg), imagesy($logoImg));
|
|
}
|
|
|
|
public function makeMap() {
|
|
if (is_countable($this->markers) ? count($this->markers) : 0) {
|
|
$this->getBoundsZoom();
|
|
}
|
|
$this->initCoords();
|
|
$this->createBaseMap();
|
|
if (is_countable($this->markers) ? count($this->markers) : 0) {
|
|
$this->placeMarkers();
|
|
}
|
|
$this->copyrightNotice();
|
|
}
|
|
|
|
public function showMap() {
|
|
if ($this->useMapCache) {
|
|
// use map cache, so check cache for map
|
|
if (!$this->checkMapCache()) {
|
|
// map is not in cache, needs to be build
|
|
$this->makeMap();
|
|
$this->mkdir_recursive(dirname($this->mapCacheIDToFilename()), 0777);
|
|
imagepng($this->image, $this->mapCacheIDToFilename(), 9);
|
|
if (file_exists($this->mapCacheIDToFilename())) {
|
|
return $this->mapCacheIDToFilename();
|
|
} else {
|
|
return imagepng($this->image);
|
|
}
|
|
} else {
|
|
// map is in cache
|
|
return $this->mapCacheIDToFilename();
|
|
}
|
|
} else {
|
|
// no cache, make map, send headers and deliver png
|
|
$this->makeMap();
|
|
return imagepng($this->image);
|
|
}
|
|
}
|
|
}
|