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

StaticMap.php (31 issues)

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
use dokuwiki\Logger;
0 ignored issues
show
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...
23
24
// phpcs:disable PSR1.Files.SideEffects
25
// TODO resolve side effect
26
include_once(realpath(__DIR__) . '/../geophp/geoPHP/geoPHP.inc');
27
28
/**
29
 *
30
 * @author Mark C. Prins <[email protected]>
31
 * @author Gerhard Koch <gerhard.koch AT ymail.com>
32
 *
33
 */
34
class StaticMap
35
{
36
37
    // the final output
38
    private $tileSize = 256;
39
    private $tileInfo = array(
40
        // OSM sources
41
        'openstreetmap' => array(
42
            'txt'  => '(c) OpenStreetMap data/ODbl',
43
            'logo' => 'osm_logo.png',
44
            'url'  => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'
45
        ),
46
        // OCM sources
47
        'cycle'         => array(
48
            'txt'  => '(c) Thunderforest maps',
49
            'logo' => 'tf_logo.png',
50
            'url'  => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'
51
        ),
52
        'transport'     => array(
53
            'txt'  => '(c) Thunderforest maps',
54
            'logo' => 'tf_logo.png',
55
            'url'  => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'
56
        ),
57
        'landscape'     => array(
58
            'txt'  => '(c) Thunderforest maps',
59
            'logo' => 'tf_logo.png',
60
            'url'  => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png'
61
        ),
62
        'outdoors'      => array(
63
            'txt'  => '(c) Thunderforest maps',
64
            'logo' => 'tf_logo.png',
65
            'url'  => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png'
66
        ),
67
        'toner-lite'    => array(
68
            'txt'  => 'Stamen tiles',
69
            'logo' => 'stamen.png',
70
            'url'  => 'https://stamen-tiles.a.ssl.fastly.net/toner/{Z}/{X}/{Y}.png'
71
        ),
72
        'terrain'       => array(
73
            'txt'  => 'Stamen tiles',
74
            'logo' => 'stamen.png',
75
            'url'  => 'https://stamen-tiles.a.ssl.fastly.net/terrain/{Z}/{X}/{Y}.jpg'
76
        )
77
        //,
78
        // 'piste'=>array(
79
        // 'txt'=>'OpenPisteMap tiles',
80
        // 'logo'=>'piste_logo.png',
81
        // 'url'=>''),
82
        // 'sea'=>array(
83
        // 'txt'=>'OpenSeaMap tiles',
84
        // 'logo'=>'sea_logo.png',
85
        // 'url'=>''),
86
        // H&B sources
87
        //          'hikeandbike' => array (
88
        //                  'txt' => 'Hike & Bike Map',
89
        //                  'logo' => 'hnb_logo.png',
90
        //                  //'url' => 'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png'
91
        //                  //moved to: https://www.toolserver.org/tiles/hikebike/12/2105/1388.png
92
        //                  'url' => 'http://c.tiles.wmflabs.org/hikebike/{Z}/{X}/{Y}.png'
93
        //          )
94
    );
95
    private $tileDefaultSrc = 'openstreetmap';
96
97
    // set up markers
98
    private $markerPrototypes = array(
99
        // found at http://www.mapito.net/map-marker-icons.html
100
        // these are 17x19 px with a pointer at the bottom left
101
        'lightblue' => array(
102
            'regex'        => '/^lightblue([0-9]+)$/',
103
            'extension'    => '.png',
104
            'shadow'       => false,
105
            'offsetImage'  => '0,-19',
106
            'offsetShadow' => false
107
        ),
108
        // openlayers std markers are 21x25px with shadow
109
        'ol-marker' => array(
110
            'regex'        => '/^marker(|-blue|-gold|-green|-red)+$/',
111
            'extension'    => '.png',
112
            'shadow'       => 'marker_shadow.png',
113
            'offsetImage'  => '-10,-25',
114
            'offsetShadow' => '-1,-13'
115
        ),
116
        // these are 16x16 px
117
        'ww_icon'   => array(
118
            'regex'        => '/ww_\S+$/',
119
            'extension'    => '.png',
120
            'shadow'       => false,
121
            'offsetImage'  => '-8,-8',
122
            'offsetShadow' => false
123
        ),
124
        // assume these are 16x16 px
125
        'rest'      => array(
126
            'regex'        => '/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/',
127
            'extension'    => '.png',
128
            'shadow'       => 'marker_shadow.png',
129
            'offsetImage'  => '-8,-8',
130
            'offsetShadow' => '-1,-1'
131
        )
132
    );
133
    private $centerX;
134
    private $centerY;
135
    private $offsetX;
136
    private $offsetY;
137
    private $image;
138
    private $zoom;
139
    private $lat;
140
    private $lon;
141
    private $width;
142
    private $height;
143
    private $markers;
144
    private $maptype;
145
    private $kmlFileName;
146
    private $gpxFileName;
147
    private $geojsonFileName;
148
    private $autoZoomExtent;
149
    private $apikey;
150
    private $tileCacheBaseDir;
151
    private $mapCacheBaseDir;
152
    private $mediaBaseDir;
153
    private $useTileCache;
154
    private $mapCacheID = '';
155
    private $mapCacheFile = '';
156
    private $mapCacheExtension = 'png';
157
158
    /**
159
     * Constructor.
160
     *
161
     * @param float  $lat
162
     *            Latitude (x) of center of map
163
     * @param float  $lon
164
     *            Longitude (y) of center of map
165
     * @param int    $zoom
166
     *            Zoomlevel
167
     * @param int    $width
168
     *            Width in pixels
169
     * @param int    $height
170
     *            Height in pixels
171
     * @param string $maptype
172
     *            Name of the map
173
     * @param array  $markers
174
     *            array of markers
175
     * @param string $gpx
176
     *            GPX filename
177
     * @param string $kml
178
     *            KML filename
179
     * @param string $geojson
180
     * @param string $mediaDir
181
     *            Directory to store/cache maps
182
     * @param string $tileCacheBaseDir
183
     *            Directory to cache map tiles
184
     * @param bool   $autoZoomExtent
185
     *            Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
186
     * @param string $apikey
187
     */
188
    public function __construct(
189
        float $lat,
190
        float $lon,
191
        int $zoom,
192
        int $width,
193
        int $height,
194
        string $maptype,
195
        array $markers,
196
        string $gpx,
197
        string $kml,
198
        string $geojson,
199
        string $mediaDir,
200
        string $tileCacheBaseDir,
201
        bool $autoZoomExtent = true,
202
        string $apikey = ''
203
    ) {
204
        $this->zoom   = $zoom;
205
        $this->lat    = $lat;
206
        $this->lon    = $lon;
207
        $this->width  = $width;
208
        $this->height = $height;
209
        // validate + set maptype
210
        $this->maptype = $this->tileDefaultSrc;
211
        if (array_key_exists($maptype, $this->tileInfo)) {
212
            $this->maptype = $maptype;
213
        }
214
        $this->markers          = $markers;
215
        $this->kmlFileName      = $kml;
216
        $this->gpxFileName      = $gpx;
217
        $this->geojsonFileName  = $geojson;
218
        $this->mediaBaseDir     = $mediaDir;
219
        $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles';
220
        $this->useTileCache     = $this->tileCacheBaseDir !== '';
221
        $this->mapCacheBaseDir  = $mediaDir . '/olmapmaps';
222
        $this->autoZoomExtent   = $autoZoomExtent;
223
        $this->apikey           = $apikey;
224
    }
225
226
    /**
227
     * get the map, this may return a reference to a cached copy.
228
     *
229
     * @return string url relative to media dir
230
     */
231
    public function getMap(): string
232
    {
233
        try {
234
            if ($this->autoZoomExtent) {
235
                $this->autoZoom();
236
            }
237
        } catch (Exception $e) {
238
            Logger::debug($e);
239
        }
240
241
        // use map cache, so check cache for map
242
        if (!$this->checkMapCache()) {
243
            // map is not in cache, needs to be build
244
            $this->makeMap();
245
            $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777);
246
            imagepng($this->image, $this->mapCacheIDToFilename(), 9);
247
        }
248
        $doc = $this->mapCacheIDToFilename();
249
        // make url relative to media dir
250
        return str_replace($this->mediaBaseDir, '', $doc);
251
    }
