Skip to content

Commit 4e0f49a

Browse files
committed
feat: Draw vt features in the order of style rules
There was no way to set the order in which features (or layers) of vector tiles were drawn. The order was fixed by the contents of the MVT tile. This commit changes the logic of how vector tiles are drawn: 1. The style rules are drawn sequentially. E.g. the first rule is drawn first, then the second etc. 2. The features that correspond to several rules will be rendered several times (before all features were drawn at most once). 3. The default rules (the ones without `layer_name` set) are applied to all layers, that don't have any other rules applied to them.
1 parent b9c28b6 commit 4e0f49a

4 files changed

Lines changed: 125 additions & 135 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ csv = "1.3"
5050
egui = "0.33"
5151
egui-wgpu = "0.33"
5252
eframe = { version = "0.33", default-features = false }
53+
either = "1"
5354
env_logger = "0.11"
5455
fontdb = { version = "0.23", default-features = false }
5556
font-kit = "0.14"

galileo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ base64 = { workspace = true }
3030
bytemuck = { workspace = true, features = ["derive"] }
3131
bytes = { workspace = true }
3232
cfg-if = { workspace = true }
33+
either = { workspace = true }
3334
futures-intrusive = { workspace = true }
3435
galileo-mvt = { workspace = true }
3536
galileo-types = { workspace = true }

galileo/src/layer/vector_tile_layer/style.rs

Lines changed: 39 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! See [`VectorTileStyle`].
22
3-
use galileo_mvt::{MvtFeature, MvtGeometry};
3+
use galileo_mvt::MvtFeature;
44
use serde::{Deserialize, Serialize};
55

66
use crate::Color;
@@ -25,81 +25,6 @@ pub struct VectorTileStyle {
2525
pub background: StyleValue<Color>,
2626
}
2727

