GisPolygon   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Test Coverage

Coverage 98.23%

Importance

Changes 0
Metric Value
wmc 24
eloc 98
dl 0
loc 288
ccs 111
cts 113
cp 0.9823
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareRowAsPng() 0 34 3
A __construct() 0 2 1
A getExtent() 0 7 1
A getCoordinateParams() 0 18 3
A generateWkt() 0 19 3
A getType() 0 3 1
A prepareRowAsPdf() 0 29 3
A prepareRowAsOl() 0 24 2
A singleton() 0 7 2
A prepareRowAsSvg() 0 31 3
A drawPath() 0 13 2
1
<?php
2
/**
3
 * Handles actions related to GIS POLYGON objects
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\Gis;
9
10
use PhpMyAdmin\Gis\Ds\Extent;
11
use PhpMyAdmin\Gis\Ds\ScaleData;
12
use PhpMyAdmin\Image\ImageWrapper;
13
use TCPDF;
14
15
use function array_merge;
16
use function array_slice;
17
use function count;
18
use function explode;
19
use function implode;
20
use function json_encode;
21
use function max;
22
use function mb_substr;
23
use function round;
24
use function sprintf;
25
26
/**
27
 * Handles actions related to GIS POLYGON objects
28
 */
29
class GisPolygon extends GisGeometry
30
{
31
    private static self $instance;
32
33
    /**
34
     * A private constructor; prevents direct creation of object.
35
     */
36 52
    private function __construct()
37
    {
38 52
    }
39
40
    /**
41
     * Returns the singleton.
42
     *
43
     * @return GisPolygon the singleton
44
     */
45 52
    public static function singleton(): GisPolygon
46
    {
47 52
        if (! isset(self::$instance)) {
48 52
            self::$instance = new GisPolygon();
49
        }
50
51 52
        return self::$instance;
52
    }
53
54
    /**
55
     * Get coordinate extent for this wkt.
56
     *
57
     * @param string $wkt Well Known Text represenatation of the geometry
58
     *
59
     * @return Extent the min, max values for x and y coordinates
60
     */
61 8
    public function getExtent(string $wkt): Extent
62
    {
63
        // Trim to remove leading 'POLYGON((' and trailing '))'
64 8
        $polygon = mb_substr($wkt, 9, -2);
65 8
        $wktOuterRing = explode('),(', $polygon)[0];
66
67 8
        return $this->getCoordinatesExtent($wktOuterRing);
68
    }
69
70
    /**
71
     * Adds to the PNG image object, the data related to a row in the GIS dataset.
72
     *
73
     * @param string    $spatial   GIS POLYGON object
74
     * @param string    $label     Label for the GIS POLYGON object
75
     * @param int[]     $color     Color for the GIS POLYGON object
76
     * @param ScaleData $scaleData Array containing data related to scaling
77
     */
78 4
    public function prepareRowAsPng(
79
        string $spatial,
80
        string $label,
81
        array $color,
82
        ScaleData $scaleData,
83
        ImageWrapper $image,
84
    ): void {
85
        // allocate colors
86 4
        $black = $image->colorAllocate(0, 0, 0);
87 4
        $fillColor = $image->colorAllocate(...$color);
88
89
        // Trim to remove leading 'POLYGON((' and trailing '))'
90 4
        $polygon = mb_substr($spatial, 9, -2);
91
92 4
        $pointsArr = [];
93 4
        $wktRings = explode('),(', $polygon);
94 4
        foreach ($wktRings as $wktRing) {
95 4
            $ring = $this->extractPoints1dLinear($wktRing, $scaleData);
96 4
            $pointsArr = array_merge($pointsArr, $ring);
97
        }
98
99
        // draw polygon
100 4
        $image->filledPolygon($pointsArr, $fillColor);
101 4
        if ($label === '') {
102
            return;
103
        }
104
105
        // print label if applicable
106 4
        $image->string(
107 4
            1,
108 4
            (int) round($pointsArr[2]),
109 4
            (int) round($pointsArr[3]),
110 4
            $label,
111 4
            $black,
112 4
        );
113
    }
114
115
    /**
116
     * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
117
     *
118
     * @param string    $spatial   GIS POLYGON object
119
     * @param string    $label     Label for the GIS POLYGON object
120
     * @param int[]     $color     Color for the GIS POLYGON object
121
     * @param ScaleData $scaleData Array containing data related to scaling
122
     */
123 4
    public function prepareRowAsPdf(
124
        string $spatial,
125
        string $label,
126
        array $color,
127
        ScaleData $scaleData,
128
        TCPDF $pdf,
129
    ): void {
130
        // Trim to remove leading 'POLYGON((' and trailing '))'
131 4
        $polygon = mb_substr($spatial, 9, -2);
132
133 4
        $wktRings = explode('),(', $polygon);
134
135 4
        $pointsArr = [];
136
137 4
        foreach ($wktRings as $wktRing) {
138 4
            $ring = $this->extractPoints1dLinear($wktRing, $scaleData);
139 4
            $pointsArr = array_merge($pointsArr, $ring);
140
        }
141
142
        // draw polygon
143 4
        $pdf->Polygon($pointsArr, 'F*', [], $color);
144 4
        if ($label === '') {
145
            return;
146
        }
147
148
        // print label if applicable
149 4
        $pdf->setXY($pointsArr[2], $pointsArr[3]);
150 4
        $pdf->setFontSize(5);
151 4
        $pdf->Cell(0, 0, $label);
152
    }
153
154
    /**
155
     * Prepares and returns the code related to a row in the GIS dataset as SVG.
156
     *
157
     * @param string    $spatial   GIS POLYGON object
158
     * @param string    $label     Label for the GIS POLYGON object
159
     * @param int[]     $color     Color for the GIS POLYGON object
160
     * @param ScaleData $scaleData Array containing data related to scaling
161
     *
162
     * @return string the code related to a row in the GIS dataset
163
     */
164 4
    public function prepareRowAsSvg(string $spatial, string $label, array $color, ScaleData $scaleData): string
165
    {
166 4
        $polygonOptions = [
167 4
            'name' => $label,
168 4
            'id' => $label . $this->getRandomId(),
169 4
            'class' => 'polygon vector',
170 4
            'stroke' => 'black',
171 4
            'stroke-width' => 0.5,
172 4
            'fill' => sprintf('#%02x%02x%02x', ...$color),
173 4
            'fill-rule' => 'evenodd',
174 4
            'fill-opacity' => 0.8,
175 4
        ];
176
177
        // Trim to remove leading 'POLYGON((' and trailing '))'
178 4
        $polygon = mb_substr($spatial, 9, -2);
179
180 4
        $row = '<path d="';
181
182 4
        $wktRings = explode('),(', $polygon);
183 4
        foreach ($wktRings as $wktRing) {
184 4
            $row .= $this->drawPath($wktRing, $scaleData);
185
        }
186
187 4
        $row .= '"';
188 4
        foreach ($polygonOptions as $option => $val) {
189 4
            $row .= ' ' . $option . '="' . $val . '"';
190
        }
191
192 4
        $row .= '/>';
193
194 4
        return $row;
195
    }
196
197
    /**
198
     * Prepares JavaScript related to a row in the GIS dataset
199
     * to visualize it with OpenLayers.
200
     *
201
     * @param string $spatial GIS POLYGON object
202
     * @param int    $srid    Spatial reference ID
203
     * @param string $label   Label for the GIS POLYGON object
204
     * @param int[]  $color   Color for the GIS POLYGON object
205
     *
206
     * @return string JavaScript related to a row in the GIS dataset
207
     */
208 4
    public function prepareRowAsOl(string $spatial, int $srid, string $label, array $color): string
209
    {
210 4
        $color[] = 0.8;
211 4
        $fillStyle = ['color' => $color];
212 4
        $strokeStyle = ['color' => [0, 0, 0], 'width' => 0.5];
213 4
        $style = 'new ol.style.Style({'
214 4
            . 'fill: new ol.style.Fill(' . json_encode($fillStyle) . '),'
215 4
            . 'stroke: new ol.style.Stroke(' . json_encode($strokeStyle) . ')';
216 4
        if ($label !== '') {
217 4
            $textStyle = ['text' => $label];
218 4
            $style .= ',text: new ol.style.Text(' . json_encode($textStyle) . ')';
219
        }
220
221 4
        $style .= '})';
222
223
        // Trim to remove leading 'POLYGON((' and trailing '))'
224 4
        $wktCoordinates = mb_substr($spatial, 9, -2);
225 4
        $olGeometry = $this->toOpenLayersObject(
226 4
            'ol.geom.Polygon',
227 4
            $this->extractPoints2d($wktCoordinates, null),
228 4
            $srid,
229 4
        );
230
231 4
        return $this->addGeometryToLayer($olGeometry, $style);
232
    }
233
234
    /**
235
     * Draws a ring of the polygon using SVG path element.
236
     *
237
     * @param string    $polygon   The ring
238
     * @param ScaleData $scaleData Array containing data related to scaling
239
     *
240
     * @return string the code to draw the ring
241
     */
242 4
    private function drawPath(string $polygon, ScaleData $scaleData): string
243
    {
244 4
        $pointsArr = $this->extractPoints1d($polygon, $scaleData);
245
246 4
        $row = ' M ' . $pointsArr[0][0] . ', ' . $pointsArr[0][1];
247 4
        $otherPoints = array_slice($pointsArr, 1, count($pointsArr) - 2);
248 4
        foreach ($otherPoints as $point) {
249 4
            $row .= ' L ' . $point[0] . ', ' . $point[1];
250
        }
251
252 4
        $row .= ' Z ';
253
254 4
        return $row;
255
    }
256
257
    /**
258
     * Generate the WKT with the set of parameters passed by the GIS editor.
259
     *
260
     * @param mixed[] $gisData GIS data
261
     * @param int     $index   Index into the parameter object
262
     * @param string  $empty   Value for empty points
263
     *
264
     * @return string WKT with the set of parameters passed by the GIS editor
265
     */
266 24
    public function generateWkt(array $gisData, int $index, string $empty = ''): string
267
    {
268 24
        $dataRow = $gisData[$index]['POLYGON'] ?? null;
269 24
        $noOfLines = max(1, $dataRow['data_length'] ?? 0);
270
271 24
        $wktRings = [];
272
        /** @infection-ignore-all */
273 24
        for ($i = 0; $i < $noOfLines; $i++) {
274 24
            $noOfPoints = max(4, $dataRow[$i]['data_length'] ?? 0);
275
276 24
            $wktPoints = [];
277 24
            for ($j = 0; $j < $noOfPoints; $j++) {
278 24
                $wktPoints[] = $this->getWktCoord($dataRow[$i][$j] ?? null, $empty);
279
            }
280
281 24
            $wktRings[] = '(' . implode(',', $wktPoints) . ')';
282
        }
283
284 24
        return 'POLYGON(' . implode(',', $wktRings) . ')';
285
    }
286
287
    /**
288
     * Generate coordinate parameters for the GIS data editor from the value of the GIS column.
289
     *
290
     * @param string $wkt Value of the GIS column
291
     *
292
     * @return mixed[] Coordinate params for the GIS data editor from the value of the GIS column
293
     */
294 4
    protected function getCoordinateParams(string $wkt): array
295
    {
296
        // Trim to remove leading 'POLYGON((' and trailing '))'
297 4
        $wktPolygon = mb_substr($wkt, 9, -2);
298 4
        $wktRings = explode('),(', $wktPolygon);
299 4
        $coords = ['data_length' => count($wktRings)];
300
301 4
        foreach ($wktRings as $j => $wktRing) {
302 4
            $points = $this->extractPoints1d($wktRing, null);
303 4
            $noOfPoints = count($points);
304 4
            $coords[$j] = ['data_length' => $noOfPoints];
305
            /** @infection-ignore-all */
306 4
            for ($i = 0; $i < $noOfPoints; $i++) {
307 4
                $coords[$j][$i] = ['x' => $points[$i][0], 'y' => $points[$i][1]];
308
            }
309
        }
310
311 4
        return $coords;
312
    }
313
314 4
    protected function getType(): string
315
    {
316 4
        return 'POLYGON';
317
    }
318
}
319