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