Coverage for pygeodesy/points.py : 94%

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 '''
property_doc_, property_RO, _Sequence, \ _xcopy, _xinstanceof, _xkwds _IsnotError, _TypeError, _ValueError, _xkwds_pop _datum_, _height_, _item_ps, _item_sq, _lat_, \ _lon_, _name_, _point_, _SPACE_, _UNDERSCORE_, \ _valid_, _x_, _y_, _0_0, _0_5, _1_0, _3_0, \ _90_0, _180_0, _360_0 notImplemented, notOverloaded, _Pass, \ _xnamed, _xother3, _xotherError LatLon2Tuple, NearestOn3Tuple, \ PhiLam2Tuple, Vector4Tuple Radius, Scalar_ unroll180, unrollPI, wrap90, wrap180
'''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.
@arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg name: Optional name (C{str}). @kwarg height: Optional height (C{float} or C{int}). @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple} or I{scalar} radius) or C{None}.
@raise TypeError: Invalid B{C{datum}}.
@note: The lat- and longitude are taken as-given, un-clipped and un-validated . ''' _spherical_datum(datum, name=self.name)
other.lat == self.lat and \ other.lon == self.lon
'''Instantiate this very class.
@arg args: Optional, positional arguments. @kwarg kwds: Optional, keyword arguments.
@return: New instance (C{self.__class__}). ''' else:
'''Make a shallow or deep copy of this instance.
@kwarg deep: If C{True} make a deep, otherwise a shallow copy (C{bool}).
@return: The copy (C{This class} or subclass thereof). '''
'''Return a string for the height B{C{height}}.
@kwarg prec: Optional number of decimals, unstripped (C{int}).
@see: Function L{hstr}. '''
'''Get the lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}). '''
'''Get the lat-, longitude and height (L{LatLon3Tuple}C{(lat, lon, height)}). '''
'''(INTERNAL) Get the minimal, low-overhead (C{nvectorBase._N_vector_}) '''
'''Refined class comparison.
@arg other: The other instance (L{any}). @kwarg name_other_up: Overriding C{name=other} and C{up=1} keyword arguments.
@return: The B{C{other}} if compatible.
@raise TypeError: Incompatible B{C{other}} C{type}. ''' and hasattr(other, _lon_)): raise _xotherError(self, other, name=name, up=up + 1)
'''Get the lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}). '''
'''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}). '''
def points(self, points, closed=False, base=None): # PYCHOK no cover '''DEPRECATED, use method C{points2}. ''' return points2(points, closed=closed, base=base)
'''Check a path or polygon represented by points.
@arg points: The path or polygon points (C{LatLon}[]) @kwarg closed: Optionally, consider the polygon closed, ignoring any duplicate or closing final B{C{points}} (C{bool}). @kwarg base: Optionally, check all B{C{points}} against this base class, if C{None} don't check.
@return: A L{Points2Tuple}C{(number, points)} with the number of points and the points C{list} or C{tuple}.
@raise PointsError: Insufficient number of B{C{points}}.
@raise TypeError: Some B{C{points}} are not B{C{base}}. '''
def to2ab(self): # PYCHOK no cover '''DEPRECATED, use property C{philam}.
@return: A L{PhiLam2Tuple}C{(phi, lam)}. ''' return self.philam
'''Convert this point to C{n-vector} (normal to the earth's surface) components, I{including height}.
@kwarg h: Optional height, overriding this point's height (C{meter}). @kwarg Nvector: Optional class to return the C{n-vector} components (C{Nvector}) or C{None}. @kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword arguments, ignored if B{C{Nvector=None}}.
@return: The C{n-vector} components B{C{Nvector}} or if B{C{Nvector}} is C{None}, a L{Vector4Tuple}C{(x, y, z, h)}.
@raise TypeError: Invalid B{C{Nvector}} or B{C{Nvector_kwds}}. '''
'''This L{LatLon_} as a string "class(<degrees>, ...)".
@kwarg kwds: Optional, keyword arguments.
@return: Class instance (C{str}). '''
'''DEPRECATED, used method L{LatLon_.toRepr}.'''
'''This L{LatLon_} as a string "<degrees>, <degrees>".
@kwarg form: Optional format, F_D, F_DM, F_DMS for deg°, deg°min′, deg°min′sec″ (C{str}). @kwarg prec: Optional number of decimal digits (0..8 or C{None}). @kwarg sep: Optional separator to join (C{str}). @kwarg kwds: Optional, keyword arguments.
@return: Instance (C{str}). ''' lonDMS(self.lon, form=form, prec=prec)) t += (repr(self.name),)
'''(INTERNAL) Base class. '''
'''(INTERNAL) Check for a matching point. '''
'''Make a shallow or deep copy of this instance.
@kwarg 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. '''
'''Get the tolerance for equality tests (C{float}). '''
'''Set the tolerance for equality tests.
@arg tol: New tolerance (C{scalar}).
@raise TypeError: Non-scalar B{C{tol}}.
@raise ValueError: Out-of-bounds B{C{tol}}. '''
'''(INTERNAL) Find the first matching point index. '''
def _findall(self, point, start_end): # PYCHOK no cover '''(INTERNAL) I{Must be implemented/overloaded}. ''' notImplemented(self, self._findall, point, start_end)
'''(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 _IndexError(self._itemname, point, txt='not found')
def isNumpy2(self): # PYCHOK no cover '''Is this a Numpy2 wrapper? ''' return False # isinstance(self, (Numpy2LatLon, ...))
def isPoints2(self): # PYCHOK no cover '''Is this a LatLon2 wrapper/converter? ''' return False # isinstance(self, (LatLon2psxy, ...))
def isTuple2(self): # PYCHOK no cover '''Is this a Tuple2 wrapper? ''' return False # isinstance(self, (Tuple2LatLon, ...))
'''(INTERNAL) Yield all points. '''
def point(self, *attrs): # PYCHOK no cover '''(INTERNAL) I{Must be overloaded}.
@arg attrs: Optional arguments. ''' notOverloaded(self, self.point, *attrs)
'''(INTERNAL) Return the range. ''' else: raise _ValueError(step=step)
'''(INTERNAL) Return a string representation. ''' # XXX use Python 3+ reprlib.repr
'''(INTERNAL) Yield all points in reverse order. '''
'''(INTERNAL) Find the last matching point index. '''
def _slicekwds(self): # PYCHOK no cover '''(INTERNAL) I{Should be overloaded}. ''' return {}
'''(INTERNAL) Check for near-zero values. '''
'''Base class for Numpy2LatLon or Tuple2LatLon. '''
'''Handle a C{NumPy} or C{Tuple} array as a sequence of C{LatLon} points. '''
raise _IndexError('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 _ValueError(ai, i) raise _ValueError(' == '.join(map1(str, ai, aj, i)))
'''Check for a specific lat-/longitude.
@arg 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.
@arg 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.
@arg latlon: Point (C{LatLon}) or 2-tuple (lat, lon). @arg 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.
@arg latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @arg start_end: Optional C{[start[, end]]} index (C{int}).
@return: Indices (C{iterable}).
@raise TypeError: Invalid B{C{latlon}}. '''
'''Find index of the first row with a specific lat-/longitude.
@arg latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @arg start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index (C{int}).
@raise IndexError: Point not found.
@raise TypeError: Invalid B{C{latlon}}. '''
'''Get the latitudes column index (C{int}). ''' return self._ilat
'''Get the longitudes column index (C{int}). ''' return self._ilon
# next = __iter__
'''Instantiate a point C{LatLon}.
@arg row: Array row (numpy.array).
@return: Point (C{LatLon}). '''
'''Find the last row with a specific lat-/longitude.
@arg latlon: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(lat, lon)}). @arg 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. '''
'''Get the shape of the C{NumPy} array or the C{Tuples} as L{Shape2Tuple}C{(nrows, ncols)}. '''
'''(INTERNAL) I{Must be implemented/overloaded}. ''' notImplemented(self, self._subset, indices)
'''Return a subset of the C{NumPy} array.
@arg 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(_item_sq(indices=i), v) raise _IndexError(_item_sq(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)}.
@arg latlons: Points C{list}, C{sequence}, C{set}, C{tuple}, etc. (C{LatLon[]}). @kwarg closed: Optionally, close the polygon (C{bool}). @kwarg radius: Mean earth radius (C{meter}). @kwarg wrap: Wrap lat- and longitudes (C{bool}).
@raise PointsError: Insufficient number of B{C{latlons}}.
@raise TypeError: Some B{C{points}} are not B{C{base}}. '''
'''Check for a matching point.
@arg 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.
@arg 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.
@arg xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @arg 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. '''
except (IndexError, TypeError, ValueError): raise _IsnotError(_valid_, xy=xy)
'''Yield indices of all matching points.
@arg xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @arg start_end: Optional C{[start[, end]]} index (C{int}).
@return: Indices (C{iterator}).
@raise TypeError: Invalid B{C{xy}}. '''
'''Find the first matching point.
@arg xy: Point (C{LatLon}) or 2-tuple (x, y) in (C{degrees}). @arg start_end: Optional C{[start[, end]]} index (C{int}).
@return: Index (C{int}).
@raise IndexError: Point not found.
@raise TypeError: Invalid B{C{xy}}. '''
'''Is this a LatLon2 wrapper/converter? '''
# next = __iter__
'''Create a pseudo-xy.
@arg ll: Point (C{LatLon}).
@return: An L{Point3Tuple}C{(x, y, ll)}. '''
'''Find the last matching point.
@arg xy: Point (C{LatLon}, L{LatLon2Tuple} or 2-tuple C{(x, y)}) in (C{degrees}. @arg 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. '''
'''5-Tuple C{(lat, lon, distance, angle, height)} all in C{degrees}, except C{height}. The C{distance} is the L{equirectangular_} distance between the closest and the reference B{C{point}} in C{degrees}. The C{angle} from the reference B{C{point}} to the closest point is in compass C{degrees360}, see function L{compassAngle}. The C{height} is the (interpolated) height at the closest point in C{meter} or C{0}. '''
'''Wrapper for C{NumPy} arrays as "on-the-fly" C{LatLon} points. ''' '''Handle a C{NumPy} array as a sequence of C{LatLon} points.
@arg array: C{NumPy} array (C{numpy.array}). @kwarg ilat: Optional index of the latitudes column (C{int}). @kwarg ilon: Optional index of the longitudes column (C{int}). @kwarg LatLon: Optional C{LatLon} 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)
'''Is this a Numpy2 wrapper? '''
'''3-Tuple C{(x, y, ll)} in C{meter}, C{meter} and C{LatLon}. '''
'''2-Tuple C{(nrows, ncols)}, the number of rows and columns, both C{int}. '''
'''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.
@arg tuples: The C{list}, C{tuple} or C{sequence} of tuples (C{tuple}[]). @kwarg ilat: Optional index of the latitudes value (C{int}). @kwarg ilon: Optional index of the longitudes value (C{int}). @kwarg LatLon: Optional C{LatLon} 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)
'''Is this a Tuple2 wrapper? '''
# 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
'''(INTERNAL) Area issue. '''
'''Approximate the area of a polygon.
@arg points: The polygon points (C{LatLon}[]). @kwarg adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg radius: Mean earth radius (C{meter}). @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: Approximate area (C{meter}, same units as B{C{radius}}, I{squared}).
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Invalid B{C{radius}}.
@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.
@arg points: The path or polygon points (C{LatLon}[]). @kwarg wrap: Wrap lat- and longitudes (C{bool}). @kwarg LatLon: Optional class to return the C{bounds} corners (C{LatLon}) or C{None}.
@return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)} as B{C{LatLon}}s if B{C{LatLon}} is C{None} a L{Bounds4Tuple}C{(latS, lonW, latN, lonE)}.
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@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.
@arg points: The polygon points (C{LatLon}[]). @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional 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 PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: The B{C{points}} enclose a pole or near-zero area.
@see: U{Centroid<https://WikiPedia.org/wiki/Centroid#Of_a_polygon>} and Paul Bourke's U{Calculating The Area And Centroid Of A Polygon <https://www.SEAS.UPenn.edu/~ese502/lab-content/extra_materials/ Polygon%20Area%20and%20Centroid.pdf>}, 1988. ''' # 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 _areaError(points, near_='near-')
'''(INTERNAL) Return first and second index. '''
'''Determine the direction of a path or polygon.
@arg points: The path or polygon points (C{LatLon}[]). @kwarg adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{True} if B{C{points}} are clockwise, C{False} otherwise.
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: 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 _areaError(points)
'''Determine whether a polygon is convex.
@arg points: The polygon points (C{LatLon}[]). @kwarg adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg 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 PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@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 I{and clockwise}.
@arg points: The polygon points (C{LatLon}[]). @kwarg adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg 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 PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@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_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>
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(points=ll, txt=_colinear_)
'''Determine whether a point is enclosed by a polygon.
@arg point: The point (C{LatLon} or 2-tuple C{(lat, lon)}). @arg points: The polygon points (C{LatLon}[]). @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: C{True} if B{C{point}} is inside the polygon, C{False} otherwise.
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Invalid B{C{point}}, lat- or longitude.
@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) as x: raise _ValueError(point=point, txt=str(x))
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_0
# 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.
@arg points: The polygon points (C{LatLon}[]). @kwarg wrap: Wrap and unroll longitudes (C{bool}).
@return: C{True} if the polygon encloses a pole, C{False} otherwise.
@raise PointsError: 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)
'''Generate an ellipsoidal or spherical U{lune <https://WikiPedia.org/wiki/Spherical_lune>}-shaped path or polygon.
@arg lon1: Left longitude (C{degrees90}). @arg lon2: Right longitude (C{degrees90}). @kwarg closed: Optionally, close the path (C{bool}). @kwarg LatLon: Class to use (L{LatLon_}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments.
@return: Yield 4 or 5 B{C{LatLon}} instances outlining the lune shape.
@see: U{Latitude-longitude quadrangle <https://www.MathWorks.com/help/map/ref/areaquad.html>}. ''' yield LatLon(_0_0, lon1, **LatLon_kwds)
'''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}}.
@arg point: The other, reference point (C{LatLon}). @arg points: The path or polygon points (C{LatLon}[]). @kwarg closed: Optionally, close the path or polygon (C{bool}). @kwarg wrap: Wrap and L{unroll180} longitudes and longitudinal delta (C{bool}) in function L{equirectangular_}. @kwarg LatLon: Optional class to return the closest point (L{LatLon}) or C{None}. @kwarg options: Other keyword arguments for function L{equirectangular_}.
@return: A L{NearestOn3Tuple}C{(closest, distance, angle)} with the {closest} point (B{C{LatLon}}) or if B{C{LatLon}} is C{None} a L{NearestOn5Tuple}C{(lat, lon, distance, angle, height)}. The C{distance} is the L{equirectangular_} distance between the C{closest} and reference B{C{point}} in C{degrees}. The C{angle} from the reference B{C{point}} to the C{closest} is in compass C{degrees360}, like function L{compassAngle}.
@raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}}, see function L{equirectangular_}.
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@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) / hypot2(dx, dy)
# 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 = LatLon(c.lat, c.lon + u, height=h) r = NearestOn3Tuple(r, d, a)
'''Approximate the perimeter of a path or polygon.
@arg points: The path or polygon points (C{LatLon}[]). @kwarg closed: Optionally, close the path or polygon (C{bool}). @kwarg adjust: Adjust the wrapped, unrolled longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg radius: Mean earth radius (C{meter}). @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: Approximate perimeter (C{meter}, same units as B{C{radius}}).
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@raise ValueError: Invalid B{C{radius}}.
@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)
'''Generate a quadrilateral path or polygon from two points.
@arg lat1: Lower latitude (C{degrees90}). @arg lon1: Left longitude (C{degrees180}). @arg lat2: Upper latitude (C{degrees90}). @arg lon2: Right longitude (C{degrees180}). @kwarg closed: Optionally, close the path (C{bool}). @kwarg LatLon: Class to use (L{LatLon_}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments.
@return: Yield 4 or 5 B{C{LatLon}} instances outlining the quadrilateral. ''' for ll in ((lat1, lon1), (lat2, lon1), (lat2, lon2), (lat1, lon2)): yield LatLon(*ll, **LatLon_kwds) if closed: yield LatLon(lat1, lon1, **LatLon_kwds)
# **) 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. |