Completed
Push — master ( b22e29...3f2aa7 )
by Mark
14s queued 11s
created

StaticMap::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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
 * Copyright (c) 2012-2018 Mark C. Prins <[email protected]>
4
 *
5
 * In part based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/
6
 *
7
 * Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com>
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
// phpcs:disable PSR1.Files.SideEffects
23
// TODO resolve side effect
24
include_once(realpath(__DIR__) . '/../geophp/geoPHP/geoPHP.inc');
25
26
/**
27
 *
28
 * @author Mark C. Prins <[email protected]>
29
 * @author Gerhard Koch <gerhard.koch AT ymail.com>
30
 *
31
 */
32
class StaticMap {
33
34
    // the final output
35
    private $tileSize = 256;
36
    private $tileInfo = array(
37
        // OSM sources
38
        'openstreetmap' => array(
39
            'txt'  => '(c) OpenStreetMap data/ODbl',
40
            'logo' => 'osm_logo.png',
41
            'url'  => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'
42
        ),
43
        // OCM sources
44
        'cycle'         => array(
45
            'txt'  => '(c) Thunderforest maps',
46
            'logo' => 'tf_logo.png',
47
            'url'  => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'
48
        ),
49
        'transport'     => array(
50
            'txt'  => '(c) Thunderforest maps',
51
            'logo' => 'tf_logo.png',
52
            'url'  => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'
53
        ),
54
        'landscape'     => array(
55
            'txt'  => '(c) Thunderforest maps',
56
            'logo' => 'tf_logo.png',
57
            'url'  => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png'
58
        ),
59
        'outdoors'      => array(
60
            'txt'  => '(c) Thunderforest maps',
61
            'logo' => 'tf_logo.png',
62
            'url'  => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png'
63
        ),
64
        'toner-lite'    => array(
65
            'txt'  => 'Stamen tiles',
66
            'logo' => 'stamen.png',
67
            'url'  => 'http://tile.stamen.com/toner-lite/{Z}/{X}/{Y}.png'
68
        ),
69
        'terrain'       => array(
70
            'txt'  => 'Stamen tiles',
71
            'logo' => 'stamen.png',
72
            'url'  => 'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png'
73
        )
74
        //,
75
        // 'piste'=>array(
76
        // 'txt'=>'OpenPisteMap tiles',
77
        // 'logo'=>'piste_logo.png',
78
        // 'url'=>''),
79
        // 'sea'=>array(
80
        // 'txt'=>'OpenSeaMap tiles',
81
        // 'logo'=>'sea_logo.png',
82
        // 'url'=>''),
83
        // H&B sources
84
        //          'hikeandbike' => array (
85
        //                  'txt' => 'Hike & Bike Map',
86
        //                  'logo' => 'hnb_logo.png',
87
        //                  //'url' => 'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png'
88
        //                  //moved to: https://www.toolserver.org/tiles/hikebike/12/2105/1388.png
89
        //                  'url' => 'http://c.tiles.wmflabs.org/hikebike/{Z}/{X}/{Y}.png'
90
        //          )
91
    );
92
    private $tileDefaultSrc = 'openstreetmap';
93
94
    // set up markers
95
    private $markerPrototypes = array(
96
        // found at http://www.mapito.net/map-marker-icons.html
97
        // these are 17x19 px with a pointer at the bottom left
98
        'lightblue' => array(
99
            'regex'        => '/^lightblue([0-9]+)$/',
100
            'extension'    => '.png',
101
            'shadow'       => false,
102
            'offsetImage'  => '0,-19',
103
            'offsetShadow' => false
104
        ),
105
        // openlayers std markers are 21x25px with shadow
106
        'ol-marker' => array(
107
            'regex'        => '/^marker(|-blue|-gold|-green|-red)+$/',
108
            'extension'    => '.png',
109
            'shadow'       => 'marker_shadow.png',
110
            'offsetImage'  => '-10,-25',
111
            'offsetShadow' => '-1,-13'
112
        ),
113
        // these are 16x16 px
114
        'ww_icon'   => array(
115
            'regex'        => '/ww_\S+$/',
116
            'extension'    => '.png',
117
            'shadow'       => false,
118
            'offsetImage'  => '-8,-8',
119
            'offsetShadow' => false
120
        ),
121
        // assume these are 16x16 px
122
        'rest'      => array(
123
            'regex'        => '/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/',
124
            'extension'    => '.png',
125
            'shadow'       => 'marker_shadow.png',
126
            'offsetImage'  => '-8,-8',
127
            'offsetShadow' => '-1,-1'
128
        )
129
    );
130
    private $centerX;
131
    private $centerY;
132
    private $offsetX;
133
    private $offsetY;
134
    private $image;
135
    private $zoom;
136
    private $lat;
137
    private $lon;
138
    private $width;
139
    private $height;
140
    private $markers;
141
    private $maptype;
142
    private $kmlFileName;
143
    private $gpxFileName;
144
    private $geojsonFileName;
145
    private $autoZoomExtent;
146
    private $apikey;
147
    private $tileCacheBaseDir;
148
    private $mapCacheBaseDir;
149
    private $mediaBaseDir;
150
    private $useTileCache;
151
    private $mapCacheID = '';
152
    private $mapCacheFile = '';
153
    private $mapCacheExtension = 'png';
154
155
    /**
156
     * Constructor.
157
     *
158
     * @param float  $lat
159
     *            Latitude (x) of center of map
160
     * @param float  $lon
161
     *            Longitude (y) of center of map
162
     * @param int    $zoom
163
     *            Zoomlevel
164
     * @param int    $width
165
     *            Width in pixels
166
     * @param int    $height
167
     *            Height in pixels
168
     * @param string $maptype
169
     *            Name of the map
170
     * @param array  $markers
171
     *            array of markers
172
     * @param string $gpx
173
     *            GPX filename
174
     * @param string $kml
175
     *            KML filename
176
     * @param string $geojson
177
     * @param string $mediaDir
178
     *            Directory to store/cache maps
179
     * @param string $tileCacheBaseDir
180
     *            Directory to cache map tiles
181
     * @param bool   $autoZoomExtent
182
     *            Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
183
     * @param string $apikey
184
     */
185
    public function __construct(
186
        float $lat,
187
        float $lon,
188
        int $zoom,
189
        int $width,
190
        int $height,
191
        string $maptype,
192
        array $markers,
193
        string $gpx,
194
        string $kml,
195
        string $geojson,
196
        string $mediaDir,
197
        string $tileCacheBaseDir,
198
        bool $autoZoomExtent = true,
199
        string $apikey = ''
200
    ) {
201
        $this->zoom   = $zoom;
202
        $this->lat    = $lat;
203
        $this->lon    = $lon;
204
        $this->width  = $width;
205
        $this->height = $height;
206
        // validate + set maptype
207
        $this->maptype = $this->tileDefaultSrc;
208
        if(array_key_exists($maptype, $this->tileInfo)) {
209
            $this->maptype = $maptype;
210
        }
211
        $this->markers          = $markers;
212
        $this->kmlFileName      = $kml;
213
        $this->gpxFileName      = $gpx;
214
        $this->geojsonFileName  = $geojson;
215
        $this->mediaBaseDir     = $mediaDir;
216
        $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles';
217
        $this->useTileCache     = $this->tileCacheBaseDir !== '';
218
        $this->mapCacheBaseDir  = $mediaDir . '/olmapmaps';
219
        $this->autoZoomExtent   = $autoZoomExtent;
220
        $this->apikey           = $apikey;
221
    }
222
223
    /**
224
     * get the map, this may return a reference to a cached copy.
225
     *
226
     * @return string url relative to media dir
227
     */
228
    public function getMap(): string {
229
        try {
230
            if($this->autoZoomExtent) {
231
                $this->autoZoom();
232
            }
233
        } catch(Exception $e) {
234
            dbglog($e);
0 ignored issues
show
Bug introduced by
The function dbglog was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

234
            /** @scrutinizer ignore-call */ 
235
            dbglog($e);
Loading history...
235
        }
236
237
        // use map cache, so check cache for map
238
        if(!$this->checkMapCache()) {
239
            // map is not in cache, needs to be build
240
            $this->makeMap();
241
            $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777);
242
            imagepng($this->image, $this->mapCacheIDToFilename(), 9);
243
        }
244
        $doc = $this->mapCacheIDToFilename();
245
        // make url relative to media dir
246
        return str_replace($this->mediaBaseDir, '', $doc);
247
    }
