StaticMap::writeTileToCache()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
/*
4
 * Copyright (c) 2012-2023 Mark C. Prins <[email protected]>
5
 *
6
 * In part based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/
7
 *
8
 * Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com>
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 *     http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22
namespace dokuwiki\plugin\openlayersmap;
23
24
use geoPHP\Geometry\Geometry;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Geometry was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use geoPHP\Geometry\GeometryCollection;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\GeometryCollection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use geoPHP\Geometry\LineString;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\LineString was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use geoPHP\Geometry\Point;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Point was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use geoPHP\Geometry\Polygon;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Polygon was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
use geoPHP\geoPHP;
0 ignored issues
show
Bug introduced by
The type geoPHP\geoPHP was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
use dokuwiki\Logger;
0 ignored issues
show
Bug introduced by
The type dokuwiki\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
32
/**
33
 *
34
 * @author Mark C. Prins <[email protected]>
35
 * @author Gerhard Koch <gerhard.koch AT ymail.com>
36
 *
37
 */
38
class StaticMap
39
{
40
    // the final output
41
    private $tileSize = 256;
42
    private $tileInfo = [
43
        // OSM sources
44
        'openstreetmap' => ['txt'  => '(c) OpenStreetMap data/ODbl', 'logo' => 'osm_logo.png', 'url'  => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'],
45
        // OpenTopoMap sources
46
        'opentopomap' => ['txt'  => '(c) OpenStreetMap data/ODbl, SRTM | style: (c) OpenTopoMap', 'logo' => 'osm_logo.png', 'url'  => 'https:/tile.opentopomap.org/{Z}/{X}/{Y}.png'],
47
        // OCM sources
48
        'cycle'         => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'],
49
        'transport'     => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'],
50
        'landscape'     => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png'],
51
        'outdoors'      => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png'],
52
        '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'],
53
        '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'],
54
    ];
55
    private $tileDefaultSrc = 'openstreetmap';
56
57
    // set up markers
58
    private $markerPrototypes = [
59
        // found at http://www.mapito.net/map-marker-icons.html
60
        // these are 17x19 px with a pointer at the bottom left
61
        'lightblue' => ['regex'        => '/^lightblue(\d+)$/', 'extension'    => '.png', 'shadow'       => false, 'offsetImage'  => '0,-19', 'offsetShadow' => false],
62
        // openlayers std markers are 21x25px with shadow
63
        'ol-marker' => ['regex'        => '/^marker(|-blue|-gold|-green|-red)+$/', 'extension'    => '.png', 'shadow'       => 'marker_shadow.png', 'offsetImage'  => '-10,-25', 'offsetShadow' => '-1,-13'],
64
        // these are 16x16 px
65
        'ww_icon'   => ['regex'        => '/ww_\S+$/', 'extension'    => '.png', 'shadow'       => false, 'offsetImage'  => '-8,-8', 'offsetShadow' => false],
66
        // assume these are 16x16 px
67
        'rest'      => ['regex'        => '/^(?!lightblue(\d+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/', 'extension'    => '.png', 'shadow'       => 'marker_shadow.png', 'offsetImage'  => '-8,-8', 'offsetShadow' => '-1,-1'],
68
    ];
69
    private $centerX;
70
    private $centerY;
71
    private $offsetX;
72
    private $offsetY;
73
    private $image;
74
    private $zoom;
75
    private $lat;
76
    private $lon;
77
    private $width;
78
    private $height;
79
    private $markers;
80
    private $maptype;
81
    private $kmlFileName;
82
    private $gpxFileName;
83
    private $geojsonFileName;
84
    private $autoZoomExtent;
85
    private $apikey;
86
    private $tileCacheBaseDir;
87
    private $mapCacheBaseDir;
88
    private $mediaBaseDir;
89
    private $useTileCache;
90
    private $mapCacheID = '';
91
    private $mapCacheFile = '';
92
    private $mapCacheExtension = 'png';
