Passed
Pull Request — master (#72)
by
unknown
02:54
created

StaticMap::tileUrlToFilename()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
30
31
/**
32
 *
33
 * @author Mark C. Prins <[email protected]>
34
 * @author Gerhard Koch <gerhard.koch AT ymail.com>
35
 *
36
 */
37
class StaticMap
38
{
39
    // the final output
40
    private $tileSize = 256;
41
    private $tileInfo = [
42
        // OSM sources
43
        'openstreetmap' => ['txt'  => '(c) OpenStreetMap data/ODbl', 'logo' => 'osm_logo.png', 'url'  => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png'],
44
        // OpenTopoMap sources
45
        'opentopomap' => ['txt'  => '(c) OpenStreetMap data/ODbl, SRTM | style: (c) OpenTopoMap', 'logo' => 'osm_logo.png', 'url'  => 'https:/tile.opentopomap.org/{Z}/{X}/{Y}.png'],
46
        // OCM sources
47
        'cycle'         => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png'],
48
        'transport'     => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png'],
49
        'landscape'     => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png'],
50
        'outdoors'      => ['txt'  => '(c) Thunderforest maps', 'logo' => 'tf_logo.png', 'url'  => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png'],
51
        'toner'    => ['txt'  => '(c) Stadia Maps;Stamen Design;OpenStreetMap contributors', 'logo' => 'stamen.png', 'url'  => 'https://tiles-eu.stadiamaps.com/tiles/stamen_toner/{Z}/{X}/{Y}.png'],
52
        'terrain'       => ['txt'  => '(c) Stadia Maps;Stamen Design;OpenStreetMap contributors', 'logo' => 'stamen.png', 'url'  => 'https://tiles-eu.stadiamaps.com/tiles/stamen_terrain/{Z}/{X}/{Y}.png'],
53
    ];
54
    private $tileDefaultSrc = 'openstreetmap';
55
56
    // set up markers
57
    private $markerPrototypes = [
58
        // found at http://www.mapito.net/map-marker-icons.html
59
        // these are 17x19 px with a pointer at the bottom left
60
        'lightblue' => ['regex'        => '/^lightblue(\d+)$/', 'extension'    => '.png', 'shadow'       => false, 'offsetImage'  => '0,-19', 'offsetShadow' => false],
61
        // openlayers std markers are 21x25px with shadow
62
        'ol-marker' => ['regex'        => '/^marker(|-blue|-gold|-green|-red)+$/', 'extension'    => '.png', 'shadow'       => 'marker_shadow.png', 'offsetImage'  => '-10,-25', 'offsetShadow' => '-1,-13'],
63
        // these are 16x16 px
64
        'ww_icon'   => ['regex'        => '/ww_\S+$/', 'extension'    => '.png', 'shadow'       => false, 'offsetImage'  => '-8,-8', 'offsetShadow' => false],
65
        // assume these are 16x16 px
66
        'rest'      => ['regex'        => '/^(?!lightblue(\d+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/', 'extension'    => '.png', 'shadow'       => 'marker_shadow.png', 'offsetImage'  => '-8,-8', 'offsetShadow' => '-1,-1'],
67
    ];
68
    private $centerX;
69
    private $centerY;
70
    private $offsetX;
71
    private $offsetY;
72
    private $image;
73
    private $zoom;
74
    private $lat;
75
    private $lon;
76
    private $width;
77
    private $height;
78
    private $markers;
79
    private $maptype;
80
    private $kmlFileName;
81
    private $gpxFileName;
82
    private $geojsonFileName;
83
    private $autoZoomExtent;
84
    private $apikey;
85
    private $tileCacheBaseDir;
86
    private $mapCacheBaseDir;
87
    private $mediaBaseDir;
88
    private $useTileCache;
89
    private $mapCacheID = '';
90
    private $mapCacheFile = '';
91
    private $mapCacheExtension = 'png';
92
93
    /**
94
     * Constructor.
95
     *
96
     * @param float  $lat
97
     *            Latitude (x) of center of map
98
     * @param float  $lon
99
     *            Longitude (y) of center of map
100
     * @param int    $zoom
101
     *            Zoomlevel
102
     * @param int    $width
103
     *            Width in pixels
104
     * @param int    $height
105
     *            Height in pixels
106
     * @param string $maptype
107
     *            Name of the map
108
     * @param array  $markers
109
     *            array of markers
110
     * @param string $gpx
111
     *            GPX filename
112
     * @param string $kml
113
     *            KML filename
114
     * @param string $geojson
115
     * @param string $mediaDir
116
     *            Directory to store/cache maps
117
     * @param string $tileCacheBaseDir
118
     *            Directory to cache map tiles
119
     * @param bool   $autoZoomExtent
120
     *            Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
121
     * @param string $apikey
122
     */
123
    public function __construct(
124
        float $lat,
125
        float $lon,
126
        int $zoom,
127
        int $width,
128
        int $height,
129
        string $maptype,
130
        array $markers,
131
        string $gpx,
132
        string $kml,
133
        string $geojson,
134
        string $mediaDir,
135
        string $tileCacheBaseDir,
136
        bool $autoZoomExtent = true,
137
        string $apikey = ''
138
    ) {
139
        $this->zoom   = $zoom;
140
        $this->lat    = $lat;
141
        $this->lon    = $lon;
142
        $this->width  = $width;
143
        $this->height = $height;
144
        // validate + set maptype
145
        $this->maptype = $this->tileDefaultSrc;
146
        if (array_key_exists($maptype, $this->tileInfo)) {
147
            $this->maptype = $maptype;
148
        }
149
        $this->markers          = $markers;
150
        $this->kmlFileName      = $kml;
151
        $this->gpxFileName      = $gpx;
152
        $this->geojsonFileName  = $geojson;
153
        $this->mediaBaseDir     = $mediaDir;
154
        $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles';
155
        $this->useTileCache     = $this->tileCacheBaseDir !== '';
156
        $this->mapCacheBaseDir  = $mediaDir . '/olmapmaps';
157
        $this->autoZoomExtent   = $autoZoomExtent;
158
        $this->apikey           = $apikey;
159
    }
160
161
    /**
162
     * get the map, this may return a reference to a cached copy.
163
     *
164
     * @return string url relative to media dir
165
     */
166
    public function getMap(): string
167
    {
168
        try {
169
            if ($this->autoZoomExtent) {
170
                $this->autoZoom();
171
            }
172
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type dokuwiki\plugin\openlayersmap\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
173
            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

173
            /** @scrutinizer ignore-call */ 
174
            dbglog($e);
Loading history...
174
        }
175
176
        // use map cache, so check cache for map
177
        if (!$this->checkMapCache()) {
178
            // map is not in cache, needs to be build
179
            $this->makeMap();
180
            $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777);
181
            imagepng($this->image, $this->mapCacheIDToFilename(), 9);
182
        }
183
        $doc = $this->mapCacheIDToFilename();
184
        // make url relative to media dir
185
        return str_replace($this->mediaBaseDir, '', $doc);
186
    }
187
188
    /**
189
     * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
190
     *
191
     * @param float $paddingFactor
192
     *            buffer constant to enlarge (>1.0) the zoom level
193
     * @throws Exception if non-geometries are found in the collection
194
     */
195
    private function autoZoom(float $paddingFactor = 1.0): void
196
    {
197
        $geoms    = [];
198
        $geoms [] = new Point($this->lon, $this->lat);
199
        if ($this->markers !== []) {
200
            foreach ($this->markers as $marker) {
201
                $geoms [] = new Point($marker ['lon'], $marker ['lat']);
202
            }
203
        }
204
        if (file_exists($this->kmlFileName)) {
205
            $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml');
206
            if ($g !== false) {
207
                $geoms [] = $g;
208
            }
209
        }
210
        if (file_exists($this->gpxFileName)) {
211
            $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx');
212
            if ($g !== false) {
213
                $geoms [] = $g;
214
            }
215
        }
216
        if (file_exists($this->geojsonFileName)) {
217
            $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson');
218
            if ($g !== false) {
219
                $geoms [] = $g;
220
            }
221
        }
222
223
        if (count($geoms) <= 1) {
224
            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

224
            /** @scrutinizer ignore-call */ 
225
            dbglog($geoms, "StaticMap::autoZoom: Skip setting autozoom options");
Loading history...
225
            return;
226
        }
227
228
        $geom     = new GeometryCollection($geoms);
229
        $centroid = $geom->centroid();
230
        $bbox     = $geom->getBBox();
231
232
        // determine vertical resolution, this depends on the distance from the equator
233
        // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
234
        $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360)));
235
        $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360)));
