Skip to content

Commit cdcabc5

Browse files
author
Alessandro Dalvit
committed
Separate usage of Point and GpsPoint
1 parent 628c5e6 commit cdcabc5

24 files changed

Lines changed: 155 additions & 114 deletions

mapillary_tools/exiftool_read_video.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def _aggregate_gps_track(
8787
alt_tag: T.Optional[str] = None,
8888
direction_tag: T.Optional[str] = None,
8989
ground_speed_tag: T.Optional[str] = None,
90-
) -> T.List[geo.PointWithFix]:
90+
) -> T.List[geo.GpsPoint]:
9191
"""
9292
Aggregate all GPS data by the tags.
9393
It requires lat, lon to be present, and their lengths must match.
@@ -173,7 +173,7 @@ def _aggregate_float_values_same_length(
173173
if timestamp is None or lon is None or lat is None:
174174
continue
175175
track.append(
176-
geo.PointWithFix(
176+
geo.GpsPoint(
177177
time=timestamp,
178178
lon=lon,
179179
lat=lat,
@@ -182,6 +182,7 @@ def _aggregate_float_values_same_length(
182182
gps_fix=None,
183183
gps_precision=None,
184184
gps_ground_speed=ground_speed,
185+
unix_timestamp_ms=int(timestamp * 1000),
185186
)
186187
)
187188

@@ -230,8 +231,8 @@ def _aggregate_gps_track_by_sample_time(
230231
ground_speed_tag: T.Optional[str] = None,
231232
gps_fix_tag: T.Optional[str] = None,
232233
gps_precision_tag: T.Optional[str] = None,
233-
) -> T.List[geo.PointWithFix]:
234-
track: T.List[geo.PointWithFix] = []
234+
) -> T.List[geo.GpsPoint]:
235+
track: T.List[geo.GpsPoint] = []
235236

236237
expanded_gps_fix_tag = None
237238
if gps_fix_tag is not None:
@@ -298,21 +299,21 @@ def __init__(
298299
self._texts_by_tag = _index_text_by_tag(self.etree.getroot())
299300
self._all_tags = set(self._texts_by_tag.keys())
300301

301-
def extract_gps_track(self) -> T.List[geo.Point]:
302+
def extract_gps_track(self) -> T.List[geo.GpsPoint]:
302303
# blackvue and many other cameras
303304
track_with_fix = self._extract_gps_track_from_quicktime()
304305
if track_with_fix:
305-
return T.cast(T.List[geo.Point], track_with_fix)
306+
return track_with_fix
306307

307308
# insta360 has its own tag
308309
track_with_fix = self._extract_gps_track_from_quicktime(namespace="Insta360")
309310
if track_with_fix:
310-
return T.cast(T.List[geo.Point], track_with_fix)
311+
return track_with_fix
311312

312313
# mostly for gopro
313314
track_with_fix = self._extract_gps_track_from_track()
314315
if track_with_fix:
315-
return T.cast(T.List[geo.Point], track_with_fix)
316+
return track_with_fix
316317

317318
return []
318319

@@ -355,7 +356,7 @@ def extract_model(self) -> T.Optional[str]:
355356
_, model = self._extract_make_and_model()
356357
return model
357358

358-
def _extract_gps_track_from_track(self) -> T.List[geo.PointWithFix]:
359+
def _extract_gps_track_from_track(self) -> T.List[geo.GpsPoint]:
359360
for track_id in range(1, MAX_TRACK_ID + 1):
360361
track_ns = f"Track{track_id}"
361362
if self._all_tags_exists(
@@ -397,7 +398,7 @@ def _all_tags_exists(self, tags: T.Set[str]) -> bool:
397398

398399
def _extract_gps_track_from_quicktime(
399400
self, namespace: str = "QuickTime"
400-
) -> T.List[geo.PointWithFix]:
401+
) -> T.List[geo.GpsPoint]:
401402
if not self._all_tags_exists(
402403
{
403404
expand_tag(f"{namespace}:GPSDateTime"),

mapillary_tools/geo.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ class GPSFix(Enum):
4040

4141

4242
@dataclasses.dataclass
43-
class PointWithFix(Point):
44-
gps_fix: T.Optional[GPSFix]
45-
gps_precision: T.Optional[float]
46-
gps_ground_speed: T.Optional[float]
43+
class GpsPoint(Point):
44+
gps_fix: T.Optional[GPSFix] = None
45+
gps_precision: T.Optional[float] = None
46+
gps_ground_speed: T.Optional[float] = None
47+
unix_timestamp_ms: T.Optional[int] = None
48+
gps_horizontal_accuracy: T.Optional[float] = None
4749

4850

4951
def _ecef_from_lla_DEPRECATED(

mapillary_tools/geotag/blackvue_parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727

2828

29-
def _parse_gps_box(gps_data: bytes) -> T.Generator[geo.Point, None, None]:
29+
def _parse_gps_box(gps_data: bytes) -> T.Generator[geo.GpsPoint, None, None]:
3030
for line_bytes in gps_data.splitlines():
3131
match = NMEA_LINE_REGEX.match(line_bytes)
3232
if match is None:
@@ -44,7 +44,7 @@ def _parse_gps_box(gps_data: bytes) -> T.Generator[geo.Point, None, None]:
4444
if not nmea.is_valid:
4545
continue
4646
epoch_ms = int(match.group(1))
47-
yield geo.Point(
47+
yield geo.GpsPoint(
4848
time=epoch_ms,
4949
lat=nmea.latitude,
5050
lon=nmea.longitude,
@@ -90,7 +90,7 @@ def extract_camera_model(fp: T.BinaryIO) -> str:
9090
return ""
9191

9292

93-
def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
93+
def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.GpsPoint]]:
9494
gps_data = simple_mp4_parser.parse_mp4_data_first(fp, [b"free", b"gps "])
9595
if gps_data is None:
9696
return None
@@ -108,7 +108,7 @@ def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
108108
return points
109109

110110

111-
def parse_gps_points(path: pathlib.Path) -> T.List[geo.Point]:
111+
def parse_gps_points(path: pathlib.Path) -> T.List[geo.GpsPoint]:
112112
with path.open("rb") as fp:
113113
points = extract_points(fp)
114114

mapillary_tools/geotag/camm_parser.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dataclasses
44
import io
55
import logging
6+
import math
67
import pathlib
78
import typing as T
89
from enum import Enum
@@ -81,12 +82,12 @@ class CAMMType(Enum):
8182

8283
def _parse_point_from_sample(
8384
fp: T.BinaryIO, sample: sample_parser.Sample
84-
) -> T.Optional[geo.Point]:
85+
) -> T.Optional[geo.GpsPoint]:
8586
fp.seek(sample.offset, io.SEEK_SET)
8687
data = fp.read(sample.size)
8788
box = CAMMSampleData.parse(data)
8889
if box.type == CAMMType.MIN_GPS.value:
89-
return geo.Point(
90+
return geo.GpsPoint(
9091
time=sample.time_offset,
9192
lat=box.data[0],
9293
lon=box.data[1],
@@ -96,19 +97,39 @@ def _parse_point_from_sample(
9697
elif box.type == CAMMType.GPS.value:
9798
# Not using box.data.time_gps_epoch as the point timestamp
9899
# because it is from another clock
99-
return geo.Point(
100+
speed = math.sqrt(
101+
math.pow(box.data.velocity_east, 2) + math.pow(box.data.velocity_north, 2)
102+
)
103+
104+
return geo.GpsPoint(
100105
time=sample.time_offset,
101106
lat=box.data.latitude,
102107
lon=box.data.longitude,
103108
alt=box.data.altitude,
104109
angle=None,
110+
unix_timestamp_ms=int(
111+
_gps_timestamp_to_unix(box.data.time_gps_epoch) * 1000
112+
),
113+
gps_fix=geo.GPSFix(box.data.gps_fix_type),
114+
gps_ground_speed=speed,
115+
gps_horizontal_accuracy=box.data.horizontal_accuracy,
105116
)
106117
return None
107118

108119

120+
def _gps_timestamp_to_unix(ts: float) -> float:
121+
"""
122+
Convert a GPS timestamp to UNIX timestamp.
123+
See https://stackoverflow.com/questions/20521750/ticks-between-unix-epoch-and-gps-epoch
124+
for some info on GPS timestamp. Here we arbitrarily use the offset as of 2023, since we
125+
are not interested in precision to the second.
126+
"""
127+
return ts + 315964800 - 18
128+
129+
109130
def filter_points_by_elst(
110-
points: T.Iterable[geo.Point], elst: T.Sequence[T.Tuple[float, float]]
111-
) -> T.Generator[geo.Point, None, None]:
131+
points: T.Iterable[geo.GpsPoint], elst: T.Sequence[T.Tuple[float, float]]
132+
) -> T.Generator[geo.GpsPoint, None, None]:
112133
empty_elst = [entry for entry in elst if entry[0] == -1]
113134
if empty_elst:
114135
offset = empty_elst[-1][1]
@@ -159,7 +180,7 @@ def _extract_camm_samples(
159180
yield from camm_samples
160181

161182

162-
def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
183+
def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.GpsPoint]]:
163184
"""
164185
Return a list of points (could be empty) if it is a valid CAMM video,
165186
otherwise None
@@ -223,7 +244,7 @@ def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
223244
return points
224245

