Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix basemap mbtile file downloads #1467

Merged
merged 1 commit into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/backend/app/auth/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,9 @@ async def mapper(
"""A mapper for a specific project."""
# If project is public, skip permission check
if project.visibility == ProjectVisibility.PUBLIC:
# FIXME this is a different type than DbUser
return user_data

db_user = await check_access(
user_data,
db,
Expand Down
3 changes: 2 additions & 1 deletion src/backend/app/projects/project_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@

async def get_project_by_id(
db: Session = Depends(get_db), project_id: Optional[int] = None
) -> DbProject:
) -> Optional[DbProject]:
"""Get a single project by id."""
if not project_id:
# Skip if no project id passed
# FIXME why do we need this?
return None

db_project = db.query(DbProject).filter(DbProject.id == project_id).first()
Expand Down
200 changes: 100 additions & 100 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,106 @@ async def set_odk_entities_mapping_status(
)


@router.get("/{project_id}/tiles-list/")
async def tiles_list(
project_id: int,
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Returns the list of tiles for a project.

Parameters:
project_id: int
db (Session): The database session, provided automatically.
current_user (AuthUser): Check if user is logged in.

Returns:
Response: List of generated tiles for a project.
"""
return await project_crud.get_mbtiles_list(db, project_id)


@router.get("/{project_id}/download-tiles/")
async def download_tiles(
tile_id: int,
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Download the basemap tile archive for a project."""
log.debug("Getting tile archive path from DB")
tiles_path = (
db.query(db_models.DbTilesPath)
.filter(db_models.DbTilesPath.id == str(tile_id))
.first()
)
log.info(f"User requested download for tiles: {tiles_path.path}")

project_id = tiles_path.project_id
project = await project_crud.get_project(db, project_id)
filename = Path(tiles_path.path).name.replace(
f"{project_id}_", f"{project.project_name_prefix}_"
)
log.debug(f"Sending tile archive to user: {filename}")

return FileResponse(
tiles_path.path,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)


@router.get("/{project_id}/tiles")
async def generate_project_tiles(
background_tasks: BackgroundTasks,
project_id: int,
source: str = Query(
..., description="Select a source for tiles", enum=TILES_SOURCE
),
format: str = Query(
"mbtiles", description="Select an output format", enum=TILES_FORMATS
),
tms: str = Query(
None,
description="Provide a custom TMS URL, optional",
),
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Returns basemap tiles for a project.

Args:
background_tasks (BackgroundTasks): FastAPI bg tasks, provided automatically.
project_id (int): ID of project to create tiles for.
source (str): Tile source ("esri", "bing", "topo", "google", "oam").
format (str, optional): Default "mbtiles". Other options: "pmtiles", "sqlite3".
tms (str, optional): Default None. Custom TMS provider URL.
db (Session): The database session, provided automatically.
current_user (AuthUser): Check if user has MAPPER permission.

Returns:
str: Success message that tile generation started.
"""
# Create task in db and return uuid
log.debug(
"Creating generate_project_tiles background task "
f"for project ID: {project_id}"
)
background_task_id = await project_crud.insert_background_task_into_database(
db, project_id=project_id
)

background_tasks.add_task(
project_crud.get_project_tiles,
db,
project_id,
background_task_id,
source,
format,
tms,
)

return {"Message": "Tile generation started"}


@router.get("/{project_id}", response_model=project_schemas.ReadProject)
async def read_project(
current_user: AuthUser = Depends(mapper),
Expand Down Expand Up @@ -1071,106 +1171,6 @@ async def convert_fgb_to_geojson(
return Response(content=json.dumps(data_extract_geojson), headers=headers)


@router.get("/tiles/{project_id}")
async def generate_project_tiles(
background_tasks: BackgroundTasks,
project_id: int,
source: str = Query(
..., description="Select a source for tiles", enum=TILES_SOURCE
),
format: str = Query(
"mbtiles", description="Select an output format", enum=TILES_FORMATS
),
tms: str = Query(
None,
description="Provide a custom TMS URL, optional",
),
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Returns basemap tiles for a project.

Args:
background_tasks (BackgroundTasks): FastAPI bg tasks, provided automatically.
project_id (int): ID of project to create tiles for.
source (str): Tile source ("esri", "bing", "topo", "google", "oam").
format (str, optional): Default "mbtiles". Other options: "pmtiles", "sqlite3".
tms (str, optional): Default None. Custom TMS provider URL.
db (Session): The database session, provided automatically.
current_user (AuthUser): Check if user has MAPPER permission.

Returns:
str: Success message that tile generation started.
"""
# Create task in db and return uuid
log.debug(
"Creating generate_project_tiles background task "
f"for project ID: {project_id}"
)
background_task_id = await project_crud.insert_background_task_into_database(
db, project_id=project_id
)

background_tasks.add_task(
project_crud.get_project_tiles,
db,
project_id,
background_task_id,
source,
format,
tms,
)

return {"Message": "Tile generation started"}


@router.get("/tiles_list/{project_id}/")
async def tiles_list(
project_id: int,
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Returns the list of tiles for a project.

Parameters:
project_id: int
db (Session): The database session, provided automatically.
current_user (AuthUser): Check if user is logged in.

Returns:
Response: List of generated tiles for a project.
"""
return await project_crud.get_mbtiles_list(db, project_id)


@router.get("/download_tiles/")
async def download_tiles(
tile_id: int,
db: Session = Depends(database.get_db),
current_user: AuthUser = Depends(mapper),
):
"""Download the basemap tile archive for a project."""
log.debug("Getting tile archive path from DB")
tiles_path = (
db.query(db_models.DbTilesPath)
.filter(db_models.DbTilesPath.id == str(tile_id))
.first()
)
log.info(f"User requested download for tiles: {tiles_path.path}")

project_id = tiles_path.project_id
project = await project_crud.get_project(db, project_id)
filename = Path(tiles_path.path).name.replace(
f"{project_id}_", f"{project.project_name_prefix}_"
)
log.debug(f"Sending tile archive to user: {filename}")

return FileResponse(
tiles_path.path,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)


@router.get("/boundary_in_osm/{project_id}/")
async def download_task_boundary_osm(
project_id: int,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/api/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const GenerateProjectTiles = (url, payload) => {
const generateProjectTiles = async (url, payload) => {
try {
const response = await CoreModules.axios.get(url);
dispatch(GetTilesList(`${import.meta.env.VITE_API_URL}/projects/tiles_list/${payload}/`));
dispatch(GetTilesList(`${import.meta.env.VITE_API_URL}/projects/${payload}/tiles-list/`));
dispatch(ProjectActions.SetGenerateProjectTilesLoading(false));
} catch (error) {
dispatch(ProjectActions.SetGenerateProjectTilesLoading(false));
Expand Down
10 changes: 7 additions & 3 deletions src/frontend/src/components/GenerateBasemap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ const GenerateBasemap = ({ projectInfo }) => {
});
const downloadBasemap = (tileId, toOpfs = false) => {
dispatch(
DownloadTile(`${import.meta.env.VITE_API_URL}/projects/download_tiles/?tile_id=${tileId}`, projectInfo, toOpfs),
DownloadTile(
`${import.meta.env.VITE_API_URL}/projects/${id}/download-tiles/?tile_id=${tileId}`,
projectInfo,
toOpfs,
),
);
};

const getTilesList = () => {
dispatch(GetTilesList(`${import.meta.env.VITE_API_URL}/projects/tiles_list/${id}/`));
dispatch(GetTilesList(`${import.meta.env.VITE_API_URL}/projects/${id}/tiles-list/`));
};

useEffect(() => {
Expand Down Expand Up @@ -80,7 +84,7 @@ const GenerateBasemap = ({ projectInfo }) => {
GenerateProjectTiles(
`${
import.meta.env.VITE_API_URL
}/projects/tiles/${id}?source=${selectedTileSource}&format=${selectedOutputFormat}&tms=${tmsUrl}`,
}/projects/${id}/tiles?source=${selectedTileSource}&format=${selectedOutputFormat}&tms=${tmsUrl}`,
id,
),
);
Expand Down
Loading