Skip to content

Commit 92025bb

Browse files
committed
api for accessing dataset tiles
1 parent 40043c8 commit 92025bb

File tree

7 files changed

+196
-21
lines changed

7 files changed

+196
-21
lines changed

services/src/api/apidoc.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(clippy::needless_for_each)] // TODO: remove when clippy is fixed for utoipa <https://github.com/juhaku/utoipa/issues/1420>
22

33
use crate::api::handlers;
4-
use crate::api::handlers::datasets::{AddDatasetTile, VolumeFileLayersResponse};
4+
use crate::api::handlers::datasets::{AddDatasetTile, DatasetTile, VolumeFileLayersResponse};
55
use crate::api::handlers::permissions::{
66
PermissionListOptions, PermissionListing, PermissionRequest, Resource,
77
};
@@ -118,6 +118,7 @@ use utoipa::{Modify, OpenApi};
118118
handlers::datasets::update_dataset_symbology_handler,
119119
handlers::datasets::update_loading_info_handler,
120120
handlers::datasets::add_dataset_tiles_handler,
121+
handlers::datasets::get_dataset_tiles_handler,
121122
handlers::layers::add_collection,
122123
handlers::layers::add_existing_collection_to_collection,
123124
handlers::layers::add_existing_layer_to_collection,
@@ -406,6 +407,7 @@ use utoipa::{Modify, OpenApi};
406407
VolumeName,
407408
DataPath,
408409
AddDatasetTile,
410+
DatasetTile,
409411
410412
PlotOutputFormat,
411413
WrappedPlotOutput,

services/src/api/handlers/datasets.rs

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::{
1515
datasets::{
1616
DatasetName,
1717
listing::{DatasetListOptions, DatasetListing, DatasetProvider},
18+
postgres::DatasetTileId,
1819
storage::{AutoCreateDataset, DatasetStore, SuggestMetaData},
1920
upload::{AdjustFilePath, Upload, UploadDb, UploadId, UploadRootPath, VolumeName, Volumes},
2021
},
@@ -28,7 +29,7 @@ use crate::{
2829
};
2930
use actix_web::{
3031
FromRequest, HttpResponse, HttpResponseBuilder, Responder,
31-
web::{self, Json},
32+
web::{self, Json, Query},
3233
};
3334
use gdal::{
3435
DatasetOptions,
@@ -63,7 +64,8 @@ use std::{
6364
convert::{TryFrom, TryInto},
6465
path::Path,
6566
};
66-
use utoipa::{ToResponse, ToSchema};
67+
use utoipa::{IntoParams, ToResponse, ToSchema};
68+
use validator::Validate;
6769

6870
pub(crate) fn init_dataset_routes<C>(cfg: &mut web::ServiceConfig)
6971
where
@@ -96,7 +98,8 @@ where
9698
)
9799
.service(
98100
web::resource("/{dataset}/tiles")
99-
.route(web::post().to(add_dataset_tiles_handler::<C>)),
101+
.route(web::post().to(add_dataset_tiles_handler::<C>))
102+
.route(web::get().to(get_dataset_tiles_handler::<C>)),
100103
)
101104
.service(
102105
web::resource("/{dataset}")
@@ -246,7 +249,6 @@ pub async fn add_dataset_tiles_handler<C: ApplicationContext>(
246249
&dataset
247250
.data_path
248251
.ok_or(AddDatasetTilesError::DatasetIsMissingDataPath)?,
249-
&session_context,
250252
)
251253
.context(CannotAddTilesToDataset)?;
252254

@@ -345,24 +347,17 @@ fn validate_tile(
345347
Ok(())
346348
}
347349

348-
fn file_path_from_data_path<T: SessionContext>(
349-
data_path: &DataPath,
350-
session_context: &T,
351-
) -> Result<std::path::PathBuf> {
350+
fn file_path_from_data_path(data_path: &DataPath) -> Result<std::path::PathBuf> {
352351
Ok(match data_path {
353-
DataPath::Volume(volume_name) => session_context
354-
.volumes()?
352+
DataPath::Volume(volume_name) => Volumes::default()
353+
.volumes
355354
.iter()
356-
.find(|v| v.name == volume_name.0)
355+
.find(|v| v.name == *volume_name)
357356
.ok_or(Error::UnknownVolumeName {
358357
volume_name: volume_name.0.clone(),
359358
})?
360359
.path
361-
.clone()
362-
.ok_or(Error::CannotAccessVolumePath {
363-
volume_name: volume_name.0.clone(),
364-
})?
365-
.into(),
360+
.clone(),
366361
DataPath::Upload(upload_id) => upload_id.root_path()?,
367362
})
368363
}
@@ -445,6 +440,71 @@ pub async fn get_dataset_handler<C: ApplicationContext>(
445440
Ok(web::Json(dataset))
446441
}
447442

443+
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema)]
444+
pub struct DatasetTile {
445+
pub id: DatasetTileId,
446+
pub time: crate::api::model::datatypes::TimeInterval,
447+
pub spatial_partition: SpatialPartition2D,
448+
pub band: u32,
449+
pub z_index: u32,
450+
pub params: GdalDatasetParameters,
451+
}
452+
453+
#[derive(Debug, Deserialize, IntoParams, Validate)]
454+
pub struct GetDatasetTilesParams {
455+
pub offset: u32,
456+
#[validate(range(min = 1, max = 100))]
457+
pub limit: u32,
458+
// TODO: filter by time, space, filename, ...
459+
}
460+
461+
/// Retrieves details about a dataset using the internal name.
462+
#[utoipa::path(
463+
tag = "Datasets",
464+
get,
465+
path = "/dataset/{dataset}/tiles",
466+
responses(
467+
(status = 200, description = "OK", body = Vec<DatasetTile>),
468+
(status = 401, response = crate::api::model::responses::UnauthorizedUserResponse)
469+
),
470+
params(
471+
("dataset" = DatasetName, description = "Dataset Name"),
472+
GetDatasetTilesParams
473+
),
474+
security(
475+
("session_token" = [])
476+
)
477+
)]
478+
pub async fn get_dataset_tiles_handler<C: ApplicationContext>(
479+
dataset: web::Path<DatasetName>,
480+
session: C::Session,
481+
params: Query<GetDatasetTilesParams>,
482+
app_ctx: web::Data<C>,
483+
) -> Result<impl Responder, GetDatasetTilesError> {
484+
let session_ctx = app_ctx.session_context(session).db();
485+
486+
let real_dataset = dataset.into_inner();
487+
488+
let dataset_id = session_ctx
489+
.resolve_dataset_name_to_id(&real_dataset)
490+
.await
491+
.context(CannotLoadDatasetForGettingTiles)?;
492+
493+
// handle the case where the dataset name is not known
494+
let dataset_id = dataset_id
495+
.ok_or(error::Error::UnknownDatasetName {
496+
dataset_name: real_dataset.to_string(),
497+
})
498+
.context(CannotLoadDatasetForGettingTiles)?;
499+
500+
let tiles = session_ctx
501+
.get_dataset_tiles(dataset_id, &params.into_inner())
502+
.await
503+
.context(CannotLoadDatasetTiles)?;
504+
505+
Ok(web::Json(tiles))
506+
}
507+
448508
/// Update details about a dataset using the internal name.
449509
#[utoipa::path(
450510
tag = "Datasets",

