use std::collections::HashMap;
use oxigdal_core::error::{IoError, OxiGdalError};
use serde_json::Value as JsonValue;
use crate::streaming::{FeatureStream, StreamingFeature};
use crate::{DatasetInfo, Result};
pub(crate) fn stream_stac_features(info: &DatasetInfo) -> Result<FeatureStream> {
let path = match &info.path {
Some(p) => p.clone(),
None => return Ok(FeatureStream::empty()),
};
let file = std::fs::File::open(&path).map_err(|e| {
OxiGdalError::Io(IoError::Read {
message: format!("cannot open STAC file for streaming '{path}': {e}"),
})
})?;
let root: JsonValue = serde_json::from_reader(std::io::BufReader::new(file)).map_err(|e| {
OxiGdalError::Internal {
message: format!("cannot parse STAC JSON from '{path}': {e}"),
}
})?;
let type_str = root
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_ascii_uppercase();
let feature_values: Vec<&JsonValue> = match type_str.as_str() {
"FEATURECOLLECTION" => {
root.get("features")
.and_then(|f| f.as_array())
.map(|arr| arr.iter().collect())
.unwrap_or_default()
}
"FEATURE" => {
vec![&root]
}
_ => {
Vec::new()
}
};
let features = feature_values
.into_iter()
.map(stac_feature_to_streaming)
.collect::<Vec<_>>();
Ok(FeatureStream::from_vec(features))
}
fn stac_feature_to_streaming(feature: &JsonValue) -> StreamingFeature {
let geometry = feature
.get("geometry")
.filter(|g| !g.is_null())
.and_then(geojson_value_to_wkb);
let mut properties: HashMap<String, JsonValue> = feature
.get("properties")
.and_then(|p| p.as_object())
.map(|obj| obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
if let Some(assets_obj) = feature.get("assets").and_then(|a| a.as_object()) {
for (asset_key, asset_val) in assets_obj {
properties.insert(format!("assets.{asset_key}"), asset_val.clone());
}
}
let mut sf = StreamingFeature::new(geometry, properties);
if let Some(id) = feature.get("id").and_then(|v| v.as_str()) {
sf = sf.with_id(id);
}
sf
}
fn geojson_value_to_wkb(geom: &JsonValue) -> Option<Vec<u8>> {
let type_str = geom.get("type")?.as_str()?;
let coords = geom.get("coordinates");
match type_str {
"Point" => {
let c = coords?.as_array()?;
let x = c.first()?.as_f64()?;
let y = c.get(1)?.as_f64()?;
Some(wkb_point(x, y))
}
"LineString" => {
let pts = coords?
.as_array()?
.iter()
.filter_map(coord2d)
.collect::<Vec<_>>();
if pts.is_empty() {
return None;
}
Some(wkb_linestring(&pts))
}
"Polygon" => {
let rings_raw = coords?.as_array()?;
let rings: Vec<Vec<(f64, f64)>> = rings_raw
.iter()
.map(|ring| {
ring.as_array()
.map(|pts| pts.iter().filter_map(coord2d).collect())
.unwrap_or_default()
})
.collect();
if rings.is_empty() || rings[0].is_empty() {
return None;
}
Some(wkb_polygon(&rings))
}
"MultiPoint" => {
let pts = coords?
.as_array()?
.iter()
.filter_map(coord2d)
.collect::<Vec<_>>();
Some(wkb_multi_point(&pts))
}
"MultiLineString" => {
let lines: Vec<Vec<(f64, f64)>> = coords?
.as_array()?
.iter()
.map(|line| {
line.as_array()
.map(|pts| pts.iter().filter_map(coord2d).collect())
.unwrap_or_default()
})
.collect();
Some(wkb_multi_linestring(&lines))
}
"MultiPolygon" => {
let polys: Vec<Vec<Vec<(f64, f64)>>> = coords?
.as_array()?
.iter()
.map(|poly| {
poly.as_array()
.map(|rings| {
rings
.iter()
.map(|ring| {
ring.as_array()
.map(|pts| pts.iter().filter_map(coord2d).collect())
.unwrap_or_default()
})
.collect()
})
.unwrap_or_default()
})
.collect();
Some(wkb_multi_polygon(&polys))
}
_ => None,
}
}
fn coord2d(v: &JsonValue) -> Option<(f64, f64)> {
let arr = v.as_array()?;
let x = arr.first()?.as_f64()?;
let y = arr.get(1)?.as_f64()?;
Some((x, y))
}
#[inline]
fn write_f64_le(buf: &mut Vec<u8>, v: f64) {
buf.extend_from_slice(&v.to_le_bytes());
}
#[inline]
fn write_u32_le(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn wkb_point(x: f64, y: f64) -> Vec<u8> {
let mut buf = Vec::with_capacity(21);
buf.push(1); write_u32_le(&mut buf, 1); write_f64_le(&mut buf, x);
write_f64_le(&mut buf, y);
buf
}
fn wkb_linestring(pts: &[(f64, f64)]) -> Vec<u8> {
let mut buf = Vec::with_capacity(9 + pts.len() * 16);
buf.push(1);
write_u32_le(&mut buf, 2); write_u32_le(&mut buf, pts.len() as u32);
for &(x, y) in pts {
write_f64_le(&mut buf, x);
write_f64_le(&mut buf, y);
}
buf
}
fn wkb_polygon(rings: &[Vec<(f64, f64)>]) -> Vec<u8> {
let total_pts: usize = rings.iter().map(|r| r.len()).sum();
let mut buf = Vec::with_capacity(9 + rings.len() * 4 + total_pts * 16);
buf.push(1);
write_u32_le(&mut buf, 3); write_u32_le(&mut buf, rings.len() as u32);
for ring in rings {
write_u32_le(&mut buf, ring.len() as u32);
for &(x, y) in ring {
write_f64_le(&mut buf, x);
write_f64_le(&mut buf, y);
}
}
buf
}
fn wkb_multi_point(pts: &[(f64, f64)]) -> Vec<u8> {
let mut buf = Vec::with_capacity(9 + pts.len() * 21);
buf.push(1);
write_u32_le(&mut buf, 4); write_u32_le(&mut buf, pts.len() as u32);
for &(x, y) in pts {
buf.extend_from_slice(&wkb_point(x, y));
}
buf
}
fn wkb_multi_linestring(lines: &[Vec<(f64, f64)>]) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(1);
write_u32_le(&mut buf, 5); write_u32_le(&mut buf, lines.len() as u32);
for line in lines {
buf.extend_from_slice(&wkb_linestring(line));
}
buf
}
fn wkb_multi_polygon(polys: &[Vec<Vec<(f64, f64)>>]) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(1);
write_u32_le(&mut buf, 6); write_u32_le(&mut buf, polys.len() as u32);
for poly in polys {
buf.extend_from_slice(&wkb_polygon(poly));
}
buf
}