1 from __future__
import division
2 from builtins
import map
3 from builtins
import range
4 from builtins
import object
33 ARCSEC_PER_DEG = 3600.0
34 DEG_PER_ARCSEC = 1.0 / 3600.0
37 ANGLE_EPSILON = 0.001 * DEG_PER_ARCSEC
40 POLE_EPSILON = 1.0 * DEG_PER_ARCSEC
44 COS_MAX = 1.0 - 1.0e-15
52 SIN_MIN = math.sqrt(CROSS_N2MIN)
59 from sys
import float_info
60 MAX_FLOAT = float_info.max
61 MIN_FLOAT = float_info.min
62 EPSILON = float_info.epsilon
65 MAX_FLOAT = 1.7976931348623157e+308
66 MIN_FLOAT = 2.2250738585072014e-308
67 EPSILON = 2.2204460492503131e-16
69 if hasattr(math,
'isinf'):
74 return x == INF
or x == NEG_INF
80 """Returns the dot product of cartesian 3-vectors v1 and v2. 82 return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
86 """Returns the cross product of cartesian 3-vectors v1 and v2. 88 return (v1[1] * v2[2] - v1[2] * v2[1],
89 v1[2] * v2[0] - v1[0] * v2[2],
90 v1[0] * v2[1] - v1[1] * v2[0])
94 """Returns a copy of the cartesian 3-vector v scaled by 1 / s. 96 return (v[0] / s, v[1] / s, v[2] / s)
100 """Returns a normalized copy of the cartesian 3-vector v. 102 n = math.sqrt(
dot(v, v))
104 raise RuntimeError(
'Cannot normalize a 3-vector with 0 magnitude')
105 return (v[0] / n, v[1] / n, v[2] / n)
109 """Returns spherical coordinates in degrees for the input coordinates, 110 which can be spherical or 3D cartesian. The 2 (spherical) or 3 111 (cartesian 3-vector) inputs can be passed either individually 112 or as a tuple/list, and can be of any type convertible to a float. 118 if t[1] < -90.0
or t[1] > 90.0:
119 raise RuntimeError(
'Latitude angle is out of bounds')
129 theta = math.degrees(math.atan2(y, x))
135 phi = math.degrees(math.atan2(z, math.sqrt(d2)))
137 raise TypeError(
'Expecting 2 or 3 coordinates, or a tuple/list ' +
138 'containing 2 or 3 coordinates')
142 """Returns a unit cartesian 3-vector corresponding to the input 143 coordinates, which can be spherical or 3D cartesian. The 2 (spherical) 144 or 3 (cartesian 3-vector) input coordinates can either be passed 145 individually or as a tuple/list, and can be of any type convertible 151 theta = math.radians(
float(args[0]))
152 phi = math.radians(
float(args[1]))
153 cosPhi = math.cos(phi)
154 return (math.cos(theta) * cosPhi,
155 math.sin(theta) * cosPhi,
161 n = math.sqrt(x * x + y * y + z * z)
163 raise RuntimeError(
'Cannot normalize a 3-vector with 0 magnitude')
164 return (x / n, y / n, z / n)
165 raise TypeError(
'Expecting 2 or 3 coordinates, or a tuple/list ' +
166 'containing 2 or 3 coordinates')
170 """Returns the angular separation in degrees between points p1 and p2, 171 which must both be specified in spherical coordinates. The implementation 172 uses the halversine distance formula. 174 sdt = math.sin(math.radians(p1[0] - p2[0]) * 0.5)
175 sdp = math.sin(math.radians(p1[1] - p2[1]) * 0.5)
176 cc = math.cos(math.radians(p1[1])) * math.cos(math.radians(p2[1]))
177 s = math.sqrt(sdp * sdp + cc * sdt * sdt)
181 return 2.0 * math.degrees(math.asin(s))
185 """Clamps the input latitude angle phi to [-90.0, 90.0] deg. 195 """Range reduces the given longitude angle to lie in the range 198 theta = math.fmod(theta, 360.0)
206 """Returns unit N,E basis vectors for a point v, which must be a 209 north = (-v[0] * v[2],
211 v[0] * v[0] + v[1] * v[1])
212 if north == (0.0, 0.0, 0.0):
214 north = (-1.0, 0.0, 0.0)
215 east = (0.0, 1.0, 0.0)
223 """Returns the longitude angle alpha of the intersections (alpha, phi), 224 (-alpha, phi) of the circle centered on (0, centerPhi) with radius r and 225 the plane z = sin(phi). If there is no intersection, None is returned. 227 if phi < centerPhi - r
or phi > centerPhi + r:
229 a =
abs(centerPhi - phi)
230 if a <= r - (90.0 - phi)
or a <= r - (90.0 + phi):
232 p = math.radians(phi)
233 cp = math.radians(centerPhi)
234 x = math.cos(math.radians(r)) - math.sin(cp) * math.sin(cp)
235 u = math.cos(cp) * math.cos(p)
236 y = math.sqrt(
abs(u * u - x * x))
237 return math.degrees(
abs(math.atan2(y, x)))
241 """Computes alpha, the extent in longitude angle [-alpha, alpha] of the 242 circle with radius r and center (0, centerPhi) on the unit sphere. 243 Both r and centerPhi are assumed to be in units of degrees. 244 centerPhi is clamped to lie in the range [-90,90] and r must 245 lie in the range [0, 90]. 247 assert r >= 0.0
and r <= 90.0
251 if abs(centerPhi) + r > 90.0 - POLE_EPSILON:
254 c = math.radians(centerPhi)
256 x = math.sqrt(
abs(math.cos(c - r) * math.cos(c + r)))
257 return math.degrees(
abs(math.atan(y / x)))
261 """Returns the angular separation in degrees between points v1 and v2, 262 which must both be specified as cartesian 3-vectors. 266 ss = math.sqrt(
dot(n, n))
267 if cs == 0.0
and ss == 0.0:
269 return math.degrees(math.atan2(ss, cs))
273 """Returns the minimum angular separation in degrees between p 274 and points on the great circle edge with plane normal n and 275 vertices v1, v2. All inputs must be unit cartesian 3-vectors. 279 if dot(p1, p) >= 0.0
and dot(p2, p) <= 0.0:
286 """Returns the minimum angular separation in degrees between p 287 and points on the small circle edge with constant latitude angle 288 phi and vertices (minTheta, phi), (maxTheta, phi). p must be in 289 spherical coordinates. 291 if minTheta > maxTheta:
292 if p[0] >= minTheta
or p[0] <= maxTheta:
293 return abs(p[1] - phi)
295 if p[0] >= minTheta
and p[0] <= maxTheta:
296 return abs(p[1] - phi)
302 """Returns the minimum angular separation in degrees between p 303 and points on the great circle edge with constant longitude angle 304 theta and vertices (theta, minPhi), (theta, maxPhi). p must be a 305 unit cartesian 3-vector. 317 """Computes the centroid of a set of vertices (which must be passed in 318 as a list of cartesian unit vectors) on the unit sphere. 320 x, y, z = 0.0, 0.0, 0.0
330 """Tests whether p lies on the shortest great circle arc from cartesian 331 unit vectors v1 to v2, assuming that p is a unit vector on the plane 332 defined by the origin, v1 and v2. 336 return dot(p1, p) >= 0.0
and dot(p2, p) <= 0.0
340 """Computes the number of segments to divide the given latitude angle 341 range [phiMin, phiMax] (degrees) into. Two points within the range 342 separated by at least one segment are guaranteed to have angular 343 separation of at least width degrees. 346 if p > 90.0 - 1.0 * DEG_PER_ARCSEC:
350 elif width < 1.0 * DEG_PER_ARCSEC:
351 width = 1.0 * DEG_PER_ARCSEC
353 cw = math.cos(math.radians(width))
358 y = math.sqrt(
abs(u * u - x * x))
359 return int(math.floor((2.0 * math.pi) /
abs(math.atan2(y, x))))
366 """Base class for regions on the unit sphere. 371 class SphericalBox(SphericalRegion):
372 """A spherical coordinate space bounding box. 374 This is similar to a bounding box in cartesian space in that 375 it is specified by a pair of points; however, a spherical box may 376 correspond to the entire unit-sphere, a spherical cap, a lune or 377 the traditional rectangle. Additionally, spherical boxes can span 378 the 0/360 degree longitude angle discontinuity. 380 Note that points falling exactly on spherical box edges are 381 considered to be inside (contained by) the box. 386 Creates a new spherical box. If no arguments are supplied, then 387 an empty box is created. If the arguments consist of a single 388 SphericalRegion, then a copy of its bounding box is created. 389 Otherwise, the arguments must consist of a pair of 2 (spherical) 390 or 3 (cartesian 3-vector) element coordinate tuples/lists that 391 specify the minimum/maximum longitude/latitude angles for the box. 392 Latitude angles must be within [-90, 90] degrees, and the minimum 393 latitude angle must be less than or equal to the maximum. If both 394 minimum and maximum longitude angles lie in the range [0.0, 360.0], 395 then the maximum can be less than the minimum. For example, a box 396 with min/max longitude angles of 350/10 deg spans the longitude angle 397 ranges [350, 360) and [0, 10]. Otherwise, the minimum must be less 398 than or equal to the maximum, though values can be arbitrary. If 399 the two are are separated by 360 degrees or more, then the box 400 spans longitude angles [0, 360). Otherwise, both values are range 401 reduced. For example, a spherical box with min/max longitude angles 402 specified as 350/370 deg spans longitude angle ranges [350, 360) and 409 if isinstance(args[0], SphericalRegion):
411 self.
min = tuple(bbox.getMin())
412 self.
max = tuple(bbox.getMax())
419 raise TypeError(
'Expecting a spherical region, 2 points, ' 420 'or a tuple/list containing 2 points')
421 if self.
min[1] > self.
max[1]:
423 'Latitude angle minimum is greater than maximum')
424 if (self.
max[0] < self.
min[0]
and 425 (self.
max[0] < 0.0
or self.
min[0] > 360.0)):
427 'Longitude angle minimum is greater than maximum')
429 if self.
max[0] - self.
min[0] >= 360.0:
430 self.
min = (0.0, self.
min[1])
431 self.
max = (360.0, self.
max[1])
437 """Returns True if this spherical box wraps across the 0/360 438 degree longitude angle discontinuity. 440 return self.
min[0] > self.
max[0]
443 """Returns a bounding box for this spherical region. 448 """Returns the minimum longitude and latitude angles of this 449 spherical box as a 2-tuple of floats (in units of degrees). 454 """Returns the maximum longitude and latitude angles of this 455 spherical box as a 2-tuple of floats (in units of degrees). 460 """Returns the extent in longitude angle of this box. 463 return 360.0 - self.
min[0] + self.
max[0]
465 return self.
max[0] - self.
min[0]
468 """Returns an 2-tuple of floats corresponding to the longitude/latitude 469 angles (in degrees) of the center of this spherical box. 471 centerTheta = 0.5 * (self.
min[0] + self.
max[0])
472 centerPhi = 0.5 * (self.
min[1] + self.
max[1])
474 if centerTheta >= 180.0:
478 return (centerTheta, centerPhi)
481 """Returns True if this spherical box contains no points. 483 return self.
min[1] > self.
max[1]
486 """Returns True if this spherical box contains every point 489 return self.
min == (0.0, -90.0)
and self.
max == (360.0, 90.0)
492 """Returns True if this spherical box contains the given point, 493 which must be specified in spherical coordinates. 495 if p[1] < self.
min[1]
or p[1] > self.
max[1]:
499 return theta >= self.
min[0]
or theta <= self.
max[0]
501 return theta >= self.
min[0]
and theta <= self.
max[0]
504 """Returns True if this spherical box completely contains the given 505 point or spherical region. Note that the implementation is 506 conservative where ellipses are concerned: False may be returned 507 for an ellipse that is actually completely contained in this box. 511 if isinstance(pointOrRegion, SphericalRegion):
512 b = pointOrRegion.getBoundingBox()
515 if b.min[1] < self.
min[1]
or b.max[1] > self.
max[1]:
519 return b.min[0] >= self.
min[0]
and b.max[0] <= self.
max[0]
521 return b.min[0] >= self.
min[0]
or b.max[0] <= self.
max[0]
524 return self.
min[0] == 0.0
and self.
max[0] == 360.0
526 return b.min[0] >= self.
min[0]
and b.max[0] <= self.
max[0]
531 """Returns True if this spherical box intersects the given point 532 or spherical region. Note that the implementation is conservative: 533 True may be returned for a region that does not actually intersect 538 if isinstance(pointOrRegion, SphericalBox):
542 if b.min[1] > self.
max[1]
or b.max[1] < self.
min[1]:
548 return b.min[0] <= self.
max[0]
or b.max[0] >= self.
min[0]
551 return self.
min[0] <= b.max[0]
or self.
max[0] >= b.min[0]
553 return self.
min[0] <= b.max[0]
and self.
max[0] >= b.min[0]
554 elif isinstance(pointOrRegion, SphericalRegion):
555 return pointOrRegion.intersects(self)
560 """Extends this box to the smallest spherical box S containing 561 the union of this box with the specified point or spherical region. 563 if self == pointOrRegion:
565 if isinstance(pointOrRegion, SphericalRegion):
566 b = pointOrRegion.getBoundingBox()
570 self.
min = tuple(b.min)
571 self.
max = tuple(b.max)
572 minPhi =
min(self.
min[1], b.min[1])
573 maxPhi =
max(self.
max[1], b.max[1])
574 minTheta = self.
min[0]
575 maxTheta = self.
max[0]
578 minMinRa =
min(self.
min[0], b.min[0])
579 maxMaxRa =
max(self.
max[0], b.max[0])
580 if maxMaxRa >= minMinRa:
587 if b.min[0] <= self.
max[0]
and b.max[0] >= self.
min[0]:
590 elif b.min[0] - self.
max[0] > self.
min[0] - b.max[0]:
596 if self.
min[0] <= b.max[0]
and self.
max[0] >= b.min[0]:
599 elif self.
min[0] - b.max[0] > b.min[0] - self.
max[0]:
604 if b.min[0] > self.
max[0]:
605 if (360.0 - b.min[0] + self.
max[0] <
606 b.max[0] - self.
min[0]):
610 elif self.
min[0] > b.max[0]:
611 if (360.0 - self.
min[0] + b.max[0] <
612 self.
max[0] - b.min[0]):
617 minTheta =
min(self.
min[0], b.min[0])
618 maxTheta =
max(self.
max[0], b.max[0])
619 self.
min = (minTheta, minPhi)
620 self.
max = (maxTheta, maxPhi)
627 self.
min = (theta, phi)
628 self.
max = (theta, phi)
630 minPhi =
min(self.
min[1], phi)
631 maxPhi =
max(self.
max[1], phi)
632 minTheta = self.
min[0]
633 maxTheta = self.
max[0]
635 if self.
min[0] - theta > theta - self.
max[0]:
639 elif theta < self.
min[0]:
640 if self.
min[0] - theta <= 360.0 - self.
max[0] + theta:
644 elif theta - self.
max[0] <= 360.0 - theta + self.
min[0]:
648 self.
min = (minTheta, minPhi)
649 self.
max = (maxTheta, maxPhi)
653 """Shrinks this box to the smallest spherical box containing 654 the intersection of this box and the specified one. 657 if not isinstance(b, SphericalBox):
658 raise TypeError(
'Expecting a SphericalBox object')
659 if self == b
or self.
isEmpty():
663 minPhi =
max(self.
min[1], b.min[1])
664 maxPhi =
min(self.
max[1], b.max[1])
665 minTheta = self.
min[0]
666 maxTheta = self.
max[0]
669 minTheta =
max(minTheta, b.min[0])
670 maxTheta =
min(maxTheta, b.max[0])
672 if b.max[0] >= minTheta:
673 if b.min[0] <= maxTheta:
674 if b.max[0] - b.min[0] <= 360.0 - minTheta + maxTheta:
678 minTheta =
max(minTheta, b.min[0])
680 elif b.min[0] <= maxTheta:
682 maxTheta =
min(maxTheta, b.max[0])
688 if maxTheta >= b.min[0]:
689 if minTheta <= b.max[0]:
690 if maxTheta - minTheta > 360.0 - b.min[0] + b.max[0]:
694 minTheta =
max(minTheta, b.min[0])
695 elif minTheta <= b.max[0]:
700 elif minTheta > b.max[0]
or maxTheta < b.min[0]:
704 minTheta =
max(minTheta, b.min[0])
705 maxTheta =
min(maxTheta, b.max[0])
706 self.
min = (minTheta, minPhi)
707 self.
max = (maxTheta, maxPhi)
711 """Empties this spherical box. 713 self.
min = (0.0, 90.0)
714 self.
max = (0.0, -90.0)
718 """Expands this spherical box to fill the unit sphere. 720 self.
min = (0.0, -90.0)
721 self.
max = (360.0, 90.0)
725 """Returns a string representation of this spherical box. 728 return ''.join([self.__class__.__name__,
'(',
')'])
729 return ''.join([self.__class__.__name__,
'(',
730 repr(self.
min),
', ', repr(self.
max),
')'])
733 if isinstance(other, SphericalBox):
734 if self.
isEmpty()
and other.isEmpty():
736 return self.
min == other.min
and self.
max == other.max
740 return hash((self.
min, self.
max))
744 """Returns a spherical bounding box for the great circle edge 745 connecting v1 to v2 with plane normal n. All arguments must be 746 cartesian unit vectors. 751 minPhi =
min(phi1, phi2)
752 maxPhi =
max(phi1, phi2)
753 d = n[0] * n[0] + n[1] * n[1]
754 if abs(d) > MIN_FLOAT:
756 if abs(n[2]) <= SIN_MIN:
757 ex = (0.0, 0.0, -1.0)
759 ex = (n[0] * n[2] / d, n[1] * n[2] / d, -d)
763 ex = (-ex[0], -ex[1], -ex[2])
767 if abs(n[2]) <= SIN_MIN:
769 d =
min(
abs(theta1 - theta2),
abs(360.0 - theta1 + theta2))
770 if d >= 90.0
and d <= 270.0:
776 minTheta =
min(theta1, theta2)
777 maxTheta =
max(theta1, theta2)
778 if maxTheta - minTheta > 180.0:
791 return SphericalBox((minTheta, minPhi), (maxTheta, maxPhi))
795 """A circle on the unit sphere. Points falling exactly on the 796 circle are considered to be inside (contained by) the circle. 800 """Creates a new spherical circle with the given center and radius. 807 'Circle radius is negative or greater than 180 deg')
811 """Returns a bounding box for this spherical circle. 823 if alpha > 180.0 - ANGLE_EPSILON:
827 minTheta = self.
center[0] - alpha
828 maxTheta = self.
center[0] + alpha
837 """Returns an (ra, dec) 2-tuple of floats corresponding to the 838 center of this circle. 843 """Returns the radius (degrees) of this circle. 848 """Returns True if this circle contains no points. 853 """Returns True if this spherical box contains every point 856 return self.
radius >= 180.0
859 """Returns True if the specified point or spherical region is 860 completely contained in this circle. Note that the implementation 861 is conservative where ellipses are concerned: False may be returned 862 for an ellipse that is actually completely contained in this circle. 869 if isinstance(pr, SphericalBox):
879 a =
alpha(r, c[1], minp[1])
881 if (pr.containsPoint((c[0] + a, minp[1]))
or 882 pr.containsPoint((c[0] - a, minp[1]))):
884 a =
alpha(r, c[1], maxp[1])
886 if (pr.containsPoint((c[0] + a, maxp[1]))
or 887 pr.containsPoint((c[0] - a, maxp[1]))):
890 elif isinstance(pr, SphericalCircle):
894 elif isinstance(pr, SphericalEllipse):
895 bc = pr.getBoundingCircle()
897 elif isinstance(pr, SphericalConvexPolygon):
899 for v
in pr.getVertices():
907 """Returns True if the given point or spherical region intersects 908 this circle. Note that the implementation is conservative where 909 ellipses are concerned: True may be returned for an ellipse that 910 is actually disjoint from this circle. 917 if isinstance(pr, SphericalBox):
920 elif pr.containsPoint(c):
930 elif isinstance(pr, SphericalCircle):
934 elif isinstance(pr, SphericalEllipse):
935 bc = pr.getBoundingCircle()
937 elif isinstance(pr, SphericalConvexPolygon):
938 return pr.intersects(self)
943 """Returns a string representation of this circle. 945 return ''.join([self.__class__.__name__,
'(', repr(self.
center),
946 ', ', repr(self.
radius),
')'])
949 if isinstance(other, SphericalCircle):
950 if self.
isEmpty()
and other.isEmpty():
952 if self.
radius == other.radius:
953 if self.
center[1] == other.center[1]:
956 return self.
center[0] == other.center[0]
963 """An ellipse on the unit sphere. This is a standard 2D cartesian 964 ellipse defined on the plane tangent to the unit sphere at the ellipse 965 center and then orthographically projected onto the surface of the 970 semiMajorAxisLength, semiMinorAxisLength, majorAxisAngle):
974 a = math.fmod(
float(majorAxisAngle), 180.0)
981 raise RuntimeError(
'Negative semi-minor axis length')
984 'Semi-major axis length is less than semi-minor axis length')
988 'Semi-major axis length must be less than or equal to 10 deg')
992 """Returns a bounding box for this spherical ellipse. Note that at 993 present this is conservative: a bounding box for the circle C with 994 radius equal to the semi-major axis length of this ellipse is returned. 999 """Returns a bounding circle for this spherical ellipse. This is 1000 a circle with the same center as this ellipse and with radius 1001 equal to the arcsine of the semi-major axis length. 1004 r = math.degrees(math.asin(math.radians(
1010 """Returns the circle of maximum radius having the same center as 1011 this ellipse and which is completely contained in the ellipse. 1014 r = math.degrees(math.asin(math.radians(
1020 """Returns an (ra, dec) 2-tuple of floats corresponding to the center 1026 """Return the major axis angle (east of north, in degrees) for this 1032 """Returns the semi-major axis length of this ellipse. Units 1033 are in arcsec since ellipses are typically small. 1038 """Returns the semi-minor axis length of this ellipse. Units 1039 are in arcsec since ellipses are typically small. 1043 def _containsPoint(self, v):
1044 theta = math.radians(self.
center[0])
1045 phi = math.radians(self.
center[1])
1047 sinTheta = math.sin(theta)
1048 cosTheta = math.cos(theta)
1049 sinPhi = math.sin(phi)
1050 cosPhi = math.cos(phi)
1051 sinAng = math.sin(ang)
1052 cosAng = math.cos(ang)
1054 n = cosPhi * v[2] - sinPhi * (sinTheta * v[1] + cosTheta * v[0])
1055 e = cosTheta * v[1] - sinTheta * v[0]
1057 x = sinAng * e + cosAng * n
1058 y = cosAng * e - sinAng * n
1063 return x * x + y * y <= 1.0
1066 """Returns True if the specified point or spherical region is 1067 completely contained in this ellipse. The implementation is 1068 conservative in the sense that False may be returned for a region 1069 that really is contained by this ellipse. 1071 if isinstance(pointOrRegion, (tuple, list)):
1078 """Returns True if the specified point or spherical region intersects 1079 this ellipse. The implementation is conservative in the sense that 1080 True may be returned for a region that does not intersect this 1083 if isinstance(pointOrRegion, (tuple, list)):
1090 """Returns a string representation of this ellipse. 1093 self.__class__.__name__,
'(', repr(self.
center),
', ',
1099 if isinstance(other, SphericalEllipse):
1100 return (self.
center == other.center
and 1111 """A convex polygon on the unit sphere with great circle edges. Points 1112 falling exactly on polygon edges are considered to be inside (contained 1117 """Creates a new polygon. Arguments must be either: 1119 1. a SphericalConvexPolygon 1120 2. a list of vertices 1121 3. a list of vertices and a list of corresponding edges 1123 In the first case, a copy is constructed. In the second case, 1124 the argument must be a sequence of 3 element tuples/lists 1125 (unit cartesian 3-vectors) - a copy of the vertices is stored 1126 and polygon edges are computed. In the last case, copies of the 1127 input vertex and edge lists are stored. 1129 Vertices must be hemispherical and in counter-clockwise order when 1130 viewed from outside the unit sphere in a right handed coordinate 1131 system. They must also form a convex polygon. 1133 When edges are specified, the i-th edge must correspond to the plane 1134 equation of great circle connecting vertices (i - 1, i), that is, 1135 the edge should be a unit cartesian 3-vector parallel to v[i-1] ^ v[i] 1136 (where ^ denotes the vector cross product). 1138 Note that these conditions are NOT verified for performance reasons. 1139 Operations on SphericalConvexPolygon objects constructed with inputs 1140 not satisfying these conditions are undefined. Use the convex() 1141 function to check for convexity and ordering of the vertices. Or, 1142 use the convexHull() function to create a SphericalConvexPolygon 1143 from an arbitrary list of hemispherical input vertices. 1148 raise RuntimeError(
'Expecting at least one argument')
1149 elif len(args) == 1:
1150 if isinstance(args[0], SphericalConvexPolygon):
1156 elif len(args) == 2:
1158 self.
edges = tuple(args[1])
1161 'number of edges does not match number of vertices')
1167 'spherical polygon must contain at least 3 vertices')
1169 def _computeEdges(self):
1170 """Computes edge plane normals from vertices. 1177 self.
edges = tuple(edges)
1180 """Returns the list of polygon vertices. 1185 """Returns the list of polygon edges. The i-th edge is the plane 1186 equation for the great circle edge formed by vertices i-1 and i. 1191 """Returns a bounding circle (not necessarily minimal) for this 1192 spherical convex polygon. 1203 """Returns a bounding box for this spherical convex polygon. 1207 for i
in range(0, len(self.
vertices)):
1213 """Returns the z coordinate range spanned by this spherical 1217 return (math.sin(math.radians(bbox.getMin()[1])),
1218 math.sin(math.radians(bbox.getMax()[1])))
1221 """Returns the polygon corresponding to the intersection of this 1222 polygon with the positive half space defined by the given plane. 1223 The plane must be specified with a cartesian unit vector (its 1224 normal) and always passes through the origin. 1226 Clipping is performed using the Sutherland-Hodgman algorithm, 1227 adapted for spherical polygons. 1229 vertices, edges, classification = [], [], []
1230 vin, vout =
False,
False 1231 for i
in range(len(self.
vertices)):
1239 classification.append(d)
1240 if not vin
and not vout:
1248 d0 = classification[-1]
1249 for i
in range(len(self.
vertices)):
1251 d1 = classification[i]
1257 edges.append(self.
edges[i])
1261 v =
normalize((v0[0] + (v1[0] - v0[0]) * f,
1262 v0[1] + (v1[1] - v0[1]) * f,
1263 v0[2] + (v1[2] - v0[2]) * f))
1265 edges.append(self.
edges[i])
1271 edges.append(self.
edges[i])
1284 v =
normalize((v0[0] + (v1[0] - v0[0]) * f,
1285 v0[1] + (v1[1] - v0[1]) * f,
1286 v0[2] + (v1[2] - v0[2]) * f))
1288 edges.append(tuple(plane))
1290 edges.append(self.
edges[i])
1295 edges.append(tuple(plane))
1302 """Returns the intersection of poly with this polygon, or 1303 None if the intersection does not exist. 1305 if not isinstance(poly, SphericalConvexPolygon):
1306 raise TypeError(
'Expecting a SphericalConvexPolygon object')
1308 for edge
in poly.getEdges():
1315 """Returns the area of this spherical convex polygon. 1319 for i
in range(numVerts):
1321 sina = math.sqrt(
dot(tmp, tmp))
1323 a = math.atan2(sina, cosa)
1325 return angleSum - (numVerts - 2) * math.pi
1328 for e
in self.
edges:
1334 """Returns True if the specified point or spherical region is 1335 completely contained in this polygon. Note that the implementation 1336 is conservative where ellipses are concerned: False may be returned 1337 for an ellipse that is actually completely contained by this polygon. 1340 if isinstance(pr, SphericalConvexPolygon):
1341 for v
in pr.getVertices():
1345 elif isinstance(pr, SphericalEllipse):
1346 return self.
contains(pr.getBoundingCircle())
1347 elif isinstance(pr, SphericalCircle):
1353 for i
in range(len(self.
vertices)):
1356 minSep =
min(minSep, s)
1357 return minSep >= pr.getRadius()
1358 elif isinstance(pr, SphericalBox):
1360 bMin, bMax = pr.getMin(), pr.getMax()
1361 bzMin = math.sin(math.radians(bMin[1]))
1362 bzMax = math.sin(math.radians(bMax[1]))
1363 verts = map(cartesianUnitVector,
1364 (bMin, bMax, (bMin[0], bMax[1]), (bMax[0], bMin[1])))
1370 for i
in range(len(self.
vertices)):
1374 d = e[0] * e[0] + e[1] * e[1]
1375 if abs(e[2]) >= COS_MAX
or d < MIN_FLOAT:
1380 D = d - bzMin * bzMin
1384 xr = -e[0] * e[2] * bzMin
1385 yr = -e[1] * e[2] * bzMin
1386 i1 = ((xr + e[1] * D) / d, (yr - e[0] * D) / d, bzMin)
1387 i2 = ((xr - e[1] * D) / d, (yr + e[0] * D) / d, bzMin)
1391 D = d - bzMax * bzMax
1395 xr = -e[0] * e[2] * bzMax
1396 yr = -e[1] * e[2] * bzMax
1397 i1 = ((xr + e[1] * D) / d, (yr - e[0] * D) / d, bzMax)
1398 i2 = ((xr - e[1] * D) / d, (yr + e[0] * D) / d, bzMax)
1407 """Returns True if the given point or spherical region intersects 1408 this polygon. Note that the implementation is conservative where 1409 ellipses are concerned: True may be returned for an ellipse that 1410 is actually disjoint from this polygon. 1413 if isinstance(pr, SphericalConvexPolygon):
1415 elif isinstance(pr, SphericalEllipse):
1416 return self.
intersects(pr.getBoundingCircle())
1417 elif isinstance(pr, SphericalCircle):
1423 for i
in range(len(self.
vertices)):
1426 minSep =
min(minSep, s)
1427 return minSep <= pr.getRadius()
1428 elif isinstance(pr, SphericalBox):
1429 minTheta = math.radians(pr.getMin()[0])
1430 maxTheta = math.radians(pr.getMax()[0])
1431 bzMin = math.sin(math.radians(pr.getMin()[1]))
1432 bzMax = math.sin(math.radians(pr.getMax()[1]))
1433 p = self.
clip((-math.sin(minTheta), math.cos(minTheta), 0.0))
1434 if pr.getThetaExtent() > 180.0:
1436 zMin, zMax = p.getZRange()
1437 if zMin <= bzMax
and zMax >= bzMin:
1439 p = self.
clip((math.sin(maxTheta), -math.cos(maxTheta), 0.0))
1442 p = p.clip((math.sin(maxTheta), -math.cos(maxTheta), 0.0))
1445 zMin, zMax = p.getZRange()
1446 return zMin <= bzMax
and zMax >= bzMin
1451 """Returns a string representation of this polygon. 1453 return ''.join([self.__class__.__name__,
'(',
1454 repr(map(sphericalCoords, self.
vertices)),
')'])
1457 if not isinstance(other, SphericalConvexPolygon):
1460 if n != len(other.vertices):
1464 offset = other.vertices.index(self.
vertices[0])
1467 for i
in range(0, n):
1484 def _partition(array, left, right, i):
1485 """Partitions an array around the pivot value at index i, 1486 returning the new index of the pivot value. 1489 array[i] = array[right - 1]
1491 for k
in range(left, right - 1):
1492 if array[k] < pivot:
1497 array[right - 1] = array[j]
1502 def _medianOfN(array, i, n):
1503 """Finds the median of n consecutive elements in an array starting 1504 at index i (efficient for small n). The index of the median element 1512 for j
in range(i, e1):
1515 for s
in range(j + 1, e2):
1516 if array[s] < minValue:
1520 array[j] = array[minIndex]
1521 array[minIndex] = tmp
1525 def _medianOfMedians(array, left, right):
1526 """Returns the "median of medians" for an array. This primitive is used 1527 for pivot selection in the median finding algorithm. 1530 if right - left <= 5:
1531 return _medianOfN(array, left, right - left)
1534 while i + 4 < right:
1535 k = _medianOfN(array, i, 5)
1545 """Finds the median element of the given array in linear time. 1553 i = _medianOfMedians(array, left, right)
1554 i = _partition(array, left, right, i)
1573 def _prune(constraints, xmin, xmax, op):
1574 """Removes redundant constraints and reports intersection points 1575 of non-redundant pairs. The constraint list is modified in place. 1579 while i < len(constraints) - 1:
1580 a1, b1 = constraints[i]
1581 a2, b2 = constraints[i + 1]
1585 if abs(da) < MIN_FLOAT / EPSILON:
1589 xierr = 2.0 * EPSILON *
abs(xi)
1592 constraints[i + 1] = constraints[-1]
1594 constraints[i] = constraints[-1]
1597 if xi + xierr <= xmin:
1599 constraints[i + 1] = constraints[-1]
1601 constraints[i] = constraints[-1]
1603 elif xi - xierr >= xmax:
1605 constraints[i] = constraints[-1]
1607 constraints[i + 1] = constraints[-1]
1611 intersections.append((xi, xierr))
1613 return intersections
1616 def _vrange(x, xerr, a, b):
1619 verr = EPSILON *
abs(p) + EPSILON *
abs(v)
1624 verr = EPSILON *
abs(p) + EPSILON *
abs(v)
1625 vmin =
min(vmin, v - verr)
1626 vmax =
max(vmax, v + verr)
1630 def _gh(constraints, x, xerr, op):
1631 a, b = constraints[0]
1633 vmin, vmax = _vrange(x, xerr, a, b)
1634 for i
in range(1, len(constraints)):
1635 a, b = constraints[i]
1636 vimin, vimax = _vrange(x, xerr, a, b)
1637 if vimax < vmin
or vimin > vmax:
1646 return vmin, vmax, amin, amax
1649 def _feasible2D(points, z):
1657 if abs(v[1]) <= MIN_FLOAT:
1658 if abs(v[0]) <= MIN_FLOAT:
1664 xlim = - z * v[2] / v[0]
1666 xmin =
max(xmin, xlim)
1668 xmax =
min(xmax, xlim)
1674 coeffs = (v[0] / v[1], - z * v[2] / v[1])
1681 if len(I1) == 0
or len(I2) == 0:
1687 intersections = _prune(I1, xmin, xmax, operator.gt)
1688 intersections.extend(_prune(I2, xmin, xmax, operator.lt))
1689 if len(intersections) == 0:
1696 xi = (b2 - b1) / (a1 - a2)
1699 return (xi > xmin
or a1 < a2)
and (xi < xmax
or a1 > a2)
1700 elif numConstraints == len(I1) + len(I2):
1705 numConstraints = len(I1) + len(I2)
1706 x, xerr =
median(intersections)
1709 gmin, gmax, sg, Sg = _gh(I1, x, xerr, operator.gt)
1710 hmin, hmax, sh, Sh = _gh(I2, x, xerr, operator.lt)
1721 def _feasible1D(points, y):
1725 if abs(v[0]) <= MIN_FLOAT:
1730 xlim = - y * v[1] / v[0]
1732 xmin =
max(xmin, xlim)
1734 xmax =
min(xmax, xlim)
1741 """Tests whether a set of points is hemispherical, i.e. whether a plane 1742 exists such that all points are strictly on one side of the plane. The 1743 algorithm used is Megiddo's algorithm for linear programming in R2 and 1744 has run-time O(n), where n is the number of points. Points must be passed 1745 in as a list of cartesian unit vectors. 1752 if _feasible2D(points, 1.0):
1754 if _feasible2D(points, -1.0):
1758 if _feasible1D(points, 1.0):
1760 if _feasible1D(points, -1.0):
1783 """Tests whether an ordered list of vertices (which must be specified 1784 as cartesian unit vectors) form a spherical convex polygon and determines 1785 their winding order. Returns a 2-tuple as follows: 1787 (True, True): The vertex list forms a spherical convex polygon and is in 1788 counter-clockwise order when viewed from outside the unit 1789 sphere in a right handed coordinate system. 1790 (True, False): The vertex list forms a spherical convex polygon and is in 1792 (False, msg): The vertex list does not form a spherical convex polygon - 1793 msg is a string describing why the test failed. 1795 The algorithm completes in O(n) time, where n is the number of 1798 if len(vertices) < 3:
1799 return (
False,
'3 or more vertices must be specified')
1801 return (
False,
'vertices are not hemispherical')
1805 counterClockwise =
False 1806 p1 =
cross(center, vertices[-1])
1808 if abs(n2) < CROSS_N2MIN:
1809 return (
False,
'centroid of vertices is too close to a vertex')
1810 for i
in range(len(vertices)):
1811 beg = vertices[i - 2]
1812 mid = vertices[i - 1]
1814 plane =
cross(mid, end)
1815 n2 =
dot(plane, plane)
1816 if dot(mid, end) >= COS_MAX
or n2 < CROSS_N2MIN:
1817 return (
False,
'vertex list contains [near-]duplicates')
1818 plane =
invScale(plane, math.sqrt(n2))
1822 return (
False,
'vertices wind around centroid in both ' +
1823 'clockwise and counter-clockwise order')
1824 counterClockwise =
True 1826 if counterClockwise:
1827 return (
False,
'vertices wind around centroid in both ' +
1828 'clockwise and counter-clockwise order')
1833 d =
dot(plane, center)
1834 if d < SIN_MIN
and counterClockwise
or d > -SIN_MIN
and clockwise:
1835 return (
False,
'centroid of vertices is not conclusively ' +
1838 p2 =
cross(center, end)
1840 if abs(n2) < CROSS_N2MIN:
1841 return (
False,
'centroid of vertices is too close to a vertex')
1846 m = math.floor(windingAngle / 360.0)
1847 if m == 0.0
and windingAngle > 180.0
or m == 1.0
and windingAngle < 540.0:
1848 return (
True, counterClockwise)
1849 return (
False,
'vertices do not completely wind around centroid, or ' +
1850 'wind around it multiple times')
1854 """Computes the convex hull (a spherical convex polygon) of an unordered 1855 list of points on the unit sphere, which must be passed in as cartesian 1856 unit vectors. The algorithm takes O(n log n) time, where n is the number 1867 for i
in range(len(points)):
1872 anchor = points[extremum]
1873 refPlane =
cross(center, anchor)
1874 n2 =
dot(refPlane, refPlane)
1875 if n2 < CROSS_N2MIN:
1878 refPlane =
invScale(refPlane, math.sqrt(n2))
1880 av = [(0.0, anchor)]
1881 for i
in range(0, len(points)):
1885 plane =
cross(center, v)
1886 n2 =
dot(plane, plane)
1887 if n2 >= CROSS_N2MIN:
1888 plane =
invScale(plane, math.sqrt(n2))
1889 p =
cross(refPlane, plane)
1890 sa = math.sqrt(
dot(p, p))
1891 if dot(p, center) < 0.0:
1893 angle = math.atan2(sa,
dot(refPlane, plane))
1895 angle += 2.0 * math.pi
1896 av.append((angle, v))
1899 av.sort(key=
lambda t: t[0])
1909 p =
cross(anchor, v)
1911 if dot(anchor, v) < COS_MAX
and n2 >= CROSS_N2MIN:
1944 p =
cross(anchor, v)
1946 if dot(anchor, v) < COS_MAX
and n2 >= CROSS_N2MIN:
1947 if dot(v, edge) > SIN_MIN:
1948 edges[0] =
invScale(p, math.sqrt(n2))
1963 """Base class for partitioning schemes. 1971 """Class that maintains a sub-list of a backing list in 1972 insertion order. Elements can be deleted in O(1) time. 1999 """Removes all elements E in the sub-list for which predicate(E) 2012 self.
active[next][1] = prev
2016 self.
active[prev][2] = next
2029 """Returns an iterator over all elements in the sub-list. 2041 """A simple partitioning scheme that breaks the unit sphere into fixed 2042 height latitude angle stripes. These are in turn broken up into fixed 2043 width longitude angle chunks (each stripe has a variable number of chunks 2044 to account for distortion at the poles). Chunks are in turn broken up 2045 into fixed height sub-stripes, and each sub-stripe is then divided into 2046 fixed width sub-chunks. Again, the number of sub-chunks per sub-stripe is 2047 variable to account for polar distortion. 2050 def __init__(self, numStripes, numSubStripesPerStripe):
2051 if (
not isinstance(numStripes, numbers.Integral)
or 2052 not isinstance(numSubStripesPerStripe, numbers.Integral)):
2053 raise TypeError(
'Number of stripes and sub-stripes per stripe ' +
2055 if numStripes < 1
or numSubStripesPerStripe < 1:
2056 raise RuntimeError(
'Number of stripes and sub-stripes per ' +
2057 'stripe must be positive')
2061 h = 180.0 / numStripes
2066 for i
in range(numStripes)]
2070 nc = self.
numChunks[i//numSubStripesPerStripe]
2071 n =
segments(i * hs - 90.0, (i + 1) * hs - 90.0, hs)//nc
2073 w = 360.0 / (n * nc)
2078 """Returns the sub-stripe number of the sub-stripe containing points 2079 with the given latitude angle. 2081 assert phi >= -90.0
and phi <= 90.0
2088 """Returns the stripe number of the stripe containing all points 2089 with the given latitude angle. 2095 """Returns the sub-chunk number of the sub-chunk containing all points 2096 in the given sub-stripe with the given longitude angle. 2098 assert subStripe >= 0
and theta >= 0.0
and theta <= 360.0
2107 """Returns the chunk number of the chunk containing all points 2108 in the given stripe with the given longitude angle. 2110 assert stripe >= 0
and theta >= 0.0
and theta <= 360.0
2115 def _getChunkBoundingBox(self, stripe, chunk):
2116 assert stripe >= 0
and stripe < self.
numStripes 2117 assert chunk >= 0
and chunk < self.
numChunks[stripe]
2127 if maxPhi >= 90.0 - ANGLE_EPSILON:
2130 maxPhi += ANGLE_EPSILON
2131 if minPhi > -90.0 + ANGLE_EPSILON:
2132 minPhi -= ANGLE_EPSILON
2136 minTheta =
max(0.0, sc * scw - ANGLE_EPSILON)
2137 maxTheta =
min((sc + nscpc) * scw + ANGLE_EPSILON, 360.0)
2140 box.min = (minTheta, minPhi)
2141 box.max = (maxTheta, maxPhi)
2145 """Returns a SphericalBox bounding the given chunk. Note that 2146 this is a bounding box only - not an exact representation! In 2147 particular, for a point p and a chunk C with bounding box B, 2148 B.contains(p) == True does not imply that p is assigned to C. 2149 However, for all points p assigned to C, B.contains(p) is True. 2155 def _getSubChunkBoundingBox(self, subStripe, subChunk):
2156 assert subStripe >= 0
2159 assert subChunk >= 0
and subChunk < nsc
2166 if maxPhi >= 90.0 - ANGLE_EPSILON:
2169 maxPhi += ANGLE_EPSILON
2170 if minPhi > -90.0 + ANGLE_EPSILON:
2171 minPhi -= ANGLE_EPSILON
2172 minTheta =
max(0.0, subChunk * scw - ANGLE_EPSILON)
2173 maxTheta =
min((subChunk + 1) * scw + ANGLE_EPSILON, 360.0)
2176 box.min = (minTheta, minPhi)
2177 box.max = (maxTheta, maxPhi)
2181 """Returns a SphericalBox bounding the given sub-chunk. Note that 2182 this is a bounding box only - not an exact representation! In 2183 particular, for a point p and a sub-chunk SC with bounding box B, 2184 B.contains(p) == True does not imply that p is assigned to SC. 2185 However, for all points p assigned to SC, B.contains(p) is True. 2196 """Returns the chunk ID of the chunk with the given 2197 stripe/chunk numbers. 2202 """Returns the sub-chunk ID of the sub-chunk with the given 2203 sub-stripe/sub-chunk numbers. 2209 def _allSubChunks(self, stripe, withEmptySet):
2210 assert stripe >= 0
and stripe < self.
numStripes 2216 for j
in range(nsc):
2217 yield (scId + j, emptySet)
2219 for j
in range(nsc):
2222 def _processChunk(self, minS, minC, c, cOverlap):
2224 while minC < c
and len(cOverlap) > 0:
2238 if theta >= 360.0 - ANGLE_EPSILON:
2239 theta = 360.0 + ANGLE_EPSILON
2241 theta -= ANGLE_EPSILON
2242 cOverlap.filter(
lambda br: br[0].getMax()[0] < theta)
2244 def _processStripe(self, minS, s, sOverlap):
2245 while minS < s
and len(sOverlap) > 0:
2247 sRegions =
list(sOverlap)
2248 sRegions.sort(key=
lambda x: x[0].getMin()[0])
2250 sRegions[0][0].getMin()[0] - ANGLE_EPSILON))
2253 for j
in range(1, len(sRegions)):
2255 sRegions[j][0].getMin()[0] - ANGLE_EPSILON))
2271 if phi >= 90.0 - ANGLE_EPSILON:
2272 phi = 90.0 + ANGLE_EPSILON
2274 phi -= ANGLE_EPSILON
2275 sOverlap.filter(
lambda br: br[0].getMax()[1] < phi)
2278 """Computes the intersection of a spherical box partitioning of the 2279 unit sphere and one or more SphericalRegions. Results are 2280 returned as an iterator over (chunkId, SubIterator) tuples. These 2281 correspond to all chunks overlapping at least one input region, 2282 and contain sub-iterators over all sub-chunks intersecting at least 2283 one input region. The sub-iterators return (subChunkId, Regions) 2284 tuples, where Regions is a set of the regions that intersect with 2285 (but do not completely contain) a particular sub-chunk. Note that 2286 Regions can be an empty set - this means that the sub-chunk 2287 is completely contained in at least one of the input regions. 2291 elif len(args) == 1
and not isinstance(args[0], SphericalRegion):
2294 if not all(isinstance(r, SphericalRegion)
for r
in args):
2296 'Input must consist of one or more SphericalRegion objects')
2300 b = r.getBoundingBox()
2303 bMin = (0.0, b.getMin()[1])
2304 bMax = (360.0, b.getMax()[1])
2311 regions.append((b2, r))
2313 regions.append((b, r))
2315 regions.sort(key=
lambda x: x[0].getMin()[1])
2317 max(-90.0, regions[0][0].getMin()[1] - ANGLE_EPSILON))
2321 for i
in range(1, len(regions)):
2323 max(-90.0, regions[i][0].getMin()[1] - ANGLE_EPSILON))
2335 def _processSubChunk(self, minSS, minSC, sc, scOverlap):
2336 while minSC < sc
and len(scOverlap) > 0:
2339 for br
in scOverlap:
2343 elif br[1].intersects(bbox):
2347 if partial
is not None:
2353 if theta >= 360.0 - ANGLE_EPSILON:
2354 theta = 360.0 + ANGLE_EPSILON
2356 theta -= ANGLE_EPSILON
2357 scOverlap.filter(
lambda br: br[0].getMax()[0] < theta)
2359 def _processSubStripe(self, minSS, ss, chunk, ssOverlap):
2360 while minSS < ss
and len(ssOverlap) > 0:
2362 ssRegions =
list(ssOverlap)
2364 ssRegions.sort(key=
lambda x: x[0].getMin()[0])
2366 ssRegions[0][0].getMin()[0] - ANGLE_EPSILON)))
2369 for j
in range(1, len(ssRegions)):
2371 ssRegions[j][0].getMin()[0] - ANGLE_EPSILON)))
2387 if phi >= 90.0 - ANGLE_EPSILON:
2388 phi = 90.0 + ANGLE_EPSILON
2390 phi -= ANGLE_EPSILON
2391 ssOverlap.filter(
lambda br: br[0].getMax()[1] < phi)
2393 def _subIntersect(self, stripe, chunk, regions):
2394 """Returns an iterator over (subChunkId, Regions) tuples, where 2395 Regions is a set of all regions that intersect with (but do not 2396 completely contain) a particular sub-chunk. 2399 regions.sort(key=
lambda x: x[0].getMin()[1])
2402 regions[0][0].getMin()[1] - ANGLE_EPSILON)))
2406 for i
in range(1, len(regions)):
2408 regions[i][0].getMin()[1] - ANGLE_EPSILON)))
2421 """Returns an iterator over (chunkId, SubIterator) tuples - one for 2422 each chunk in the partition map. Each SubIterator is an iterator over 2423 subChunkIds for the corresponding chunk. 2430 if isinstance(other, SphericalBoxPartitionMap):
2431 return (self.
numStripes == other.numStripes
and 2436 return ''.join([self.__class__.__name__,
'(', repr(self.
numStripes),
def filter(self, predicate)
def _subIntersect(self, stripe, chunk, regions)
def _processSubChunk(self, minSS, minSC, sc, scOverlap)
Angle abs(Angle const &a)
def getBoundingCircle(self)
def getMajorAxisAngle(self)
bool all(CoordinateExpr< N > const &expr)
Return true if all elements are true.
def hemispherical(points)
bool contains(VertexIterator const begin, VertexIterator const end, UnitVector3d const &v)
def _processSubStripe(self, minSS, ss, chunk, ssOverlap)
def _getChunkBoundingBox(self, stripe, chunk)
def maxAlpha(r, centerPhi)
def intersects(self, pointOrRegion)
def _processStripe(self, minS, s, sOverlap)
def contains(self, pointOrRegion)
def getSubChunkBoundingBox(self, chunkId, subChunkId)
def minThetaEdgeSep(p, theta, minPhi, maxPhi)
def getSemiMinorAxisLength(self)
def __init__(self, center, radius)
def __init__(self, center, semiMajorAxisLength, semiMinorAxisLength, majorAxisAngle)
def containsPoint(self, p)
def getChunkBoundingBox(self, chunkId)
def minPhiEdgeSep(p, phi, minTheta, maxTheta)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def getSemiMajorAxisLength(self)
def containsPoint(self, v)
daf::base::PropertySet * set
def contains(self, pointOrRegion)
def _allSubChunks(self, stripe, withEmptySet)
def intersect(self, poly)
def _containsPoint(self, v)
bool any(CoordinateExpr< N > const &expr)
Return true if any elements are true.
def sphericalCoords(args)
def getSubChunkId(self, subStripe, subChunk)
def extend(self, pointOrRegion)
def __init__(self, numStripes, numSubStripesPerStripe)
def intersects(self, pointOrRegion)
def contains(self, pointOrRegion)
def getChunk(self, stripe, theta)
def getSubStripe(self, phi)
def contains(self, pointOrRegion)
def getChunkId(self, stripe, chunk)
def getBoundingCircle(self)
def between(p, n, v1, v2)
def intersects(self, pointOrRegion)
def getBoundingCircle(self)
def minEdgeSep(p, n, v1, v2)
def alpha(r, centerPhi, phi)
def _getSubChunkBoundingBox(self, subStripe, subChunk)
def intersects(self, pointOrRegion)
def _processChunk(self, minS, minC, c, cOverlap)
def sphericalAngularSep(p1, p2)
def __init__(self, backingList)
def cartesianUnitVector(args)
def segments(phiMin, phiMax, width)
daf::base::PropertyList * list
def cartesianAngularSep(v1, v2)
def getSubChunk(self, subStripe, theta)
def intersect(self, args)