Completed
Push — development ( 1cbaaa...3e1df6 )
by Mirco
21s
created

StaticMap::writeTileToCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This class is heavily inspired from StaticMap 0.3.1 written by Gerhard Koch
4
 *
5
 * @author Gerhard Koch <gerhard.koch AT ymail.com>
6
 * @link: https://github.com/dfacts/staticmaplite
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
namespace OcLegacy\Map;
19
20
class StaticMap
21
{
22
    private $maxWidth = 1024;
23
24
    private $maxHeight = 1024;
25
26
    private $tileSize = 256;
27
28
    private $tileSrcUrl = 'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png';
29
30
    private $markerBaseDir = './theme/frontend/images/markers';
31
32
    private $attribution = '(c) OpenStreetMap contributors';
33
34
    private $useTileCache = true;
35
36
    private $tileCacheBaseDir =  __DIR__ . '/var/cache2/staticmap';
37
38
    private $zoom = 0;
39
40
    private $lat = 0;
41
42
    private $lon = 0;
43
44
    private $width = 100;
45
46
    private $height = 100;
47
48
    private $markers = [];
49
50
    private $image;
51
52
    private $centerX;
53
54
    private $centerY;
55
56
    private $offsetX;
57
58
    private $offsetY;
59
60
    private function parseParams()
61
    {
62
        $this->zoom = isset($_GET['zoom']) ? intval($_GET['zoom']) : 0;
63
        if ($this->zoom > 18) {
64
            $this->zoom = 18;
65
        }
66
67
        list($this->lat, $this->lon) = explode(',', $_GET['center']);
68
        $this->lat = floatval($this->lat);
0 ignored issues
show
Documentation Bug introduced by
The property $lat was declared of type integer, but floatval($this->lat) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
69
        $this->lon = floatval($this->lon);
0 ignored issues
show
Documentation Bug introduced by
The property $lon was declared of type integer, but floatval($this->lon) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
70
71
        if (isset($_GET['size']) && !empty($_GET['size'])) {
72
            list($this->width, $this->height) = explode('x', $_GET['size']);
73
            $this->width = (int) $this->width;
74
            if ($this->width > $this->maxWidth) {
75
                $this->width = $this->maxWidth;
76
            }
77
            $this->height = (int) $this->height;
78
            if ($this->height > $this->maxHeight) {
79
                $this->height = $this->maxHeight;
80
            }
81
        }
82
        if (isset($_GET['markers']) && !empty($_GET['markers'])) {
83
            foreach (explode('|', $_GET['markers']) as $marker) {
84
                list($markerLat, $markerLon, $markerType) = explode(',', $marker);
85
                $markerLat = floatval($markerLat);
86
                $markerLon = floatval($markerLon);
87
                $markerType = basename($markerType);
88
                $this->markers[] = ['lat' => $markerLat, 'lon' => $markerLon, 'type' => $markerType];
89
            }
90
        }
91
    }
92
93
    private function lonToTile($long, $zoom)
94
    {
95
        return (($long + 180) / 360) * (2 ** $zoom);
96
    }
97
98
    private function latToTile($lat, $zoom)
99
    {
100
        return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * (2 ** $zoom);
101
    }
102
103
    private function initCoordinates()
104
    {
105
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
106
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
107
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
108
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
109
    }
110
111
    private function createBaseMap()
112
    {
113
        $this->image = imagecreatetruecolor($this->width, $this->height);
114
        $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2);
115
        $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2);
116
        $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
117
        $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
118
        $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
0 ignored issues
show
Documentation Bug introduced by
The property $offsetX was declared of type double, but -floor(($this->centerX -...rX)) * $this->tileSize) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
119
        $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
0 ignored issues
show
Documentation Bug introduced by
The property $offsetY was declared of type double, but -floor(($this->centerY -...rY)) * $this->tileSize) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
120
        $this->offsetX += floor($this->width / 2);
121
        $this->offsetY += floor($this->height / 2);
122
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
123
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
124
125
        for ($x = $startX; $x <= $endX; $x++) {
126
            for ($y = $startY; $y <= $endY; $y++) {
127
                $url = str_replace(
128
                    ['{Z}', '{X}', '{Y}'],
129
                    [$this->zoom, $x, $y],
130
                    $this->tileSrcUrl
131
                );
132
                $tileData = $this->fetchTile($url);
133
                if ($tileData) {
134
                    $tileImage = imagecreatefromstring($tileData);
135
                } else {
136
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
137
                    $color = imagecolorallocate($tileImage, 255, 255, 255);
138
                    @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 here. This can introduce security issues, and is generally not recommended.

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...
139
                }
140
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
141
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
142
                imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
143
            }
144
        }
145
    }
146
147
    private function placeMarkers()
148
    {
149
        foreach ($this->markers as $marker) {
150
            // set some local variables
151
            $markerLat = $marker['lat'];
152
            $markerLon = $marker['lon'];
153
154
            $markerImageOffsetX = -8;
155
            $markerImageOffsetY = -23;
156
157
            $markerImg = imagecreatefrompng($this->markerBaseDir . '/small-blue.png');
158
159
            // calc position
160
            $destinationX = floor(
161
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom))
162
            );
163
            $destinationY = floor(
164
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom))
165
            );
166
167
            imagecopy(
168
                $this->image,
169
                $markerImg,
170
                $destinationX + intval($markerImageOffsetX),
171
                $destinationY + intval($markerImageOffsetY),
172
                0,
173
                0,
174
                imagesx($markerImg),
175
                imagesy($markerImg)
176
            );
177
178
        };
179
    }
180
181
    private function tileUrlToFilename($url)
182
    {
183
        return $this->tileCacheBaseDir . '/' . str_replace(['http://'], '', $url);
184
    }
185
186
    private function checkTileCache($url)
187
    {
188
        $filename = $this->tileUrlToFilename($url);
189
        if (file_exists($filename)) {
190
            return file_get_contents($filename);
191
        }
192
    }
193
194
    private function mkdirRecursive($pathname, $mode)
195
    {
196
        is_dir(dirname($pathname)) || $this->mkdirRecursive(dirname($pathname), $mode);
197
198
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
199
    }
200
201
    private function writeTileToCache($url, $data)
202
    {
203
        $filename = $this->tileUrlToFilename($url);
204
        $this->mkdirRecursive(dirname($filename), 0777);
205
        file_put_contents($filename, $data);
206
    }
207
208
    private function fetchTile($url)
209
    {
210
        if ($this->useTileCache && $cached = $this->checkTileCache($url)) {
211
            return $cached;
212
        }
213
        $opts = [
214
            'http' => [
215
                'method' => 'GET',
216
                'timeout' => 2.0,
217
                'header' => 'User-Agent: https://www.opencaching.de',
218
            ],
219
        ];
220
221
        $context = stream_context_create($opts);
222
        $tile = file_get_contents($url, false, $context);
223
224
225
        if ($tile && $this->useTileCache) {
226
            $this->writeTileToCache($url, $tile);
227
        }
228
229
        return $tile;
230
    }
231
232
    private function copyrightNotice()
233
    {
234
        $string = $this->attribution;
235
        $fontSize = 1;
236
        $len = strlen($string);
237
        $width = imagefontwidth($fontSize) * $len;
238
        $height = imagefontheight($fontSize);
239
        $img = imagecreate($width, $height);
240
241
        imagesavealpha($img, true);
242
        imagealphablending($img, false);
243
        $white = imagecolorallocatealpha($img, 200, 200, 200, 50);
244
        imagefill($img, 0, 0, $white);
245
246
        $color = imagecolorallocate($img, 0, 0, 0);
247
        $ypos = 0;
248
        for ($i = 0; $i < $len; $i++) {
249
            // Position of the character horizontally
250
            $xPosition = $i * imagefontwidth($fontSize);
251
            // Draw character
252
            imagechar($img, $fontSize, $xPosition, $ypos, $string, $color);
253
            // Remove character from string
254
            $string = substr($string, 1);
255
        }
256
257
        imagecopy(
258
            $this->image,
259
            $img,
260
            imagesx($this->image) - imagesx($img),
261
            imagesy($this->image) - imagesy($img),
262
            0,
263
            0,
264
            imagesx($img),
265
            imagesy($img)
266
        );
267
    }
268
269
    private function sendHeader()
270
    {
271
        header('Content-Type: image/png');
272
        $expires = strtotime('+14 days', 0);
273
        header('Cache-Control: private, maxage=' . $expires);
274
        header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');
275
    }
276
277
    private function makeMap()
278
    {
279
        $this->initCoordinates();
280
281
        $this->createBaseMap();
282
        if (count($this->markers)) {
283
            $this->placeMarkers();
284
        }
285
        $this->copyrightNotice();
286
    }
287
288
    public function showMap()
289
    {
290
        $this->parseParams();
291
292
        $this->makeMap();
293
294
        $this->sendHeader();
295
296
        return imagepng($this->image);
297
    }
298
}
299