services/src/api/model/responses/datasets/errors.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,27 @@ impl fmt::Debug for AddDatasetTilesError {
176176
write!(f, "{}", ge_report(self))
177177
}
178178
}
179+
180+
#[derive(Snafu, IntoStaticStr)]
181+
#[snafu(visibility(pub(crate)))]
182+
#[snafu(context(suffix(false)))] // disables default `Snafu` suffix
183+
pub enum GetDatasetTilesError {
184+
CannotLoadDatasetForGettingTiles { source: error::Error },
185+
CannotLoadDatasetTiles { source: error::Error },
186+
}
187+
188+
impl ResponseError for GetDatasetTilesError {
189+
fn error_response(&self) -> HttpResponse {
190+
HttpResponse::build(self.status_code()).json(ErrorResponse::from(self))
191+
}
192+
193+
fn status_code(&self) -> StatusCode {
194+
StatusCode::BAD_REQUEST
195+
}
196+
}
197+
198+
impl fmt::Debug for GetDatasetTilesError {
199+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200+
write!(f, "{}", ge_report(self))
201+
}
202+
}

services/src/api/model/services.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ pub struct UpdateDataset {
177177
pub description: String,
178178
#[validate(custom(function = "validate_tags"))]
179179
pub tags: Vec<String>,
180+
// TODO: result descriptor update? or change the model so that the result descriptor is only part of the metadata
180181
}
181182

182183
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone, ToSchema, Validate)]

services/src/datasets/postgres.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::path::PathBuf;
22

3-
use crate::api::handlers::datasets::AddDatasetTile;
3+
use crate::api::handlers::datasets::{AddDatasetTile, DatasetTile, GetDatasetTilesParams};
44
use crate::api::model::datatypes::SpatialPartition2D;
55
use crate::api::model::services::{DataPath, UpdateDataset};
6+
use crate::config::Gdal;
67
use crate::contexts::PostgresDb;
78
use crate::datasets::listing::Provenance;
89
use crate::datasets::listing::{DatasetListOptions, DatasetListing, DatasetProvider};
@@ -1321,6 +1322,54 @@ where
13211322

