use clap::{Args, Parser, Subcommand, ValueEnum};
use gistools::{
parsers::{FileReader, FileWriter},
readers::{GISReader, ReaderType},
writers::{ToJSONOptions, to_json, to_jsonld},
};
use s2json::Projection;
#[derive(Parser)]
#[command(
name = "gis-tools",
version,
about = "CLI for GIS Tools",
long_about = "Found an issue? Please open an issue on GitHub at:\nhttps://github.com/Open-S2/gis-tools/issues"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Convert(ConvertArgs),
}
#[derive(Args)]
struct ConvertArgs {
#[arg(short = 'i', long = "input")]
input: Vec<String>, #[arg(short = 'o', long = "output")]
output: String,
#[arg(short = 'f', long = "inputFormat", value_enum)]
input_format: Option<ReaderType>,
#[arg(short = 'O', long = "outputFormat", value_enum)]
output_format: Option<OutFileType>,
#[arg(short, long, default_value_t = false)]
verbose: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
enum OutFileType {
Json,
Geojson,
S2json,
#[value(name = "json-ld")]
JsonLd,
#[value(name = "geojson-ld")]
GeojsonLd,
#[value(name = "s2json-ld")]
S2jsonLd,
}
impl std::fmt::Display for OutFileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutFileType::Json => f.write_str("json"),
OutFileType::Geojson => f.write_str("geojson"),
OutFileType::S2json => f.write_str("s2json"),
OutFileType::JsonLd => f.write_str("json-ld"),
OutFileType::GeojsonLd => f.write_str("geojson-ld"),
OutFileType::S2jsonLd => f.write_str("s2json-ld"),
}
}
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Convert(args) => {
if args.verbose {
println!("Converting inputs: {:?}", args.input);
}
let out_type = args.output_format.unwrap_or(OutFileType::Json);
let readers: Vec<GISReader<FileReader>> = args
.input
.iter()
.map(|path| GISReader::from_path(path.clone(), args.input_format.clone(), None))
.collect();
let mut writer = FileWriter::new(args.output.clone()).unwrap();
println!(
"Input: {:?} ({:?}), Output: {} ({})",
args.input, args.input_format, args.output, out_type
);
match out_type {
OutFileType::Json | OutFileType::Geojson | OutFileType::S2json => to_json(
&mut writer,
readers.iter().collect(),
Some(ToJSONOptions {
projection: Some(if out_type == OutFileType::S2json {
Projection::S2
} else {
Projection::WG
}),
geojson: Some(
out_type == OutFileType::Json || out_type == OutFileType::Geojson,
),
..Default::default()
}),
),
OutFileType::JsonLd | OutFileType::GeojsonLd | OutFileType::S2jsonLd => to_jsonld(
&mut writer,
readers.iter().collect(),
Some(ToJSONOptions {
projection: Some(if out_type == OutFileType::S2jsonLd {
Projection::S2
} else {
Projection::WG
}),
geojson: Some(
out_type == OutFileType::JsonLd || out_type == OutFileType::GeojsonLd,
),
..Default::default()
}),
),
}
if args.verbose {
println!("Output written to {}", args.output);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::OutFileType;
use assert_cmd::Command;
use predicates::prelude::*;
use std::path::PathBuf;
use tempfile::NamedTempFile;
#[test]
fn test_cli_out_file_type() {
let test = OutFileType::Json;
assert_eq!(test.to_string(), "json");
let test = OutFileType::JsonLd;
assert_eq!(test.to_string(), "json-ld");
let test = OutFileType::Geojson;
assert_eq!(test.to_string(), "geojson");
let test = OutFileType::GeojsonLd;
assert_eq!(test.to_string(), "geojson-ld");
let test = OutFileType::S2json;
assert_eq!(test.to_string(), "s2json");
let test = OutFileType::S2jsonLd;
assert_eq!(test.to_string(), "s2json-ld");
}
#[test]
#[allow(deprecated)]
fn test_convert_geojson_to_json() {
let mut input = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
input.push("tests/readers/json/fixtures/points.geojson");
let output = NamedTempFile::new().expect("Failed to create temporary file");
let output_path = output.path();
let mut cmd = Command::cargo_bin("gis-tools").unwrap();
cmd.args([
"convert",
"-i",
input.to_str().unwrap(),
"-o",
output_path.to_str().unwrap(),
"-f",
"geojson",
"-O",
"json",
"-v",
]);
cmd.assert().success().stdout(predicate::str::contains("Converting inputs"));
let contents = std::fs::read_to_string(output_path).expect("Output not written");
assert!(contents.contains("{"));
assert!(contents.contains("features"));
}
#[test]
#[allow(deprecated)]
fn test_convert_geojson_to_geojsonld() {
let mut input = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
input.push("tests/readers/json/fixtures/points.geojson");
let output = NamedTempFile::new().expect("Failed to create temporary file");
let output_path = output.path();
let mut cmd = Command::cargo_bin("gis-tools").unwrap();
cmd.args([
"convert",
"-i",
input.to_str().unwrap(),
"-o",
output_path.to_str().unwrap(),
"-f",
"geojson",
"-O",
"geojson-ld",
"-v",
]);
cmd.assert().success().stdout(predicate::str::contains("Converting inputs"));
let contents = std::fs::read_to_string(output_path).expect("Output not written");
assert!(contents.contains("Feature"), "Expected JSON-LD context in output");
}
}