1"""
2Computational geometry code for PySAL: Python Spatial Analysis Library.
3
4"""
5
6__author__ = "Sergio J. Rey, Xinyue Ye, Charles Schmidt, Andrew Winslow, Hu Shao"
7
8import math
9from .sphere import arcdist
10
11from typing import Union
12
13__all__ = [
14    "Point",
15    "LineSegment",
16    "Line",
17    "Ray",
18    "Chain",
19    "Polygon",
20    "Rectangle",
21    "asShape",
22]
23
24
25def asShape(obj):
26    """Returns a PySAL shape object from ``obj``, which
27    must support the ``__geo_interface__``.
28
29    Parameters
30    ----------
31    obj : {libpysal.cg.{Point, LineSegment, Line, Ray, Chain, Polygon}
32        A geometric representation of an object.
33
34    Raises
35    ------
36    TypeError
37        Raised when ``obj`` is not a supported shape.
38    NotImplementedError
39        Raised when ``geo_type`` is not a supported type.
40
41    Returns
42    -------
43    obj : {libpysal.cg.{Point, LineSegment, Line, Ray, Chain, Polygon}
44        A new geometric representation of the object.
45
46    """
47
48    if isinstance(obj, (Point, LineSegment, Line, Ray, Chain, Polygon)):
49        pass
50    else:
51        if hasattr(obj, "__geo_interface__"):
52            geo = obj.__geo_interface__
53        else:
54            geo = obj
55
56        if hasattr(geo, "type"):
57            raise TypeError("%r does not appear to be a shape object." % (obj))
58
59        geo_type = geo["type"].lower()
60
61        # if geo_type.startswith('multi'):
62        #    raise NotImplementedError, "%s are not supported at this time."%geo_type
63
64        if geo_type in _geoJSON_type_to_Pysal_type:
65
66            obj = _geoJSON_type_to_Pysal_type[geo_type].__from_geo_interface__(geo)
67        else:
68            raise NotImplementedError("%s is not supported at this time." % geo_type)
69
70    return obj
71
72
73class Geometry(object):
74    """A base class to help implement ``is_geometry``
75    and make geometric types extendable.
76
77    """
78
79    def __init__(self):
80        pass
81
82
83class Point(Geometry):
84    """Geometric class for point objects.
85
86    Parameters
87    ----------
88    loc : tuple
89        The point's location (number :math:`x`-tuple, :math:`x` > 1).
90
91    Examples
92    --------
93
94    >>> p = Point((1, 3))
95
96    """
97
98    def __init__(self, loc):
99
100        self.__loc = tuple(map(float, loc))
101
102    @classmethod
103    def __from_geo_interface__(cls, geo):
104        return cls(geo["coordinates"])
105
106    @property
107    def __geo_interface__(self):
108        return {"type": "Point", "coordinates": self.__loc}
109
110    def __lt__(self, other) -> bool:
111        """Tests if the point is less than another object.
112
113        Parameters
114        ----------
115        other : libpysal.cg.Point
116            An object to test equality against.
117
118        Examples
119        --------
120
121        >>> Point((0, 1)) < Point((0, 1))
122        False
123
124        >>> Point((0, 1)) < Point((1, 1))
125        True
126
127        """
128
129        return (self.__loc) < (other.__loc)
130
131    def __le__(self, other) -> bool:
132        """Tests if the point is less than or equal to another object.
133
134        Parameters
135        ----------
136        other : libpysal.cg.Point
137            An object to test equality against.
138
139        Examples
140        --------
141
142        >>> Point((0, 1)) <= Point((0, 1))
143        True
144
145        >>> Point((0, 1)) <= Point((1, 1))
146        True
147
148        """
149
150        return (self.__loc) <= (other.__loc)
151
152    def __eq__(self, other) -> bool:
153        """Tests if the point is equal to another object.
154
155        Parameters
156        ----------
157        other : libpysal.cg.Point
158            An object to test equality against.
159
160        Examples
161        --------
162
163        >>> Point((0, 1)) == Point((0, 1))
164        True
165
166        >>> Point((0, 1)) == Point((1, 1))
167        False
168
169        """
170
171        try:
172            return (self.__loc) == (other.__loc)
173        except AttributeError:
174            return False
175
176    def __ne__(self, other) -> bool:
177        """Tests if the point is not equal to another object.
178
179        Parameters
180        ----------
181        other : libpysal.cg.Point
182            An object to test equality against.
183
184        Examples
185        --------
186
187        >>> Point((0, 1)) != Point((0, 1))
188        False
189
190        >>> Point((0, 1)) != Point((1, 1))
191        True
192
193        """
194
195        try:
196            return (self.__loc) != (other.__loc)
197        except AttributeError:
198            return True
199
200    def __gt__(self, other) -> bool:
201        """Tests if the point is greater than another object.
202
203        Parameters
204        ----------
205        other : libpysal.cg.Point
206            An object to test equality against.
207
208        Examples
209        --------
210
211        >>> Point((0, 1)) > Point((0, 1))
212        False
213
214        >>> Point((0, 1)) > Point((1, 1))
215        False
216
217        """
218
219        return (self.__loc) > (other.__loc)
220
221    def __ge__(self, other) -> bool:
222        """Tests if the point is greater than or equal to another object.
223
224        Parameters
225        ----------
226        other : libpysal.cg.Point
227            An object to test equality against.
228
229        Examples
230        --------
231
232        >>> Point((0, 1)) >= Point((0, 1))
233        True
234
235        >>> Point((0, 1)) >= Point((1, 1))
236        False
237
238        """
239
240        return (self.__loc) >= (other.__loc)
241
242    def __hash__(self) -> int:
243        """Returns the hash of the point's location.
244
245        Examples
246        --------
247
248        >>> hash(Point((0, 1))) == hash(Point((0, 1)))
249        True
250
251        >>> hash(Point((0, 1))) == hash(Point((1, 1)))
252        False
253
254        """
255
256        return hash(self.__loc)
257
258    def __getitem__(self, *args) -> Union[int, float]:
259        """Return the coordinate for the given dimension.
260
261        Parameters
262        ----------
263        *args : tuple
264            A singleton tuple of :math:`(i)` with :math:`i`
265            as the index of the desired dimension.
266
267        Examples
268        --------
269
270        >>> p = Point((5.5, 4.3))
271        >>> p[0] == 5.5
272        True
273        >>> p[1] == 4.3
274        True
275
276        """
277
278        return self.__loc.__getitem__(*args)
279
280    def __getslice__(self, *args) -> slice:
281        """Return the coordinates for the given dimensions.
282
283        Parameters
284        ----------
285        *args : tuple
286            A tuple of :math:`(i,j)` with :math:`i` as the index to the start
287            slice and :math:`j` as the index to end the slice (excluded).
288
289        Examples
290        --------
291
292        >>> p = Point((3, 6, 2))
293        >>> p[:2] == (3, 6)
294        True
295
296        >>> p[1:2] == (6,)
297        True
298
299        """
300
301        return self.__loc.__getslice__(*args)
302
303    def __len__(self) -> int:
304        """ Returns the dimensions of the point.
305
306        Examples
307        --------
308
309        >>> len(Point((1, 2)))
310        2
311
312        """
313
314        return len(self.__loc)
315
316    def __repr__(self) -> str:
317        """Returns the string representation of the ``Point``.
318
319        Examples
320        --------
321
322        >>> Point((0, 1))
323        (0.0, 1.0)
324
325        """
326
327        return str(self)
328
329    def __str__(self) -> str:
330        """Returns a string representation of a ``Point`` object.
331
332        Examples
333        --------
334
335        >>> p = Point((1, 3))
336        >>> str(p)
337        '(1.0, 3.0)'
338
339        """
340
341        return str(self.__loc)
342        # return "POINT ({} {})".format(*self.__loc)
343
344
345class LineSegment(Geometry):
346    """Geometric representation of line segment objects.
347
348    Parameters
349    ----------
350    start_pt : libpysal.cg.Point
351        The point where the segment begins.
352    end_pt : libpysal.cg.Point
353        The point where the segment ends.
354
355    Attributes
356    ----------
357    p1 : libpysal.cg.Point
358        The starting point of the line segment.
359    p2 : Point
360        The ending point of the line segment.
361    bounding_box : libpysal.cg.Rectangle
362        The bounding box of the segment.
363    len : float
364        The length of the segment.
365    line : libpysal.cg.Line
366        The line on which the segment lies.
367
368    Examples
369    --------
370
371    >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
372
373    """
374
375    def __init__(self, start_pt, end_pt):
376
377        self._p1 = start_pt
378        self._p2 = end_pt
379        self._reset_props()
380
381    def __str__(self):
382        return "LineSegment(" + str(self._p1) + ", " + str(self._p2) + ")"
383        # return "LINESTRING ({} {}, {} {})".format(
384        #    self._p1[0], self._p1[1], self._p2[0], self._p2[1]
385        # )
386
387    def __eq__(self, other) -> bool:
388        """Returns ``True`` if ``self`` and ``other`` are the same line segment.
389
390        Examples
391        --------
392
393        >>> l1 = LineSegment(Point((1, 2)), Point((5, 6)))
394        >>> l2 = LineSegment(Point((5, 6)), Point((1, 2)))
395        >>> l1 == l2
396        True
397
398        >>> l2 == l1
399        True
400
401        """
402
403        eq = False
404
405        if not isinstance(other, self.__class__):
406            pass
407        else:
408            if other.p1 == self._p1 and other.p2 == self._p2:
409                eq = True
410            elif other.p2 == self._p1 and other.p1 == self._p2:
411                eq = True
412
413        return eq
414
415    def intersect(self, other) -> bool:
416        """Test whether segment intersects with other segment (``True``) or
417        not (``False``). Handles endpoints of segments being on other segment.
418
419        Parameters
420        ----------
421        other : libpysal.cg.LineSegment
422            Another line segment to check against.
423
424        Examples
425        --------
426
427        >>> ls = LineSegment(Point((5, 0)), Point((10, 0)))
428        >>> ls1 = LineSegment(Point((5, 0)), Point((10, 1)))
429        >>> ls.intersect(ls1)
430        True
431
432        >>> ls2 = LineSegment(Point((5, 1)), Point((10, 1)))
433        >>> ls.intersect(ls2)
434        False
435
436        >>> ls2 = LineSegment(Point((7, -1)), Point((7, 2)))
437        >>> ls.intersect(ls2)
438        True
439
440        """
441
442        ccw1 = self.sw_ccw(other.p2)
443        ccw2 = self.sw_ccw(other.p1)
444        ccw3 = other.sw_ccw(self.p1)
445        ccw4 = other.sw_ccw(self.p2)
446
447        intersects = ccw1 * ccw2 <= 0 and ccw3 * ccw4 <= 0
448
449        return intersects
450
451    def _reset_props(self):
452        """**HELPER METHOD. DO NOT CALL.**
453        Resets attributes which are functions of other attributes.
454        The getters for these attributes (implemented as properties)
455        then recompute their values if they have been reset since
456        the last call to the getter.
457
458        Examples
459        --------
460
461        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
462        >>> ls._reset_props()
463
464        """
465
466        self._bounding_box = None
467        self._len = None
468        self._line = False
469
470    def _get_p1(self):
471        """**HELPER METHOD. DO NOT CALL.**
472        Returns the ``p1`` attribute of the line segment.
473
474        Returns
475        -------
476        self._p1 : libpysal.cg.Point
477            The ``_p1`` attribute.
478
479        Examples
480        --------
481
482        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
483        >>> r = ls._get_p1()
484        >>> r == Point((1, 2))
485        True
486
487        """
488
489        return self._p1
490
491    def _set_p1(self, p1):
492        """**HELPER METHOD. DO NOT CALL.**
493        Sets the ``p1`` attribute of the line segment.
494
495        Parameters
496        ----------
497        p1 : libpysal.cg.Point
498            A point.
499
500        Returns
501        -------
502        self._p1 : libpysal.cg.Point
503            The reset ``p1`` attribute.
504
505        Examples
506        --------
507
508        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
509        >>> r = ls._set_p1(Point((3, -1)))
510        >>> r == Point((3.0, -1.0))
511        True
512
513        """
514
515        self._p1 = p1
516        self._reset_props()
517
518        return self._p1
519
520    p1 = property(_get_p1, _set_p1)
521
522    def _get_p2(self):
523        """**HELPER METHOD. DO NOT CALL.**
524        Returns the ``p2`` attribute of the line segment.
525
526        Returns
527        -------
528        self._p2 : libpysal.cg.Point
529            The ``_p2`` attribute.
530
531        Examples
532        --------
533
534        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
535        >>> r = ls._get_p2()
536        >>> r == Point((5, 6))
537        True
538
539        """
540
541        return self._p2
542
543    def _set_p2(self, p2):
544        """**HELPER METHOD. DO NOT CALL.**
545        Sets the ``p2`` attribute of the line segment.
546
547        Parameters
548        ----------
549        p2 : libpysal.cg.Point
550            A point.
551
552        Returns
553        -------
554        self._p2 : libpysal.cg.Point
555            The reset ``p2`` attribute.
556
557        Examples
558        --------
559
560        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
561        >>> r = ls._set_p2(Point((3, -1)))
562        >>> r == Point((3.0, -1.0))
563        True
564
565        """
566
567        self._p2 = p2
568        self._reset_props()
569
570        return self._p2
571
572    p2 = property(_get_p2, _set_p2)
573
574    def is_ccw(self, pt) -> bool:
575        """Returns whether a point is counterclockwise of the
576        segment (``True``) or not (``False``). Exclusive.
577
578        Parameters
579        ----------
580        pt : libpysal.cg.Point
581            A point lying ccw or cw of a segment.
582
583        Examples
584        --------
585
586        >>> ls = LineSegment(Point((0, 0)), Point((5, 0)))
587        >>> ls.is_ccw(Point((2, 2)))
588        True
589
590        >>> ls.is_ccw(Point((2, -2)))
591        False
592
593        """
594
595        v1 = (self._p2[0] - self._p1[0], self._p2[1] - self._p1[1])
596        v2 = (pt[0] - self._p1[0], pt[1] - self._p1[1])
597
598        return v1[0] * v2[1] - v1[1] * v2[0] > 0
599
600    def is_cw(self, pt) -> bool:
601        """Returns whether a point is clockwise of the
602        segment (``True``) or not (``False``). Exclusive.
603
604        Parameters
605        ----------
606        pt : libpysal.cg.Point
607            A point lying ccw or cw of a segment.
608
609        Examples
610        --------
611
612        >>> ls = LineSegment(Point((0, 0)), Point((5, 0)))
613        >>> ls.is_cw(Point((2, 2)))
614        False
615
616        >>> ls.is_cw(Point((2, -2)))
617        True
618
619        """
620
621        v1 = (self._p2[0] - self._p1[0], self._p2[1] - self._p1[1])
622        v2 = (pt[0] - self._p1[0], pt[1] - self._p1[1])
623
624        return v1[0] * v2[1] - v1[1] * v2[0] < 0
625
626    def sw_ccw(self, pt):
627        """Sedgewick test for ``pt`` being ccw of segment.
628
629        Returns
630        -------
631        is_ccw : bool
632            ``1`` if turn from ``self.p1`` to ``self.p2`` to ``pt`` is ccw.
633            ``-1`` if turn from ``self.p1`` to ``self.p2`` to ``pt`` is cw.
634            ``-1`` if the points are collinear and ``self.p1`` is in the middle.
635            ``1`` if the points are collinear and ``self.p2`` is in the middle.
636            ``0`` if the points are collinear and ``pt`` is in the middle.
637
638        """
639
640        p0 = self.p1
641        p1 = self.p2
642        p2 = pt
643
644        dx1 = p1[0] - p0[0]
645        dy1 = p1[1] - p0[1]
646        dx2 = p2[0] - p0[0]
647        dy2 = p2[1] - p0[1]
648
649        if dy1 * dx2 < dy2 * dx1:
650            is_ccw = 1
651        elif dy1 * dx2 > dy2 * dx1:
652            is_ccw = -1
653        elif dx1 * dx2 < 0 or dy1 * dy2 < 0:
654            is_ccw = -1
655        elif dx1 * dx1 + dy1 * dy1 >= dx2 * dx2 + dy2 * dy2:
656            is_ccw = 0
657        else:
658            is_ccw = 1
659
660        return is_ccw
661
662    def get_swap(self):
663        """Returns a ``LineSegment`` object which has its endpoints swapped.
664
665        Returns
666        -------
667        line_seg : libpysal.cg.LineSegment
668            The ``LineSegment`` object which has its endpoints swapped.
669
670        Examples
671        --------
672
673        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
674        >>> swap = ls.get_swap()
675        >>> swap.p1[0]
676        5.0
677
678        >>> swap.p1[1]
679        6.0
680
681        >>> swap.p2[0]
682        1.0
683
684        >>> swap.p2[1]
685        2.0
686
687        """
688
689        line_seg = LineSegment(self._p2, self._p1)
690
691        return line_seg
692
693    @property
694    def bounding_box(self):
695        """Returns the minimum bounding box of a ``LineSegment`` object.
696
697        Returns
698        -------
699        self._bounding_box : libpysal.cg.Rectangle
700            The bounding box of the line segment.
701
702        Examples
703        --------
704
705        >>> ls = LineSegment(Point((1, 2)), Point((5, 6)))
706        >>> ls.bounding_box.left
707        1.0
708
709        >>> ls.bounding_box.lower
710        2.0
711
712        >>> ls.bounding_box.right
713        5.0
714
715        >>> ls.bounding_box.upper
716        6.0
717
718        """
719
720        # If LineSegment attributes p1, p2 changed, recompute
721        if self._bounding_box is None:
722            self._bounding_box = Rectangle(
723                min([self._p1[0], self._p2[0]]),
724                min([self._p1[1], self._p2[1]]),
725                max([self._p1[0], self._p2[0]]),
726                max([self._p1[1], self._p2[1]]),
727            )
728        return Rectangle(
729            self._bounding_box.left,
730            self._bounding_box.lower,
731            self._bounding_box.right,
732            self._bounding_box.upper,
733        )
734
735    @property
736    def len(self) -> float:
737        """Returns the length of a ``LineSegment`` object.
738
739        Examples
740        --------
741
742        >>> ls = LineSegment(Point((2, 2)), Point((5, 2)))
743        >>> ls.len
744        3.0
745
746        """
747
748        # If LineSegment attributes p1, p2 changed, recompute
749        if self._len is None:
750            self._len = math.hypot(self._p1[0] - self._p2[0], self._p1[1] - self._p2[1])
751
752        return self._len
753
754    @property
755    def line(self):
756        """Returns a ``Line`` object of the line on which the segment lies.
757
758        Returns
759        -------
760        self._line : libpysal.cg.Line
761            The ``Line`` object of the line on which the segment lies.
762
763        Examples
764        --------
765
766        >>> ls = LineSegment(Point((2, 2)), Point((3, 3)))
767        >>> l = ls.line
768        >>> l.m
769        1.0
770
771        >>> l.b
772        0.0
773
774        """
775
776        if self._line == False:
777            dx = self._p1[0] - self._p2[0]
778            dy = self._p1[1] - self._p2[1]
779
780            if dx == 0 and dy == 0:
781                self._line = None
782            elif dx == 0:
783                self._line = VerticalLine(self._p1[0])
784            else:
785                m = dy / float(dx)
786                # y - mx
787                b = self._p1[1] - m * self._p1[0]
788                self._line = Line(m, b)
789
790        return self._line
791
792
793class VerticalLine(Geometry):
794    """Geometric representation of verticle line objects.
795
796    Parameters
797    ----------
798    x : {int, float}
799        The :math:`x`-intercept of the line. ``x`` is also an attribute.
800
801    Examples
802    --------
803
804    >>> ls = VerticalLine(0)
805    >>> ls.m
806    inf
807
808    >>> ls.b
809    nan
810
811    """
812
813    def __init__(self, x):
814
815        self._x = float(x)
816        self.m = float("inf")
817        self.b = float("nan")
818
819    def x(self, y) -> float:
820        """Returns the :math:`x`-value of the line at a particular :math:`y`-value.
821
822        Parameters
823        ----------
824        y : {int, float}
825            The :math:`y`-value at which to compute :math:`x`.
826
827        Examples
828        --------
829
830        >>> l = VerticalLine(0)
831        >>> l.x(0.25)
832        0.0
833
834        """
835
836        return self._x
837
838    def y(self, x) -> float:
839        """Returns the :math:`y`-value of the line at a particular :math:`x`-value.
840
841        Parameters
842        ----------
843        x : {int, float}
844            The :math:`x`-value at which to compute :math:`y`.
845
846        Examples
847        --------
848
849        >>> l = VerticalLine(1)
850        >>> l.y(1)
851        nan
852
853        """
854
855        return float("nan")
856
857
858class Line(Geometry):
859    """Geometric representation of line objects.
860
861    Parameters
862    ----------
863    m : {int, float}
864        The slope of the line. ``m`` is also an attribute.
865    b : {int, float}
866        The :math:`y`-intercept of the line. ``b`` is also an attribute.
867
868    Raises
869    ------
870    ArithmeticError
871        Raised when infinity is passed in as the slope.
872
873    Examples
874    --------
875
876    >>> ls = Line(1, 0)
877    >>> ls.m
878    1.0
879
880    >>> ls.b
881    0.0
882
883    """
884
885    def __init__(self, m, b):
886
887        if m == float("inf"):
888            raise ArithmeticError("Slope cannot be infinite.")
889
890        self.m = float(m)
891        self.b = float(b)
892
893    def x(self, y: Union[int, float]) -> float:
894        """Returns the :math:`x`-value of the line at a particular :math:`y`-value.
895
896        Parameters
897        ----------
898        y : {int, float}
899            The :math:`y`-value at which to compute :math:`x`.
900
901        Raises
902        ------
903        ArithmeticError
904            Raised when ``0.`` is passed in as the slope.
905
906        Examples
907        --------
908
909        >>> l = Line(0.5, 0)
910        >>> l.x(0.25)
911        0.5
912
913        """
914
915        if self.m == 0:
916            raise ArithmeticError("Cannot solve for 'x' when slope is zero.")
917
918        return (y - self.b) / self.m
919
920    def y(self, x: Union[int, float]) -> float:
921        """Returns the :math:`y`-value of the line at a particular :math:`x`-value.
922
923        Parameters
924        ----------
925        x : {int, float}
926            The :math:`x`-value at which to compute :math:`y`.
927
928        Examples
929        --------
930
931        >>> l = Line(1, 0)
932        >>> l.y(1)
933        1.0
934
935        """
936
937        if self.m == 0:
938            return self.b
939
940        return self.m * x + self.b
941
942
943class Ray:
944    """Geometric representation of ray objects.
945
946    Parameters
947    ----------
948    origin : libpysal.cg.Point
949        The point where the ray originates.
950    second_p :
951        The second point specifying the ray (not ``origin``.)
952
953    Attributes
954    ----------
955    o : libpysal.cg.Point
956        The origin (point where ray originates). See ``origin``.
957    p : libpysal.cg.Point
958        The second point on the ray (not the point where the
959        ray originates). See ``second_p``.
960
961    Examples
962    --------
963
964    >>> l = Ray(Point((0, 0)), Point((1, 0)))
965    >>> str(l.o)
966    '(0.0, 0.0)'
967
968    >>> str(l.p)
969    '(1.0, 0.0)'
970
971    """
972
973    def __init__(self, origin, second_p):
974
975        self.o = origin
976        self.p = second_p
977
978
979class Chain(Geometry):
980    """Geometric representation of a chain, also known as a polyline.
981
982    Parameters
983    ----------
984    vertices : list
985        A point list or list of point lists.
986
987    Attributes
988    ----------
989    vertices : list
990        The list of points of the vertices of the chain in order.
991    len : float
992        The geometric length of the chain.
993
994    Examples
995    --------
996
997    >>> c = Chain([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((2, 1))])
998
999    """
1000
1001    def __init__(self, vertices: list):
1002
1003        if isinstance(vertices[0], list):
1004            self._vertices = [part for part in vertices]
1005        else:
1006            self._vertices = [vertices]
1007        self._reset_props()
1008
1009    @classmethod
1010    def __from_geo_interface__(cls, geo: dict):
1011        if geo["type"].lower() == "linestring":
1012            verts = [Point(pt) for pt in geo["coordinates"]]
1013        elif geo["type"].lower() == "multilinestring":
1014            verts = [list(map(Point, part)) for part in geo["coordinates"]]
1015        else:
1016            raise TypeError("%r is not a Chain." % geo)
1017        return cls(verts)
1018
1019    @property
1020    def __geo_interface__(self) -> dict:
1021        if len(self.parts) == 1:
1022            return {"type": "LineString", "coordinates": self.vertices}
1023        else:
1024            return {"type": "MultiLineString", "coordinates": self.parts}
1025
1026    def _reset_props(self):
1027        """**HELPER METHOD. DO NOT CALL.** Resets attributes which are
1028        functions of other attributes. The ``getter``s for these attributes
1029        (implemented as ``properties``) then recompute their values if they
1030        have been reset since the last call to the ``getter``.
1031
1032        """
1033
1034        self._len = None
1035        self._arclen = None
1036        self._bounding_box = None
1037
1038    @property
1039    def vertices(self) -> list:
1040        """Returns the vertices of the chain in clockwise order.
1041
1042        Examples
1043        --------
1044
1045        >>> c = Chain([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((2, 1))])
1046        >>> verts = c.vertices
1047        >>> len(verts)
1048        4
1049
1050        """
1051
1052        return sum([part for part in self._vertices], [])
1053
1054    @property
1055    def parts(self) -> list:
1056        """Returns the parts (lists of ``libpysal.cg.Point`` objects) of the chain.
1057
1058        Examples
1059        --------
1060
1061        >>> c = Chain(
1062        ...     [
1063        ...         [Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((0, 1))],
1064        ...         [Point((2, 1)), Point((2, 2)), Point((1, 2)), Point((1, 1))]
1065        ...     ]
1066        ... )
1067        >>> len(c.parts)
1068        2
1069
1070        """
1071
1072        return [[v for v in part] for part in self._vertices]
1073
1074    @property
1075    def bounding_box(self):
1076        """Returns the bounding box of the chain.
1077
1078        Returns
1079        -------
1080        self._bounding_box : libpysal.cg.Rectangle
1081            The bounding box of the chain.
1082
1083        Examples
1084        --------
1085
1086        >>> c = Chain([Point((0, 0)), Point((2, 0)), Point((2, 1)), Point((0, 1))])
1087        >>> c.bounding_box.left
1088        0.0
1089
1090        >>> c.bounding_box.lower
1091        0.0
1092
1093        >>> c.bounding_box.right
1094        2.0
1095
1096        >>> c.bounding_box.upper
1097        1.0
1098
1099        """
1100
1101        if self._bounding_box is None:
1102            vertices = self.vertices
1103            self._bounding_box = Rectangle(
1104                min([v[0] for v in vertices]),
1105                min([v[1] for v in vertices]),
1106                max([v[0] for v in vertices]),
1107                max([v[1] for v in vertices]),
1108            )
1109
1110        return self._bounding_box
1111
1112    @property
1113    def len(self) -> int:
1114        """Returns the geometric length of the chain.
1115
1116        Examples
1117        --------
1118
1119        >>> c = Chain([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((2, 1))])
1120        >>> c.len
1121        3.0
1122
1123        >>> c = Chain(
1124        ...     [
1125        ...         [Point((0, 0)), Point((1, 0)), Point((1, 1))],
1126        ...         [Point((10, 10)), Point((11, 10)), Point((11, 11))]
1127        ...     ]
1128        ... )
1129        >>> c.len
1130        4.0
1131
1132        """
1133
1134        def dist(v1: tuple, v2: tuple) -> Union[int, float]:
1135            return math.hypot(v1[0] - v2[0], v1[1] - v2[1])
1136
1137        def part_perimeter(p: list) -> Union[int, float]:
1138            return sum([dist(p[i], p[i + 1]) for i in range(len(p) - 1)])
1139
1140        if self._len is None:
1141            self._len = sum([part_perimeter(part) for part in self._vertices])
1142
1143        return self._len
1144
1145    @property
1146    def arclen(self) -> Union[int, float]:
1147        """Returns the geometric length of the chain
1148        computed using 'arcdistance' (meters).
1149
1150        """
1151
1152        def part_perimeter(p: list) -> Union[int, float]:
1153            return sum([arcdist(p[i], p[i + 1]) * 1000.0 for i in range(len(p) - 1)])
1154
1155        if self._arclen is None:
1156            self._arclen = sum([part_perimeter(part) for part in self._vertices])
1157
1158        return self._arclen
1159
1160    @property
1161    def segments(self) -> list:
1162        """Returns the segments that compose the chain."""
1163
1164        return [
1165            [LineSegment(a, b) for (a, b) in zip(part[:-1], part[1:])]
1166            for part in self._vertices
1167        ]
1168
1169
1170class Ring(Geometry):
1171    """Geometric representation of a linear ring. Linear rings must be
1172    closed, the first and last point must be the same. Open rings will
1173    be closed. This class exists primarily as a geometric primitive to
1174    form complex polygons with multiple rings and holes. The ordering
1175    of the vertices is ignored and will not be altered.
1176
1177    Parameters
1178    ----------
1179    vertices : list
1180        A list of vertices.
1181
1182    Attributes
1183    ----------
1184    vertices : list
1185        A list of points with the vertices of the ring.
1186    len : int
1187        The number of vertices.
1188    perimeter : float
1189        The geometric length of the perimeter of the ring.
1190    bounding_box : libpysal.cg.Rectangle
1191        The bounding box of the ring.
1192    area : float
1193        The area enclosed by the ring.
1194    centroid : {tuple, libpysal.cg.Point}
1195        The centroid of the ring defined by the 'center of gravity'
1196        or 'center or mass'.
1197    _quad_tree_structure : libpysal.cg.QuadTreeStructureSingleRing
1198        The quad tree structure for the ring. This structure helps
1199        test if a point is inside the ring.
1200
1201    """
1202
1203    def __init__(self, vertices):
1204        if vertices[0] != vertices[-1]:
1205            vertices = vertices[:] + vertices[0:1]
1206            # msg = "Supplied vertices do not form a closed ring, "
1207            # msg += "the first and last vertices are not the same."
1208            # raise ValueError(msg)
1209
1210        self.vertices = tuple(vertices)
1211        self._perimeter = None
1212        self._bounding_box = None
1213        self._area = None
1214        self._centroid = None
1215        self._quad_tree_structure = None
1216
1217    def __len__(self) -> int:
1218        return len(self.vertices)
1219
1220    @property
1221    def len(self) -> int:
1222        return len(self)
1223
1224    @staticmethod
1225    def dist(v1, v2) -> Union[int, float]:
1226
1227        return math.hypot(v1[0] - v2[0], v1[1] - v2[1])
1228
1229    @property
1230    def perimeter(self) -> Union[int, float]:
1231
1232        if self._perimeter is None:
1233            dist = self.dist
1234            v = self.vertices
1235            self._perimeter = sum(
1236                [dist(v[i], v[i + 1]) for i in range(-1, len(self) - 1)]
1237            )
1238        return self._perimeter
1239
1240    @property
1241    def bounding_box(self):
1242        """Returns the bounding box of the ring.
1243
1244        Returns
1245        -------
1246        self._bounding_box : libpysal.cg.Rectangle
1247            The bounding box of the ring.
1248
1249        Examples
1250        --------
1251
1252        >>> r = Ring(
1253        ...     [
1254        ...         Point((0, 0)),
1255        ...         Point((2, 0)),
1256        ...         Point((2, 1)),
1257        ...         Point((0, 1)),
1258        ...         Point((0, 0))
1259        ...     ]
1260        ... )
1261
1262        >>> r.bounding_box.left
1263        0.0
1264
1265        >>> r.bounding_box.lower
1266        0.0
1267
1268        >>> r.bounding_box.right
1269        2.0
1270
1271        >>> r.bounding_box.upper
1272        1.0
1273
1274        """
1275
1276        if self._bounding_box is None:
1277            vertices = self.vertices
1278            x = [v[0] for v in vertices]
1279            y = [v[1] for v in vertices]
1280            self._bounding_box = Rectangle(min(x), min(y), max(x), max(y))
1281
1282        return self._bounding_box
1283
1284    @property
1285    def area(self) -> Union[int, float]:
1286        """Returns the area of the ring.
1287
1288        Examples
1289        --------
1290
1291        >>> r = Ring(
1292        ...     [
1293        ...         Point((0, 0)),
1294        ...         Point((2, 0)),
1295        ...         Point((2, 1)),
1296        ...         Point((0, 1)),
1297        ...         Point((0, 0))
1298        ...     ]
1299        ... )
1300        >>> r.area
1301        2.0
1302
1303        """
1304
1305        return abs(self.signed_area)
1306
1307    @property
1308    def signed_area(self) -> Union[int, float]:
1309        if self._area is None:
1310            vertices = self.vertices
1311            x = [v[0] for v in vertices]
1312            y = [v[1] for v in vertices]
1313            N = len(self)
1314
1315            A = 0.0
1316            for i in range(N - 1):
1317                A += (x[i] + x[i + 1]) * (y[i] - y[i + 1])
1318            A = A * 0.5
1319            self._area = -A
1320
1321        return self._area
1322
1323    @property
1324    def centroid(self):
1325        """Returns the centroid of the ring.
1326
1327        Returns
1328        -------
1329        self._centroid : libpysal.cg.Point
1330            The ring's centroid.
1331
1332        Notes
1333        -----
1334
1335        The centroid returned by this method is the geometric centroid.
1336        Also known as the 'center of gravity' or 'center of mass'.
1337
1338        Examples
1339        --------
1340
1341        >>> r = Ring(
1342        ...     [
1343        ...         Point((0, 0)),
1344        ...         Point((2, 0)),
1345        ...         Point((2, 1)),
1346        ...         Point((0, 1)),
1347        ...         Point((0, 0))
1348        ...     ]
1349        ... )
1350        >>> str(r.centroid)
1351        '(1.0, 0.5)'
1352
1353        """
1354
1355        if self._centroid is None:
1356            vertices = self.vertices
1357            x = [v[0] for v in vertices]
1358            y = [v[1] for v in vertices]
1359            A = self.signed_area
1360            N = len(self)
1361            cx = 0
1362            cy = 0
1363            for i in range(N - 1):
1364                f = x[i] * y[i + 1] - x[i + 1] * y[i]
1365                cx += (x[i] + x[i + 1]) * f
1366                cy += (y[i] + y[i + 1]) * f
1367            cx = 1.0 / (6 * A) * cx
1368            cy = 1.0 / (6 * A) * cy
1369            self._centroid = Point((cx, cy))
1370
1371        return self._centroid
1372
1373    def build_quad_tree_structure(self):
1374        """Build the quad tree structure for this polygon. Once
1375        the structure is built, speed for testing if a point is
1376        inside the ring will be increased significantly.
1377
1378        """
1379
1380        self._quad_tree_structure = QuadTreeStructureSingleRing(self)
1381
1382    def contains_point(self, point):
1383        """Point containment using winding number. The implementation is based on
1384        `this <http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf>`_.
1385
1386        Parameters
1387        ----------
1388        point : libpysal.cg.Point
1389            The point to test for containment.
1390
1391        Returns
1392        -------
1393        point_contained : bool
1394            ``True`` if ``point`` is contained within the polygon, otherwise ``False``.
1395
1396        """
1397
1398        point_contained = False
1399
1400        if self._quad_tree_structure is None:
1401            x, y = point
1402
1403            # bbox checks
1404            bbleft = x < self.bounding_box.left
1405            bbright = x > self.bounding_box.right
1406            bblower = y < self.bounding_box.lower
1407            bbupper = y > self.bounding_box.upper
1408
1409            if bbleft or bbright or bblower or bbupper:
1410                pass
1411            else:
1412                rn = len(self.vertices)
1413                xs = [self.vertices[i][0] - point[0] for i in range(rn)]
1414                ys = [self.vertices[i][1] - point[1] for i in range(rn)]
1415                w = 0
1416
1417                for i in range(len(self.vertices) - 1):
1418                    yi = ys[i]
1419                    yj = ys[i + 1]
1420                    xi = xs[i]
1421                    xj = xs[i + 1]
1422                    if yi * yj < 0:
1423                        r = xi + yi * (xj - xi) / (yi - yj)
1424                        if r > 0:
1425                            if yi < 0:
1426                                w += 1
1427                            else:
1428                                w -= 1
1429                    elif yi == 0 and xi > 0:
1430                        if yj > 0:
1431                            w += 0.5
1432                        else:
1433                            w -= 0.5
1434                    elif yj == 0 and xj > 0:
1435                        if yi < 0:
1436                            w += 0.5
1437                        else:
1438                            w -= 0.5
1439                if w == 0:
1440                    pass
1441                else:
1442                    point_contained = True
1443        else:
1444            point_contained = self._quad_tree_structure.contains_point(point)
1445
1446        return point_contained
1447
1448
1449class Polygon(Geometry):
1450    """Geometric representation of polygon objects.
1451    Returns a polygon created from the objects specified.
1452
1453    Parameters
1454    ----------
1455    vertices : list
1456        A list of vertices or a list of lists of vertices.
1457    holes : list
1458        A list of sub-polygons to be considered as holes.
1459        Default is ``None``.
1460
1461    Attributes
1462    ----------
1463    vertices : list
1464        A list of points with the vertices of the polygon in clockwise order.
1465    len : int
1466        The number of vertices including holes.
1467    perimeter : float
1468        The geometric length of the perimeter of the polygon.
1469    bounding_box : libpysal.cg.Rectangle
1470        The bounding box of the polygon.
1471    bbox : list
1472        A list representation of the bounding box in the
1473        form ``[left, lower, right, upper]``.
1474    area : float
1475        The area enclosed by the polygon.
1476    centroid : tuple
1477        The 'center of gravity', i.e. the mean point of the polygon.
1478
1479    Examples
1480    --------
1481
1482    >>> p1 = Polygon([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((0, 1))])
1483
1484    """
1485
1486    def __init__(self, vertices, holes=None):
1487
1488        self._part_rings = []
1489        self._hole_rings = []
1490
1491        def clockwise(part: list) -> list:
1492            if standalone.is_clockwise(part):
1493                return part[:]
1494            else:
1495                return part[::-1]
1496
1497        vl = list(vertices)
1498        if isinstance(vl[0], list):
1499            self._part_rings = list(map(Ring, vertices))
1500            self._vertices = [clockwise(part) for part in vertices]
1501        else:
1502            self._part_rings = [Ring(vertices)]
1503            self._vertices = [clockwise(vertices)]
1504        if holes is not None and holes != []:
1505            if isinstance(holes[0], list):
1506                self._hole_rings = list(map(Ring, holes))
1507                self._holes = [clockwise(hole) for hole in holes]
1508            else:
1509                self._hole_rings = [Ring(holes)]
1510                self._holes = [clockwise(holes)]
1511        else:
1512            self._holes = [[]]
1513        self._reset_props()
1514
1515    @classmethod
1516    def __from_geo_interface__(cls, geo: dict):
1517        """While PySAL does not differentiate polygons and multipolygons
1518        GEOS, Shapely, and geoJSON do. In GEOS, etc, polygons may only
1519        have a single exterior ring, all other parts are holes.
1520        MultiPolygons are simply a list of polygons.
1521
1522        """
1523
1524        geo_type = geo["type"].lower()
1525        if geo_type == "multipolygon":
1526            parts = []
1527            holes = []
1528            for polygon in geo["coordinates"]:
1529                verts = [[Point(pt) for pt in part] for part in polygon]
1530                parts += verts[0:1]
1531                holes += verts[1:]
1532            if not holes:
1533                holes = None
1534            return cls(parts, holes)
1535        else:
1536            verts = [[Point(pt) for pt in part] for part in geo["coordinates"]]
1537            return cls(verts[0:1], verts[1:])
1538
1539    @property
1540    def __geo_interface__(self) -> dict:
1541        """Return ``__geo_interface__`` information lookup."""
1542
1543        if len(self.parts) > 1:
1544            geo = {
1545                "type": "MultiPolygon",
1546                "coordinates": [[part] for part in self.parts],
1547            }
1548            if self._holes[0]:
1549                geo["coordinates"][0] += self._holes
1550            return geo
1551        if self._holes[0]:
1552            return {"type": "Polygon", "coordinates": self._vertices + self._holes}
1553        else:
1554            return {"type": "Polygon", "coordinates": self._vertices}
1555
1556    def _reset_props(self):
1557        """Resets the geometric properties of the polygon."""
1558        self._perimeter = None
1559        self._bounding_box = None
1560        self._bbox = None
1561        self._area = None
1562        self._centroid = None
1563        self._len = None
1564
1565    def __len__(self) -> int:
1566        return self.len
1567
1568    @property
1569    def len(self) -> int:
1570        """Returns the number of vertices in the polygon.
1571
1572        Examples
1573        --------
1574
1575        >>> p1 = Polygon([Point((0, 0)), Point((0, 1)), Point((1, 1)), Point((1, 0))])
1576        >>> p1.len
1577        4
1578
1579        >>> len(p1)
1580        4
1581
1582        """
1583
1584        if self._len is None:
1585            self._len = len(self.vertices)
1586        return self._len
1587
1588    @property
1589    def vertices(self) -> list:
1590        """Returns the vertices of the polygon in clockwise order.
1591
1592        Examples
1593        --------
1594
1595        >>> p1 = Polygon([Point((0, 0)), Point((0, 1)), Point((1, 1)), Point((1, 0))])
1596        >>> len(p1.vertices)
1597        4
1598
1599        """
1600
1601        return sum([part for part in self._vertices], []) + sum(
1602            [part for part in self._holes], []
1603        )
1604
1605    @property
1606    def holes(self) -> list:
1607        """Returns the holes of the polygon in clockwise order.
1608
1609        Examples
1610        --------
1611
1612        >>> p = Polygon(
1613        ...     [Point((0, 0)), Point((10, 0)), Point((10, 10)), Point((0, 10))],
1614        ...     [Point((1, 2)), Point((2, 2)), Point((2, 1)), Point((1, 1))]
1615        ... )
1616        >>> len(p.holes)
1617        1
1618
1619        """
1620
1621        return [[v for v in part] for part in self._holes]
1622
1623    @property
1624    def parts(self) -> list:
1625        """Returns the parts of the polygon in clockwise order.
1626
1627        Examples
1628        --------
1629
1630        >>> p = Polygon(
1631        ...     [
1632        ...         [Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((0, 1))],
1633        ...         [Point((2, 1)), Point((2, 2)), Point((1, 2)), Point((1, 1))]
1634        ...     ]
1635        ... )
1636        >>> len(p.parts)
1637        2
1638
1639        """
1640
1641        return [[v for v in part] for part in self._vertices]
1642
1643    @property
1644    def perimeter(self) -> Union[int, float]:
1645        """Returns the perimeter of the polygon.
1646
1647        Examples
1648        --------
1649
1650        >>> p = Polygon([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((0, 1))])
1651        >>> p.perimeter
1652        4.0
1653
1654        """
1655
1656        def dist(v1: Union[int, float], v2: Union[int, float]) -> float:
1657            return math.hypot(v1[0] - v2[0], v1[1] - v2[1])
1658
1659        def part_perimeter(part) -> Union[int, float]:
1660            return sum([dist(part[i], part[i + 1]) for i in range(-1, len(part) - 1)])
1661
1662        sum_perim = lambda part_type: sum([part_perimeter(part) for part in part_type])
1663
1664        if self._perimeter is None:
1665            self._perimeter = sum_perim(self._vertices) + sum_perim(self._holes)
1666
1667        return self._perimeter
1668
1669    @property
1670    def bbox(self):
1671        """Returns the bounding box of the polygon as a list.
1672
1673        Returns
1674        -------
1675        self._bbox : list
1676            The bounding box of the polygon as a list.
1677
1678        See Also
1679        --------
1680
1681        libpysal.cg.bounding_box
1682
1683        """
1684
1685        if self._bbox is None:
1686            self._bbox = [
1687                self.bounding_box.left,
1688                self.bounding_box.lower,
1689                self.bounding_box.right,
1690                self.bounding_box.upper,
1691            ]
1692        return self._bbox
1693
1694    @property
1695    def bounding_box(self):
1696        """Returns the bounding box of the polygon.
1697
1698        Returns
1699        -------
1700        self._bounding_box : libpysal.cg.Rectangle
1701            The bounding box of the polygon.
1702
1703        Examples
1704        --------
1705
1706        >>> p = Polygon([Point((0, 0)), Point((2, 0)), Point((2, 1)), Point((0, 1))])
1707        >>> p.bounding_box.left
1708        0.0
1709
1710        >>> p.bounding_box.lower
1711        0.0
1712
1713        >>> p.bounding_box.right
1714        2.0
1715
1716        >>> p.bounding_box.upper
1717        1.0
1718
1719        """
1720
1721        if self._bounding_box is None:
1722            vertices = self.vertices
1723            self._bounding_box = Rectangle(
1724                min([v[0] for v in vertices]),
1725                min([v[1] for v in vertices]),
1726                max([v[0] for v in vertices]),
1727                max([v[1] for v in vertices]),
1728            )
1729        return self._bounding_box
1730
1731    @property
1732    def area(self) -> float:
1733        """Returns the area of the polygon.
1734
1735        Examples
1736        --------
1737
1738        >>> p = Polygon([Point((0, 0)), Point((1, 0)), Point((1, 1)), Point((0, 1))])
1739        >>> p.area
1740        1.0
1741
1742        >>> p = Polygon(
1743        ...     [Point((0, 0)), Point((10, 0)), Point((10, 10)), Point((0, 10))],
1744        ...     [Point((2, 1)), Point((2, 2)), Point((1, 2)), Point((1, 1))]
1745        ... )
1746        >>> p.area
1747        99.0
1748
1749        """
1750
1751        def part_area(pv: list) -> float:
1752            __area = 0
1753            for i in range(-1, len(pv) - 1):
1754                __area += (pv[i][0] + pv[i + 1][0]) * (pv[i][1] - pv[i + 1][1])
1755            __area = __area * 0.5
1756            if __area < 0:
1757                __area = -area
1758            return __area
1759
1760        sum_area = lambda part_type: sum([part_area(part) for part in part_type])
1761        _area = sum_area(self._vertices) - sum_area(self._holes)
1762
1763        return _area
1764
1765    @property
1766    def centroid(self) -> tuple:
1767        """Returns the centroid of the polygon.
1768
1769        Notes
1770        -----
1771
1772        The centroid returned by this method is the geometric
1773        centroid and respects multipart polygons with holes.
1774        Also known as the 'center of gravity' or 'center of mass'.
1775
1776        Examples
1777        --------
1778
1779        >>> p = Polygon(
1780        ...     [Point((0, 0)), Point((10, 0)), Point((10, 10)), Point((0, 10))],
1781        ...     [Point((1, 1)), Point((1, 2)), Point((2, 2)), Point((2, 1))]
1782        ... )
1783        >>> p.centroid
1784        (5.0353535353535355, 5.0353535353535355)
1785
1786        """
1787
1788        CP = [ring.centroid for ring in self._part_rings]
1789        AP = [ring.area for ring in self._part_rings]
1790        CH = [ring.centroid for ring in self._hole_rings]
1791        AH = [-ring.area for ring in self._hole_rings]
1792
1793        A = AP + AH
1794        cx = sum([pt[0] * area for pt, area in zip(CP + CH, A)]) / sum(A)
1795        cy = sum([pt[1] * area for pt, area in zip(CP + CH, A)]) / sum(A)
1796
1797        return cx, cy
1798
1799    def build_quad_tree_structure(self):
1800        """Build the quad tree structure for this polygon. Once
1801        the structure is built, speed for testing if a point is
1802        inside the ring will be increased significantly.
1803
1804        """
1805
1806        for ring in self._part_rings:
1807            ring.build_quad_tree_structure()
1808        for ring in self._hole_rings:
1809            ring.build_quad_tree_structure()
1810        self.is_quad_tree_structure_built = True
1811
1812    def contains_point(self, point):
1813        """Test if a polygon contains a point.
1814
1815        Parameters
1816        ----------
1817        point : libpysal.cg.Point
1818            A point to test for containment.
1819
1820        Returns
1821        -------
1822        contains : bool
1823            ``True`` if the polygon contains ``point`` otherwise ``False``.
1824
1825        Examples
1826        --------
1827
1828        >>> p = Polygon(
1829        ...     [Point((0,0)), Point((4,0)), Point((4,5)), Point((2,3)), Point((0,5))]
1830        ... )
1831        >>> p.contains_point((3,3))
1832        1
1833
1834        >>> p.contains_point((0,6))
1835        0
1836
1837        >>> p.contains_point((2,2.9))
1838        1
1839
1840        >>> p.contains_point((4,5))
1841        0
1842
1843        >>> p.contains_point((4,0))
1844        0
1845
1846        Handles holes.
1847
1848        >>> p = Polygon(
1849        ...     [Point((0, 0)), Point((0, 10)), Point((10, 10)), Point((10, 0))],
1850        ...     [Point((2, 2)), Point((4, 2)), Point((4, 4)), Point((2, 4))]
1851        ... )
1852        >>> p.contains_point((3.0, 3.0))
1853        False
1854
1855        >>> p.contains_point((1.0, 1.0))
1856        True
1857
1858        Notes
1859        -----
1860
1861        Points falling exactly on polygon edges may yield unpredictable results.
1862
1863        """
1864
1865        searching = True
1866
1867        for ring in self._hole_rings:
1868            if ring.contains_point(point):
1869                contains = False
1870                searching = False
1871                break
1872
1873        if searching:
1874            for ring in self._part_rings:
1875                if ring.contains_point(point):
1876                    contains = True
1877                    searching = False
1878                    break
1879            if searching:
1880                contains = False
1881
1882        return contains
1883
1884
1885class Rectangle(Geometry):
1886    """Geometric representation of rectangle objects.
1887
1888    Attributes
1889    ----------
1890    left : float
1891        Minimum x-value of the rectangle.
1892    lower : float
1893        Minimum y-value of the rectangle.
1894    right : float
1895        Maximum x-value of the rectangle.
1896    upper : float
1897        Maximum y-value of the rectangle.
1898
1899    Examples
1900    --------
1901
1902    >>> r = Rectangle(-4, 3, 10, 17)
1903    >>> r.left #minx
1904    -4.0
1905
1906    >>> r.lower #miny
1907    3.0
1908
1909    >>> r.right #maxx
1910    10.0
1911
1912    >>> r.upper #maxy
1913    17.0
1914
1915    """
1916
1917    def __init__(self, left, lower, right, upper):
1918
1919        if right < left or upper < lower:
1920            raise ArithmeticError("Rectangle must have positive area.")
1921        self.left = float(left)
1922        self.lower = float(lower)
1923        self.right = float(right)
1924        self.upper = float(upper)
1925
1926    def __bool__(self):
1927        """Rectangles will evaluate to False if they have zero area.
1928        ``___nonzero__`` is used "to implement truth value
1929        testing and the built-in operation ``bool()``"
1930        ``-- http://docs.python.org/reference/datamodel.html
1931
1932        Examples
1933        --------
1934
1935        >>> r = Rectangle(0, 0, 0, 0)
1936        >>> bool(r)
1937        False
1938
1939        >>> r = Rectangle(0, 0, 1, 1)
1940        >>> bool(r)
1941        True
1942
1943        """
1944
1945        return bool(self.area)
1946
1947    def __eq__(self, other):
1948        if other:
1949            return self[:] == other[:]
1950        return False
1951
1952    def __add__(self, other):
1953        x, y, X, Y = self[:]
1954        x1, y2, X1, Y1 = other[:]
1955
1956        return Rectangle(
1957            min(self.left, other.left),
1958            min(self.lower, other.lower),
1959            max(self.right, other.right),
1960            max(self.upper, other.upper),
1961        )
1962
1963    def __getitem__(self, key):
1964        """
1965
1966        Examples
1967        --------
1968
1969        >>> r = Rectangle(-4, 3, 10, 17)
1970        >>> r[:]
1971        [-4.0, 3.0, 10.0, 17.0]
1972
1973        """
1974
1975        l = [self.left, self.lower, self.right, self.upper]
1976
1977        return l.__getitem__(key)
1978
1979    def set_centroid(self, new_center):
1980        """Moves the rectangle center to a new specified point.
1981
1982        Parameters
1983        ----------
1984        new_center : libpysal.cg.Point
1985            The new location of the centroid of the polygon.
1986
1987        Examples
1988        --------
1989
1990        >>> r = Rectangle(0, 0, 4, 4)
1991        >>> r.set_centroid(Point((4, 4)))
1992        >>> r.left
1993        2.0
1994
1995        >>> r.right
1996        6.0
1997
1998        >>> r.lower
1999        2.0
2000
2001        >>> r.upper
2002        6.0
2003
2004        """
2005
2006        shift = (
2007            new_center[0] - (self.left + self.right) / 2,
2008            new_center[1] - (self.lower + self.upper) / 2,
2009        )
2010
2011        self.left = self.left + shift[0]
2012        self.right = self.right + shift[0]
2013        self.lower = self.lower + shift[1]
2014        self.upper = self.upper + shift[1]
2015
2016    def set_scale(self, scale):
2017        """Rescales the rectangle around its center.
2018
2019        Parameters
2020        ----------
2021        scale : int, float
2022            The ratio of the new scale to the old
2023            scale (e.g. 1.0 is current size).
2024
2025        Examples
2026        --------
2027
2028        >>> r = Rectangle(0, 0, 4, 4)
2029        >>> r.set_scale(2)
2030        >>> r.left
2031        -2.0
2032        >>> r.right
2033        6.0
2034        >>> r.lower
2035        -2.0
2036        >>> r.upper
2037        6.0
2038
2039        """
2040
2041        center = ((self.left + self.right) / 2, (self.lower + self.upper) / 2)
2042
2043        self.left = center[0] + scale * (self.left - center[0])
2044        self.right = center[0] + scale * (self.right - center[0])
2045        self.lower = center[1] + scale * (self.lower - center[1])
2046        self.upper = center[1] + scale * (self.upper - center[1])
2047
2048    @property
2049    def area(self) -> Union[int, float]:
2050        """Returns the area of the Rectangle.
2051
2052        Examples
2053        --------
2054
2055        >>> r = Rectangle(0, 0, 4, 4)
2056        >>> r.area
2057        16.0
2058
2059        """
2060
2061        return (self.right - self.left) * (self.upper - self.lower)
2062
2063    @property
2064    def width(self) -> Union[int, float]:
2065        """Returns the width of the Rectangle.
2066
2067        Examples
2068        --------
2069
2070        >>> r = Rectangle(0, 0, 4, 4)
2071        >>> r.width
2072        4.0
2073
2074        """
2075
2076        return self.right - self.left
2077
2078    @property
2079    def height(self) -> Union[int, float]:
2080        """Returns the height of the Rectangle.
2081
2082        Examples
2083        --------
2084
2085        >>> r = Rectangle(0, 0, 4, 4)
2086        >>> r.height
2087        4.0
2088
2089        """
2090
2091        return self.upper - self.lower
2092
2093
2094_geoJSON_type_to_Pysal_type = {
2095    "point": Point,
2096    "linestring": Chain,
2097    "multilinestring": Chain,
2098    "polygon": Polygon,
2099    "multipolygon": Polygon,
2100}
2101
2102# moving this to top breaks unit tests !
2103from . import standalone
2104from .polygonQuadTreeStructure import QuadTreeStructureSingleRing
2105