13221323
Ok(())
13231324
}
1325+
1326+
async fn get_dataset_tiles(
1327+
&self,
1328+
dataset: DatasetId,
1329+
params: &GetDatasetTilesParams,
1330+
) -> Result<Vec<DatasetTile>> {
1331+
let mut conn = self.conn_pool.get().await?;
1332+
let tx = conn.build_transaction().start().await?;
1333+
1334+
self.ensure_permission_in_tx(dataset.into(), Permission::Read, &tx)
1335+
.await
1336+
.boxed_context(crate::error::PermissionDb)?;
1337+
1338+
let rows = tx
1339+
.query(
1340+
"
1341+
SELECT
1342+
id, time, bbox, band, z_index, gdal_params
1343+
FROM
1344+
dataset_tiles
1345+
WHERE
1346+
dataset_id = $1
1347+
ORDER BY
1348+
(time).start,
1349+
band,
1350+
(bbox).upper_left_coordinate.x,
1351+
(bbox).upper_left_coordinate.y,
1352+
z_index
1353+
OFFSET $2
1354+
LIMIT $3",
1355+
&[&dataset, &(params.offset as i64), &(params.limit as i64)],
1356+
)
1357+
.await?;
1358+
1359+
let tiles: Vec<DatasetTile> = rows
1360+
.into_iter()
1361+
.map(|row| DatasetTile {
1362+
id: row.get(0),
1363+
time: row.get(1),
1364+
spatial_partition: row.get(2),
1365+
band: row.get(3),
1366+
z_index: row.get(4),
1367+
params: row.get::<_, GdalDatasetParameters>(5).into(),
1368+
})
1369+
.collect();
1370+
1371+
Ok(tiles)
1372+
}
13241373
}
13251374

13261375
async fn validate_time(

services/src/datasets/storage.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::listing::Provenance;
22
use super::postgres::DatasetMetaData;
33
use super::{DatasetIdAndName, DatasetName};
4-
use crate::api::handlers::datasets::AddDatasetTile;
4+
use crate::api::handlers::datasets::{AddDatasetTile, DatasetTile, GetDatasetTilesParams};
55
use crate::api::model::services::{DataPath, UpdateDataset};
66
use crate::datasets::listing::{DatasetListing, DatasetProvider};
77
use crate::datasets::upload::UploadDb;
@@ -323,4 +323,10 @@ pub trait DatasetStore {
323323

324324
async fn add_dataset_tiles(&self, dataset: DatasetId, tiles: Vec<AddDatasetTile>)
325325
-> Result<()>;
326+
327+
async fn get_dataset_tiles(
328+
&self,
329+
dataset: DatasetId,
330+
params: &GetDatasetTilesParams,
331+
) -> Result<Vec<DatasetTile>>;
326332
}

test_data/api_calls/multi_tile.http

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# @name anonymousSession
2-
POST http://localhost:3030/api/anonymous
2+
POST http://localhost:3030/api/login
33
Content-Type: application/json
44

5+
{
6+
"email": "admin@localhost",
7+
"password": "adminadmin"
8+
}
9+
510
###
611
# @name dataset
712
POST http://localhost:3030/api/dataset
813
Content-Type: application/json
914
Authorization: Bearer {{anonymousSession.response.body.$.id}}
1015

11-
< ../raster/multi_tile/metadata/dataset.json
16+
< ../raster/multi_tile/metadata/dataset_irregular.json
1217

1318
###
1419

@@ -44,4 +49,32 @@ Authorization: Bearer {{anonymousSession.response.body.$.id}}
4449
GET http://localhost:3030/api/wms/{{workflow.response.body.$.id}}?REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&FORMAT=image%2Fpng&STYLES=custom%3A{{colorizer}}&TRANSPARENT=true&layers={{workflow.response.body.$.id}}&time=2025-01-01T00%3A00%3A00.000Z&EXCEPTIONS=application%2Fjson&WIDTH=1800&HEIGHT=900&CRS=EPSG%3A4326&BBOX=-90%2C-180%2C90%2C180
4550
Authorization: Bearer {{anonymousSession.response.body.$.id}}
4651

52+
### Share with users
53+
54+
PUT http://localhost:3030/api/permissions
55+
Authorization: Bearer {{anonymousSession.response.body.$.id}}
56+
Content-Type: application/json
57+
58+
{
59+
"resource": {
60+
"type": "dataset",
61+
"id": "{{dataset.response.body.$.datasetName}}"
62+
},
63+
"roleId": "4e8081b6-8aa6-4275-af0c-2fa2da557d28",
64+
"permission": "Read"
65+
}
66+
67+
### Share wit hanonymous
68+
69+
PUT http://localhost:3030/api/permissions
70+
Authorization: Bearer {{anonymousSession.response.body.$.id}}
71+
Content-Type: application/json
4772

73+
{
74+
"resource": {
75+
"type": "dataset",
76+
"id": "{{dataset.response.body.$.datasetName}}"
77+
},
78+
"roleId": "fd8e87bf-515c-4f36-8da6-1a53702ff102",
79+
"permission": "Read"
80+
}

0 commit comments

Comments
 (0)