def _reverse(self, x, y, name, LatLon, LatLon_kwds, _c_t, lea): '''(INTERNAL) Azimuthal (spherical) reverse C{x, y} to C{lat, lon}. ''' x = Scalar(x=x) y = Scalar(y=y) r = hypot(x, y) c, t = _c_t(r / self.radius) if t: s0, c0 = self._sc0 sc, cc = sincos2(c) k = c / sc z = atan2b(x, y) # (x, y) for azimuth from true North lat = degrees(asin1(s0 * cc + c0 * sc * (y / r))) if lea or abs(c0) > EPS: lon = atan2(x * sc, c0 * cc * r - s0 * sc * y) else: lon = atan2(x, (y if s0 < 0 else -y)) lon = _norm180(self.lon0 + degrees(lon)) else: k, z = _1_0, _0_0 lat, lon = self.latlon0 t = self._toLatLon(lat, lon, LatLon, LatLon_kwds) if LatLon else \ Azimuthal7Tuple(x, y, lat, lon, z, k, self.datum) return self._xnamed(t, name=name)
def __new__(cls, z, h, e, n, B, d, c, s, Error=None): if Error is not None: e = Easting(e, Error=Error) n = Northing(n, Error=Error) c = Scalar(c, name=_convergence_, Error=Error) s = Scalar(s, name=_scale_, Error=Error) return _NamedTuple.__new__(cls, z, h, e, n, B, d, c, s)
def reverse(self, x, y, name=NN, LatLon=None, **LatLon_kwds): '''Convert an azimuthal equidistant location to (ellipsoidal) geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Azimuthal7Tuple}C{(x, y, lat, lon, azimuth, scale, datum)}. @note: The C{lat} will be in the range C{[-90..90] degrees} and C{lon} in the range C{[-180..180] degrees}. The scale of the projection is C{1} in I{radial} direction, C{azimuth} clockwise from true North and is C{1 / reciprocal} in the direction perpendicular to this. ''' x = Scalar(x, name=_x_) y = Scalar(y, name=_y_) z = atan2d(x, y) # (x, y) for azimuth from true North s = hypot(x, y) r = self.geodesic.Direct(self.lat0, self.lon0, z, s, self._mask) t = self._toLatLon(r.lat2, r.lon2, LatLon, LatLon_kwds) if LatLon else \ Azimuthal7Tuple(x, y, r.lat2, r.lon2, r.azi2, self._1_rk(r), self.datum) return _xnamed(t, name or self.name)
def __init__(self, easting, northing, band='', datum=None, falsed=True, convergence=None, scale=None): '''(INTERNAL) New L{UtmUpsBase}. ''' E = self._Error if not E: notOverloaded(self, '_Error') self._easting = Easting(easting, Error=E) self._northing = Northing(northing, Error=E) if band: _xinstanceof(str, band=band) self._band = band if datum: _xinstanceof(Datum, datum=datum) if datum != self._datum: self._datum = datum if not falsed: self._falsed = False if convergence is not self._convergence: self._convergence = Scalar(convergence, name='convergence', Error=E) if scale is not self._scale: self._scale = Scalar(scale, name='scale', Error=E)
def __init__(self, easting, northing, band=NN, datum=None, falsed=True, convergence=None, scale=None): '''(INTERNAL) New L{UtmUpsBase}. ''' E = self._Error if not E: notOverloaded(self, '_Error') self._easting = Easting(easting, Error=E) self._northing = Northing(northing, Error=E) if band: _xinstanceof(str, band=band) self._band = band if datum not in (None, self._datum): self._datum = _ellipsoidal_datum(datum) # XXX name=band if not falsed: self._falsed = False if convergence is not self._convergence: self._convergence = Scalar(convergence, name=_convergence_, Error=E) if scale is not self._scale: self._scale = Scalar(scale, name=_scale_, Error=E)
def reverse(self, x, y, name=NN, LatLon=None, **LatLon_kwds): '''Convert an azimuthal gnomonic location to (ellipsoidal) geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Azimuthal7Tuple}C{(x, y, lat, lon, azimuth, scale, datum)}. @raise AzimuthalError: No convergence. @note: The C{lat} will be in the range C{[-90..90] degrees} and C{lon} in the range C{[-180..180] degrees}. The C{azimuth} is clockwise from true North. The scale is C{1 / reciprocal**2} in C{radial} direction and C{1 / reciprocal} in the direction perpendicular to this. ''' x = Scalar(x=x) y = Scalar(y=y) z = atan2d(x, y) # (x, y) for azimuth from true North q = hypot(x, y) d = e = self.equatoradius s = e * atan(q / e) if q > e: def _d(r, q): return (r.M12 - q * r.m12) * r.m12 # negated q = 1 / q else: # little == True def _d(r, q): # PYCHOK _d return (q * r.M12 - r.m12) * r.M12 # negated e *= _Karney_eps S = Fsum(s) g = self.geodesic.Line(self.lat0, self.lon0, z, self._mask) for self._iteration in range(1, _TRIPS): r = g.Position(s, self._mask) if abs(d) < e: break s, d = S.fsum2_(_d(r, q)) else: raise AzimuthalError(x=x, y=y, txt=_no_(Fmt.convergence(e))) t = self._7Tuple(x, y, r, r.M12) if LatLon is None else \ self._toLatLon(r.lat2, r.lon2, LatLon, LatLon_kwds) t._iteration = self._iteration return self._xnamed(t, name=name)
def __new__(cls, x, y, lat, lon, azi, s, datum): return _NamedTuple.__new__( cls, Scalar(x, name=_x_, Error=AzimuthalError), Scalar(y, name=_y_, Error=AzimuthalError), # PYCHOK indent Lat_(lat, Error=AzimuthalError), Lon_(lon, Error=AzimuthalError), Bearing(azi, name=_azimuth_, Error=AzimuthalError), Scalar(s, name=_scale_, Error=AzimuthalError), datum)
def __new__(cls, z, h, e, n, B, d, c, s, Error=None): if Error is not None: e = Easting(e, Error=Error) n = Northing(n, Error=Error) c = Degrees(convergence=c, Error=Error) s = Scalar(scale=s, Error=Error) return _NamedTuple.__new__(cls, z, h, e, n, B, d, c, s)
def intermediateChordTo(self, other, fraction, height=None): '''Locate the point projected from the point at given fraction on a straight line (chord) between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height at the intermediate point, overriding the fractional height (C{meter}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(48.857, 2.351) >>> i = p.intermediateChordTo(q, 0.25) # 51.3723°N, 000.7072°E @JSname: I{intermediatePointOnChordTo}, I{intermediatePointDirectlyTo}. ''' self.others(other) f = Scalar(fraction=fraction) i = other.toNvector().times(f).plus( self.toNvector().times(1 - f)) # i = other.toNvector() * f + \ # self.toNvector() * (1 - f)) h = self._havg(other, f=f) if height is None else Height(height) return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
def _intermediateTo(p1, p2, fraction, height, wrap): # (INTERNAL) Helper for C{ellipsoidalKarney.LatLon.intermediateTo} # and C{ellipsoidalVincenty.LatLon.intermediateTo}. t = p1.distanceTo3(p2, wrap=wrap) f = Scalar(fraction=fraction) h = p1._havg(p2, f=f) if height is None else Height(height) return p1.destination(t.distance * f, t.initial, height=h)
def intermediateTo(self, other, fraction, height=None, wrap=False): '''Locate the point at given fraction between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height, overriding the fractional height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: Invalid B{C{fraction}} or B{C{height}}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> p = p1.intermediateTo(p2, 0.25) # 51.3721°N, 000.7073°E @JSname: I{intermediatePointTo}. ''' self.others(other) f = Scalar(fraction, name='fraction') a1, b1 = self.philam a2, b2 = other.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) sr = sin(r) if abs(sr) > EPS: sa1, ca1, sa2, ca2, \ sb1, cb1, sb2, cb2 = sincos2(a1, a2, b1, b2) A = sin((1 - f) * r) / sr B = sin(f * r) / sr x = A * ca1 * cb1 + B * ca2 * cb2 y = A * ca1 * sb1 + B * ca2 * sb2 z = A * sa1 + B * sa2 a = atan2(z, hypot(x, y)) b = atan2(y, x) else: # points too close a = favg(a1, a2, f=f) b = favg(b1, b2, f=f) if height is None: h = self._havg(other, f=f) else: h = Height(height) return self.classof(degrees90(a), degrees180(b), height=h)
def elevation2(lat, lon, timeout=2.0): '''Get the geoid elevation at an C{NAD83} to C{NAVD88} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{Elevation2Tuple}C{(elevation, data_source)} or (C{None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{elevation} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{USGS National Map<https://NationalMap.gov/epqs>}, the U{FAQ<https://www.USGS.gov/faqs/what-are-projection- horizontal-and-vertical-datum-units-and-resolution-3dep-standard-dems>}, U{geoid.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: x = _qURL( 'https://NED.USGS.gov/epqs/pqs.php', # 'https://NationalMap.gov/epqs/pqs.php' ( '%s=%.6F' % (_x_, Lon(lon)), '%s=%.6F' % (_y_, Lat(lat)), '%s=%s' % (_units_, 'Meters'), # 'Feet', capitalized 'output=xml'), # json, case_sensitive timeout=Scalar(timeout, name=_timeout_)) if x[:6] == '<?xml ': e = _xml('Elevation', x) try: e = float(e) if -1000000 < e < 1000000: return Elevation2Tuple(e, _xml('Data_Source', x)) e = 'non-CONUS %.2F' % (e, ) except (TypeError, ValueError): pass else: e = 'no %s "%s"' % ( _XML_, clips(x, limit=128, white=_SPACE_), ) except (HTTPError, IOError, TypeError, ValueError) as x: e = repr(x) e = _error(elevation2, lat, lon, e) return Elevation2Tuple(None, e)
def __init__(self, north, east, down, name=NN): '''New North-East-Down vector. @arg north: North component (C{meter}). @arg east: East component (C{meter}). @arg down: Down component, normal to the surface of the ellipsoid (C{meter}). @kwarg name: Optional name (C{str}). @raise ValueError: Invalid B{C{north}}, B{C{east}} or B{C{down}}. @example: >>> from ellipsiodalNvector import Ned >>> delta = Ned(110569, 111297, 1936) >>> delta.toStr(prec=0) # [N:110569, E:111297, D:1936] ''' self._north = Scalar(north=north or 0) self._east = Scalar(east=east or 0) self._down = Scalar(down=down or 0) if name: self.name = name
def geoidHeight2(lat, lon, model=0, timeout=2.0): '''Get the C{NAVD88} geoid height at an C{NAD83} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg model: Optional, geoid model ID (C{int}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{GeoidHeight2Tuple}C{(height, model_name)} or C{(None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{height} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if the B{C{model}} was invalid, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{NOAA National Geodetic Survey <https://www.NGS.NOAA.gov/INFO/geodesy.shtml>}, U{Geoid<https://www.NGS.NOAA.gov/web_services/geoid.shtml>}, U{USGS10mElev.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: j = _qURL('https://Geodesy.NOAA.gov/api/geoid/ght', ('lat=%.6F' % (Lat(lat), ), 'lon=%.6F' % (Lon(lon), ), 'model=%s' % (model, ) if model else ''), timeout=Scalar(timeout, name='timeout')) # PYCHOK 5 if j[:1] == '{' and j[-1:] == '}' and j.find('"error":') > 0: d = _json(j) if isinstance(d.get('error', 'N/A'), float): h = d.get('geoidHeight', None) if h is not None: m = _str(d.get('geoidModel', 'N/A')) return GeoidHeight2Tuple(h, m) e = 'geoidHeight' else: e = 'JSON' e = 'no %s "%s"' % (e, clips(j, limit=256, white=' ')) except (HTTPError, IOError, TypeError, ValueError) as x: e = repr(x) e = _error(geoidHeight2, lat, lon, e) return GeoidHeight2Tuple(None, e)
def rescale0(self, lat, scale0=_K0): '''Set the central scale factor for this UPS projection. @arg lat: Northern latitude (C{degrees}). @arg scale0: UPS k0 scale at B{C{lat}} latitude (C{scalar}). @raise RangeError: If B{C{lat}} outside the valid range and L{rangerrors} set to C{True}. @raise UPSError: Invalid B{C{scale}}. ''' s0 = Scalar_(scale0, Error=UPSError, name='scale0', low=EPS) # <= 1.003 or 1.0016? u = toUps8(abs(Lat(lat)), 0, datum=self.datum, Ups=_UpsK1) k = s0 / u.scale if self.scale0 != k: self._band = NN # force re-compute self._latlon = self._epsg = self._mgrs = self._utm = None self._scale0 = Scalar(k)
def intermediateTo(self, other, fraction, height=None): '''Locate the point at a given fraction between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height at the intermediate point, overriding the fractional height (C{meter}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise Valuerror: Points coincide or invalid B{C{height}}. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(48.857, 2.351) >>> i = p.intermediateTo(q, 0.25) # 51.3721°N, 000.7074°E @JSname: I{intermediatePointTo}. ''' q = self.others(other).toNvector() p = self.toNvector() f = Scalar(fraction=fraction) x = p.cross(q, raiser=_points_) d = x.unit().cross(p) # unit(p × q) × p # angular distance α, tan(α) = |p × q| / p ⋅ q s, c = sincos2(atan2(x.length, p.dot(q)) * f) # interpolated i = p.times(c).plus(d.times(s)) # p * cosα + d * sinα h = self._havg(other, f=f) if height is None else Height(height) return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
from pygeodesy.units import Meter, Lat, Scalar, Scalar_ from pygeodesy.utily import degrees90, degrees180, sincos2d from pygeodesy.utmupsBase import _LLEB, _hemi, _parseUTMUPS5, \ _to4lldn, _to3zBhp, _to3zll, \ _UPS_LAT_MAX, _UPS_LAT_MIN, _UPS_ZONE, \ _UPS_ZONE_STR, UtmUpsBase from math import atan, atan2, radians, sqrt, tan __all__ = _ALL_LAZY.ups __version__ = '20.11.04' _Bands = _A_, 'B', 'Y', 'Z' # polar bands _EPS__2 = EPS**2 _Falsing = Meter(2000e3) # false easting and northing (C{meter}) _K0 = Scalar(0.994) # central UPS scale factor _K1 = Scalar(_1_0) # rescale point scale factor class UPSError(_ValueError): '''Universal Polar Stereographic (UPS) parse or other L{Ups} issue. ''' pass def _Band(a, b): # determine the polar band letter return _Bands[(0 if a < 0 else 2) + (0 if b < 0 else 1)] def _scale(E, rho, tau):
from pygeodesy.units import Meter, Lat, Scalar, Scalar_ from pygeodesy.utily import degrees90, degrees180, sincos2d from pygeodesy.utmupsBase import _LLEB, _hemi, _parseUTMUPS5, \ _to4lldn, _to3zBhp, _to3zll, \ _UPS_LAT_MAX, _UPS_LAT_MIN, _UPS_ZONE, \ _UPS_ZONE_STR, UtmUpsBase, UtmUps5Tuple, \ UtmUps8Tuple, UtmUpsLatLon5Tuple # PYCHOK indent from math import atan, atan2, radians, sqrt, tan __all__ = _ALL_LAZY.ups __version__ = '20.09.01' _Bands = 'A', 'B', 'Y', 'Z' #: (INTERNAL) Polar bands. _Falsing = Meter(2000e3) #: (INTERNAL) False easting and northing (C{meter}). _K0 = Scalar(0.994) #: (INTERNAL) Central UPS scale factor. _K1 = Scalar(1.0) #: (INTERNAL) Rescale point scale factor. class UPSError(_ValueError): '''Universal Polar Stereographic (UPS) parse or other L{Ups} issue. ''' pass def _Band(a, b): # determine the polar band letter return _Bands[(0 if a < 0 else 2) + (0 if b < 0 else 1)] def _scale(E, rho, tau):
def __new__(cls, e, n, azi, rk): return _NamedTuple.__new__(cls, Easting(e, Error=CSSError), Northing(n, Error=CSSError), Bearing(azi, Error=CSSError), Scalar(rk, Error=CSSError))
def __new__(cls, lat, lon, azi, rk): return _NamedTuple.__new__(cls, Lat(lat, Error=CSSError), Lon(lon, Error=CSSError), Bearing(azi, Error=CSSError), Scalar(rk, Error=CSSError))
def __new__(cls, x, y, lat, lon, g, k, datum): return _NamedTuple.__new__(cls, Scalar(x, name=_x_, Error=AlbersError), Scalar(y, name=_y_, Error=AlbersError), _Lat_(lat), _Lon_(lon), Degrees(g, name=_gamma_, Error=AlbersError), _Ks(k, _scale_), datum)
def reverse(self, x, y, lon0=0, name=NN, LatLon=None, **LatLon_kwds): '''Convert an east- and northing location to geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg lon0: Optional central meridian longitude (C{degrees}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Albers7Tuple}C{(x, y, lat, lon, gamma, scale, datum)}. @note: The origin latitude is returned by C{property lat0}. No false easting or northing is added. The returned value of C{lon} is in the range C{[-180..180] degrees} and C{lat} is in the range C{[-90..90] degrees}. If the given B{C{x}} or B{C{y}} point is outside the valid projected space the nearest pole is returned. ''' x = Scalar(x, name=_x_) y = Scalar(y, name=_y_) E = self.datum.ellipsoid k0 = self._k0 n0 = self._n0 k0n0 = self._k0n0 nrho0 = self._nrho0 txi0 = self._txi0 y_ = self._sign * y nx = k0n0 * x ny = k0n0 * y_ y1 = nrho0 - ny drho = den = nrho0 + hypot(nx, y1) # 0 implies origin with polar aspect if den: drho = fsum_(x * nx, -2 * y_ * nrho0, y_ * ny) * k0 / den # dsxia = scxi0 * dsxi dsxia = -self._scxi0 * (2 * nrho0 + n0 * drho) * drho / self._qZa2 t = 1 - dsxia * (2 * txi0 + dsxia) txi = (txi0 + dsxia) / (sqrt(t) if t > _EPSX2 else _EPSX) ta = self._tanf(txi) lat = degrees(atan(ta * self._sign)) b = atan2(nx, y1) lon = degrees(b / self._k02n0 if n0 else x / (y1 * k0)) if lon0: lon += _norm180(_Lon_(lon0, name=_lon0_)) lon = _norm180(lon) if LatLon is None: g = degrees360(self._sign * b) if den: k0 *= (nrho0 + n0 * drho) * hypot1(E.b_a * ta) / E.a r = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) else: kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(lat, lon, **kwds) return _xnamed(r, name or self.name)
def _nd2(p, d, r, _i_, *qs): # .toNvector and angular distance squared for q in qs: if p.isequalTo(q, EPS): raise _ValueError(points=p, txt=_coincident_) return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2
def _scale(E, rho, tau): # compute the point scale factor, ala Karney t = hypot1(tau) return Scalar((rho / E.a) * t * sqrt(E.e12 + E.e2 / t**2))
from pygeodesy.units import Easting, Lam_, Northing, Phi_, Scalar from pygeodesy.utily import degrees90, degrees180, sincos2 from math import cos, radians, sin, sqrt, tan __all__ = _ALL_LAZY.osgr __version__ = '20.09.01' _10um = 1e-5 #: (INTERNAL) 0.01 millimeter (C{meter}) _100km = 100000 #: (INTERNAL) 100 km (int meter) _A0 = Phi_(49) #: (INTERNAL) NatGrid true origin latitude, 49°N. _B0 = Lam_(-2) #: (INTERNAL) NatGrid true origin longitude, 2°W. _E0 = Easting(400e3) #: (INTERNAL) Easting of true origin (C{meter}). _N0 = Northing(-100e3) #: (INTERNAL) Northing of true origin (C{meter}). _F0 = Scalar( 0.9996012717) #: (INTERNAL) NatGrid scale of central meridian (C{float}). _Datums_OSGB36 = Datums.OSGB36 #: (INTERNAL) Airy130 ellipsoid _latlon_ = 'latlon' _no_convertDatum_ = 'no .convertDatum' _ord_A = ord('A') _TRIPS = 33 #: (INTERNAL) .toLatLon convergence def _ll2datum(ll, datum, name): '''(INTERNAL) Convert datum if needed. ''' if datum not in (None, ll.datum): try: ll = ll.convertDatum(datum) except AttributeError:
def __new__(cls, lat, lon, d, c, s): return _NamedTuple.__new__(cls, Lat(lat), Lon(lon), d, Scalar(c, name=_convergence_), Scalar(s, name=_scale_))