252
253
    /**
254
     * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
255
     * can throw an error like
256
     * "Fatal error: Uncaught Exception: Cannot create a collection with non-geometries in
257
     * D:\www\wild-water.nl\www\dokuwiki\lib\plugins\geophp\geoPHP\lib\geometry\Collection.class.php:29"
258
     *
259
     * @param float $paddingFactor
260
     *            buffer constant to enlarge (>1.0) the zoom level
261
     * @throws Exception
262
     */
263
    private function autoZoom(float $paddingFactor = 1.0): void
264
    {
265
        $geoms    = array();
266
        $geoms [] = new Point ($this->lon, $this->lat);
267
        if (!empty ($this->markers)) {
268
            foreach ($this->markers as $marker) {
269
                $geoms [] = new Point ($marker ['lon'], $marker ['lat']);
270
            }
271
        }
272
        if (file_exists($this->kmlFileName)) {
273
            $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
0 ignored issues
show
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...
274
            if ($g !== false) {
275
                $geoms [] = $g;
276
            }
277
        }
278
        if (file_exists($this->gpxFileName)) {
279
            $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
280
            if ($g !== false) {
281
                $geoms [] = $g;
282
            }
283
        }
284
        if (file_exists($this->geojsonFileName)) {
285
            $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson');
286
            if ($g !== false) {
287
                $geoms [] = $g;
288
            }
289
        }
290
291
        if (count($geoms) <= 1) {
292
            Logger::debug("StaticMap::autoZoom: Skip setting autozoom options", $geoms);
293
            return;
294
        }
295
296
        $geom     = new GeometryCollection ($geoms);
0 ignored issues
show
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...
297
        $centroid = $geom->centroid();
298
        $bbox     = $geom->getBBox();
299
300
        // determine vertical resolution, this depends on the distance from the equator
301
        // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
302
        $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360)));
