1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::collections::BTreeMap;
use std::error::Error;

use serde::{Deserialize, Serialize};

use abstutil::{prettyprint_usize, Timer};
use geom::{GPSBounds, LonLat};

#[derive(Serialize, Deserialize)]
pub struct ExtraShapes {
    pub shapes: Vec<ExtraShape>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtraShape {
    pub points: Vec<LonLat>,
    pub attributes: BTreeMap<String, String>,
}

pub fn load(
    path: &str,
    gps_bounds: &GPSBounds,
    require_all_pts_in_bounds: bool,
    timer: &mut Timer,
) -> Result<ExtraShapes, Box<dyn Error>> {
    timer.start(format!("read {}", path));
    let bytes = abstutil::slurp_file(path)?;
    let raw_string = std::str::from_utf8(&bytes)?;
    let tree = roxmltree::Document::parse(raw_string)?;
    timer.stop(format!("read {}", path));

    let mut shapes = Vec::new();
    let mut skipped_count = 0;
    let mut kv = BTreeMap::new();

    timer.start("scrape objects");
    recurse(
        tree.root(),
        &mut shapes,
        &mut skipped_count,
        &mut kv,
        gps_bounds,
        require_all_pts_in_bounds,
    )?;
    timer.stop("scrape objects");

    timer.note(format!(
        "Got {} shapes from {} and skipped {} shapes",
        prettyprint_usize(shapes.len()),
        path,
        prettyprint_usize(skipped_count)
    ));

    Ok(ExtraShapes { shapes })
}

fn recurse(
    node: roxmltree::Node,
    shapes: &mut Vec<ExtraShape>,
    skipped_count: &mut usize,
    kv: &mut BTreeMap<String, String>,
    gps_bounds: &GPSBounds,
    require_all_pts_in_bounds: bool,
) -> Result<(), Box<dyn Error>> {
    for child in node.children() {
        recurse(
            child,
            shapes,
            skipped_count,
            kv,
            gps_bounds,
            require_all_pts_in_bounds,
        )?;
    }
    if node.tag_name().name() == "SimpleData" {
        let key = node.attribute("name").unwrap().to_string();
        let value = node
            .text()
            .map(|x| x.to_string())
            .unwrap_or_else(String::new);
        kv.insert(key, value);
    } else if node.tag_name().name() == "coordinates" {
        let mut any_oob = false;
        let mut any_ok = false;
        let mut pts: Vec<LonLat> = Vec::new();
        if let Some(txt) = node.text() {
            for pair in txt.split(' ') {
                if let Some(pt) = parse_pt(pair) {
                    pts.push(pt);
                    if gps_bounds.contains(pt) {
                        any_ok = true;
                    } else {
                        any_oob = true;
                    }
                } else {
                    return Err(format!("Malformed coordinates: {}", pair).into());
                }
            }
        }
        if any_ok && (!any_oob || !require_all_pts_in_bounds) {
            let attributes = std::mem::replace(kv, BTreeMap::new());
            shapes.push(ExtraShape {
                points: pts,
                attributes,
            });
        } else {
            *skipped_count += 1;
        }
    }
    Ok(())
}

fn parse_pt(input: &str) -> Option<LonLat> {
    let coords: Vec<&str> = input.split(',').collect();
    if coords.len() != 2 {
        return None;
    }
    match (coords[0].parse::<f64>(), coords[1].parse::<f64>()) {
        (Ok(lon), Ok(lat)) => Some(LonLat::new(lon, lat)),
        _ => None,
    }
}