93
94
    /**
95
     * Constructor.
96
     *
97
     * @param float  $lat
98
     *            Latitude (x) of center of map
99
     * @param float  $lon
100
     *            Longitude (y) of center of map
101
     * @param int    $zoom
102
     *            Zoomlevel
103
     * @param int    $width
104
     *            Width in pixels
105
     * @param int    $height
106
     *            Height in pixels
107
     * @param string $maptype
108
     *            Name of the map
109
     * @param array  $markers
110
     *            array of markers
111
     * @param string $gpx
112
     *            GPX filename
113
     * @param string $kml
114
     *            KML filename
115
     * @param string $geojson
116
     * @param string $mediaDir
117
     *            Directory to store/cache maps
118
     * @param string $tileCacheBaseDir
119
     *            Directory to cache map tiles
120
     * @param bool   $autoZoomExtent
121
     *            Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
122
     * @param string $apikey
123
     */
124
    public function __construct(
125
        float $lat,
126
        float $lon,
127
        int $zoom,
128
        int $width,
129
        int $height,
130
        string $maptype,
131
        array $markers,
132
        string $gpx,
133
        string $kml,
134
        string $geojson,
135
        string $mediaDir,
136
        string $tileCacheBaseDir,
137
        bool $autoZoomExtent = true,
138
        string $apikey = ''
139
    ) {
140
        $this->zoom   = $zoom;
141
        $this->lat    = $lat;
142
        $this->lon    = $lon;
143
        $this->width  = $width;
144
        $this->height = $height;
145
        // validate + set maptype
146
        $this->maptype = $this->tileDefaultSrc;
147
        if (array_key_exists($maptype, $this->tileInfo)) {
148
            $this->maptype = $maptype;
149
        }
150
        $this->markers          = $markers;
151
        $this->kmlFileName      = $kml;
152
        $this->gpxFileName      = $gpx;
153
        $this->geojsonFileName  = $geojson;
154
        $this->mediaBaseDir     = $mediaDir;
155
        $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles';
156
        $this->useTileCache     = $this->tileCacheBaseDir !== '';
157
        $this->mapCacheBaseDir  = $mediaDir . '/olmapmaps';
158
        $this->autoZoomExtent   = $autoZoomExtent;
159
        $this->apikey           = $apikey;
160
    }
161
162
    /**
163
     * get the map, this may return a reference to a cached copy.
164
     *
165
     * @return string url relative to media dir
166
     */
167
    public function getMap(): string
168
    {
169
        try {
170
            if ($this->autoZoomExtent) {
171
                $this->autoZoom();
172
            }
173
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type dokuwiki\plugin\openlayersmap\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
174
            Logger::debug($e);
175
        }
176
177
        // use map cache, so check cache for map
178
        if (!$this->checkMapCache()) {
179
            // map is not in cache, needs to be build
180
            $this->makeMap();
181
            $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777);
182
            imagepng($this->image, $this->mapCacheIDToFilename(), 9);
183
        }
184
        $doc = $this->mapCacheIDToFilename();
185
        // make url relative to media dir
186
        return str_replace($this->mediaBaseDir, '', $doc);
187
    }
188
189
    /**
190
     * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
191
     *
192
     * @param float $paddingFactor
193
     *            buffer constant to enlarge (>1.0) the zoom level
194
     * @throws Exception if non-geometries are found in the collection
195
     */
196
    private function autoZoom(float $paddingFactor = 1.0): void
197
    {
198
        $geoms    = [];
199
        $geoms [] = new Point($this->lon, $this->lat);
200
        if ($this->markers !== []) {
201
            foreach ($this->markers as $marker) {
202
                $geoms [] = new Point($marker ['lon'], $marker ['lat']);
203
            }
204
        }
205
        if (file_exists($this->kmlFileName)) {
206
            $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
207
            if ($g !== false) {
208
                $geoms [] = $g;
209
            }
210
        }
211
        if (file_exists($this->gpxFileName)) {
212
            $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
213
            if ($g !== false) {
214
                $geoms [] = $g;
215
            }
216
        }
217
        if (file_exists($this->geojsonFileName)) {
218
            $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson');
219
            if ($g !== false) {
220
                $geoms [] = $g;
221
            }
222
        }
223
224
        if (count($geoms) <= 1) {
225
            Logger::debug("StaticMap::autoZoom: Skip setting autozoom options", $geoms);
226
            return;
227
        }
228
229
        $geom     = new GeometryCollection($geoms);
230
        $centroid = $geom->centroid();
231
        $bbox     = $geom->getBBox();
232
233
        // determine vertical resolution, this depends on the distance from the equator
234
        // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
235
        $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360)));