225246

226-
def parse_gpx(path: pathlib.Path) -> T.List[geo.Point]:
247+
def parse_gpx(path: pathlib.Path) -> T.List[geo.GpsPoint]:
227248
with path.open("rb") as fp:
228249
points = extract_points(fp)
229250
if points is None:

mapillary_tools/geotag/geotag_images_from_gpx_file.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ def __init__(
3232
len(tracks),
3333
source_path,
3434
)
35-
self.points: T.List[geo.Point] = sum(tracks, [])
35+
points: T.List[geo.GpsPoint] = sum(tracks, [])
36+
self.points = [
37+
geo.Point(time=p.time, lat=p.lat, lon=p.lon, alt=p.alt, angle=p.angle)
38+
for p in points
39+
]
3640
self.image_paths = image_paths
3741
self.source_path = source_path
3842
self.use_gpx_start_time = use_gpx_start_time
@@ -120,7 +124,7 @@ def to_description(self) -> T.List[types.ImageMetadataOrError]:
120124
)
121125

122126

123-
Track = T.List[geo.Point]
127+
Track = T.List[geo.GpsPoint]
124128

125129

126130
def parse_gpx(gpx_file: Path) -> T.List[Track]:
@@ -134,13 +138,18 @@ def parse_gpx(gpx_file: Path) -> T.List[Track]:
134138
tracks.append([])
135139
for point in segment.points:
136140
if point.time is not None:
141+
time = geo.as_unix_time(point.time) if point.time else 0
137142
tracks[-1].append(
138-
geo.Point(
139-
time=geo.as_unix_time(point.time),
143+
geo.GpsPoint(
144+
time=time,
140145
lat=point.latitude,
141146
lon=point.longitude,
142147
alt=point.elevation,
143148
angle=None,
149+
gps_fix=None,
150+
gps_precision=None,
151+
gps_ground_speed=point.speed,
152+
unix_timestamp_ms=int(time * 1000),
144153
)
145154
)
146155

mapillary_tools/geotag/geotag_images_from_nmea_file.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(
2727
)
2828

2929

30-
def get_lat_lon_time_from_nmea(nmea_file: Path) -> T.List[geo.Point]:
30+
def get_lat_lon_time_from_nmea(nmea_file: Path) -> T.List[geo.GpsPoint]:
3131
with nmea_file.open("r") as f:
3232
lines = f.readlines()
3333
lines = [line.rstrip("\n\r") for line in lines]
@@ -49,10 +49,19 @@ def get_lat_lon_time_from_nmea(nmea_file: Path) -> T.List[geo.Point]:
4949
if "$GPGGA" in line:
5050
data = pynmea2.parse(line)
5151
dt = datetime.datetime.combine(date, data.timestamp)
52+
time = geo.as_unix_time(dt)
5253
lat, lon, alt = data.latitude, data.longitude, data.altitude
5354
points.append(
54-
geo.Point(
55-
time=geo.as_unix_time(dt), lat=lat, lon=lon, alt=alt, angle=None
55+
geo.GpsPoint(
56+
time=time,
57+
lat=lat,
58+
lon=lon,
59+
alt=alt,
60+
angle=None,
61+
unix_timestamp_ms=int(time * 1000),
62+
gps_fix=None,
63+
gps_ground_speed=None,
64+
gps_precision=None,
5665
)
5766
)
5867

mapillary_tools/geotag/geotag_videos_from_exiftool_video.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,9 @@ def geotag_video(element: ET.Element) -> types.VideoMetadataOrError:
4545
points = geo.extend_deduplicate_points(points)
4646
assert points, "must have at least one point"
4747

48-
if all(isinstance(p, geo.PointWithFix) for p in points):
49-
points = T.cast(
50-
T.List[geo.Point],
51-
gpmf_gps_filter.remove_noisy_points(
52-
T.cast(T.List[geo.PointWithFix], points)
53-
),
54-
)
55-
if not points:
56-
raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
48+
points = list(gpmf_gps_filter.remove_noisy_points(points))
49+
if not points:
50+
raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
5751

5852
stationary = video_utils.is_video_stationary(
5953
geo.get_max_distance_from_start([(p.lat, p.lon) for p in points])
@@ -66,7 +60,7 @@ def geotag_video(element: ET.Element) -> types.VideoMetadataOrError:
6660
video_path,
6761
md5sum=None,
6862
filetype=types.FileType.VIDEO,
69-
points=points,
63+
points=list(points),
7064
make=exif.extract_make(),
7165
model=exif.extract_model(),
7266
)

mapillary_tools/geotag/geotag_videos_from_video.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ def geotag_video(
160160
video_metadata.points = geo.extend_deduplicate_points(video_metadata.points)
161161
assert video_metadata.points, "must have at least one point"
162162

163-
if all(isinstance(p, geo.PointWithFix) for p in video_metadata.points):
163+
if all(isinstance(p, geo.GpsPoint) for p in video_metadata.points):
164164
video_metadata.points = T.cast(
165165
T.List[geo.Point],
166166
gpmf_gps_filter.remove_noisy_points(
167-
T.cast(T.List[geo.PointWithFix], video_metadata.points)
167+
T.cast(T.List[geo.GpsPoint], video_metadata.points)
168168
),
169169
)
170170
if not video_metadata.points:

mapillary_tools/geotag/gpmf_gps_filter.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414

1515
def remove_outliers(
16-
sequence: T.Sequence[geo.PointWithFix],
17-
) -> T.Sequence[geo.PointWithFix]:
16+
sequence: T.Sequence[geo.GpsPoint],
17+
) -> T.Sequence[geo.GpsPoint]:
1818
distances = [
1919
geo.gps_distance((left.lat, left.lon), (right.lat, right.lon))
2020
for left, right in geo.pairwise(sequence)
@@ -50,14 +50,14 @@ def remove_outliers(
5050
)
5151

5252
return T.cast(
53-
T.List[geo.PointWithFix],
53+
T.List[geo.GpsPoint],
5454
gps_filter.find_majority(merged.values()),
5555
)
5656

5757

5858
def remove_noisy_points(
59-
sequence: T.Sequence[geo.PointWithFix],
60-
) -> T.Sequence[geo.PointWithFix]:
59+
sequence: T.Sequence[geo.GpsPoint],
60+
) -> T.Sequence[geo.GpsPoint]:
6161
num_points = len(sequence)
6262
sequence = [
6363
p

0 commit comments

Comments
 (0)