Back to Atlas map viewer
SOURCE / PINNED RELEASE

Made of little things.

Atlas map viewer

Release
1d2807e80b44…
Author-recorded commit
43810ed2b368…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-4757dbabc86

Archive hash verified: 8db6876d96c460ac…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

crates/routing/src/contours.rs
//! Bounded raster contours around the reachable network. These polygons are a
//! visualization envelope, not a claim that every enclosed point is traversable.
use crate::Snap;
use serde::Serialize;
use std::collections::BTreeMap;
#[derive(Clone, Copy)]
pub struct Piece {
    pub a: [f64; 2],
    pub b: [f64; 2],
    pub start: f64,
    pub duration: f64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Reachability {
    pub status: &'static str,
    pub start: Snap,
    pub visited: usize,
    pub contours: FeatureCollection,
    pub resolution_meters: f64,
    pub segments: usize,
}
#[derive(Serialize)]
pub struct FeatureCollection {
    #[serde(rename = "type")]
    pub kind: &'static str,
    pub features: Vec<Feature>,
}
#[derive(Serialize)]
pub struct Feature {
    #[serde(rename = "type")]
    pub kind: &'static str,
    pub properties: Properties,
    pub geometry: Geometry,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Properties {
    pub seconds: f64,
    pub minutes: f64,
    pub area_km2: f64,
    pub approximate: bool,
}
#[derive(Serialize)]
pub struct Geometry {
    #[serde(rename = "type")]
    pub kind: &'static str,
    pub coordinates: Vec<Vec<Vec<[f64; 2]>>>,
}
pub fn build(start: Snap, bands: &[f64], pieces: Vec<Piece>, visited: usize) -> Reachability {
    let origin = start.coordinates;
    let scale = [
        111_195.080_233_532_9 * origin[1].to_radians().cos(),
        111_195.080_233_532_9,
    ];
    let project = |p: [f64; 2]| [(p[0] - origin[0]) * scale[0], (p[1] - origin[1]) * scale[1]];
    let max = *bands.last().unwrap();
    let mut bounds = [0_f64; 4];
    let clipped: Vec<_> = pieces
        .iter()
        .filter(|p| p.start <= max)
        .map(|p| {
            let t = if p.duration <= 0. {
                1.
            } else {
                ((max - p.start) / p.duration).clamp(0., 1.)
            };
            let a = project(p.a);
            let full = project(p.b);
            let b = [a[0] + (full[0] - a[0]) * t, a[1] + (full[1] - a[1]) * t];
            for q in [a, b] {
                bounds[0] = bounds[0].min(q[0]);
                bounds[1] = bounds[1].min(q[1]);
                bounds[2] = bounds[2].max(q[0]);
                bounds[3] = bounds[3].max(q[1]);
            }
            (a, b, p.start, p.duration * t)
        })
        .collect();
    let resolution = ((bounds[2] - bounds[0]).max(bounds[3] - bounds[1]) / 500.).max(25.);
    let x0 = bounds[0] - resolution * 2.;
    let y0 = bounds[1] - resolution * 2.;
    let width = ((bounds[2] - bounds[0]) / resolution).ceil() as usize + 5;
    let height = ((bounds[3] - bounds[1]) / resolution).ceil() as usize + 5;
    let mut grid = vec![f64::INFINITY; width * height];
    for (a, b, t, duration) in &clipped {
        let steps = (((b[0] - a[0]).hypot(b[1] - a[1]) / resolution) * 2.)
            .ceil()
            .max(1.) as usize;
        for i in 0..=steps {
            let f = i as f64 / steps as f64;
            let p = [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
            let cost = t + duration * f;
            let x = ((p[0] - x0) / resolution).floor() as isize;
            let y = ((p[1] - y0) / resolution).floor() as isize;
            for dy in -1..=1 {
                for dx in -1..=1 {
                    let (cx, cy) = (x + dx, y + dy);
                    if cx >= 0 && cy >= 0 && (cx as usize) < width && (cy as usize) < height {
                        let at = cy as usize * width + cx as usize;
                        grid[at] = grid[at].min(cost);
                    }
                }
            }
        }
    }
    let unproject = |p: [f64; 2]| {
        [
            origin[0] + (x0 + p[0] * resolution) / scale[0],
            origin[1] + (y0 + p[1] * resolution) / scale[1],
        ]
    };
    let features = bands
        .iter()
        .map(|&seconds| {
            let mask: Vec<_> = grid.iter().map(|v| *v <= seconds).collect();
            let rings = boundary(&mask, width, height);
            let mut polygons: Vec<Vec<Vec<[f64; 2]>>> = Vec::new();
            let mut holes = Vec::new();
            for ring in rings {
                if signed_area(&ring) > 0. {
                    polygons.push(vec![ring]);
                } else {
                    holes.push(ring);
                }
            }
            for hole in holes {
                if let Some(p) = polygons
                    .iter_mut()
                    .filter(|p| contains(hole[0], &p[0]))
                    .min_by(|a, b| signed_area(&a[0]).total_cmp(&signed_area(&b[0])))
                {
                    p.push(hole);
                }
            }
            for polygon in &mut polygons {
                for ring in polygon {
                    for p in ring {
                        *p = unproject(*p);
                    }
                }
            }
            Feature {
                kind: "Feature",
                properties: Properties {
                    seconds,
                    minutes: seconds / 60.,
                    area_km2: mask.iter().filter(|v| **v).count() as f64 * resolution * resolution
                        / 1e6,
                    approximate: true,
                },
                geometry: Geometry {
                    kind: "MultiPolygon",
                    coordinates: polygons,
                },
            }
        })
        .collect();
    Reachability {
        status: "reachable",
        start,
        visited,
        contours: FeatureCollection {
            kind: "FeatureCollection",
            features,
        },
        resolution_meters: resolution,
        segments: clipped.len(),
    }
}
fn signed_area(ring: &[[f64; 2]]) -> f64 {
    ring.windows(2)
        .map(|v| v[0][0] * v[1][1] - v[1][0] * v[0][1])
        .sum::<f64>()
        / 2.
}
fn contains(p: [f64; 2], ring: &[[f64; 2]]) -> bool {
    let mut inside = false;
    for v in ring.windows(2) {
        let (a, b) = (v[0], v[1]);
        if (a[1] > p[1]) != (b[1] > p[1])
            && p[0] < (b[0] - a[0]) * (p[1] - a[1]) / (b[1] - a[1]) + a[0]
        {
            inside = !inside;
        }
    }
    inside
}
fn boundary(mask: &[bool], w: usize, h: usize) -> Vec<Vec<[f64; 2]>> {
    let mut edges = BTreeMap::<usize, Vec<usize>>::new();
    let stride = w + 1;
    let mut add = |a, b| {
        edges.entry(a).or_default().push(b);
    };
    for y in 0..h {
        for x in 0..w {
            if !mask[y * w + x] {
                continue;
            }
            let v = y * stride + x;
            if y == 0 || !mask[(y - 1) * w + x] {
                add(v, v + 1);
            }
            if x + 1 == w || !mask[y * w + x + 1] {
                add(v + 1, v + 1 + stride);
            }
            if y + 1 == h || !mask[(y + 1) * w + x] {
                add(v + 1 + stride, v + stride);
            }
            if x == 0 || !mask[y * w + x - 1] {
                add(v + stride, v);
            }
        }
    }
    let point = |v: usize| [(v % stride) as f64, (v / stride) as f64];
    let mut rings = Vec::new();
    while let Some((&start, _)) = edges.first_key_value() {
        let mut at = start;
        let mut ring = vec![point(start)];
        let mut direction = [1., 0.];
        loop {
            let Some(nexts) = edges.get_mut(&at) else {
                break;
            };
            let p = point(at);
            let choice = nexts
                .iter()
                .enumerate()
                .max_by_key(|(_, v)| {
                    let q = point(**v);
                    let d = [q[0] - p[0], q[1] - p[1]];
                    let cross = direction[0] * d[1] - direction[1] * d[0];
                    let dot = direction[0] * d[0] + direction[1] * d[1];
                    if cross > 0. {
                        3
                    } else if dot > 0. {
                        2
                    } else if cross < 0. {
                        1
                    } else {
                        0
                    }
                })
                .unwrap()
                .0;
            let next = nexts.remove(choice);
            if nexts.is_empty() {
                edges.remove(&at);
            }
            let q = point(next);
            direction = [q[0] - p[0], q[1] - p[1]];
            if ring.len() >= 2 {
                let prev = ring[ring.len() - 2];
                if (p[0] - prev[0]) * direction[1] - (p[1] - prev[1]) * direction[0] == 0. {
                    ring.pop();
                }
            }
            ring.push(q);
            at = next;
            if at == start {
                break;
            }
        }
        if ring.len() >= 4 && ring.first() == ring.last() {
            rings.push(ring);
        }
    }
    rings
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn contours_keep_holes_and_disconnected_components() {
        let mut m = vec![true; 25];
        m[12] = false;
        let rings = boundary(&m, 5, 5);
        assert_eq!(rings.len(), 2);
        assert_eq!(rings.iter().map(|r| signed_area(r)).sum::<f64>(), 24.);
        let r = boundary(&[true, false, false, true], 2, 2);
        assert_eq!(r.len(), 2);
        assert!(r.iter().all(|p| signed_area(p) == 1.));
    }
    #[test]
    fn bands_are_nested_and_clip_partial_edges() {
        let a = [16., 48.];
        let b = [16.01, 48.];
        let r = build(
            Snap {
                coordinates: a,
                distance_meters: 0.,
                way_id: "way/1".into(),
                segment: 0,
                t: 0.,
            },
            &[60., 120.],
            vec![Piece {
                a,
                b,
                start: 0.,
                duration: 300.,
            }],
            2,
        );
        assert_eq!(r.contours.features.len(), 2);
        assert!(
            r.contours.features[1].properties.area_km2 > r.contours.features[0].properties.area_km2
        );
        let max = r.contours.features[1]
            .geometry
            .coordinates
            .iter()
            .flatten()
            .flatten()
            .map(|p| p[0])
            .fold(0., f64::max);
        assert!(max < 16.007);
        assert!(crate::distance(a, b) > 500.);
    }
}