236
        $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360)));
237
        Logger::debug("StaticMap::autoZoom: vertical resolution: $vy0, $vy1");
238
        if ($vy1 - $vy0 === 0.0) {
239
            $resolutionVertical = 0;
240
            Logger::debug("StaticMap::autoZoom: using $resolutionVertical");
241
        } else {
242
            $zoomFactorPowered  = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0));
243
            $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
244
        }
245
        // determine horizontal resolution
246
        $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width;
247
        Logger::debug("StaticMap::autoZoom: using $resolutionHorizontal");
248
        $resolution           = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
249
        $zoom                 = $this->zoom;
250
        if ($resolution > 0) {
251
            $zoom             = log(360 / ($resolution * $this->tileSize), 2);
252
        }
253
254
        if (is_finite($zoom) && $zoom < 15 && $zoom > 2) {
255
            $this->zoom = floor($zoom);
256
        }
257
        $this->lon = $centroid->getX();
258
        $this->lat = $centroid->getY();
259
        Logger::debug("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat");
260
    }
261
262
    public function checkMapCache(): bool
263
    {
264
        // side effect: set the mapCacheID
265
        $this->mapCacheID = md5($this->serializeParams());
266
        $filename         = $this->mapCacheIDToFilename();
267
        return file_exists($filename);
268
    }
269
270
    public function serializeParams(): string
271
    {
272
        return implode(
273
            "&",
274
            [$this->zoom, $this->lat, $this->lon, $this->width, $this->height, serialize($this->markers), $this->maptype, $this->kmlFileName, $this->gpxFileName, $this->geojsonFileName]
275
        );
276
    }
277
278
    public function mapCacheIDToFilename(): string
279
    {
280
        if (!$this->mapCacheFile) {
281
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_"
0 ignored issues
show
Bug introduced by
Are you sure $this->maptype of type mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . /** @scrutinizer ignore-type */ $this->maptype . "/" . $this->zoom . "/cache_"
Loading history...
282
                . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2)
283
                . "/" . substr($this->mapCacheID, 4);
284
        }
285
        return $this->mapCacheFile . "." . $this->mapCacheExtension;
286
    }
287
288
    /**
289
     * make the map.
290
     */
291
    public function makeMap(): void
292
    {
293
        $this->initCoords();
294
        $this->createBaseMap();
295
        if ($this->markers !== []) {
296
            $this->placeMarkers();
297
        }
298
        if (file_exists($this->kmlFileName)) {
299
            try {
300
                $this->drawKML();
301
            } catch (exception $e) {
0 ignored issues
show
Bug introduced by
The type dokuwiki\plugin\openlayersmap\exception was not found. Did you mean exception? If so, make sure to prefix the type with \.
Loading history...
302
                Logger::error('failed to load KML file', $e);
303
            }
304
        }
305
        if (file_exists($this->gpxFileName)) {
306
            try {
307
                $this->drawGPX();
308
            } catch (exception $e) {
309
                Logger::error('failed to load GPX file', $e);
310
            }
311
        }
312
        if (file_exists($this->geojsonFileName)) {
313
            try {
314
                $this->drawGeojson();
315
            } catch (exception $e) {
316
                Logger::error('failed to load GeoJSON file', $e);
317
            }
318
        }
319
320
        $this->drawCopyright();
321
    }
322
323
    /**
324
     */
325
    public function initCoords(): void
326
    {
327
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
328
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
329
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
330
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
331
    }
332
333
    /**
334
     *
335
     * @param float $long
336
     * @param int   $zoom
337
     * @return float|int
338
     */
339
    public function lonToTile(float $long, int $zoom)
340
    {
341
        return (($long + 180) / 360) * 2 ** $zoom;
342
    }
343
344
    /**
345
     *
346
     * @param float $lat
347
     * @param int   $zoom
348
     * @return float|int
349
     */
350
    public function latToTile(float $lat, int $zoom)
351
    {
352
        return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * 2 ** $zoom;
353
    }
