|
3 | 3 | import dataclasses |
4 | 4 | import datetime |
5 | 5 | import json |
| 6 | +import math |
6 | 7 | import sys |
7 | 8 | import typing as T |
8 | 9 | from pathlib import Path |
|
21 | 22 | import jsonschema |
22 | 23 |
|
23 | 24 | from .. import exceptions, geo |
| 25 | +from ..constants import ( |
| 26 | + ALTITUDE_BASE10_DIGIT_PLACES_PRECISION, |
| 27 | + COORDINATE_BASE10_DIGIT_PLACES_PRECISION, |
| 28 | + DIRECTION_BASE10_DIGIT_PLACES_PRECISION, |
| 29 | +) |
24 | 30 | from ..types import ( |
25 | 31 | BaseSerializer, |
26 | 32 | describe_error_metadata, |
|
33 | 39 | ) |
34 | 40 |
|
35 | 41 |
|
36 | | -# http://wiki.gis.com/wiki/index.php/Decimal_degrees |
37 | | -# decimal places degrees distance |
38 | | -# 0 1.0 111 km |
39 | | -# 1 0.1 11.1 km |
40 | | -# 2 0.01 1.11 km |
41 | | -# 3 0.001 111 m |
42 | | -# 4 0.0001 11.1 m |
43 | | -# 5 0.00001 1.11 m |
44 | | -# 6 0.000001 0.111 m |
45 | | -# 7 0.0000001 1.11 cm |
46 | | -# 8 0.00000001 1.11 mm |
47 | | -_COORDINATES_PRECISION = 7 |
48 | | -_ALTITUDE_PRECISION = 3 |
49 | | -_ANGLE_PRECISION = 3 |
50 | | - |
51 | | - |
52 | 42 | class _CompassHeading(TypedDict, total=True): |
53 | 43 | TrueHeading: float |
54 | 44 | MagneticHeading: float |
@@ -209,6 +199,13 @@ class ErrorDescription(TypedDict, total=False): |
209 | 199 | } |
210 | 200 |
|
211 | 201 |
|
| 202 | +def _fraction_decimal_digits(value: float, precision: int) -> int: |
| 203 | + fraction_digits: int = precision - math.ceil( |
| 204 | + math.log10(value if (value := int(math.fabs(value))) > 0 else 1) |
| 205 | + ) |
| 206 | + return fraction_digits if fraction_digits >= 0 else 0 |
| 207 | + |
| 208 | + |
212 | 209 | def _merge_schema(*schemas: dict) -> dict: |
213 | 210 | for s in schemas: |
214 | 211 | assert s.get("type") == "object", "must be all object schemas" |
@@ -406,16 +403,37 @@ def _as_image_desc(cls, metadata: ImageMetadata) -> ImageDescription: |
406 | 403 | "md5sum": metadata.md5sum, |
407 | 404 | "filesize": metadata.filesize, |
408 | 405 | "filetype": FileType.IMAGE.value, |
409 | | - "MAPLatitude": round(metadata.lat, _COORDINATES_PRECISION), |
410 | | - "MAPLongitude": round(metadata.lon, _COORDINATES_PRECISION), |
| 406 | + "MAPLatitude": round( |
| 407 | + metadata.lat, |
| 408 | + _fraction_decimal_digits( |
| 409 | + metadata.lat, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 410 | + ), |
| 411 | + ), |
| 412 | + "MAPLongitude": round( |
| 413 | + metadata.lon, |
| 414 | + _fraction_decimal_digits( |
| 415 | + metadata.lon, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 416 | + ), |
| 417 | + ), |
411 | 418 | "MAPCaptureTime": build_capture_time(metadata.time), |
412 | 419 | } |
413 | 420 | if metadata.alt is not None: |
414 | | - desc["MAPAltitude"] = round(metadata.alt, _ALTITUDE_PRECISION) |
| 421 | + desc["MAPAltitude"] = round( |
| 422 | + metadata.alt, |
| 423 | + _fraction_decimal_digits( |
| 424 | + metadata.alt, ALTITUDE_BASE10_DIGIT_PLACES_PRECISION |
| 425 | + ), |
| 426 | + ) |
415 | 427 | if metadata.angle is not None: |
| 428 | + direction: float = round( |
| 429 | + metadata.angle, |
| 430 | + _fraction_decimal_digits( |
| 431 | + metadata.angle, DIRECTION_BASE10_DIGIT_PLACES_PRECISION |
| 432 | + ), |
| 433 | + ) |
416 | 434 | desc["MAPCompassHeading"] = { |
417 | | - "TrueHeading": round(metadata.angle, _ANGLE_PRECISION), |
418 | | - "MagneticHeading": round(metadata.angle, _ANGLE_PRECISION), |
| 435 | + "TrueHeading": direction, |
| 436 | + "MagneticHeading": direction, |
419 | 437 | } |
420 | 438 | fields = dataclasses.fields(metadata) |
421 | 439 | for field in fields: |
@@ -498,10 +516,32 @@ class PointEncoder: |
498 | 516 | def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]: |
499 | 517 | entry = [ |
500 | 518 | int(p.time * 1000), |
501 | | - round(p.lon, _COORDINATES_PRECISION), |
502 | | - round(p.lat, _COORDINATES_PRECISION), |
503 | | - round(p.alt, _ALTITUDE_PRECISION) if p.alt is not None else None, |
504 | | - round(p.angle, _ANGLE_PRECISION) if p.angle is not None else None, |
| 519 | + round( |
| 520 | + p.lon, |
| 521 | + _fraction_decimal_digits( |
| 522 | + p.lon, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 523 | + ), |
| 524 | + ), |
| 525 | + round( |
| 526 | + p.lat, |
| 527 | + _fraction_decimal_digits( |
| 528 | + p.lat, COORDINATE_BASE10_DIGIT_PLACES_PRECISION |
| 529 | + ), |
| 530 | + ), |
| 531 | + round( |
| 532 | + p.alt, |
| 533 | + _fraction_decimal_digits(p.alt, ALTITUDE_BASE10_DIGIT_PLACES_PRECISION), |
| 534 | + ) |
| 535 | + if p.alt is not None |
| 536 | + else None, |
| 537 | + round( |
| 538 | + p.angle, |
| 539 | + _fraction_decimal_digits( |
| 540 | + p.angle, DIRECTION_BASE10_DIGIT_PLACES_PRECISION |
| 541 | + ), |
| 542 | + ) |
| 543 | + if p.angle is not None |
| 544 | + else None, |
505 | 545 | ] |
506 | 546 | return entry |
507 | 547 |
|
|
0 commit comments