Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

osm-katana/src/geometry.rs

#![allow(clippy::float_arithmetic, clippy::arithmetic_side_effects)]

/// Tags whose mere PRESENCE implies a closed way is a filled area.
///
/// **`"area"` is deliberately not in this list.** It used to be, and that made the
/// explicit `area == "yes"` test below dead code — `area` matched here whatever its
/// value, so `area=no`, OSM's one way of saying *this closed way is a line, not a
/// filled shape*, produced a Polygon. See [`area_verdict`].
const AREA_TAGS: &[&str] = &["building", "landuse", "natural", "amenity", "leisure"];

/// What an explicit `area=…` value says, if anything.
///
/// `area` is a yes/no key, so a value is one of three things and not two: an
/// affirmation, a denial, or noise. MEASURED on korp's 2026-08-03 `sweden` cut, over
/// 9 204 689 ways: 16 176 `area=yes`, 505 `area=no`, and 14 values that are neither
/// (`highway`, `yess`, `0.4`, `1 070`, …). Noise must not be read as either verdict —
/// it has to fall through to [`AREA_TAGS`], because a way tagged
/// `building=yes` + `area=vehicletesting` is still a building.
#[inline]
fn area_verdict(v: &str) -> Option<bool> {
    match v {
        "yes" | "true" | "1" => Some(true),
        "no" | "false" | "0" => Some(false),
        _ => None,
    }
}

/// Encode a WKB Point on the stack — exactly 21 bytes, no heap allocation.
/// This is called 9.5 billion times for planet; the stack array avoids
/// that many malloc/free cycles.
#[inline]
pub fn encode_point_inline(lon: f64, lat: f64) -> [u8; 21] {
    let mut buf = [0u8; 21];
    buf[0] = 1; // little-endian
    buf[1..5].copy_from_slice(&1u32.to_le_bytes()); // WKB Point type
    buf[5..13].copy_from_slice(&lon.to_le_bytes());
    buf[13..21].copy_from_slice(&lat.to_le_bytes());
    buf
}

/// Is this closed way an area (polygon) rather than a ring-shaped line?
///
/// Takes the tags as the **borrowed pairs both call sites already hold** —
/// exactly the shape [`crate::tags::serialize_tags`] takes, so a way's tags are
/// read twice and copied zero times. This used to take a
/// `HashMap<String, String>`, which meant every caller built a throwaway map,
/// with two `to_string()` per tag, to answer three lookups: 8 594 851 maps per
/// Sweden convert, inside the gatling workers where memory bandwidth is the
/// shared resource (stinky `katana-5`).
///
/// Generic over the pair types so the PBF path can pass string-table slices
/// (`&str`) and the XML path its already-owned `String`s, without either side
/// converting.
///
/// Duplicate-key semantics are the `HashMap`'s: **last wins**. That only shows
/// up on a way tagged `area` twice with different values, which OSM does not
/// permit, but matching it exactly keeps the change byte-for-byte invisible in
/// the output.
///
/// ## `area=no` is honoured, and it OVERRIDES
///
/// An explicit `area` verdict wins over every implied one: `area=no` on a closed
/// `building=yes` way means the mapper is saying that ring is a line. That is the whole
/// point of the key — it exists only to contradict what the other tags imply, so a
/// version that lets `building` outvote it has no effect at all.
///
/// This decides the WKB the encoder writes — `true` ⇒ Polygon, `false` ⇒ LineString —
/// so it moves geometry in every published pack. MEASURED on korp's 2026-08-03 `sweden`
/// cut: **279 of 9 204 689 ways (0,0030 %)** are closed and tagged `area=no` and were
/// therefore shipped as Polygons; they now ship as LineStrings. `area=yes` is unchanged
/// (16 176 ways), and so is every way with no `area` key at all — 9 187 994 of them.
pub fn is_closed_area<K: AsRef<str>, V: AsRef<str>>(refs: &[i64], tags: &[(K, V)]) -> bool {
    if refs.len() < 4 {
        return false;
    }
    if refs.first() != refs.last() {
        return false;
    }
    // An explicit, INTELLIGIBLE `area` value is the answer — in both directions.
    // An unintelligible one (`area=0.4`) says nothing and falls through.
    if let Some(verdict) = tags
        .iter()
        .rev()
        .find(|(k, _)| k.as_ref() == "area")
        .and_then(|(_, v)| area_verdict(v.as_ref()))
    {
        return verdict;
    }
    tags.iter().any(|(k, _)| AREA_TAGS.contains(&k.as_ref()))
}

