StaticMap::__construct()   A
last analyzed

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
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