Test Failed
Push — master ( 168ada...7a89d9 )
by Swen
03:01
created

Point   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 515
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 76
eloc 131
c 3
b 0
f 0
dl 0
loc 515
rs 2.32

38 Methods

Rating   Name   Duplication   Size   Complexity  
A isMeasured() 0 3 1
A minimumZ() 0 3 1
A dimension() 0 3 1
A isValid() 0 3 1
A length3D() 0 3 1
A translate() 0 5 1
A isClosed() 0 3 1
A geometryN() 0 3 2
A haversineLength() 0 3 1
A greatCircleLength() 0 3 1
A hasZ() 0 3 1
A boundary() 0 3 1
A numPoints() 0 3 1
A asArray() 0 10 5
A maximumZ() 0 3 1
A minimumM() 0 3 1
B __construct() 0 28 11
A getY() 0 3 1
A equals() 0 7 4
A flatten() 0 5 1
A getCentroid() 0 3 1
A getPoints() 0 3 1
A getArea() 0 3 1
A numGeometries() 0 3 2
A isSimple() 0 3 1
A invertXY() 0 7 1
D distance() 0 92 19
A getLength() 0 3 1
A fromArray() 0 4 1
A getM() 0 3 1
A explode() 0 3 1
A geometryType() 0 3 1
A maximumM() 0 3 1
A getComponents() 0 3 1
A getZ() 0 3 1
A isEmpty() 0 3 1
A getBBox() 0 11 2
A getX() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Point often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Point, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace geoPHP\Geometry;
3
4
use geoPHP\Exception\InvalidGeometryException;
5
use geoPHP\geoPHP;
6
7
/**
8
 * A Point is a 0-dimensional geometric object and represents a single location in coordinate space.
9
 * A Point has an x-coordinate value, a y-coordinate value.
10
 * If called for by the associated Spatial Reference System, it may also have coordinate values for z and m.
11
 */
