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

CLI adds bounds calculation and style flag to csv writer #251

Merged
merged 3 commits into from
Feb 8, 2025
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
53 changes: 39 additions & 14 deletions geozero-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ struct Cli {

/// The path to the file to write
dest: PathBuf,

/// Will be placed within <style>...</style> tags of the top level svg element.
#[arg(long)]
svg_style: Option<String>,
}

#[derive(Copy, Clone, Debug, PartialEq)]
Expand All @@ -55,7 +59,7 @@ fn parse_extent(src: &str) -> std::result::Result<Extent, ParseFloatError> {
})
}

async fn transform<P: FeatureProcessor>(args: Cli, processor: &mut P) -> Result<()> {
async fn transform<P: FeatureProcessor>(args: &Cli, processor: &mut P) -> Result<()> {
let path_in = Path::new(&args.input);
if path_in.starts_with("http:") || path_in.starts_with("https:") {
if path_in.extension().and_then(OsStr::to_str) != Some("fgb") {
Expand All @@ -78,8 +82,9 @@ async fn transform<P: FeatureProcessor>(args: Cli, processor: &mut P) -> Result<
Some("csv") => {
let geometry_column_name = args
.csv_geometry_column
.as_ref()
.expect("must specify --csv-geometry-column=<column name> when parsing CSV");
let mut ds = CsvReader::new(&geometry_column_name, &mut filein);
let mut ds = CsvReader::new(geometry_column_name, &mut filein);
GeozeroDatasource::process(&mut ds, processor)
}
Some("json") | Some("geojson") => {
Expand Down Expand Up @@ -107,32 +112,52 @@ async fn transform<P: FeatureProcessor>(args: Cli, processor: &mut P) -> Result<
async fn process(args: Cli) -> Result<()> {
let mut fout = BufWriter::new(File::create(&args.dest)?);
match args.dest.extension().and_then(OsStr::to_str) {
Some("csv") => transform(args, &mut CsvWriter::new(&mut fout)).await?,
Some("wkt") => transform(args, &mut WktWriter::new(&mut fout)).await?,
Some("csv") => transform(&args, &mut CsvWriter::new(&mut fout)).await?,
Some("wkt") => transform(&args, &mut WktWriter::new(&mut fout)).await?,
Some("json") | Some("geojson") => {
transform(args, &mut GeoJsonWriter::new(&mut fout)).await?
transform(&args, &mut GeoJsonWriter::new(&mut fout)).await?
}
Some("fgb") => {
let mut fgb =
FgbWriter::create("fgb", GeometryType::Unknown).map_err(fgb_to_geozero_err)?;
transform(args, &mut fgb).await?;
transform(&args, &mut fgb).await?;
fgb.write(&mut fout).map_err(fgb_to_geozero_err)?;
}
Some("svg") => {
let extent = get_extent(&args).await?;
let mut processor = SvgWriter::new(&mut fout, true);
set_dimensions(&mut processor, args.extent);
transform(args, &mut processor).await?;
// TODO: get width/height from args
processor.set_style(args.svg_style.clone());
processor.set_dimensions(extent.minx, extent.miny, extent.maxx, extent.maxy, 800, 600);
transform(&args, &mut processor).await?;
}
_ => panic!("Unknown output file extension"),
}
Ok(())
}
fn set_dimensions(processor: &mut SvgWriter<&mut BufWriter<File>>, extent: Option<Extent>) {
if let Some(extent) = extent {
processor.set_dimensions(extent.minx, extent.miny, extent.maxx, extent.maxy, 800, 600);
} else {
// TODO: get image size as opts and full extent from data
processor.set_dimensions(-180.0, -90.0, 180.0, 90.0, 800, 600);

async fn get_extent(args: &Cli) -> Result<Extent> {
match args.extent {
Some(extent) => Ok(extent),
None => {
let mut bounds_processor = geozero::bounds::BoundsProcessor::new();
transform(args, &mut bounds_processor).await?;
let Some(computed_bounds) = bounds_processor.bounds() else {
return Ok(Extent {
minx: 0.0,
miny: 0.0,
maxx: 0.0,
maxy: 0.0,
});
};

Ok(Extent {
minx: computed_bounds.min_x(),
miny: computed_bounds.min_y(),
maxx: computed_bounds.max_x(),
maxy: computed_bounds.max_y(),
})
}
}
}

Expand Down
1 change: 1 addition & 0 deletions geozero/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## UNRELEASED

* Add `style` option to SVGWriter for writing \<style\> tags
* Add `BoundsProcessor` to compute bounds of geometry
* Update Deps:
* BREAKING: `flatgeobuf` to 4.5.0
Expand Down
63 changes: 61 additions & 2 deletions geozero/src/svg/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub struct SvgWriter<W: Write> {
out: W,
invert_y: bool,
view_box: Option<(f64, f64, f64, f64)>,
style: Option<String>,
size: Option<(u32, u32)>,
}

Expand All @@ -16,6 +17,7 @@ impl<W: Write> SvgWriter<W> {
out,
invert_y,
view_box: None,
style: None,
size: None,
}
}
Expand All @@ -35,6 +37,9 @@ impl<W: Write> SvgWriter<W> {
};
self.size = Some((width, height));
}
pub fn set_style(&mut self, style: Option<String>) {
self.style = style;
}
}

impl<W: Write> FeatureProcessor for SvgWriter<W> {
Expand All @@ -55,8 +60,14 @@ impl<W: Write> FeatureProcessor for SvgWriter<W> {
}
self.out.write_all(
br#"stroke-linecap="round" stroke-linejoin="round">
<g id=""#,
"#,
)?;

if let Some(style) = &self.style {
writeln!(self.out, "<style>{style}</style>")?;
}

self.out.write_all(br#"<g id=""#)?;
if let Some(name) = name {
self.out.write_all(name.as_bytes())?;
}
Expand Down Expand Up @@ -131,7 +142,7 @@ impl<W: Write> PropertyProcessor for SvgWriter<W> {}
mod test {
use super::*;
use crate::geojson::read_geojson;
use crate::ToSvg;
use crate::{GeozeroDatasource, GeozeroGeometry, ToSvg};
use geo_types::polygon;

#[test]
Expand Down Expand Up @@ -303,4 +314,52 @@ mod test {
</svg>"#
);
}

#[test]
fn test_style() {
let geojson = serde_json::json!({
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[-122.4194, 37.7749],
[-118.2437, 34.0522]
]
},
"properties": {
"name": "San Francisco to Los Angeles"
}
})
.to_string();
let mut geom = crate::geojson::GeoJson(&geojson);

{
let mut output: Vec<u8> = vec![];
let mut svg_writer = SvgWriter::new(&mut output, false);
geom.process_geom(&mut svg_writer).unwrap();
let svg_output = String::from_utf8(output).unwrap();
assert_eq!(
svg_output,
r#"<path d="-122.4194 37.7749 -118.2437 34.0522 "/>"#
);
}

{
let mut output: Vec<u8> = vec![];
let mut svg_writer = SvgWriter::new(&mut output, false);
svg_writer.set_style(Some("path { opacity: 0.123; }".to_string()));
GeozeroDatasource::process(&mut geom, &mut svg_writer).unwrap();
let svg_output = String::from_utf8(output).unwrap();
assert_eq!(
svg_output,
r#"<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" stroke-linecap="round" stroke-linejoin="round">
<style>path { opacity: 0.123; }</style>
<g id="">
<path d="-122.4194 37.7749 -118.2437 34.0522 "/>
</g>
</svg>"#
);
}
}
}