Skip to content

Feature/panoramax#236

Open
ofr1tz wants to merge 35 commits intodevelopfrom
feature/panoramax
Open

Feature/panoramax#236
ofr1tz wants to merge 35 commits intodevelopfrom
feature/panoramax

Conversation

@ofr1tz
Copy link
Copy Markdown

@ofr1tz ofr1tz commented Feb 23, 2026

Depends on

Changes

This enables the MapSwipe backend to handle View Streets projects with different image providers (currently Mapillary and Panoramax).

  • Adds image provider to the View Streets project specifics
  • Adds logic to download and process Panoramax metadata
  • Maps Panoramax metadata attribute names on the respective Mapillary attribute names for filtering
  • Changes max length of task ID to accommodate Panoramax image IDs as task IDs

This PR doesn't introduce any:

  • temporary files, auto-generated files or secret keys
  • n+1 queries
  • flake8 issues
  • print
  • typos
  • unwanted comments

This PR contains valid:

  • tests
  • permission checks (tests here too)
  • translations

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 89.40397% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.47%. Comparing base (60d5974) to head (5a5553c).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
utils/geo/street_image_provider/models.py 69.23% 5 Missing and 3 partials ⚠️
project_types/street/api_calls.py 87.09% 2 Missing and 2 partials ⚠️
project_types/street/project.py 81.25% 2 Missing and 1 partial ⚠️
...ps/project/tests/e2e_create_street_project_test.py 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #236      +/-   ##
===========================================
+ Coverage    87.41%   87.47%   +0.05%     
===========================================
  Files          205      206       +1     
  Lines        12461    12568     +107     
  Branches      1049     1065      +16     
===========================================
+ Hits         10893    10994     +101     
- Misses        1181     1185       +4     
- Partials       387      389       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ofr1tz ofr1tz marked this pull request as ready for review February 24, 2026 17:42
@frozenhelium frozenhelium requested a review from susilnem March 6, 2026 08:09
Comment thread project_types/street/project.py Outdated
return firebase_models.FbMappingTaskStreetCreateOnlyInput(
# XXX: converting this to int for backwards compatibility
taskId=int(task.firebase_id),
taskId=task.firebase_id,
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.

Is there any reason to remove the casting of firebase_id?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, we use the street-level image ID as the MapSwipe task ID so that we can easily link MapSwipe results back to the original images without having to add an additional task attribute.

However, whereas Mapillary has integer image identifiers, Panoramax uses UUID v4 for identifiers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should still cast it to int for backward compatibility if the provider is Mapillary

Comment thread utils/spatial_sampling.py Outdated
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.

Shouldn't this be in street/project.py?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is consistent with raster_tile_server and vector_tile_server also in utils/geo/

Comment thread utils/geo/street_image_provider/models.py
Comment thread project_types/street/api_calls.py Outdated
Comment thread apps/project/models.py Outdated

# Type hints
id: int
id: int | str
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 reason on changing the type?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above: task id == Panoramax image ID (UUID v4)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is just a type hint.
How was "id" change to "str" in the database.

Also, why change the internal "id" representation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I see. This was a mix-up with the firebase ID, the internal id representation does not need to be changed indeed.

Comment thread project_types/street/project.py Outdated
@ofr1tz ofr1tz force-pushed the feature/panoramax branch from 71352ae to f15f817 Compare March 19, 2026 15:50
@ofr1tz ofr1tz requested a review from susilnem March 23, 2026 16:06
@ofr1tz ofr1tz force-pushed the feature/panoramax branch 2 times, most recently from a2dad0a to f15f817 Compare March 23, 2026 16:53
Comment thread apps/project/models.py Outdated

# Type hints
id: int
id: int | str
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is just a type hint.
How was "id" change to "str" in the database.

Also, why change the internal "id" representation?

Comment thread project_types/street/api_calls.py Outdated
Comment on lines +196 to +197
else:
raise Exception(f"Unknown provider {getattr(provider.name, 'value', '')}")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use assert_never so that we get static type checks.

Comment thread schema.graphql
endTime: String
isPano: Boolean
organizationId: String
panoOnly: Boolean
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This might require a migration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

To my understanding:

  • A migration is not required as the attribute is stored in JSON fields, not in a dedicated column
  • We probably do not need a backfill neither, as this would only be a potential issue for unpublished project drafts created with the former version having isPano: true. The filter is only used during project processing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The data in the system should conform with the schema.

There can be 2 different scenarios that will be problematic if we do not migrate/backfill the data:

  • The data does not confirm with the schema at all
    • This will raise validation issue on graphql (serialization).
    • This will directly impact UI for all the existing projects (draft, completed, etc.)
  • There will be no validation issue but the system will interpret the data differently (e.g. non-mandatory fields renamed)

Comment thread schema.graphql Outdated
"""Numeric value as string"""
aoiGeometry: String!
customOptions: [CustomOptionInput!] = null
imageProvider: StreetImageProviderInput = null
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This might require a migration.

Copy link
Copy Markdown
Author

@ofr1tz ofr1tz Apr 2, 2026

Choose a reason for hiding this comment

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

This is a nested key inside the JSON field project_type_specifics and does not create a new column.

Backend treats it as optional and defaults to MAPILLARY, so existing records without imageProvider continue to validate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should not have fallback pattern on the client-side. That will cause 2 source of truth.
Instead this field should be mandatory and the data should be migrated/back-filled.

Comment thread schema.graphql Outdated
"""Numeric value as string"""
aoiGeometry: String!
customOptions: [ProjectCustomOption!]
imageProvider: StreetImageProvider
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This might require a migration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

See above

Comment thread project_types/street/project.py Outdated
return firebase_models.FbMappingTaskStreetCreateOnlyInput(
# XXX: converting this to int for backwards compatibility
taskId=int(task.firebase_id),
taskId=task.firebase_id,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should still cast it to int for backward compatibility if the provider is Mapillary

Comment on lines +193 to +195
elif provider.name in (StreetImageProviderNameEnum.PANORAMAX, StreetImageProviderNameEnum.PANORAMAX_CUSTOM):
base = (provider.url or Config.PANORAMAX_API_LINK).rstrip("/")
url = f"{base}/api/map/{z}/{x}/{y}.mvt"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is the different between PANORMAX and PANORAMAX_CUSTOM?
Do we need both of these?

Also, the code above indicates that user can override MAPILLARY url

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The difference is:

  • PANORAMAX uses the default public Metacatalog API endpoint with access to images of all federated Panoramax instances.
  • PANORAMAX_CUSTOM allows users to specify a custom Panoramax API URL and thereby uses images from unfederated instances, or filter for images from a specific Panoramax instance.

Both use the same API format (/api/map/{z}/{x}/{y}.mvt) and return the same data structure, but PANORAMAX_CUSTOM is validated to require a URL.

The requirement to add an explicit PANORAMAX_CUSTOM came from review of the changes in Manager Dashboard: mapswipe/manager-dashboard#74 (review)

I agree that the user should not be able to override the MAPILLARY url, as the Pydantic validator in StreetImageProvider already enforces. I pushed a commit that clearly indicates that we always call the Mapillary API at Config.MAPILLARY_API_LINK.

@ofr1tz ofr1tz force-pushed the feature/panoramax branch from d4cb5bf to 5a5553c Compare April 29, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants