Point::explode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
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
            // Check to see if this point has Z (height) value
58
            if ($z !== null) {
59
                if (!is_numeric($z)) {
60
                    throw new InvalidGeometryException("Cannot construct Point. z should be numeric");
61
                }
62
                $this->z = floatval($z);
63
            }
64
65
            // Check to see if this is a measure
66
            if ($m !== null) {
67
                if (!is_numeric($m)) {
68
                    throw new InvalidGeometryException("Cannot construct Point. m should be numeric");
69
                }
70
                $this->m = floatval($m);
71
            }
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];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(null, null) returns the type array<integer,null> which is incompatible with the return type mandated by geoPHP\Geometry\Geometry::asArray() of array<integer,array>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
211
        }
212
        if (!$this->hasZ()) {
213
            return !$this->isMeasured() ? [$this->x, $this->y] : [$this->x, $this->y, null, $this->m];
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! $this->isMeasur...his->y, null, $this->m) returns the type array<integer,double|null> which is incompatible with the return type mandated by geoPHP\Geometry\Geometry::asArray() of array<integer,array>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
214
        }
215
        
216
        return !$this->isMeasured() ? [$this->x, $this->y, $this->z] : [$this->x, $this->y, $this->z, $this->m];
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! $this->isMeasur...>y, $this->z, $this->m) returns the type array<integer,double|null> which is incompatible with the return type mandated by geoPHP\Geometry\Geometry::asArray() of array<integer,array>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
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 &&
0 ignored issues
show
Bug introduced by
Are you sure the usage of $geometry->getX() targeting geoPHP\Geometry\Geometry::getX() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
277
                abs(($this->getY() ?? 0) - ($geometry->getY() ?? 0)) <= 1.0E-9 &&
0 ignored issues
show
Bug introduced by
Are you sure the usage of $geometry->getY() targeting geoPHP\Geometry\Geometry::getY() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
278
                abs(($this->getZ() ?? 0) - ($geometry->getZ() ?? 0)) <= 1.0E-9)
0 ignored issues
show
Bug introduced by
Are you sure the usage of $geometry->getZ() targeting geoPHP\Geometry\Geometry::getZ() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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
        // use GEOS if available
320
        $geosObj = $this->getGeos();
321
        if (is_object($geosObj)) {
322
            // @codeCoverageIgnoreStart
323
            /** @noinspection PhpUndefinedMethodInspection */
324
            $geosObj2 = $geometry->getGeos();
325
            return is_object($geosObj2) ? $geosObj->distance($geosObj2) : null;
326
            // @codeCoverageIgnoreEnd
327
        }
328
        
329
        switch ($geometry->geometryType()) {
330
            // Point
331
            case Geometry::POINT:
332
                return sqrt(
333
                    pow(($this->getX() - $geometry->getX()), 2) +
0 ignored issues
show
Bug introduced by
Are you sure the usage of $geometry->getX() targeting geoPHP\Geometry\Geometry::getX() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
334
                    pow(($this->getY() - $geometry->getY()), 2)
0 ignored issues
show
Bug introduced by
Are you sure the usage of $geometry->getY() targeting geoPHP\Geometry\Geometry::getY() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
335
                );
336
            
337
            // MultiPoint, GeometryCollection
338
            case Geometry::MULTI_POINT:
339
            case Geometry::GEOMETRY_COLLECTION:
340
                return $this->distanceToMultiPointOrCollection($geometry);
341
                
342
            // LineString, Polygon, MultiLineString, MultiPolygon.
343
            default:
344
                return $this->distanceToMultiGeometry($geometry);
345
        }
346
    }
347
    
348
    /**
349
     * @param  Geometry|Collection $geometry
350
     * @return float|null
351
     */
352
    private function distanceToMultiPointOrCollection($geometry)
353
    {
354
        $distance = null;
355
        foreach ($geometry->getComponents() as $component) {
356
            $checkDistance = $this->distance($component);
357
            if ($checkDistance === 0.0) {
358
                return 0.0;
359
            }
360
            if ($checkDistance === null) {
361
                continue;
362
            }
363
            $distance = $distance ?? $checkDistance;
364
365
            if ($checkDistance < $distance) {
366
                $distance = $checkDistance;
367
            }
368
        }
369
        
370
        return $distance;
371
    }
372
    
373
    /**
374
     * @param  Geometry $geometry
375
     * @return float|null
376
     */
377
    private function distanceToMultiGeometry(Geometry $geometry)
