mod common;
use common::render_with_features;
use ezu_features::{Feature, FeatureLayer, Geometry, Value};
use ezu_graph::TileId;
use std::collections::HashMap;
fn hline(y: i32) -> Vec<(i32, i32)> {
vec![(0, y), (4095, y)]
}
fn line_feature(class: &str, y: i32) -> Feature {
let mut properties = HashMap::new();
properties.insert("class".to_string(), Value::String(class.to_string()));
let mut geometry = Geometry::default();
geometry.lines.push(hline(y));
Feature {
id: None,
geometry,
properties,
}
}
fn one_line_layer(name: &str, class: &str, y: i32) -> FeatureLayer {
FeatureLayer {
name: name.to_string(),
extent: 4096,
features: vec![line_feature(class, y)],
}
}
#[test]
fn color_expr_matches_per_feature_class() {
let layer = FeatureLayer {
name: "roads".to_string(),
extent: 4096,
features: vec![line_feature("a", 1024), line_feature("b", 3072)],
};
let json = r##"{
"name": "dd-stroke",
"tile-size": 64,
"sources": { "src": { "type": "mvt", "url": "http://example.invalid/{z}/{x}/{y}" } },
"nodes": {
"feats": { "op": "features", "source": "src", "layer": "roads" },
"out": { "op": "stroke", "features": "@feats", "color": "#000000", "width-px": 6,
"color-expr": ["match", ["get","class"], "a", "#ff0000", "b", "#00ff00", "#000000"] }
},
"output": "@out"
}"##;
let r = render_with_features(
json,
64,
0,
TileId { z: 0, x: 0, y: 0 },
&[("src.roads", layer)],
);
let top = r.pixel(32, 16);
assert!(
top[0] > 150 && top[1] < 80 && top[3] > 150,
"top line should be red: {top:?}"
);
let bottom = r.pixel(32, 48);
assert!(
bottom[1] > 150 && bottom[0] < 80 && bottom[3] > 150,
"bottom line should be green: {bottom:?}"
);
}
#[test]
fn width_expr_drives_stroke_thickness() {
let layer = FeatureLayer {
name: "roads".to_string(),
extent: 4096,
features: vec![line_feature("thin", 1024), line_feature("thick", 3072)],
};
let json = r##"{
"name": "dd-width",
"tile-size": 64,
"sources": { "src": { "type": "mvt", "url": "http://example.invalid/{z}/{x}/{y}" } },
"nodes": {
"feats": { "op": "features", "source": "src", "layer": "roads" },
"out": { "op": "stroke", "features": "@feats", "color": "#ffffff", "width-px": 1,
"width-expr": ["match", ["get","class"], "thin", 2, 12] }
},
"output": "@out"
}"##;
let r = render_with_features(
json,
64,
0,
TileId { z: 0, x: 0, y: 0 },
&[("src.roads", layer)],
);
let count_rows =
|cx: u32, y_lo: u32, y_hi: u32| (y_lo..y_hi).filter(|&y| r.pixel(cx, y)[3] > 100).count();
let thin_rows = count_rows(32, 8, 24); let thick_rows = count_rows(32, 40, 56); assert!(
thick_rows > thin_rows,
"thick line ({thick_rows} rows) should be wider than thin ({thin_rows} rows)"
);
}
#[test]
fn opaque_color_expr_matches_constant_color_pixel_for_pixel() {
let constant_json = r##"{
"name": "const",
"tile-size": 64,
"sources": { "src": { "type": "mvt", "url": "http://example.invalid/{z}/{x}/{y}" } },
"nodes": {
"feats": { "op": "features", "source": "src", "layer": "roads" },
"out": { "op": "stroke", "features": "@feats", "color": "#ff0000", "width-px": 6 }
},
"output": "@out"
}"##;
let expr_json = r##"{
"name": "expr",
"tile-size": 64,
"sources": { "src": { "type": "mvt", "url": "http://example.invalid/{z}/{x}/{y}" } },
"nodes": {
"feats": { "op": "features", "source": "src", "layer": "roads" },
"out": { "op": "stroke", "features": "@feats", "color": "#000000", "width-px": 6,
"color-expr": ["match", ["get","class"], "nope", "#000000", "#ff0000"] }
},
"output": "@out"
}"##;
let tile = TileId { z: 0, x: 0, y: 0 };
let a = render_with_features(
constant_json,
64,
0,
tile,
&[("src.roads", one_line_layer("roads", "x", 2048))],
);
let b = render_with_features(
expr_json,
64,
0,
tile,
&[("src.roads", one_line_layer("roads", "x", 2048))],
);
assert_eq!(a.width, b.width);
assert_eq!(a.height, b.height);
assert_eq!(
a.pixels, b.pixels,
"data-driven opaque color-expr must match constant color pixel-for-pixel"
);
let mid = a.pixel(32, 32);
assert!(mid[0] > 150 && mid[3] > 150, "line should be red: {mid:?}");
}