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

StaticMap   F

Complexity

Total Complexity 89

Size/Duplication

Total Lines 814
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 403
c 1
b 0
f 0
dl 0
loc 814
rs 2
wmc 89

25 Methods

Rating   Name   Duplication   Size   Complexity  
A mapCacheIDToFilename() 0 8 2
A lonToTile() 0 3 1
A serializeParams() 0 5 1
A getMap() 0 20 4
A checkMapCache() 0 6 1
A latToTile() 0 3 1
A createBaseMap() 0 42 4
F autoZoom() 0 64 15
A __construct() 0 36 2
B makeMap() 0 30 8
A initCoords() 0 6 1
A writeTileToCache() 0 5 1
C placeMarkers() 0 101 12
A drawLineString() 0 24 2
B drawGeometry() 0 30 10
A drawCopyright() 0 68 2
A drawKML() 0 6 1
A checkTileCache() 0 7 2
A drawGPX() 0 5 1
A tileUrlToFilename() 0 3 1
A drawPolygon() 0 26 2
B fetchTile() 0 38 9
A mkdirRecursive() 0 6 4
A drawGeojson() 0 5 1
A drawPoint() 0 18 1

How to fix   Complexity   

Complex Class

Complex classes like StaticMap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StaticMap, and based on these observations, apply Extract Interface, too.

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
            Logger::debug($e);
0 ignored issues
show
Bug introduced by
The type dokuwiki\plugin\openlayersmap\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
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
            Logger::debug("StaticMap::autoZoom: Skip setting autozoom options", $geoms);
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
        Logger::debug("StaticMap::autoZoom: vertical resolution: $vy0, $vy1");
237
        if ($vy1 - $vy0 === 0.0) {
238
            $resolutionVertical = 0;
239
            Logger::debug("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
        Logger::debug("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
        Logger::debug("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
                Logger::error('failed to load KML file', $e);
302
            }
303
        }
304
        if (file_exists($this->gpxFileName)) {
305
            try {
306
                $this->drawGPX();
307
            } catch (exception $e) {
308
                Logger::error('failed to load GPX file', $e);
309
            }
310
        }
311
        if (file_exists($this->geojsonFileName)) {
312
            try {
313
                $this->drawGeojson();
314
            } catch (exception $e) {
315
                Logger::error('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
                Logger::debug("imagecopy tile into image: $destX, $destY", $this->tileSize);
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
            Logger::debug("StaticMap::fetchTile: getting: $url using curl_exec");
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
            Logger::debug(
441
                "StaticMap::fetchTile: getting: $url . $this->apikey using file_get_contents and options $opts"
442
            );
443
            $tile = file_get_contents($url . $this->apikey, false, $context);
444
        }
445
        if ($tile && $this->useTileCache) {
446
            $this->writeTileToCache($url, $tile);
447
        }
448
        return $tile;
449
    }
450
451
    /**
452
     *
453
     * @param string $url
454
     * @return string|false
455
     */
456
    public function checkTileCache(string $url)
457
    {
458
        $filename = $this->tileUrlToFilename($url);
459
        if (file_exists($filename)) {
460
            return file_get_contents($filename);
461
        }
462
        return false;
463
    }
464
465
    /**
466
     *
467
     * @param string $url
468
     */
469
    public function tileUrlToFilename(string $url): string
470
    {
471
        return $this->tileCacheBaseDir . "/" . substr($url, strpos($url, '/') + 1);
472
    }
473
474
    /**
475
     * Write a tile into the cache.
476
     *
477
     * @param string $url
478
     * @param mixed  $data
479
     */
480
    public function writeTileToCache($url, $data): void
481
    {
482
        $filename = $this->tileUrlToFilename($url);
483
        $this->mkdirRecursive(dirname($filename), 0777);
484
        file_put_contents($filename, $data);
485
    }
486
487
    /**
488
     * Recursively create the directory.
489
     *
490
     * @param string $pathname
491
     *            The directory path.
492
     * @param int    $mode
493
     *            File access mode. For more information on modes, read the details on the chmod manpage.
494
     */
495
    public function mkdirRecursive(string $pathname, int $mode): bool
496
    {
497
        if (!is_dir(dirname($pathname))) {
498
            $this->mkdirRecursive(dirname($pathname), $mode);
499
        }
500
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
501
    }
502
503
    /**
504
     * Place markers on the map and number them in the same order as they are listed in the html.
505
     */
506
    public function placeMarkers(): void
507
    {
508
        $count         = 0;
509
        $color         = imagecolorallocate($this->image, 0, 0, 0);
510
        $bgcolor       = imagecolorallocate($this->image, 200, 200, 200);
511
        $markerBaseDir = __DIR__ . '/icons';
512
        $markerImageOffsetX  = 0;
513
        $markerImageOffsetY  = 0;
514
        $markerShadowOffsetX = 0;
515
        $markerShadowOffsetY = 0;
516
        $markerShadowImg     = null;
517
        // loop thru marker array
518
        foreach ($this->markers as $marker) {
519
            // set some local variables
520
            $markerLat  = $marker ['lat'];
521
            $markerLon  = $marker ['lon'];
522
            $markerType = $marker ['type'];
523
            // clear variables from previous loops
524
            $markerFilename = '';
525
            $markerShadow   = '';
526
            $matches        = false;
527
            // check for marker type, get settings from markerPrototypes
528
            if ($markerType) {
529
                foreach ($this->markerPrototypes as $markerPrototype) {
530
                    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

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

572
                    /** @scrutinizer ignore-type */ $destX + (int) $markerShadowOffsetX,
Loading history...
573
                    $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

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

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

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

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

726
            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

726
            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

726
            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

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

751
        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

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

754
        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

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

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

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

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