236
        dbglog("StaticMap::autoZoom: vertical resolution: $vy0, $vy1");
237
        if ($vy1 - $vy0 === 0.0) {
238
            $resolutionVertical = 0;
239
            dbglog("StaticMap::autoZoom: using $resolutionVertical");
240
        } else {
241
            $zoomFactorPowered  = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0));
242
            $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
243
        }
244
        // determine horizontal resolution
245
        $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width;
246
        dbglog("StaticMap::autoZoom: using $resolutionHorizontal");
247
        $resolution           = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
248
        $zoom                 = $this->zoom;
249
        if ($resolution > 0) {
250
            $zoom             = log(360 / ($resolution * $this->tileSize), 2);
251
        }
252
253
        if (is_finite($zoom) && $zoom < 15 && $zoom > 2) {
254
            $this->zoom = floor($zoom);
255
        }
256
        $this->lon = $centroid->getX();
257
        $this->lat = $centroid->getY();
258
        dbglog("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat");
259
    }
260
261
    public function checkMapCache(): bool
262
    {
263
        // side effect: set the mapCacheID
264
        $this->mapCacheID = md5($this->serializeParams());
265
        $filename         = $this->mapCacheIDToFilename();
266
        return file_exists($filename);
267
    }
268
269
    public function serializeParams(): string