354
355
    /**
356
     * make basemap image.
357
     */
358
    public function createBaseMap(): void
359
    {
360
        $this->image   = imagecreatetruecolor($this->width, $this->height);
361
        $startX        = floor($this->centerX - ($this->width / $this->tileSize) / 2);
362
        $startY        = floor($this->centerY - ($this->height / $this->tileSize) / 2);
363
        $endX          = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
364
        $endY          = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
365
        $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
366
        $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
367
        $this->offsetX += floor($this->width / 2);
368
        $this->offsetY += floor($this->height / 2);
369
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
370
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
371
372
        for ($x = $startX; $x <= $endX; $x++) {
373
            for ($y = $startY; $y <= $endY; $y++) {
374
                $url = str_replace(
375
                    ['{Z}', '{X}', '{Y}'],
376
                    [$this->zoom, $x, $y],
377
                    $this->tileInfo [$this->maptype] ['url']
378
                );
379
380
                $tileData = $this->fetchTile($url);
381
                if ($tileData) {
382
                    $tileImage = imagecreatefromstring($tileData);
0 ignored issues
show
Bug introduced by
It seems like $tileData can also be of type true; however, parameter $image of imagecreatefromstring() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

382
                    $tileImage = imagecreatefromstring(/** @scrutinizer ignore-type */ $tileData);
Loading history...
383
                } else {
384
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
385
                    $color     = imagecolorallocate($tileImage, 255, 255, 255);
386
                    @imagestring($tileImage, 1, 127, 127, 'err', $color);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for imagestring(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

386
                    /** @scrutinizer ignore-unhandled */ @imagestring($tileImage, 1, 127, 127, 'err', $color);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
387
                }
388
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
389
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
390
                Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize);
391
                imagecopy(
392
                    $this->image,
393
                    $tileImage,
394
                    $destX,
0 ignored issues
show
Bug introduced by
$destX of type double is incompatible with the type integer expected by parameter $dst_x of imagecopy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

394
                    /** @scrutinizer ignore-type */ $destX,
Loading history...
395
                    $destY,
0 ignored issues
show
Bug introduced by
$destY of type double is incompatible with the type integer expected by parameter $dst_y of imagecopy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

395
                    /** @scrutinizer ignore-type */ $destY,
Loading history...
396
                    0,
397
                    0,
398
                    $this->tileSize,
399
                    $this->tileSize
400
                );
401
            }
402
        }
403
    }
404
405
    /**
406
     * Fetch a tile and (if configured) store it in the cache.
407
     * @param string $url
408
     * @return bool|string
409
     * @todo refactor this to use dokuwiki\HTTP\HTTPClient or dokuwiki\HTTP\DokuHTTPClient
410
     *          for better proxy handling...
411
     */
412
    public function fetchTile(string $url)
