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