Repository navigation
feat(map): a point's marks stacked, shifted and turned, and markers turned by a field #933
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
Changes from all commits
826826f
a7c5f36
0bf7ccd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,7 @@ pub use globe::{Globe, Projection}; | |
| pub use raster::{RasterDefaults, RasterLayer, RasterRenderer, RasterStyle, Resampling, Stretch}; | ||
| pub use symbology::{ | ||
| ClassMethod, FontName, Glyph, LabelClass, LabelKey, LabelMatcher, LabelPlacement, Labeling, LineCap, LineJoin, LineMark, LineMarkers, | ||
| LineStroke, Marker, Placements, Renderer, Symbol, | ||
| LineStroke, Marker, MarkerRotation, Placements, Renderer, Symbol, | ||
| }; | ||
| pub use tiles::{TileKind, TileLayer}; | ||
|
|
||
|
|
@@ -144,14 +144,20 @@ pub struct Layer { | |
| #[serde(default)] | ||
| pub extrusion: Option<effects::Extrusion>, | ||
| /// A line layer's stacked strokes, drawn bottom first in place of its single stroke (none: | ||
| /// the renderer's stroke alone). | ||
| /// the renderer's stroke alone). A point layer's are its marks: each stroke of markers draws | ||
| /// its marker at the point, bottom first, in place of the symbol's (see | ||
| /// [`LineStroke`]); a stroke without markers draws nothing there. | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "symbology::de_strokes")] | ||
| pub line_strokes: Vec<symbology::LineStroke>, | ||
| /// Draws the stacked strokes in levels (symbol levels): every feature's strokes of one | ||
| /// [`LineStroke::level`] before any of the next, so road casings join under the cores | ||
| /// instead of cutting across them where lines meet. | ||
| #[serde(default, skip_serializing_if = "std::ops::Not::not")] | ||
| pub stroke_levels: bool, | ||
| /// Turns each point's marker (and its marks, their offsets with them) by the angle in a | ||
| /// field, as QGIS's data-defined symbol angle does. | ||
| #[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "symbology::de_marker_rotation")] | ||
| pub marker_rotation: Option<symbology::MarkerRotation>, | ||
| /// The scale (1:N) its symbol and label sizes are set for: drawn at another scale they grow | ||
| /// and shrink with the map, as on paper (QGIS's reference scale). `None`: they keep their | ||
| /// size on screen. | ||
|
|
@@ -308,6 +314,7 @@ impl Layer { | |
| extrusion: None, | ||
| line_strokes: Vec::new(), | ||
| stroke_levels: false, | ||
| marker_rotation: None, | ||
| reference_scale: None, | ||
| priority: 0, | ||
| design: design::Design::default(), | ||
|
|
@@ -503,6 +510,13 @@ impl Layer { | |
| if self.graphics { graphics::symbol(&self.dataset, row) } else { self.renderer.symbol_for(&self.dataset, row) } | ||
| } | ||
|
|
||
| /// How far a point row's marker is turned, in degrees clockwise ([`Layer::marker_rotation`]): 0 | ||
| /// without, or where the row's value isn't a number. | ||
| pub fn marker_turn(&self, row: usize) -> f32 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Declining. |
||
| let Some(r) = self.marker_rotation.as_ref().filter(|_| !self.graphics) else { return 0.0 }; | ||
| r.turn(&self.dataset, row) | ||
| } | ||
|
|
||
| /// The stacked strokes a line feature row is drawn with: its class's own | ||
| /// ([`Renderer::strokes_for`]), else the layer's `line_strokes`; none (the single stroke) for | ||
| /// a graphics layer. | ||
|
|
@@ -1737,6 +1751,34 @@ mod tests { | |
| d | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_marker_rotation_is_kept_and_a_crafted_one_reads_as_none() { | ||
| let mut l = | ||
| Layer::new(1, Dataset::new("p", GeometryType::Point, vec![rustgis_data::Field::new("a", rustgis_data::FieldType::Double)])); | ||
| l.dataset.push(Some(Geometry::Point(Coord::new(0.0, 0.0))), vec![rustgis_data::Value::Float(450.0)]); | ||
| // None isn't written: an older RustGIS reads the layer as before. | ||
| assert!(serde_json::to_value(&l).unwrap().get("marker_rotation").is_none()); | ||
| l.marker_rotation = Some(symbology::MarkerRotation { field: "a".into(), counterclockwise: true }); | ||
| let v = serde_json::to_value(&l).unwrap(); | ||
| let back: Layer = serde_json::from_value(v.clone()).unwrap(); | ||
| assert_eq!(back.marker_rotation, l.marker_rotation); | ||
| assert_eq!(back.marker_turn(0), -90.0, "450° counterclockwise"); | ||
| for junk in [ | ||
| serde_json::json!(5), | ||
| serde_json::json!({"field": 3}), | ||
| serde_json::json!({"field": ""}), | ||
| serde_json::json!({"field": "x".repeat(10_000)}), | ||
| ] { | ||
| let mut v = v.clone(); | ||
| v["marker_rotation"] = junk.clone(); | ||
| let read: Layer = serde_json::from_value(v).unwrap(); | ||
| assert!(read.marker_rotation.is_none(), "{junk}"); | ||
| } | ||
| // A field the data lacks turns nothing. | ||
| l.marker_rotation = Some(symbology::MarkerRotation { field: "gone".into(), counterclockwise: false }); | ||
| assert_eq!(l.marker_turn(0), 0.0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn hostile_line_strokes_in_a_project_are_brought_into_range() { | ||
| // A crafted project file: too many strokes, and widths and offsets far out of range. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
/// A line layer is active.doc comment now sits onneed_lines_or_points, andneed_linesbelow has lost its doc. Move it back and document the new functions:and add
/// A point layer is active.aboveneed_points, and/// A line layer is active.aboveneed_lines.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right: the doc comment landed on
need_lines_or_points, andneed_lineslost its own. This PR merged before the fix could go in. I'll leave it for a small follow-up.