413
    {
414
        if ($this->useTileCache && ($cached = $this->checkTileCache($url)))
415
            return $cached;
416
417
        $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')';
418
        if (function_exists("curl_init")) {
419
            // use cUrl
420
            $ch = curl_init();
421
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
422
            curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
423
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
424
            curl_setopt($ch, CURLOPT_URL, $url . $this->apikey);
425
            Logger::debug("StaticMap::fetchTile: getting: $url using curl_exec");
426
            $tile = curl_exec($ch);
427
            curl_close($ch);
428
        } else {
429
            // use file_get_contents
430
            global $conf;
431
            $opts = ['http' => ['method'          => "GET", 'header'          => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n", 'request_fulluri' => true]];
432
            if (
433
                isset($conf['proxy']['host'], $conf['proxy']['port'])
434
                && $conf['proxy']['host'] !== ''
435
                && $conf['proxy']['port'] !== ''
436
            ) {
437
                $opts['http'] += ['proxy' => "tcp://" . $conf['proxy']['host'] . ":" . $conf['proxy']['port']];
438
            }
439
440
            $context = stream_context_create($opts);
441
            Logger::debug(
442
                "StaticMap::fetchTile: getting: $url . $this->apikey using file_get_contents and options $opts"
443
            );
444
            $tile = file_get_contents($url . $this->apikey, false, $context);
445
        }
446
        if ($tile && $this->useTileCache) {
447
            $this->writeTileToCache($url, $tile);
448
        }
449
        return $tile;
450
    }
451
452
    /**
453
     *
454
     * @param string $url
455
     * @return string|false
456
     */
457
    public function checkTileCache(string $url)
458
    {
459
        $filename = $this->tileUrlToFilename($url);
460
        if (file_exists($filename)) {
461
            return file_get_contents($filename);
462
        }
463
        return false;
464
    }
465
466
    /**
467
     *
468
     * @param string $url
469
     */
470
    public function tileUrlToFilename(string $url): string
471
    {
472
        return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1);
473
    }
474
475
    /**
476
     * Write a tile into the cache.
477
     *
478
     * @param string $url
479
     * @param mixed  $data
480
     */
481
    public function writeTileToCache($url, $data): void
482
    {
483
        $filename = $this->tileUrlToFilename($url);
484
        $this->mkdirRecursive(dirname($filename), 0777);
485
        file_put_contents($filename, $data);
486
    }
487
488
    /**
489
     * Recursively create the directory.
490
     *
491
     * @param string $pathname
492
     *            The directory path.
493
     * @param int    $mode
494
     *            File access mode. For more information on modes, read the details on the chmod manpage.
495
     */
496
    public function mkdirRecursive(string $pathname, int $mode): bool
497
    {
498
        if (!is_dir(dirname($pathname))) {
499
            $this->mkdirRecursive(dirname($pathname), $mode);
500
        }
501
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
502
    }
503
504
    /**
505
     * Place markers on the map and number them in the same order as they are listed in the html.
506
     */
507
    public function placeMarkers(): void
508
    {
509
        $count         = 0;
510
        $color         = imagecolorallocate($this->image, 0, 0, 0);
511
        $bgcolor       = imagecolorallocate($this->image, 200, 200, 200);
512
        $markerBaseDir = __DIR__ . '/icons';
513
        $markerImageOffsetX  = 0;
514
        $markerImageOffsetY  = 0;
515
        $markerShadowOffsetX = 0;
516
        $markerShadowOffsetY = 0;
517
        $markerShadowImg     = null;
518
        // loop thru marker array
519
        foreach ($this->markers as $marker) {
520
            // set some local variables
521
            $markerLat  = $marker ['lat'];
522
            $markerLon  = $marker ['lon'];
523
            $markerType = $marker ['type'];
524
            // clear variables from previous loops
525
            $markerFilename = '';
526
            $markerShadow   = '';
527
            $matches        = false;
528
            // check for marker type, get settings from markerPrototypes
529
            if ($markerType) {
530
                foreach ($this->markerPrototypes as $markerPrototype) {
531
                    if (preg_match($markerPrototype ['regex'], $markerType, $matches)) {
0 ignored issues
show
Bug introduced by
$matches of type false is incompatible with the type string[] expected by parameter $matches of preg_match(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

531
                    if (preg_match($markerPrototype ['regex'], $markerType, /** @scrutinizer ignore-type */ $matches)) {
Loading history...
532
                        $markerFilename = $matches [0] . $markerPrototype ['extension'];
533
                        if ($markerPrototype ['offsetImage']) {
534
                            [$markerImageOffsetX, $markerImageOffsetY] = explode(
535
                                ",",
536
                                $markerPrototype ['offsetImage']
537
                            );
538
                        }
539
                        $markerShadow = $markerPrototype ['shadow'];
540
                        if ($markerShadow) {
541
                            [$markerShadowOffsetX, $markerShadowOffsetY] = explode(
542
                                ",",
543
                                $markerPrototype ['offsetShadow']
544
                            );
545
                        }
546
                    }
547
                }
548
            }
549
            // create img resource
550
            if (file_exists($markerBaseDir . '/' . $markerFilename)) {
551
                $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename);
552
            } else {
553
                $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png');
554
            }
555
            // check for shadow + create shadow recource
556
            if ($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) {
557
                $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow);
558
            }
559
            // calc position
560
            $destX = floor(
561
                ($this->width / 2) -
562
                $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom))
563
            );
564
            $destY = floor(
565
                ($this->height / 2) -
566
                $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom))
567
            );
568
            // copy shadow on basemap