#[cfg(test)]
mod tests {
    use super::is_closed_area;

    const CLOSED: [i64; 4] = [1, 2, 3, 1];

    #[test]
    fn open_or_short_ways_are_never_areas() {
        assert!(!is_closed_area(&[1, 2, 3, 4], &[("building", "yes")]));
        assert!(!is_closed_area(&[1, 2, 1], &[("building", "yes")]));
    }

    #[test]
    fn an_area_tag_makes_a_closed_way_an_area() {
        assert!(is_closed_area(&CLOSED, &[("area", "yes")]));
        assert!(is_closed_area(&CLOSED, &[("landuse", "forest")]));
        assert!(!is_closed_area(&CLOSED, &[("highway", "residential")]));
        assert!(!is_closed_area(&CLOSED, &[] as &[(&str, &str)]));
    }

    /// **`area=no` means NOT an area — stinky `katana-10`, fixed.**
    ///
    /// This test used to assert the opposite and called itself
    /// `area_no_is_still_treated_as_an_area_quirk_preserved`: `"area"` was itself
    /// in `AREA_TAGS`, so the key matched whatever its value and the explicit
    /// `area == "yes"` branch was unreachable. A closed way tagged `area=no` was
    /// shipped as a Polygon.
    ///
    /// MEASURED on korp's 2026-08-03 `sweden` cut: 279 of 9 204 689 ways.
    ///
    /// Every assertion here is one the OLD implementation fails, and the last two
    /// are the ones that matter: the verdict must OVERRIDE the implied tags, in
    /// both directions, or the key does nothing.
    #[test]
    fn area_no_means_not_an_area() {
        assert!(!is_closed_area(&CLOSED, &[("area", "no")]));
        // Last wins, as it always did — but now the value is read.
        assert!(!is_closed_area(&CLOSED, &[("area", "yes"), ("area", "no")]));
        assert!(is_closed_area(&CLOSED, &[("area", "no"), ("area", "yes")]));
        // The override, which is the entire purpose of the key: an implied area
        // tag does not outvote an explicit denial…
        assert!(
            !is_closed_area(&CLOSED, &[("building", "yes"), ("area", "no")]),
            "area=no must override an implied area tag — otherwise the key is inert"
        );
        assert!(!is_closed_area(
            &CLOSED,
            &[("landuse", "forest"), ("area", "false")]
        ));
        // …and an explicit affirmation still stands on its own.
        assert!(is_closed_area(
            &CLOSED,
            &[("highway", "pedestrian"), ("area", "yes")]
        ));
    }

    /// A value that is neither yes nor no carries no verdict, so the other tags
    /// decide. MEASURED on the same cut: 14 ways carry such a value (`highway`,
    /// `yess`, `vehicletesting`, `0.4`, `1 070`, …). Reading them as "no" would
    /// silently unfill real buildings; reading them as "yes" is the old bug.
    #[test]
    fn an_unintelligible_area_value_decides_nothing() {
        assert!(is_closed_area(
            &CLOSED,
            &[("building", "yes"), ("area", "vehicletesting")]
        ));
        assert!(!is_closed_area(
            &CLOSED,
            &[("highway", "residential"), ("area", "0.4")]
        ));
        // `area` alone, with junk, is no longer an area tag by mere presence.
        assert!(!is_closed_area(&CLOSED, &[("area", "yess")]));
    }