378
    {
379
        // The nearest point might be a vertex, but it could also be somewhere along a line-segment
380
        // that makes up the geometry (between vertices).
381
        // Here we brute force check all line segments that make up these geometries.
382
        $distance = null;
383
        /** @var Point[] $seg */
384
        foreach ($geometry->explode(true) as $seg) {
385
            // As per http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
386
            // and http://paulbourke.net/geometry/pointline/
387
            $x1 = $seg[0]->getX();
388
            $y1 = $seg[0]->getY();
389
            $x2 = $seg[1]->getX();
390
            $y2 = $seg[1]->getY();
391
            $px = $x2 - $x1;
392
            $py = $y2 - $y1;
393
            $d = ($px * $px) + ($py * $py);
394
            if ($d == 0) {
395
                // Line-segment's endpoints are identical. This is merely a point masquerading a line-segment.
396
                $checkDistance = $this->distance($seg[1]);
397
            } else {
398
                $x3 = $this->getX();
399
                $y3 = $this->getY();
400
                $u = ((($x3 - $x1) * $px) + (($y3 - $y1) * $py)) / $d;
401
                if ($u > 1) {
402
                    $u = 1;
403
                }
404
                if ($u < 0) {
405
                    $u = 0;
406
                }
407
                $x = $x1 + ($u * $px);
408
                $y = $y1 + ($u * $py);
409
                $dx = $x - $x3;
410
                $dy = $y - $y3;
411
                $checkDistance = sqrt(($dx * $dx) + ($dy * $dy));
412
            }
413
            
414
            if ($checkDistance === 0.0) {
415
                return 0.0;
416
            }
417
            if ($checkDistance === null) {
418
                continue;
419
            }
420
            $distance = ($distance ?? $checkDistance);
421
            
422
            if ($checkDistance < $distance) {
423
                $distance = $checkDistance;
424
            }
425
        }
426
        
427
        return $distance;
428
    }
429
430
    /**
431
     * @return float|int|null
432
     */
433
    public function minimumZ()
434
    {
435
        return $this->z;
436
    }
437
438
    /**
439
     * @return float|int|null
440
     */
441
    public function maximumZ()
442
    {
443
        return $this->z;
444
    }
445
446
    /**
447
     * @return float|int|null
448
     */
449
    public function minimumM()
450
    {
451
        return $this->m;
452
    }
453
454
    /**
455
     * @return float|int|null
456
     */
457
    public function maximumM()
458
    {
459
        return $this->m;
460
    }
461
462
    /**
463
     * @return float 0.0
464
     */
465
    public function getArea(): float
466
    {
467
        return 0.0;
468
    }
469
470
    /**
471
     * @return float 0.0
472
     */
473
    public function getLength(): float
474
    {
475
        return 0.0;
476
    }
477
478
    /**
479
     * @return float 0.0
480
     */
481
    public function length3D(): float
482
    {
483
        return 0.0;
484
    }
485
486
    /**
487
     * @param  float|int $radius
488
     * @return float 0.0
489
     */
490
    public function greatCircleLength($radius = geoPHP::EARTH_WGS84_SEMI_MAJOR_AXIS): float
491
    {
492
        return 0.0;
493
    }
494
495
    /**
496
     * @return float 0.0
497
     */
498
    public function haversineLength(): float
499
    {
500
        return 0.0;
501
    }
502
503
    /**
504
     * @return int 1
505
     */
506
    public function numGeometries(): int
507
    {
508
        return $this->isEmpty() ? 0 : 1;
509
    }
510
511
    /**
512
     * @param  int $n
513
     * @return Point|null
514
     */
515
    public function geometryN(int $n)
516
    {
517
        return $n === 1 ? $this : null;
518
    }
519
520
    /**
521
     * @return bool true
522
     */
523
    public function isClosed(): bool
524
    {
525
        return true;
526
    }
527
528
    /**
529
     * Not valid for this geometry type
530
     *
531
     * @param  bool $toArray default false
532
     * @return array{}
533
     */
534
    public function explode(bool $toArray = false): array
535
    {
536
        return [];
537
    }
538
    
539
    /**
540
     * @param int|float $dx
541
     * @param int|float $dy
542
     * @param int|float $dz
543
     * @return void
544
     */
545
    public function translate($dx = 0, $dy = 0, $dz = 0)
546
    {
547
        $this->x += $dx;
548
        $this->y += $dy;
549
        $this->z += $dz;
550
    }
551
}
552