Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nusamai-gpkg: クレートの作成、空のGeoPackageファイル作成 #89

Merged
merged 9 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"nusamai-geometry",
"nusamai-gltf",
"nusamai-geojson",
"nusamai-gpkg",
"nusamai-plateau",
"nusamai-mvt",
"nusamai",
Expand Down
3 changes: 3 additions & 0 deletions nusamai-gpkg/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.gpkg
*.gpkg-shm
*.gpkg-wal
15 changes: 15 additions & 0 deletions nusamai-gpkg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "nusamai-gpkg"
version = "0.1.0"
edition = "2021"

[dependencies]
sqlx = { version = "0.7.3", features = ["sqlite", "runtime-tokio"] }
tokio = { version = "1.35.1", features = ["full"] }
nusamai-plateau = { path = "../nusamai-plateau" }
thiserror = "1.0.51"

[dev-dependencies]
citygml = {path = "../nusamai-plateau/citygml" }
clap = { version = "4.4.8", features = ["derive"] }
quick-xml = "0.31.0"
68 changes: 68 additions & 0 deletions nusamai-gpkg/examples/gml2gpkg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use citygml::{CityGMLElement, CityGMLReader, ParseError, SubTreeReader};
use clap::Parser;
use nusamai_plateau::TopLevelCityObject;
use std::io::BufRead;

#[derive(Parser)]
struct Args {
#[clap(required = true)]
filename: String,
}

fn toplevel_dispatcher<R: BufRead>(
st: &mut SubTreeReader<R>,
) -> Result<Vec<TopLevelCityObject>, ParseError> {
let mut cityobjs: Vec<TopLevelCityObject> = vec![];

match st.parse_children(|st| match st.current_path() {
b"core:cityObjectMember" => {
let mut cityobj: nusamai_plateau::models::TopLevelCityObject = Default::default();
cityobj.parse(st)?;
let geometries = st.collect_geometries();

if let Some(root) = cityobj.into_object() {
let obj = TopLevelCityObject { root, geometries };
cityobjs.push(obj);
}

Ok(())
}
b"gml:boundedBy" | b"app:appearanceMember" => {
st.skip_current_element()?;
Ok(())
}
other => Err(ParseError::SchemaViolation(format!(
"Unrecognized element {}",
String::from_utf8_lossy(other)
))),
}) {
Ok(_) => Ok(cityobjs),
Err(e) => {
println!("Err: {:?}", e);
Err(e)
}
}
}

#[tokio::main]
async fn main() {
// Parse CityGML

let args = Args::parse();

let reader = std::io::BufReader::new(std::fs::File::open(args.filename).unwrap());
let mut xml_reader = quick_xml::NsReader::from_reader(reader);
let _cityobjs = match CityGMLReader::new().start_root(&mut xml_reader) {
Ok(mut st) => match toplevel_dispatcher(&mut st) {
Ok(items) => items,
Err(e) => panic!("Err: {:?}", e),
},
Err(e) => panic!("Err: {:?}", e),
};

// GeoPackage

let output_path = "output.gpkg";
let _handler = nusamai_gpkg::GpkgHandler::init(output_path).await.unwrap();
// TODO: handler.add_objects(&cityobjs).await;
}
98 changes: 98 additions & 0 deletions nusamai-gpkg/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use nusamai_plateau::TopLevelCityObject;
use sqlx::Row;
use sqlx::{migrate::MigrateDatabase, Pool, Sqlite, SqlitePool};
use std::path::Path;
use thiserror::Error;

pub struct GpkgHandler {
sorami marked this conversation as resolved.
Show resolved Hide resolved
pool: Pool<Sqlite>,
}

