Passed
Push — master ( 1e7188...a76082 )
by Mark
02:08
created

StaticMap::writeTileToCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
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
                // TODO implement / do nothing
646
                break;
647
            case 'Polygon':
648
                $this->drawPolygon($geom, $colour);
649
                break;
650
            case 'LineString':
651
                $this->drawLineString($geom, $colour);
652
                break;
653
            case 'Point':
654
                $this->drawPoint($geom, $colour);
655
                break;
656
            default:
657
                // draw nothing
658
                break;
659
        }
660
    }
661
662
    /**
663
     * Draw a polygon on the map.
664
     *
665
     * @param Polygon $polygon
666
     * @param int     $colour
667
     *            drawing colour
668
     */
669
    private function drawPolygon($polygon, int $colour)
670
    {
671
        // TODO implementation of drawing holes,
672
        // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol?
673
674
        // print_r('Polygon:<br />');
675
        // print_r($polygon);
676
        $extPoints = [];
677
        // extring is a linestring actually..
678
        $extRing = $polygon->exteriorRing();
679
680
        for ($i = 1; $i < $extRing->numGeometries(); $i++) {
681
            $p1           = $extRing->geometryN($i);
682
            $x            = floor(
683
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
684
            );
685
            $y            = floor(
686
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
687
            );
688
            $extPoints [] = $x;
689
            $extPoints [] = $y;
690
        }
691
        // print_r('points:('.($i-1).')<br />');
692
        // print_r($extPoints);
693
        // imagepolygon ($this->image, $extPoints, $i-1, $colour );
694
        imagefilledpolygon($this->image, $extPoints, $i - 1, $colour);
695
    }
696
697
    /**
698
     * Draw a line on the map.
699
     *
700
     * @param LineString $line
701
     * @param int        $colour
702
     *            drawing colour
703
     */
704
    private function drawLineString($line, $colour)
705
    {
706
        imagesetthickness($this->image, 2);
707
        for ($p = 1; $p < $line->numGeometries(); $p++) {
708
            // get first pair of points
709
            $p1 = $line->geometryN($p);
710
            $p2 = $line->geometryN($p + 1);
711
            // translate to paper space
712
            $x1 = floor(
713
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom))
714
            );
715
            $y1 = floor(
716
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom))
717
            );
718
            $x2 = floor(
719
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom))
720
            );
721
            $y2 = floor(
722
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom))
723
            );
724
            // draw to image
725
            imageline($this->image, $x1, $y1, $x2, $y2, $colour);
0 ignored issues
show
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

725
            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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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