1628e43ccSMark Prins<?php 2a760825cSgithub-actions[bot] 3628e43ccSMark Prins/* 4751c8ef2SMark Prins * Copyright (c) 2012-2023 Mark C. Prins <mprins@users.sf.net> 5628e43ccSMark Prins * 6f4b9bdacSMark Prins * In part based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/ 7628e43ccSMark Prins * 8628e43ccSMark Prins * Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com> 9628e43ccSMark Prins * 10628e43ccSMark Prins * Licensed under the Apache License, Version 2.0 (the "License"); 11628e43ccSMark Prins * you may not use this file except in compliance with the License. 12628e43ccSMark Prins * You may obtain a copy of the License at 13628e43ccSMark Prins * 14628e43ccSMark Prins * http://www.apache.org/licenses/LICENSE-2.0 15628e43ccSMark Prins * 16628e43ccSMark Prins * Unless required by applicable law or agreed to in writing, software 17628e43ccSMark Prins * distributed under the License is distributed on an "AS IS" BASIS, 18628e43ccSMark Prins * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19628e43ccSMark Prins * See the License for the specific language governing permissions and 20628e43ccSMark Prins * limitations under the License. 21628e43ccSMark Prins */ 22d2f1674eSMark Prinsnamespace dokuwiki\plugin\openlayersmap; 23ba56c962SMark Prins 248a541d8fSMark Prinsuse Exception; 253ebe658bSMark Prinsuse geoPHP\Geometry\Geometry; 263ebe658bSMark Prinsuse geoPHP\Geometry\GeometryCollection; 273ebe658bSMark Prinsuse geoPHP\Geometry\LineString; 283ebe658bSMark Prinsuse geoPHP\Geometry\Point; 293ebe658bSMark Prinsuse geoPHP\Geometry\Polygon; 303ebe658bSMark Prinsuse geoPHP\geoPHP; 31f204b8caSMark Prinsuse dokuwiki\Logger; 323ebe658bSMark Prins 33628e43ccSMark Prins/** 34ab8cbd2bSMark Prins * 35628e43ccSMark Prins * @author Mark C. Prins <mprins@users.sf.net> 36628e43ccSMark Prins * @author Gerhard Koch <gerhard.koch AT ymail.com> 37628e43ccSMark Prins * 38628e43ccSMark Prins */ 39a760825cSgithub-actions[bot]class StaticMap 40a760825cSgithub-actions[bot]{ 41628e43ccSMark Prins // the final output 428a541d8fSMark Prins private int $tileSize = 256; 438a541d8fSMark Prins private array $tileInfo = [ 44628e43ccSMark Prins // OSM sources 458a541d8fSMark Prins 'openstreetmap' => ['txt' => '(c) OpenStreetMap data/ODbL', 'logo' => 'osm_logo.png', 'url' => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'], 46976e50f5SMark Prins // OpenTopoMap sources 478a541d8fSMark Prins 'opentopomap' => ['txt' => '(c) OpenStreetMap data/ODbL, SRTM | style: (c) OpenTopoMap', 'logo' => 'osm_logo.png', 'url' => 'https://tile.opentopomap.org/{Z}/{X}/{Y}.png'], 48628e43ccSMark Prins // OCM sources 49a760825cSgithub-actions[bot] 'cycle' => ['txt' => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url' => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'], 50a760825cSgithub-actions[bot] 'transport' => ['txt' => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url' => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'], 51a760825cSgithub-actions[bot] 'landscape' => ['txt' => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url' => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png'], 52a760825cSgithub-actions[bot] 'outdoors' => ['txt' => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url' => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png'], 53a760825cSgithub-actions[bot] 'toner' => ['txt' => '(c) Stadia Maps;Stamen Design;OpenStreetMap contributors', 'logo' => 'stamen.png', 'url' => 'https://tiles-eu.stadiamaps.com/tiles/stamen_toner/{Z}/{X}/{Y}.png'], 54a760825cSgithub-actions[bot] 'terrain' => ['txt' => '(c) Stadia Maps;Stamen Design;OpenStreetMap contributors', 'logo' => 'stamen.png', 'url' => 'https://tiles-eu.stadiamaps.com/tiles/stamen_terrain/{Z}/{X}/{Y}.png'], 55a760825cSgithub-actions[bot] ]; 568a541d8fSMark Prins private string $tileDefaultSrc = 'openstreetmap'; 57628e43ccSMark Prins 58628e43ccSMark Prins // set up markers 598a541d8fSMark Prins private array $markerPrototypes = [ 60628e43ccSMark Prins // found at http://www.mapito.net/map-marker-icons.html 61628e43ccSMark Prins // these are 17x19 px with a pointer at the bottom left 62a760825cSgithub-actions[bot] 'lightblue' => ['regex' => '/^lightblue(\d+)$/', 'extension' => '.png', 'shadow' => false, 'offsetImage' => '0,-19', 'offsetShadow' => false], 63628e43ccSMark Prins // openlayers std markers are 21x25px with shadow 64a760825cSgithub-actions[bot] 'ol-marker' => ['regex' => '/^marker(|-blue|-gold|-green|-red)+$/', 'extension' => '.png', 'shadow' => 'marker_shadow.png', 'offsetImage' => '-10,-25', 'offsetShadow' => '-1,-13'], 65628e43ccSMark Prins // these are 16x16 px 66a760825cSgithub-actions[bot] 'ww_icon' => ['regex' => '/ww_\S+$/', 'extension' => '.png', 'shadow' => false, 'offsetImage' => '-8,-8', 'offsetShadow' => false], 67628e43ccSMark Prins // assume these are 16x16 px 68a760825cSgithub-actions[bot] 'rest' => ['regex' => '/^(?!lightblue(\d+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/', 'extension' => '.png', 'shadow' => 'marker_shadow.png', 'offsetImage' => '-8,-8', 'offsetShadow' => '-1,-1'], 69a760825cSgithub-actions[bot] ]; 708a541d8fSMark Prins // note even though $centerX, $centerY, $offsetX and $offsetY are defined as float, 718a541d8fSMark Prins // these are actually integer numbers, but the php floor()/ceil() functions return a float 728a541d8fSMark Prins private float $centerX; 738a541d8fSMark Prins private float $centerY; 748a541d8fSMark Prins private float $offsetX; 758a541d8fSMark Prins private float $offsetY; 7657f8d5bbSMark Prins private $image; 778a541d8fSMark Prins private int $zoom; 788a541d8fSMark Prins private float $lat; 798a541d8fSMark Prins private float $lon; 808a541d8fSMark Prins private int $width; 818a541d8fSMark Prins private int $height; 828a541d8fSMark Prins private array $markers; 838a541d8fSMark Prins private string $maptype; 848a541d8fSMark Prins private string $kmlFileName; 858a541d8fSMark Prins private string $gpxFileName; 868a541d8fSMark Prins private string $geojsonFileName; 878a541d8fSMark Prins private bool $autoZoomExtent; 888a541d8fSMark Prins private string $apikey; 898a541d8fSMark Prins private string $tileCacheBaseDir; 908a541d8fSMark Prins private string $mapCacheBaseDir; 918a541d8fSMark Prins private string $mediaBaseDir; 928a541d8fSMark Prins private bool $useTileCache; 938a541d8fSMark Prins private string $mapCacheID = ''; 948a541d8fSMark Prins private string $mapCacheFile = ''; 958a541d8fSMark Prins private string $mapCacheExtension = 'png'; 96628e43ccSMark Prins 97628e43ccSMark Prins /** 98f4b9bdacSMark Prins * Constructor. 99ab8cbd2bSMark Prins * 100ab8cbd2bSMark Prins * @param float $lat 101ab8cbd2bSMark Prins * Latitude (x) of center of map 102ab8cbd2bSMark Prins * @param float $lon 103ab8cbd2bSMark Prins * Longitude (y) of center of map 104ab8cbd2bSMark Prins * @param int $zoom 105ab8cbd2bSMark Prins * Zoomlevel 106ab8cbd2bSMark Prins * @param int $width 107ab8cbd2bSMark Prins * Width in pixels 108ab8cbd2bSMark Prins * @param int $height 109ab8cbd2bSMark Prins * Height in pixels 110ab8cbd2bSMark Prins * @param string $maptype 111ab8cbd2bSMark Prins * Name of the map 11257f8d5bbSMark Prins * @param array $markers 113ab8cbd2bSMark Prins * array of markers 114ab8cbd2bSMark Prins * @param string $gpx 115ab8cbd2bSMark Prins * GPX filename 116ab8cbd2bSMark Prins * @param string $kml 117ab8cbd2bSMark Prins * KML filename 118257dffd7SMark Prins * @param string $geojson 119ab8cbd2bSMark Prins * @param string $mediaDir 120ab8cbd2bSMark Prins * Directory to store/cache maps 121ab8cbd2bSMark Prins * @param string $tileCacheBaseDir 122ab8cbd2bSMark Prins * Directory to cache map tiles 12357f8d5bbSMark Prins * @param bool $autoZoomExtent 124ab8cbd2bSMark Prins * Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers 125257dffd7SMark Prins * @param string $apikey 126628e43ccSMark Prins */ 12757f8d5bbSMark Prins public function __construct( 12857f8d5bbSMark Prins float $lat, 12957f8d5bbSMark Prins float $lon, 13057f8d5bbSMark Prins int $zoom, 13157f8d5bbSMark Prins int $width, 13257f8d5bbSMark Prins int $height, 13357f8d5bbSMark Prins string $maptype, 13457f8d5bbSMark Prins array $markers, 13557f8d5bbSMark Prins string $gpx, 13657f8d5bbSMark Prins string $kml, 13757f8d5bbSMark Prins string $geojson, 13857f8d5bbSMark Prins string $mediaDir, 13957f8d5bbSMark Prins string $tileCacheBaseDir, 14057f8d5bbSMark Prins bool $autoZoomExtent = true, 141257dffd7SMark Prins string $apikey = '' 14257f8d5bbSMark Prins ) { 143628e43ccSMark Prins $this->zoom = $zoom; 144628e43ccSMark Prins $this->lat = $lat; 145628e43ccSMark Prins $this->lon = $lon; 146628e43ccSMark Prins $this->width = $width; 147628e43ccSMark Prins $this->height = $height; 148628e43ccSMark Prins // validate + set maptype 149628e43ccSMark Prins $this->maptype = $this->tileDefaultSrc; 150628e43ccSMark Prins if (array_key_exists($maptype, $this->tileInfo)) { 151628e43ccSMark Prins $this->maptype = $maptype; 152628e43ccSMark Prins } 1532d11d700SMark Prins $this->markers = $markers; 1542d11d700SMark Prins $this->kmlFileName = $kml; 1552d11d700SMark Prins $this->gpxFileName = $gpx; 1566914b920SMark Prins $this->geojsonFileName = $geojson; 1572d11d700SMark Prins $this->mediaBaseDir = $mediaDir; 158628e43ccSMark Prins $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles'; 159628e43ccSMark Prins $this->useTileCache = $this->tileCacheBaseDir !== ''; 160628e43ccSMark Prins $this->mapCacheBaseDir = $mediaDir . '/olmapmaps'; 1612d11d700SMark Prins $this->autoZoomExtent = $autoZoomExtent; 1625c603532SMark Prins $this->apikey = $apikey; 163628e43ccSMark Prins } 164628e43ccSMark Prins 165628e43ccSMark Prins /** 16657f8d5bbSMark Prins * get the map, this may return a reference to a cached copy. 167628e43ccSMark Prins * 16857f8d5bbSMark Prins * @return string url relative to media dir 169628e43ccSMark Prins */ 170a760825cSgithub-actions[bot] public function getMap(): string 171a760825cSgithub-actions[bot] { 17257f8d5bbSMark Prins try { 17357f8d5bbSMark Prins if ($this->autoZoomExtent) { 17457f8d5bbSMark Prins $this->autoZoom(); 175628e43ccSMark Prins } 17657f8d5bbSMark Prins } catch (Exception $e) { 177f204b8caSMark Prins Logger::debug($e); 17857f8d5bbSMark Prins } 17957f8d5bbSMark Prins 18057f8d5bbSMark Prins // use map cache, so check cache for map 18157f8d5bbSMark Prins if (!$this->checkMapCache()) { 18257f8d5bbSMark Prins // map is not in cache, needs to be build 18357f8d5bbSMark Prins $this->makeMap(); 18457f8d5bbSMark Prins $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777); 18557f8d5bbSMark Prins imagepng($this->image, $this->mapCacheIDToFilename(), 9); 18657f8d5bbSMark Prins } 187ba56c962SMark Prins $doc = $this->mapCacheIDToFilename(); 18857f8d5bbSMark Prins // make url relative to media dir 189ba56c962SMark Prins return str_replace($this->mediaBaseDir, '', $doc); 19057f8d5bbSMark Prins } 19157f8d5bbSMark Prins 192628e43ccSMark Prins /** 19357f8d5bbSMark Prins * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map. 194628e43ccSMark Prins * 19557f8d5bbSMark Prins * @param float $paddingFactor 19657f8d5bbSMark Prins * buffer constant to enlarge (>1.0) the zoom level 1973ebe658bSMark Prins * @throws Exception if non-geometries are found in the collection 198628e43ccSMark Prins */ 199a760825cSgithub-actions[bot] private function autoZoom(float $paddingFactor = 1.0): void 200a760825cSgithub-actions[bot] { 201a760825cSgithub-actions[bot] $geoms = []; 20257f8d5bbSMark Prins $geoms [] = new Point($this->lon, $this->lat); 203a760825cSgithub-actions[bot] if ($this->markers !== []) { 20457f8d5bbSMark Prins foreach ($this->markers as $marker) { 20557f8d5bbSMark Prins $geoms [] = new Point($marker ['lon'], $marker ['lat']); 20657f8d5bbSMark Prins } 20757f8d5bbSMark Prins } 20857f8d5bbSMark Prins if (file_exists($this->kmlFileName)) { 20957f8d5bbSMark Prins $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 21057f8d5bbSMark Prins if ($g !== false) { 21157f8d5bbSMark Prins $geoms [] = $g; 21257f8d5bbSMark Prins } 21357f8d5bbSMark Prins } 21457f8d5bbSMark Prins if (file_exists($this->gpxFileName)) { 21557f8d5bbSMark Prins $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 21657f8d5bbSMark Prins if ($g !== false) { 21757f8d5bbSMark Prins $geoms [] = $g; 21857f8d5bbSMark Prins } 21957f8d5bbSMark Prins } 22057f8d5bbSMark Prins if (file_exists($this->geojsonFileName)) { 22157f8d5bbSMark Prins $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson'); 22257f8d5bbSMark Prins if ($g !== false) { 22357f8d5bbSMark Prins $geoms [] = $g; 22457f8d5bbSMark Prins } 22557f8d5bbSMark Prins } 22657f8d5bbSMark Prins 22757f8d5bbSMark Prins if (count($geoms) <= 1) { 228f204b8caSMark Prins Logger::debug("StaticMap::autoZoom: Skip setting autozoom options", $geoms); 22957f8d5bbSMark Prins return; 23057f8d5bbSMark Prins } 23157f8d5bbSMark Prins 23257f8d5bbSMark Prins $geom = new GeometryCollection($geoms); 23357f8d5bbSMark Prins $centroid = $geom->centroid(); 23457f8d5bbSMark Prins $bbox = $geom->getBBox(); 23557f8d5bbSMark Prins 23657f8d5bbSMark Prins // determine vertical resolution, this depends on the distance from the equator 23757f8d5bbSMark Prins // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360))); 23857f8d5bbSMark Prins $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360))); 23957f8d5bbSMark Prins $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360))); 240f204b8caSMark Prins Logger::debug("StaticMap::autoZoom: vertical resolution: $vy0, $vy1"); 241e98967e1SMark Prins if ($vy1 - $vy0 === 0.0) { 242e98967e1SMark Prins $resolutionVertical = 0; 243f204b8caSMark Prins Logger::debug("StaticMap::autoZoom: using $resolutionVertical"); 244e98967e1SMark Prins } else { 24557f8d5bbSMark Prins $zoomFactorPowered = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0)); 24657f8d5bbSMark Prins $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize); 247e98967e1SMark Prins } 24857f8d5bbSMark Prins // determine horizontal resolution 24957f8d5bbSMark Prins $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width; 250f204b8caSMark Prins Logger::debug("StaticMap::autoZoom: using $resolutionHorizontal"); 25157f8d5bbSMark Prins $resolution = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor; 2523e7791adSMark Prins $zoom = $this->zoom; 2533e7791adSMark Prins if ($resolution > 0) { 25457f8d5bbSMark Prins $zoom = log(360 / ($resolution * $this->tileSize), 2); 2553e7791adSMark Prins } 25657f8d5bbSMark Prins 25757f8d5bbSMark Prins if (is_finite($zoom) && $zoom < 15 && $zoom > 2) { 25857f8d5bbSMark Prins $this->zoom = floor($zoom); 25957f8d5bbSMark Prins } 26057f8d5bbSMark Prins $this->lon = $centroid->getX(); 26157f8d5bbSMark Prins $this->lat = $centroid->getY(); 262f204b8caSMark Prins Logger::debug("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat"); 26357f8d5bbSMark Prins } 26457f8d5bbSMark Prins 265a760825cSgithub-actions[bot] public function checkMapCache(): bool 266a760825cSgithub-actions[bot] { 26757f8d5bbSMark Prins // side effect: set the mapCacheID 26857f8d5bbSMark Prins $this->mapCacheID = md5($this->serializeParams()); 26957f8d5bbSMark Prins $filename = $this->mapCacheIDToFilename(); 27057f8d5bbSMark Prins return file_exists($filename); 27157f8d5bbSMark Prins } 27257f8d5bbSMark Prins 273a760825cSgithub-actions[bot] public function serializeParams(): string 274a760825cSgithub-actions[bot] { 2753e7791adSMark Prins return implode( 276a760825cSgithub-actions[bot] "&", 277af5cdfa8SMark Prins [ 278af5cdfa8SMark Prins $this->zoom, 279af5cdfa8SMark Prins $this->lat, 280af5cdfa8SMark Prins $this->lon, 281af5cdfa8SMark Prins $this->width, 282af5cdfa8SMark Prins $this->height, 283af5cdfa8SMark Prins serialize($this->markers), 284af5cdfa8SMark Prins $this->maptype, 285af5cdfa8SMark Prins $this->kmlFileName, 286af5cdfa8SMark Prins $this->gpxFileName, 287af5cdfa8SMark Prins $this->geojsonFileName 288af5cdfa8SMark Prins ] 28957f8d5bbSMark Prins ); 29057f8d5bbSMark Prins } 29157f8d5bbSMark Prins 292a760825cSgithub-actions[bot] public function mapCacheIDToFilename(): string 293a760825cSgithub-actions[bot] { 29457f8d5bbSMark Prins if (!$this->mapCacheFile) { 29557f8d5bbSMark Prins $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_" 29657f8d5bbSMark Prins . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2) 29757f8d5bbSMark Prins . "/" . substr($this->mapCacheID, 4); 29857f8d5bbSMark Prins } 29957f8d5bbSMark Prins return $this->mapCacheFile . "." . $this->mapCacheExtension; 30057f8d5bbSMark Prins } 30157f8d5bbSMark Prins 30257f8d5bbSMark Prins /** 30357f8d5bbSMark Prins * make the map. 30457f8d5bbSMark Prins */ 305a760825cSgithub-actions[bot] public function makeMap(): void 306a760825cSgithub-actions[bot] { 30757f8d5bbSMark Prins $this->initCoords(); 30857f8d5bbSMark Prins $this->createBaseMap(); 309a760825cSgithub-actions[bot] if ($this->markers !== []) { 31057f8d5bbSMark Prins $this->placeMarkers(); 311e98967e1SMark Prins } 312e98967e1SMark Prins if (file_exists($this->kmlFileName)) { 313e98967e1SMark Prins try { 31457f8d5bbSMark Prins $this->drawKML(); 315e98967e1SMark Prins } catch (exception $e) { 316f204b8caSMark Prins Logger::error('failed to load KML file', $e); 317e98967e1SMark Prins } 318e98967e1SMark Prins } 319e98967e1SMark Prins if (file_exists($this->gpxFileName)) { 320e98967e1SMark Prins try { 32157f8d5bbSMark Prins $this->drawGPX(); 322e98967e1SMark Prins } catch (exception $e) { 323f204b8caSMark Prins Logger::error('failed to load GPX file', $e); 324e98967e1SMark Prins } 325e98967e1SMark Prins } 326e98967e1SMark Prins if (file_exists($this->geojsonFileName)) { 327e98967e1SMark Prins try { 32857f8d5bbSMark Prins $this->drawGeojson(); 329e98967e1SMark Prins } catch (exception $e) { 330f204b8caSMark Prins Logger::error('failed to load GeoJSON file', $e); 331e98967e1SMark Prins } 332e98967e1SMark Prins } 33357f8d5bbSMark Prins 33457f8d5bbSMark Prins $this->drawCopyright(); 335628e43ccSMark Prins } 336f4b9bdacSMark Prins 337628e43ccSMark Prins /** 338628e43ccSMark Prins */ 339a760825cSgithub-actions[bot] public function initCoords(): void 340a760825cSgithub-actions[bot] { 341628e43ccSMark Prins $this->centerX = $this->lonToTile($this->lon, $this->zoom); 342628e43ccSMark Prins $this->centerY = $this->latToTile($this->lat, $this->zoom); 343628e43ccSMark Prins $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize); 344628e43ccSMark Prins $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize); 345628e43ccSMark Prins } 346628e43ccSMark Prins 347628e43ccSMark Prins /** 34857f8d5bbSMark Prins * 34957f8d5bbSMark Prins * @param float $long 35057f8d5bbSMark Prins * @param int $zoom 3518a541d8fSMark Prins * @return float 35257f8d5bbSMark Prins */ 3538a541d8fSMark Prins public function lonToTile(float $long, int $zoom): float 354a760825cSgithub-actions[bot] { 355a760825cSgithub-actions[bot] return (($long + 180) / 360) * 2 ** $zoom; 35657f8d5bbSMark Prins } 35757f8d5bbSMark Prins 35857f8d5bbSMark Prins /** 35957f8d5bbSMark Prins * 36057f8d5bbSMark Prins * @param float $lat 36157f8d5bbSMark Prins * @param int $zoom 3628a541d8fSMark Prins * @return float 36357f8d5bbSMark Prins */ 3648a541d8fSMark Prins public function latToTile(float $lat, int $zoom): float 365a760825cSgithub-actions[bot] { 366a760825cSgithub-actions[bot] return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * 2 ** $zoom; 36757f8d5bbSMark Prins } 36857f8d5bbSMark Prins 36957f8d5bbSMark Prins /** 370628e43ccSMark Prins * make basemap image. 371628e43ccSMark Prins */ 372a760825cSgithub-actions[bot] public function createBaseMap(): void 373a760825cSgithub-actions[bot] { 374628e43ccSMark Prins $this->image = imagecreatetruecolor($this->width, $this->height); 375628e43ccSMark Prins $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2); 376628e43ccSMark Prins $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2); 377628e43ccSMark Prins $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2); 378628e43ccSMark Prins $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2); 379628e43ccSMark Prins $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize); 380628e43ccSMark Prins $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize); 381628e43ccSMark Prins $this->offsetX += floor($this->width / 2); 382628e43ccSMark Prins $this->offsetY += floor($this->height / 2); 383628e43ccSMark Prins $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize; 384628e43ccSMark Prins $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize; 385628e43ccSMark Prins 386628e43ccSMark Prins for ($x = $startX; $x <= $endX; $x++) { 387628e43ccSMark Prins for ($y = $startY; $y <= $endY; $y++) { 38857f8d5bbSMark Prins $url = str_replace( 389a760825cSgithub-actions[bot] ['{Z}', '{X}', '{Y}'], 390a760825cSgithub-actions[bot] [$this->zoom, $x, $y], 391a760825cSgithub-actions[bot] $this->tileInfo [$this->maptype] ['url'] 39257f8d5bbSMark Prins ); 393c8eb1362SMark Prins 394628e43ccSMark Prins $tileData = $this->fetchTile($url); 395628e43ccSMark Prins if ($tileData) { 396628e43ccSMark Prins $tileImage = imagecreatefromstring($tileData); 397af5cdfa8SMark Prins } 398*ca4a05fbSgithub-actions[bot] if (!$tileData || $tileImage === false || !$tileImage instanceof \GdImage) { 399628e43ccSMark Prins $tileImage = imagecreate($this->tileSize, $this->tileSize); 400628e43ccSMark Prins $color = imagecolorallocate($tileImage, 255, 255, 255); 401628e43ccSMark Prins @imagestring($tileImage, 1, 127, 127, 'err', $color); 402628e43ccSMark Prins } 403af5cdfa8SMark Prins $destX = (int) ($x - $startX) * $this->tileSize + $this->offsetX; 404af5cdfa8SMark Prins $destY = (int) ($y - $startY) * $this->tileSize + $this->offsetY; 405f204b8caSMark Prins Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize); 40657f8d5bbSMark Prins imagecopy( 407a760825cSgithub-actions[bot] $this->image, 408a760825cSgithub-actions[bot] $tileImage, 409a760825cSgithub-actions[bot] $destX, 410a760825cSgithub-actions[bot] $destY, 411a760825cSgithub-actions[bot] 0, 412a760825cSgithub-actions[bot] 0, 413a760825cSgithub-actions[bot] $this->tileSize, 41457f8d5bbSMark Prins $this->tileSize 41557f8d5bbSMark Prins ); 416628e43ccSMark Prins } 417628e43ccSMark Prins } 418628e43ccSMark Prins } 419628e43ccSMark Prins 420628e43ccSMark Prins /** 421af5cdfa8SMark Prins * Fetch a tile (from the source or from the cache) and optionally store it in the cache, 422af5cdfa8SMark Prins * returns false if the tile is not a valid image. 4232d11d700SMark Prins * @param string $url 424257dffd7SMark Prins * @return bool|string 425ebb06a66SMark Prins * @todo refactor this to use dokuwiki\HTTP\HTTPClient or dokuwiki\HTTP\DokuHTTPClient 426ebb06a66SMark Prins * for better proxy handling... 4272d11d700SMark Prins */ 428af5cdfa8SMark Prins public function fetchTile(string $url): bool|string 429a760825cSgithub-actions[bot] { 430af5cdfa8SMark Prins if ($this->useTileCache) { 431af5cdfa8SMark Prins $cached = $this->checkTileCache($url); 432af5cdfa8SMark Prins if ($cached) { 433ab8cbd2bSMark Prins return $cached; 434af5cdfa8SMark Prins } 435af5cdfa8SMark Prins } 436e4f115f4SMark Prins 437e4f115f4SMark Prins $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')'; 438e4f115f4SMark Prins if (function_exists("curl_init")) { 439e4f115f4SMark Prins // use cUrl 440628e43ccSMark Prins $ch = curl_init(); 441628e43ccSMark Prins curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 442e4f115f4SMark Prins curl_setopt($ch, CURLOPT_USERAGENT, $_UA); 4438a541d8fSMark Prins curl_setopt($ch, CURLOPT_REFERER, DOKU_URL); 444628e43ccSMark Prins curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 445b63742deSMark Prins curl_setopt($ch, CURLOPT_URL, $url . $this->apikey); 446af5cdfa8SMark Prins Logger::debug("fetchTile: getting: $url using curl_exec"); 447628e43ccSMark Prins $tile = curl_exec($ch); 448628e43ccSMark Prins curl_close($ch); 449e4f115f4SMark Prins } else { 450e4f115f4SMark Prins // use file_get_contents 451e4f115f4SMark Prins global $conf; 4528a541d8fSMark Prins $opts = ['http' => [ 4538a541d8fSMark Prins 'method' => "GET", 4548a541d8fSMark Prins 'header' => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n" . "Referer: " . DOKU_URL . "\r\n", 4558a541d8fSMark Prins 'request_fulluri' => true 4568a541d8fSMark Prins ]]; 457*ca4a05fbSgithub-actions[bot] if ( 458*ca4a05fbSgithub-actions[bot] isset($conf['proxy']['host'], $conf['proxy']['port']) 459ebb06a66SMark Prins && $conf['proxy']['host'] !== '' 460a760825cSgithub-actions[bot] && $conf['proxy']['port'] !== '' 461a760825cSgithub-actions[bot] ) { 462ebb06a66SMark Prins $opts['http'] += ['proxy' => "tcp://" . $conf['proxy']['host'] . ":" . $conf['proxy']['port']]; 463ebb06a66SMark Prins } 464ebb06a66SMark Prins 465e4f115f4SMark Prins $context = stream_context_create($opts); 466af5cdfa8SMark Prins Logger::debug("fetchTile: getting: $url . $this->apikey using file_get_contents and options", $opts); 467b63742deSMark Prins $tile = file_get_contents($url . $this->apikey, false, $context); 468e4f115f4SMark Prins } 469af5cdfa8SMark Prins 470af5cdfa8SMark Prins // check if we retrieved a valid image 471af5cdfa8SMark Prins if (@imagecreatefromstring($tile) !== false) { 472628e43ccSMark Prins if ($tile && $this->useTileCache) { 473628e43ccSMark Prins $this->writeTileToCache($url, $tile); 474628e43ccSMark Prins } 475628e43ccSMark Prins return $tile; 476628e43ccSMark Prins } 477af5cdfa8SMark Prins return false; 478af5cdfa8SMark Prins } 479628e43ccSMark Prins 480628e43ccSMark Prins /** 48157f8d5bbSMark Prins * 48257f8d5bbSMark Prins * @param string $url 483257dffd7SMark Prins * @return string|false 484628e43ccSMark Prins */ 4858a541d8fSMark Prins public function checkTileCache(string $url): string|false 486a760825cSgithub-actions[bot] { 48757f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 48857f8d5bbSMark Prins if (file_exists($filename)) { 48957f8d5bbSMark Prins return file_get_contents($filename); 49057f8d5bbSMark Prins } 491257dffd7SMark Prins return false; 492628e43ccSMark Prins } 493628e43ccSMark Prins 4946914b920SMark Prins /** 49557f8d5bbSMark Prins * @param string $url 4968a541d8fSMark Prins * @return string 4976914b920SMark Prins */ 498a760825cSgithub-actions[bot] public function tileUrlToFilename(string $url): string 499a760825cSgithub-actions[bot] { 500257dffd7SMark Prins return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1); 50157f8d5bbSMark Prins } 50257f8d5bbSMark Prins 50357f8d5bbSMark Prins /** 50457f8d5bbSMark Prins * Write a tile into the cache. 50557f8d5bbSMark Prins * 50657f8d5bbSMark Prins * @param string $url 50757f8d5bbSMark Prins * @param mixed $data 50857f8d5bbSMark Prins */ 5098a541d8fSMark Prins public function writeTileToCache(string $url, mixed $data): void 510a760825cSgithub-actions[bot] { 51157f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 51257f8d5bbSMark Prins $this->mkdirRecursive(dirname($filename), 0777); 51357f8d5bbSMark Prins file_put_contents($filename, $data); 51457f8d5bbSMark Prins } 51557f8d5bbSMark Prins 51657f8d5bbSMark Prins /** 51757f8d5bbSMark Prins * Recursively create the directory. 51857f8d5bbSMark Prins * 51957f8d5bbSMark Prins * @param string $pathname 52057f8d5bbSMark Prins * The directory path. 52157f8d5bbSMark Prins * @param int $mode 52257f8d5bbSMark Prins * File access mode. For more information on modes, read the details on the chmod manpage. 52357f8d5bbSMark Prins */ 524a760825cSgithub-actions[bot] public function mkdirRecursive(string $pathname, int $mode): bool 525a760825cSgithub-actions[bot] { 526a760825cSgithub-actions[bot] if (!is_dir(dirname($pathname))) { 527a760825cSgithub-actions[bot] $this->mkdirRecursive(dirname($pathname), $mode); 528a760825cSgithub-actions[bot] } 52957f8d5bbSMark Prins return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname); 53057f8d5bbSMark Prins } 53157f8d5bbSMark Prins 53257f8d5bbSMark Prins /** 53357f8d5bbSMark Prins * Place markers on the map and number them in the same order as they are listed in the html. 53457f8d5bbSMark Prins */ 535a760825cSgithub-actions[bot] public function placeMarkers(): void 536a760825cSgithub-actions[bot] { 53757f8d5bbSMark Prins $count = 0; 53857f8d5bbSMark Prins $color = imagecolorallocate($this->image, 0, 0, 0); 53957f8d5bbSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 54057f8d5bbSMark Prins $markerBaseDir = __DIR__ . '/icons'; 541e98967e1SMark Prins $markerImageOffsetX = 0; 542e98967e1SMark Prins $markerImageOffsetY = 0; 543e98967e1SMark Prins $markerShadowOffsetX = 0; 544e98967e1SMark Prins $markerShadowOffsetY = 0; 545e98967e1SMark Prins $markerShadowImg = null; 54657f8d5bbSMark Prins // loop thru marker array 54757f8d5bbSMark Prins foreach ($this->markers as $marker) { 54857f8d5bbSMark Prins // set some local variables 54957f8d5bbSMark Prins $markerLat = $marker ['lat']; 55057f8d5bbSMark Prins $markerLon = $marker ['lon']; 55157f8d5bbSMark Prins $markerType = $marker ['type']; 55257f8d5bbSMark Prins // clear variables from previous loops 55357f8d5bbSMark Prins $markerFilename = ''; 55457f8d5bbSMark Prins $markerShadow = ''; 55557f8d5bbSMark Prins $matches = false; 55657f8d5bbSMark Prins // check for marker type, get settings from markerPrototypes 55757f8d5bbSMark Prins if ($markerType) { 55857f8d5bbSMark Prins foreach ($this->markerPrototypes as $markerPrototype) { 55957f8d5bbSMark Prins if (preg_match($markerPrototype ['regex'], $markerType, $matches)) { 56057f8d5bbSMark Prins $markerFilename = $matches [0] . $markerPrototype ['extension']; 56157f8d5bbSMark Prins if ($markerPrototype ['offsetImage']) { 562a760825cSgithub-actions[bot] [$markerImageOffsetX, $markerImageOffsetY] = explode( 56357f8d5bbSMark Prins ",", 56457f8d5bbSMark Prins $markerPrototype ['offsetImage'] 56557f8d5bbSMark Prins ); 56657f8d5bbSMark Prins } 56757f8d5bbSMark Prins $markerShadow = $markerPrototype ['shadow']; 56857f8d5bbSMark Prins if ($markerShadow) { 569a760825cSgithub-actions[bot] [$markerShadowOffsetX, $markerShadowOffsetY] = explode( 57057f8d5bbSMark Prins ",", 57157f8d5bbSMark Prins $markerPrototype ['offsetShadow'] 57257f8d5bbSMark Prins ); 57357f8d5bbSMark Prins } 57457f8d5bbSMark Prins } 57557f8d5bbSMark Prins } 57657f8d5bbSMark Prins } 57757f8d5bbSMark Prins // create img resource 57857f8d5bbSMark Prins if (file_exists($markerBaseDir . '/' . $markerFilename)) { 57957f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename); 58057f8d5bbSMark Prins } else { 58157f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png'); 58257f8d5bbSMark Prins } 58357f8d5bbSMark Prins // check for shadow + create shadow recource 58457f8d5bbSMark Prins if ($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) { 58557f8d5bbSMark Prins $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow); 58657f8d5bbSMark Prins } 58757f8d5bbSMark Prins // calc position 588af5cdfa8SMark Prins $destX = (int) floor( 58957f8d5bbSMark Prins ($this->width / 2) - 59057f8d5bbSMark Prins $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)) 59157f8d5bbSMark Prins ); 592af5cdfa8SMark Prins $destY = (int) floor( 59357f8d5bbSMark Prins ($this->height / 2) - 59457f8d5bbSMark Prins $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)) 59557f8d5bbSMark Prins ); 59657f8d5bbSMark Prins // copy shadow on basemap 59757f8d5bbSMark Prins if ($markerShadow && $markerShadowImg) { 59857f8d5bbSMark Prins imagecopy( 59957f8d5bbSMark Prins $this->image, 60057f8d5bbSMark Prins $markerShadowImg, 60157f8d5bbSMark Prins $destX + (int) $markerShadowOffsetX, 60257f8d5bbSMark Prins $destY + (int) $markerShadowOffsetY, 60357f8d5bbSMark Prins 0, 60457f8d5bbSMark Prins 0, 60557f8d5bbSMark Prins imagesx($markerShadowImg), 60657f8d5bbSMark Prins imagesy($markerShadowImg) 60757f8d5bbSMark Prins ); 60857f8d5bbSMark Prins } 60957f8d5bbSMark Prins // copy marker on basemap above shadow 61057f8d5bbSMark Prins imagecopy( 61157f8d5bbSMark Prins $this->image, 61257f8d5bbSMark Prins $markerImg, 61357f8d5bbSMark Prins $destX + (int) $markerImageOffsetX, 61457f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 61557f8d5bbSMark Prins 0, 61657f8d5bbSMark Prins 0, 61757f8d5bbSMark Prins imagesx($markerImg), 61857f8d5bbSMark Prins imagesy($markerImg) 61957f8d5bbSMark Prins ); 62057f8d5bbSMark Prins // add label 62157f8d5bbSMark Prins imagestring( 62257f8d5bbSMark Prins $this->image, 62357f8d5bbSMark Prins 3, 62457f8d5bbSMark Prins $destX - imagesx($markerImg) + 1, 62557f8d5bbSMark Prins $destY + (int) $markerImageOffsetY + 1, 62657f8d5bbSMark Prins ++$count, 62757f8d5bbSMark Prins $bgcolor 62857f8d5bbSMark Prins ); 62957f8d5bbSMark Prins imagestring( 63057f8d5bbSMark Prins $this->image, 63157f8d5bbSMark Prins 3, 63257f8d5bbSMark Prins $destX - imagesx($markerImg), 63357f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 63457f8d5bbSMark Prins $count, 63557f8d5bbSMark Prins $color 63657f8d5bbSMark Prins ); 63757f8d5bbSMark Prins } 6386914b920SMark Prins } 63957e65445SMark Prins 640628e43ccSMark Prins /** 641628e43ccSMark Prins * Draw kml trace on the map. 6428a541d8fSMark Prins * @throws Exception when loading the KML fails 643628e43ccSMark Prins */ 644a760825cSgithub-actions[bot] public function drawKML(): void 645a760825cSgithub-actions[bot] { 6462d11d700SMark Prins // TODO get colour from kml node (not currently supported in geoPHP) 647c977deacSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127); 648c977deacSMark Prins $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 649c977deacSMark Prins $this->drawGeometry($kmlgeom, $col); 650c977deacSMark Prins } 65157e65445SMark Prins 652c977deacSMark Prins /** 653c977deacSMark Prins * Draw geometry or geometry collection on the map. 654ab8cbd2bSMark Prins * 655c977deacSMark Prins * @param Geometry $geom 656ab8cbd2bSMark Prins * @param int $colour 657ab8cbd2bSMark Prins * drawing colour 658c977deacSMark Prins */ 659a760825cSgithub-actions[bot] private function drawGeometry(Geometry $geom, int $colour): void 660a760825cSgithub-actions[bot] { 66157f8d5bbSMark Prins if (empty($geom)) { 66257f8d5bbSMark Prins return; 66357f8d5bbSMark Prins } 664e5840fc5SMark Prins 6656c6bb022SMark Prins switch ($geom->geometryType()) { 666c977deacSMark Prins case 'GeometryCollection': 667c977deacSMark Prins // recursively draw part of the collection 668c977deacSMark Prins for ($i = 1; $i < $geom->numGeometries() + 1; $i++) { 669c977deacSMark Prins $_geom = $geom->geometryN($i); 670c977deacSMark Prins $this->drawGeometry($_geom, $colour); 671c977deacSMark Prins } 6726c6bb022SMark Prins break; 6736c6bb022SMark Prins case 'Polygon': 674c977deacSMark Prins $this->drawPolygon($geom, $colour); 675c977deacSMark Prins break; 676c977deacSMark Prins case 'LineString': 677c977deacSMark Prins $this->drawLineString($geom, $colour); 678c977deacSMark Prins break; 679c977deacSMark Prins case 'Point': 680c977deacSMark Prins $this->drawPoint($geom, $colour); 6816c6bb022SMark Prins break; 682ad8fb8a2SMark Prins // TODO implement / do nothing 683ad8fb8a2SMark Prins case 'MultiPolygon': 684ad8fb8a2SMark Prins case 'MultiLineString': 685ad8fb8a2SMark Prins case 'MultiPoint': 6866c6bb022SMark Prins default: 6872d11d700SMark Prins // draw nothing 6886c6bb022SMark Prins break; 6896c6bb022SMark Prins } 6906c6bb022SMark Prins } 691c977deacSMark Prins 692e61425c7SMark Prins /** 693e61425c7SMark Prins * Draw a polygon on the map. 694ab8cbd2bSMark Prins * 695e61425c7SMark Prins * @param Polygon $polygon 696ab8cbd2bSMark Prins * @param int $colour 697ab8cbd2bSMark Prins * drawing colour 698e61425c7SMark Prins */ 6998a541d8fSMark Prins private function drawPolygon(Polygon $polygon, int $colour): void 700a760825cSgithub-actions[bot] { 701c977deacSMark Prins // TODO implementation of drawing holes, 702c977deacSMark Prins // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol? 703c977deacSMark Prins 704c977deacSMark Prins // print_r('Polygon:<br />'); 705c977deacSMark Prins // print_r($polygon); 706a760825cSgithub-actions[bot] $extPoints = []; 7078a541d8fSMark Prins // extRing is a linestring actually... 708c977deacSMark Prins $extRing = $polygon->exteriorRing(); 709c977deacSMark Prins 710c977deacSMark Prins for ($i = 1; $i < $extRing->numGeometries(); $i++) { 711c977deacSMark Prins $p1 = $extRing->geometryN($i); 71257f8d5bbSMark Prins $x = floor( 71357f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 71457f8d5bbSMark Prins ); 71557f8d5bbSMark Prins $y = floor( 71657f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 71757f8d5bbSMark Prins ); 718c977deacSMark Prins $extPoints [] = $x; 719c977deacSMark Prins $extPoints [] = $y; 720e61425c7SMark Prins } 721c977deacSMark Prins // print_r('points:('.($i-1).')<br />'); 722c977deacSMark Prins // print_r($extPoints); 723c977deacSMark Prins // imagepolygon ($this->image, $extPoints, $i-1, $colour ); 724c977deacSMark Prins imagefilledpolygon($this->image, $extPoints, $i - 1, $colour); 725c977deacSMark Prins } 726c977deacSMark Prins 727628e43ccSMark Prins /** 72857f8d5bbSMark Prins * Draw a line on the map. 72957f8d5bbSMark Prins * 73057f8d5bbSMark Prins * @param LineString $line 73157f8d5bbSMark Prins * @param int $colour 73257f8d5bbSMark Prins * drawing colour 73357f8d5bbSMark Prins */ 7348a541d8fSMark Prins private function drawLineString(LineString $line, int $colour): void 735a760825cSgithub-actions[bot] { 73657f8d5bbSMark Prins imagesetthickness($this->image, 2); 73757f8d5bbSMark Prins for ($p = 1; $p < $line->numGeometries(); $p++) { 73857f8d5bbSMark Prins // get first pair of points 73957f8d5bbSMark Prins $p1 = $line->geometryN($p); 74057f8d5bbSMark Prins $p2 = $line->geometryN($p + 1); 74157f8d5bbSMark Prins // translate to paper space 74257f8d5bbSMark Prins $x1 = floor( 74357f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 74457f8d5bbSMark Prins ); 74557f8d5bbSMark Prins $y1 = floor( 74657f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 74757f8d5bbSMark Prins ); 74857f8d5bbSMark Prins $x2 = floor( 74957f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom)) 75057f8d5bbSMark Prins ); 75157f8d5bbSMark Prins $y2 = floor( 75257f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom)) 75357f8d5bbSMark Prins ); 75457f8d5bbSMark Prins // draw to image 75557f8d5bbSMark Prins imageline($this->image, $x1, $y1, $x2, $y2, $colour); 75657f8d5bbSMark Prins } 75757f8d5bbSMark Prins imagesetthickness($this->image, 1); 75857f8d5bbSMark Prins } 75957f8d5bbSMark Prins 76057f8d5bbSMark Prins /** 76157f8d5bbSMark Prins * Draw a point on the map. 76257f8d5bbSMark Prins * 76357f8d5bbSMark Prins * @param Point $point 76457f8d5bbSMark Prins * @param int $colour 76557f8d5bbSMark Prins * drawing colour 76657f8d5bbSMark Prins */ 7678a541d8fSMark Prins private function drawPoint(Point $point, int $colour): void 768a760825cSgithub-actions[bot] { 76957f8d5bbSMark Prins imagesetthickness($this->image, 2); 77057f8d5bbSMark Prins // translate to paper space 77157f8d5bbSMark Prins $cx = floor( 77257f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom)) 77357f8d5bbSMark Prins ); 77457f8d5bbSMark Prins $cy = floor( 77557f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom)) 77657f8d5bbSMark Prins ); 77757f8d5bbSMark Prins $r = 5; 77857f8d5bbSMark Prins // draw to image 77957f8d5bbSMark Prins // imageellipse($this->image, $cx, $cy,$r, $r, $colour); 78057f8d5bbSMark Prins imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour); 78157f8d5bbSMark Prins // don't use imageellipse because the imagesetthickness function has 78257f8d5bbSMark Prins // no effect. So the better workaround is to use imagearc. 78357f8d5bbSMark Prins imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour); 78457f8d5bbSMark Prins imagesetthickness($this->image, 1); 78557f8d5bbSMark Prins } 78657f8d5bbSMark Prins 78757f8d5bbSMark Prins /** 78857f8d5bbSMark Prins * Draw gpx trace on the map. 7898a541d8fSMark Prins * @throws Exception when loading the GPX fails 79057f8d5bbSMark Prins */ 7918a541d8fSMark Prins public function drawGPX(): void 792a760825cSgithub-actions[bot] { 79357f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127); 79457f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 79557f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 79657f8d5bbSMark Prins } 79757f8d5bbSMark Prins 79857f8d5bbSMark Prins /** 79957f8d5bbSMark Prins * Draw geojson on the map. 8008a541d8fSMark Prins * @throws Exception when loading the JSON fails 80157f8d5bbSMark Prins */ 8028a541d8fSMark Prins public function drawGeojson(): void 803a760825cSgithub-actions[bot] { 80457f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127); 80557f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json'); 80657f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 80757f8d5bbSMark Prins } 80857f8d5bbSMark Prins 80957f8d5bbSMark Prins /** 810628e43ccSMark Prins * add copyright and origin notice and icons to the map. 811628e43ccSMark Prins */ 8128a541d8fSMark Prins public function drawCopyright(): void 813a760825cSgithub-actions[bot] { 814a760825cSgithub-actions[bot] $logoBaseDir = __DIR__ . '/' . 'logo/'; 815628e43ccSMark Prins $logoImg = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']); 816628e43ccSMark Prins $textcolor = imagecolorallocate($this->image, 0, 0, 0); 817628e43ccSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 818628e43ccSMark Prins 81957f8d5bbSMark Prins imagecopy( 82057f8d5bbSMark Prins $this->image, 82157f8d5bbSMark Prins $logoImg, 82257f8d5bbSMark Prins 0, 82357f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 82457f8d5bbSMark Prins 0, 82557f8d5bbSMark Prins 0, 82657f8d5bbSMark Prins imagesx($logoImg), 82757f8d5bbSMark Prins imagesy($logoImg) 82857f8d5bbSMark Prins ); 82957f8d5bbSMark Prins imagestring( 83057f8d5bbSMark Prins $this->image, 83157f8d5bbSMark Prins 1, 83257f8d5bbSMark Prins imagesx($logoImg) + 2, 83357f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg) + 1, 83457f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 83557f8d5bbSMark Prins $bgcolor 83657f8d5bbSMark Prins ); 83757f8d5bbSMark Prins imagestring( 83857f8d5bbSMark Prins $this->image, 83957f8d5bbSMark Prins 1, 84057f8d5bbSMark Prins imagesx($logoImg) + 1, 84157f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 84257f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 84357f8d5bbSMark Prins $textcolor 84457f8d5bbSMark Prins ); 845628e43ccSMark Prins 846628e43ccSMark Prins // additional tile source info, ie. who created/hosted the tiles 847257dffd7SMark Prins $xIconOffset = 0; 848257dffd7SMark Prins if ($this->maptype === 'openstreetmap') { 849257dffd7SMark Prins $mapAuthor = "(c) OpenStreetMap maps/CC BY-SA"; 850257dffd7SMark Prins } else { 851257dffd7SMark Prins $mapAuthor = $this->tileInfo [$this->maptype] ['txt']; 852628e43ccSMark Prins $iconImg = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']); 853257dffd7SMark Prins $xIconOffset = imagesx($iconImg); 85457f8d5bbSMark Prins imagecopy( 85557f8d5bbSMark Prins $this->image, 856a760825cSgithub-actions[bot] $iconImg, 857a760825cSgithub-actions[bot] imagesx($logoImg) + 1, 85857f8d5bbSMark Prins imagesy($this->image) - imagesy($iconImg), 85957f8d5bbSMark Prins 0, 86057f8d5bbSMark Prins 0, 861a760825cSgithub-actions[bot] imagesx($iconImg), 862a760825cSgithub-actions[bot] imagesy($iconImg) 86357f8d5bbSMark Prins ); 864257dffd7SMark Prins } 86557f8d5bbSMark Prins imagestring( 86657f8d5bbSMark Prins $this->image, 867a760825cSgithub-actions[bot] 1, 868a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 4, 86957f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1, 870257dffd7SMark Prins $mapAuthor, 87157f8d5bbSMark Prins $bgcolor 87257f8d5bbSMark Prins ); 87357f8d5bbSMark Prins imagestring( 87457f8d5bbSMark Prins $this->image, 875a760825cSgithub-actions[bot] 1, 876a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 3, 87757f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2), 878257dffd7SMark Prins $mapAuthor, 87957f8d5bbSMark Prins $textcolor 88057f8d5bbSMark Prins ); 881628e43ccSMark Prins } 882628e43ccSMark Prins} 883