#[derive(Error, Debug)]
pub enum GpkgError {
#[error("Database file already exists: {0}")]
DatabaseExists(String),
#[error("SQLx error: {0}")]
SqlxError(#[from] sqlx::Error),
sorami marked this conversation as resolved.
Show resolved Hide resolved
}

impl GpkgHandler {
/// Create and initialize new GeoPackage database
pub async fn init(path: &str) -> Result<Self, GpkgError> {
if Path::new(path).exists() {
return Err(GpkgError::DatabaseExists(path.to_string()));
}

let db_url = format!("sqlite://{}", path);

Sqlite::create_database(&db_url).await?;
let pool = SqlitePool::connect(&db_url).await?;

// Initialize the database with minimum GeoPackage schema
let create_query = include_str!("sql/gpkg_template.sql");
sqlx::query(create_query).execute(&pool).await?;

Ok(Self { pool })
}

/// Connect to an existing GeoPackage database
pub async fn connect(path: &str) -> Result<Self, GpkgError> {
sorami marked this conversation as resolved.
Show resolved Hide resolved
let db_url = format!("sqlite://{}", path);
let pool = SqlitePool::connect(&db_url).await?;
Ok(Self { pool })
}

/// Get the names of all tables in the GeoPackage database
pub async fn table_names(&self) -> Vec<String> {
let result = sqlx::query(
"SELECT name
FROM sqlite_schema
WHERE type ='table'
AND name NOT LIKE 'sqlite_%';",
)
.fetch_all(&self.pool)
.await
.unwrap();

let mut table_names: Vec<String> = result
.iter()
.map(|row| row.get::<String, &str>("name"))
.collect();
table_names.sort();
table_names
}

/// Add a TopLevelCityObjects to the GeoPackage database
pub async fn add_object(&self, _object: &TopLevelCityObject) {
todo!();
}

/// Add TopLevelCityObjects to the GeoPackage database
pub async fn add_objects(&self, _objects: &[TopLevelCityObject]) {
todo!();
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn test_init_connect() {
let handler = GpkgHandler::init("sqlite::memory:").await.unwrap();
let _handler2 = GpkgHandler::connect("sqlite::memory:").await.unwrap();

let table_names = handler.table_names().await;
assert_eq!(
table_names,
vec![
"gpkg_contents",
"gpkg_extensions",
"gpkg_geometry_columns",
"gpkg_spatial_ref_sys",
"gpkg_tile_matrix",
"gpkg_tile_matrix_set"
]
);
}
}
3 changes: 3 additions & 0 deletions nusamai-gpkg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod handler;

pub use handler::GpkgHandler;
67 changes: 67 additions & 0 deletions nusamai-gpkg/src/sql/gpkg_template.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
CREATE TABLE gpkg_contents (
sorami marked this conversation as resolved.
Show resolved Hide resolved
table_name TEXT NOT NULL PRIMARY KEY,
data_type TEXT NOT NULL,
identifier TEXT UNIQUE,
description TEXT DEFAULT '',
last_change DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
min_x DOUBLE,
min_y DOUBLE,
max_x DOUBLE,
max_y DOUBLE,
srs_id INTEGER,
CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id)
);

CREATE TABLE gpkg_extensions (
table_name TEXT,
column_name TEXT,
extension_name TEXT NOT NULL,
definition TEXT NOT NULL,
scope TEXT NOT NULL,
CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)
);

CREATE TABLE gpkg_geometry_columns (
table_name TEXT NOT NULL,
column_name TEXT NOT NULL,
geometry_type_name TEXT NOT NULL,
srs_id INTEGER NOT NULL,
z TINYINT NOT NULL,
m TINYINT NOT NULL,
CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),
CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name),
CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys (srs_id)
);

CREATE TABLE gpkg_spatial_ref_sys (
srs_name TEXT NOT NULL,
srs_id INTEGER NOT NULL PRIMARY KEY,
organization TEXT NOT NULL,
organization_coordsys_id INTEGER NOT NULL,
definition TEXT NOT NULL,
description TEXT
);

CREATE TABLE gpkg_tile_matrix (
table_name TEXT NOT NULL,
zoom_level INTEGER NOT NULL,
matrix_width INTEGER NOT NULL,
matrix_height INTEGER NOT NULL,
tile_width INTEGER NOT NULL,
tile_height INTEGER NOT NULL,
pixel_x_size DOUBLE NOT NULL,
pixel_y_size DOUBLE NOT NULL,
CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),
CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name)
);

CREATE TABLE gpkg_tile_matrix_set (
table_name TEXT NOT NULL PRIMARY KEY,
srs_id INTEGER NOT NULL,
min_x DOUBLE NOT NULL,
min_y DOUBLE NOT NULL,
max_x DOUBLE NOT NULL,
max_y DOUBLE NOT NULL,
CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name),
CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys (srs_id)
);