Completed
Push — development ( 71acc5...1386d5 )
by Thomas
217:58 queued 213:30
created

StaticMap::copyrightNotice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 36
rs 9.344
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
//error_reporting(E_ALL);
19
//ini_set('display_errors', 'on');
20
21
class StaticMap
22
{
23
    private $maxWidth = 1024;
24
25
    private $maxHeight = 1024;
26
27
    private $tileSize = 256;
28
29
    private $tileSrcUrl = 'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png';
30
31
    private $markerBaseDir = './theme/frontend/images/markers';
32
33
    private $attribution = '(c) OpenStreetMap contributors';
34
35
    private $useTileCache = true;
36
37
    private $tileCacheBaseDir =  __DIR__ . '/var/cache2/staticmap';
38
39
    private $zoom = 0;
40
41
    private $lat = 0;
42
43
    private $lon = 0;
44
45
    private $width = 100;
46
47
    private $height = 100;
48
49
    private $markers = [];
50
51
    private $image;
52
53
    private $centerX;
54
55
    private $centerY;
56
57
    private $offsetX;
58
59
    private $offsetY;
60
61
    private function parseParams()
62
    {
63
        $this->zoom = isset($_GET['zoom']) ? intval($_GET['zoom']) : 0;
64
        if ($this->zoom > 18) {
65
            $this->zoom = 18;
66
        }
67
68
        list($this->lat, $this->lon) = explode(',', $_GET['center']);
69
        $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...
70
        $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...
71
72
        if (isset($_GET['size']) && !empty($_GET['size'])) {
73
            list($this->width, $this->height) = explode('x', $_GET['size']);
74
            $this->width = (int) $this->width;
75
            if ($this->width > $this->maxWidth) {
76
                $this->width = $this->maxWidth;
77
            }
78
            $this->height = (int) $this->height;
79
            if ($this->height > $this->maxHeight) {
80
                $this->height = $this->maxHeight;
81
            }
82
        }
83
        if (isset($_GET['markers']) && !empty($_GET['markers'])) {
84
            foreach (explode('|', $_GET['markers']) as $marker) {
85
                list($markerLat, $markerLon, $markerType) = explode(',', $marker);
86
                $markerLat = floatval($markerLat);
87
                $markerLon = floatval($markerLon);
88
                $markerType = basename($markerType);
89
                $this->markers[] = ['lat' => $markerLat, 'lon' => $markerLon, 'type' => $markerType];
90
            }
91
        }
92
    }
93
94
    private function lonToTile($long, $zoom)
95
    {
96
        return (($long + 180) / 360) * (2 ** $zoom);
97
    }
98
99
    private function latToTile($lat, $zoom)
100
    {
101
        return (1 - log(tan($lat * M_PI / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * (2 ** $zoom);
102
    }
103
104
    private function initCoordinates()
105
    {
106
        $this->centerX = $this->lonToTile($this->lon, $this->zoom);
107
        $this->centerY = $this->latToTile($this->lat, $this->zoom);
108
        $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
109
        $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
110
    }
111
112
    private function createBaseMap()
113
    {
114
        $this->image = imagecreatetruecolor($this->width, $this->height);
115
        $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2);
116
        $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2);
117
        $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
118
        $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
119
        $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...
120
        $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...
121
        $this->offsetX += floor($this->width / 2);
122
        $this->offsetY += floor($this->height / 2);
123
        $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
124
        $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
125
126
        for ($x = $startX; $x <= $endX; $x++) {
127
            for ($y = $startY; $y <= $endY; $y++) {
128
                $url = str_replace(
129
                    ['{Z}', '{X}', '{Y}'],
130
                    [$this->zoom, $x, $y],
131
                    $this->tileSrcUrl
132
                );
133
                $tileData = $this->fetchTile($url);
134
                if ($tileData) {
135
                    $tileImage = imagecreatefromstring($tileData);
136
                } else {
137
                    $tileImage = imagecreate($this->tileSize, $this->tileSize);
138
                    $color = imagecolorallocate($tileImage, 255, 255, 255);
139
                    @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...
140
                }
141
                $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
142
                $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
143
                imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
144
            }
145
        }
146
    }
147
148
    private function placeMarkers()
149
    {
150
        foreach ($this->markers as $marker) {
151
            // set some local variables
152
            $markerLat = $marker['lat'];
153
            $markerLon = $marker['lon'];
154
155
            $markerImageOffsetX = -8;
156
            $markerImageOffsetY = -23;
157
158
            $markerImg = imagecreatefrompng($this->markerBaseDir . '/small-blue.png');
159
160
            // calc position
161
            $destinationX = floor(
162
                ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom))
163
            );
164
            $destinationY = floor(
165
                ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom))
166
            );
