use crate::FgbWriter;
use flatgeobuf::*;
use geo_types::{line_string, LineString};
use geozero::geojson::{GeoJson, GeoJsonReader};
use geozero::{ColumnValue, GeozeroDatasource, PropertyProcessor};
use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use tempfile::{tempfile, NamedTempFile};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[test]
fn write_file() -> std::io::Result<()> {
let mut file = BufWriter::new(tempfile()?);
let points = [[1.0, 1.0], [2.0, 2.0]];
const MAGIC_BYTES: [u8; 8] = [b'f', b'g', b'b', 3, b'f', b'g', b'b', 0];
file.write_all(&MAGIC_BYTES)?;
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let column_args = ColumnArgs {
name: Some(fbb.create_string("STATE_FIPS")),
type_: ColumnType::String,
..Default::default()
};
let column = Column::create(&mut fbb, &column_args);
let header_args = HeaderArgs {
name: Some(fbb.create_string("Test1")),
geometry_type: GeometryType::Point,
columns: Some(fbb.create_vector(&[column])),
features_count: points.len() as u64,
index_node_size: 0,
..Default::default()
};
let header = Header::create(&mut fbb, &header_args);
fbb.finish_size_prefixed(header, None);
let buf = fbb.finished_data();
file.write_all(buf)?;
for point in points {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let xy = fbb.create_vector(&point);
let g = Geometry::create(
&mut fbb,
&GeometryArgs {
xy: Some(xy),
..Default::default()
},
);
let f = Feature::create(
&mut fbb,
&FeatureArgs {
geometry: Some(g),
..Default::default()
},
);
fbb.finish_size_prefixed(f, None);
let buf = fbb.finished_data();
assert_eq!(buf.len(), 64);
file.write_all(buf)?;
}
Ok(())
}
#[test]
fn verify_header() {
let mut builder = flatbuffers::FlatBufferBuilder::with_capacity(1024);
builder.start_vector::<u8>(0);
let empty_vec = builder.end_vector(0);
let header_args = HeaderArgs {
name: Some(builder.create_string("triangle")),
envelope: Some(builder.create_vector(&[0.0, 0.0, 9.0, 9.0])),
geometry_type: GeometryType::Triangle,
columns: Some(empty_vec),
features_count: 1,
..Default::default()
};
let header = Header::create(&mut builder, &header_args);
builder.finish_size_prefixed(header, None);
let buf = builder.finished_data();
// verify
let header = size_prefixed_root_as_header(buf).unwrap();
assert_eq!(header.features_count(), 1);
assert!(
root_as_header(&buf[4..]).is_err(),
"Verification without size prefix fails"
);
}
#[test]
fn json_to_fgb() -> Result<()> {
let mut fgb = FgbWriter::create_with_options(
"countries",
GeometryType::MultiPolygon,
FgbWriterOptions {
description: Some("Country polygons"),
crs: FgbCrs {
code: 4326,
..Default::default()
},
..Default::default()
},
)?;
fgb.add_column("fid", ColumnType::ULong, |_fbb, col| {
col.nullable = false;
});
fgb.add_column("name", ColumnType::String, |_, _| {});
let geojson = GeoJson(
r#"{"type": "Feature", "properties": {"fid": 42, "name": "New Zealand"}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[173.020375,-40.919052],[173.247234,-41.331999],[173.958405,-40.926701],[174.247587,-41.349155],[174.248517,-41.770008],[173.876447,-42.233184],[173.22274,-42.970038],[172.711246,-43.372288],[173.080113,-43.853344],[172.308584,-43.865694],[171.452925,-44.242519],[171.185138,-44.897104],[170.616697,-45.908929],[169.831422,-46.355775],[169.332331,-46.641235],[168.411354,-46.619945],[167.763745,-46.290197],[166.676886,-46.219917],[166.509144,-45.852705],[167.046424,-45.110941],[168.303763,-44.123973],[168.949409,-43.935819],[169.667815,-43.555326],[170.52492,-43.031688],[171.12509,-42.512754],[171.569714,-41.767424],[171.948709,-41.514417],[172.097227,-40.956104],[172.79858,-40.493962],[173.020375,-40.919052]]],[[[174.612009,-36.156397],[175.336616,-37.209098],[175.357596,-36.526194],[175.808887,-36.798942],[175.95849,-37.555382],[176.763195,-37.881253],[177.438813,-37.961248],[178.010354,-37.579825],[178.517094,-37.695373],[178.274731,-38.582813],[177.97046,-39.166343],[177.206993,-39.145776],[176.939981,-39.449736],[177.032946,-39.879943],[176.885824,-40.065978],[176.508017,-40.604808],[176.01244,-41.289624],[175.239567,-41.688308],[175.067898,-41.425895],[174.650973,-41.281821],[175.22763,-40.459236],[174.900157,-39.908933],[173.824047,-39.508854],[173.852262,-39.146602],[174.574802,-38.797683],[174.743474,-38.027808],[174.697017,-37.381129],[174.292028,-36.711092],[174.319004,-36.534824],[173.840997,-36.121981],[173.054171,-35.237125],[172.636005,-34.529107],[173.007042,-34.450662],[173.551298,-35.006183],[174.32939,-35.265496],[174.612009,-36.156397]]]]}}"#,
);
fgb.add_feature(geojson)?;
// // Process geometry only and use properties API
let geom = GeoJson(
r#"{"type": "MultiPolygon", "coordinates": [[[[31.521001,-29.257387],[31.325561,-29.401978],[30.901763,-29.909957],[30.622813,-30.423776],[30.055716,-31.140269],[28.925553,-32.172041],[28.219756,-32.771953],[27.464608,-33.226964],[26.419452,-33.61495],[25.909664,-33.66704],[25.780628,-33.944646],[25.172862,-33.796851],[24.677853,-33.987176],[23.594043,-33.794474],[22.988189,-33.916431],[22.574157,-33.864083],[21.542799,-34.258839],[20.689053,-34.417175],[20.071261,-34.795137],[19.616405,-34.819166],[19.193278,-34.462599],[18.855315,-34.444306],[18.424643,-33.997873],[18.377411,-34.136521],[18.244499,-33.867752],[18.25008,-33.281431],[17.92519,-32.611291],[18.24791,-32.429131],[18.221762,-31.661633],[17.566918,-30.725721],[17.064416,-29.878641],[17.062918,-29.875954],[16.344977,-28.576705],[16.824017,-28.082162],[17.218929,-28.355943],[17.387497,-28.783514],[17.836152,-28.856378],[18.464899,-29.045462],[19.002127,-28.972443],[19.894734,-28.461105],[19.895768,-24.76779],[20.165726,-24.917962],[20.758609,-25.868136],[20.66647,-26.477453],[20.889609,-26.828543],[21.605896,-26.726534],[22.105969,-26.280256],[22.579532,-25.979448],[22.824271,-25.500459],[23.312097,-25.26869],[23.73357,-25.390129],[24.211267,-25.670216],[25.025171,-25.71967],[25.664666,-25.486816],[25.765849,-25.174845],[25.941652,-24.696373],[26.485753,-24.616327],[26.786407,-24.240691],[27.11941,-23.574323],[28.017236,-22.827754],[29.432188,-22.091313],[29.839037,-22.102216],[30.322883,-22.271612],[30.659865,-22.151567],[31.191409,-22.25151],[31.670398,-23.658969],[31.930589,-24.369417],[31.752408,-25.484284],[31.837778,-25.843332],[31.333158,-25.660191],[31.04408,-25.731452],[30.949667,-26.022649],[30.676609,-26.398078],[30.685962,-26.743845],[31.282773,-27.285879],[31.86806,-27.177927],[32.071665,-26.73382],[32.83012,-26.742192],[32.580265,-27.470158],[32.462133,-28.301011],[32.203389,-28.752405],[31.521001,-29.257387]],[[28.978263,-28.955597],[28.5417,-28.647502],[28.074338,-28.851469],[27.532511,-29.242711],[26.999262,-29.875954],[27.749397,-30.645106],[28.107205,-30.545732],[28.291069,-30.226217],[28.8484,-30.070051],[29.018415,-29.743766],[29.325166,-29.257387],[28.978263,-28.955597]]]]}"#,
);
fgb.add_feature_geom(geom, |feat| {
feat.property(0, "fid", &ColumnValue::Long(43)).unwrap();
feat.property(1, "name", &ColumnValue::String("South Africa"))
.unwrap();
})?;
// let mut file = BufWriter::new(File::create("test_multipoly.fgb")?);
let mut file = BufWriter::new(tempfile()?);
fgb.write(&mut file)?;
Ok(())
}
#[test]
fn column_size() -> Result<()> {
let mut fgb = FgbWriter::create_with_options(
"countries",
GeometryType::Point,
FgbWriterOptions {
description: Some("My Points"),
crs: FgbCrs {
code: 4326,
..Default::default()
},
..Default::default()
},
)?;
fgb.add_column("max_byte", ColumnType::Byte, |_fbb, _col| {});
fgb.add_column("max_ubyte", ColumnType::UByte, |_fbb, _col| {});
fgb.add_column("max_bool", ColumnType::Bool, |_fbb, _col| {});
fgb.add_column("max_short", ColumnType::Short, |_fbb, _col| {});
fgb.add_column("max_ushort", ColumnType::UShort, |_fbb, _col| {});
fgb.add_column("max_int", ColumnType::Int, |_fbb, _col| {});
fgb.add_column("max_uint", ColumnType::UInt, |_fbb, _col| {});
fgb.add_column("max_long", ColumnType::Long, |_fbb, _col| {});
fgb.add_column("max_ulong", ColumnType::ULong, |_fbb, _col| {});
fgb.add_column("max_float", ColumnType::Float, |_fbb, _col| {});
fgb.add_column("max_double", ColumnType::Double, |_fbb, _col| {});
let geom = GeoJson(r#"{"type": "Point", "coordinates": [100.0,-50.0]}"#);
fgb.add_feature_geom(geom, |feat| {
feat.property(0, "max_byte", &ColumnValue::Byte(i8::MAX))
.unwrap();
feat.property(1, "max_ubyte", &ColumnValue::UByte(u8::MAX))
.unwrap();
feat.property(2, "max_bool", &ColumnValue::Bool(true))
.unwrap();
feat.property(3, "max_short", &ColumnValue::Short(i16::MAX))
.unwrap();
feat.property(4, "max_ushort", &ColumnValue::UShort(u16::MAX))
.unwrap();
feat.property(5, "max_int", &ColumnValue::Int(i32::MAX))
.unwrap();
feat.property(6, "max_uint", &ColumnValue::UInt(u32::MAX))
.unwrap();
feat.property(7, "max_long", &ColumnValue::Long(i64::MAX))
.unwrap();
feat.property(8, "max_ulong", &ColumnValue::ULong(u64::MAX))
.unwrap();
feat.property(9, "max_float", &ColumnValue::Float(f32::MAX))
.unwrap();
feat.property(10, "max_double", &ColumnValue::Double(f64::MAX))
.unwrap();
})
.expect("valid feature");
let mut output = vec![];
fgb.write(&mut output).expect("writable");
let mut reader = FgbReader::open(&*output)
.expect("openable")
.select_all_seq()
.expect("select all");
let feature = FallibleStreamingIterator::next(&mut reader)
.expect("successful read")
.expect("feature exists");
let max_byte: i8 = feature.property_n(0).expect("valid byte");
assert_eq!(max_byte, i8::MAX);
let max_ubyte: u8 = feature.property_n(1).expect("valid ubyte");
assert_eq!(max_ubyte, u8::MAX);
let max_bool: bool = feature.property_n(2).expect("valid bool");
assert!(max_bool);
let max_short: i16 = feature.property_n(3).expect("valid short");
assert_eq!(max_short, i16::MAX);
let max_ushort: u16 = feature.property_n(4).expect("valid ushort");
assert_eq!(max_ushort, u16::MAX);
let max_int: i32 = feature.property_n(5).expect("valid int");
assert_eq!(max_int, i32::MAX);
let max_uint: u32 = feature.property_n(6).expect("valid uint");
assert_eq!(max_uint, u32::MAX);
let max_long: i64 = feature.property_n(7).expect("valid max_long");
assert_eq!(max_long, i64::MAX);
let max_ulong: u64 = feature.property_n(8).expect("valid max_ulong");
assert_eq!(max_ulong, u64::MAX);
let max_float: f32 = feature.property_n(9).expect("valid max_float");
assert_eq!(max_float, f32::MAX);
let max_double: f64 = feature.property_n(10).expect("valid max_double");
assert_eq!(max_double, f64::MAX);
Ok(())
}
#[test]
fn geozero_to_fgb() -> Result<()> {
let mut fgb = FgbWriter::create("countries", GeometryType::MultiPolygon)?;
let mut fin = BufReader::new(File::open("../../test/data/countries.geojson")?);
let mut reader = GeoJsonReader(&mut fin);
reader.process(&mut fgb)?;
// let mut fout = BufWriter::new(File::create("test_multipoly.fgb")?);
let mut fout = BufWriter::new(tempfile()?);
fgb.write(&mut fout)?;
Ok(())
}
#[test]
fn test_save_fgb_and_load() -> Result<()> {
let file_to_write = NamedTempFile::new()?;
// Save
let linestrings: Vec<LineString<f64>> = vec![
geo_types::line_string![
(x: -21.95156, y: 64.1446),
(x: -21.951, y: 64.14479),
(x: -21.95044, y: 64.14527),
(x: -21.951445, y: 64.145508)
],
geo_types::line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0),],
];
let mut fgb = FgbWriter::create_with_options(
"test_write",
GeometryType::LineString,
FgbWriterOptions {
write_index: false,
crs: FgbCrs {
code: 4326,
..Default::default()
},
..Default::default()
},
)?;
for geom in linestrings.iter() {
let geom: geo_types::Geometry<f64> = geom.to_owned().into();
fgb.add_feature_geom(geom, |_feat| {})?;
}
let mut file = BufWriter::new(&file_to_write);
fgb.write(&mut file)?;
file.flush()?;
// Load
let read_file_again = file_to_write.reopen()?;
let mut filein = BufReader::new(&read_file_again);
let mut fgb = FgbReader::open(&mut filein)?.select_all()?;
let mut cnt = 0;
while let Some(feature) = fgb.next().unwrap() {
let _props = feature.properties();
let _geometry = feature.geometry().unwrap();
cnt += 1
}
assert_eq!(cnt, 2);
Ok(())
}