569
            if ($markerShadow && $markerShadowImg) {
570
                imagecopy(
571
                    $this->image,
572
                    $markerShadowImg,
573
                    $destX + (int) $markerShadowOffsetX,
0 ignored issues
show
Bug introduced by
$destX + (int)$markerShadowOffsetX of type double is incompatible with the type integer expected by parameter $dst_x of imagecopy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

573
                    /** @scrutinizer ignore-type */ $destX + (int) $markerShadowOffsetX,
Loading history...
574
                    $destY + (int) $markerShadowOffsetY,
0 ignored issues
show
Bug introduced by
$destY + (int)$markerShadowOffsetY of type double is incompatible with the type integer expected by parameter $dst_y of imagecopy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

574
                    /** @scrutinizer ignore-type */ $destY + (int) $markerShadowOffsetY,
Loading history...
575
                    0,
576
                    0,
577
                    imagesx($markerShadowImg),
578
                    imagesy($markerShadowImg)
579
                );
580
            }
581
            // copy marker on basemap above shadow
582
            imagecopy(
583
                $this->image,
584
                $markerImg,
585
                $destX + (int) $markerImageOffsetX,
586
                $destY + (int) $markerImageOffsetY,
587
                0,
588
                0,
589
                imagesx($markerImg),
590
                imagesy($markerImg)
591
            );
592
            // add label
593
            imagestring(
594
                $this->image,
595
                3,
596
                $destX - imagesx($markerImg) + 1,
0 ignored issues
show
Bug introduced by
$destX - imagesx($markerImg) + 1 of type double is incompatible with the type integer expected by parameter $x of imagestring(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

596
                /** @scrutinizer ignore-type */ $destX - imagesx($markerImg) + 1,
Loading history...
597
                $destY + (int) $markerImageOffsetY + 1,
0 ignored issues
show
Bug introduced by
$destY + (int)$markerImageOffsetY + 1 of type double is incompatible with the type integer expected by parameter $y of imagestring(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

597
                /** @scrutinizer ignore-type */ $destY + (int) $markerImageOffsetY + 1,
Loading history...
598
                ++$count,
599
                $bgcolor
600
            );
601
            imagestring(
602
                $this->image,
603
                3,
604
                $destX - imagesx($markerImg),
605
                $destY + (int) $markerImageOffsetY,
606
                $count,
607
                $color
608
            );
609
        }
610
    }
611
612
    /**
613
     * Draw kml trace on the map.
614
     * @throws exception when loading the KML fails
615
     */
616
    public function drawKML(): void
617
    {
618
        // TODO get colour from kml node (not currently supported in geoPHP)
619
        $col     = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127);
