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 24*8a541d8fSMark 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 42*8a541d8fSMark Prins private int $tileSize = 256; 43*8a541d8fSMark Prins private array $tileInfo = [ 44628e43ccSMark Prins // OSM sources 45*8a541d8fSMark Prins 'openstreetmap' => ['txt' => '(c) OpenStreetMap data/ODbL', 'logo' => 'osm_logo.png', 'url' => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'], 46976e50f5SMark Prins // OpenTopoMap sources 47*8a541d8fSMark 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] ]; 56*8a541d8fSMark Prins private string $tileDefaultSrc = 'openstreetmap'; 57628e43ccSMark Prins 58628e43ccSMark Prins // set up markers 59*8a541d8fSMark 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] ]; 70*8a541d8fSMark Prins // note event hough $centerX, $centerY, $offsetX and $offsetY are defined as float, 71*8a541d8fSMark Prins // these are actually integer numbers, but the php floor()/ceil() functions return a float 72*8a541d8fSMark Prins private float $centerX; 73*8a541d8fSMark Prins private float $centerY; 74*8a541d8fSMark Prins private float $offsetX; 75*8a541d8fSMark Prins private float $offsetY; 7657f8d5bbSMark Prins private $image; 77*8a541d8fSMark Prins private int $zoom; 78*8a541d8fSMark Prins private float $lat; 79*8a541d8fSMark Prins private float $lon; 80*8a541d8fSMark Prins private int $width; 81*8a541d8fSMark Prins private int $height; 82*8a541d8fSMark Prins private array $markers; 83*8a541d8fSMark Prins private string $maptype; 84*8a541d8fSMark Prins private string $kmlFileName; 85*8a541d8fSMark Prins private string $gpxFileName; 86*8a541d8fSMark Prins private string $geojsonFileName; 87*8a541d8fSMark Prins private bool $autoZoomExtent; 88*8a541d8fSMark Prins private string $apikey; 89*8a541d8fSMark Prins private string $tileCacheBaseDir; 90*8a541d8fSMark Prins private string $mapCacheBaseDir; 91*8a541d8fSMark Prins private string $mediaBaseDir; 92*8a541d8fSMark Prins private bool $useTileCache; 93*8a541d8fSMark Prins private string $mapCacheID = ''; 94*8a541d8fSMark Prins private string $mapCacheFile = ''; 95*8a541d8fSMark 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] "&", 277a760825cSgithub-actions[bot] [$this->zoom, $this->lat, $this->lon, $this->width, $this->height, serialize($this->markers), $this->maptype, $this->kmlFileName, $this->gpxFileName, $this->geojsonFileName] 27857f8d5bbSMark Prins ); 27957f8d5bbSMark Prins } 28057f8d5bbSMark Prins 281a760825cSgithub-actions[bot] public function mapCacheIDToFilename(): string 282a760825cSgithub-actions[bot] { 28357f8d5bbSMark Prins if (!$this->mapCacheFile) { 28457f8d5bbSMark Prins $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_" 28557f8d5bbSMark Prins . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2) 28657f8d5bbSMark Prins . "/" . substr($this->mapCacheID, 4); 28757f8d5bbSMark Prins } 28857f8d5bbSMark Prins return $this->mapCacheFile . "." . $this->mapCacheExtension; 28957f8d5bbSMark Prins } 29057f8d5bbSMark Prins 29157f8d5bbSMark Prins /** 29257f8d5bbSMark Prins * make the map. 29357f8d5bbSMark Prins */ 294a760825cSgithub-actions[bot] public function makeMap(): void 295a760825cSgithub-actions[bot] { 29657f8d5bbSMark Prins $this->initCoords(); 29757f8d5bbSMark Prins $this->createBaseMap(); 298a760825cSgithub-actions[bot] if ($this->markers !== []) { 29957f8d5bbSMark Prins $this->placeMarkers(); 300e98967e1SMark Prins } 301e98967e1SMark Prins if (file_exists($this->kmlFileName)) { 302e98967e1SMark Prins try { 30357f8d5bbSMark Prins $this->drawKML(); 304e98967e1SMark Prins } catch (exception $e) { 305f204b8caSMark Prins Logger::error('failed to load KML file', $e); 306e98967e1SMark Prins } 307e98967e1SMark Prins } 308e98967e1SMark Prins if (file_exists($this->gpxFileName)) { 309e98967e1SMark Prins try { 31057f8d5bbSMark Prins $this->drawGPX(); 311e98967e1SMark Prins } catch (exception $e) { 312f204b8caSMark Prins Logger::error('failed to load GPX file', $e); 313e98967e1SMark Prins } 314e98967e1SMark Prins } 315e98967e1SMark Prins if (file_exists($this->geojsonFileName)) { 316e98967e1SMark Prins try { 31757f8d5bbSMark Prins $this->drawGeojson(); 318e98967e1SMark Prins } catch (exception $e) { 319f204b8caSMark Prins Logger::error('failed to load GeoJSON file', $e); 320e98967e1SMark Prins } 321e98967e1SMark Prins } 32257f8d5bbSMark Prins 32357f8d5bbSMark Prins $this->drawCopyright(); 324628e43ccSMark Prins } 325f4b9bdacSMark Prins 326628e43ccSMark Prins /** 327628e43ccSMark Prins */ 328a760825cSgithub-actions[bot] public function initCoords(): void 329a760825cSgithub-actions[bot] { 330628e43ccSMark Prins $this->centerX = $this->lonToTile($this->lon, $this->zoom); 331628e43ccSMark Prins $this->centerY = $this->latToTile($this->lat, $this->zoom); 332628e43ccSMark Prins $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize); 333628e43ccSMark Prins $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize); 334628e43ccSMark Prins } 335628e43ccSMark Prins 336628e43ccSMark Prins /** 33757f8d5bbSMark Prins * 33857f8d5bbSMark Prins * @param float $long 33957f8d5bbSMark Prins * @param int $zoom 340*8a541d8fSMark Prins * @return float 34157f8d5bbSMark Prins */ 342*8a541d8fSMark Prins public function lonToTile(float $long, int $zoom): float 343a760825cSgithub-actions[bot] { 344a760825cSgithub-actions[bot] return (($long + 180) / 360) * 2 ** $zoom; 34557f8d5bbSMark Prins } 34657f8d5bbSMark Prins 34757f8d5bbSMark Prins /** 34857f8d5bbSMark Prins * 34957f8d5bbSMark Prins * @param float $lat 35057f8d5bbSMark Prins * @param int $zoom 351*8a541d8fSMark Prins * @return float 35257f8d5bbSMark Prins */ 353*8a541d8fSMark Prins public function latToTile(float $lat, int $zoom): float 354a760825cSgithub-actions[bot] { 355a760825cSgithub-actions[bot] return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * 2 ** $zoom; 35657f8d5bbSMark Prins } 35757f8d5bbSMark Prins 35857f8d5bbSMark Prins /** 359628e43ccSMark Prins * make basemap image. 360628e43ccSMark Prins */ 361a760825cSgithub-actions[bot] public function createBaseMap(): void 362a760825cSgithub-actions[bot] { 363628e43ccSMark Prins $this->image = imagecreatetruecolor($this->width, $this->height); 364628e43ccSMark Prins $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2); 365628e43ccSMark Prins $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2); 366628e43ccSMark Prins $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2); 367628e43ccSMark Prins $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2); 368628e43ccSMark Prins $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize); 369628e43ccSMark Prins $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize); 370628e43ccSMark Prins $this->offsetX += floor($this->width / 2); 371628e43ccSMark Prins $this->offsetY += floor($this->height / 2); 372628e43ccSMark Prins $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize; 373628e43ccSMark Prins $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize; 374628e43ccSMark Prins 375628e43ccSMark Prins for ($x = $startX; $x <= $endX; $x++) { 376628e43ccSMark Prins for ($y = $startY; $y <= $endY; $y++) { 37757f8d5bbSMark Prins $url = str_replace( 378a760825cSgithub-actions[bot] ['{Z}', '{X}', '{Y}'], 379a760825cSgithub-actions[bot] [$this->zoom, $x, $y], 380a760825cSgithub-actions[bot] $this->tileInfo [$this->maptype] ['url'] 38157f8d5bbSMark Prins ); 382c8eb1362SMark Prins 383628e43ccSMark Prins $tileData = $this->fetchTile($url); 384628e43ccSMark Prins if ($tileData) { 385628e43ccSMark Prins $tileImage = imagecreatefromstring($tileData); 386628e43ccSMark Prins } else { 387628e43ccSMark Prins $tileImage = imagecreate($this->tileSize, $this->tileSize); 388628e43ccSMark Prins $color = imagecolorallocate($tileImage, 255, 255, 255); 389628e43ccSMark Prins @imagestring($tileImage, 1, 127, 127, 'err', $color); 390628e43ccSMark Prins } 391628e43ccSMark Prins $destX = ($x - $startX) * $this->tileSize + $this->offsetX; 392628e43ccSMark Prins $destY = ($y - $startY) * $this->tileSize + $this->offsetY; 393f204b8caSMark Prins Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize); 39457f8d5bbSMark Prins imagecopy( 395a760825cSgithub-actions[bot] $this->image, 396a760825cSgithub-actions[bot] $tileImage, 397a760825cSgithub-actions[bot] $destX, 398a760825cSgithub-actions[bot] $destY, 399a760825cSgithub-actions[bot] 0, 400a760825cSgithub-actions[bot] 0, 401a760825cSgithub-actions[bot] $this->tileSize, 40257f8d5bbSMark Prins $this->tileSize 40357f8d5bbSMark Prins ); 404628e43ccSMark Prins } 405628e43ccSMark Prins } 406628e43ccSMark Prins } 407628e43ccSMark Prins 408628e43ccSMark Prins /** 4092d11d700SMark Prins * Fetch a tile and (if configured) store it in the cache. 4102d11d700SMark Prins * @param string $url 411257dffd7SMark Prins * @return bool|string 412ebb06a66SMark Prins * @todo refactor this to use dokuwiki\HTTP\HTTPClient or dokuwiki\HTTP\DokuHTTPClient 413ebb06a66SMark Prins * for better proxy handling... 4142d11d700SMark Prins */ 415a760825cSgithub-actions[bot] public function fetchTile(string $url) 416a760825cSgithub-actions[bot] { 417ab8cbd2bSMark Prins if ($this->useTileCache && ($cached = $this->checkTileCache($url))) 418ab8cbd2bSMark Prins return $cached; 419e4f115f4SMark Prins 420e4f115f4SMark Prins $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')'; 421e4f115f4SMark Prins if (function_exists("curl_init")) { 422e4f115f4SMark Prins // use cUrl 423628e43ccSMark Prins $ch = curl_init(); 424628e43ccSMark Prins curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 425e4f115f4SMark Prins curl_setopt($ch, CURLOPT_USERAGENT, $_UA); 426*8a541d8fSMark Prins curl_setopt($ch, CURLOPT_REFERER, DOKU_URL); 427628e43ccSMark Prins curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 428b63742deSMark Prins curl_setopt($ch, CURLOPT_URL, $url . $this->apikey); 429f204b8caSMark Prins Logger::debug("StaticMap::fetchTile: getting: $url using curl_exec"); 430628e43ccSMark Prins $tile = curl_exec($ch); 431628e43ccSMark Prins curl_close($ch); 432e4f115f4SMark Prins } else { 433e4f115f4SMark Prins // use file_get_contents 434e4f115f4SMark Prins global $conf; 435*8a541d8fSMark Prins $opts = ['http' => [ 436*8a541d8fSMark Prins 'method' => "GET", 437*8a541d8fSMark Prins 'header' => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n" . "Referer: " . DOKU_URL . "\r\n", 438*8a541d8fSMark Prins 'request_fulluri' => true 439*8a541d8fSMark Prins ]]; 440*8a541d8fSMark Prins if (isset($conf['proxy']['host'], $conf['proxy']['port']) 441ebb06a66SMark Prins && $conf['proxy']['host'] !== '' 442a760825cSgithub-actions[bot] && $conf['proxy']['port'] !== '' 443a760825cSgithub-actions[bot] ) { 444ebb06a66SMark Prins $opts['http'] += ['proxy' => "tcp://" . $conf['proxy']['host'] . ":" . $conf['proxy']['port']]; 445ebb06a66SMark Prins } 446ebb06a66SMark Prins 447e4f115f4SMark Prins $context = stream_context_create($opts); 448f204b8caSMark Prins Logger::debug( 449f204b8caSMark Prins "StaticMap::fetchTile: getting: $url . $this->apikey using file_get_contents and options $opts" 450f204b8caSMark Prins ); 451b63742deSMark Prins $tile = file_get_contents($url . $this->apikey, false, $context); 452e4f115f4SMark Prins } 453628e43ccSMark Prins if ($tile && $this->useTileCache) { 454628e43ccSMark Prins $this->writeTileToCache($url, $tile); 455628e43ccSMark Prins } 456628e43ccSMark Prins return $tile; 457628e43ccSMark Prins } 458628e43ccSMark Prins 459628e43ccSMark Prins /** 46057f8d5bbSMark Prins * 46157f8d5bbSMark Prins * @param string $url 462257dffd7SMark Prins * @return string|false 463628e43ccSMark Prins */ 464*8a541d8fSMark Prins public function checkTileCache(string $url): string|false 465a760825cSgithub-actions[bot] { 46657f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 46757f8d5bbSMark Prins if (file_exists($filename)) { 46857f8d5bbSMark Prins return file_get_contents($filename); 46957f8d5bbSMark Prins } 470257dffd7SMark Prins return false; 471628e43ccSMark Prins } 472628e43ccSMark Prins 4736914b920SMark Prins /** 47457f8d5bbSMark Prins * @param string $url 475*8a541d8fSMark Prins * @return string 4766914b920SMark Prins */ 477a760825cSgithub-actions[bot] public function tileUrlToFilename(string $url): string 478a760825cSgithub-actions[bot] { 479257dffd7SMark Prins return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1); 48057f8d5bbSMark Prins } 48157f8d5bbSMark Prins 48257f8d5bbSMark Prins /** 48357f8d5bbSMark Prins * Write a tile into the cache. 48457f8d5bbSMark Prins * 48557f8d5bbSMark Prins * @param string $url 48657f8d5bbSMark Prins * @param mixed $data 48757f8d5bbSMark Prins */ 488*8a541d8fSMark Prins public function writeTileToCache(string $url, mixed $data): void 489a760825cSgithub-actions[bot] { 49057f8d5bbSMark Prins $filename = $this->tileUrlToFilename($url); 49157f8d5bbSMark Prins $this->mkdirRecursive(dirname($filename), 0777); 49257f8d5bbSMark Prins file_put_contents($filename, $data); 49357f8d5bbSMark Prins } 49457f8d5bbSMark Prins 49557f8d5bbSMark Prins /** 49657f8d5bbSMark Prins * Recursively create the directory. 49757f8d5bbSMark Prins * 49857f8d5bbSMark Prins * @param string $pathname 49957f8d5bbSMark Prins * The directory path. 50057f8d5bbSMark Prins * @param int $mode 50157f8d5bbSMark Prins * File access mode. For more information on modes, read the details on the chmod manpage. 50257f8d5bbSMark Prins */ 503a760825cSgithub-actions[bot] public function mkdirRecursive(string $pathname, int $mode): bool 504a760825cSgithub-actions[bot] { 505a760825cSgithub-actions[bot] if (!is_dir(dirname($pathname))) { 506a760825cSgithub-actions[bot] $this->mkdirRecursive(dirname($pathname), $mode); 507a760825cSgithub-actions[bot] } 50857f8d5bbSMark Prins return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname); 50957f8d5bbSMark Prins } 51057f8d5bbSMark Prins 51157f8d5bbSMark Prins /** 51257f8d5bbSMark Prins * Place markers on the map and number them in the same order as they are listed in the html. 51357f8d5bbSMark Prins */ 514a760825cSgithub-actions[bot] public function placeMarkers(): void 515a760825cSgithub-actions[bot] { 51657f8d5bbSMark Prins $count = 0; 51757f8d5bbSMark Prins $color = imagecolorallocate($this->image, 0, 0, 0); 51857f8d5bbSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 51957f8d5bbSMark Prins $markerBaseDir = __DIR__ . '/icons'; 520e98967e1SMark Prins $markerImageOffsetX = 0; 521e98967e1SMark Prins $markerImageOffsetY = 0; 522e98967e1SMark Prins $markerShadowOffsetX = 0; 523e98967e1SMark Prins $markerShadowOffsetY = 0; 524e98967e1SMark Prins $markerShadowImg = null; 52557f8d5bbSMark Prins // loop thru marker array 52657f8d5bbSMark Prins foreach ($this->markers as $marker) { 52757f8d5bbSMark Prins // set some local variables 52857f8d5bbSMark Prins $markerLat = $marker ['lat']; 52957f8d5bbSMark Prins $markerLon = $marker ['lon']; 53057f8d5bbSMark Prins $markerType = $marker ['type']; 53157f8d5bbSMark Prins // clear variables from previous loops 53257f8d5bbSMark Prins $markerFilename = ''; 53357f8d5bbSMark Prins $markerShadow = ''; 53457f8d5bbSMark Prins $matches = false; 53557f8d5bbSMark Prins // check for marker type, get settings from markerPrototypes 53657f8d5bbSMark Prins if ($markerType) { 53757f8d5bbSMark Prins foreach ($this->markerPrototypes as $markerPrototype) { 53857f8d5bbSMark Prins if (preg_match($markerPrototype ['regex'], $markerType, $matches)) { 53957f8d5bbSMark Prins $markerFilename = $matches [0] . $markerPrototype ['extension']; 54057f8d5bbSMark Prins if ($markerPrototype ['offsetImage']) { 541a760825cSgithub-actions[bot] [$markerImageOffsetX, $markerImageOffsetY] = explode( 54257f8d5bbSMark Prins ",", 54357f8d5bbSMark Prins $markerPrototype ['offsetImage'] 54457f8d5bbSMark Prins ); 54557f8d5bbSMark Prins } 54657f8d5bbSMark Prins $markerShadow = $markerPrototype ['shadow']; 54757f8d5bbSMark Prins if ($markerShadow) { 548a760825cSgithub-actions[bot] [$markerShadowOffsetX, $markerShadowOffsetY] = explode( 54957f8d5bbSMark Prins ",", 55057f8d5bbSMark Prins $markerPrototype ['offsetShadow'] 55157f8d5bbSMark Prins ); 55257f8d5bbSMark Prins } 55357f8d5bbSMark Prins } 55457f8d5bbSMark Prins } 55557f8d5bbSMark Prins } 55657f8d5bbSMark Prins // create img resource 55757f8d5bbSMark Prins if (file_exists($markerBaseDir . '/' . $markerFilename)) { 55857f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename); 55957f8d5bbSMark Prins } else { 56057f8d5bbSMark Prins $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png'); 56157f8d5bbSMark Prins } 56257f8d5bbSMark Prins // check for shadow + create shadow recource 56357f8d5bbSMark Prins if ($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) { 56457f8d5bbSMark Prins $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow); 56557f8d5bbSMark Prins } 56657f8d5bbSMark Prins // calc position 56757f8d5bbSMark Prins $destX = floor( 56857f8d5bbSMark Prins ($this->width / 2) - 56957f8d5bbSMark Prins $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)) 57057f8d5bbSMark Prins ); 57157f8d5bbSMark Prins $destY = floor( 57257f8d5bbSMark Prins ($this->height / 2) - 57357f8d5bbSMark Prins $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)) 57457f8d5bbSMark Prins ); 57557f8d5bbSMark Prins // copy shadow on basemap 57657f8d5bbSMark Prins if ($markerShadow && $markerShadowImg) { 57757f8d5bbSMark Prins imagecopy( 57857f8d5bbSMark Prins $this->image, 57957f8d5bbSMark Prins $markerShadowImg, 58057f8d5bbSMark Prins $destX + (int) $markerShadowOffsetX, 58157f8d5bbSMark Prins $destY + (int) $markerShadowOffsetY, 58257f8d5bbSMark Prins 0, 58357f8d5bbSMark Prins 0, 58457f8d5bbSMark Prins imagesx($markerShadowImg), 58557f8d5bbSMark Prins imagesy($markerShadowImg) 58657f8d5bbSMark Prins ); 58757f8d5bbSMark Prins } 58857f8d5bbSMark Prins // copy marker on basemap above shadow 58957f8d5bbSMark Prins imagecopy( 59057f8d5bbSMark Prins $this->image, 59157f8d5bbSMark Prins $markerImg, 59257f8d5bbSMark Prins $destX + (int) $markerImageOffsetX, 59357f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 59457f8d5bbSMark Prins 0, 59557f8d5bbSMark Prins 0, 59657f8d5bbSMark Prins imagesx($markerImg), 59757f8d5bbSMark Prins imagesy($markerImg) 59857f8d5bbSMark Prins ); 59957f8d5bbSMark Prins // add label 60057f8d5bbSMark Prins imagestring( 60157f8d5bbSMark Prins $this->image, 60257f8d5bbSMark Prins 3, 60357f8d5bbSMark Prins $destX - imagesx($markerImg) + 1, 60457f8d5bbSMark Prins $destY + (int) $markerImageOffsetY + 1, 60557f8d5bbSMark Prins ++$count, 60657f8d5bbSMark Prins $bgcolor 60757f8d5bbSMark Prins ); 60857f8d5bbSMark Prins imagestring( 60957f8d5bbSMark Prins $this->image, 61057f8d5bbSMark Prins 3, 61157f8d5bbSMark Prins $destX - imagesx($markerImg), 61257f8d5bbSMark Prins $destY + (int) $markerImageOffsetY, 61357f8d5bbSMark Prins $count, 61457f8d5bbSMark Prins $color 61557f8d5bbSMark Prins ); 61657f8d5bbSMark Prins } 6176914b920SMark Prins } 61857e65445SMark Prins 619628e43ccSMark Prins /** 620628e43ccSMark Prins * Draw kml trace on the map. 621*8a541d8fSMark Prins * @throws Exception when loading the KML fails 622628e43ccSMark Prins */ 623a760825cSgithub-actions[bot] public function drawKML(): void 624a760825cSgithub-actions[bot] { 6252d11d700SMark Prins // TODO get colour from kml node (not currently supported in geoPHP) 626c977deacSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127); 627c977deacSMark Prins $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 628c977deacSMark Prins $this->drawGeometry($kmlgeom, $col); 629c977deacSMark Prins } 63057e65445SMark Prins 631c977deacSMark Prins /** 632c977deacSMark Prins * Draw geometry or geometry collection on the map. 633ab8cbd2bSMark Prins * 634c977deacSMark Prins * @param Geometry $geom 635ab8cbd2bSMark Prins * @param int $colour 636ab8cbd2bSMark Prins * drawing colour 637c977deacSMark Prins */ 638a760825cSgithub-actions[bot] private function drawGeometry(Geometry $geom, int $colour): void 639a760825cSgithub-actions[bot] { 64057f8d5bbSMark Prins if (empty($geom)) { 64157f8d5bbSMark Prins return; 64257f8d5bbSMark Prins } 643e5840fc5SMark Prins 6446c6bb022SMark Prins switch ($geom->geometryType()) { 645c977deacSMark Prins case 'GeometryCollection': 646c977deacSMark Prins // recursively draw part of the collection 647c977deacSMark Prins for ($i = 1; $i < $geom->numGeometries() + 1; $i++) { 648c977deacSMark Prins $_geom = $geom->geometryN($i); 649c977deacSMark Prins $this->drawGeometry($_geom, $colour); 650c977deacSMark Prins } 6516c6bb022SMark Prins break; 6526c6bb022SMark Prins case 'Polygon': 653c977deacSMark Prins $this->drawPolygon($geom, $colour); 654c977deacSMark Prins break; 655c977deacSMark Prins case 'LineString': 656c977deacSMark Prins $this->drawLineString($geom, $colour); 657c977deacSMark Prins break; 658c977deacSMark Prins case 'Point': 659c977deacSMark Prins $this->drawPoint($geom, $colour); 6606c6bb022SMark Prins break; 661ad8fb8a2SMark Prins // TODO implement / do nothing 662ad8fb8a2SMark Prins case 'MultiPolygon': 663ad8fb8a2SMark Prins case 'MultiLineString': 664ad8fb8a2SMark Prins case 'MultiPoint': 6656c6bb022SMark Prins default: 6662d11d700SMark Prins // draw nothing 6676c6bb022SMark Prins break; 6686c6bb022SMark Prins } 6696c6bb022SMark Prins } 670c977deacSMark Prins 671e61425c7SMark Prins /** 672e61425c7SMark Prins * Draw a polygon on the map. 673ab8cbd2bSMark Prins * 674e61425c7SMark Prins * @param Polygon $polygon 675ab8cbd2bSMark Prins * @param int $colour 676ab8cbd2bSMark Prins * drawing colour 677e61425c7SMark Prins */ 678*8a541d8fSMark Prins private function drawPolygon(Polygon $polygon, int $colour): void 679a760825cSgithub-actions[bot] { 680c977deacSMark Prins // TODO implementation of drawing holes, 681c977deacSMark Prins // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol? 682c977deacSMark Prins 683c977deacSMark Prins // print_r('Polygon:<br />'); 684c977deacSMark Prins // print_r($polygon); 685a760825cSgithub-actions[bot] $extPoints = []; 686*8a541d8fSMark Prins // extRing is a linestring actually... 687c977deacSMark Prins $extRing = $polygon->exteriorRing(); 688c977deacSMark Prins 689c977deacSMark Prins for ($i = 1; $i < $extRing->numGeometries(); $i++) { 690c977deacSMark Prins $p1 = $extRing->geometryN($i); 69157f8d5bbSMark Prins $x = floor( 69257f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 69357f8d5bbSMark Prins ); 69457f8d5bbSMark Prins $y = floor( 69557f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 69657f8d5bbSMark Prins ); 697c977deacSMark Prins $extPoints [] = $x; 698c977deacSMark Prins $extPoints [] = $y; 699e61425c7SMark Prins } 700c977deacSMark Prins // print_r('points:('.($i-1).')<br />'); 701c977deacSMark Prins // print_r($extPoints); 702c977deacSMark Prins // imagepolygon ($this->image, $extPoints, $i-1, $colour ); 703c977deacSMark Prins imagefilledpolygon($this->image, $extPoints, $i - 1, $colour); 704c977deacSMark Prins } 705c977deacSMark Prins 706628e43ccSMark Prins /** 70757f8d5bbSMark Prins * Draw a line on the map. 70857f8d5bbSMark Prins * 70957f8d5bbSMark Prins * @param LineString $line 71057f8d5bbSMark Prins * @param int $colour 71157f8d5bbSMark Prins * drawing colour 71257f8d5bbSMark Prins */ 713*8a541d8fSMark Prins private function drawLineString(LineString $line, int $colour): void 714a760825cSgithub-actions[bot] { 71557f8d5bbSMark Prins imagesetthickness($this->image, 2); 71657f8d5bbSMark Prins for ($p = 1; $p < $line->numGeometries(); $p++) { 71757f8d5bbSMark Prins // get first pair of points 71857f8d5bbSMark Prins $p1 = $line->geometryN($p); 71957f8d5bbSMark Prins $p2 = $line->geometryN($p + 1); 72057f8d5bbSMark Prins // translate to paper space 72157f8d5bbSMark Prins $x1 = floor( 72257f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 72357f8d5bbSMark Prins ); 72457f8d5bbSMark Prins $y1 = floor( 72557f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 72657f8d5bbSMark Prins ); 72757f8d5bbSMark Prins $x2 = floor( 72857f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom)) 72957f8d5bbSMark Prins ); 73057f8d5bbSMark Prins $y2 = floor( 73157f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom)) 73257f8d5bbSMark Prins ); 73357f8d5bbSMark Prins // draw to image 73457f8d5bbSMark Prins imageline($this->image, $x1, $y1, $x2, $y2, $colour); 73557f8d5bbSMark Prins } 73657f8d5bbSMark Prins imagesetthickness($this->image, 1); 73757f8d5bbSMark Prins } 73857f8d5bbSMark Prins 73957f8d5bbSMark Prins /** 74057f8d5bbSMark Prins * Draw a point on the map. 74157f8d5bbSMark Prins * 74257f8d5bbSMark Prins * @param Point $point 74357f8d5bbSMark Prins * @param int $colour 74457f8d5bbSMark Prins * drawing colour 74557f8d5bbSMark Prins */ 746*8a541d8fSMark Prins private function drawPoint(Point $point, int $colour): void 747a760825cSgithub-actions[bot] { 74857f8d5bbSMark Prins imagesetthickness($this->image, 2); 74957f8d5bbSMark Prins // translate to paper space 75057f8d5bbSMark Prins $cx = floor( 75157f8d5bbSMark Prins ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom)) 75257f8d5bbSMark Prins ); 75357f8d5bbSMark Prins $cy = floor( 75457f8d5bbSMark Prins ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom)) 75557f8d5bbSMark Prins ); 75657f8d5bbSMark Prins $r = 5; 75757f8d5bbSMark Prins // draw to image 75857f8d5bbSMark Prins // imageellipse($this->image, $cx, $cy,$r, $r, $colour); 75957f8d5bbSMark Prins imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour); 76057f8d5bbSMark Prins // don't use imageellipse because the imagesetthickness function has 76157f8d5bbSMark Prins // no effect. So the better workaround is to use imagearc. 76257f8d5bbSMark Prins imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour); 76357f8d5bbSMark Prins imagesetthickness($this->image, 1); 76457f8d5bbSMark Prins } 76557f8d5bbSMark Prins 76657f8d5bbSMark Prins /** 76757f8d5bbSMark Prins * Draw gpx trace on the map. 768*8a541d8fSMark Prins * @throws Exception when loading the GPX fails 76957f8d5bbSMark Prins */ 770*8a541d8fSMark Prins public function drawGPX(): void 771a760825cSgithub-actions[bot] { 77257f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127); 77357f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 77457f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 77557f8d5bbSMark Prins } 77657f8d5bbSMark Prins 77757f8d5bbSMark Prins /** 77857f8d5bbSMark Prins * Draw geojson on the map. 779*8a541d8fSMark Prins * @throws Exception when loading the JSON fails 78057f8d5bbSMark Prins */ 781*8a541d8fSMark Prins public function drawGeojson(): void 782a760825cSgithub-actions[bot] { 78357f8d5bbSMark Prins $col = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127); 78457f8d5bbSMark Prins $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json'); 78557f8d5bbSMark Prins $this->drawGeometry($gpxgeom, $col); 78657f8d5bbSMark Prins } 78757f8d5bbSMark Prins 78857f8d5bbSMark Prins /** 789628e43ccSMark Prins * add copyright and origin notice and icons to the map. 790628e43ccSMark Prins */ 791*8a541d8fSMark Prins public function drawCopyright(): void 792a760825cSgithub-actions[bot] { 793a760825cSgithub-actions[bot] $logoBaseDir = __DIR__ . '/' . 'logo/'; 794628e43ccSMark Prins $logoImg = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']); 795628e43ccSMark Prins $textcolor = imagecolorallocate($this->image, 0, 0, 0); 796628e43ccSMark Prins $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 797628e43ccSMark Prins 79857f8d5bbSMark Prins imagecopy( 79957f8d5bbSMark Prins $this->image, 80057f8d5bbSMark Prins $logoImg, 80157f8d5bbSMark Prins 0, 80257f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 80357f8d5bbSMark Prins 0, 80457f8d5bbSMark Prins 0, 80557f8d5bbSMark Prins imagesx($logoImg), 80657f8d5bbSMark Prins imagesy($logoImg) 80757f8d5bbSMark Prins ); 80857f8d5bbSMark Prins imagestring( 80957f8d5bbSMark Prins $this->image, 81057f8d5bbSMark Prins 1, 81157f8d5bbSMark Prins imagesx($logoImg) + 2, 81257f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg) + 1, 81357f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 81457f8d5bbSMark Prins $bgcolor 81557f8d5bbSMark Prins ); 81657f8d5bbSMark Prins imagestring( 81757f8d5bbSMark Prins $this->image, 81857f8d5bbSMark Prins 1, 81957f8d5bbSMark Prins imagesx($logoImg) + 1, 82057f8d5bbSMark Prins imagesy($this->image) - imagesy($logoImg), 82157f8d5bbSMark Prins $this->tileInfo ['openstreetmap'] ['txt'], 82257f8d5bbSMark Prins $textcolor 82357f8d5bbSMark Prins ); 824628e43ccSMark Prins 825628e43ccSMark Prins // additional tile source info, ie. who created/hosted the tiles 826257dffd7SMark Prins $xIconOffset = 0; 827257dffd7SMark Prins if ($this->maptype === 'openstreetmap') { 828257dffd7SMark Prins $mapAuthor = "(c) OpenStreetMap maps/CC BY-SA"; 829257dffd7SMark Prins } else { 830257dffd7SMark Prins $mapAuthor = $this->tileInfo [$this->maptype] ['txt']; 831628e43ccSMark Prins $iconImg = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']); 832257dffd7SMark Prins $xIconOffset = imagesx($iconImg); 83357f8d5bbSMark Prins imagecopy( 83457f8d5bbSMark Prins $this->image, 835a760825cSgithub-actions[bot] $iconImg, 836a760825cSgithub-actions[bot] imagesx($logoImg) + 1, 83757f8d5bbSMark Prins imagesy($this->image) - imagesy($iconImg), 83857f8d5bbSMark Prins 0, 83957f8d5bbSMark Prins 0, 840a760825cSgithub-actions[bot] imagesx($iconImg), 841a760825cSgithub-actions[bot] imagesy($iconImg) 84257f8d5bbSMark Prins ); 843257dffd7SMark Prins } 84457f8d5bbSMark Prins imagestring( 84557f8d5bbSMark Prins $this->image, 846a760825cSgithub-actions[bot] 1, 847a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 4, 84857f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1, 849257dffd7SMark Prins $mapAuthor, 85057f8d5bbSMark Prins $bgcolor 85157f8d5bbSMark Prins ); 85257f8d5bbSMark Prins imagestring( 85357f8d5bbSMark Prins $this->image, 854a760825cSgithub-actions[bot] 1, 855a760825cSgithub-actions[bot] imagesx($logoImg) + $xIconOffset + 3, 85657f8d5bbSMark Prins imagesy($this->image) - ceil(imagesy($logoImg) / 2), 857257dffd7SMark Prins $mapAuthor, 85857f8d5bbSMark Prins $textcolor 85957f8d5bbSMark Prins ); 860628e43ccSMark Prins } 861628e43ccSMark Prins} 862