Skip to content

Commit

Permalink
CLI adds bounds calculation and style flag to csv writer (#251)
Browse files Browse the repository at this point in the history
Adds the ability to specify a top level `<style>` block in your svg
output.

Also exposes this functionality (and the recent bound computation #250)
to the CLI's svg output.

It's very basic. So basic that I'm not sure it's worth it, but I'm
personally using it so I thought I'd see if it had wider appeal.
  • Loading branch information
michaelkirk authored Feb 8, 2025
1 parent 6658338 commit f33714d
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 16 deletions.
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>"#
);
}
}
}

0 comments on commit f33714d

Please sign in to comment.