From 2211ea3d384e909a52a5ac1b6d208dad3ef1d293 Mon Sep 17 00:00:00 2001 From: stark256-spec Date: Wed, 3 Jun 2026 11:15:38 -0500 Subject: [PATCH] fix: avoid scientific notation in CMR URL coordinate parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python's default float formatting uses scientific notation for numbers outside roughly [1e-4, 1e16) — e.g. 0.00001 becomes 1e-05. CMR rejects scientific notation in URL parameters with "is not a valid URL encoded point", causing otherwise valid queries to fail silently. Add _format_float() which falls back to fixed-point formatting whenever the default %g representation contains 'e' or 'E'. Apply it in all five coordinate methods: point(), circle(), polygon(), bounding_box(), line(). Fixes #108 --- cmr/queries.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/cmr/queries.py b/cmr/queries.py index cdf9f2e..3e1c33a 100644 --- a/cmr/queries.py +++ b/cmr/queries.py @@ -39,6 +39,25 @@ PointLike: TypeAlias = Tuple[FloatLike, FloatLike] +def _format_float(value: Union[float, int, str]) -> str: + """Format a number as a plain decimal string, never using scientific notation. + + Python's default str() switches to scientific notation for floats outside + roughly [1e-4, 1e16), e.g. ``1e-05`` for ``0.00001``. CMR rejects + scientific notation in URL parameters with "is not a valid URL encoded point". + + Integers and strings are returned as-is (``str(1000)`` → ``"1000"``). + Floats use their natural str() representation (``str(1.0)`` → ``"1.0"``) + unless that produces scientific notation, in which case a fixed-point + decimal is returned instead. + """ + s = str(value) + if "e" not in s and "E" not in s: + return s + # Scientific notation detected: convert to plain decimal, strip trailing zeros + return f"{float(value):.15f}".rstrip("0").rstrip(".") + + class Query: """ Base class for all CMR queries. @@ -618,7 +637,7 @@ def point(self, lon: FloatLike, lat: FloatLike) -> Self: if "point" not in self.params: self.params["point"] = [] - self.params["point"].append(f"{lon},{lat}") + self.params["point"].append(f"{_format_float(lon)},{_format_float(lat)}") return self @@ -630,7 +649,7 @@ def circle(self, lon: FloatLike, lat: FloatLike, dist: FloatLike) -> Self: :param dist: distance in meters around waypoint (lat,lon) :returns: self """ - self.params['circle'] = f"{lon},{lat},{dist}" + self.params['circle'] = f"{_format_float(lon)},{_format_float(lat)},{_format_float(dist)}" return self @@ -672,7 +691,7 @@ def polygon(self, coordinates: Sequence[PointLike]) -> Self: ) # convert to strings - as_strs = [str(val) for val in as_floats] + as_strs = [_format_float(val) for val in as_floats] self.params["polygon"] = ",".join(as_strs) @@ -697,7 +716,8 @@ def bounding_box( """ self.params["bounding_box"] = ( - f"{float(lower_left_lon)},{float(lower_left_lat)},{float(upper_right_lon)},{float(upper_right_lat)}" + f"{_format_float(float(lower_left_lon))},{_format_float(float(lower_left_lat))}," + f"{_format_float(float(upper_right_lon))},{_format_float(float(upper_right_lat))}" ) return self @@ -734,7 +754,7 @@ def line(self, coordinates: Sequence[PointLike]) -> Self: as_floats.extend([float(lon), float(lat)]) # cast back to string for join - as_strs = [str(val) for val in as_floats] + as_strs = [_format_float(val) for val in as_floats] self.params["line"] = ",".join(as_strs)