Skip to content

Commit 02e6370

Browse files
committed
feat(project): Update mapillary api calls
1 parent efa68e1 commit 02e6370

File tree

10 files changed

+78
-97
lines changed

10 files changed

+78
-97
lines changed

apps/project/graphql/inputs/project_types/street.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from project_types.street import project as street_project
44

55

6-
@strawberry.experimental.pydantic.input(model=street_project.StreetMappilaryImageFilters, all_fields=True)
7-
class StreetMappilaryImageFiltersInput: ...
6+
@strawberry.experimental.pydantic.input(model=street_project.StreetMapilaryImageFilters, all_fields=True)
7+
class StreetMapilaryImageFiltersInput: ...
88

99

1010
@strawberry.experimental.pydantic.input(model=street_project.StreetProjectProperty, all_fields=True)

apps/project/graphql/types/project_types/street.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from project_types.street import project as street_project
44

55

6-
@strawberry.experimental.pydantic.type(model=street_project.StreetMappilaryImageFilters, all_fields=True)
7-
class StreetMappilaryImageFilters: ...
6+
@strawberry.experimental.pydantic.type(model=street_project.StreetMapilaryImageFilters, all_fields=True)
7+
class StreetMapilaryImageFilters: ...
88

99

1010
@strawberry.experimental.pydantic.type(model=street_project.StreetProjectProperty, all_fields=True)