167
168
            imagecopy(
169
                $this->image,
170
                $markerImg,
171
                $destinationX + intval($markerImageOffsetX),
172
                $destinationY + intval($markerImageOffsetY),
173
                0,
174
                0,
175
                imagesx($markerImg),
176
                imagesy($markerImg)
177
            );
178
179
        };
180
    }
181
182
    private function tileUrlToFilename($url)
183
    {
184
        return $this->tileCacheBaseDir . '/' . str_replace(['http://'], '', $url);
185
    }
186
187
    private function checkTileCache($url)
188
    {
189
        $filename = $this->tileUrlToFilename($url);
190
        if (file_exists($filename)) {
191
            return file_get_contents($filename);
192
        }
193
    }
194
195
    private function mkdirRecursive($pathname, $mode)
196
    {
197
        is_dir(dirname($pathname)) || $this->mkdirRecursive(dirname($pathname), $mode);
198
199
        return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname);
200
    }
201
202
    private function writeTileToCache($url, $data)
203
    {
204
        $filename = $this->tileUrlToFilename($url);
205
        $this->mkdirRecursive(dirname($filename), 0777);
206
        file_put_contents($filename, $data);
207
    }
208
209
    private function fetchTile($url)
210
    {
211
        if ($this->useTileCache && $cached = $this->checkTileCache($url)) {
212
            return $cached;
213
        }
214
        $opts = [
215
            'http' => [
216
                'method' => 'GET',
217
                'timeout' => 2.0,
218
                'header' => 'User-Agent: https://www.opencaching.de',
219
            ],
220
        ];
221
222
        $context = stream_context_create($opts);
223
        $tile = file_get_contents($url, false, $context);
224
225
226
        if ($tile && $this->useTileCache) {
227
            $this->writeTileToCache($url, $tile);
228
        }
229
230
        return $tile;
231
    }
232
233
    private function copyrightNotice()
234
    {
235
        $string = $this->attribution;
236
        $fontSize = 1;
237
        $len = strlen($string);
238
        $width = imagefontwidth($fontSize) * $len;
239
        $height = imagefontheight($fontSize);
240
        $img = imagecreate($width, $height);
241
242
        imagesavealpha($img, true);
243
        imagealphablending($img, false);
244
        $white = imagecolorallocatealpha($img, 200, 200, 200, 50);
245
        imagefill($img, 0, 0, $white);
246
247
        $color = imagecolorallocate($img, 0, 0, 0);
248
        $ypos = 0;
249
        for ($i = 0; $i < $len; $i++) {
250
            // Position of the character horizontally
251
            $xPosition = $i * imagefontwidth($fontSize);
252
            // Draw character
253
            imagechar($img, $fontSize, $xPosition, $ypos, $string, $color);
254
            // Remove character from string
255
            $string = substr($string, 1);
256
        }
257
258
        imagecopy(
259
            $this->image,
260
            $img,
261
            imagesx($this->image) - imagesx($img),
262
            imagesy($this->image) - imagesy($img),
263
            0,
264
            0,
265
            imagesx($img),
266
            imagesy($img)
267
        );
268
    }
269
270
    private function sendHeader()
271
    {
272
        header('Content-Type: image/png');
273
        $expires = strtotime('+14 days', 0);
274
        header('Cache-Control: private, maxage=' . $expires);
275
        header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');
276
    }
277
278
    private function makeMap()
279
    {
280
        $this->initCoordinates();
281
282
        $this->createBaseMap();
283
        if (count($this->markers)) {
284
            $this->placeMarkers();
285
        }
286
        $this->copyrightNotice();
287
    }
288
289
    public function showMap()
290
    {
291
        $this->parseParams();
292
293
        $this->makeMap();
294
295
        $this->sendHeader();
296
297
        return imagepng($this->image);
298
    }
299
}
300
301
$map = new StaticMap();
302
echo $map->showMap();
303