12
class Point extends Geometry
13
{
14
15
    /**
16
     * @var float|null
17
     */
18
    public $x = null;
19
20
    /**
21
     * @var float|null
22
     */
23
    public $y = null;
24
25
    /**
26
     * @var float|null
27
     */
28
    public $z = null;
29
30
    /**
31
     * @var float|null
32
     */
33
    public $m = null;
34
35
    /**
36
     * Constructor
37
     *
38
     * @param  mixed $x The x coordinate (or longitude)
39
     * @param  mixed $y The y coordinate (or latitude)
40
     * @param  mixed $z The z coordinate (or altitude) - optional
41
     * @param  mixed $m Measure - optional
42
     * @throws InvalidGeometryException
43
     */
44
    public function __construct($x = null, $y = null, $z = null, $m = null)
45
    {
46
        // If X or Y is null than it is an empty point
47
        if ($x !== null && $y !== null) {
48
            // Basic validation on x and y
49
            if (!is_numeric($x) || !is_numeric($y)) {
50
                throw new InvalidGeometryException("Cannot construct Point. x and y should be numeric");
51
            }
52
53
            // Convert to float in case they are passed in as a string or integer etc.
54
            $this->x = floatval($x);
55
            $this->y = floatval($y);
56
        }
57
58
        // Check to see if this point has Z (height) value
59
        if ($z !== null) {
60
            if (!is_numeric($z)) {
61
                throw new InvalidGeometryException("Cannot construct Point. z should be numeric");
62
            }
63
            $this->z = $this->x !== null ? floatval($z) : null;
64
        }
65
66
        // Check to see if this is a measure
67
        if ($m !== null) {
68
            if (!is_numeric($m)) {
69
                throw new InvalidGeometryException("Cannot construct Point. m should be numeric");
70
            }
71
            $this->m = $this->x !== null ? floatval($m) : null;
72
        }
73
    }
74
75
    /**
76
     * @param  array<mixed> $coordinates
77
     * @return Point
78
     * @throws InvalidGeometryException
79
     */
80
    public static function fromArray(array $coordinates)
81
    {
82
        /** @noinspection PhpIncompatibleReturnTypeInspection */
83
        return (new \ReflectionClass(get_called_class()))->newInstanceArgs($coordinates);
84
    }
85
86
    /**
87
     * @return string "Point"
88
     */
89
    public function geometryType(): string
90
    {
91
        return Geometry::POINT;
92
    }
93
94
    /**
95
     * @return int 0
96
     */
97
    public function dimension(): int
98
    {
99
        return 0;
100
    }
101
102
    /**
103
     * Get X (longitude) coordinate
104
     *
105
     * @return float|null The X coordinate
106
     */
107
    public function getX()
108
    {
109
        return $this->x ?? null;
110
    }
111
112
    /**
113
     * Returns Y (latitude) coordinate
114
     *
115
     * @return float|null The Y coordinate
116
     */
117
    public function getY()
118
    {
119
        return $this->y ?? null;
120
    }
121
122
    /**
123
     * Returns Z (altitude) coordinate
124
     *
125
     * @return float|null The Z coordinate or NULL if it is not a 3D point.
126
     */
127
    public function getZ()
128
    {
129
        return $this->z ?? null;
130
    }
131
132
    /**
133
     * Returns M (measured) value
134
     *
135
     * @return float|null The measured value
136
     */
137
    public function getM()
138
    {
139
        return $this->m ?? null;
140
    }
141
142
    /**
143
     * check if Geometry has a measure value
144
     *
145
     * @return bool true if collection has measure values
146
     */
147
    public function isMeasured(): bool
148
    {
149
        return $this->getM() !== null;
150
    }
151
152
    /**
153
     * check if Geometry has Z (altitude) coordinate
154
     *
155
     * @return bool true if geometry has a Z-value
156
     */
157
    public function hasZ(): bool
158
    {
159
        return $this->getZ() !== null;
160
    }
161
    
162
    /**
163
     * Inverts x and y coordinates
164
     * Useful if old applications still use lng lat
165
     *
166
     * @return self
167
     * */
168
    public function invertXY()
169
    {
170
        $x = $this->x;
171
        $this->x = $this->y;
172
        $this->y = $x;
173
        $this->setGeos(null);
174
        return $this;
175
    }
176
177
    /**
178
     * A point's centroid is itself
179
     *
180
     * @return Point object itself
181
     */
182
    public function getCentroid(): Point
183
    {
184
        return $this;
185
    }
186
187
    /**
188
     * @return array{'minx'?:float|null, 'miny'?:float|null, 'maxx'?:float|null, 'maxy'?:float|null}
189
     */
190
    public function getBBox(): array
191
    {
192
        if ($this->isEmpty()) {
193
            return [];
194
        }
195
        
196
        return [
197
            'maxy' => $this->getY(),
198
            'miny' => $this->getY(),
199
            'maxx' => $this->getX(),
200
            'minx' => $this->getX(),
201
        ];
202
    }
203
204
    /**
205
     * @return array<int, mixed>
206
     */
207
    public function asArray(): array
208
    {
209
        if ($this->isEmpty()) {
210
            return [null, null];
211
        }
212
        if (!$this->hasZ()) {
213
            return !$this->isMeasured() ? [$this->x, $this->y] : [$this->x, $this->y, null, $this->m];
214
        }
215
        
216
        return !$this->isMeasured() ? [$this->x, $this->y, $this->z] : [$this->x, $this->y, $this->z, $this->m];
217
    }
218
219
    /**
220
     * The boundary of a Point is the empty set.
221
     *
222
     * @return GeometryCollection
223
     */
224
    public function boundary(): Geometry
225
    {
226
        return new GeometryCollection();
227
    }
228
229
    /**
230
     * @return bool
231
     */
232
    public function isEmpty(): bool
233
    {
234
        return $this->x === null;
235
    }
236
237
    /**
238
     * @return int Returns always 1
239
     */
240
    public function numPoints(): int
241
    {
242
        return 1;
243
    }
244
245
    /**
246
     * @return Point[]
247
     */
248
    public function getPoints(): array
249
    {
250
        return [$this];
251
    }
252
253
    /**
254
     * @return Point[]
255
     */
256
    public function getComponents(): array
257
    {
258
        return [$this];
259
    }
260
    
261
    /**
262
     * Determines weather the specified geometry is spatially equal to this Point
263
     *
264
     * Because of limited floating point precision in PHP, equality can be only approximated
265
     *
266
     * @see: http://php.net/manual/en/function.bccomp.php
267
     * @see: http://php.net/manual/en/language.types.float.php
268
     *
269
     * @param Point|Geometry $geometry
270
     *
271
     * @return bool
272
     */
273
    public function equals(Geometry $geometry): bool
274
    {
275
        return $geometry->geometryType() === Geometry::POINT
276
            ? ( abs(($this->getX()??0) - ($geometry->getX()??0)) <= 1.0E-9 &&
277
                abs(($this->getY()??0) - ($geometry->getY()??0)) <= 1.0E-9 &&
278
                abs(($this->getZ()??0) - ($geometry->getZ()??0)) <= 1.0E-9)
279
            : false;
280
    }
281
282
    /**
283
     * @return bool always true
284
     */
285
    public function isSimple(): bool
286
    {
287
        return true;
288
    }
289
    
290
    /**
291
     * @return bool
292
     */
293
    public function isValid(): bool
294
    {
295
        return true;
296
    }
297
298
    /**
299
     * resets/empties all values
300
     * @return void
301
     */
302
    public function flatten()
303
    {
304
        unset($this->z);
305
        unset($this->m);
306
        $this->setGeos(null);
307
    }
308
309
    /**
310
     * @param  Geometry|Collection $geometry
311
     * @return float|null
312
     */
313
    public function distance(Geometry $geometry)
314
    {
315
        if ($this->isEmpty() || $geometry->isEmpty()) {
316
            return null;
317
        }
318
        
319
        $geosObj = $this->getGeos();
320
        if (is_object($geosObj)) {
321
            // @codeCoverageIgnoreStart
322
            /** @noinspection PhpUndefinedMethodInspection */
323
            $geosObj2 = $geometry->getGeos();
324
            return is_object($geosObj2) ? $geosObj->distance($geosObj2) : null;
325
            // @codeCoverageIgnoreEnd
326
        }
327
        
328
        if ($geometry->geometryType() === Geometry::POINT) {
329
            return sqrt(
330
                pow(($this->getX() - $geometry->getX()), 2) +
331
                pow(($this->getY() - $geometry->getY()), 2)
332
            );
333
        }
334
        
335
        if ($geometry->geometryType() === Geometry::MULTI_POINT
336
            || $geometry->geometryType() === Geometry::GEOMETRY_COLLECTION
337
        ) {
338
            $distance = null;
339
            foreach ($geometry->getComponents() as $component) {
340
                $checkDistance = $this->distance($component);
341
                if ($checkDistance === 0.0) {
342
                    return 0.0;
343
                }
344
                if ($checkDistance === null) {
345
                    continue;
346
                }
347
                $distance = $distance ?? $checkDistance;
348
                
349
                if ($checkDistance < $distance) {
350
                    $distance = $checkDistance;
351
                }
352
            }
353
            return $distance;
354
        }
355
        
356
        // For LineString, Polygons, MultiLineString and MultiPolygon. The nearest point might be a vertex,
357
        // but it could also be somewhere along a line-segment that makes up the geometry (between vertices).
358
        // Here we brute force check all line segments that make up these geometries.
359
        $distance = null;
360
        /** @var Point[] $seg */
361
        foreach ($geometry->explode(true) as $seg) {
362
            // As per http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
363
            // and http://paulbourke.net/geometry/pointline/
364
            $x1 = $seg[0]->getX();
365
            $y1 = $seg[0]->getY();
366
            $x2 = $seg[1]->getX();
367
            $y2 = $seg[1]->getY();
368
            $px = $x2 - $x1;
369
            $py = $y2 - $y1;
370
            $d = ($px * $px) + ($py * $py);
371
            if ($d == 0) {
372
                // Line-segment's endpoints are identical. This is merely a point masquerading a line-segment.
373
                $checkDistance = $this->distance($seg[1]);
374
            } else {
375
                $x3 = $this->getX();
376
                $y3 = $this->getY();
377
                $u = ((($x3 - $x1) * $px) + (($y3 - $y1) * $py)) / $d;
378
                if ($u > 1) {
379
                    $u = 1;
380
                }
381
                if ($u < 0) {
382
                    $u = 0;
383
                }
384
                $x = $x1 + ($u * $px);
385
                $y = $y1 + ($u * $py);
386
                $dx = $x - $x3;
387
                $dy = $y - $y3;
388
                $checkDistance = sqrt(($dx * $dx) + ($dy * $dy));
389
            }
390
            
391
            if ($checkDistance === 0.0) {
392
                return 0.0;
393
            }
394
            if ($checkDistance === null) {
395
                continue;
396
            }
397
            $distance = ($distance ?? $checkDistance);
398
            
399
            if ($checkDistance < $distance) {
400
                $distance = $checkDistance;
401
            }
402
        }
403
        
404
        return $distance;
405
    }
406
407
    /**
408
     * @return float|int|null
409
     */
410
    public function minimumZ()
411
    {
412
        return $this->z;
413
    }
414
415
    /**
416
     * @return float|int|null
417
     */
418
    public function maximumZ()
419
    {
420
        return $this->z;
421
    }
422
423
    /**
424
     * @return float|int|null
425
     */
426
    public function minimumM()
427
    {
428
        return $this->m;
429
    }
430
431
    /**
432
     * @return float|int|null
433
     */
434
    public function maximumM()
435
    {
436
        return $this->m;
437
    }
438
439
    /**
440
     * @return float 0.0
441
     */
442
    public function getArea(): float
443
    {
444
        return 0.0;
445
    }
446
447
    /**
448
     * @return float 0.0
449
     */
450
    public function getLength(): float
451
    {
452
        return 0.0;
453
    }
454
455
    /**
456
     * @return float 0.0
457
     */
458
    public function length3D(): float
459
    {
460
        return 0.0;
461
    }
462
463
    /**
464
     * @param  float|int $radius
465
     * @return float 0.0
466
     */
467
    public function greatCircleLength($radius = geoPHP::EARTH_WGS84_SEMI_MAJOR_AXIS): float
468
    {
469
        return 0.0;
470
    }
471
472
    /**
473
     * @return float 0.0
474
     */
475
    public function haversineLength(): float
476
    {
477
        return 0.0;
478
    }
479
480
    /**
481
     * @return int 1
482
     */
483
    public function numGeometries(): int
484
    {
485
        return $this->isEmpty() ? 0 : 1;
486
    }
487
488
    /**
489
     * @param  int $n
490
     * @return Point|null
491
     */
492
    public function geometryN(int $n)
493
    {
494
        return $n === 1 ? $this : null;
495
    }
496
497
    /**
498
     * @return bool true
499
     */
500
    public function isClosed(): bool
501
    {
502
        return true;
503
    }
504
505
    /**
506
     * Not valid for this geometry type
507
     *
508
     * @param  bool $toArray default false
509
     * @return array{}
510
     */
511
    public function explode(bool $toArray = false): array
512
    {
513
        return [];
514
    }
515
    
516
    /**
517
     * @param int|float $dx
518
     * @param int|float $dy
519
     * @param int|float $dz
520
     * @return void
521
     */
522
    public function translate($dx = 0, $dy = 0, $dz = 0)
523
    {
524
        $this->x += $dx;
525
        $this->y += $dy;
526
        $this->z += $dz;
527
    }
528
}
529