303
        $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360)));
304
        Logger::debug("StaticMap::autoZoom: vertical resolution: $vy0, $vy1");
305
        if ($vy1 - $vy0 === 0.0){
306
            $resolutionVertical = 0;
307
            Logger::debug("StaticMap::autoZoom: using $resolutionVertical");
308
        } else {
309
            $zoomFactorPowered  = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0));
310
            $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
311
        }
312
        // determine horizontal resolution
313
        $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width;
314
        Logger::debug("StaticMap::autoZoom: using $resolutionHorizontal");
315
        $resolution           = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
316
        $zoom                 = $this->zoom;
317
        if ($resolution > 0){
318
            $zoom             = log(360 / ($resolution * $this->tileSize), 2);
319
        }
320
321
        if (is_finite($zoom) && $zoom < 15 && $zoom > 2) {
322
            $this->zoom = floor($zoom);
323
        }
324
        $this->lon = $centroid->getX();
325
        $this->lat = $centroid->getY();
326
        Logger::debug("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat");
327
    }
328
329
    public function checkMapCache(): bool
330
    {
331
        // side effect: set the mapCacheID
332
        $this->mapCacheID = md5($this->serializeParams());
333
        $filename         = $this->mapCacheIDToFilename();
334
        return file_exists($filename);
335
    }
336
337
    public function serializeParams(): string
338
    {
339
        return join(
340
            "&", array(
341
                   $this->zoom,
342
                   $this->lat,
343
                   $this->lon,
344
                   $this->width,
345
                   $this->height,
346
                   serialize($this->markers),
347
                   $this->maptype,
348
                   $this->kmlFileName,
349
                   $this->gpxFileName,
350
                   $this->geojsonFileName
351
               )
352
        );
353
    }
354
355
    public function mapCacheIDToFilename(): string
