Skip to content

Commit 2e7e353

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 51caa8a commit 2e7e353

File tree

2 files changed

+92
-25
lines changed

2 files changed

+92
-25
lines changed

mapillary_tools/constants.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,33 @@ def _parse_scaled_integers(
137137
os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
138138
)
139139

140+
# Max amount of `MAPAltitude` value decimal digit positions written to image description file
141+
ALTITUDE_BASE10_DIGIT_PLACES_PRECISION: int = int(
142+
os.getenv(_ENV_PREFIX + "ALTITUDE_PRECISION_BASE10_DIGITS", 10)
143+
)
144+
# http://wiki.gis.com/wiki/index.php/Decimal_degrees
145+
# decimal places degrees distance
146+
# 0 1.0 111 km
147+
# 1 0.1 11.1 km
148+
# 2 0.01 1.11 km
149+
# 3 0.001 111 m
150+
# 4 0.0001 11.1 m
151+
# 5 0.00001 1.11 m
152+
# 6 0.000001 0.111 m
153+
# 7 0.0000001 1.11 cm
154+
# 8 0.00000001 1.11 mm
155+
# Max amount of `MAPLatitude` and `MAPLongitude` value decimal digit positions
156+
# written to image description file
157+
COORDINATE_BASE10_DIGIT_PLACES_PRECISION: int = int(
158+
os.getenv(_ENV_PREFIX + "COORDINATES_PRECISION_BASE10_DIGITS", 10)
159+
)
160+
# Max amount of `MAPCompassHeading` value decimal digit positions written to
161+
# image description file
162+
DIRECTION_BASE10_DIGIT_PLACES_PRECISION: int = int(
163+
os.getenv(_ENV_PREFIX + "DIRECTION_PRECISION_BASE10_DIGITS", 10)
164+
)
165+
# Smallest time delta in seconds (must not be user customized due to `datetime`’s fixed ε)
166+
_TIME_EPSILON: float = 1e-6
140167

141168
##################
142169
##### UPLOAD #####

mapillary_tools/serializer/description.py

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dataclasses
44
import datetime
55
import json
6+
import math
67
import sys
78
import typing as T
89
from pathlib import Path
@@ -21,6 +22,11 @@
2122
import jsonschema
2223

2324
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+
)
2430
from ..types import (
2531
BaseSerializer,
2632
describe_error_metadata,
@@ -33,22 +39,6 @@
3339
)
3440

3541

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-
5242
class _CompassHeading(TypedDict, total=True):
5343
TrueHeading: float
5444
MagneticHeading: float
@@ -209,6 +199,13 @@ class ErrorDescription(TypedDict, total=False):
209199
}
210200

211201

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+
212209
def _merge_schema(*schemas: dict) -> dict:
213210
for s in schemas:
214211
assert s.get("type") == "object", "must be all object schemas"
@@ -406,16 +403,37 @@ def _as_image_desc(cls, metadata: ImageMetadata) -> ImageDescription:
406403
"md5sum": metadata.md5sum,
407404
"filesize": metadata.filesize,
408405
"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+
),
411418
"MAPCaptureTime": build_capture_time(metadata.time),
412419
}
413420
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+
)
415427
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+
)
416434
desc["MAPCompassHeading"] = {
417-
"TrueHeading": round(metadata.angle, _ANGLE_PRECISION),
418-
"MagneticHeading": round(metadata.angle, _ANGLE_PRECISION),
435+
"TrueHeading": direction,
436+
"MagneticHeading": direction,
419437
}
420438
fields = dataclasses.fields(metadata)
421439
for field in fields:
@@ -498,10 +516,32 @@ class PointEncoder:
498516
def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]:
499517
entry = [
500518
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,
505545
]
506546
return entry
507547

0 commit comments

Comments
 (0)