248
249
    /**
250
     * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
251
     * can throw an error like
252
     * "Fatal error: Uncaught Exception: Cannot create a collection with non-geometries in
253
     * D:\www\wild-water.nl\www\dokuwiki\lib\plugins\geophp\geoPHP\lib\geometry\Collection.class.php:29"
254
     *
255
     * @param float $paddingFactor
256
     *            buffer constant to enlarge (>1.0) the zoom level
257
     * @throws Exception
258
     */
259
    private function autoZoom(float $paddingFactor = 1.0): void {
260
        $geoms    = array();
261
        $geoms [] = new Point ($this->lon, $this->lat);
262
        if(!empty ($this->markers)) {
263
            foreach($this->markers as $marker) {
264
                $geoms [] = new Point ($marker ['lon'], $marker ['lat']);
265
            }
266
        }
267
        if(file_exists($this->kmlFileName)) {
268
            $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
0 ignored issues
show
Bug introduced by
The type 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...
269
            if($g !== false) {
270
                $geoms [] = $g;
271
            }
272
        }
273
        if(file_exists($this->gpxFileName)) {
274
            $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
275
            if($g !== false) {
276
                $geoms [] = $g;
277
            }
278
        }
279
        if(file_exists($this->geojsonFileName)) {
280
            $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson');
281
            if($g !== false) {
282
                $geoms [] = $g;
283
            }
284
        }
285
286
        if(count($geoms) <= 1) {
287
            dbglog($geoms, "StaticMap::autoZoom: Skip setting autozoom options");
0 ignored issues
show
Bug introduced by
The function dbglog was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

287
            /** @scrutinizer ignore-call */ 
288
            dbglog($geoms, "StaticMap::autoZoom: Skip setting autozoom options");
Loading history...
288
            return;
289
        }
290
291
        $geom     = new GeometryCollection ($geoms);
0 ignored issues
show
Bug introduced by
The type 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...
292
        $centroid = $geom->centroid();
293
        $bbox     = $geom->getBBox();
294
295
        // determine vertical resolution, this depends on the distance from the equator
296
        // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
297
        $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360)));
