one query for pois
This commit is contained in:
parent
08ea09a347
commit
2e326b8cd7
2 changed files with 56 additions and 40 deletions
|
|
@ -16,10 +16,21 @@ pub struct PoiRow {
|
||||||
pub wheelchair: Option<String>,
|
pub wheelchair: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count row for total matching POIs.
|
/// POI row with window-function total (replaces separate COUNT query).
|
||||||
#[derive(Debug, sqlx::FromRow)]
|
#[derive(Debug, sqlx::FromRow)]
|
||||||
pub struct CountRow {
|
pub struct PoiRowWithTotal {
|
||||||
pub count: Option<i64>,
|
pub osm_id: i64,
|
||||||
|
pub osm_type: String,
|
||||||
|
pub name: String,
|
||||||
|
pub category: String,
|
||||||
|
pub geometry: serde_json::Value,
|
||||||
|
pub address: Option<serde_json::Value>,
|
||||||
|
pub tags: Option<serde_json::Value>,
|
||||||
|
pub opening_hours: Option<String>,
|
||||||
|
pub phone: Option<String>,
|
||||||
|
pub website: Option<String>,
|
||||||
|
pub wheelchair: Option<String>,
|
||||||
|
pub total: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Address sub-object in the API response.
|
/// Address sub-object in the API response.
|
||||||
|
|
@ -86,6 +97,39 @@ pub struct PaginationMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a database row into a GeoJSON Feature.
|
/// Converts a database row into a GeoJSON Feature.
|
||||||
|
impl PoiRowWithTotal {
|
||||||
|
pub fn into_feature(self) -> PoiFeature {
|
||||||
|
let address: Option<PoiAddress> = self
|
||||||
|
.address
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
||||||
|
|
||||||
|
let opening_hours_parsed = self.opening_hours.as_ref().map(|_oh| OpeningHoursParsed {
|
||||||
|
is_open: true,
|
||||||
|
today: None,
|
||||||
|
next_change: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
PoiFeature {
|
||||||
|
r#type: "Feature".into(),
|
||||||
|
geometry: self.geometry,
|
||||||
|
properties: PoiProperties {
|
||||||
|
osm_id: self.osm_id,
|
||||||
|
osm_type: self.osm_type,
|
||||||
|
name: self.name,
|
||||||
|
category: self.category,
|
||||||
|
address,
|
||||||
|
opening_hours: self.opening_hours,
|
||||||
|
opening_hours_parsed,
|
||||||
|
phone: self.phone,
|
||||||
|
website: self.website,
|
||||||
|
wheelchair: self.wheelchair,
|
||||||
|
tags: self.tags,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PoiRow {
|
impl PoiRow {
|
||||||
pub fn into_feature(self) -> PoiFeature {
|
pub fn into_feature(self) -> PoiFeature {
|
||||||
let address: Option<PoiAddress> = self
|
let address: Option<PoiAddress> = self
|
||||||
|
|
|
||||||
|
|
@ -72,43 +72,13 @@ pub async fn list_pois(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build and execute the count query
|
// Single query: window function computes total within the same spatial scan.
|
||||||
let total = if let Some(ref cats) = categories {
|
let rows: Vec<PoiRowWithTotal> = if let Some(ref cats) = categories {
|
||||||
sqlx::query_as::<_, CountRow>(
|
sqlx::query_as::<_, PoiRowWithTotal>(
|
||||||
"SELECT COUNT(*) as count FROM pois \
|
|
||||||
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
|
|
||||||
AND category = ANY($5)",
|
|
||||||
)
|
|
||||||
.bind(min_lon)
|
|
||||||
.bind(min_lat)
|
|
||||||
.bind(max_lon)
|
|
||||||
.bind(max_lat)
|
|
||||||
.bind(cats)
|
|
||||||
.fetch_one(pool.get_ref())
|
|
||||||
.await?
|
|
||||||
.count
|
|
||||||
.unwrap_or(0)
|
|
||||||
} else {
|
|
||||||
sqlx::query_as::<_, CountRow>(
|
|
||||||
"SELECT COUNT(*) as count FROM pois \
|
|
||||||
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326)",
|
|
||||||
)
|
|
||||||
.bind(min_lon)
|
|
||||||
.bind(min_lat)
|
|
||||||
.bind(max_lon)
|
|
||||||
.bind(max_lat)
|
|
||||||
.fetch_one(pool.get_ref())
|
|
||||||
.await?
|
|
||||||
.count
|
|
||||||
.unwrap_or(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build and execute the data query
|
|
||||||
let rows: Vec<PoiRow> = if let Some(ref cats) = categories {
|
|
||||||
sqlx::query_as::<_, PoiRow>(
|
|
||||||
"SELECT osm_id, osm_type, name, category, \
|
"SELECT osm_id, osm_type, name, category, \
|
||||||
ST_AsGeoJSON(geometry)::json AS geometry, \
|
ST_AsGeoJSON(geometry)::json AS geometry, \
|
||||||
address, tags, opening_hours, phone, website, wheelchair \
|
address, tags, opening_hours, phone, website, wheelchair, \
|
||||||
|
COUNT(*) OVER() AS total \
|
||||||
FROM pois \
|
FROM pois \
|
||||||
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
|
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
|
||||||
AND category = ANY($5) \
|
AND category = ANY($5) \
|
||||||
|
|
@ -125,10 +95,11 @@ pub async fn list_pois(
|
||||||
.fetch_all(pool.get_ref())
|
.fetch_all(pool.get_ref())
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
sqlx::query_as::<_, PoiRow>(
|
sqlx::query_as::<_, PoiRowWithTotal>(
|
||||||
"SELECT osm_id, osm_type, name, category, \
|
"SELECT osm_id, osm_type, name, category, \
|
||||||
ST_AsGeoJSON(geometry)::json AS geometry, \
|
ST_AsGeoJSON(geometry)::json AS geometry, \
|
||||||
address, tags, opening_hours, phone, website, wheelchair \
|
address, tags, opening_hours, phone, website, wheelchair, \
|
||||||
|
COUNT(*) OVER() AS total \
|
||||||
FROM pois \
|
FROM pois \
|
||||||
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
|
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
|
||||||
ORDER BY name \
|
ORDER BY name \
|
||||||
|
|
@ -144,6 +115,7 @@ pub async fn list_pois(
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let total = rows.first().and_then(|r| r.total).unwrap_or(0);
|
||||||
let features: Vec<PoiFeature> = rows.into_iter().map(|r| r.into_feature()).collect();
|
let features: Vec<PoiFeature> = rows.into_iter().map(|r| r.into_feature()).collect();
|
||||||
|
|
||||||
let response = PoiFeatureCollection {
|
let response = PoiFeatureCollection {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue