GisPolygon   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Test Coverage

Coverage 98.17%

Importance

Changes 0
Metric Value
wmc 24
eloc 94
dl 0
loc 282
ccs 107
cts 109
cp 0.9817
rs 10
c 0
b 0
f 0

11 Methods

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