356
    {
357
        if (!$this->mapCacheFile) {
358
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_"
0 ignored issues
show
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

358
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . /** @scrutinizer ignore-type */ $this->maptype . "/" . $this->zoom . "/cache_"
Loading history...
359
                . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2)
360
                . "/" . substr($this->mapCacheID, 4);
361
        }
362
        return $this->mapCacheFile . "." . $this->mapCacheExtension;
363
    }
364
365
    /**
366
     * make the map.
367
     */
368
    public function makeMap(): void
369
    {
370
        $this->initCoords();
371
        $this->createBaseMap();
372
        if (!empty ($this->markers)) {
373
            $this->placeMarkers();
374
        }
375
        if (file_exists($this->kmlFileName)) {
376
            try {
377
                $this->drawKML();
378
            } catch (exception $e) {
379
                Logger::error('failed to load KML file', $e);
380
            }
381
        }
382
        if (file_exists($this->gpxFileName)) {
383
            try {
384
                $this->drawGPX();
385
            } catch (exception $e) {
386
                Logger::error('failed to load GPX file', $e);
387
            }
388
        }
389
        if (file_exists($this->geojsonFileName)) {
390
            try {
391
                $this->drawGeojson();
392
            } catch (exception $e) {
393
                Logger::error('failed to load GeoJSON file', $e);
394
            }
395
        }
396
397
        $this->drawCopyright();
398
    }
399
400
    /**
401
     */
402
    public function initCoords(): void
403
    {
404
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
405
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
406
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
407
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
408
    }
409
410
    /**
411
     *
412
     * @param float $long
413
     * @param int   $zoom
414
     * @return float|int
415
     */
416
    public function lonToTile(float $long, int $zoom)
417
    {
418
        return (($long + 180) / 360) * pow(2, $zoom);
419
    }
420
421
    /**
422
     *
423
     * @param float $lat
424
     * @param int   $zoom
425
     * @return float|int
426
     */
427
    public function latToTile(float $lat, int $zoom)
428
    {
429
        return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * pow(2, $zoom);
430
    }
431
432
    /**
433
     * make basemap image.
434
     */
435
    public function createBaseMap(): void
436
    {
437
        $this->image   = imagecreatetruecolor($this->width, $this->height);
438
        $startX        = floor($this->centerX - ($this->width / $this->tileSize) / 2);
439
        $startY        = floor($this->centerY - ($this->height / $this->tileSize) / 2);
440
        $endX          = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
441
        $endY          = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
442
        $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
443
        $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
444
        $this->offsetX += floor($this->width / 2);
445
        $this->offsetY += floor($this->height / 2);
446
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
447
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
448
449
        for ($x = $startX; $x <= $endX; $x++) {
450
            for ($y = $startY; $y <= $endY; $y++) {
451
                $url = str_replace(
452
                    array(
453
                        '{Z}',
454
                        '{X}',
455
                        '{Y}'
456
                    ),
457
                    array(
458
                        $this->zoom,
459
                        $x,
460
                        $y
461
                    ),
462
                    $this->tileInfo [$this->maptype] ['url']
463
                );
464
465
                $tileData = $this->fetchTile($url);
466
                if ($tileData) {
467
                    $tileImage = imagecreatefromstring($tileData);
0 ignored issues
show
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

467
                    $tileImage = imagecreatefromstring(/** @scrutinizer ignore-type */ $tileData);
Loading history...
468
                } else {
469
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
470
                    $color     = imagecolorallocate($tileImage, 255, 255, 255);
471
                    @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

471
                    /** @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...
472
                }
473
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
474
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
475
                Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize);
476
                imagecopy(
477
                    $this->image,
478
                    $tileImage,
479
                    $destX,
0 ignored issues
show
$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

479
                    /** @scrutinizer ignore-type */ $destX,
Loading history...
480
                    $destY,
0 ignored issues
show
$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

480
                    /** @scrutinizer ignore-type */ $destY,
Loading history...
481
                    0,
482
                    0,
483
                    $this->tileSize,
484
                    $this->tileSize
485
                );
486
            }
487
        }
488
    }
