1<?php
2
3namespace geoPHP\Geometry;
4
5use geoPHP\Exception\UnsupportedMethodException;
6use geoPHP\geoPHP;
7
8/**
9 * Geometry is the root class of the hierarchy. Geometry is an abstract (non-instantiable) class.
10 *
11 * OGC 06-103r4 6.1.2 specification:
12 * The instantiable subclasses of Geometry defined in this Standard are restricted to
13 * 0, 1 and 2-dimensional geometric objects that exist in 2, 3 or 4-dimensional coordinate space.
14 *
15 * Geometry values in R^2 have points with coordinate values for x and y.
16 * Geometry values in R^3 have points with coordinate values for x, y and z or for x, y and m.
17 * Geometry values in R^4 have points with coordinate values for x, y, z and m.
18 * The interpretation of the coordinates is subject to the coordinate reference systems associated to the point.
19 * All coordinates within a geometry object should be in the same coordinate reference systems.
20 * Each coordinate shall be unambiguously associated to a coordinate reference system
21 * either directly or through its containing geometry.
22 *
23 * The z coordinate of a point is typically, but not necessarily, represents altitude or elevation.
24 * The m coordinate represents a measurement.
25 */
26abstract class Geometry
27{
28
29    /**
30     * Type constants
31     */
32    const POINT = 'Point';
33    const LINE_STRING = 'LineString';
34    const POLYGON = 'Polygon';
35    const MULTI_POINT = 'MultiPoint';
36    const MULTI_LINE_STRING = 'MultiLineString';
37    const MULTI_POLYGON = 'MultiPolygon';
38    const GEOMETRY_COLLECTION = 'GeometryCollection';
39
40    const CIRCULAR_STRING = 'CircularString';
41    const COMPOUND_CURVE = 'CompoundCurve';
42    const CURVE_POLYGON = 'CurvePolygon';
43    const MULTI_CURVE = 'MultiCurve'; // Abstract
44    const MULTI_SURFACE = 'MultiSurface'; // Abstract
45    const CURVE = 'Curve'; // Abstract
46    const SURFACE = 'Surface'; // Abstract
47    const POLYHEDRAL_SURFACE = 'PolyhedralSurface';
48    const TIN = 'TIN';
49    const TRIANGLE = 'Triangle';
50
51    /**
52     * @var bool True if Geometry has Z (altitude) value
53     */
54    protected $hasZ = false;
55
56    /**
57     * @var bool True if Geometry has M (measure) value
58     */
59    protected $isMeasured = false;
60
61    /** @var int|null $srid Spatial Reference System Identifier (http://en.wikipedia.org/wiki/SRID) */
62    protected $srid = null;
63
64    /**
65     * @var mixed|null Custom (meta)data
66     */
67    protected $data;
68
69    /**
70     * @var \GEOSGeometry|null|false
71     */
72    private $geos = null;
73
74
75
76
77    /****************************************
78     *  Basic methods on geometric objects  *
79     ****************************************/
80
81    /**
82     * The inherent dimension of the geometric object, which must be less than or equal to the coordinate dimension.
83     * In non-homogeneous collections, this will return the largest topological dimension of the contained objects.
84     *
85     * @return int
86     */
87    abstract public function dimension();
88
89    /**
90     * Returns the name of the instantiable subtype of Geometry of which the geometric object is an instantiable member.
91     *
92     * @return string
93     */
94    abstract public function geometryType();
95
96    /**
97     * Returns true if the geometric object is the empty Geometry.
98     * If true, then the geometric object represents the empty point set ∅ for the coordinate space.
99     *
100     * @return bool
101     */
102    abstract public function isEmpty();
103
104    /**
105     * Returns true if the geometric object has no anomalous geometric points,
106     * such as self intersection or self tangency.
107     * The description of each instantiable geometric class will include the specific conditions
108     * that cause an instance of that class to be classified as not simple
109     *
110     * @return bool
111     */
112    abstract public function isSimple();
113
114    /**
115     * Returns the closure of the combinatorial boundary of the geometric object
116     *
117     * @return Geometry
118     */
119    abstract public function boundary();
120
121    /**
122     * @return Geometry[]
123     */
124    abstract public function getComponents();
125
126
127    /*************************************************
128     *  Methods applicable on certain geometry types *
129     *************************************************/
130
131    abstract public function area();
132
133    /**
134     * @return Point
135     */
136    abstract public function centroid();
137
138    abstract public function length();
139
140    abstract public function length3D();
141
142    /**
143     * @return float|null
144     */
145    abstract public function x();
146
147    /**
148     * @return float|null
149     */
150    abstract public function y();
151
152    /**
153     * @return float|null
154     */
155    abstract public function z();
156
157    /**
158     * @return float|null
159     */
160    abstract public function m();
161
162    /**
163     * @return int|null
164     */
165    abstract public function numGeometries();
166
167    /**
168     * @param int $n One-based index.
169     * @return Geometry|null The geometry, or null if not found.
170     */
171    abstract public function geometryN($n);
172
173    /**
174     * @return Point|null
175     */
176    abstract public function startPoint();
177
178    /**
179     * @return Point|null
180     */
181    abstract public function endPoint();
182
183    abstract public function isRing(); // Missing dependency
184
185    abstract public function isClosed(); // Missing dependency
186
187    abstract public function numPoints();
188
189    /**
190     * @param int $n Nth point
191     * @return Point|null
192     */
193    abstract public function pointN($n);
194
195    abstract public function exteriorRing();
196
197    abstract public function numInteriorRings();
198
199    abstract public function interiorRingN($n);
200
201    abstract public function distance($geom);
202
203    abstract public function equals($geom);
204
205
206    // Abstract: Non-Standard
207    // ----------------------------------------------------------
208
209    abstract public function getBBox();
210
211    abstract public function asArray();
212
213    /**
214     * @return Point[]
215     */
216    abstract public function getPoints();
217
218    abstract public function invertXY();
219
220    /**
221     * Get all line segments
222     * @param bool $toArray return segments as LineString or array of start and end points. Explode(true) is faster
223     *
224     * @return LineString[] | Point[][] | null
225     */
226    abstract public function explode($toArray = false);
227
228    abstract public function greatCircleLength($radius = null); //meters
229
230    abstract public function haversineLength(); //degrees
231
232    abstract public function flatten(); // 3D to 2D
233
234    // Elevations statistics
235
236    abstract public function minimumZ();
237
238    abstract public function maximumZ();
239
240    abstract public function minimumM();
241
242    abstract public function maximumM();
243
244    abstract public function zDifference();
245
246    abstract public function elevationGain($verticalTolerance = 0);
247
248    abstract public function elevationLoss($verticalTolerance = 0);
249
250
251    // Public: Standard -- Common to all geometries
252    // ----------------------------------------------------------
253
254    /**
255     * Check if Geometry has Z (altitude) coordinate
256     *
257     * @return bool True if collection has Z value
258     */
259    public function is3D()
260    {
261        return $this->hasZ;
262    }
263
264    /**
265     * Check if Geometry has a measure value
266     *
267     * @return bool True if collection has measure value
268     */
269    public function isMeasured()
270    {
271        return $this->isMeasured;
272    }
273
274    public function SRID()
275    {
276        return $this->srid;
277    }
278
279    /**
280     * @param int $srid Spatial Reference System Identifier
281     */
282    public function setSRID($srid)
283    {
284        if ($this->getGeos()) {
285            // @codeCoverageIgnoreStart
286            /** @noinspection PhpUndefinedMethodInspection */
287            $this->getGeos()->setSRID((int) $srid);
288            // @codeCoverageIgnoreEnd
289        }
290        $this->srid = $srid;
291    }
292
293    /**
294     * Adds custom data to the geometry
295     *
296     * @param string|array $property The name of the data or an associative array
297     * @param mixed|null $value The data. Can be any type (string, integer, array, etc.)
298     */
299    public function setData($property, $value = null)
300    {
301        if (is_array($property)) {
302            $this->data = $property;
303        } else {
304            $this->data[$property] = $value;
305        }
306    }
307
308    /**
309     * Returns the requested data by property name, or all data of the geometry
310     *
311     * @param string|null $property The name of the data. If omitted, all data will be returned
312     * @return mixed|null The data or null if not exists
313     */
314    public function getData($property = null)
315    {
316        if ($property) {
317            return $this->hasDataProperty($property) ? $this->data[$property] : null;
318        } else {
319            return $this->data;
320        }
321    }
322
323    /**
324     * Tells whether the geometry has data with the specified name
325     * @param string $property The name of the property
326     * @return bool True if the geometry has data with the specified name
327     */
328    public function hasDataProperty($property)
329    {
330        return array_key_exists($property, $this->data ?: []);
331    }
332
333    public function envelope()
334    {
335        if ($this->isEmpty()) {
336            $type = geoPHP::CLASS_NAMESPACE . 'Geometry\\' . $this->geometryType();
337            return new $type();
338        }
339        if ($this->geometryType() === Geometry::POINT) {
340            return $this;
341        }
342
343        if ($this->getGeos()) {
344            // @codeCoverageIgnoreStart
345            /** @noinspection PhpUndefinedMethodInspection */
346            return geoPHP::geosToGeometry($this->getGeos()->envelope());
347            // @codeCoverageIgnoreEnd
348        }
349
350        $boundingBox = $this->getBBox();
351        $points = [
352            new Point($boundingBox['maxx'], $boundingBox['miny']),
353            new Point($boundingBox['maxx'], $boundingBox['maxy']),
354            new Point($boundingBox['minx'], $boundingBox['maxy']),
355            new Point($boundingBox['minx'], $boundingBox['miny']),
356            new Point($boundingBox['maxx'], $boundingBox['miny']),
357        ];
358        return new Polygon([new LineString($points)]);
359    }
360
361    // Public: Non-Standard -- Common to all geometries
362    // ------------------------------------------------
363
364    // $this->out($format, $other_args);
365    public function out()
366    {
367        $args = func_get_args();
368
369        $format = strtolower(array_shift($args));
370        if (strstr($format, 'xdr')) {   //Big Endian WKB
371            $args[] = true;
372            $format = str_replace('xdr', '', $format);
373        }
374
375        $processorType = geoPHP::CLASS_NAMESPACE . 'Adapter\\' . geoPHP::getAdapterMap()[$format];
376        $processor = new $processorType();
377        array_unshift($args, $this);
378
379
380        $result = call_user_func_array([$processor, 'write'], $args);
381
382        return $result;
383    }
384
385    public function coordinateDimension()
386    {
387        return 2 + ($this->hasZ() ? 1 : 0) + ($this->isMeasured() ? 1 : 0);
388    }
389
390    /**
391     * Utility function to check if any line segments intersect
392     * Derived from @source http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
393     * @param Point $segment1Start
394     * @param Point $segment1End
395     * @param Point $segment2Start
396     * @param Point $segment2End
397     * @return bool
398     */
399    public static function segmentIntersects($segment1Start, $segment1End, $segment2Start, $segment2End)
400    {
401        $p0x = $segment1Start->x();
402        $p0y = $segment1Start->y();
403        $p1x = $segment1End->x();
404        $p1y = $segment1End->y();
405        $p2x = $segment2Start->x();
406        $p2y = $segment2Start->y();
407        $p3x = $segment2End->x();
408        $p3y = $segment2End->y();
409
410        $s1x = $p1x - $p0x;
411        $s1y = $p1y - $p0y;
412        $s2x = $p3x - $p2x;
413        $s2y = $p3y - $p2y;
414
415        $fps = (-$s2x * $s1y) + ($s1x * $s2y);
416        $fpt = (-$s2x * $s1y) + ($s1x * $s2y);
417
418        if ($fps == 0 || $fpt == 0) {
419            return false;
420        }
421
422        $s = (-$s1y * ($p0x - $p2x) + $s1x * ($p0y - $p2y)) / $fps;
423        $t = ($s2x * ($p0y - $p2y) - $s2y * ($p0x - $p2x)) / $fpt;
424
425        // Return true if collision is detected
426        return ($s > 0 && $s < 1 && $t > 0 && $t < 1);
427    }
428
429
430    // Public: Aliases
431    // ------------------------------------------------
432
433    public function hasZ()
434    {
435        return $this->is3D();
436    }
437    public function getX()
438    {
439        return $this->x();
440    }
441    public function getY()
442    {
443        return $this->y();
444    }
445    public function getZ()
446    {
447        return $this->z();
448    }
449    public function getM()
450    {
451        return $this->m();
452    }
453    public function getBoundingBox()
454    {
455        return $this->getBBox();
456    }
457    public function dump()
458    {
459        return $this->getComponents();
460    }
461    public function getCentroid()
462    {
463        return $this->centroid();
464    }
465    public function getArea()
466    {
467        return $this->area();
468    }
469    public function geos()
470    {
471        return $this->getGeos();
472    }
473    public function getGeomType()
474    {
475        return $this->geometryType();
476    }
477    public function getSRID()
478    {
479        return $this->SRID();
480    }
481    public function asText()
482    {
483        return $this->out('wkt');
484    }
485    public function asBinary()
486    {
487        return $this->out('wkb');
488    }
489
490    // Public: GEOS Only Functions
491    // ------------------------------------------------
492
493    /**
494     * Returns the GEOS representation of Geometry if GEOS is installed
495     *
496     * @return \GEOSGeometry|false
497     * @codeCoverageIgnore
498     */
499    public function getGeos()
500    {
501        // If it's already been set, just return it
502        if ($this->geos && geoPHP::geosInstalled()) {
503            return $this->geos;
504        }
505        // It hasn't been set yet, generate it
506        if (geoPHP::geosInstalled()) {
507            /** @noinspection PhpUndefinedClassInspection */
508            $reader = new \GEOSWKBReader();
509            /** @noinspection PhpUndefinedMethodInspection */
510            $this->geos = $reader->read($this->out('wkb'));
511        } else {
512            $this->geos = false;
513        }
514        return $this->geos;
515    }
516
517    public function setGeos($geos)
518    {
519        $this->geos = $geos;
520    }
521
522    /**
523     * @return Geometry|Point|null
524     * @throws UnsupportedMethodException
525     * @codeCoverageIgnore
526     */
527    public function pointOnSurface()
528    {
529        if ($this->isEmpty()) {
530            return new Point();
531        }
532        if ($this->getGeos()) {
533            /** @noinspection PhpUndefinedMethodInspection */
534            return geoPHP::geosToGeometry($this->getGeos()->pointOnSurface());
535        }
536        // help for implementation: http://gis.stackexchange.com/questions/76498/how-is-st-pointonsurface-calculated
537        throw UnsupportedMethodException::geos(__METHOD__);
538    }
539
540    /**
541     * @param Geometry $geometry
542     * @return bool
543     * @throws UnsupportedMethodException
544     * @codeCoverageIgnore
545     */
546    public function equalsExact(Geometry $geometry)
547    {
548        if ($this->getGeos()) {
549            /** @noinspection PhpUndefinedMethodInspection */
550            return $this->getGeos()->equalsExact($geometry->getGeos());
551        }
552        throw UnsupportedMethodException::geos(__METHOD__);
553    }
554
555    /**
556     * @param Geometry $geometry
557     * @param string|null $pattern
558     * @return string|null
559     * @throws UnsupportedMethodException
560     * @codeCoverageIgnore
561     */
562    public function relate(Geometry $geometry, $pattern = null)
563    {
564        if ($this->getGeos()) {
565            if ($pattern) {
566                /** @noinspection PhpUndefinedMethodInspection */
567                return $this->getGeos()->relate($geometry->getGeos(), $pattern);
568            } else {
569                /** @noinspection PhpUndefinedMethodInspection */
570                return $this->getGeos()->relate($geometry->getGeos());
571            }
572        }
573        throw UnsupportedMethodException::geos(__METHOD__);
574    }
575
576    /**
577     * @return array
578     * @throws UnsupportedMethodException
579     * @codeCoverageIgnore
580     */
581    public function checkValidity()
582    {
583        if ($this->getGeos()) {
584            /** @noinspection PhpUndefinedMethodInspection */
585            return $this->getGeos()->checkValidity();
586        }
587        throw UnsupportedMethodException::geos(__METHOD__);
588    }
589
590    /**
591     * @param float|int $distance
592     * @return Geometry|null
593     * @throws UnsupportedMethodException
594     * @codeCoverageIgnore
595     */
596    public function buffer($distance)
597    {
598        if ($this->getGeos()) {
599            /** @noinspection PhpUndefinedMethodInspection */
600            return geoPHP::geosToGeometry($this->getGeos()->buffer($distance));
601        }
602        throw UnsupportedMethodException::geos(__METHOD__);
603    }
604
605    /**
606     * @param Geometry $geometry
607     * @return Geometry|null
608     * @throws UnsupportedMethodException
609     * @codeCoverageIgnore
610     */
611    public function intersection(Geometry $geometry)
612    {
613        if ($this->getGeos()) {
614            /** @noinspection PhpUndefinedMethodInspection */
615            return geoPHP::geosToGeometry($this->getGeos()->intersection($geometry->getGeos()));
616        }
617        throw UnsupportedMethodException::geos(__METHOD__);
618    }
619
620    /**
621     * @return Geometry|null
622     * @throws UnsupportedMethodException
623     * @codeCoverageIgnore
624     */
625    public function convexHull()
626    {
627        if ($this->getGeos()) {
628            /** @noinspection PhpUndefinedMethodInspection */
629            return geoPHP::geosToGeometry($this->getGeos()->convexHull());
630        }
631        throw UnsupportedMethodException::geos(__METHOD__);
632    }
633
634    /**
635     * @param Geometry $geometry
636     * @return Geometry|null
637     * @throws UnsupportedMethodException
638     * @codeCoverageIgnore
639     */
640    public function difference(Geometry $geometry)
641    {
642        if ($this->getGeos()) {
643            /** @noinspection PhpUndefinedMethodInspection */
644            return geoPHP::geosToGeometry($this->getGeos()->difference($geometry->getGeos()));
645        }
646        throw UnsupportedMethodException::geos(__METHOD__);
647    }
648
649    /**
650     * @param Geometry $geometry
651     * @return Geometry|null
652     * @throws UnsupportedMethodException
653     * @codeCoverageIgnore
654     */
655    public function symDifference(Geometry $geometry)
656    {
657        if ($this->getGeos()) {
658            /** @noinspection PhpUndefinedMethodInspection */
659            return geoPHP::geosToGeometry($this->getGeos()->symDifference($geometry->getGeos()));
660        }
661        throw UnsupportedMethodException::geos(__METHOD__);
662    }
663
664    /**
665     * Can pass in a geometry or an array of geometries
666     *
667     * @param Geometry $geometry
668     * @return bool|mixed|null|GeometryCollection
669     * @throws UnsupportedMethodException
670     * @codeCoverageIgnore
671     */
672    public function union(Geometry $geometry)
673    {
674        if ($this->getGeos()) {
675            if (is_array($geometry)) {
676                $geom = $this->getGeos();
677                foreach ($geometry as $item) {
678                    /** @noinspection PhpUndefinedMethodInspection */
679                    $geom = $geom->union($item->geos());
680                }
681                return geoPHP::geosToGeometry($geom);
682            } else {
683                /** @noinspection PhpUndefinedMethodInspection */
684                return geoPHP::geosToGeometry($this->getGeos()->union($geometry->getGeos()));
685            }
686        }
687        throw UnsupportedMethodException::geos(__METHOD__);
688    }
689
690    /**
691     * @param float      $tolerance
692     * @param bool|false $preserveTopology
693     * @return Geometry|null
694     * @throws UnsupportedMethodException
695     * @codeCoverageIgnore
696     */
697    public function simplify($tolerance, $preserveTopology = false)
698    {
699        if ($this->getGeos()) {
700            /** @noinspection PhpUndefinedMethodInspection */
701            return geoPHP::geosToGeometry($this->getGeos()->simplify($tolerance, $preserveTopology));
702        }
703        throw UnsupportedMethodException::geos(__METHOD__);
704    }
705
706    /**
707     * @return Geometry|null
708     * @throws UnsupportedMethodException
709     * @codeCoverageIgnore
710     */
711    public function makeValid()
712    {
713        if ($this->getGeos()) {
714            /** @noinspection PhpUndefinedMethodInspection */
715            return geoPHP::geosToGeometry($this->getGeos()->makeValid());
716        }
717        throw UnsupportedMethodException::geos(__METHOD__);
718    }
719
720    /**
721     * @return Geometry|null
722     * @throws UnsupportedMethodException
723     * @codeCoverageIgnore
724     */
725    public function buildArea()
726    {
727        if ($this->getGeos()) {
728            /** @noinspection PhpUndefinedMethodInspection */
729            return geoPHP::geosToGeometry($this->getGeos()->buildArea());
730        }
731        throw UnsupportedMethodException::geos(__METHOD__);
732    }
733
734    /**
735     * @param Geometry $geometry
736     * @return bool
737     * @throws UnsupportedMethodException
738     * @codeCoverageIgnore
739     */
740    public function disjoint(Geometry $geometry)
741    {
742        if ($this->getGeos()) {
743            /** @noinspection PhpUndefinedMethodInspection */
744            return $this->getGeos()->disjoint($geometry->getGeos());
745        }
746        throw UnsupportedMethodException::geos(__METHOD__);
747    }
748
749    /**
750     * @param Geometry $geometry
751     * @return bool
752     * @throws UnsupportedMethodException
753     * @codeCoverageIgnore
754     */
755    public function touches(Geometry $geometry)
756    {
757        if ($this->getGeos()) {
758            /** @noinspection PhpUndefinedMethodInspection */
759            return $this->getGeos()->touches($geometry->getGeos());
760        }
761        throw UnsupportedMethodException::geos(__METHOD__);
762    }
763
764    /**
765     * @param Geometry $geometry
766     * @return bool
767     * @throws UnsupportedMethodException
768     * @codeCoverageIgnore
769     */
770    public function intersects(Geometry $geometry)
771    {
772        if ($this->getGeos()) {
773            /** @noinspection PhpUndefinedMethodInspection */
774            return $this->getGeos()->intersects($geometry->getGeos());
775        }
776        throw UnsupportedMethodException::geos(__METHOD__);
777    }
778
779    /**
780     * @param Geometry $geometry
781     * @return bool
782     * @throws UnsupportedMethodException
783     * @codeCoverageIgnore
784     */
785    public function crosses(Geometry $geometry)
786    {
787        if ($this->getGeos()) {
788            /** @noinspection PhpUndefinedMethodInspection */
789            return $this->getGeos()->crosses($geometry->getGeos());
790        }
791        throw UnsupportedMethodException::geos(__METHOD__);
792    }
793
794    /**
795     * @param Geometry $geometry
796     * @return bool
797     * @throws UnsupportedMethodException
798     * @codeCoverageIgnore
799     */
800    public function within(Geometry $geometry)
801    {
802        if ($this->getGeos()) {
803            /** @noinspection PhpUndefinedMethodInspection */
804            return $this->getGeos()->within($geometry->getGeos());
805        }
806        throw UnsupportedMethodException::geos(__METHOD__);
807    }
808
809    /**
810     * @param Geometry $geometry
811     * @return bool
812     * @throws UnsupportedMethodException
813     * @codeCoverageIgnore
814     */
815    public function contains(Geometry $geometry)
816    {
817        if ($this->getGeos()) {
818            /** @noinspection PhpUndefinedMethodInspection */
819            return $this->getGeos()->contains($geometry->getGeos());
820        }
821        throw UnsupportedMethodException::geos(__METHOD__);
822    }
823
824    /**
825     * @param Geometry $geometry
826     * @return bool
827     * @throws UnsupportedMethodException
828     * @codeCoverageIgnore
829     */
830    public function overlaps(Geometry $geometry)
831    {
832        if ($this->getGeos()) {
833            /** @noinspection PhpUndefinedMethodInspection */
834            return $this->getGeos()->overlaps($geometry->getGeos());
835        }
836        throw UnsupportedMethodException::geos(__METHOD__);
837    }
838
839    /**
840     * @param Geometry $geometry
841     * @return bool
842     * @throws UnsupportedMethodException
843     * @codeCoverageIgnore
844     */
845    public function covers(Geometry $geometry)
846    {
847        if ($this->getGeos()) {
848            /** @noinspection PhpUndefinedMethodInspection */
849            return $this->getGeos()->covers($geometry->getGeos());
850        }
851        throw UnsupportedMethodException::geos(__METHOD__);
852    }
853
854    /**
855     * @param Geometry $geometry
856     * @return bool
857     * @throws UnsupportedMethodException
858     * @codeCoverageIgnore
859     */
860    public function coveredBy(Geometry $geometry)
861    {
862        if ($this->getGeos()) {
863            /** @noinspection PhpUndefinedMethodInspection */
864            return $this->getGeos()->coveredBy($geometry->getGeos());
865        }
866        throw UnsupportedMethodException::geos(__METHOD__);
867    }
868
869    /**
870     * @param Geometry $geometry
871     * @return float
872     * @throws UnsupportedMethodException
873     * @codeCoverageIgnore
874     */
875    public function hausdorffDistance(Geometry $geometry)
876    {
877        if ($this->getGeos()) {
878            /** @noinspection PhpUndefinedMethodInspection */
879            return $this->getGeos()->hausdorffDistance($geometry->getGeos());
880        }
881        throw UnsupportedMethodException::geos(__METHOD__);
882    }
883
884    /**
885     * @param Geometry $point
886     * @param null     $normalized
887     * @return \GEOSGeometry
888     * @throws UnsupportedMethodException
889     * @codeCoverageIgnore
890     */
891    public function project(Geometry $point, $normalized = null)
892    {
893        if ($this->getGeos()) {
894            /** @noinspection PhpUndefinedMethodInspection */
895            return $this->getGeos()->project($point->getGeos(), $normalized);
896        }
897        throw UnsupportedMethodException::geos(__METHOD__);
898    }
899}
900