270
    {
271
        return implode(
272
            "&",
273
            [$this->zoom, $this->lat, $this->lon, $this->width, $this->height, serialize($this->markers), $this->maptype, $this->kmlFileName, $this->gpxFileName, $this->geojsonFileName]
274
        );
275
    }
276
277
    public function mapCacheIDToFilename(): string
278
    {
279
        if (!$this->mapCacheFile) {
280
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_"
0 ignored issues
show
Bug introduced by
Are you sure $this->maptype of type mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

280
            $this->mapCacheFile = $this->mapCacheBaseDir . "/" . /** @scrutinizer ignore-type */ $this->maptype . "/" . $this->zoom . "/cache_"
Loading history...
281
                . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2)
282
                . "/" . substr($this->mapCacheID, 4);
283
        }
284
        return $this->mapCacheFile . "." . $this->mapCacheExtension;
285
    }
286
287
    /**
288
     * make the map.
289
     */
290
    public function makeMap(): void
291
    {
292
        $this->initCoords();
293
        $this->createBaseMap();
294
        if ($this->markers !== []) {
295
            $this->placeMarkers();
296
        }
297
        if (file_exists($this->kmlFileName)) {
298
            try {
299
                $this->drawKML();
300
            } catch (exception $e) {
0 ignored issues
show
Bug introduced by
The type dokuwiki\plugin\openlayersmap\exception was not found. Did you mean exception? If so, make sure to prefix the type with \.
Loading history...
301
                dbglog('failed to load KML file', $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

301
                /** @scrutinizer ignore-call */ 
302
                dbglog('failed to load KML file', $e);
Loading history...
302
            }
303
        }
304
        if (file_exists($this->gpxFileName)) {
305
            try {
306
                $this->drawGPX();
307
            } catch (exception $e) {
308
                dbglog('failed to load GPX file', $e);
309
            }
310
        }
311
        if (file_exists($this->geojsonFileName)) {
312
            try {
313
                $this->drawGeojson();
314
            } catch (exception $e) {
315
                dbglog('failed to load GeoJSON file', $e);
316
            }
317
        }
318
319
        $this->drawCopyright();
320
    }
321
322
    /**
323
     */
324
    public function initCoords(): void
325
    {
326
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
327
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
328
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
329
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
330
    }
331
332
    /**
333
     *
334
     * @param float $long
335
     * @param int   $zoom
336
     * @return float|int
337
     */
338
    public function lonToTile(float $long, int $zoom)
339
    {
340
        return (($long + 180) / 360) * 2 ** $zoom;
341
    }
342
343
    /**
344
     *
345
     * @param float $lat
346
     * @param int   $zoom
347
     * @return float|int
348
     */
349
    public function latToTile(float $lat, int $zoom)
350
    {
351
        return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * 2 ** $zoom;
352
    }
353
354
    /**
355
     * make basemap image.
356
     */
357
    public function createBaseMap(): void
358
    {
359
        $this->image   = imagecreatetruecolor($this->width, $this->height);
360
        $startX        = floor($this->centerX - ($this->width / $this->tileSize) / 2);
361
        $startY        = floor($this->centerY - ($this->height / $this->tileSize) / 2);
362
        $endX          = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
363
        $endY          = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
364
        $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
365
        $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
366
        $this->offsetX += floor($this->width / 2);
367
        $this->offsetY += floor($this->height / 2);
368
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
369
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
370
371
        for ($x = $startX; $x <= $endX; $x++) {
372
            for ($y = $startY; $y <= $endY; $y++) {
373
                $url = str_replace(
374
                    ['{Z}', '{X}', '{Y}'],
375
                    [$this->zoom, $x, $y],
376
                    $this->tileInfo [$this->maptype] ['url']
377
                );
378
379
                $tileData = $this->fetchTile($url);
380
                if ($tileData) {
381
                    $tileImage = imagecreatefromstring($tileData);
0 ignored issues
show
Bug introduced by
It seems like $tileData can also be of type true; however, parameter $image of imagecreatefromstring() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
386
                }
387
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
388
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
389
                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

389
                /** @scrutinizer ignore-call */ 
390
                dbglog($this->tileSize, "imagecopy tile into image: $destX, $destY");
Loading history...
390
                imagecopy(
391
                    $this->image,
392
                    $tileImage,
393
                    $destX,
0 ignored issues
show
Bug introduced by
$destX of type double is incompatible with the type integer expected by parameter $dst_x of imagecopy(). ( Ignorable by Annotation )

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

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

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

394
                    /** @scrutinizer ignore-type */ $destY,
Loading history...
395
                    0,
396
                    0,
397
                    $this->tileSize,
398
                    $this->tileSize
399
                );
400
            }
401
        }
402
    }
403
404
    /**
405
     * Fetch a tile and (if configured) store it in the cache.
406
     * @param string $url
407
     * @return bool|string
408
     * @todo refactor this to use dokuwiki\HTTP\HTTPClient or dokuwiki\HTTP\DokuHTTPClient
409
     *          for better proxy handling...
410
     */
411
    public function fetchTile(string $url)
412
    {
413
        if ($this->useTileCache && ($cached = $this->checkTileCache($url)))
414
            return $cached;
415
416
        $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')';
417
        if (function_exists("curl_init")) {
418
            // use cUrl
419
            $ch = curl_init();
420
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
421
            curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
422
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
423
            curl_setopt($ch, CURLOPT_URL, $url . $this->apikey);
424
            dbglog("StaticMap::fetchTile: getting: $url using curl_exec");
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

748
        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

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

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

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

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

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

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

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

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