Skip to content

Image footprint coverage - #850

Open
shreya-hegde wants to merge 3 commits into
hotosm:mainfrom
shreya-hegde:image-footprint-coverage
Open

Image footprint coverage#850
shreya-hegde wants to merge 3 commits into
hotosm:mainfrom
shreya-hegde:image-footprint-coverage

Conversation

@shreya-hegde

@shreya-hegde shreya-hegde commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation
  • 🧑‍💻 Refactor
  • ✅ Test
  • 🤖 Build or CI
  • ❓ Other (please specify)

Related Issue

Fixes #819

Describe this PR

What changed

  • Added rectangular image footprints to task verification.
  • Coverage now uses the union of those footprints instead of over-generous point coverage.
  • Draws footprint outlines on the map.
  • Clicking an image highlights its matching footprint.
  • Added FC9313 for DJI Mini 5 Pro.

Notes

  • Footprints use project altitude first, then EXIF relative altitude.
  • Avoided EXIF absolute altitude because it is height above sea level and made coverage too large.

Tested

AI Tool Usage

  • No AI tools were used
  • [ x] AI tools were used (complete below)

If AI-assisted:

  • Tool(s) used: Codex
  • What was generated: Draft code changes, explanations, and debugging guidance.
  • What you reviewed and changed: Reviewed the code, ran local/Docker tests, checked UI behavior, and adjusted altitude/footprint logic.

Screenshots

If applicable.

Alternative Approaches Considered

Did you consider other approaches? Why this one?

Review Guide

Notes for the reviewer. How to test this change?

Main things to look at:

  • image_footprints.py: footprint size/shape calculation and union coverage logic.
  • image_classification.py: task verification now uses footprint coverage instead of the old circular buffer coverage.
  • TaskVerificationModal.tsx: footprint outlines are drawn on the map and selected image footprints are highlighted.

Things I’d especially like feedback on:

  • Whether using project altitude first, then EXIF relative altitude, is the right assumption.
  • Whether the Identify Flight Gaps flow should be updated to use the same footprints.

Checklist

  • [ x] I have read the Contributing Guide
  • [ x] I have read the Code of Conduct
  • [x ] PR is focused and small
  • [ x] Tests are included or updated
  • [ x] I understand all code in this PR and can answer questions about it
  • [ x] No secrets, credentials, or sensitive data are included
  • [x ] Commit messages are descriptive
  • Related docs and screenshots are updated

[optional] What gif best describes this PR or how it makes you feel?

https://giphy.com/gifs/square-triangle-shape-slap-V6kvt4UVqfHha3Eb19 (cuz the rectangles/squares are the superior shapes in image coverage)

drone_type.py: add FC9313 -> DJI_MINI_5_PRO.
image_footprints.py: new helper functions to calculate one image’s rectangle on the ground, build footprint GeoJSON, and compute its union coverage percentage.
image_classification.py: call image_footprints.py instead of circle ST_Buffer logic, then return coverage_percentage and image_footprints.
classification.ts: add image_footprints to the frontend response type.
TaskVerificationModal.tsx: draw image_footprints as thin rectangle outlines on the map. check if the overlap is too much/hard to see
test_flight_gap_detection.py: test FC9313 works
test_image_footprints: test the footprint code
Changed absolute altitude to relative altitude
Highlight the rectangles based on image selected
@github-actions

Copy link
Copy Markdown
Contributor

Contributor Signature Required

Thank you for your contribution!

Before we can accept your pull request, you need to sign our Contribution Policy.

Why do I need to do this?

  • Ensures you have read and will abide by our contribution guidelines.
  • Adds a small barrier for bot / AI account contribution, which we do not allow.
  • We care deeply about our community at HOT, and want everyone to be on the same page.

How to sign

To sign the agreement, please comment on this PR with:

I have read the CONTRIBUTING.md document and I hereby sign and agree with the guidelines

⚠️ Note: You only need to sign once. Future contributions to this repository will not require re-signing.


I have read the CONTRIBUTING.md document and I hereby sign and agree with the guidelines


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@github-actions github-actions Bot added backend Related to backend code frontend dependency:drone-flightplan Requires updates in drone-flightplan labels Jul 16, 2026
@shreya-hegde

Copy link
Copy Markdown
Collaborator Author

I have read the CONTRIBUTING.md document and I hereby sign and agree with the guidelines

@spwoodcock spwoodcock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work!! This is looking great 😄

Left some small comments. The main one is about the mercator project towards the poles, which will distort the footprint calcs a lot

# Build in meters first, then convert back to lon/lat only for display.
width_m, height_m = footprint_size
# Convert image GPS point into meter coordinates
center = transform(projector.transform, shape(image["location"]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be an EPSG:3857 (web mercator) coordination, but the footprint calculated below is true ground meters, so the calculations as we get closer to the equator will be increasingly incorrect.

I would propose a simple solution is to correct by the 1/cos(latitude) adjustment that web mercator uses:

center = transform(projector.transform, shape(image["location"]))
x, y = center.x, center.y

# EPSG:3857 stretches ground distance by 1/cos(lat). Scale the rectangle
# so its footprint is stretched to match the mercator projection.
lat_deg = image["location"]["coordinates"][1]
mercator_scale = 1 / math.cos(math.radians(lat_deg))
half_width = (width_m / 2) * mercator_scale
half_height = (height_m / 2) * mercator_scale

# A tiny fake image where GSD makes the footprint exactly 10m x 10m.
image = {
"id": "image-1",
"location": {"type": "Point", "coordinates": [0, 0]},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both these tests uses a 0,0 coord, so won't be affected by the web meracator stretching that I mentioned.

It would be good to add an additional test at say 45 latitude, to ensure our fix handles it well.

(perhaps add the tests first, run the test and see it fail, apply the fix, run again and see it pass 😄 )

return {"type": "FeatureCollection", "features": features}


def coverage_percentage_from_footprints(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great 👍

We calculate coverage in this endpoint, but also for the entire project in get_project_coverage.

It would be great to refactor the usage there to also use this updated code, so the project level coverage is calculated correctly (merging this as-is would cause two different values between the two implementations. The old implementation can be removed / cleaned up)

# Frontend map layers need GeoJSON in lon/lat.
features = []
for image in images:
footprint = image_footprint_polygon(image)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

**any way we could consolidate these two footprint calcs, so we only have to do it once per image?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Related to backend code dependency:drone-flightplan Requires updates in drone-flightplan frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants