This commit is contained in:
2026-05-26 17:01:23 +05:30
parent f83724951f
commit 4ee278a47e
2 changed files with 116 additions and 46 deletions

View File

@@ -1999,11 +1999,33 @@ pub async fn export_assets(
.await
.map_err(|e| e.to_string())?;
// Pre-fetch all bboxes once so every format branch that wants to surface
// them can do so without re-querying. Grouped by asset_id → slot.
let bbox_rows_all: Vec<(i64, String, f64, f64, f64, f64)> = sqlx::query_as(
"SELECT b.asset_id, b.slot, b.x1, b.y1, b.x2, b.y2
FROM asset_bboxes b
JOIN assets a ON a.id = b.asset_id
WHERE a.deleted = 0",
)
.fetch_all(&state.pool)
.await
.map_err(|e| e.to_string())?;
let mut bbox_by_asset_all: std::collections::HashMap<
i64,
std::collections::HashMap<String, (f64, f64, f64, f64)>,
> = std::collections::HashMap::new();
for (aid, slot, x1, y1, x2, y2) in bbox_rows_all {
bbox_by_asset_all
.entry(aid)
.or_default()
.insert(slot, (x1, y1, x2, y2));
}
let body = match format.to_lowercase().as_str() {
"geojson" | "json" => {
let mut features = vec![];
for a in &assets {
let props = serde_json::json!({
let mut props = serde_json::json!({
"row_id": a.row_id,
"asset_type": a.asset_type,
"asset_name": a.asset_name,
@@ -2018,6 +2040,36 @@ pub async fn export_assets(
"link_pair_id": a.link_pair_id,
"link_locked": a.link_locked,
});
// Inline bbox into the GeoJSON feature's properties using the
// same prefixed-key convention the importer parses, so a
// GeoJSON round-trip preserves the user's drawn bboxes too.
if let Some(slots) = bbox_by_asset_all.get(&a.id) {
let inject = |m: &mut serde_json::Map<String, serde_json::Value>,
prefix: &str,
bb: &(f64, f64, f64, f64)| {
m.insert(format!("{prefix}bbox_x1"), serde_json::json!(bb.0));
m.insert(format!("{prefix}bbox_y1"), serde_json::json!(bb.1));
m.insert(format!("{prefix}bbox_x2"), serde_json::json!(bb.2));
m.insert(format!("{prefix}bbox_y2"), serde_json::json!(bb.3));
};
if let Some(map) = props.as_object_mut() {
if a.asset_type == "fixed" {
if let Some(bb) = slots.get("fixed") {
inject(map, "", bb);
}
} else if a.asset_type == "range" {
if let Some(bb) = slots.get("start") {
inject(map, "start_", bb);
}
if let Some(bb) = slots.get("mid") {
inject(map, "mid_", bb);
}
if let Some(bb) = slots.get("end") {
inject(map, "end_", bb);
}
}
}
}
if a.asset_type == "fixed" {
if let (Some(la), Some(ln)) = (a.lat, a.lng) {
features.push(serde_json::json!({
@@ -2114,26 +2166,7 @@ pub async fn export_assets(
// the importer parses (`bbox_*` for fixed, `start_bbox_*` /
// `mid_bbox_*` / `end_bbox_*` for range) so a re-import preserves
// every bbox the user drew.
let bbox_rows: Vec<(i64, String, f64, f64, f64, f64)> =
sqlx::query_as(
"SELECT b.asset_id, b.slot, b.x1, b.y1, b.x2, b.y2
FROM asset_bboxes b
JOIN assets a ON a.id = b.asset_id
WHERE a.deleted = 0",
)
.fetch_all(&state.pool)
.await
.map_err(|e| e.to_string())?;
let mut bbox_by_asset: std::collections::HashMap<
i64,
std::collections::HashMap<String, (f64, f64, f64, f64)>,
> = std::collections::HashMap::new();
for (aid, slot, x1, y1, x2, y2) in bbox_rows {
bbox_by_asset
.entry(aid)
.or_default()
.insert(slot, (x1, y1, x2, y2));
}
let bbox_by_asset = &bbox_by_asset_all;
let inject_bbox = |obj: &mut serde_json::Value, prefix: &str, bb: &(f64, f64, f64, f64)| {
if let Some(map) = obj.as_object_mut() {
map.insert(format!("{prefix}bbox_x1"), serde_json::json!(bb.0));