use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::{
collections::HashMap,
convert::TryFrom,
convert::TryInto,
io::{BufRead, Seek, Write},
};
use super::{read_json_header, write_json_header};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct FeatureTableDataReference {
#[serde(rename = "byteOffset")]
pub byte_offset: usize,
#[serde(rename = "componentType", skip_serializing_if = "Option::is_none")]
pub component_type: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum FeatureTableValue {
SingleValue(serde_json::Value),
Array(Vec<serde_json::Value>),
DataReference(FeatureTableDataReference),
}
impl TryFrom<Value> for FeatureTableValue {
type Error = anyhow::Error;
fn try_from(val: Value) -> Result<Self> {
if val.is_array() {
let as_array = val.as_array().unwrap();
return Ok(FeatureTableValue::Array(as_array.clone()));
}
if val.is_object() {
let as_obj = val.as_object().unwrap();
if as_obj.contains_key("byteOffset") {
let data_reference = serde_json::from_value::<FeatureTableDataReference>(val)?;
return Ok(FeatureTableValue::DataReference(data_reference));
} else {
return Ok(FeatureTableValue::SingleValue(val));
}
}
Ok(FeatureTableValue::SingleValue(val))
}
}
impl TryFrom<&Value> for FeatureTableValue {
type Error = anyhow::Error;
fn try_from(val: &Value) -> Result<Self> {
if val.is_array() {
let as_array = val.as_array().unwrap();
return Ok(FeatureTableValue::Array(as_array.clone()));
}
if val.is_object() {
let as_obj = val.as_object().unwrap();
if as_obj.contains_key("byteOffset") {
let data_reference =
serde_json::from_value::<FeatureTableDataReference>(val.clone())?;
return Ok(FeatureTableValue::DataReference(data_reference));
} else {
return Ok(FeatureTableValue::SingleValue(val.clone()));
}
}
Ok(FeatureTableValue::SingleValue(val.clone()))
}
}
impl From<FeatureTableValue> for Value {
fn from(val: FeatureTableValue) -> Self {
match val {
FeatureTableValue::SingleValue(val) => val,
FeatureTableValue::Array(arr) => Value::Array(arr),
FeatureTableValue::DataReference(data_reference) => {
serde_json::to_value(data_reference)
.expect("Could not convert FeatureTableDataReference to JSON Value")
}
}
}
}
impl From<&FeatureTableValue> for Value {
fn from(val: &FeatureTableValue) -> Self {
match val {
FeatureTableValue::SingleValue(val) => val.clone(),
FeatureTableValue::Array(arr) => Value::Array(arr.clone()),
FeatureTableValue::DataReference(data_reference) => {
serde_json::to_value(data_reference)
.expect("Could not convert FeatureTableDataReference to JSON Value")
}
}
}
}
pub type FeatureTableHeader = HashMap<String, FeatureTableValue>;
pub fn deser_feature_table_header<R: BufRead + Seek>(
mut reader: R,
feature_table_header_size: usize,
_header_start_position_in_file: usize,
) -> Result<FeatureTableHeader> {
let feature_table_header_json = read_json_header(&mut reader, feature_table_header_size)?;
let feature_table_obj = feature_table_header_json
.as_object()
.ok_or(anyhow!("FeatureTable JSON header was no JSON object"))?;
feature_table_obj
.iter()
.map(|(k, v)| -> Result<(String, FeatureTableValue)> {
let feature_table_value: FeatureTableValue = v.try_into()?;
Ok((k.clone(), feature_table_value))
})
.collect::<Result<HashMap<_, _>, _>>()
}
pub fn ser_feature_table_header<W: Write>(
mut writer: W,
feature_table_header: &FeatureTableHeader,
header_start_position_in_file: usize,
) -> Result<()> {
let header_as_map = feature_table_header
.iter()
.map(|(k, v)| -> (String, Value) { (k.clone(), v.into()) })
.collect::<Map<_, _>>();
let header_json_obj = Value::Object(header_as_map);
write_json_header(&mut writer, &header_json_obj, header_start_position_in_file)
}
#[cfg(test)]
mod tests {
use std::io::{BufReader, BufWriter, Cursor, SeekFrom};
use super::*;
use serde_json::json;
fn dummy_feature_table_header() -> FeatureTableHeader {
let mut header = FeatureTableHeader::new();
header.insert(
"SINGLE_FIELD".into(),
FeatureTableValue::SingleValue(json!(23)),
);
header.insert(
"ARRAY_FIELD".into(),
FeatureTableValue::Array(vec![json!(1), json!(2), json!(3)]),
);
header.insert(
"REFERENCE_FIELD".into(),
FeatureTableValue::DataReference(FeatureTableDataReference {
byte_offset: 42,
component_type: Some("FLOAT".into()),
}),
);
header
}
#[test]
fn test_3dtiles_feature_table_io() -> Result<()> {
let expected_header = dummy_feature_table_header();
let mut writer = BufWriter::new(Cursor::new(vec![]));
ser_feature_table_header(&mut writer, &expected_header, 0)?;
let header_size_in_file = writer.stream_position()? as usize;
assert_eq!(header_size_in_file % 8, 0);
let mut cursor = writer.into_inner()?;
cursor.seek(SeekFrom::Start(0))?;
let mut reader = BufReader::new(cursor);
let actual_header = deser_feature_table_header(&mut reader, header_size_in_file, 0)?;
assert_eq!(expected_header, actual_header);
Ok(())
}
}