298
        $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360)));
299
        dbglog("StaticMap::autoZoom: vertical resolution: $vy0, $vy1");
300
        $zoomFactorPowered  = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0));
301
        $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
302
        // determine horizontal resolution
303
        $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width;
304
        $resolution           = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
305
        $zoom                 = log(360 / ($resolution * $this->tileSize), 2);
306
307
        if(is_finite($zoom) && $zoom < 15 && $zoom > 2) {
308
            $this->zoom = floor($zoom);
309
        }
310
        $this->lon = $centroid->getX();
311
        $this->lat = $centroid->getY();
312
        dbglog("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat");
313
    }
314
315
    public function checkMapCache(): bool {
316
        // side effect: set the mapCacheID
317
        $this->mapCacheID = md5($this->serializeParams());
318
        $filename         = $this->mapCacheIDToFilename();
319
        return file_exists($filename);
320
    }
321
322
    public function serializeParams(): string {
323
        return join(
324
            "&", array(
325
                   $this->zoom,
326
                   $this->lat,
327
                   $this->lon,
328
                   $this->width,
329
                   $this->height,
330
                   serialize($this->markers),
331
                   $this->maptype,
332
                   $this->kmlFileName,
333
                   $this->gpxFileName,
334
                   $this->geojsonFileName
335
               )
336
        );
337
    }
338
339
    public function mapCacheIDToFilename(): string {
340
        if(!$this->mapCacheFile) {
341
            $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

341
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . /** @scrutinizer ignore-type */ $this->maptype . "/" . $this->zoom . "/cache_"
Loading history...
342
                . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2)
343
                . "/" . substr($this->mapCacheID, 4);
344
        }
345
        return $this->mapCacheFile . "." . $this->mapCacheExtension;
346
    }
347
348
    /**
349
     * make the map.
350
     */
351
    public function makeMap(): void {
352
        $this->initCoords();
353
        $this->createBaseMap();
354
        if(!empty ($this->markers))
355
            $this->placeMarkers();
356
        if(file_exists($this->kmlFileName))
357
            $this->drawKML();
358
        if(file_exists($this->gpxFileName))
359
            $this->drawGPX();
360
        if(file_exists($this->geojsonFileName))
361
            $this->drawGeojson();
362
363
        $this->drawCopyright();
364
    }
365
366
    /**
367
     */
368
    public function initCoords(): void {
369
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
370
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
371
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
372
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
373
    }
374
375
    /**
376
     *
377
     * @param float $long
378
     * @param int   $zoom
379
     * @return float|int
380
     */
381
    public function lonToTile(float $long, int $zoom) {
382
        return (($long + 180) / 360) * pow(2, $zoom);
383
    }
384
385
    /**
386
     *
387
     * @param float $lat
388
     * @param int   $zoom
389
     * @return float|int
390
     */
391
    public function latToTile(float $lat, int $zoom) {
392
        return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * pow(2, $zoom);
393
    }
394
395
    /**
396
     * make basemap image.
397
     */
398
    public function createBaseMap(): void {
399
        $this->image   = imagecreatetruecolor($this->width, $this->height);
400
        $startX        = floor($this->centerX - ($this->width / $this->tileSize) / 2);
401
        $startY        = floor($this->centerY - ($this->height / $this->tileSize) / 2);
402
        $endX          = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
403
        $endY          = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
404
        $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
405
        $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
406
        $this->offsetX += floor($this->width / 2);
407
        $this->offsetY += floor($this->height / 2);
408
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
409
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
410
411
        for($x = $startX; $x <= $endX; $x++) {
412
            for($y = $startY; $y <= $endY; $y++) {
413
                $url = str_replace(
414
                    array(
415
                        '{Z}',
416
                        '{X}',
417
                        '{Y}'
418
                    ), array(
419
                        $this->zoom,
420
                        $x,
421
                        $y
422
                    ), $this->tileInfo [$this->maptype] ['url']
423
                );
424
425
                $tileData = $this->fetchTile($url);
426
                if($tileData) {
427
                    $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

427
                    $tileImage = imagecreatefromstring(/** @scrutinizer ignore-type */ $tileData);
Loading history...
428
                } else {
429
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
430
                    $color     = imagecolorallocate($tileImage, 255, 255, 255);
431
                    @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

431
                    /** @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...
432
                }
433
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
434
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
435
                dbglog($this->tileSize, "imagecopy tile into image: $destX, $destY");
0 ignored issues
show
Bug introduced by
The function dbglog was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

435
                /** @scrutinizer ignore-call */ 
436
                dbglog($this->tileSize, "imagecopy tile into image: $destX, $destY");
Loading history...
436
                imagecopy(
437
                    $this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize,
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

437
                    $this->image, $tileImage, /** @scrutinizer ignore-type */ $destX, $destY, 0, 0, $this->tileSize,
Loading history...
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

437
                    $this->image, $tileImage, $destX, /** @scrutinizer ignore-type */ $destY, 0, 0, $this->tileSize,
Loading history...
438
                    $this->tileSize
439
                );
440
            }
441
        }
442
    }
443
444
    /**
445
     * Fetch a tile and (if configured) store it in the cache.
446
     *
447
     * @param string $url
448
     * @return bool|string
449
     */
450
    public function fetchTile(string $url) {
451
        if($this->useTileCache && ($cached = $this->checkTileCache($url)))
452
            return $cached;
453
454
        $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')';
455
        if(function_exists("curl_init")) {
456
            // use cUrl
457
            $ch = curl_init();
458
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
459
            curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
460
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
461
            curl_setopt($ch, CURLOPT_URL, $url . $this->apikey);
462
            $tile = curl_exec($ch);
463
            curl_close($ch);
464
        } else {
465
            // use file_get_contents
466
            global $conf;
467
            $opts    = array(
468
                'http' => array(
469
                    'method'          => "GET",
470
                    'header'          => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n",
471
                    'proxy'           => "tcp://" . $conf ['proxy'] ['host'] . ":" . $conf ['proxy'] ['port'],
472
                    'request_fulluri' => true
473
                )
474
            );
475
            $context = stream_context_create($opts);
476
            $tile    = file_get_contents($url . $this->apikey, false, $context);
477
        }
478
        if($tile && $this->useTileCache) {
479
            $this->writeTileToCache($url, $tile);
480
        }
481
        return $tile;
482
    }
483
484
    /**
485
     *
486
     * @param string $url
487
     * @return string|false
488
     */
489
    public function checkTileCache(string $url) {
490
        $filename = $this->tileUrlToFilename($url);
491
        if(file_exists($filename)) {
492
            return file_get_contents($filename);
493
        }
494
        return false;
495
    }
496
497
    /**
498
     *
499
     * @param string $url
500
     * @return string
501
     */
502
    public function tileUrlToFilename(string $url): string {
503
        return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1);
504
    }