    /// **The APPLIED output, not the predicate.** `is_closed_area` only matters
    /// because it picks a WKB type byte, so this drives the real PBF parser and
    /// the real encoder ([`crate::shared::gpu_backend::CpuBackend::encode_wkb`])
    /// over a real synthetic PBF and reads the type byte off the produced bytes.
    ///
    /// WKB type 2 = LineString, 3 = Polygon. Three closed ways over the same four
    /// nodes, differing only in tags: `building=yes` (Polygon), the same plus
    /// `area=no` (LineString), and `area=yes` on a highway (Polygon).
    #[test]
    fn the_encoder_writes_a_linestring_for_area_no() {
        use crate::pbf_enc;
        use crate::shared::gpu_backend::{CpuBackend, GpuBackend as _, RawWay};

        /// WKB geometry type of a little-endian body: bytes 1..5.
        fn wkb_type(g: &[u8]) -> u32 {
            u32::from_le_bytes(g[1..5].try_into().expect("wkb shorter than its header"))
        }

        let mut st = pbf_enc::StringTable::new();
        let sid = |st: &mut pbf_enc::StringTable, s: &str| st.intern(s.as_bytes());
        let (k_building, v_yes) = (sid(&mut st, "building"), sid(&mut st, "yes"));
        let (k_area, v_no) = (sid(&mut st, "area"), sid(&mut st, "no"));
        let (k_highway, v_ped) = (sid(&mut st, "highway"), sid(&mut st, "pedestrian"));
        let v_yes2 = sid(&mut st, "yes");

        let mut dn = pbf_enc::DenseNodesBuilder::new();
        for id in 1..=4i64 {
            dn.push(id, 100_000 * id as i32, 200_000 * id as i32, &[]);
        }
        let ring = [1i64, 2, 3, 1];
        let ways = vec![
            pbf_enc::encode_way(10, &[(k_building, v_yes)], &ring),
            pbf_enc::encode_way(11, &[(k_building, v_yes), (k_area, v_no)], &ring),
            pbf_enc::encode_way(12, &[(k_highway, v_ped), (k_area, v_yes2)], &ring),
        ];
        let block = pbf_enc::encode_primitive_block(&st, Some(&dn), &ways, &[]);
        let data = pbf_enc::fixtures::pbf_file(&[block]);

        let positions = crate::pbf_io::scan_blob_positions_par(&data);
        let store = crate::reader::read_pass1_mmap(&data, &positions).unwrap();
        let (par_ways, _rels, _loss) =
            crate::reader::read_pass2_mmap(&data, &positions, &store, &CpuBackend).unwrap();

        let mut by_id: Vec<(i64, u32)> = par_ways
            .iter()
            .map(|w| {
                let g = w.geometry.as_ref().unwrap_or_else(|| {
                    panic!(
                        "way {} produced no geometry — the fixture never reached the encoder",
                        w.id
                    )
                });
                (w.id, wkb_type(g))
            })
            .collect();
        by_id.sort_unstable();
        assert_eq!(
            by_id,
            vec![(10, 3u32), (11, 2u32), (12, 3u32)],
            "building=yes ⇒ Polygon(3); the SAME way plus area=no ⇒ LineString(2); \
             highway=pedestrian + area=yes ⇒ Polygon(3)"
        );

        // The instrument is alive: the encoder really can produce both bytes off
        // this same ring, so a single-value result above would be a dead test.
        let coords: Vec<(f64, f64)> = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)];
        let both = CpuBackend.encode_wkb(&[
            RawWay {
                coords: coords.clone(),
                is_area: true,
            },
            RawWay {
                coords,
                is_area: false,
            },
        ]);
        assert_eq!(
            both.iter()
                .map(|g| wkb_type(g.as_ref().unwrap()))
                .collect::<Vec<_>>(),
            vec![3u32, 2u32]
        );
    }

    /// Owned and borrowed tag lists must give the same answer — the two call
    /// sites hold different types and there is only one implementation.
    #[test]
    fn owned_and_borrowed_tags_agree() {
        let owned: Vec<(String, String)> = vec![(String::from("natural"), String::from("water"))];
        let borrowed: Vec<(&str, &str)> = vec![("natural", "water")];
        assert_eq!(
            is_closed_area(&CLOSED, &owned),
            is_closed_area(&CLOSED, &borrowed)
        );
        assert!(is_closed_area(&CLOSED, &owned));
    }
}