Passed
Pull Request — master (#8)
by Mark
02:16
created

StaticMap::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 18
nc 2
nop 14
dl 0
loc 36
rs 9.6666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

381
                    $tileImage = imagecreatefromstring(/** @scrutinizer ignore-type */ $tileData);
Loading history...
382
                } else {
383
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
384
                    $color     = imagecolorallocate($tileImage, 255, 255, 255);
385
                    @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

385
                    /** @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...
386
                }
387
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
388
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
389
                Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize);
390
                imagecopy(
391
                    $this->image,
392
                    $tileImage,
393
                    $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

393
                    /** @scrutinizer ignore-type */ $destX,
Loading history...
394
                    $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

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

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

572
                    /** @scrutinizer ignore-type */ $destX + (int) $markerShadowOffsetX,
Loading history...
573
                    $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

573
                    /** @scrutinizer ignore-type */ $destY + (int) $markerShadowOffsetY,
Loading history...
574
                    0,
575
                    0,
576
                    imagesx($markerShadowImg),
577
                    imagesy($markerShadowImg)
578
                );
579
            }
580
            // copy marker on basemap above shadow
581
            imagecopy(
582
                $this->image,
583
                $markerImg,
584
                $destX + (int) $markerImageOffsetX,
585
                $destY + (int) $markerImageOffsetY,
586
                0,
587
                0,
588
                imagesx($markerImg),
589
                imagesy($markerImg)
590
            );
591
            // add label
592
            imagestring(
593
                $this->image,
594
                3,
595
                $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

595
                /** @scrutinizer ignore-type */ $destX - imagesx($markerImg) + 1,
Loading history...
596
                $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

596
                /** @scrutinizer ignore-type */ $destY + (int) $markerImageOffsetY + 1,
Loading history...
597
                ++$count,
598
                $bgcolor
599
            );
600
            imagestring(
601
                $this->image,
602
                3,
603
                $destX - imagesx($markerImg),
604
                $destY + (int) $markerImageOffsetY,
605
                $count,
606
                $color
607
            );
608
        }
609
    }
610
611
    /**
612
     * Draw kml trace on the map.
613
     * @throws exception when loading the KML fails
614
     */
615
    public function drawKML(): void
616
    {
617
        // TODO get colour from kml node (not currently supported in geoPHP)
618
        $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

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

726
            imageline($this->image, /** @scrutinizer ignore-type */ $x1, $y1, $x2, $y2, $colour);
Loading history...
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

726
            imageline($this->image, $x1, $y1, /** @scrutinizer ignore-type */ $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

726
            imageline($this->image, $x1, $y1, $x2, /** @scrutinizer ignore-type */ $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

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

751
        imagefilledellipse($this->image, /** @scrutinizer ignore-type */ $cx, $cy, $r, $r, $colour);
Loading history...
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

751
        imagefilledellipse($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, $colour);
Loading history...
752
        // don't use imageellipse because the imagesetthickness function has
753
        // no effect. So the better workaround is to use imagearc.
754
        imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour);
0 ignored issues
show
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

754
        imagearc($this->image, /** @scrutinizer ignore-type */ $cx, $cy, $r, $r, 0, 359, $colour);
Loading history...
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

754
        imagearc($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, 0, 359, $colour);
Loading history...
755
        imagesetthickness($this->image, 1);
756
    }
757
758
    /**
759
     * Draw gpx trace on the map.
760
     * @throws exception when loading the GPX fails
761
     */
762
    public function drawGPX()
763
    {
764
        $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

764
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
765
        $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
766
        $this->drawGeometry($gpxgeom, $col);
767
    }
768
769
    /**
770
     * Draw geojson on the map.
771
     * @throws exception when loading the JSON fails
772
     */
773
    public function drawGeojson()
774
    {
775
        $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

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

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