489
490
    /**
491
     * Fetch a tile and (if configured) store it in the cache.
492
     * @param string $url
493
     * @return bool|string
494
     * @todo refactor this to use dokuwiki\HTTP\HTTPClient or dokuwiki\HTTP\DokuHTTPClient
495
     *          for better proxy handling...
496
     */
497
    public function fetchTile(string $url)
498
    {
499
        if ($this->useTileCache && ($cached = $this->checkTileCache($url))) {
500
            return $cached;
501
        }
502
503
        $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')';
504
        if (function_exists("curl_init")) {
505
            // use cUrl
506
            $ch = curl_init();
507
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
508
            curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
509
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
510
            curl_setopt($ch, CURLOPT_URL, $url . $this->apikey);
511
            Logger::debug("StaticMap::fetchTile: getting: $url using curl_exec");
512
            $tile = curl_exec($ch);
513
            curl_close($ch);
514
        } else {
515
            // use file_get_contents
516
            global $conf;
517
            $opts = array(
518
                'http' => array(
519
                    'method'          => "GET",
520
                    'header'          => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n",
521
                    'request_fulluri' => true
522
                )
523
            );
524
            if (isset($conf['proxy']['host'], $conf['proxy']['port'])
525
                && $conf['proxy']['host'] !== ''
526
                && $conf['proxy']['port'] !== '') {
527
                $opts['http'] += ['proxy' => "tcp://" . $conf['proxy']['host'] . ":" . $conf['proxy']['port']];
528
            }
529
530
            $context = stream_context_create($opts);
531
            Logger::debug(
532
                "StaticMap::fetchTile: getting: $url . $this->apikey using file_get_contents and options $opts"
533
            );
534
            $tile = file_get_contents($url . $this->apikey, false, $context);
535
        }
536
        if ($tile && $this->useTileCache) {
537
            $this->writeTileToCache($url, $tile);
538
        }
539
        return $tile;
540
    }
541
542
    /**
543
     *
544
     * @param string $url
545
     * @return string|false
546
     */
547
    public function checkTileCache(string $url)
548
    {
549
        $filename = $this->tileUrlToFilename($url);
550
        if (file_exists($filename)) {
551
            return file_get_contents($filename);
552
        }
553
        return false;
554
    }
555
556
    /**
557
     *
558
     * @param string $url
559
     * @return string
560
     */
561
    public function tileUrlToFilename(string $url): string
562
    {
563
        return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1);
564
    }
565
566
    /**
567
     * Write a tile into the cache.
568
     *
569
     * @param string $url
570
     * @param mixed  $data
571
     */
572
    public function writeTileToCache(string $url, $data): void
573
    {
574
        $filename = $this->tileUrlToFilename($url);
575
        $this->mkdirRecursive(dirname($filename), 0777);
576
        file_put_contents($filename, $data);
577
    }
578
579
    /**
580
     * Recursively create the directory.
581
     *
582
     * @param string $pathname
583
     *            The directory path.
584
     * @param int    $mode
585
     *            File access mode. For more information on modes, read the details on the chmod manpage.
586
     */
587
    public function mkdirRecursive(string $pathname, int $mode): bool
588
    {
589
        is_dir(dirname($pathname)) || $this->mkdirRecursive(dirname($pathname), $mode);
590
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
591
    }
592
593
    /**
594
     * Place markers on the map and number them in the same order as they are listed in the html.
595
     */
596
    public function placeMarkers(): void
