Skip to content

Commit adef9cc

Browse files
committed
fix: Full Exif tag precision and dynamic length decimal values for numeric values in image description file
fix: Drop redundant `MAPMagneticHeading` value computation (identical to `MAPTrueHeading`) feat: Add user control over value precision with `MAPILLARY_TOOLS_ALTITUDE_BASE10_DIGIT_PLACES_PRECISION`, `MAPILLARY_TOOLS_COORDINATE_BASE10_DIGIT_PLACES_PRECISION`, and `MAPILLARY_TOOLS_DIRECTION_BASE10_DIGIT_PLACES_PRECISION` environment variables
1 parent abc0056 commit adef9cc

2 files changed

Lines changed: 92 additions & 25 deletions

File tree

mapillary_tools/constants.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,33 @@ def _parse_scaled_integers(
149149
ZIGZAG_MIN_DEVIATIONS = int(os.getenv(_ENV_PREFIX + "ZIGZAG_MIN_DEVIATIONS", 1))
150150
ZIGZAG_MIN_DISTANCE = float(os.getenv(_ENV_PREFIX + "ZIGZAG_MIN_DISTANCE", 30))
151151

152+
# Max amount of `MAPAltitude` value decimal digit positions written to image description file
153+
ALTITUDE_BASE10_DIGIT_PLACES_PRECISION: int = int(
154+
os.getenv(_ENV_PREFIX + "ALTITUDE_PRECISION_BASE10_DIGITS", 10)
155+
)
156+
# http://wiki.gis.com/wiki/index.php/Decimal_degrees
157+
# decimal places degrees distance
158+
# 0 1.0 111 km
159+
# 1 0.1 11.1 km
160+
# 2 0.01 1.11 km
161+
# 3 0.001 111 m
162+
# 4 0.0001 11.1 m
163+
# 5 0.00001 1.11 m
164+
# 6 0.000001 0.111 m
165+
# 7 0.0000001 1.11 cm
166+
# 8 0.00000001 1.11 mm
167+
# Max amount of `MAPLatitude` and `MAPLongitude` value decimal digit positions
168+
# written to image description file
169+
COORDINATE_BASE10_DIGIT_PLACES_PRECISION: int = int(
170+
os.getenv(_ENV_PREFIX + "COORDINATE_PRECISION_BASE10_DIGITS", 10)
171+
)
172+
# Max amount of `MAPCompassHeading` value decimal digit positions written to
173+
# image description file
174+
DIRECTION_BASE10_DIGIT_PLACES_PRECISION: int = int(
175+
os.getenv(_ENV_PREFIX + "DIRECTION_PRECISION_BASE10_DIGITS", 10)
176+
)
177+
# Smallest time delta in seconds (must not be user customizable due to `datetime`’s fixed ε)
178+
_TIME_EPSILON: float = 1e-6
152179

153180
##################
154181
##### UPLOAD #####

mapillary_tools/serializer/description.py

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dataclasses
99
import datetime
1010
import json
11+
import math
1112
import sys
1213
import typing as T
1314
from pathlib import Path
@@ -26,6 +27,11 @@
2627
import jsonschema
2728

2829
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+
)
2935
from ..types import (
3036
BaseSerializer,
3137
describe_error_metadata,
@@ -38,22 +44,6 @@
3844
)
3945

4046

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-
5747
class _CompassHeading(TypedDict, total=True):
5848
TrueHeading: float
5949
MagneticHeading: float
@@ -223,6 +213,13 @@ class ErrorDescription(TypedDict, total=False):
223213
}
224214

225215

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+
226223
def _merge_schema(*schemas: dict) -> dict:
227224
for s in schemas:
228225
assert s.get("type") == "object", "must be all object schemas"
@@ -422,16 +419,37 @@ def _as_image_desc(cls, metadata: ImageMetadata) -> ImageDescription:
422419
"md5sum": metadata.md5sum,
423420
"filesize": metadata.filesize,
424421
"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+
),
427434
"MAPCaptureTime": build_capture_time(metadata.time),
428435
}
429436
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+
)
431443
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+
)
432450
desc["MAPCompassHeading"] = {
433-
"TrueHeading": round(metadata.angle, _ANGLE_PRECISION),
434-
"MagneticHeading": round(metadata.angle, _ANGLE_PRECISION),
451+
"TrueHeading": direction,
452+
"MagneticHeading": direction,
435453
}
436454
fields = dataclasses.fields(metadata)
437455
for field in fields:
@@ -515,10 +533,32 @@ class PointEncoder:
515533
def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]:
516534
entry: list[float | int | None] = [
517535
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,
522562
p.get_gps_epoch_time(),
523563
]
524564
return entry

0 commit comments

Comments
 (0)