Coverage for pygeodesy/points.py : 89%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*-
specified as 2-d U{NumPy<https://www.NumPy.org>}, C{arrays} or tuples as C{LatLon} or as C{pseudo-x/-y} pairs.
C{NumPy} arrays are assumed to contain rows of points with a lat-, a longitude -and possibly other- values in different columns. While iterating over the array rows, create an instance of a given C{LatLon} class "on-the-fly" for each row with the row's lat- and longitude.
The original C{NumPy} array is read-accessed only and never duplicated, except to create a I{subset} of the original array.
For example, to process a C{NumPy} array, wrap the array by instantiating class L{Numpy2LatLon} and specifying the column index for the lat- and longitude in each row. Then, pass the L{Numpy2LatLon} instance to any L{pygeodesy} function or method accepting a I{points} argument.
Similarly, class L{Tuple2LatLon} is used to instantiate a C{LatLon} for each 2+tuple in a list, tuple or sequence of such 2+tuples from the index for the lat- and longitude index in each 2+tuple.
@newfield example: Example, Examples '''
_IsNotError, map1, scalar LatLon2Tuple, NearestOn3Tuple, NearestOn5Tuple, \ PhiLam2Tuple, Point3Tuple, Shape2Tuple, \ nameof, _xnamed degrees2m, issequence, property_RO, \ unroll180, unrollPI, unStr, \ wrap90, wrap180, _TypeError
except ImportError: _Sequence = object # XXX or tuple
'''Low-overhead C{LatLon} class for L{Numpy2LatLon} and L{Tuple2LatLon}. ''' # __slots__ efficiency is voided if the __slots__ class attribute # is used in a subclass of a class with the traditional __dict__, # see <https://docs.Python.org/2/reference/datamodel.html#slots> # and __slots__ must be repeated in sub-classes, see "Problems # with __slots__" in Luciano Ramalho, "Fluent Python", page # 276+, O'Reilly, 2016, also at <https://Books.Google.ie/ # books?id=bIZHCgAAQBAJ&lpg=PP1&dq=fluent%20python&pg= # PT364#v=onepage&q=“Problems%20with%20__slots__”&f=false>
'''Creat a new, mininal, low-overhead L{LatLon_} instance, without heigth and datum.
@param lat: Latitude (C{degrees}). @param lon: Longitude (C{degrees}). @keyword name: Optional name (C{str}).
@note: The lat- and longitude are taken as-given, un-clipped and un-validated. '''
other.lat == self.lat and \ other.lon == self.lon
return not self.__eq__(other)
'''Instantiate this very class.
@param args: Optional, positional arguments. @keyword kwds: Optional, keyword arguments.
@return: New instance (C{self.__class__}). ''' if 'name' in kwds: return self.__class__(*args, **kwds) else: return self.__class__(name=self.name, *args, **kwds)
'''Make a shallow or deep copy of this instance.
@keyword deep: If C{True} make a deep, otherwise a shallow copy (C{bool}).
@return: The copy (C{This class} or subclass thereof). ''' return _xcopy(self, deep=deep)
'''Check this and an other instance for type compatiblility.
@param other: The other instance (any C{type}). @keyword name: Optional, name for other (C{str}).
@return: C{None}.
@raise TypeError: Incompatible B{C{other}} C{type}. ''' if not (isinstance(other, self.__class__) or (hasattr(other, 'lat') and hasattr(other, 'lon'))): raise TypeError('type(%s) mismatch: %s vs %s' % (name, classname(other), classname(self)))
'''DEPRECATED, use method C{points2}. ''' return points2(points, closed=closed, base=base)
return points2(points, closed=closed, base=base)
'''Return the lat- and longitude in C{radians}.
@return: A L{PhiLam2Tuple}C{(phi, lam)}. ''' return PhiLam2Tuple(radians(self.lat), radians(self.lon))
'''This L{LatLon_} as a string "<degrees>, <degrees>".
@keyword form: Optional format, F_D, F_DM, F_DMS for deg°, deg°min′, deg°min′sec″ (C{str}). @keyword prec: Optional number of decimal digits (0..8 or C{None}). @keyword sep: Optional separator to join (C{str}). @keyword kwds: Optional, keyword arguments.
@return: Instance (C{str}). ''' lonDMS(self.lon, form=form, prec=prec)] t += ['%+.2f' % (self.height,)] t += [repr(self.name)] t += ['%s=%s' % _ for _ in sorted(kwds.items())]
'''This L{LatLon_} as a string "class(<degrees>, ...)".
@keyword kwds: Optional, keyword arguments.
@return: Class instance (C{str}). '''
'''(INTERNAL) Base class. '''
'''(INTERNAL) Check for a matching point. '''
'''Make a shallow or deep copy of this instance.
@keyword deep: If C{True} make a deep, otherwise a shallow copy (C{bool}).
@return: The copy (C{This class} or subclass thereof). ''' return _xcopy(self, deep=deep)
'''(INTERNAL) Count the number of matching points. '''
'''(INTERNAL) Find the first matching point index. ''' return i
'''Must be overloaded. ''' raise NotImplementedError('method: %s' % ('_findall',))
'''(INTERNAL) Return point [index] or return a slice. ''' # Luciano Ramalho, "Fluent Python", page 290+, O'Reilly, 2016 # XXX an numpy.array slice is a view, not a copy else:
'''(INTERNAL) Find the first matching point index. ''' raise ValueError('%s not found: %r' % (self._itemname, point))
'''(INTERNAL) Yield all points. '''
'''Raise an error for a method or property not overloaded. ''' n = '%s %s.%s' % (self._notOverloaded.__name__, classname(self, prefixed=True), name) raise AssertionError(unStr(n, *args, **kwds))
'''(INTERNAL) Must be overloaded.
@param attrs: Optional arguments. ''' self._notOverloaded(self.point.__name__, *attrs)
'''(INTERNAL) Return the range. ''' else: raise ValueError('%s invalid: %r' % ('step', step))
'''(INTERNAL) Return a string representation. ''' # XXX use Python 3+ reprlib.repr
'''(INTERNAL) Yield all points in reverse order. ''' yield self.point(self._array[i])
'''(INTERNAL) Find the last matching point index. '''
'''(INTERNAL) Should be overloaded. ''' return {}
'''(INTERNAL) Check for near-zero values. '''
def epsilon(self): '''Get the tolerance for equality tests (C{float}). '''
def epsilon(self, tol): '''Set the tolerance for equality tests.
@param tol: New tolerance (C{scalar}).
@raise TypeError: Non-scalar B{C{tol}}.
@raise ValueError: Out-of-bounds B{C{tol}}. '''
def isNumpy2(self): '''Is this a Numpy2 wrapper? ''' return False # isinstance(self, (Numpy2LatLon, ...))
def isPoints2(self): '''Is this a LatLon2 wrapper/converter? ''' return False # isinstance(self, (LatLon2psxy, ...))
def isTuple2(self): '''Is this a Tuple2 wrapper? ''' return False # isinstance(self, (Tuple2LatLon, ...))
'''Base class for Numpy2LatLon or Tuple2LatLon. '''
'''Handle a C{NumPy} or C{Tuple} array as a sequence of C{LatLon} points. '''
raise IndexError('%s invalid: %r' % ('array shape', shape))
# check the point class if isclass(LatLon) and all(hasattr(LatLon, a) for a in LatLon_.__slots__): self._LatLon = LatLon else: raise _IsNotError('valid', LatLon=LatLon)
# check the attr indices raise _IsNotError(int.__name__, **{ai: i}) raise _IsNotError('valid', Error=ValueError, **{ai: i}) raise ValueError('%s == %s == %s' % (ai, aj, i))
'''Check for a specific lat-/longitude.
@param latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}).
@return: C{True} if B{C{latlon}} is present, C{False} otherwise.
@raise TypeError: Invalid B{C{latlon}}. '''
'''Return row[index] as C{LatLon} or return a L{Numpy2LatLon} slice. '''
'''Yield rows as C{LatLon}. '''
'''Return the number of rows. '''
'''Return a string representation. '''
'''Yield rows as C{LatLon} in reverse order. '''
'''Count the number of rows with a specific lat-/longitude.
@param latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}).
@return: Count (C{int}).
@raise TypeError: Invalid B{C{latlon}}. '''
'''Find the first row with a specific lat-/longitude.
@param latlon: Point (C{LatLon}) or 2-tuple (lat, lon). @param start_end: Optional C{[start[, end]]} index (integers).
@return: Index or -1 if not found (C{int}).
@raise TypeError: Invalid B{C{latlon}}. '''
'''(INTERNAL) Yield indices of all matching rows. ''' except (TypeError, ValueError): raise _IsNotError('valid', latlon=latlon)
row[self._ilon] - lon):
'''Yield indices of all rows with a specific lat-/longitude.
@param latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Indices (C{iterable}).
@raise TypeError: Invalid B{C{latlon}}. ''' return self._findall(latlon, start_end)
'''Find index of the first row with a specific lat-/longitude.
@param latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index (C{int}).
@raise TypeError: Invalid B{C{latlon}}.
@raise ValueError: Point not found. '''
def ilat(self): '''Get the latitudes column index (C{int}). ''' return self._ilat
def ilon(self): '''Get the longitudes column index (C{int}). ''' return self._ilon
# next = __iter__
'''Instantiate a point C{LatLon}.
@param row: Array row (numpy.array).
@return: Point (C{LatLon}). '''
'''Find the last row with a specific lat-/longitude.
@param latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @param start_end: Optional C{[start[, end]]} index (C{int}).
@note: Keyword order, first stop, then start.
@return: Index or -1 if not found (C{int}).
@raise TypeError: Invalid B{C{latlon}}. '''
'''(INTERNAL) Slice kwds. '''
def shape(self): '''Get the shape of the C{NumPy} array or the C{Tuples} as L{Shape2Tuple}C{(nrows, ncols)}. '''
'''Must be overloaded. ''' raise NotImplementedError('method: %s' % ('_subset',))
'''Return a subset of the C{NumPy} array.
@param indices: Row indices (C{range} or C{int}[]).
@note: A C{subset} is different from a C{slice} in 2 ways: (a) the C{subset} is typically specified as a list of (un-)ordered indices and (b) the C{subset} allocates a new, separate C{NumPy} array while a C{slice} is just an other C{view} of the original C{NumPy} array.
@return: Sub-array (C{numpy.array}).
@raise IndexError: Out-of-range B{C{indices}} value.
@raise TypeError: If B{C{indices}} is not a C{range} nor an C{int}[]. ''' # and range work properly to get Numpy array sub-sets raise _IsNotError('valid', indices=type(indices))
raise TypeError('%s[%s] not valid: %r' % ('indices', i, v)) raise IndexError('%s[%s] not valid: %r' % ('indices', i, v))
'''Wrapper for C{LatLon} points as "on-the-fly" pseudo-xy coordinates. '''
'''Handle C{LatLon} points as pseudo-xy coordinates.
@note: The C{LatLon} latitude is considered the I{pseudo-y} and longitude the I{pseudo-x} coordinate, likewise for L{LatLon2Tuple}. However, 2-tuples C{(x, y)} are considered as I{(longitude, latitude)}.
@param latlons: Points C{list}, C{sequence}, C{set}, C{tuple}, etc. (C{LatLon[]}). @keyword closed: Optionally, close the polygon (C{bool}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap lat- and longitudes (C{bool}).
@raise TypeError: Some B{C{latlons}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{latlons}}. '''
'''Check for a matching point.
@param xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}.
@return: C{True} if B{C{xy}} is present, C{False} otherwise.
@raise TypeError: Invalid B{C{xy}}. '''
'''Return the pseudo-xy or return a L{LatLon2psxy} slice. '''
'''Yield all pseudo-xy's. '''
'''Return the number of pseudo-xy's. '''
'''Return a string representation. '''
'''Yield all pseudo-xy's in reverse order. '''
'''Count the number of matching points.
@param xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}.
@return: Count (C{int}).
@raise TypeError: Invalid B{C{xy}}. '''
'''Find the first matching point.
@param xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index or -1 if not found (C{int}).
@raise TypeError: Invalid B{C{xy}}. '''
'''(INTERNAL) Yield indices of all matching points. '''
def _3xyll(ll): # match LatLon return ll.lon, ll.lat, ll
except (TypeError, ValueError): raise _IsNotError('valid', xy=xy)
'''Yield indices of all matching points.
@param xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Indices (C{iterator}).
@raise TypeError: Invalid B{C{xy}}. ''' return self._findall(xy, start_end)
'''Find the first matching point.
@param xy: Point (C{LatLon}) or 2-tuple (x, y) in (C{degrees}). @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index (C{int}).
@raise TypeError: Invalid B{C{xy}}.
@raise ValueError: Point not found. '''
def isPoints2(self): '''Is this a LatLon2 wrapper/converter? ''' return True # isinstance(self, (LatLon2psxy, ...))
# next = __iter__
'''Create a pseudo-xy.
@param ll: Point (C{LatLon}).
@return: An L{Point3Tuple}C{(x, y, ll)}. '''
'''Find the last matching point.
@param xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @param start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index or -1 if not found (C{int}).
@raise TypeError: Invalid B{C{xy}}. '''
'''(INTERNAL) Slice kwds. '''
'''Wrapper for C{NumPy} arrays as "on-the-fly" C{LatLon} points. ''' '''Handle a C{NumPy} array as a sequence of C{LatLon} points.
@param array: C{NumPy} array (C{numpy.array}). @keyword ilat: Optional index of the latitudes column (C{int}). @keyword ilon: Optional index of the longitudes column (C{int}). @keyword LatLon: Optional C{LatLon} (sub-)class to use (L{LatLon_}).
@raise IndexError: If B{C{array.shape}} is not (1+, 2+).
@raise TypeError: If B{C{array}} is not a C{NumPy} array or C{LatLon} is not a class with C{lat} and C{lon} attributes.
@raise ValueError: If the B{C{ilat}} and/or B{C{ilon}} values are the same or out of range.
@example:
>>> type(array) <type 'numpy.ndarray'> # <class ...> in Python 3+ >>> points = Numpy2LatLon(array, lat=0, lon=1) >>> simply = simplifyRDP(points, ...) >>> type(simply) <type 'numpy.ndarray'> # <class ...> in Python 3+ >>> sliced = points[1:-1] >>> type(sliced) <class '...Numpy2LatLon'> ''' except AttributeError: raise _IsNotError('NumPy', array=type(array))
LatLon=LatLon, shape=s)
def isNumpy2(self): '''Is this a Numpy2 wrapper? '''
'''Wrapper for tuple sequences as "on-the-fly" C{LatLon} points. ''' '''Handle a list of tuples, each containing a lat- and longitude and perhaps other values as a sequence of C{LatLon} points.
@param tuples: The C{list}, C{tuple} or C{sequence} of tuples (C{tuple}[]). @keyword ilat: Optional index of the latitudes value (C{int}). @keyword ilon: Optional index of the longitudes value (C{int}). @keyword LatLon: Optional C{LatLon} (sub-)class to use (L{LatLon_}).
@raise IndexError: If I{(len(B{C{tuples}}), min(len(t) for t in B{C{tuples}}))} is not (1+, 2+).
@raise TypeError: If B{C{tuples}} is not a C{list}, C{tuple} or C{sequence} or if B{C{LatLon}} is not a C{LatLon} with C{lat}, C{lon} and C{name} attributes.
@raise ValueError: If the B{C{ilat}} and/or B{C{ilon}} values are the same or out of range.
@example:
>>> tuples = [(0, 1), (2, 3), (4, 5)] >>> type(tuples) <type 'list'> # <class ...> in Python 3+ >>> points = Tuple2LatLon(tuples, lat=0, lon=1) >>> simply = simplifyRW(points, 0.5, ...) >>> type(simply) <type 'list'> # <class ...> in Python 3+ >>> simply [(0, 1), (4, 5)] >>> sliced = points[1:-1] >>> type(sliced) <class '...Tuple2LatLon'> >>> sliced ...Tuple2LatLon([(2, 3), ...][1], ilat=0, ilon=1)
>>> closest, _ = nearestOn2(LatLon_(2, 1), points, adjust=False) >>> closest LatLon_(lat=1.0, lon=2.0)
>>> closest, _ = nearestOn2(LatLon_(3, 2), points) >>> closest LatLon_(lat=2.001162, lon=3.001162) ''' LatLon=LatLon, shape=s)
def isTuple2(self): '''Is this a Tuple2 wrapper? ''' return True # isinstance(self, (Tuple2LatLon, ...))
# return the signed area in radians squared
# setting radius=1 converts degrees to radians
# approximate trapezoid by a rectangle, adjusting # the top width by the cosine of the latitudinal # average and bottom width by some fudge factor
'''Approximate the area of a polygon.
@param points: The polygon points (C{LatLon}[]). @keyword adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: Approximate area (C{meter}, same units as B{C{radius}}, I{squared}).
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@note: This area approximation has limited accuracy and is ill-suited for regions exceeding several hundred Km or Miles or with near-polar latitudes.
@see: L{sphericalNvector.areaOf}, L{sphericalTrigonometry.areaOf} and L{ellipsoidalKarney.areaOf}. '''
'''Determine the lower-left SW and upper-right NE corners of a path or polygon.
@param points: The path or polygon points (C{LatLon}[]). @keyword wrap: Wrap lat- and longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the C{bounds} corners (C{LatLon}) or C{None}.
@return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)} as B{C{LatLon}} or a L{Bounds4Tuple}C{(latS, lonW, latN, lonE)} if B{C{LatLon}} is C{None}.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@example:
>>> b = LatLon(45,1), LatLon(45,2), LatLon(46,2), LatLon(46,1) >>> boundsOf(b) # False >>> 45.0, 1.0, 46.0, 2.0 '''
Bounds2Tuple(LatLon(loy, lox), LatLon(hiy, hix)) # PYCHOK inconsistent
'''Determine the centroid of a polygon.
@param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the centroid (L{LatLon}) or C{None}.
@return: Centroid location (B{C{LatLon}}) or a L{LatLon2Tuple}C{(lat, lon)} if B{C{LatLon}} is C{None}.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}} or B{C{points}} enclose a pole or zero area.
@see: U{Centroid<https://WikiPedia.org/wiki/Centroid#Of_a_polygon>} and U{Calculating The Area And Centroid Of A Polygon <https://www.Seas.UPenn.edu/~sys502/extra_materials/ Polygon%20Area%20and%20Centroid.pdf>}. ''' # setting radius=1 converts degrees to radians
# XXX more elaborately: # t1, t2 = x1 * y2, -(x2 * y1) # A.fadd_(t1, t2) # X.fadd_(t1 * x1, t1 * x2, t2 * x1, t2 * x2) # Y.fadd_(t1 * y1, t1 * y2, t2 * y1, t2 * y2)
raise ValueError('polar or zero area: %r' % (pts,))
'''(INTERNAL) Return first and second index. '''
'''Determine the direction of a path or polygon.
@param points: The path or polygon points (C{LatLon}[]). @keyword adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{True} if B{C{points}} are clockwise, C{False} otherwise.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}} or the B{C{points}} enclose a pole or zero area.
@example:
>>> f = LatLon(45,1), LatLon(45,2), LatLon(46,2), LatLon(46,1) >>> isclockwise(f) # False >>> isclockwise(reversed(f)) # True ''' # <https://blog.Element84.com/determining-if-a-spherical-polygon-contains-a-pole.html> raise ValueError('polar or zero area: %r' % (pts,))
'''Determine whether a polygon is convex.
@param points: The polygon points (C{LatLon}[]). @keyword adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{True} if B{C{points}} are convex, C{False} otherwise.
@raise CrossError: Some B{C{points}} are colinear.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@example:
>>> t = LatLon(45,1), LatLon(46,1), LatLon(46,2) >>> isconvex(t) # True
>>> f = LatLon(45,1), LatLon(46,2), LatLon(45,2), LatLon(46,1) >>> isconvex(f) # False '''
'''Determine whether a polygon is convex and clockwise.
@param points: The polygon points (C{LatLon}[]). @keyword adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{+1} if B{C{points}} are convex clockwise, C{-1} for convex counter-clockwise B{C{points}}, C{0} otherwise.
@raise CrossError: Some B{C{points}} are colinear.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@example:
>>> t = LatLon(45,1), LatLon(46,1), LatLon(46,2) >>> isconvex_(t) # +1
>>> f = LatLon(45,1), LatLon(46,2), LatLon(45,2), LatLon(46,1) >>> isconvex_(f) # 0 ''' y = radians(y1 + y2) * 0.5 x21 *= cos(y) if abs(y) < PI_2 else 0
wrap if i < (n - 2) else False)
# get the sign of the distance from point # x3, y3 to the line from x1, y1 to x2, y2 # <https://WikiPedia.org/wiki/Distance_from_a_point_to_a_line>
return 0
elif c and fdot((x32, y1 - y2), y3 - y2, -x21) < 0: # colinear u-turn: x3, y3 not on the # opposite side of x2, y2 as x1, y1 raise CrossError('%s %s: %r' % ('colinear', 'point', ll))
'''Determine whether a point is enclosed by a polygon.
@param point: The point (C{LatLon} or 2-tuple C{(lat, lon)}). @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{True} if B{C{point}} is inside the polygon, C{False} otherwise.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}} or invalid B{C{point}}.
@see: L{sphericalNvector.LatLon.isenclosedBy}, L{sphericalTrigonometry.LatLon.isenclosedBy} and U{MultiDop GeogContainPt<https://GitHub.com/NASA/MultiDop>} (U{Shapiro et al. 2009, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' except (IndexError, TypeError, ValueError): raise ValueError('%s invalid: %r' % ('point', point))
x0, y0 = wrap180(x0), wrap90(y0)
def _dxy(x1, i): x2, y2, _ = pts[i] dx, x2 = unroll180(x1, x2, wrap=i < (n - 1)) return dx, x2, y2
else:
x += 360
# ignore duplicate and near-duplicate pts # determine if polygon edge (x1, y1)..(x2, y2) straddles # point (lat, lon) or is on boundary, but do not count # edges on boundary as more than one crossing
# An odd number of meridian crossings means, the polygon # contains a pole. Assume it is the pole on the hemisphere # containing the polygon mean point and if the polygon does # contain the North Pole, flip the result.
'''Check whether a polygon encloses a pole.
@param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap and unroll longitudes (C{bool}).
@return: C{True} if the polygon encloses a pole, C{False} otherwise.
@raise ValueError: Insufficient number of B{C{points}}.
@raise TypeError: Some B{C{points}} are not C{LatLon} or don't have C{bearingTo2}, C{initialBearingTo} and C{finalBearingTo} methods. '''
except AttributeError: raise _IsNotError('.bearingTo2', points=p1)
# sum of course deltas around pole is 0° rather than normally ±360° # <https://blog.Element84.com/determining-if-a-spherical-polygon-contains-a-pole.html>
# XXX fix (intermittant) edge crossing pole - eg (85,90), (85,0), (85,-90)
'''Locate the point on a path or polygon closest to an other point.
If the given point is within the extent of a polygon edge, the closest point is on that edge, otherwise the closest point is the nearest of that edge's end points.
Distances are approximated by function L{equirectangular_}, subject to the supplied B{C{options}}.
@param point: The other, reference point (C{LatLon}). @param points: The path or polygon points (C{LatLon}[]). @keyword closed: Optionally, close the path or polygon (C{bool}). @keyword wrap: Wrap and L{unroll180} longitudes and longitudinal delta (C{bool}) in function L{equirectangular_}. @keyword LatLon: Optional (sub-)class to return the closest point (L{LatLon}) or C{None}. @keyword options: Other keyword arguments for function L{equirectangular_}.
@return: A L{NearestOn3Tuple}C{(closest, distance, angle)} or a L{NearestOn5Tuple}C{(lat, lon, distance, angle, height)} if B{C{LatLon}} is C{None}.
@raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}}, see function L{equirectangular_}.
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@see: Function L{degrees2m} to convert C{degrees} to C{meter}. '''
# equirectangular_ returns a Distance4Tuple(distance # in degrees squared, delta lat, delta lon, p2.lon # unroll/wrap); the previous p2.lon unroll/wrap # is also applied to the next edge's p1.lon p2.lat, p2.lon, wrap=w, **options)
except AttributeError: return 0
# point (x, y) on axis rotated ccw by angle a: # x' = y * sin(a) + x * cos(a) # y' = y * cos(a) - x * sin(a) # # distance (w) along and perpendicular (h) to # a line thru point (dx, dy) and the origin: # w = (y * dy + x * dx) / hypot(dx, dy) # h = (y * dx - x * dy) / hypot(dx, dy) # # closest point on that line thru (dx, dy): # xc = dx * w / hypot(dx, dy) # yc = dy * w / hypot(dx, dy) # or # xc = dx * f # yc = dy * f # with # f = w / hypot(dx, dy) # or # f = (y * dy + x * dx) / (dx**2 + dy**2)
# iff wrapped, unroll lon1 (actually previous # lon2) like function unroll180/-PI would've # distance point to p1, y01 and x01 inverted # closest is between p1 and p2, use # original delta's, not y21 and x21 favg(p1.lon, p2.lon + u2, f=f), height=favg(_h(p1), _h(p2), f=f)) else: # p2 is closest
else: r = NearestOn3Tuple(LatLon(c.lat, c.lon + u, height=h), d, a)
'''Approximate the perimeter of a path or polygon.
@param points: The path or polygon points (C{LatLon}[]). @keyword closed: Optionally, close the path or polygon (C{bool}). @keyword adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: Approximate perimeter (C{meter}, same units as B{C{radius}}).
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Insufficient number of B{C{points}}.
@note: This perimeter is based on the L{equirectangular_} distance approximation and is ill-suited for regions exceeding several hundred Km or Miles or with near-polar latitudes.
@see: L{sphericalTrigonometry.perimeterOf} and L{ellipsoidalKarney.perimeterOf}. '''
# apply previous x2's unroll/wrap to new x1 adjust=adjust, limit=None, wrap=w)
# **) MIT License # # Copyright (C) 2016-2020 -- mrJean1 at Gmail -- All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. |