apps/project/tests/mutation_test.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,8 +1375,15 @@ def test_project_street(self, mock_requests):
13751375
"iconColor": "#FF0000",
13761376
"title": "Street Project Title",
13771377
"value": 1,
1378+
"subOptions": [
1379+
{
1380+
"clientId": str(ULID()),
1381+
"value": 1,
1382+
"description": "Street sub option description",
1383+
},
1384+
],
13781385
},
1379-
"mapillaryImageFilters": {
1386+
"mapilaryImageFilters": {
13801387
"isPano": True,
13811388
"creatorId": None,
13821389
"organizationId": None,
@@ -1396,9 +1403,7 @@ def test_project_street(self, mock_requests):
13961403
assert latest_project.created_by_id == self.user.pk
13971404
assert latest_project.modified_by_id == self.user.pk
13981405
assert latest_project.image_id == int(image_asset["id"])
1399-
assert latest_project.project_type_specifics == {
1400-
"aoi_geometry": aoi_geometry_asset["id"],
1401-
}
1406+
assert latest_project.project_type_specifics is not None
14021407

14031408
street_project.StreetProjectProperty.model_validate(
14041409
latest_project.project_type_specifics,
@@ -1419,5 +1424,3 @@ def test_project_street(self, mock_requests):
14191424

14201425
mock_requests.assert_called_once()
14211426
mock_requests.assert_has_calls([call(int(project_id))])
1422-
1423-
process_project_task(int(project_id))

main/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class Config:
3939
OSM_API_LINK = "https://www.openstreetmap.org/api/0.6/"
4040

4141
# NOTE: We get mapillary data from mapillary
42-
MAPILLARY_API_LINK = "https://graph.mapillary.com/"
42+
MAPILLARY_API_LINK = "https://tiles.mapillary.com/maps/vtp/mly1_computed_public/2/"
4343
MAPILLARY_API_KEY = typing.cast("str", settings.MAPILLARY_API_KEY)
4444

4545
FIREBASE_HELPER = typing.cast("FirebaseHelper", settings.FIREBASE_HELPER)

main/settings.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ def urlparse(value) -> ParseResult:
113113
MAP_IMAGE_ESRI_BETA_API_KEY=str,
114114
OSMCHA_API_KEY=str, # os.environ["OSMCHA_API_KEY"]
115115
# Mapillary
116-
MAPILLARY_API_LINK=str,
117116
MAPILLARY_API_KEY=str, # os.environ["MAPILLARY_API_KEY"]
118117
# MAP_IMAGE_DIGITAL_GLOBE_API_KEY=str,
119118
# Firebase
@@ -535,8 +534,6 @@ def urlparse(value) -> ParseResult:
535534
OSMCHA_API_KEY = env("OSMCHA_API_KEY")
536535

537536
# Mapillary
538-
539-
MAPILLARY_API_LINK = env("MAPILLARY_API_LINK")
540537
MAPILLARY_API_KEY = env("MAPILLARY_API_KEY")
541538

542539
# Firebase

project_types/store.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,4 @@ def get_tutorial_type_handler(tutorial_type: ProjectTypeEnum) -> TutorialTypeHan
166166
case ProjectTypeEnum.VALIDATE_IMAGE:
167167
raise Exception("Validate Image tutorial is not yet supported")
168168
case ProjectTypeEnum.STREET:
169-
raise Exception("Street tutorial is not yet supported")
169+
return StreetTutorial

project_types/street/api_calls.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
from collections.abc import Hashable
33
from concurrent.futures import ProcessPoolExecutor
44
from functools import partial
5-
from typing import Any
5+
from typing import Any, TypedDict
66
from warnings import deprecated
77

88
import mercantile
99
import pandas as pd
1010
import requests
11-
from django.contrib.gis.geos import Point
1211
from geojson_pydantic import FeatureCollection
13-
from geojson_pydantic.geometries import MultiPolygon as GeoJsonMultiPolygon
14-
from geojson_pydantic.geometries import Polygon as GeojsonPolygon
12+
from geojson_pydantic.geometries import MultiPolygon as PydanticMultiPolygon
13+
from geojson_pydantic.geometries import Polygon as PydanticPolygon
1514
from pydantic import ValidationError
16-
from shapely import MultiPolygon, Polygon, box, unary_union
15+
from shapely import MultiPolygon, Point, Polygon, box, unary_union
16+
from shapely.geometry import shape
1717
from shapely.geometry.base import BaseGeometry
1818
from vt2geojson import tools as vt2geojson_tools
1919

@@ -24,6 +24,11 @@
2424
logger = logging.getLogger(__name__)
2525

2626

27+
class StreetRawGroupItem(TypedDict):
28+
ids: list[int]
29+
geometries: list[Point]
30+
31+
2732
class StreetException(Exception):
2833
pass
2934

@@ -69,13 +74,13 @@ def geojson_to_polygon(geojson_data: dict[str, Any]):
6974
except ValidationError as e:
7075
raise ValueError("Invalid GeoJSON FeatureCollection") from e
7176

72-
polygon_types = (GeojsonPolygon, GeoJsonMultiPolygon)
73-
filtered_features = [feature for feature in fc.features if isinstance(feature.geometry, polygon_types)]
77+
polygon_types = (PydanticPolygon, PydanticMultiPolygon)
78+
geometries = [shape(feature.geometry) for feature in fc.features if isinstance(feature.geometry, polygon_types)]
7479

75-
if not filtered_features:
80+
if not geometries:
7681
raise ValueError("No valid Polygon or MultiPolygon found in the GeoJSON FeatureCollection")
7782

78-
return unary_union(filtered_features)
83+
return unary_union(geometries)
7984

8085

8186
def coordinate_download(
@@ -261,7 +266,7 @@ def get_image_metadata(
261266
end_time: str | None = None,
262267
randomize_order: bool = False,
263268
sampling_threshold: int | None = None,
264-
) -> dict[str, list[Any]]:
269+
) -> StreetRawGroupItem:
265270
kwargs = {
266271
"is_pano": is_pano,
267272
"creator_id": creator_id,
@@ -296,7 +301,7 @@ def get_image_metadata(
296301
f"Too many Images with selected filter options for the AoI: {total_images}",
297302
)
298303

299-
return {
300-
"ids": downloaded_metadata["id"].tolist(),
301-
"geometries": downloaded_metadata["geometry"].tolist(),
302-
}
304+
return StreetRawGroupItem(
305+
ids=downloaded_metadata["id"].tolist(),
306+
geometries=downloaded_metadata["geometry"].tolist(),
307+
)

project_types/street/project.py

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,27 @@
22
import logging
33
import math
44
import typing
5-
from typing import Any, TypedDict
65

76
from django.contrib.gis.geos import GEOSGeometry
87
from django.core.files.base import ContentFile
9-
from geojson_pydantic import Feature
10-
from geojson_pydantic.geometries import MultiPolygon, Polygon
11-
from osgeo import ogr
128
from pydantic import BaseModel, Field
139
from pyfirebase_mapswipe import models as firebase_models
10+
from shapely import to_wkt
11+
from shapely.geometry.point import Point
1412
from ulid import ULID
1513

1614
from apps.common.models import AssetMimetypeEnum, AssetTypeEnum
1715
from apps.project.models import Project, ProjectAsset, ProjectTask, ProjectTaskGroup, ProjectTypeEnum
1816
from main.bulk_managers import BulkCreateManager
1917
from project_types.base import project as base_project
20-
from project_types.street.api_calls import get_image_metadata
18+
from project_types.street.api_calls import StreetRawGroupItem, get_image_metadata
2119
from utils.common import create_json_dump
2220
from utils.custom_options.models import CustomOption
2321

2422
logger = logging.getLogger(__name__)
2523

2624

27-
class ValidFeature(Feature[Polygon | MultiPolygon, dict[str, Any]]): ...
28-
29-
30-
class StreetRawGroupItem(TypedDict):
31-
feature_ids: list[int]
32-
features: list[ValidFeature]
33-
34-
35-
class StreetMappilaryImageFilters(BaseModel):
25+
class StreetMapilaryImageFilters(BaseModel):
3626
is_pano: bool | None = Field(
3727
default=None,
3828
description="Filter for images that are panoramas.",
@@ -66,7 +56,7 @@ class StreetMappilaryImageFilters(BaseModel):
6656
class StreetProjectProperty(base_project.BaseProjectProperty):
6757
aoi_geometry: typing.Annotated[str, Field(strict=True, pattern=r"^\d+$")] | None = None
6858
custom_options: list[CustomOption] | None = None
69-
mappilary_image_filters: StreetMappilaryImageFilters
59+
mapilary_image_filters: StreetMapilaryImageFilters
7060

7161

7262
class StreetTaskGroupProperty(base_project.BaseProjectTaskGroupProperty): ...
@@ -84,7 +74,7 @@ class StreetProject(
8474
StreetTaskGroupProperty,
8575
StreetTaskProperty,
8676
StreetRawGroupItem,
87-
list[tuple[int, ValidFeature]],
77+
list[tuple[int, Point]],
8878
],
8979
):
9080
project_property_class = StreetProjectProperty
@@ -111,77 +101,61 @@ def validate(self) -> StreetRawGroupItem:
111101
with aoi_asset.file.open() as aoi_file:
112102
aoi_geojson = json.loads(aoi_file.read())
113103

114-
mappilary_image_filters = self.project_type_specifics.mappilary_image_filters
104+
mapilary_image_filters = self.project_type_specifics.mapilary_image_filters
115105

116-
image_metadata_data = get_image_metadata(
106+
return get_image_metadata(
117107
aoi_geojson=aoi_geojson,
118-
is_pano=mappilary_image_filters.is_pano,
119-
creator_id=mappilary_image_filters.creator_id,
120-
organization_id=mappilary_image_filters.organization_id,
121-
start_time=mappilary_image_filters.start_time,
122-
end_time=mappilary_image_filters.end_time,
123-
randomize_order=mappilary_image_filters.randomize_order,
124-
sampling_threshold=mappilary_image_filters.sampling_threshold,
125-
)
126-
return StreetRawGroupItem(
127-
feature_ids=image_metadata_data["ids"],
128-
features=image_metadata_data["geometries"],
108+
is_pano=mapilary_image_filters.is_pano,
109+
creator_id=mapilary_image_filters.creator_id,
110+
organization_id=mapilary_image_filters.organization_id,
111+
start_time=mapilary_image_filters.start_time,
112+
end_time=mapilary_image_filters.end_time,
113+
randomize_order=mapilary_image_filters.randomize_order,
114+
sampling_threshold=mapilary_image_filters.sampling_threshold,
129115
)
130116

131117
@typing.override
132118
def create_tasks(
133119
self,
134120
/,
135121
group: ProjectTaskGroup,
136-
raw_group: list[tuple[int, ValidFeature]],
137-
previous_tasks_count: int,
122+
raw_group: list[tuple[int, Point]],
138123
) -> int:
139124
"""Create tasks for a group."""
140125
bulk_mgr = BulkCreateManager(chunk_size=1000)
141-
tasks_count = previous_tasks_count
126+
tasks_count = 0
142127

143128
for f_id, feature in raw_group:
144-
if feature.geometry is not None:
145-
geom = ogr.CreateGeometryFromJson(
146-
feature.geometry.model_dump_json(),
147-
)
148-
149-
if geom.GetCoordinateDimension() == 3:
150-
geom.FlattenTo2D()
151-
152-
geometry_str = geom.ExportToWkt()
153-
154-
bulk_mgr.add(
155-
ProjectTask(
156-
firebase_id=f"t{tasks_count + 1}",
157-
task_group_id=group.pk,
129+
geometry_str = to_wkt(feature)
130+
131+
bulk_mgr.add(
132+
ProjectTask(
133+
firebase_id=f"t{f_id}",
134+
task_group_id=group.pk,
135+
geometry=geometry_str,
136+
project_type_specifics=self.project_task_property_class(
137+
task_id=f"t{f_id}",
138+
group_id=f"g{group.pk}",
158139
geometry=geometry_str,
159-
project_type_specifics=self.project_task_property_class(
160-
task_id=f"t{f_id}",
161-
group_id=f"g{group.pk}",
162-
geometry=geometry_str,
163-
).model_dump(),
164-
),
165-
)
166-
tasks_count += 1
140+
).model_dump(),
141+
),
142+
)
143+
tasks_count += 1
167144

168145
bulk_mgr.done()
169146
return tasks_count
170147

171148
@typing.override
172149
def create_groups(self, resp: StreetRawGroupItem):
173150
self.project.update_processing_status(Project.ProcessingStatus.GENERATING_GROUPS_AND_TASKS, True)
174-
number_of_groups = math.ceil(len(resp["feature_ids"]) / self.project.group_size)
175-
176-
# FIXME (susilnem): We can directly use the firebase_id instead of total_tasks_accumulated
177-
total_tasks_accumulated: int = 0
151+
number_of_groups = math.ceil(len(resp["ids"]) / self.project.group_size)
178152

179153
for group_id in range(number_of_groups):
180154
start_index = group_id * self.project.group_size
181155
end_index = start_index + self.project.group_size
182156

183-
group_features = resp["features"][start_index:end_index]
184-
group_feature_ids = resp["feature_ids"][start_index:end_index]
157+
group_features = resp["geometries"][start_index:end_index]
158+
group_feature_ids = resp["ids"][start_index:end_index]
185159
raw_group = list(zip(group_feature_ids, group_features, strict=True))
186160
new_group = ProjectTaskGroup.objects.create(
187161
firebase_id=f"g{group_id}",
@@ -194,9 +168,8 @@ def create_groups(self, resp: StreetRawGroupItem):
194168
)
195169

196170
# Create new tasks for this group
197-
total_tasks = self.create_tasks(new_group, raw_group, total_tasks_accumulated)
171+
total_tasks = self.create_tasks(new_group, raw_group)
198172
logger.info("Created %s tasks for group: %s", total_tasks, new_group.pk)
199-
total_tasks_accumulated += total_tasks
200173

201174
@typing.override
202175
def post_create_groups(self):
@@ -237,7 +210,7 @@ def get_feature(task: ProjectTask):
237210
file_size=file.size,
238211
type=AssetTypeEnum.OUTPUT,
239212
mimetype=AssetMimetypeEnum.GEOJSON,
240-
# FIXME(tnagorra): Maybe create a internal user like mapswipe-bot
213+
# FIXME(susilnem): Maybe create a internal user like mapswipe-bot
241214
created_by=self.project.modified_by,
242215
modified_by=self.project.modified_by,
243216
)

project_types/validate/project.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,10 @@ def create_groups(self, resp: list[ValidFeature]):
307307
)
308308

309309
# Create new tasks for this group
310-
total_tasks = self.create_tasks(new_group, raw_group)
310+
total_tasks = self.create_tasks(
311+
group=new_group,
312+
raw_group=raw_group,
313+
)
311314
logger.info("Created %s tasks for group: %s", total_tasks, new_group.pk)
312315

313316
@typing.override

schema.graphql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,7 +1893,7 @@ input StrFilterLookup {
18931893
iRegex: String
18941894
}
18951895

1896-
type StreetMappilaryImageFilters {
1896+
type StreetMapilaryImageFilters {
18971897
"""Filter for images that are panoramas."""
18981898
isPano: Boolean
18991899

@@ -1916,7 +1916,7 @@ type StreetMappilaryImageFilters {
19161916
samplingThreshold: Int
19171917
}
19181918

1919-
input StreetMappilaryImageFiltersInput {
1919+
input StreetMapilaryImageFiltersInput {
19201920
"""Filter for images that are panoramas."""
19211921
isPano: Boolean = null
19221922

@@ -1942,13 +1942,13 @@ input StreetMappilaryImageFiltersInput {
19421942
input StreetProjectPropertyInput {
19431943
aoiGeometry: String = null
19441944
customOptions: [CustomOptionInput!] = null
1945-
mappilaryImageFilters: StreetMappilaryImageFiltersInput!
1945+
mapilaryImageFilters: StreetMapilaryImageFiltersInput!
19461946
}
19471947

19481948
type StreetProjectPropertyType {
19491949
aoiGeometry: String
19501950
customOptions: [ProjectCustomOption!]
1951-
mappilaryImageFilters: StreetMappilaryImageFilters!
1951+
mapilaryImageFilters: StreetMapilaryImageFilters!
19521952
}
19531953

19541954
type StreetTutorialTaskPropertyType {

0 commit comments

Comments
 (0)