597
    {
598
        $count               = 0;
599
        $color               = imagecolorallocate($this->image, 0, 0, 0);
600
        $bgcolor             = imagecolorallocate($this->image, 200, 200, 200);
601
        $markerBaseDir       = __DIR__ . '/icons';
602
        $markerImageOffsetX  = 0;
603
        $markerImageOffsetY  = 0;
604
        $markerShadowOffsetX = 0;
605
        $markerShadowOffsetY = 0;
606
        $markerShadowImg     = null;
607
        // loop thru marker array
608
        foreach ($this->markers as $marker) {
609
            // set some local variables
610
            $markerLat  = $marker ['lat'];
611
            $markerLon  = $marker ['lon'];
612
            $markerType = $marker ['type'];
613
            // clear variables from previous loops
614
            $markerFilename = '';
615
            $markerShadow   = '';
616
            $matches        = false;
617
            // check for marker type, get settings from markerPrototypes
618
            if ($markerType) {
619
                foreach ($this->markerPrototypes as $markerPrototype) {
620
                    if (preg_match($markerPrototype ['regex'], $markerType, $matches)) {
0 ignored issues
show
$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

620
                    if (preg_match($markerPrototype ['regex'], $markerType, /** @scrutinizer ignore-type */ $matches)) {
Loading history...
621
                        $markerFilename = $matches [0] . $markerPrototype ['extension'];
622
                        if ($markerPrototype ['offsetImage']) {
623
                            list ($markerImageOffsetX, $markerImageOffsetY) = explode(
624
                                ",",
625
                                $markerPrototype ['offsetImage']
626
                            );
627
                        }
628
                        $markerShadow = $markerPrototype ['shadow'];
629
                        if ($markerShadow) {
630
                            list ($markerShadowOffsetX, $markerShadowOffsetY) = explode(
631
                                ",",
632
                                $markerPrototype ['offsetShadow']
633
                            );
634
                        }
635
                    }
636
                }
637
            }
638
            // create img resource
639
            if (file_exists($markerBaseDir . '/' . $markerFilename)) {
640
                $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename);
641
            } else {
642
                $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png');
643
            }
644
            // check for shadow + create shadow recource
645
            if ($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) {
646
                $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow);
647
            }
648
            // calc position
649
            $destX = floor(
650
                ($this->width / 2) -
651
                $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom))
652
            );
653
            $destY = floor(
654
                ($this->height / 2) -
655
                $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom))
656
            );
657
            // copy shadow on basemap
658
            if ($markerShadow && $markerShadowImg) {
659
                imagecopy(
660
                    $this->image,
661
                    $markerShadowImg,
662
                    $destX + (int)$markerShadowOffsetX,
0 ignored issues
show
$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

662
                    /** @scrutinizer ignore-type */ $destX + (int)$markerShadowOffsetX,
Loading history...
663
                    $destY + (int)$markerShadowOffsetY,
0 ignored issues
show
$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

663
                    /** @scrutinizer ignore-type */ $destY + (int)$markerShadowOffsetY,
Loading history...
664
                    0,
665
                    0,
666
                    imagesx($markerShadowImg),
667
                    imagesy($markerShadowImg)
668
                );
669
            }
670
            // copy marker on basemap above shadow
671
            imagecopy(
672
                $this->image,
673
                $markerImg,
674
                $destX + (int)$markerImageOffsetX,
675
                $destY + (int)$markerImageOffsetY,
676
                0,
677
                0,
678
                imagesx($markerImg),
679
                imagesy($markerImg)
680
            );
681
            // add label
682
            imagestring(
683
                $this->image,
684
                3,
685
                $destX - imagesx($markerImg) + 1,
0 ignored issues
show
$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

685
                /** @scrutinizer ignore-type */ $destX - imagesx($markerImg) + 1,
Loading history...
686
                $destY + (int)$markerImageOffsetY + 1,
0 ignored issues
show
$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

686
                /** @scrutinizer ignore-type */ $destY + (int)$markerImageOffsetY + 1,
Loading history...
687
                ++$count,
688
                $bgcolor
689
            );
690
            imagestring(
691
                $this->image,
692
                3,
693
                $destX - imagesx($markerImg),
694
                $destY + (int)$markerImageOffsetY,
695
                $count,
696
                $color
697
            );
698
        }
699
    }
700
701
    /**
702
     * Draw kml trace on the map.
703
     * @throws exception if loading the specified KML fails
704
     */
705
    public function drawKML(): void
706
    {
707
        // TODO get colour from kml node (not currently supported in geoPHP)
708
        $col     = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127);
0 ignored issues
show
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

708
        $col     = imagecolorallocatealpha($this->image, 255, 0, 0, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
709
        $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
710
        $this->drawGeometry($kmlgeom, $col);
711
    }
