Browse Source

Des cartes statiques pour GIS

Presque 7 ans plus tard, il était temps de partager ce code que je n'ai encore jamais utilisé, ref https://seenthis.net/messages/178291

Pour la petite histoire, j'ai eu une sacré frayeur quand j'ai remarqué que je ne l'avais plus sous la main et que je l'avais certainement effacé par erreur il y a bien longtemps. Heureusement pour moi, il était au chaud (plutôt au froid) dans un disque de sauvegarde, publions le avant qu'il ne se cache de nouveau :p
pull/34/head
b_b 7 months ago
parent
commit
1906871680
  1. 59
      gis_fonctions.php
  2. 381
      inc/staticmap.php

59
gis_fonctions.php

@ -516,3 +516,62 @@ function gis_icon_properties($img = '') {
return $props;
}
if (!defined('_TAILLE_MAX_GIS_STATIC')) define('_TAILLE_MAX_GIS_STATIC',1024);
/**
* Retourne la balise img d'une carte statique
*
* @param int $id_gis
* ID du point à afficher
* @param int $width
* Largeur de l'image
* @param int $height
* Hauteur de l'image
* @param string $maptype
* Le nom de la couche à utiliser en fond de carte
* @param int $zoom
* Le zoom de la carte
* @param float $lat
* La latitude du centre de la carte
* @param float $lon
* La longitude du centre de la carte
* @param string $markers
* Les informations des markers à afficher sous la forme suivante lat;lon;url|lat;lon;url
* @return string
* Le code HTML de l'image de la carte
**/
function gis_static_map($id_gis=null, $width, $height, $maptype=false, $zoom=null, $markers=null, $lat=0, $lon=0) {
$gis = array();
if($id_gis)
$gis = sql_fetsel('lat,lon,zoom','spip_gis','id_gis='.intval($id_gis));
$zoom = $zoom ? $zoom : $gis['zoom'];
$lat = $gis['lat'] ? $gis['lat'] : $lat;
$lon = $gis['lon'] ? $gis['lon'] : $lon;
if (intval($id_gis) AND !$markers)
$markers_[] = array('lat' => $lat, 'lon' => $lon);
else {
$markers = explode('|', $markers);
foreach ($markers as $marker) {
list($markerLat, $markerLon, $markerUrl) = explode(';', $marker);
$markers_[] = array('lat' => $markerLat, 'lon' => $markerLon, 'url' => $markerUrl);
}
}
if ($width > _TAILLE_MAX_GIS_STATIC)
$width = _TAILLE_MAX_GIS_STATIC;
if ($height > _TAILLE_MAX_GIS_STATIC)
$height = _TAILLE_MAX_GIS_STATIC;
include_spip('inc/staticmap');
$map = new staticMapGis($lat, $lon, $zoom, $width, $height, $maptype, $markers_);
$balise_img = charger_filtre('balise_img');
return $balise_img($map->showMap());
}

381
inc/staticmap.php

@ -0,0 +1,381 @@
<?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 = array(
'openstreetmap_mapnik' => array(
'url' => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors'
),
'openstreetmap_de' => array(
'url' => 'https://tile.openstreetmap.de/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors'
),
'openstreetmap_fr' => array(
'url' => 'https://a.tile.openstreetmap.fr/osmfr/{Z}/{X}/{Y}.png',
'credits' => '© Openstreetmap France | © OpenStreetMap contributors'
),
'openstreetmap_hot' => array(
'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' => array(
'url' => 'https://tile.opentopomap.org/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributorsn, SRTM | Map style: © OpenTopoMap (CC-BY-SA)'
),
'cartodb_positron' => array(
'url' => 'https://basemaps.cartocdn.com/light_all/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'cartodb_positron_base' => array(
'url' => 'https://basemaps.cartocdn.com/light_nolabels/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'cartodb_darkmatter' => array(
'url' => 'https://basemaps.cartocdn.com/dark_all/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'cartodb_darkmatter_base' => array(
'url' => 'https://basemaps.cartocdn.com/dark_nolabels/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'cartodb_voyager' => array(
'url' => 'https://basemaps.cartocdn.com/rastertiles/voyager/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'cartodb_voyager_base' => array(
'url' => 'https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{Z}/{X}/{Y}.png',
'credits' => '© OpenStreetMap contributors © CARTO'
),
'stamen_toner' => array(
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/toner/{Z}/{X}/{Y}.png',
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 &mdash; Map data © OpenStreetMap contributors'
),
'stamen_tonerlite' => array(
'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 &mdash; Map data © OpenStreetMap contributors'
),
'stamen_terrain' => array(
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/terrain/{Z}/{X}/{Y}.png',
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 &mdash; Map data © OpenStreetMap contributors'
),
'stamen_watercolor' => array(
'url' => 'https://stamen-tiles-c.a.ssl.fastly.net/watercolor/{Z}/{X}/{Y}.jpg',
'credits' => 'Map tiles by Stamen Design, CC BY 3.0 &mdash; Map data © OpenStreetMap contributors'
)
);
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 array(
'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 = array('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 = 21;
$doesNotFit = true;
while($fitZoom > 1 && $doesNotFit) {
$center = $this->latLngToPixels($this->lat, $this->lon, $fitZoom);
$leftEdge = $center['x'] - $this->width/2;
$topEdge = $center['y'] - $this->height/2;
// 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(array('{Z}', '{X}', '{Y}'), array($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
$markerFilename = false;
$markerShadow = false;
$matches = 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(array('http://'), '', $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("&", array($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);
}
}
}
Loading…
Cancel
Save