0 ignored issues
show
Bug introduced by
0.4 * 127 of type double is incompatible with the type integer expected by parameter $alpha of imagecolorallocatealpha(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

619
        $col     = imagecolorallocatealpha($this->image, 255, 0, 0, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
620
        $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
621
        $this->drawGeometry($kmlgeom, $col);
622
    }
623
624
    /**
625
     * Draw geometry or geometry collection on the map.
626
     *
627
     * @param Geometry $geom
628
     * @param int      $colour
629
     *            drawing colour
630
     */
631
    private function drawGeometry(Geometry $geom, int $colour): void
632
    {
633
        if (empty($geom)) {
634
            return;
635
        }
636
637
        switch ($geom->geometryType()) {
638
            case 'GeometryCollection':
639
                // recursively draw part of the collection
640
                for ($i = 1; $i < $geom->numGeometries() + 1; $i++) {
641
                    $_geom = $geom->geometryN($i);
642
                    $this->drawGeometry($_geom, $colour);
643
                }
644
                break;
645
            case 'Polygon':
646
                $this->drawPolygon($geom, $colour);
647
                break;
648
            case 'LineString':
649
                $this->drawLineString($geom, $colour);
650
                break;
651
            case 'Point':
652
                $this->drawPoint($geom, $colour);
653
                break;
654
            // TODO implement / do nothing
655
            case 'MultiPolygon':
656
            case 'MultiLineString':
657
            case 'MultiPoint':
658
            default:
659
                // draw nothing
660
                break;
661
        }
662
    }
663
664
    /**
665
     * Draw a polygon on the map.
666
     *
667
     * @param Polygon $polygon
668
     * @param int     $colour
669
     *            drawing colour
670
     */
671
    private function drawPolygon($polygon, int $colour)
672
    {
673
        // TODO implementation of drawing holes,
674
        // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol?
675
676
        // print_r('Polygon:<br />');
677
        // print_r($polygon);
678
        $extPoints = [];
679
        // extring is a linestring actually..
680
        $extRing = $polygon->exteriorRing();
681
682
        for ($i = 1; $i < $extRing->numGeometries(); $i++) {
683
            $p1           = $extRing->geometryN($i);
684
            $x            = floor(
685
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
686
            );
687
            $y            = floor(
688
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
689
            );
690
            $extPoints [] = $x;
691
            $extPoints [] = $y;
692
        }
693
        // print_r('points:('.($i-1).')<br />');
694
        // print_r($extPoints);
695
        // imagepolygon ($this->image, $extPoints, $i-1, $colour );
696
        imagefilledpolygon($this->image, $extPoints, $i - 1, $colour);
697
    }
698
699
    /**
700
     * Draw a line on the map.
701
     *
702
     * @param LineString $line
703
     * @param int        $colour
704
     *            drawing colour
705
     */
706
    private function drawLineString($line, $colour)
707
    {
708
        imagesetthickness($this->image, 2);
709
        for ($p = 1; $p < $line->numGeometries(); $p++) {
710
            // get first pair of points
711
            $p1 = $line->geometryN($p);
712
            $p2 = $line->geometryN($p + 1);
713
            // translate to paper space
714
            $x1 = floor(
715
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
716
            );
717
            $y1 = floor(
718
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
719
            );
720
            $x2 = floor(
721
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom))
722
            );
723
            $y2 = floor(
724
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom))
725
            );
726
            // draw to image
727
            imageline($this->image, $x1, $y1, $x2, $y2, $colour);
0 ignored issues
show
Bug introduced by
$x2 of type double is incompatible with the type integer expected by parameter $x2 of imageline(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

727
            imageline($this->image, $x1, $y1, /** @scrutinizer ignore-type */ $x2, $y2, $colour);
Loading history...
Bug introduced by
$y1 of type double is incompatible with the type integer expected by parameter $y1 of imageline(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

727
            imageline($this->image, $x1, /** @scrutinizer ignore-type */ $y1, $x2, $y2, $colour);
Loading history...
Bug introduced by
$y2 of type double is incompatible with the type integer expected by parameter $y2 of imageline(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

727
            imageline($this->image, $x1, $y1, $x2, /** @scrutinizer ignore-type */ $y2, $colour);
Loading history...
Bug introduced by
$x1 of type double is incompatible with the type integer expected by parameter $x1 of imageline(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

727
            imageline($this->image, /** @scrutinizer ignore-type */ $x1, $y1, $x2, $y2, $colour);
Loading history...
728
        }
729
        imagesetthickness($this->image, 1);
730
    }
731
732
    /**
733
     * Draw a point on the map.
734
     *
735
     * @param Point $point
736
     * @param int   $colour
737
     *            drawing colour
738
     */
739
    private function drawPoint($point, $colour)
740
    {
741
        imagesetthickness($this->image, 2);
742
        // translate to paper space
743
        $cx = floor(
744
            ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom))
745
        );
746
        $cy = floor(
747
            ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom))
748
        );
749
        $r  = 5;
750
        // draw to image
751
        // imageellipse($this->image, $cx, $cy,$r, $r, $colour);
752
        imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour);
0 ignored issues
show
Bug introduced by
$cy of type double is incompatible with the type integer expected by parameter $cy of imagefilledellipse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

752
        imagefilledellipse($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, $colour);
Loading history...
Bug introduced by
$cx of type double is incompatible with the type integer expected by parameter $cx of imagefilledellipse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

752
        imagefilledellipse($this->image, /** @scrutinizer ignore-type */ $cx, $cy, $r, $r, $colour);
Loading history...
753
        // don't use imageellipse because the imagesetthickness function has
754
        // no effect. So the better workaround is to use imagearc.
755
        imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour);