505
506
    /**
507
     * Write a tile into the cache.
508
     *
509
     * @param string $url
510
     * @param mixed  $data
511
     */
512
    public function writeTileToCache($url, $data): void {
513
        $filename = $this->tileUrlToFilename($url);
514
        $this->mkdirRecursive(dirname($filename), 0777);
515
        file_put_contents($filename, $data);
516
    }
517
518
    /**
519
     * Recursively create the directory.
520
     *
521
     * @param string $pathname
522
     *            The directory path.
523
     * @param int    $mode
524
     *            File access mode. For more information on modes, read the details on the chmod manpage.
525
     */
526
    public function mkdirRecursive(string $pathname, int $mode): bool {
527
        is_dir(dirname($pathname)) || $this->mkdirRecursive(dirname($pathname), $mode);
528
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
529
    }
530
531
    /**
532
     * Place markers on the map and number them in the same order as they are listed in the html.
533
     */
534
    public function placeMarkers(): void {
535
        $count         = 0;
536
        $color         = imagecolorallocate($this->image, 0, 0, 0);
537
        $bgcolor       = imagecolorallocate($this->image, 200, 200, 200);
538
        $markerBaseDir = __DIR__ . '/icons';
539
        // loop thru marker array
540
        foreach($this->markers as $marker) {
541
            // set some local variables
542
            $markerLat  = $marker ['lat'];
543
            $markerLon  = $marker ['lon'];
544
            $markerType = $marker ['type'];
545
            // clear variables from previous loops
546
            $markerFilename = '';
547
            $markerShadow   = '';
548
            $matches        = false;
549
            // check for marker type, get settings from markerPrototypes
550
            if($markerType) {
551
                foreach($this->markerPrototypes as $markerPrototype) {
552
                    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

552
                    if(preg_match($markerPrototype ['regex'], $markerType, /** @scrutinizer ignore-type */ $matches)) {
Loading history...
553
                        $markerFilename = $matches [0] . $markerPrototype ['extension'];
554
                        if($markerPrototype ['offsetImage']) {
555
                            list ($markerImageOffsetX, $markerImageOffsetY) = explode(
556
                                ",",
557
                                $markerPrototype ['offsetImage']
558
                            );
559
                        }
560
                        $markerShadow = $markerPrototype ['shadow'];
561
                        if($markerShadow) {
562
                            list ($markerShadowOffsetX, $markerShadowOffsetY) = explode(
563
                                ",",
564
                                $markerPrototype ['offsetShadow']
565
                            );
566
                        }
567
                    }
568
                }
569
            }
570
            // create img resource
571
            if(file_exists($markerBaseDir . '/' . $markerFilename)) {
572
                $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename);
573
            } else {
574
                $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png');
575
            }
576
            // check for shadow + create shadow recource
577
            if($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) {
578
                $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow);
579
            }
580
            // calc position
581
            $destX = floor(
582
                ($this->width / 2) -
583
                $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom))
584
            );
585
            $destY = floor(
586
                ($this->height / 2) -
587
                $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom))
588
            );
589
            // copy shadow on basemap
