diff --git a/geozero-cli/src/main.rs b/geozero-cli/src/main.rs index 3a45b586..0ab28b1c 100644 --- a/geozero-cli/src/main.rs +++ b/geozero-cli/src/main.rs @@ -29,6 +29,10 @@ struct Cli { /// The path to the file to write dest: PathBuf, + + /// Will be placed within tags of the top level svg element. + #[arg(long)] + svg_style: Option, } #[derive(Copy, Clone, Debug, PartialEq)] @@ -55,7 +59,7 @@ fn parse_extent(src: &str) -> std::result::Result { }) } -async fn transform(args: Cli, processor: &mut P) -> Result<()> { +async fn transform(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") { @@ -78,8 +82,9 @@ async fn transform(args: Cli, processor: &mut P) -> Result< Some("csv") => { let geometry_column_name = args .csv_geometry_column + .as_ref() .expect("must specify --csv-geometry-column= 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") => { @@ -107,32 +112,52 @@ async fn transform(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>, extent: Option) { - 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 { + 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(), + }) + } } } diff --git a/geozero/CHANGELOG.md b/geozero/CHANGELOG.md index 9748a114..3802af72 100644 --- a/geozero/CHANGELOG.md +++ b/geozero/CHANGELOG.md @@ -1,5 +1,6 @@ ## UNRELEASED +* Add `style` option to SVGWriter for writing \ tags * Add `BoundsProcessor` to compute bounds of geometry * Update Deps: * BREAKING: `flatgeobuf` to 4.5.0 diff --git a/geozero/src/svg/writer.rs b/geozero/src/svg/writer.rs index 6db58176..48c1ae54 100644 --- a/geozero/src/svg/writer.rs +++ b/geozero/src/svg/writer.rs @@ -7,6 +7,7 @@ pub struct SvgWriter { out: W, invert_y: bool, view_box: Option<(f64, f64, f64, f64)>, + style: Option, size: Option<(u32, u32)>, } @@ -16,6 +17,7 @@ impl SvgWriter { out, invert_y, view_box: None, + style: None, size: None, } } @@ -35,6 +37,9 @@ impl SvgWriter { }; self.size = Some((width, height)); } + pub fn set_style(&mut self, style: Option) { + self.style = style; + } } impl FeatureProcessor for SvgWriter { @@ -55,8 +60,14 @@ impl FeatureProcessor for SvgWriter { } self.out.write_all( br#"stroke-linecap="round" stroke-linejoin="round"> -{style}")?; + } + + self.out.write_all(br#" PropertyProcessor for SvgWriter {} mod test { use super::*; use crate::geojson::read_geojson; - use crate::ToSvg; + use crate::{GeozeroDatasource, GeozeroGeometry, ToSvg}; use geo_types::polygon; #[test] @@ -303,4 +314,52 @@ mod test { "# ); } + + #[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 = 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#""# + ); + } + + { + let mut output: Vec = 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#" + + + + + +"# + ); + } + } }