|
8 | 8 | import dataclasses |
9 | 9 | import datetime |
10 | 10 | import json |
| 11 | +import math |
11 | 12 | import sys |
12 | 13 | import typing as T |
13 | 14 | from pathlib import Path |
|
26 | 27 | import jsonschema |
27 | 28 |
|
28 | 29 | from .. import exceptions, geo, telemetry |
| 30 | +from ..constants import ( |
| 31 | + ALTITUDE_BASE10_DIGIT_PLACES_PRECISION, |
| 32 | + COORDINATE_BASE10_DIGIT_PLACES_PRECISION, |
| 33 | + DIRECTION_BASE10_DIGIT_PLACES_PRECISION, |
| 34 | +) |
29 | 35 | from ..types import ( |
30 | 36 | BaseSerializer, |
31 | 37 | describe_error_metadata, |
|
38 | 44 | ) |
39 | 45 |
|
40 | 46 |
|
41 | | -# http://wiki.gis.com/wiki/index.php/Decimal_degrees |
42 | | -# decimal places degrees distance |
43 | | -# 0 1.0 111 km |
44 | | -# 1 0.1 11.1 km |
45 | | -# 2 0.01 1.11 km |
46 | | -# 3 0.001 111 m |
47 | | -# 4 0.0001 11.1 m |
48 | | -# 5 0.00001 1.11 m |
49 | | -# 6 0.000001 0.111 m |
50 | | -# 7 0.0000001 1.11 cm |
51 | | -# 8 0.00000001 1.11 mm |
52 | | -_COORDINATES_PRECISION = 7 |
53 | | -_ALTITUDE_PRECISION = 3 |
54 | | -_ANGLE_PRECISION = 3 |
55 | | - |
56 | | - |
57 | 47 | class _CompassHeading(TypedDict, total=True): |
58 | 48 | TrueHeading: float |
59 | 49 | MagneticHeading: float |
@@ -223,6 +213,13 @@ class ErrorDescription(TypedDict, total=False): |
223 | 213 | } |
224 | 214 |
|
225 | 215 |
|
| 216 | +def _fraction_decimal_digits(value: float, precision: int) -> int: |
| 217 | + fraction_digits: int = precision - math.ceil( |
| 218 | + math.log10(value if (value := int(math.fabs(value))) > 0 else 1) |
| 219 | + ) |
| 220 | + return fraction_digits if fraction_digits >= 0 else 0 |
| 221 | + |
| 222 | + |
226 | 223 | def _merge_schema(*schemas: dict) -> dict: |
227 | 224 | for s in schemas: |
228 | 225 | assert s.get("type") == "object", "must be all object schemas" |
@@ -422,16 +419,37 @@ def _as_image_desc(cls, metadata: ImageMetadata) -> ImageDescription: |
422 | 419 | "md5sum": metadata.md5sum, |
423 | 420 | "filesize": metadata.filesize, |
424 | 421 | "filetype": FileType.IMAGE.value, |
425 | | - "MAPLatitude": round(metadata.lat, _COORDINATES_PRECISION), |
426 | | - "MAPLongitude": round(metadata.lon, _COORDINATES_PRECISION), |
| 422 | + "MAPLatitude": round( |
| 423 | + metadata.lat, |
| 424 | + _fraction_decimal_digits( |
| 425 | + metadata.lat, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 426 | + ), |
| 427 | + ), |
| 428 | + "MAPLongitude": round( |
| 429 | + metadata.lon, |
| 430 | + _fraction_decimal_digits( |
| 431 | + metadata.lon, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 432 | + ), |
| 433 | + ), |
427 | 434 | "MAPCaptureTime": build_capture_time(metadata.time), |
428 | 435 | } |
429 | 436 | if metadata.alt is not None: |
430 | | - desc["MAPAltitude"] = round(metadata.alt, _ALTITUDE_PRECISION) |
| 437 | + desc["MAPAltitude"] = round( |
| 438 | + metadata.alt, |
| 439 | + _fraction_decimal_digits( |
| 440 | + metadata.alt, ALTITUDE_BASE10_DIGIT_PLACES_PRECISION |
| 441 | + ), |
| 442 | + ) |
431 | 443 | if metadata.angle is not None: |
| 444 | + direction: float = round( |
| 445 | + metadata.angle, |
| 446 | + _fraction_decimal_digits( |
| 447 | + metadata.angle, DIRECTION_BASE10_DIGIT_PLACES_PRECISION |
| 448 | + ), |
| 449 | + ) |
432 | 450 | desc["MAPCompassHeading"] = { |
433 | | - "TrueHeading": round(metadata.angle, _ANGLE_PRECISION), |
434 | | - "MagneticHeading": round(metadata.angle, _ANGLE_PRECISION), |
| 451 | + "TrueHeading": direction, |
| 452 | + "MagneticHeading": direction, |
435 | 453 | } |
436 | 454 | fields = dataclasses.fields(metadata) |
437 | 455 | for field in fields: |
@@ -515,10 +533,32 @@ class PointEncoder: |
515 | 533 | def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]: |
516 | 534 | entry: list[float | int | None] = [ |
517 | 535 | int(p.time * 1000), |
518 | | - round(p.lon, _COORDINATES_PRECISION), |
519 | | - round(p.lat, _COORDINATES_PRECISION), |
520 | | - round(p.alt, _ALTITUDE_PRECISION) if p.alt is not None else None, |
521 | | - round(p.angle, _ANGLE_PRECISION) if p.angle is not None else None, |
| 536 | + round( |
| 537 | + p.lon, |
| 538 | + _fraction_decimal_digits( |
| 539 | + p.lon, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 540 | + ), |
| 541 | + ), |
| 542 | + round( |
| 543 | + p.lat, |
| 544 | + _fraction_decimal_digits( |
| 545 | + p.lat, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 546 | + ), |
| 547 | + ), |
| 548 | + round( |
| 549 | + p.alt, |
| 550 | + _fraction_decimal_digits(p.alt, ALTITUDE_BASE10_DIGIT_PLACES_PRECISION), |
| 551 | + ) |
| 552 | + if p.alt is not None |
| 553 | + else None, |
| 554 | + round( |
| 555 | + p.angle, |
| 556 | + _fraction_decimal_digits( |
| 557 | + p.angle, DIRECTION_BASE10_DIGIT_PLACES_PRECISION |
| 558 | + ), |
| 559 | + ) |
| 560 | + if p.angle is not None |
| 561 | + else None, |
522 | 562 | p.get_gps_epoch_time(), |
523 | 563 | ] |
524 | 564 | return entry |
|
0 commit comments