28-
impl VectorTileStyle {
29-
/// Get a rule for the given feature.
30-
pub fn get_style_rule(
31-
&self,
32-
layer_name: &str,
33-
resolution: f64,
34-
feature: &MvtFeature,
35-
) -> Option<&StyleRule> {
36-
self.rules.iter().find(|&rule| {
37-
let correct_geometry_type = match feature.geometry {
38-
MvtGeometry::Point(_)
39-
if matches!(
40-
rule.symbol,
41-
VectorTileSymbol::Point(_) | VectorTileSymbol::Label(_)
42-
) =>
43-
{
44-
true
45-
}
46-
MvtGeometry::LineString(_) if matches!(rule.symbol, VectorTileSymbol::Line(_)) => {
47-
true
48-
}
49-
MvtGeometry::Polygon(_) if matches!(rule.symbol, VectorTileSymbol::Polygon(_)) => {
50-
true
51-
}
52-
_ => false,
53-
};
54-
55-
if !correct_geometry_type {
56-
return false;
57-
}
58-
59-
if rule.layer_name.as_ref().is_some_and(|v| v != layer_name) {
60-
return false;
61-
}
62-
if rule.max_resolution.is_some_and(|v| v < resolution) {
63-
return false;
64-
}
65-
if rule.min_resolution.is_some_and(|v| v > resolution) {
66-
return false;
67-
}
68-
69-
rule.properties.iter().all(|filter| {
70-
let value = feature.properties.get(&filter.property_name);
71-
match (&filter.operator, value) {
72-
(PropertyFilterOperator::Equal(value), Some(v)) => v.eq_str(value),
73-
(PropertyFilterOperator::NotEqual(value), Some(v)) => !v.eq_str(value),
74-
(PropertyFilterOperator::NotEqual(_), None) => true,
75-
(PropertyFilterOperator::GreaterThan(value), Some(v)) => {
76-
compare_numeric(v, value, |a, b| a > b)
77-
}
78-
(PropertyFilterOperator::LessThan(value), Some(v)) => {
79-
compare_numeric(v, value, |a, b| a < b)
80-
}
81-
(PropertyFilterOperator::GreaterThanOrEqual(value), Some(v)) => {
82-
compare_numeric(v, value, |a, b| a >= b)
83-
}
84-
(PropertyFilterOperator::LessThanOrEqual(value), Some(v)) => {
85-
compare_numeric(v, value, |a, b| a <= b)
86-
}
87-
(PropertyFilterOperator::OneOf(values), Some(v)) => {
88-
values.iter().any(|candidate| v.eq_str(candidate))
89-
}
90-
(PropertyFilterOperator::NotOneOf(values), Some(v)) => {
91-
!values.iter().any(|candidate| v.eq_str(candidate))
92-
}
93-
(PropertyFilterOperator::Exist, Some(_)) => true,
94-
(PropertyFilterOperator::NotExist, None) => true,
95-
96-
_ => false,
97-
}
98-
})
99-
})
100-
}
101-
}
102-
10328
fn compare_numeric(a: &galileo_mvt::MvtValue, b: &str, cmp: impl Fn(f64, f64) -> bool) -> bool {
10429
if let Some(a_num) = a.as_f64()
10530
&& let Ok(b_num) = b.parse::<f64>()
@@ -113,7 +38,8 @@ fn compare_numeric(a: &galileo_mvt::MvtValue, b: &str, cmp: impl Fn(f64, f64) ->
11338
/// A rule that specifies what kind of features can be drawing with the given symbol.
11439
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
11540
pub struct StyleRule {
116-
/// If set, a feature must belong to the set layer. If not set, layer is not checked.
41+
/// If set, a feature must belong to the set layer. If not set, rule is applied to all layers, that don't have
42+
/// a rule (e.g. this will be used as a default style).
11743
pub layer_name: Option<String>,
11844
/// If set, the rule will only be applied at resolutions lower than this value.
11945
pub max_resolution: Option<f64>,
@@ -127,6 +53,42 @@ pub struct StyleRule {
12753
pub symbol: VectorTileSymbol,
12854
}
12955

56+
impl StyleRule {
57+
/// Returns true, if the rule should be applied for the given feature.
58+
pub fn applies(&self, feature: &MvtFeature) -> bool {
59+
self.properties.iter().all(|filter| {
60+
let value = feature.properties.get(&filter.property_name);
61+
match (&filter.operator, value) {
62+
(PropertyFilterOperator::Equal(value), Some(v)) => v.eq_str(value),
63+
(PropertyFilterOperator::NotEqual(value), Some(v)) => !v.eq_str(value),
64+
(PropertyFilterOperator::NotEqual(_), None) => true,
65+
(PropertyFilterOperator::GreaterThan(value), Some(v)) => {
66+
compare_numeric(v, value, |a, b| a > b)
67+
}
68+
(PropertyFilterOperator::LessThan(value), Some(v)) => {
69+
compare_numeric(v, value, |a, b| a < b)
70+
}
71+
(PropertyFilterOperator::GreaterThanOrEqual(value), Some(v)) => {
72+
compare_numeric(v, value, |a, b| a >= b)
73+
}
74+
(PropertyFilterOperator::LessThanOrEqual(value), Some(v)) => {
75+
compare_numeric(v, value, |a, b| a <= b)
76+
}
77+
(PropertyFilterOperator::OneOf(values), Some(v)) => {
78+
values.iter().any(|candidate| v.eq_str(candidate))
79+
}
80+
(PropertyFilterOperator::NotOneOf(values), Some(v)) => {
81+
!values.iter().any(|candidate| v.eq_str(candidate))
82+
}
83+
(PropertyFilterOperator::Exist, Some(_)) => true,
84+
(PropertyFilterOperator::NotExist, None) => true,
85+
86+
_ => false,
87+
}
88+
})
89+
}
90+
}
91+
13092
/// A filter that checks if a feature's property matches specific criteria.
13193
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13294
pub struct PropertyFilter {

galileo/src/layer/vector_tile_layer/tile_provider/vt_processor.rs

Lines changed: 84 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use galileo_mvt::{MvtFeature, MvtGeometry, MvtPolygon, MvtTile};
1+
use either::Either;
2+
use galileo_mvt::{MvtFeature, MvtGeometry, MvtLayer, MvtPolygon, MvtTile};
23
use galileo_types::cartesian::{CartesianPoint2d, Point3, Rect, Vector2};
34
use galileo_types::impls::{ClosedContour, Polygon};
45
use galileo_types::{Contour, MultiContour, MultiPolygon, Polygon as PolygonTrait};
@@ -28,6 +29,21 @@ pub struct VectorTileDecodeContext {
2829
pub bundle: RenderBundle,
2930
}
3031

32+
fn get_rule_layers<'a>(
33+
rule: &StyleRule,
34+
all_rules: &'a [StyleRule],
35+
layers: &'a [MvtLayer],
36+
) -> impl Iterator<Item = &'a MvtLayer> {
37+
match &rule.layer_name {
38+
Some(layer_name) => Either::Left(layers.iter().find(|l| &l.name == layer_name).into_iter()),
39+
None => Either::Right(layers.iter().filter(|l| {
40+
!all_rules
41+
.iter()
42+
.any(|r| r.layer_name.as_ref() == Some(&l.name))
43+
})),
44+
}
45+
}
46+
3147
impl VtProcessor {
3248
/// Pre-render the given tile into the given `bundle`.
3349
pub fn prepare(
@@ -57,69 +73,79 @@ impl VtProcessor {
5773
);
5874
bundle.world_set.clip_area(&bounds);
5975

60-
for layer in mvt_tile.layers.iter().rev() {
61-
for feature in &layer.features {
62-
let Some(rule) = style.get_style_rule(&layer.name, lod_resolution, feature) else {
63-
continue;
64-
};
65-
66-
match &feature.geometry {
67-
MvtGeometry::Point(points) => {
68-
let Some(paint) =
69-
Self::get_point_symbol(rule, lod_resolution, feature, tile_schema)
70-
else {
71-
continue;
72-
};
73-
74-
for point in points {
75-
let position = Self::transform_point(point, tile_resolution);
76-
match &paint.shape {
77-
PointShape::Label { text, style } => {
78-
if !text.is_empty() {
79-
bundle.add_label(
80-
&position,
81-
text,
82-
style,
83-
Vector2::default(),
84-
false,
85-
);
76+
for rule in &style.rules {
77+
if rule.max_resolution.is_some_and(|v| v < lod_resolution) {
78+
continue;
79+
}
80+
81+
if rule.min_resolution.is_some_and(|v| v > lod_resolution) {
82+
continue;
83+
}
84+
85+
for layer in get_rule_layers(rule, &style.rules, &mvt_tile.layers) {
86+
for feature in &layer.features {
87+
if !rule.applies(feature) {
88+
continue;
89+
}
90+
91+
match &feature.geometry {
92+
MvtGeometry::Point(points) => {
93+
let Some(paint) =
94+
Self::get_point_symbol(rule, lod_resolution, feature, tile_schema)
95+
else {
96+
continue;
97+
};
98+
99+
for point in points {
100+
let position = Self::transform_point(point, tile_resolution);
101+
match &paint.shape {
102+
PointShape::Label { text, style } => {
103+
if !text.is_empty() {
104+
bundle.add_label(
105+
&position,
106+
text,
107+
style,
108+
Vector2::default(),
109+
false,
110+
);
111+
}
112+
}
113+
_ => {
114+
bundle.add_point(&position, &paint, lod_resolution);
86115
}
87-
}
88-
_ => {
89-
bundle.add_point(&position, &paint, lod_resolution);
90116
}
91117
}
92118
}
93-
}
94-
MvtGeometry::LineString(contours) => {
95-
if let Some(paint) =
96-
Self::get_line_symbol(rule, lod_resolution, feature, tile_schema)
97-
{
98-
for contour in contours.contours() {
99-
bundle.add_line(
100-
&galileo_types::impls::Contour::new(
101-
contour
102-
.iter_points()
103-
.map(|p| Self::transform_point(&p, tile_resolution))
104-
.collect(),
105-
false,
106-
),
107-
&paint,
108-
lod_resolution,
109-
);
119+
MvtGeometry::LineString(contours) => {
120+
if let Some(paint) =
121+
Self::get_line_symbol(rule, lod_resolution, feature, tile_schema)
122+
{
123+
for contour in contours.contours() {
124+
bundle.add_line(
125+
&galileo_types::impls::Contour::new(
126+
contour
127+
.iter_points()
128+
.map(|p| Self::transform_point(&p, tile_resolution))
129+
.collect(),
130+
false,
131+
),
132+
&paint,
133+
lod_resolution,
134+
);
135+
}
110136
}
111137
}
112-
}
113-
MvtGeometry::Polygon(polygons) => {
114-
if let Some(paint) =
115-
Self::get_polygon_symbol(rule, lod_resolution, feature, tile_schema)
116-
{
117-
for polygon in polygons.polygons() {
118-
bundle.add_polygon(
119-
&Self::transform_polygon(polygon, tile_resolution),
120-
&paint,
121-
lod_resolution,
122-
);
138+
MvtGeometry::Polygon(polygons) => {
139+
if let Some(paint) =
140+
Self::get_polygon_symbol(rule, lod_resolution, feature, tile_schema)
141+
{
142+
for polygon in polygons.polygons() {
143+
bundle.add_polygon(
144+
&Self::transform_polygon(polygon, tile_resolution),
145+
&paint,
146+
lod_resolution,
147+
);
148+
}
123149
}
124150
}
125151
}

0 commit comments

Comments
 (0)