712
713
    /**
714
     * Draw geometry or geometry collection on the map.
715
     *
716
     * @param Geometry|GeometryCollection|MultiPolygon|MultiLineString|MultiPoint|Polygon|LineString|Point $geom
0 ignored issues
show
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...
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...
The type MultiLineString 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...
The type MultiPolygon 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...
The type MultiPoint 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...
717
     * @param int                                                                                          $colour
718
     *            drawing colour
719
     */
720
    private function drawGeometry(Geometry $geom, int $colour): void
0 ignored issues
show
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...
721
    {
722
        if (empty($geom)) {
723
            return;
724
        }
725
726
        switch ($geom->geometryType()) {
727
            case 'GeometryCollection' :
728
                // recursively draw part of the collection
729
                for ($i = 1; $i < $geom->numGeometries() + 1; $i++) {
730
                    $_geom = $geom->geometryN($i);
731
                    $this->drawGeometry($_geom, $colour);
732
                }
733
                break;
734
            case 'MultiPolygon' :
735
            case 'MultiLineString' :
736
            case 'MultiPoint' :
737
                // TODO implement / do nothing
738
                break;
739
            case 'Polygon' :
740
                $this->drawPolygon($geom, $colour);
741
                break;
742
            case 'LineString' :
743
                $this->drawLineString($geom, $colour);
744
                break;
745
            case 'Point' :
746
                $this->drawPoint($geom, $colour);
747
                break;
748
            default :
749
                // draw nothing
750
                break;
751
        }
752
    }
753
754
    /**
755
     * Draw a polygon on the map.
756
     *
757
     * @param Polygon $polygon
758
     * @param int     $colour
759
     *            drawing colour
760
     */
761
    private function drawPolygon(Polygon $polygon, int $colour)
762
    {
763
        // TODO implementation of drawing holes,
764
        // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol?
765
766
        // print_r('Polygon:<br />');
767
        // print_r($polygon);
768
        $extPoints = array();
769
        // extring is a linestring actually..
770
        $extRing = $polygon->exteriorRing();
771
772
        for ($i = 1; $i < $extRing->numGeometries(); $i++) {
773
            $p1           = $extRing->geometryN($i);
774
            $x            = floor(
775
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
776
            );
777
            $y            = floor(
778
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
779
            );
780
            $extPoints [] = $x;
781
            $extPoints [] = $y;
782
        }
783
        // print_r('points:('.($i-1).')<br />');
784
        // print_r($extPoints);
785
        // imagepolygon ($this->image, $extPoints, $i-1, $colour );
786
        imagefilledpolygon($this->image, $extPoints, $i - 1, $colour);
787
    }
788
789
    /**
790
     * Draw a line on the map.
791
     *
792
     * @param LineString $line
793
     * @param int        $colour
794
     *            drawing colour
795
     */
796
    private function drawLineString(LineString $line, int $colour)
797
    {
798
        imagesetthickness($this->image, 2);
799
        for ($p = 1; $p < $line->numGeometries(); $p++) {
800
            // get first pair of points
801
            $p1 = $line->geometryN($p);
802
            $p2 = $line->geometryN($p + 1);
803
            // translate to paper space
804
            $x1 = floor(
805
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
806
            );
807
            $y1 = floor(
808
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
809
            );
810
            $x2 = floor(
811
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom))
812
            );
813
            $y2 = floor(
814
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom))
815
            );
816
            // draw to image
817
            imageline($this->image, $x1, $y1, $x2, $y2, $colour);
0 ignored issues
show
$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

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

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

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

817
            imageline($this->image, /** @scrutinizer ignore-type */ $x1, $y1, $x2, $y2, $colour);
Loading history...
818
        }
819
        imagesetthickness($this->image, 1);
820
    }
821
822
    /**
823
     * Draw a point on the map.
824
     *
825
     * @param Point $point
826
     * @param int   $colour
827
     *            drawing colour
828
     */
829
    private function drawPoint(Point $point, int $colour)
