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] "&", 277*af5cdfa8SMark Prins [ 278*af5cdfa8SMark Prins $this->zoom, 279*af5cdfa8SMark Prins $this->lat, 280*af5cdfa8SMark Prins $this->lon, 281*af5cdfa8SMark Prins $this->width, 282*af5cdfa8SMark Prins $this->height, 283*af5cdfa8SMark Prins serialize($this->markers), 284*af5cdfa8SMark Prins $this->maptype, 285*af5cdfa8SMark Prins $this->kmlFileName, 286*af5cdfa8SMark Prins $this->gpxFileName, 287*af5cdfa8SMark Prins $this->geojsonFileName 288*af5cdfa8SMark 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); 397*af5cdfa8SMark Prins } 398*af5cdfa8SMark Prins if (!$tileData || $tileImage === false || $tileImage === null) { 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 } 403*af5cdfa8SMark Prins $destX = (int) ($x - $startX) * $this->tileSize + $this->offsetX; 404*af5cdfa8SMark 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 /** 421*af5cdfa8SMark Prins * Fetch a tile (from the source or from the cache) and optionally store it in the cache, 422*af5cdfa8SMark 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 */ 428*af5cdfa8SMark Prins public function fetchTile(string $url): bool|string 429a760825cSgithub-actions[bot] { 430*af5cdfa8SMark Prins if ($this->useTileCache) { 431*af5cdfa8SMark Prins $cached = $this->checkTileCache($url); 432*af5cdfa8SMark Prins if ($cached) { 433ab8cbd2bSMark Prins return $cached; 434*af5cdfa8SMark Prins } 435*af5cdfa8SMark 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); 446*af5cdfa8SMark 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 ]]; 4578a541d8fSMark Prins if (isset($conf['proxy']['host'], $conf['proxy']['port']) 458ebb06a66SMark Prins && $conf['proxy']['host'] !== '' 459a760825cSgithub-actions[bot] && $conf['proxy']['port'] !== '' 460a760825cSgithub-actions[bot] ) { 461ebb06a66SMark Prins $opts['http'] += ['proxy' => "tcp://" . $conf['proxy']['host'] . ":" . $conf['proxy']['port']]; 462ebb06a66SMark Prins } 463ebb06a66SMark Prins 464e4f115f4SMark Prins $context = stream_context_create($opts); 465*af5cdfa8SMark Prins Logger::debug("fetchTile: getting: $url . $this->apikey using file_get_contents and options", $opts); 466b63742deSMark Prins $tile = file_get_contents($url . $this->apikey, false, $context); 467e4f115f4SMark Prins } 468*af5cdfa8SMark Prins 469*af5cdfa8SMark Prins // check if we retrieved a valid image 470*af5cdfa8SMark Prins if (@imagecreatefromstring($tile) !== false) { 471628e43ccSMark Prins if ($tile && $this->useTileCache) { 472628e43ccSMark Prins $this->writeTileToCache($url, $tile); 473628e43ccSMark Prins } 474628e43ccSMark Prins return $tile; 475628e43ccSMark Prins } 476*af5cdfa8SMark Prins return false; 477*af5cdfa8SMark Prins } 478628e43ccSMark Prins 479628e43ccSMark Prins /** 48057f8d5bbSMark Prins * 48157f8d5bbSMark Prins * @param string $url 482257dffd7SMark Prins * @return string|false 483628e43ccSMark Prins */ 4848a541d8fSMark Prins public function checkTileCache(string $url): string|false 485a760825cSgithub-actions[bot] { 48657f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 48757f8d5bbSMark Prins if (file_exists($filename)) { 48857f8d5bbSMark Prins return file_get_contents($filename); 48957f8d5bbSMark Prins } 490257dffd7SMark Prins return false; 491628e43ccSMark Prins } 492628e43ccSMark Prins 4936914b920SMark Prins /** 49457f8d5bbSMark Prins * @param string $url 4958a541d8fSMark Prins * @return string 4966914b920SMark Prins */ 497a760825cSgithub-actions[bot] public function tileUrlToFilename(string $url): string 498a760825cSgithub-actions[bot] { 499257dffd7SMark Prins return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1); 50057f8d5bbSMark Prins } 50157f8d5bbSMark Prins 50257f8d5bbSMark Prins /** 50357f8d5bbSMark Prins * Write a tile into the cache. 50457f8d5bbSMark Prins * 50557f8d5bbSMark Prins * @param string $url 50657f8d5bbSMark Prins * @param mixed $data 50757f8d5bbSMark Prins */ 5088a541d8fSMark Prins public function writeTileToCache(string $url, mixed $data): void 509a760825cSgithub-actions[bot] { 51057f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 51157f8d5bbSMark Prins $this->mkdirRecursive(dirname($filename), 0777); 51257f8d5bbSMark Prins file_put_contents($filename, $data); 51357f8d5bbSMark Prins } 51457f8d5bbSMark Prins 51557f8d5bbSMark Prins /** 51657f8d5bbSMark Prins * Recursively create the directory. 51757f8d5bbSMark Prins * 51857f8d5bbSMark Prins * @param string $pathname 51957f8d5bbSMark Prins * The directory path. 52057f8d5bbSMark Prins * @param int $mode 52157f8d5bbSMark Prins * File access mode. For more information on modes, read the details on the chmod manpage. 52257f8d5bbSMark Prins */ 523a760825cSgithub-actions[bot] public function mkdirRecursive(string $pathname, int $mode): bool 524a760825cSgithub-actions[bot] { 525a760825cSgithub-actions[bot] if (!is_dir(dirname($pathname))) { 526a760825cSgithub-actions[bot] $this->mkdirRecursive(dirname($pathname), $mode); 527a760825cSgithub-actions[bot] } 52857f8d5bbSMark Prins return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname); 52957f8d5bbSMark Prins } 53057f8d5bbSMark Prins 53157f8d5bbSMark Prins /** 53257f8d5bbSMark Prins * Place markers on the map and number them in the same order as they are listed in the html. 53357f8d5bbSMark Prins */ 534a760825cSgithub-actions[bot] public function placeMarkers(): void 535a760825cSgithub-actions[bot] { 53657f8d5bbSMark Prins $count = 0; 53757f8d5bbSMark Prins $color = imagecolorallocate($this->image, 0, 0, 0); 53857f8d5bbSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 53957f8d5bbSMark Prins $markerBaseDir = __DIR__ . '/icons'; 540e98967e1SMark Prins $markerImageOffsetX = 0; 541e98967e1SMark Prins $markerImageOffsetY = 0; 542e98967e1SMark Prins $markerShadowOffsetX = 0; 543e98967e1SMark Prins $markerShadowOffsetY = 0; 544e98967e1SMark Prins $markerShadowImg = null; 54557f8d5bbSMark Prins // loop thru marker array 54657f8d5bbSMark Prins foreach ($this->markers as $marker) { 54757f8d5bbSMark Prins // set some local variables 54857f8d5bbSMark Prins $markerLat = $marker ['lat']; 54957f8d5bbSMark Prins $markerLon = $marker ['lon']; 55057f8d5bbSMark Prins $markerType = $marker ['type']; 55157f8d5bbSMark Prins // clear variables from previous loops 55257f8d5bbSMark Prins $markerFilename = ''; 55357f8d5bbSMark Prins $markerShadow = ''; 55457f8d5bbSMark Prins $matches = false; 55557f8d5bbSMark Prins // check for marker type, get settings from markerPrototypes 55657f8d5bbSMark Prins if ($markerType) { 55757f8d5bbSMark Prins foreach ($this->markerPrototypes as $markerPrototype) { 55857f8d5bbSMark Prins if (preg_match($markerPrototype ['regex'], $markerType, $matches)) { 55957f8d5bbSMark Prins $markerFilename = $matches [0] . $markerPrototype ['extension']; 56057f8d5bbSMark Prins if ($markerPrototype ['offsetImage']) { 561a760825cSgithub-actions[bot] [$markerImageOffsetX, $markerImageOffsetY] = explode( 56257f8d5bbSMark Prins ",", 56357f8d5bbSMark Prins $markerPrototype ['offsetImage'] 56457f8d5bbSMark Prins ); 56557f8d5bbSMark Prins } 56657f8d5bbSMark Prins $markerShadow = $markerPrototype ['shadow']; 56757f8d5bbSMark Prins if ($markerShadow) { 568a760825cSgithub-actions[bot] [$markerShadowOffsetX, $markerShadowOffsetY] = explode( 56957f8d5bbSMark Prins ",", 57057f8d5bbSMark Prins $markerPrototype ['offsetShadow'] 57157f8d5bbSMark Prins ); 57257f8d5bbSMark Prins } 57357f8d5bbSMark Prins } 57457f8d5bbSMark Prins } 57557f8d5bbSMark Prins } 57657f8d5bbSMark Prins // create img resource 57757f8d5bbSMark Prins if (file_exists($markerBaseDir . '/' . $markerFilename)) { 57857f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename); 57957f8d5bbSMark Prins } else { 58057f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png'); 58157f8d5bbSMark Prins } 58257f8d5bbSMark Prins // check for shadow + create shadow recource 58357f8d5bbSMark Prins if ($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) { 58457f8d5bbSMark Prins $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow); 58557f8d5bbSMark Prins } 58657f8d5bbSMark Prins // calc position 587*af5cdfa8SMark Prins $destX = (int) floor( 58857f8d5bbSMark Prins ($this->width / 2) - 58957f8d5bbSMark Prins $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)) 59057f8d5bbSMark Prins ); 591*af5cdfa8SMark Prins $destY = (int) floor( 59257f8d5bbSMark Prins ($this->height / 2) - 59357f8d5bbSMark Prins $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)) 59457f8d5bbSMark Prins ); 59557f8d5bbSMark Prins // copy shadow on basemap 59657f8d5bbSMark Prins if ($markerShadow && $markerShadowImg) { 59757f8d5bbSMark Prins imagecopy( 59857f8d5bbSMark Prins $this->image, 59957f8d5bbSMark Prins $markerShadowImg, 60057f8d5bbSMark Prins $destX + (int) $markerShadowOffsetX, 60157f8d5bbSMark Prins $destY + (int) $markerShadowOffsetY, 60257f8d5bbSMark Prins 0, 60357f8d5bbSMark Prins 0, 60457f8d5bbSMark Prins imagesx($markerShadowImg), 60557f8d5bbSMark Prins imagesy($markerShadowImg) 60657f8d5bbSMark Prins ); 60757f8d5bbSMark Prins } 60857f8d5bbSMark Prins // copy marker on basemap above shadow 60957f8d5bbSMark Prins imagecopy( 61057f8d5bbSMark Prins $this->image, 61157f8d5bbSMark Prins $markerImg, 61257f8d5bbSMark Prins $destX + (int) $markerImageOffsetX, 61357f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 61457f8d5bbSMark Prins 0, 61557f8d5bbSMark Prins 0, 61657f8d5bbSMark Prins imagesx($markerImg), 61757f8d5bbSMark Prins imagesy($markerImg) 61857f8d5bbSMark Prins ); 61957f8d5bbSMark Prins // add label 62057f8d5bbSMark Prins imagestring( 62157f8d5bbSMark Prins $this->image, 62257f8d5bbSMark Prins 3, 62357f8d5bbSMark Prins $destX - imagesx($markerImg) + 1, 62457f8d5bbSMark Prins $destY + (int) $markerImageOffsetY + 1, 62557f8d5bbSMark Prins ++$count, 62657f8d5bbSMark Prins $bgcolor 62757f8d5bbSMark Prins ); 62857f8d5bbSMark Prins imagestring( 62957f8d5bbSMark Prins $this->image, 63057f8d5bbSMark Prins 3, 63157f8d5bbSMark Prins $destX - imagesx($markerImg), 63257f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 63357f8d5bbSMark Prins $count, 63457f8d5bbSMark Prins $color 63557f8d5bbSMark Prins ); 63657f8d5bbSMark Prins } 6376914b920SMark Prins } 63857e65445SMark Prins 639628e43ccSMark Prins /** 640628e43ccSMark Prins * Draw kml trace on the map. 6418a541d8fSMark Prins * @throws Exception when loading the KML fails 642628e43ccSMark Prins */ 643a760825cSgithub-actions[bot] public function drawKML(): void 644a760825cSgithub-actions[bot] { 6452d11d700SMark Prins // TODO get colour from kml node (not currently supported in geoPHP) 646c977deacSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127); 647c977deacSMark Prins $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 648c977deacSMark Prins $this->drawGeometry($kmlgeom, $col); 649c977deacSMark Prins } 65057e65445SMark Prins 651c977deacSMark Prins /** 652c977deacSMark Prins * Draw geometry or geometry collection on the map. 653ab8cbd2bSMark Prins * 654c977deacSMark Prins * @param Geometry $geom 655ab8cbd2bSMark Prins * @param int $colour 656ab8cbd2bSMark Prins * drawing colour 657c977deacSMark Prins */ 658a760825cSgithub-actions[bot] private function drawGeometry(Geometry $geom, int $colour): void 659a760825cSgithub-actions[bot] { 66057f8d5bbSMark Prins if (empty($geom)) { 66157f8d5bbSMark Prins return; 66257f8d5bbSMark Prins } 663e5840fc5SMark Prins 6646c6bb022SMark Prins switch ($geom->geometryType()) { 665c977deacSMark Prins case 'GeometryCollection': 666c977deacSMark Prins // recursively draw part of the collection 667c977deacSMark Prins for ($i = 1; $i < $geom->numGeometries() + 1; $i++) { 668c977deacSMark Prins $_geom = $geom->geometryN($i); 669c977deacSMark Prins $this->drawGeometry($_geom, $colour); 670c977deacSMark Prins } 6716c6bb022SMark Prins break; 6726c6bb022SMark Prins case 'Polygon': 673c977deacSMark Prins $this->drawPolygon($geom, $colour); 674c977deacSMark Prins break; 675c977deacSMark Prins case 'LineString': 676c977deacSMark Prins $this->drawLineString($geom, $colour); 677c977deacSMark Prins break; 678c977deacSMark Prins case 'Point': 679c977deacSMark Prins $this->drawPoint($geom, $colour); 6806c6bb022SMark Prins break; 681ad8fb8a2SMark Prins // TODO implement / do nothing 682ad8fb8a2SMark Prins case 'MultiPolygon': 683ad8fb8a2SMark Prins case 'MultiLineString': 684ad8fb8a2SMark Prins case 'MultiPoint': 6856c6bb022SMark Prins default: 6862d11d700SMark Prins // draw nothing 6876c6bb022SMark Prins break; 6886c6bb022SMark Prins } 6896c6bb022SMark Prins } 690c977deacSMark Prins 691e61425c7SMark Prins /** 692e61425c7SMark Prins * Draw a polygon on the map. 693ab8cbd2bSMark Prins * 694e61425c7SMark Prins * @param Polygon $polygon 695ab8cbd2bSMark Prins * @param int $colour 696ab8cbd2bSMark Prins * drawing colour 697e61425c7SMark Prins */ 6988a541d8fSMark Prins private function drawPolygon(Polygon $polygon, int $colour): void 699a760825cSgithub-actions[bot] { 700c977deacSMark Prins // TODO implementation of drawing holes, 701c977deacSMark Prins // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol? 702c977deacSMark Prins 703c977deacSMark Prins // print_r('Polygon:<br />'); 704c977deacSMark Prins // print_r($polygon); 705a760825cSgithub-actions[bot] $extPoints = []; 7068a541d8fSMark Prins // extRing is a linestring actually... 707c977deacSMark Prins $extRing = $polygon->exteriorRing(); 708c977deacSMark Prins 709c977deacSMark Prins for ($i = 1; $i < $extRing->numGeometries(); $i++) { 710c977deacSMark Prins $p1 = $extRing->geometryN($i); 71157f8d5bbSMark Prins $x = floor( 71257f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 71357f8d5bbSMark Prins ); 71457f8d5bbSMark Prins $y = floor( 71557f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 71657f8d5bbSMark Prins ); 717c977deacSMark Prins $extPoints [] = $x; 718c977deacSMark Prins $extPoints [] = $y; 719e61425c7SMark Prins } 720c977deacSMark Prins // print_r('points:('.($i-1).')<br />'); 721c977deacSMark Prins // print_r($extPoints); 722c977deacSMark Prins // imagepolygon ($this->image, $extPoints, $i-1, $colour ); 723c977deacSMark Prins imagefilledpolygon($this->image, $extPoints, $i - 1, $colour); 724c977deacSMark Prins } 725c977deacSMark Prins 726628e43ccSMark Prins /** 72757f8d5bbSMark Prins * Draw a line on the map. 72857f8d5bbSMark Prins * 72957f8d5bbSMark Prins * @param LineString $line 73057f8d5bbSMark Prins * @param int $colour 73157f8d5bbSMark Prins * drawing colour 73257f8d5bbSMark Prins */ 7338a541d8fSMark Prins private function drawLineString(LineString $line, int $colour): void 734a760825cSgithub-actions[bot] { 73557f8d5bbSMark Prins imagesetthickness($this->image, 2); 73657f8d5bbSMark Prins for ($p = 1; $p < $line->numGeometries(); $p++) { 73757f8d5bbSMark Prins // get first pair of points 73857f8d5bbSMark Prins $p1 = $line->geometryN($p); 73957f8d5bbSMark Prins $p2 = $line->geometryN($p + 1); 74057f8d5bbSMark Prins // translate to paper space 74157f8d5bbSMark Prins $x1 = floor( 74257f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 74357f8d5bbSMark Prins ); 74457f8d5bbSMark Prins $y1 = floor( 74557f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 74657f8d5bbSMark Prins ); 74757f8d5bbSMark Prins $x2 = floor( 74857f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom)) 74957f8d5bbSMark Prins ); 75057f8d5bbSMark Prins $y2 = floor( 75157f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom)) 75257f8d5bbSMark Prins ); 75357f8d5bbSMark Prins // draw to image 75457f8d5bbSMark Prins imageline($this->image, $x1, $y1, $x2, $y2, $colour); 75557f8d5bbSMark Prins } 75657f8d5bbSMark Prins imagesetthickness($this->image, 1); 75757f8d5bbSMark Prins } 75857f8d5bbSMark Prins 75957f8d5bbSMark Prins /** 76057f8d5bbSMark Prins * Draw a point on the map. 76157f8d5bbSMark Prins * 76257f8d5bbSMark Prins * @param Point $point 76357f8d5bbSMark Prins * @param int $colour 76457f8d5bbSMark Prins * drawing colour 76557f8d5bbSMark Prins */ 7668a541d8fSMark Prins private function drawPoint(Point $point, int $colour): void 767a760825cSgithub-actions[bot] { 76857f8d5bbSMark Prins imagesetthickness($this->image, 2); 76957f8d5bbSMark Prins // translate to paper space 77057f8d5bbSMark Prins $cx = floor( 77157f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom)) 77257f8d5bbSMark Prins ); 77357f8d5bbSMark Prins $cy = floor( 77457f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom)) 77557f8d5bbSMark Prins ); 77657f8d5bbSMark Prins $r = 5; 77757f8d5bbSMark Prins // draw to image 77857f8d5bbSMark Prins // imageellipse($this->image, $cx, $cy,$r, $r, $colour); 77957f8d5bbSMark Prins imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour); 78057f8d5bbSMark Prins // don't use imageellipse because the imagesetthickness function has 78157f8d5bbSMark Prins // no effect. So the better workaround is to use imagearc. 78257f8d5bbSMark Prins imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour); 78357f8d5bbSMark Prins imagesetthickness($this->image, 1); 78457f8d5bbSMark Prins } 78557f8d5bbSMark Prins 78657f8d5bbSMark Prins /** 78757f8d5bbSMark Prins * Draw gpx trace on the map. 7888a541d8fSMark Prins * @throws Exception when loading the GPX fails 78957f8d5bbSMark Prins */ 7908a541d8fSMark Prins public function drawGPX(): void 791a760825cSgithub-actions[bot] { 79257f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127); 79357f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 79457f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 79557f8d5bbSMark Prins } 79657f8d5bbSMark Prins 79757f8d5bbSMark Prins /** 79857f8d5bbSMark Prins * Draw geojson on the map. 7998a541d8fSMark Prins * @throws Exception when loading the JSON fails 80057f8d5bbSMark Prins */ 8018a541d8fSMark Prins public function drawGeojson(): void 802a760825cSgithub-actions[bot] { 80357f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127); 80457f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json'); 80557f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 80657f8d5bbSMark Prins } 80757f8d5bbSMark Prins 80857f8d5bbSMark Prins /** 809628e43ccSMark Prins * add copyright and origin notice and icons to the map. 810628e43ccSMark Prins */ 8118a541d8fSMark Prins public function drawCopyright(): void 812a760825cSgithub-actions[bot] { 813a760825cSgithub-actions[bot] $logoBaseDir = __DIR__ . '/' . 'logo/'; 814628e43ccSMark Prins $logoImg = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']); 815628e43ccSMark Prins $textcolor = imagecolorallocate($this->image, 0, 0, 0); 816628e43ccSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 817628e43ccSMark Prins 81857f8d5bbSMark Prins imagecopy( 81957f8d5bbSMark Prins $this->image, 82057f8d5bbSMark Prins $logoImg, 82157f8d5bbSMark Prins 0, 82257f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 82357f8d5bbSMark Prins 0, 82457f8d5bbSMark Prins 0, 82557f8d5bbSMark Prins imagesx($logoImg), 82657f8d5bbSMark Prins imagesy($logoImg) 82757f8d5bbSMark Prins ); 82857f8d5bbSMark Prins imagestring( 82957f8d5bbSMark Prins $this->image, 83057f8d5bbSMark Prins 1, 83157f8d5bbSMark Prins imagesx($logoImg) + 2, 83257f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg) + 1, 83357f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 83457f8d5bbSMark Prins $bgcolor 83557f8d5bbSMark Prins ); 83657f8d5bbSMark Prins imagestring( 83757f8d5bbSMark Prins $this->image, 83857f8d5bbSMark Prins 1, 83957f8d5bbSMark Prins imagesx($logoImg) + 1, 84057f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 84157f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 84257f8d5bbSMark Prins $textcolor 84357f8d5bbSMark Prins ); 844628e43ccSMark Prins 845628e43ccSMark Prins // additional tile source info, ie. who created/hosted the tiles 846257dffd7SMark Prins $xIconOffset = 0; 847257dffd7SMark Prins if ($this->maptype === 'openstreetmap') { 848257dffd7SMark Prins $mapAuthor = "(c) OpenStreetMap maps/CC BY-SA"; 849257dffd7SMark Prins } else { 850257dffd7SMark Prins $mapAuthor = $this->tileInfo [$this->maptype] ['txt']; 851628e43ccSMark Prins $iconImg = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']); 852257dffd7SMark Prins $xIconOffset = imagesx($iconImg); 85357f8d5bbSMark Prins imagecopy( 85457f8d5bbSMark Prins $this->image, 855a760825cSgithub-actions[bot] $iconImg, 856a760825cSgithub-actions[bot] imagesx($logoImg) + 1, 85757f8d5bbSMark Prins imagesy($this->image) - imagesy($iconImg), 85857f8d5bbSMark Prins 0, 85957f8d5bbSMark Prins 0, 860a760825cSgithub-actions[bot] imagesx($iconImg), 861a760825cSgithub-actions[bot] imagesy($iconImg) 86257f8d5bbSMark Prins ); 863257dffd7SMark Prins } 86457f8d5bbSMark Prins imagestring( 86557f8d5bbSMark Prins $this->image, 866a760825cSgithub-actions[bot] 1, 867a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 4, 86857f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1, 869257dffd7SMark Prins $mapAuthor, 87057f8d5bbSMark Prins $bgcolor 87157f8d5bbSMark Prins ); 87257f8d5bbSMark Prins imagestring( 87357f8d5bbSMark Prins $this->image, 874a760825cSgithub-actions[bot] 1, 875a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 3, 87657f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2), 877257dffd7SMark Prins $mapAuthor, 87857f8d5bbSMark Prins $textcolor 87957f8d5bbSMark Prins ); 880628e43ccSMark Prins } 881628e43ccSMark Prins} 882