0 ignored issues
show
Bug introduced by
$cy of type double is incompatible with the type integer expected by parameter $cy of imagearc(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

755
        imagearc($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, 0, 359, $colour);
Loading history...
Bug introduced by
$cx of type double is incompatible with the type integer expected by parameter $cx of imagearc(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

755
        imagearc($this->image, /** @scrutinizer ignore-type */ $cx, $cy, $r, $r, 0, 359, $colour);
Loading history...
756
        imagesetthickness($this->image, 1);
757
    }
758
759
    /**
760
     * Draw gpx trace on the map.
761
     * @throws exception when loading the GPX fails
762
     */
763
    public function drawGPX()
764
    {
765
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127);
0 ignored issues
show
Bug introduced by
0.4 * 127 of type double is incompatible with the type integer expected by parameter $alpha of imagecolorallocatealpha(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

765
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
766
        $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
767
        $this->drawGeometry($gpxgeom, $col);
768
    }
769
770
    /**
771
     * Draw geojson on the map.
772
     * @throws exception when loading the JSON fails
773
     */
774
    public function drawGeojson()
775
    {
776
        $col     = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127);
0 ignored issues
show
Bug introduced by
0.4 * 127 of type double is incompatible with the type integer expected by parameter $alpha of imagecolorallocatealpha(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

776
        $col     = imagecolorallocatealpha($this->image, 255, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
777
        $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json');
778
        $this->drawGeometry($gpxgeom, $col);
779
    }
780
781
    /**
782
     * add copyright and origin notice and icons to the map.
783
     */
784
    public function drawCopyright()
785
    {
786
        $logoBaseDir = __DIR__ . '/' . 'logo/';
787
        $logoImg     = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']);
788
        $textcolor   = imagecolorallocate($this->image, 0, 0, 0);
789
        $bgcolor     = imagecolorallocate($this->image, 200, 200, 200);
790
791
        imagecopy(
792
            $this->image,
793
            $logoImg,
794
            0,
795
            imagesy($this->image) - imagesy($logoImg),
796
            0,
797
            0,
798
            imagesx($logoImg),
799
            imagesy($logoImg)
800
        );
801
        imagestring(
802
            $this->image,
803
            1,
804
            imagesx($logoImg) + 2,
805
            imagesy($this->image) - imagesy($logoImg) + 1,
806
            $this->tileInfo ['openstreetmap'] ['txt'],
807
            $bgcolor
808
        );
809
        imagestring(
810
            $this->image,
811
            1,
812
            imagesx($logoImg) + 1,
813
            imagesy($this->image) - imagesy($logoImg),
814
            $this->tileInfo ['openstreetmap'] ['txt'],
815
            $textcolor
816
        );
817
818
        // additional tile source info, ie. who created/hosted the tiles
819
        $xIconOffset = 0;
820
        if ($this->maptype === 'openstreetmap') {
821
            $mapAuthor = "(c) OpenStreetMap maps/CC BY-SA";
822
        } else {
823
            $mapAuthor   = $this->tileInfo [$this->maptype] ['txt'];
824
            $iconImg     = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']);
825
            $xIconOffset = imagesx($iconImg);
826
            imagecopy(
827
                $this->image,
828
                $iconImg,
829
                imagesx($logoImg) + 1,
830
                imagesy($this->image) - imagesy($iconImg),
831
                0,
832
                0,
833
                imagesx($iconImg),
834
                imagesy($iconImg)
835
            );
836
        }
837
        imagestring(
838
            $this->image,
839
            1,
840
            imagesx($logoImg) + $xIconOffset + 4,
841
            imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1,
0 ignored issues
show
Bug introduced by
imagesy($this->image) - ...gesy($logoImg) / 2) + 1 of type double is incompatible with the type integer expected by parameter $y of imagestring(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

841
            /** @scrutinizer ignore-type */ imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1,
Loading history...
842
            $mapAuthor,
843
            $bgcolor
844
        );
845
        imagestring(
846
            $this->image,
847
            1,
848
            imagesx($logoImg) + $xIconOffset + 3,
849
            imagesy($this->image) - ceil(imagesy($logoImg) / 2),
850
            $mapAuthor,
851
            $textcolor
852
        );
853
    }
854
}
855