* * 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 = null, $lon = null, $zoom = null, $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) * pow(2, $zoom); } public function latToTile($lat, $zoom) { return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * pi() / 180)) / pi()) / 2 * pow(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++) { for ($y = $startY; $y <= $endY; $y++) { $url = str_replace(['{Z}', '{X}', '{Y}'], [$this->zoom, $x, $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); list($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); 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_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 (count($this->markers)) { $this->getBoundsZoom(); } $this->initCoords(); $this->createBaseMap(); if (count($this->markers)) { $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); } } }