1<?php
2/**
3 * Handles actions related to GIS POINT objects
4 */
5
6declare(strict_types=1);
7
8namespace PhpMyAdmin\Gis;
9
10use TCPDF;
11use function hexdec;
12use function imagearc;
13use function imagecolorallocate;
14use function imagestring;
15use function json_encode;
16use function mb_strlen;
17use function mb_substr;
18use function round;
19use function trim;
20
21/**
22 * Handles actions related to GIS POINT objects
23 */
24class GisPoint extends GisGeometry
25{
26    /** @var self */
27    private static $instance;
28
29    /**
30     * A private constructor; prevents direct creation of object.
31     *
32     * @access private
33     */
34    private function __construct()
35    {
36    }
37
38    /**
39     * Returns the singleton.
40     *
41     * @return GisPoint the singleton
42     *
43     * @access public
44     */
45    public static function singleton()
46    {
47        if (! isset(self::$instance)) {
48            self::$instance = new GisPoint();
49        }
50
51        return self::$instance;
52    }
53
54    /**
55     * Scales each row.
56     *
57     * @param string $spatial spatial data of a row
58     *
59     * @return array an array containing the min, max values for x and y coordinates
60     *
61     * @access public
62     */
63    public function scaleRow($spatial)
64    {
65        // Trim to remove leading 'POINT(' and trailing ')'
66        $point
67            = mb_substr(
68                $spatial,
69                6,
70                mb_strlen($spatial) - 7
71            );
72
73        return $this->setMinMax($point, []);
74    }
75
76    /**
77     * Adds to the PNG image object, the data related to a row in the GIS dataset.
78     *
79     * @param string      $spatial     GIS POLYGON object
80     * @param string|null $label       Label for the GIS POLYGON object
81     * @param string      $point_color Color for the GIS POLYGON object
82     * @param array       $scale_data  Array containing data related to scaling
83     * @param resource    $image       Image object
84     *
85     * @return resource the modified image object
86     *
87     * @access public
88     */
89    public function prepareRowAsPng(
90        $spatial,
91        ?string $label,
92        $point_color,
93        array $scale_data,
94        $image
95    ) {
96        // allocate colors
97        $black = imagecolorallocate($image, 0, 0, 0);
98        $red = hexdec(mb_substr($point_color, 1, 2));
99        $green = hexdec(mb_substr($point_color, 3, 2));
100        $blue = hexdec(mb_substr($point_color, 4, 2));
101        $color = imagecolorallocate($image, $red, $green, $blue);
102
103        // Trim to remove leading 'POINT(' and trailing ')'
104        $point
105            = mb_substr(
106                $spatial,
107                6,
108                mb_strlen($spatial) - 7
109            );
110        $points_arr = $this->extractPoints($point, $scale_data);
111
112        // draw a small circle to mark the point
113        if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
114            imagearc(
115                $image,
116                (int) round($points_arr[0][0]),
117                (int) round($points_arr[0][1]),
118                7,
119                7,
120                0,
121                360,
122                $color
123            );
124            // print label if applicable
125            if (isset($label) && trim($label) != '') {
126                imagestring(
127                    $image,
128                    1,
129                    (int) round($points_arr[0][0]),
130                    (int) round($points_arr[0][1]),
131                    trim($label),
132                    $black
133                );
134            }
135        }
136
137        return $image;
138    }
139
140    /**
141     * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
142     *
143     * @param string      $spatial     GIS POINT object
144     * @param string|null $label       Label for the GIS POINT object
145     * @param string      $point_color Color for the GIS POINT object
146     * @param array       $scale_data  Array containing data related to scaling
147     * @param TCPDF       $pdf         TCPDF instance
148     *
149     * @return TCPDF the modified TCPDF instance
150     *
151     * @access public
152     */
153    public function prepareRowAsPdf(
154        $spatial,
155        ?string $label,
156        $point_color,
157        array $scale_data,
158        $pdf
159    ) {
160        // allocate colors
161        $red = hexdec(mb_substr($point_color, 1, 2));
162        $green = hexdec(mb_substr($point_color, 3, 2));
163        $blue = hexdec(mb_substr($point_color, 4, 2));
164        $line = [
165            'width' => 1.25,
166            'color' => [
167                $red,
168                $green,
169                $blue,
170            ],
171        ];
172
173        // Trim to remove leading 'POINT(' and trailing ')'
174        $point
175            = mb_substr(
176                $spatial,
177                6,
178                mb_strlen($spatial) - 7
179            );
180        $points_arr = $this->extractPoints($point, $scale_data);
181
182        // draw a small circle to mark the point
183        if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
184            $pdf->Circle(
185                $points_arr[0][0],
186                $points_arr[0][1],
187                2,
188                0,
189                360,
190                'D',
191                $line
192            );
193            // print label if applicable
194            if (isset($label) && trim($label) != '') {
195                $pdf->SetXY($points_arr[0][0], $points_arr[0][1]);
196                $pdf->SetFontSize(5);
197                $pdf->Cell(0, 0, trim($label));
198            }
199        }
200
201        return $pdf;
202    }
203
204    /**
205     * Prepares and returns the code related to a row in the GIS dataset as SVG.
206     *
207     * @param string $spatial     GIS POINT object
208     * @param string $label       Label for the GIS POINT object
209     * @param string $point_color Color for the GIS POINT object
210     * @param array  $scale_data  Array containing data related to scaling
211     *
212     * @return string the code related to a row in the GIS dataset
213     *
214     * @access public
215     */
216    public function prepareRowAsSvg($spatial, $label, $point_color, array $scale_data)
217    {
218        $point_options = [
219            'name'         => $label,
220            'id'           => $label . $this->getRandomId(),
221            'class'        => 'point vector',
222            'fill'         => 'white',
223            'stroke'       => $point_color,
224            'stroke-width' => 2,
225        ];
226
227        // Trim to remove leading 'POINT(' and trailing ')'
228        $point
229            = mb_substr(
230                $spatial,
231                6,
232                mb_strlen($spatial) - 7
233            );
234        $points_arr = $this->extractPoints($point, $scale_data);
235
236        $row = '';
237        if (((float) $points_arr[0][0]) !== 0.0 && ((float) $points_arr[0][1]) !== 0.0) {
238            $row .= '<circle cx="' . $points_arr[0][0]
239                . '" cy="' . $points_arr[0][1] . '" r="3"';
240            foreach ($point_options as $option => $val) {
241                $row .= ' ' . $option . '="' . trim((string) $val) . '"';
242            }
243            $row .= '/>';
244        }
245
246        return $row;
247    }
248
249    /**
250     * Prepares JavaScript related to a row in the GIS dataset
251     * to visualize it with OpenLayers.
252     *
253     * @param string $spatial     GIS POINT object
254     * @param int    $srid        Spatial reference ID
255     * @param string $label       Label for the GIS POINT object
256     * @param array  $point_color Color for the GIS POINT object
257     * @param array  $scale_data  Array containing data related to scaling
258     *
259     * @return string JavaScript related to a row in the GIS dataset
260     *
261     * @access public
262     */
263    public function prepareRowAsOl(
264        $spatial,
265        $srid,
266        $label,
267        $point_color,
268        array $scale_data
269    ) {
270        $fill_style = ['color' => 'white'];
271        $stroke_style = [
272            'color' => $point_color,
273            'width' => 2,
274        ];
275        $result = 'var fill = new ol.style.Fill(' . json_encode($fill_style) . ');'
276            . 'var stroke = new ol.style.Stroke(' . json_encode($stroke_style) . ');'
277            . 'var style = new ol.style.Style({'
278            . 'image: new ol.style.Circle({'
279            . 'fill: fill,'
280            . 'stroke: stroke,'
281            . 'radius: 3'
282            . '}),'
283            . 'fill: fill,'
284            . 'stroke: stroke';
285
286        if (trim($label) !== '') {
287            $text_style = [
288                'text' => trim($label),
289                'offsetY' => -9,
290            ];
291            $result .= ',text: new ol.style.Text(' . json_encode($text_style) . ')';
292        }
293
294        $result .= '});';
295
296        if ($srid == 0) {
297            $srid = 4326;
298        }
299        $result .= $this->getBoundsForOl($srid, $scale_data);
300
301        // Trim to remove leading 'POINT(' and trailing ')'
302        $point
303            = mb_substr(
304                $spatial,
305                6,
306                mb_strlen($spatial) - 7
307            );
308        $points_arr = $this->extractPoints($point, null);
309
310        if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
311            $result .= 'var point = new ol.Feature({geometry: '
312                . $this->getPointForOpenLayers($points_arr[0], $srid) . '});'
313                . 'point.setStyle(style);'
314                . 'vectorLayer.addFeature(point);';
315        }
316
317        return $result;
318    }
319
320    /**
321     * Generate the WKT with the set of parameters passed by the GIS editor.
322     *
323     * @param array  $gis_data GIS data
324     * @param int    $index    Index into the parameter object
325     * @param string $empty    Point does not adhere to this parameter
326     *
327     * @return string WKT with the set of parameters passed by the GIS editor
328     *
329     * @access public
330     */
331    public function generateWkt(array $gis_data, $index, $empty = '')
332    {
333        return 'POINT('
334        . (isset($gis_data[$index]['POINT']['x'])
335            && trim((string) $gis_data[$index]['POINT']['x']) != ''
336            ? $gis_data[$index]['POINT']['x'] : '')
337        . ' '
338        . (isset($gis_data[$index]['POINT']['y'])
339            && trim((string) $gis_data[$index]['POINT']['y']) != ''
340            ? $gis_data[$index]['POINT']['y'] : '') . ')';
341    }
342
343    /**
344     * Generate the WKT for the data from ESRI shape files.
345     *
346     * @param array $row_data GIS data
347     *
348     * @return string the WKT for the data from ESRI shape files
349     *
350     * @access public
351     */
352    public function getShape(array $row_data)
353    {
354        return 'POINT(' . ($row_data['x'] ?? '')
355        . ' ' . ($row_data['y'] ?? '') . ')';
356    }
357
358    /**
359     * Generate parameters for the GIS data editor from the value of the GIS column.
360     *
361     * @param string $value of the GIS column
362     * @param int    $index of the geometry
363     *
364     * @return array params for the GIS data editor from the value of the GIS column
365     *
366     * @access public
367     */
368    public function generateParams($value, $index = -1)
369    {
370        $params = [];
371        if ($index == -1) {
372            $index = 0;
373            $data = GisGeometry::generateParams($value);
374            $params['srid'] = $data['srid'];
375            $wkt = $data['wkt'];
376        } else {
377            $params[$index]['gis_type'] = 'POINT';
378            $wkt = $value;
379        }
380
381        // Trim to remove leading 'POINT(' and trailing ')'
382        $point
383            = mb_substr(
384                $wkt,
385                6,
386                mb_strlen($wkt) - 7
387            );
388        $points_arr = $this->extractPoints($point, null);
389
390        $params[$index]['POINT']['x'] = $points_arr[0][0];
391        $params[$index]['POINT']['y'] = $points_arr[0][1];
392
393        return $params;
394    }
395}
396