830
    {
831
        imagesetthickness($this->image, 2);
832
        // translate to paper space
833
        $cx = floor(
834
            ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom))
835
        );
836
        $cy = floor(
837
            ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom))
838
        );
839
        $r  = 5;
840
        // draw to image
841
        // imageellipse($this->image, $cx, $cy,$r, $r, $colour);
842
        imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour);
0 ignored issues
show
$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

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

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

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

845
        imagearc($this->image, $cx, /** @scrutinizer ignore-type */ $cy, $r, $r, 0, 359, $colour);
Loading history...
846
        imagesetthickness($this->image, 1);
847
    }
848
849
    /**
850
     * Draw gpx trace on the map.
851
     * @throws exception if loading the specified GPX fails
852
     */
853
    public function drawGPX()
854
    {
855
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127);
0 ignored issues
show
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

855
        $col     = imagecolorallocatealpha($this->image, 0, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
856
        $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
857
        $this->drawGeometry($gpxgeom, $col);
858
    }
859
860
    /**
861
     * Draw geojson on the map.
862
     * @throws exception if loading the specified GeoJSON fails
863
     */
864
    public function drawGeojson()
865
    {
866
        $col     = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127);
0 ignored issues
show
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

866
        $col     = imagecolorallocatealpha($this->image, 255, 0, 255, /** @scrutinizer ignore-type */ .4 * 127);
Loading history...
867
        $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json');
868
        $this->drawGeometry($gpxgeom, $col);
869
    }
870
871
    /**
872
     * add copyright and origin notice and icons to the map.
873
     */
874
    public function drawCopyright()
875
    {
876
        $logoBaseDir = dirname(__FILE__) . '/' . 'logo/';
877
        $logoImg     = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']);
878
        $textcolor   = imagecolorallocate($this->image, 0, 0, 0);
879
        $bgcolor     = imagecolorallocate($this->image, 200, 200, 200);
880
881
        imagecopy(
882
            $this->image,
883
            $logoImg,
884
            0,
885
            imagesy($this->image) - imagesy($logoImg),
886
            0,
887
            0,
888
            imagesx($logoImg),
889
            imagesy($logoImg)
890
        );
891
        imagestring(
892
            $this->image,
893
            1,
894
            imagesx($logoImg) + 2,
895
            imagesy($this->image) - imagesy($logoImg) + 1,
896
            $this->tileInfo ['openstreetmap'] ['txt'],
897
            $bgcolor
898
        );
899
        imagestring(
900
            $this->image,
901
            1,
902
            imagesx($logoImg) + 1,
903
            imagesy($this->image) - imagesy($logoImg),
904
            $this->tileInfo ['openstreetmap'] ['txt'],
905
            $textcolor
906
        );
907
908
        // additional tile source info, ie. who created/hosted the tiles
909
        $xIconOffset = 0;
910
        if ($this->maptype === 'openstreetmap') {
911
            $mapAuthor = "(c) OpenStreetMap maps/CC BY-SA";
912
        } else {
913
            $mapAuthor   = $this->tileInfo [$this->maptype] ['txt'];
914
            $iconImg     = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']);
915
            $xIconOffset = imagesx($iconImg);
916
            imagecopy(
917
                $this->image,
918
                $iconImg,
919
                imagesx($logoImg) + 1,
920
                imagesy($this->image) - imagesy($iconImg),
921
                0,
922
                0,
923
                imagesx($iconImg),
924
                imagesy($iconImg)
925
            );
926
        }
927
        imagestring(
928
            $this->image,
929
            1,
930
            imagesx($logoImg) + $xIconOffset + 4,
931
            imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1,
0 ignored issues
show
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

931
            /** @scrutinizer ignore-type */ imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1,
Loading history...
932
            $mapAuthor,
933
            $bgcolor
934
        );
935
        imagestring(
936
            $this->image,
937
            1,
938
            imagesx($logoImg) + $xIconOffset + 3,
939
            imagesy($this->image) - ceil(imagesy($logoImg) / 2),
940
            $mapAuthor,
941
            $textcolor
942
        );
943
    }
944
}
945