Coverage for pygeodesy/points.py : 92%

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 -*-
u'''Utilities for point lists, tuples, etc.
Functions to handle collections and sequences of C{LatLon} points 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 to be returned.
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} from each 2+tuple in a sequence of such 2+tuples using the C{ilat} lat- and C{ilon} longitude index in each 2+tuple. '''
issequence, issubclassof, map1, _Sequence, \ _xcopy, _xdup, _xinstanceof _IsnotError, _TypeError, _ValueError, \ _xkwds, _xkwds_pop _colinear_, _COMMASPACE_, _DEQUALSPACED_, \ _ELLIPSIS_, _height_, _immutable_, _lat_, \ _lon_, _near_, _not_, _point_, _SPACE_, \ _UNDER_, _valid_, _0_0, _0_5, _1_0, _3_0, \ _90_0, _N_90_0, _180_0, _360_0 _NamedTuple, _xnamed, _xother3, _xotherError LatLon2Tuple, NearestOn3Tuple, \ NearestOn5Tuple, PhiLam2Tuple, \ Point3Tuple, Vector3Tuple, Vector4Tuple property_RO 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> # # __slots__ = (_lat_, _lon_, _height_, _datum_, _name_) # Property_RO = property_RO # no __dict__ with __slots__ # # However, sys.getsizeof(LatLon_(1, 2)) is 72-88 with __slots__ # but only 48-64 bytes without in Python 2.7.18+ and Python 3+.
'''Creat a new, mininal, low-overhead L{LatLon_} instance, without height 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 . ''' except (TypeError, ValueError): self.lat, self.lon = parseDMS2(lat, lon, clipLat=0, clipLon=0) # PYCHOK LatLon2Tuple _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__}). '''
'''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} (sub-)class). '''
'''Duplicate this instance, replacing some items.
@kwarg items: Attributes to be changed (C{any}).
@return: The duplicate (C{This} (sub-)class).
@raise AttributeError: Some B{C{items}} invalid. ''' return _xdup(self, **items)
'''Return a string for the height B{C{height}}.
@kwarg prec: Optional number of decimals, unstripped (C{int}).
@see: Function L{pygeodesy.hstr}. '''
'''Locate the point at a given fraction between (or along) this and an other point.
@arg other: The other point (C{LatLon}). @arg fraction: Fraction between both points (C{float}, 0.0 for this and 1.0 for the other point). @kwarg height: Optional height (C{meter}), overriding the intermediate height. @kwarg wrap: Wrap and unroll longitudes (C{bool}).
@return: Intermediate point (this C{LatLon}).
@raise TypeError: Incompatible B{C{other}} C{type}. ''' f = Scalar(fraction=fraction) if isnear0(f): r = self elif isnear1(f) and not wrap: r = self.others(other) else: r = self.others(other) h = favg(self.height, r.height, f=f) if height is None else height _, lon = unroll180(self.lon, r.lon, wrap=wrap) r = self.classof(favg(self.lat, r.lat, f=f), favg(self.lon, lon, f=f), height=h, datum=self.datum, name=self.intermediateTo.__name__) return r
'''Compare this point with an other point, I{ignoring} height.
@arg other: The other point (C{LatLon}). @kwarg eps: Tolerance for equality (C{degrees}).
@return: C{True} if both points are identical, I{ignoring} height, C{False} otherwise.
@raise UnitError: Invalid B{C{eps}}. '''
abs(self.lon - other.lon)) < Scalar_(eps=eps) else: self.lon == other.lon
'''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_}) ''' h=self.height, name=self.name)
'''Refined class comparison.
@arg other: The other instance (any C{type}). @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}}. '''
'''Return a points iterator.
@arg points: The path or polygon points (C{LatLon}[]) @kwarg loop: Number of loop-back points (non-negative C{int}). @kwarg dedup: Skip duplicate points (C{bool}).
@return: A new C{PointsIter} iterator.
@raise PointsError: Insufficient number of B{C{points}}. ''' return PointsIter(points, loop=loop, base=self, dedup=dedup)
def to2ab(self): # PYCHOK no cover '''DEPRECATED, use property L{philam}.''' 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 C{B{Nvector} is 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}} argument. '''
'''This L{LatLon_} as a string "class(<degrees>, ...)".
@kwarg kwds: Optional, keyword arguments.
@return: Class instance (C{str}). '''
'''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))
def toStr2(self, **kwds): # PYCHOK no cover '''DEPRECATED, used method L{toRepr}.''' return self.toRepr(**kwds)
'''(INTERNAL) Check a C{LatLon} or C{LatLon_} instance. '''
'''(INTERNAL) Check a (sub-)class of C{LatLon_}. ''' if LatLon_._ATTRS_ is None: # cached pseudo-__slots__ LatLon_._ATTRS_ = tuple(LatLon_(0, 0).__dict__.keys())
return issubclassof(LL, LatLon_) or (isclass(LL) and all(hasattr(LL, a) for a in LatLon_._ATTRS_))
'''(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. '''
'''Duplicate this instance, I{without replacing items}.
@kwarg items: No attributes (C{none}).
@return: The duplicate (C{This} (sub-)class).
@raise _TypeError: Any B{C{items}} invalid. ''' if items: t = _SPACE_(classname(self), _immutable_) raise _TypeError(txt=t, this=self, **items) return _xdup(self)
'''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. ''' else:
def _findall(self, point, start_end): # PYCHOK no cover '''(INTERNAL) I{Must be implemented/overloaded}. ''' notImplemented(self, 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}, see function C{notOverloaded}.
@arg attrs: Optional arguments. ''' notOverloaded(self, *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)
if _isLatLon_(LatLon): 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(_DEQUALSPACED_(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 AttributeError: try: lat, lon = latlon except (TypeError, ValueError): raise _IsnotError(_valid_, latlon=latlon)
row[_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)}. '''
def _subset(self, indices): # PYCHOK no cover '''(INTERNAL) I{Must be implemented/overloaded}. ''' notImplemented(self, 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(Fmt.SQUARE(indices=i), v) raise _IndexError(Fmt.SQUARE(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}}. ''' self._radius = r = Radius(radius) self._deg2m = degrees2m(_1_0, r)
'''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 AttributeError: try: x, y = xy[:2] except (IndexError, TypeError, ValueError): raise _IsnotError(_valid_, xy=xy)
_x_y_ll3 = self.point # PYCHOK expected
'''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? '''
'''Create a pseudo-xy.
@arg ll: Point (C{LatLon}).
@return: An L{Point3Tuple}C{(x, y, ll)}. ''' x, y = wrap180(x), wrap90(y) x *= d y *= d
'''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. '''
'''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? '''
'''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 C{(len(B{tuples}), min(len(t) for t in B{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? '''
'''(INTERNAL) Approximate the area in radians squared, I{signed}. ''' # approximate trapezoid by a rectangle, adjusting # the top width by the cosine of the latitudinal # average and bottom width by some fudge factor else:
# setting radius=1 converts degrees to radians
'''(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}) or C{None}. @kwarg wrap: Wrap lat-, wrap and unroll longitudes (C{bool}).
@return: Approximate area (I{square} C{meter}, same units as B{C{radius}} or C{radians} I{squared} if B{C{radius}} is C{None}).
@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}.
@see: Function L{quadOf}.
@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 (C{LatLon}) or C{None}.
@return: Centroid (B{C{LatLon}}) or a L{LatLon2Tuple}C{(lat, lon)} if C{B{LatLon} is 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(pts, near_=_near_)
'''Return the point at a given I{fractional} index.
@arg points: The points (C{LatLon}[], L{Numpy2LatLon}[], L{Tuple2LatLon}[], C{Cartesian}[], C{Vector3d}[], L{Vector3Tuple}[]). @arg fi: The fractional index (L{FIx}, C{float} or C{int}). @kwarg j: Optionally, index of the other point (C{int}). @kwarg wrap: Wrap and unroll longitudes (C{bool}) or C{None} for backward compatible L{LatLon2Tuple} or B{C{LatLon}} with averaged lat- and longitudes. Use C{True} or C{False} to get the I{fractional} point computed method C{points[fi].intermediateTo}. @kwarg LatLon: Optional class to return the I{intermediate}, I{fractional} point (C{LatLon}) or C{None}. @kwarg Vector: Optional class to return the I{intermediate}, I{fractional} point (C{Cartesian}, C{Vector3d}) or C{None}. @kwarg kwds: Optional, additional B{C{LatLon}} I{or} B{C{Vector}} keyword arguments, ignored if both C{B{LatLon}} and C{B{Vector}} are C{None}.
@return: A L{LatLon2Tuple}C{(lat, lon)} if B{C{wrap}}, B{C{LatLon}} and B{C{Vector}} all are C{None}, the defaults.
An instance of B{C{LatLon}} if not C{None} I{or} an instance of B{C{Vector}} if not C{None}.
Otherwise with B{C{wrap}} either C{True} or C{False} and B{C{LatLon}} and B{C{Vector}} both C{None}, an instance of B{C{points}}' (sub-)class C{intermediateTo} I{fractional}.
Summarized as follows:
>>> wrap | LatLon | Vector | returned type/value # -------+--------+--------+--------------+------ # | | | LatLon2Tuple | favg # None | None | None | or** | # | | | Vector3Tuple | favg # None | LatLon | None | LatLon | favg # None | None | Vector | Vector | favg # -------+--------+--------+--------------+------ # True | None | None | points' | .iTo # True | LatLon | None | LatLon | .iTo # True | None | Vector | Vector | .iTo # -------+--------+--------+--------------+------ # False | None | None | points' | .iTo # False | LatLon | None | LatLon | .iTo # False | None | Vector | Vector | .iTo # _____ # favg) averaged lat, lon or x, y, z values # .iTo) value from points[fi].intermediateTo # **) depends on base class of points[fi]
@raise IndexError: Fractional index B{C{fi}} invalid or B{C{points}} not subscriptable or not closed.
@raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}} argument.
@see: Class L{FIx} and method L{FIx.fractional}. ''' kwds = _xkwds(kwds, fi=fi, LatLon=LatLon, Vector=Vector) raise _TypeError(txt=fractional.__name__, **kwds) raise IndexError p = Vector(p.x, p.y, p.z, **kwds) except (IndexError, TypeError): raise _IndexError(fi=fi, points=points, wrap=wrap, txt=fractional.__name__)
'''(INTERNAL) Compute point at L{fractional} index C{fi} and C{j}. ''' p = q p = p.intermediateTo(q, r, wrap=wrap) favg(p.lon, q.lon, f=r), name=fractional.__name__) else: # assume p and q are cartesian or vectorial z = p.z if p.z is q.z else favg(p.z, q.z, f=r) p = Vector3Tuple(favg(p.x, q.x, f=r), favg(p.y, q.y, f=r), z, name=fractional.__name__)
'''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(pts)
'''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
# 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>
# colinear u-turn: x3, y3 not on the # opposite side of x2, y2 as x1, y1 t = Fmt.SQUARE(points=i) raise CrossError(t, 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 AttributeError: try: y0, x0 = map1(float, *point[:2]) except (IndexError, TypeError, ValueError) as x: raise _ValueError(point=point, txt=str(x))
x0, y0 = wrap180(x0), wrap90(y0)
def _dxy3(x1, x2, y2, w): dx, x2 = unroll180(x1, x2, wrap=w) 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. '''
# summation 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: A tuple of 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>}. ''' LatLon( _90_0, lon1, **LatLon_kwds), LatLon( _0_0, lon2, **LatLon_kwds), LatLon(_N_90_0, lon2, **LatLon_kwds)) t += t[:1]
'''Locate the point on a path or polygon closest to a reference point.
The closest point is either on and within the extent of a polygon edge or the nearest of that edge's end points.
@arg point: 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{pygeodesy.unroll180} longitudes and longitudinal delta (C{bool}) in function L{pygeodesy.equirectangular_}. @kwarg LatLon: Optional class to return the closest point (C{LatLon}) or C{None}. @kwarg options: Other keyword arguments for function L{pygeodesy.equirectangular_}.
@return: A L{NearestOn3Tuple}C{(closest, distance, angle)} with the {closest} point (B{C{LatLon}}) or if C{B{LatLon} is None}, a L{NearestOn5Tuple}C{(lat, lon, distance, angle, height)}. The C{distance} is the L{pygeodesy.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{pygeodesy.compassAngle}.
@raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}}, see function L{pygeodesy.equirectangular_}.
@raise PointsError: Insufficient number of B{C{points}}
@raise TypeError: Some B{C{points}} are not C{LatLon}.
@note: Distances are I{approximated} using function L{pygeodesy.equirectangular_}, subject to the supplied B{C{options}}. Method C{LatLon.nearestOn6} measures distances more accurately.
@see: Function L{pygeodesy.nearestOn6} for cartesian points. Use function L{pygeodesy.degrees2m} to convert C{degrees} to C{meter}. ''' # w = wrap if (not closed or w < (n - 1)) else False # 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)
# 3-D version used in .vector3d._nearestOn2 # # 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) # # i.e. no need for sqrt or hypot
# 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{pygeodesy.equirectangular_} distance approximation and is ill-suited for regions exceeding several hundred Km or Miles or with near-polar latitudes.
@see: Functions L{sphericalTrigonometry.perimeterOf} and L{ellipsoidalKarney.perimeterOf}. ''' # apply previous x2's unroll/wrap to new x1 adjust=adjust, limit=None, wrap=w) # PYCHOK non-sequence
'''Generate a quadrilateral path or polygon from two points.
@arg latS: Southernmost latitude (C{degrees90}). @arg lonW: Westernmost longitude (C{degrees180}). @arg latN: Northernmost latitude (C{degrees90}). @arg lonE: Easternmost 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: Return a tuple of 4 or 5 B{C{LatLon}} instances outlining the quadrilateral.
@see: Function L{boundsOf}. ''' LatLon(latN, lonW, **LatLon_kwds), LatLon(latN, lonE, **LatLon_kwds), LatLon(latS, lonE, **LatLon_kwds)) t += t[:1]
# **) MIT License # # Copyright (C) 2016-2022 -- 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. |