590
            if($markerShadow && $markerShadowImg) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $markerShadowImg does not seem to be defined for all execution paths leading up to this point.
Loading history...
591
                imagecopy(
592
                    $this->image,
593
                    $markerShadowImg,
594
                    $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

594
                    /** @scrutinizer ignore-type */ $destX + (int) $markerShadowOffsetX,
Loading history...
Comprehensibility Best Practice introduced by
The variable $markerShadowOffsetX does not seem to be defined for all execution paths leading up to this point.
Loading history...
595
                    $destY + (int) $markerShadowOffsetY,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $markerShadowOffsetY does not seem to be defined for all execution paths leading up to this point.
Loading history...
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

595
                    /** @scrutinizer ignore-type */ $destY + (int) $markerShadowOffsetY,
Loading history...
596
                    0,
597
                    0,
598
                    imagesx($markerShadowImg),
599
                    imagesy($markerShadowImg)
600
                );
601
            }
602
            // copy marker on basemap above shadow
603
            imagecopy(
604
                $this->image,
605
                $markerImg,
606
                $destX + (int) $markerImageOffsetX,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $markerImageOffsetX does not seem to be defined for all execution paths leading up to this point.
Loading history...
607
                $destY + (int) $markerImageOffsetY,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $markerImageOffsetY does not seem to be defined for all execution paths leading up to this point.
Loading history...
608
                0,
609
                0,
610
                imagesx($markerImg),
611
                imagesy($markerImg)
612
            );
613
            // add label
614
            imagestring(
615
                $this->image,
616
                3,
617
                $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

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

618
                /** @scrutinizer ignore-type */ $destY + (int) $markerImageOffsetY + 1,
Loading history...
619
                ++$count,
620
                $bgcolor
621
            );
622
            imagestring(
623
                $this->image,
624
                3,
625
                $destX - imagesx($markerImg),
626
                $destY + (int) $markerImageOffsetY,
627
                $count,
628
                $color
629
            );
630
        }
631
    }
632
633
    /**
634
     * Draw kml trace on the map.
635
     */
636
    public function drawKML(): void {
637
        // TODO get colour from kml node (not currently supported in geoPHP)
638
        $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

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

744
            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

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

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

744
            imageline($this->image, $x1, $y1, $x2, /** @scrutinizer ignore-type */ $y2, $colour);
Loading history...
745
        }
746
        imagesetthickness($this->image, 1);
747
    }
748
749
    /**
750
     * Draw a point on the map.
751
     *
752
     * @param Point $point
753
     * @param int   $colour
754
     *            drawing colour
755
     */
756
    private function drawPoint($point, $colour) {
757
        imagesetthickness($this->image, 2);
758
        // translate to paper space
759
        $cx = floor(
760
            ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom))
761
        );
762
        $cy = floor(
763
            ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom))
764
        );
765
        $r  = 5;
766
        // draw to image
767
        // imageellipse($this->image, $cx, $cy,$r, $r, $colour);
768
        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

768
        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

768
        imagefilledellipse($this->image, /** @scrutinizer ignore-type */ $cx, $cy, $r, $r, $colour);
Loading history...
769
        // don't use imageellipse because the imagesetthickness function has
770
        // no effect. So the better workaround is to use imagearc.
771
        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

771
        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

771
        imagearc($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, 0, 359, $colour);
Loading history...
772
        imagesetthickness($this->image, 1);
773
    }
774
775
    /**
776
     * Draw gpx trace on the map.
777
     */
778
    public function drawGPX() {
779
        $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

779
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
780
        $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
781
        $this->drawGeometry($gpxgeom, $col);
782
    }
783
784
    /**
785
     * Draw geojson on the map.
786
     */
787
    public function drawGeojson() {
788
        $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

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

849
            /** @scrutinizer ignore-type */ imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1,
Loading history...
850
            $mapAuthor,
851
            $bgcolor
852
        );
853
        imagestring(
854
            $this->image,
855
            1, imagesx($logoImg) + $xIconOffset + 3,
856
            imagesy($this->image) - ceil(imagesy($logoImg) / 2),
857
            $mapAuthor,
858
            $textcolor
859
        );
860
861
    }
862
}
863