fix(backend): keep GPS coordinates of 0 during metadata extraction - #1425
fix(backend): keep GPS coordinates of 0 during metadata extraction#1425prawnsgupta wants to merge 3 commits into
Conversation
extract_gps_coordinates() resolved its fallbacks with truthiness
(`lat or gps.get("latitude")`), so a latitude or longitude of exactly 0
-- a real location on the equator or the prime meridian -- was treated
as missing and overwritten with None before the `is not None` guard
could keep it. Photos from Kenya, Ecuador, Indonesia, the UK and
elsewhere lost their location entirely and never reached the
location-based Memories feature.
Each coordinate is now resolved through a _first_present() helper that
skips only None and blank strings, so 0 survives while genuinely absent
values still fall through to the next source. Source precedence
(top-level, then nested exif.gps, then the lat/lon aliases) is unchanged
and the -90..90 / -180..180 range validation is untouched.
The same truthiness test in the upload logging in images.py is corrected
too, since it otherwise omits valid zero coordinates from the log.
Adds tests covering the equator, the prime meridian, Null Island, the
nested and aliased field names, source precedence, and the existing
missing / out-of-range / malformed cases.
Fixes AOSSIE-Org#1406
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughGPS extraction now preserves valid zero coordinates, resolves latitude and longitude independently across metadata locations, normalizes malformed nested structures, and updates image logging checks. Tests cover precedence, validation, JSON and bytes input, invalid metadata, and datetime parsing. ChangesGPS coordinate handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/utils/extract_location_metadata.py`:
- Around line 101-112: The coordinate extraction in the metadata parsing method
must fall through unusable higher-priority values instead of selecting them
before conversion. Replace the shared _first_present selection for latitude and
longitude with independent candidate iteration that converts and range-checks
each value, continuing to the next source when malformed or out of range; add
automated regressions covering malformed and out-of-range top-level coordinates
with valid exif.gps fallbacks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 963bf3b0-801c-4ca8-bf30-320485acc4c3
📒 Files selected for processing (3)
backend/app/utils/extract_location_metadata.pybackend/app/utils/images.pybackend/tests/test_extract_location_metadata.py
Resolving a coordinate picked the first non-blank candidate and only then converted it, so a malformed or out-of-range value in a preferred field masked a valid value in a less preferred one: a top-level "latitude": "not-a-number" discarded a perfectly good exif.gps latitude and returned (None, None). _first_present is replaced by _resolve_coordinate, which converts and range checks each candidate in turn and continues past any that is absent, unreadable or outside its range. Latitude and longitude keep their existing precedence and are still resolved independently, and 0 is still preserved. Booleans are skipped explicitly, since bool subclasses int and float(True) would otherwise be accepted as latitude 1.0. NaN and infinity are rejected by the range check. An exception midway through resolution now returns (None, None) rather than a half-resolved pair. Adds tests for the resolver itself and for falling through malformed, out-of-range, blank and boolean values in a higher-priority field to a valid exif.gps or lat/lon alias, including falling through to a valid 0.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/utils/extract_location_metadata.py`:
- Around line 59-82: Update the candidate conversion in _resolve_coordinate to
catch OverflowError alongside ValueError and TypeError, so oversized integer
candidates are ignored and resolution continues to lower-priority values. Add
regression coverage using an oversized candidate such as 10**400 followed by a
valid fallback, preserving the fallback guarantee.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 561cbd6b-e1e6-4814-963c-25a179afc0af
📒 Files selected for processing (2)
backend/app/utils/extract_location_metadata.pybackend/tests/test_extract_location_metadata.py
float() raises OverflowError, not ValueError, on an integer beyond the float range, and JSON puts no size limit on integers. That escaped the conversion guard in _resolve_coordinate and unwound to the outer handler, so one oversized value abandoned the whole extraction and discarded a valid coordinate in a lower-priority field along with it. OverflowError now joins ValueError and TypeError, so an oversized integer is skipped like any other unreadable candidate and resolution continues. Adds coverage for oversized positive and negative integers, with and without a usable fallback, and through the extract_all JSON entry point where such a value would actually arrive.
|
@rohan-pandeyy — could you approve the CI workflow run on this PR when you have a moment? PR Check is currently at Per the testing steps in CONTRIBUTING.md, run locally against this branch:
The changes are limited to Let me know for any further changes. If no changes then feel free to merge the PR. |
Fixes #1406
The problem
MetadataExtractor.extract_gps_coordinates()resolved its fallback sources with truthiness rather than an explicitNonecheck:0.0is falsy in Python, so a latitude or longitude of exactly0— a real location on the equator or the prime meridian — was treated as missing and overwritten withNone. By the time execution reached the correctif lat is not None and lon is not None:guard the value was already gone, soextract_all()returned(None, None)for perfectly valid coordinates.extract_all()is called during image upload (app/utils/images.py:275) to populate the location fields, so photos taken in Kenya, Uganda, Ecuador, Indonesia, Brazil (equator) or the UK, France, Spain, Ghana, Algeria (prime meridian) lost their location entirely and never showed up in the location-based Memories feature.Before / after
Running
extract_all()on realistic metadata, onmainand on this branch:(None, None)(0.0, 36.8219)(None, None)(51.4779, 0.0)(0, 0)(None, None)(0.0, 0.0)exif.gps)(None, None)(0.0, 109.3425)lat/lonaliases)(None, None)(0.0, -78.4678)exif.gps(None, None)(28.6, 77.2)exif.gps(None, None)(28.6, 77.2)(28.6139, 77.209)(28.6139, 77.209)(None, None)(None, None)The fix
Each coordinate is resolved through
_resolve_coordinate(), which walks its candidate sources in order of preference and converts and range-checks each one in turn, returning the first that is actually usable. A candidate is skipped when it is:None, or a blank string),"not-a-number", a list, a dict),bool—boolsubclassesint, sofloat(True)would otherwise sail through as latitude1.0,±90/±180, which also rejectsNaNand infinity).0is explicitly kept, which is the actual bug: it is a real location but falsy in Python, so ana or bchain discarded it.Deliberately unchanged:
exif.gps, then thelat/lon/Latitude/Longitudealiases, exactly as before.±90/±180check still rejects genuinely invalid values.0is valid and was never what that check was meant to catch.(None, None).Two things that fall out of resolving each coordinate independently: a top-level
0.0can no longer be silently replaced by a different non-zero value fromexif.gps(the wrong location rather than no location), and an unusable value in a preferred field no longer masks a good one further down the chain.Also included
One line in
app/utils/images.py— the upload path's GPS log used the sameif latitude and longitude:truthiness test, so valid zero coordinates were extracted but omitted from the logs. Same root cause, log-only, no behavioural change beyond the log line.Scope
The separate
_extract_gps_coordinates()inapp/utils/images.pyparses GPS as DMS tuples and is not affected — a(0, 0, 0)DMS tuple is a non-empty tuple and therefore truthy — so this fix is correctly scoped toextract_location_metadata.py.Tests
backend/tests/test_extract_location_metadata.py— 81 tests covering the equator, prime meridian, Null Island, integer/string/negative zeros, the nested and aliased field names, source precedence, and the existing missing / half-present / out-of-range / unparseable / malformed-exifcases.Plus, per review, the resolver itself and the fallthrough behaviour: malformed, out-of-range, blank, boolean and non-finite values in a higher-priority field falling through to a valid
exif.gpsor alias, falling through more than one bad source, falling through to a valid0, and each coordinate falling through independently.Verified the tests catch the original bug: 19 of them fail on
main.Full backend suite: 567 passed.
blackandruffclean on the changed files.Summary by CodeRabbit
0(including when nested in EXIF).