Skip to content

fix: force right-hand-rule winding on AOI geometry - #7322

Open
dulcetberg wants to merge 1 commit into
hotosm:developfrom
dulcetberg:fix/tasking-manager-4225-right-hand-rule
Open

fix: force right-hand-rule winding on AOI geometry#7322
dulcetberg wants to merge 1 commit into
hotosm:developfrom
dulcetberg:fix/tasking-manager-4225-right-hand-rule

Conversation

@dulcetberg

Copy link
Copy Markdown

Related to #4225.
Assisted-by: Claude

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

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

Related Issue

Related to #4225

Describe this PR

I investigated this issue in Jupyter Notebook trying to reproduce the results given in Shapely. From the gist linked to the issue, I tested it out in Shapely to see if it still worked. What I discovered was it parses fine in Shapely (is_valid: True), but the exterior ring is still wound clockwise instead of counter-clockwise. This is in violation of the right hand rule convention which states the ring around a polygon must be viewed counter-clockwise and a ring (such as a lake) inside the polygon should go clockwise.

What I cannot confirm, without access to the original Sentry logs, is exactly which part of the processing chain triggers the actual reported error ('Polygon' object does not support indexing). What I can confirm directly was the winding-order anomaly @spwoodcock flagged was real and still present in this data. I applied ST_ForceRHR() which is a low risk normalization that removes said anomaly regardless if that is the cause of the original crash.

Following the fix @spwoodcock proposed which was to wrap the geometry with ST_ForceRHR() before it is actually stored, forcing the correct winding order. The code has been refactored since his original sketch with parameterized SQL instead of SQLAlchemy function objects. This means that no wrapper function is needed in utils.py, I just added it directly to the existing query.
Testing : I could not run the actual ST_ForceRHR PostGIS function directly because I have no live database available to me. What I did try was DuckDB's spatial extension first since I already had that locally installed in Jupyter. Turns out that did not work because DuckDB does not implement ST_ForceRHR. My next step was to try it in shapely.geometry.polygon.orient and confirmed it flips the geometry's exterior ring from clockwise to counter-clockwise, which matches what ST_ForceRHR does in PostGIS. Here is my screenshot of what I produced in Jupyter Notebook:

Screenshots

Screenshot 2026-08-22 at 11 54 02 PM Screenshot 2026-08-22 at 11 55 19 PM Screenshot 2026-08-22 at 11 55 38 PM

Alternative Approaches Considered

Tried DuckDB's spatial extension first, since I had it available locally. I confirmed it doesn't implement ST_ForceRHR (real error, not an assumption — see screenshot 2). Fell back to Shapely's orient() as a proxy verification of the winding-order fix, since I don't have a live PostGIS instance to test the actual function directly.

Review Guide

No live database available to me to test ST_ForceRHR() directly against real PostGIS. The change itself is a single added SQL function wrapper — reviewer testing against a real DB with the failing geometry from the linked gist would be the strongest confirmation.

AI Disclosure

  • This PR was created with significant help from AI tools (e.g., Claude, Copilot, ChatGPT)

If checked, briefly describe what AI assisted with (e.g., code generation, refactoring, tests, documentation) and confirm you have reviewed and understood the changes.

Claude helped me understand the right-hand-rule concept, scope the fix, and draft this description for me to edit. All testing — the Shapely reproduction, the DuckDB attempt, and the orient() verification — I ran myself in my own Jupyter notebook (screenshots attached). I've read through the change and understand what it does.

Checklist before requesting a review

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

Related to hotosm#4225.
Assisted-by: Claude

Signed-off-by: Brian Bergstrom <dulcetberg@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

@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.

Small change - I think this is good to merge 👍

Note that @dulcetberg and I have already discussed AI-assisted contributions in this thread: hotosm/drone-tm#872 (comment)

This PR includes some human reviewed tests done, that I think satisfy the criteria.

From https://responsibleai.guide/managing-ai-contributions:

Image

I would say this is probably a 4 on the scale. Feel free to correct me 🙏

@dulcetberg

Copy link
Copy Markdown
Author

I want to challenge you on the rating, as I overlooked the entire scale, admittedly. The AI helped me write the code, but the critical thinking parts and decisions to test the code in Jupyter Notebook were all mine. The PR itself was written in my own words, and I understood the main concept involved with the right hand rule. Level 2, according to the image says "I planned the change and decided the approach." Level 2 does not forbid AI writing all the code. Level 3 was what I was previously doing with the prompts, not looking at the code at all. This time I actually took the code from the AI, ran it myself and came up with an alternative approach and it failed, then AI gave me the correct code and approach which I approved was the best solution to commit. Furthermore, I manually did all the git commands, not letting AI submit everything like before. It was for those reasons, I feel this should be at Level 2, not 4.

@spwoodcock

Copy link
Copy Markdown
Member

Sounds good to me, thanks for clarifying!

It's on us for not including the rating in the PR template for Tasking Manager yet. The latest revision of the responsible AI guide was just released, so we are due to implement a few things across multiple tools 👍

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants