use std::time::Instant;
use geopackage::arrow::ArrowReadOptions;
use geopackage::{BoundingBox, GeoPackage, TableSchemaBuilder};
fn report(elapsed: std::time::Duration, reps: usize) {
println!(
"elapsed_ms={:.3}",
elapsed.as_secs_f64() * 1000.0 / reps as f64
);
}
fn info(path: &str, layer_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let gpkg = GeoPackage::open(path)?;
let layer = gpkg.layer(layer_name)?;
let rows: i64 = gpkg.connection().query_row(
&format!(
"SELECT COUNT(*) FROM {}",
geopackage::core::ident::quote(layer_name)?
),
[],
|row| row.get(0),
)?;
println!("rows={rows}");
println!("columns={}", layer.schema().columns.len());
match layer.geometry_column() {
Some(column) => {
println!("geometry_type={:?}", column.geometry_type);
println!("srs_id={}", column.srs_id);
}
None => println!("geometry_type=none"),
}
println!("spatial_index={}", layer.has_spatial_index()?);
println!("file_bytes={}", std::fs::metadata(path)?.len());
if let Some(entry) = gpkg
.contents()?
.into_iter()
.find(|entry| entry.table_name == layer_name)
&& let (Some(min_x), Some(min_y), Some(max_x), Some(max_y)) =
(entry.min_x, entry.min_y, entry.max_x, entry.max_y)
{
println!("extent={min_x} {min_y} {max_x} {max_y}");
}
Ok(())
}
fn scan(
path: &str,
layer_name: &str,
mode: &str,
reps: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let gpkg = GeoPackage::open(path)?;
let layer = gpkg.layer(layer_name)?;
let mut cursor = layer.cursor()?;
let mut rows = 0usize;
let mut geometries = 0usize;
let start = Instant::now();
for _ in 0..reps {
rows = 0;
geometries = 0;
for feature in cursor.features()? {
let feature = feature?;
rows += 1;
match mode {
"values" => {
std::hint::black_box(feature.values().len());
}
"geom" => {
if let Some(geometry) = feature.geometry()? {
geometries += 1;
std::hint::black_box(geometry.wkb_body().len());
}
}
_ => {
if let Some(geometry) = feature.geometry()?
&& let Some(geo) = geometry.to_geo()
{
geometries += 1;
std::hint::black_box(&geo);
}
}
}
}
}
let elapsed = start.elapsed();
report(elapsed, reps);
println!("rows={rows}");
println!("geometries={geometries}");
println!("mode={mode}");
Ok(())
}
fn arrow(
path: &str,
layer_name: &str,
reps: usize,
threads: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let gpkg = GeoPackage::open(path)?;
let layer = gpkg.layer(layer_name)?;
let options = ArrowReadOptions::default().with_threads(threads);
let mut rows = 0usize;
let mut batches = 0usize;
let mut columns = 0usize;
let start = Instant::now();
for _ in 0..reps {
rows = 0;
batches = 0;
for batch in layer.read_arrow(options)? {
let batch = batch?;
rows += batch.num_rows();
columns = batch.num_columns();
batches += 1;
}
}
let elapsed = start.elapsed();
report(elapsed, reps);
println!("rows={rows}");
println!("batches={batches}");
println!("columns={columns}");
println!("threads={threads}");
Ok(())
}
fn index(path: &str, layer_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let gpkg = GeoPackage::open(path)?;
let layer = gpkg.layer(layer_name)?;
if layer.has_spatial_index()? {
return Err("layer already has a spatial index; hand this a fresh copy".into());
}
let start = Instant::now();
layer.create_spatial_index()?;
let elapsed = start.elapsed();
report(elapsed, 1);
println!("spatial_index={}", layer.has_spatial_index()?);
Ok(())
}
fn bbox(
path: &str,
layer_name: &str,
bounds: BoundingBox,
reps: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let gpkg = GeoPackage::open(path)?;
let layer = gpkg.layer(layer_name)?;
let indexed = layer.has_spatial_index()?;
let mut cursor = layer.cursor_in(bounds)?;
let mut hits = 0usize;
let start = Instant::now();
for _ in 0..reps {
hits = 0;
for feature in cursor.features()? {
std::hint::black_box(feature?.fid());
hits += 1;
}
}
let elapsed = start.elapsed();
report(elapsed, reps);
println!("hits={hits}");
println!("spatial_index={indexed}");
Ok(())
}
fn write(
source: &str,
source_layer: &str,
target: &str,
with_index: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let src = GeoPackage::open(source)?;
let layer = src.layer(source_layer)?;
let schema = layer.arrow_schema()?;
let batches: Vec<_> = layer
.read_arrow(ArrowReadOptions::default())?
.collect::<Result<Vec<_>, _>>()?;
let batch_count = batches.len();
let dst = GeoPackage::create(target)?;
let created = dst.create_layer(
&TableSchemaBuilder::new(source_layer)
.from_arrow_schema(&schema)?
.spatial_index(false),
)?;
if with_index {
created.create_spatial_index()?;
}
drop(created);
let start = Instant::now();
let written = dst
.layer(source_layer)?
.write_arrow(batches.into_iter().map(Ok), 0)?;
dst.close()?;
let elapsed = start.elapsed();
report(elapsed, 1);
println!("rows={}", written.len());
println!("batches={batch_count}");
println!("index={}", if with_index { "yes" } else { "no" });
Ok(())
}
fn noop(path: &str) -> Result<(), Box<dyn std::error::Error>> {
let start = Instant::now();
let gpkg = GeoPackage::open(path)?;
drop(gpkg);
report(start.elapsed(), 1);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
let usage = "usage: dataset_bench <info|scan|arrow|index|bbox|write|noop> <file> <layer> [...]";
let command = args.get(1).map(String::as_str).unwrap_or("");
let path = args.get(2).map(String::as_str).unwrap_or("");
let layer = args.get(3).map(String::as_str).unwrap_or("");
match command {
"info" => info(path, layer),
"scan" => {
let mode = args.get(4).map(String::as_str).unwrap_or("geom");
let reps: usize = args.get(5).map_or(Ok(1), |r| r.parse())?;
scan(path, layer, mode, reps.max(1))
}
"arrow" => {
let reps: usize = args.get(4).map_or(Ok(1), |r| r.parse())?;
let threads: usize = args.get(5).map_or(Ok(0), |t| t.parse())?;
arrow(path, layer, reps.max(1), threads)
}
"index" => index(path, layer),
"bbox" => {
let value = |i: usize| -> Result<f64, Box<dyn std::error::Error>> {
Ok(args.get(i).ok_or(usage)?.parse()?)
};
let bounds = BoundingBox::new(value(4)?, value(5)?, value(6)?, value(7)?);
let reps: usize = args.get(8).map_or(Ok(1), |r| r.parse())?;
bbox(path, layer, bounds, reps.max(1))
}
"write" => {
let target = args.get(4).ok_or(usage)?;
let with_index = args.get(5).map(String::as_str) == Some("yes");
write(path, layer, target, with_index)
}
"noop" => noop(path),
_ => Err(usage.into()),
}
}