#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) struct SqliteFixture(PathBuf);
impl std::ops::Deref for SqliteFixture {
type Target = std::path::Path;
fn deref(&self) -> &std::path::Path {
&self.0
}
}
impl AsRef<std::path::Path> for SqliteFixture {
fn as_ref(&self) -> &std::path::Path {
&self.0
}
}
impl Drop for SqliteFixture {
fn drop(&mut self) {
for suffix in ["", "-wal", "-shm", "-journal"] {
let mut sidecar = self.0.clone().into_os_string();
sidecar.push(suffix);
let _ = std::fs::remove_file(PathBuf::from(sidecar));
}
}
}
fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
}
pub(crate) fn demo_fixture(relative: &str) -> PathBuf {
workspace_root().join(relative)
}
pub(crate) fn unique_temp_path(label: &str, extension: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let mut path = std::env::temp_dir();
path.push(format!("oxigeo_cli_test_{label}_{pid}_{n}.{extension}"));
path
}
fn write_temp_file(bytes: &[u8], label: &str, extension: &str) -> PathBuf {
let path = unique_temp_path(label, extension);
std::fs::write(&path, bytes).expect("write fixture file");
path
}
pub(crate) fn flatgeobuf_fixture_path() -> PathBuf {
use oxigeo_core::vector::{Feature, Geometry, Point};
use oxigeo_flatgeobuf::{FlatGeobufWriterBuilder, GeometryType};
use std::fs::File;
use std::io::BufWriter;
let path = unique_temp_path("fixture", "fgb");
let file = File::create(&path).expect("create fgb fixture file");
let buf_writer = BufWriter::new(file);
let mut writer = FlatGeobufWriterBuilder::new(GeometryType::Point)
.build(buf_writer)
.expect("build FlatGeobuf writer");
for (x, y) in [(139.7, 35.7), (-74.0, 40.7), (2.35, 48.85)] {
let feature = Feature::new(Geometry::Point(Point::new(x, y)));
writer.add_feature(&feature).expect("add feature");
}
writer.finish().expect("finish FlatGeobuf writer");
path
}
pub(crate) fn geometrycollection_fgb_fixture_path() -> PathBuf {
use oxigeo_core::vector::{Feature, Geometry, Point};
use oxigeo_flatgeobuf::FlatGeobufWriterBuilder;
use oxigeo_flatgeobuf::header::{CrsInfo, GeometryType};
use std::fs::File;
use std::io::BufWriter;
let path = unique_temp_path("gc_fixture", "fgb");
let file = File::create(&path).expect("create fgb fixture file");
let buf_writer = BufWriter::new(file);
let mut writer = FlatGeobufWriterBuilder::new(GeometryType::GeometryCollection)
.with_index()
.with_crs(CrsInfo::from_epsg(4326))
.build(buf_writer)
.expect("build FlatGeobuf writer");
for (x, y) in [(-2.9, 43.2), (-3.4, 42.7), (-2.4, 44.2), (-4.4, 43.7)] {
let feature = Feature::new(Geometry::Point(Point::new(x, y)));
writer.add_feature(&feature).expect("add feature");
}
writer.finish().expect("finish FlatGeobuf writer");
path
}
pub(crate) fn gpkg_fixture_bytes() -> Vec<u8> {
oxigeo_gpkg::GeoPackageBuilder::new(4326)
.add_feature_table(
"cities",
"POINT",
vec![(1, 139.7, 35.7), (2, -74.0, 40.7), (3, 2.35, 48.85)],
)
.build()
.expect("build GeoPackage fixture")
}
pub(crate) fn gpkg_fixture_path() -> SqliteFixture {
SqliteFixture(write_temp_file(&gpkg_fixture_bytes(), "fixture", "gpkg"))
}
pub(crate) fn mbtiles_fixture_path() -> SqliteFixture {
use oxisql_core::{Connection, ToSqlValue};
use oxisql_sqlite_compat::SqliteConnection;
let path = unique_temp_path("mbtiles", "mbtiles");
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("build tokio runtime");
let path_str = path.to_string_lossy().into_owned();
let conn = rt
.block_on(SqliteConnection::open(&path_str))
.expect("open sqlite connection");
rt.block_on(conn.execute_batch(
"CREATE TABLE metadata (name TEXT, value TEXT);
CREATE TABLE tiles (
zoom_level INTEGER,
tile_column INTEGER,
tile_row INTEGER,
tile_data BLOB
);",
))
.expect("create schema");
for (name, value) in &[
("name", "oxigeo-cli-fixture"),
("format", "png"),
("bounds", "-180,-85,180,85"),
("minzoom", "0"),
("maxzoom", "1"),
] {
rt.block_on(conn.execute(
"INSERT INTO metadata (name, value) VALUES ($1, $2)",
&[name as &dyn ToSqlValue, value as &dyn ToSqlValue],
))
.expect("insert metadata row");
}
let tiles: &[(i64, i64, i64, Vec<u8>)] = &[
(0, 0, 0, vec![0x89, 0x50, 0x4e, 0x47]),
(1, 0, 0, vec![0x01, 0x02, 0x03, 0x04]),
(1, 1, 1, vec![0x05, 0x06, 0x07, 0x08]),
];
for (z, col, row, blob) in tiles {
let blob_owned: Vec<u8> = blob.clone();
rt.block_on(conn.execute(
"INSERT INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES ($1, $2, $3, $4)",
&[z as &dyn ToSqlValue, col, row, &blob_owned],
))
.expect("insert tile row");
}
rt.block_on(conn.execute_batch("PRAGMA wal_checkpoint"))
.expect("checkpoint wal");
SqliteFixture(path)
}
pub(crate) fn geoparquet_fixture_path() -> PathBuf {
use oxigeo_geoparquet::geometry::{Geometry, Point};
use oxigeo_geoparquet::{Crs, GeoParquetWriter, GeometryColumnMetadata};
let path = unique_temp_path("fixture", "parquet");
let column_metadata = GeometryColumnMetadata::new_wkb().with_crs(Crs::wgs84());
let mut writer = GeoParquetWriter::new(&path, "geometry", column_metadata)
.expect("create GeoParquet writer");
for (x, y) in [(139.7, 35.7), (-74.0, 40.7), (2.35, 48.85), (0.0, 0.0)] {
writer
.add_geometry(&Geometry::Point(Point::new_2d(x, y)))
.expect("add geometry");
}
writer.finish().expect("finish GeoParquet writer");
path
}
pub(crate) fn pmtiles_fixture_bytes() -> Vec<u8> {
use oxigeo_pmtiles::{PmTilesBuilder, TileType};
let mut builder = PmTilesBuilder::new(TileType::Png, 0, 1);
builder
.add_tile(0, 0, 0, &[0x89, 0x50, 0x4e, 0x47])
.expect("add tile 0/0/0");
builder
.add_tile(1, 0, 0, &[0x01, 0x02, 0x03, 0x04])
.expect("add tile 1/0/0");
builder
.add_tile(1, 1, 1, &[0x05, 0x06, 0x07, 0x08])
.expect("add tile 1/1/1");
builder.auto_bounds();
builder.build().expect("build PMTiles fixture")
}
pub(crate) fn pmtiles_fixture_path() -> PathBuf {
write_temp_file(&pmtiles_fixture_bytes(), "fixture", "pmtiles")
}
pub(crate) fn j2k_fixture_bytes() -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(&[0xFF, 0x4F]);
out.extend_from_slice(&[0xFF, 0x51]);
out.extend_from_slice(&[0x00, 0x29]);
out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x01]); out.push(0x07); out.push(0x01); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x52]);
out.extend_from_slice(&[0x00, 0x0C]);
out.push(0x00); out.push(0x00); out.extend_from_slice(&[0x00, 0x01]); out.push(0x00); out.push(0x00); out.push(0x02); out.push(0x02); out.push(0x00); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x5C]);
out.extend_from_slice(&[0x00, 0x04]);
out.push(0x00); out.push(0x00);
out.extend_from_slice(&[0xFF, 0x90]);
out.extend_from_slice(&[0x00, 0x0A]);
out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.push(0x00); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x93]);
out.extend_from_slice(&[0xFF, 0xD9]);
out
}
pub(crate) fn j2k_fixture_path() -> PathBuf {
write_temp_file(&j2k_fixture_bytes(), "fixture", "jp2")
}
fn make_las_header(number_of_points: u32, bounds: ([f64; 3], [f64; 3])) -> Vec<u8> {
let (min, max) = bounds;
let mut data = vec![0u8; 227];
data[0..4].copy_from_slice(b"LASF");
data[24] = 1; data[25] = 2; data[94..96].copy_from_slice(&227u16.to_le_bytes()); data[96..100].copy_from_slice(&227u32.to_le_bytes());
data[100..104].copy_from_slice(&2u32.to_le_bytes()); data[104] = 6; data[105..107].copy_from_slice(&30u16.to_le_bytes()); data[107..111].copy_from_slice(&number_of_points.to_le_bytes());
let scale = 0.001f64.to_le_bytes();
data[131..139].copy_from_slice(&scale);
data[139..147].copy_from_slice(&scale);
data[147..155].copy_from_slice(&scale);
data[179..187].copy_from_slice(&max[0].to_le_bytes());
data[187..195].copy_from_slice(&min[0].to_le_bytes());
data[195..203].copy_from_slice(&max[1].to_le_bytes());
data[203..211].copy_from_slice(&min[1].to_le_bytes());
data[211..219].copy_from_slice(&max[2].to_le_bytes());
data[219..227].copy_from_slice(&min[2].to_le_bytes());
data
}
fn make_copc_info_payload() -> Vec<u8> {
let mut data = vec![0u8; 160];
data[0..8].copy_from_slice(&50.0f64.to_le_bytes()); data[8..16].copy_from_slice(&50.0f64.to_le_bytes()); data[16..24].copy_from_slice(&25.0f64.to_le_bytes()); data[24..32].copy_from_slice(&50.0f64.to_le_bytes()); data[32..40].copy_from_slice(&1.0f64.to_le_bytes()); data[40..48].copy_from_slice(&500u64.to_le_bytes()); data[48..56].copy_from_slice(&16u64.to_le_bytes()); data
}
fn append_vlr(buf: &mut Vec<u8>, user_id: &str, record_id: u16, payload: &[u8]) {
buf.extend_from_slice(&[0u8; 2]); let mut uid_buf = [0u8; 16];
let uid_bytes = user_id.as_bytes();
let len = uid_bytes.len().min(16);
uid_buf[..len].copy_from_slice(&uid_bytes[..len]);
buf.extend_from_slice(&uid_buf);
buf.extend_from_slice(&record_id.to_le_bytes());
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(&[0u8; 32]); buf.extend_from_slice(payload);
}
pub(crate) fn copc_fixture_bytes(point_count: u32, bounds: ([f64; 3], [f64; 3])) -> Vec<u8> {
let mut data = make_las_header(point_count, bounds);
append_vlr(&mut data, "copc", 1, &make_copc_info_payload());
append_vlr(&mut data, "copc", 1000, &[0u8; 16]);
data
}
pub(crate) fn copc_fixture_path(point_count: u32, bounds: ([f64; 3], [f64; 3])) -> PathBuf {
write_temp_file(&copc_fixture_bytes(point_count, bounds), "fixture", "copc")
}