Bring in Seattle parcel data to experiment with filling the negative

space on maps with "private area around houses".

And a few tweaks to the KML viewer to make it more useful:

- optionally save the clipped file
- click an object to view all attributes in a scrollable popup
This commit is contained in:
Dustin Carlino 2020-11-20 10:30:13 -08:00
parent 4690ea36cd
commit 308eb90956
3 changed files with 123 additions and 66 deletions

View File

@ -352,6 +352,14 @@
"checksum": "d4a8e733045b28c0385fb81359d6df03",
"size_bytes": 3047098014
},
"data/input/seattle/zoning_parcels.bin": {
"checksum": "3e6272a58de99bc631919962eaf93e62",
"size_bytes": 411106182
},
"data/input/seattle/zoning_parcels.kml": {
"checksum": "c9fc2eb5ab8d1481b45f90a0297dac8e",
"size_bytes": 777469024
},
"data/input/tel_aviv/footways.bin": {
"checksum": "4fb70e32d0cc3ddeaa8924c240bc9d5b",
"size_bytes": 544579

View File

@ -4,9 +4,9 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use aabb_quadtree::QuadTree;
use abstutil::{prettyprint_usize, Parallelism};
use abstutil::{prettyprint_usize, Parallelism, Timer};
use geom::{Circle, Distance, PolyLine, Polygon, Pt2D, Ring};
use kml::ExtraShapes;
use kml::{ExtraShape, ExtraShapes};
use map_model::BuildingID;
use widgetry::{
lctrl, Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
@ -15,7 +15,7 @@ use widgetry::{
use crate::app::App;
use crate::colors::ColorScheme;
use crate::game::{ChooseSomething, Transition};
use crate::game::{ChooseSomething, PopupMsg, Transition};
pub struct ViewKML {
panel: Panel,
@ -41,69 +41,9 @@ const THICKNESS: Distance = Distance::const_meters(2.0);
impl ViewKML {
pub fn new(ctx: &mut EventCtx, app: &App, path: Option<String>) -> Box<dyn State<App>> {
ctx.loading_screen("load kml", |ctx, mut timer| {
let raw_shapes = if let Some(ref path) = path {
if path.ends_with(".kml") {
let shapes =
kml::load(&path, &app.primary.map.get_gps_bounds(), true, &mut timer)
.unwrap();
// Assuming this is some huge file, conveniently convert the extract to .bin.
// The new file will show up as untracked in git, so it'll be obvious this
// happened.
abstutil::write_binary(path.replace(".kml", ".bin"), &shapes);
shapes
} else if path.ends_with(".csv") {
let shapes =
ExtraShapes::load_csv(&path, &app.primary.map.get_gps_bounds(), &mut timer)
.unwrap();
// Assuming this is some huge file, conveniently convert the extract to .bin.
// The new file will show up as untracked in git, so it'll be obvious this
// happened.
abstutil::write_binary(path.replace(".csv", ".bin"), &shapes);
shapes
} else {
abstutil::read_binary::<ExtraShapes>(path.to_string(), &mut timer)
}
} else {
ExtraShapes { shapes: Vec::new() }
};
let bounds = app.primary.map.get_gps_bounds();
let boundary = app.primary.map.get_boundary_polygon();
let dataset_name = path
.as_ref()
.map(abstutil::basename)
.unwrap_or("no file".to_string());
let bldg_lookup: HashMap<String, BuildingID> = app
.primary
.map
.all_buildings()
.iter()
.map(|b| (b.orig_id.inner().to_string(), b.id))
.collect();
let cs = &app.cs;
let objects: Vec<Object> = timer
.parallelize(
"convert shapes",
Parallelism::Fastest,
raw_shapes.shapes.into_iter().enumerate().collect(),
|(idx, shape)| {
let pts = bounds.convert(&shape.points);
if pts.iter().any(|pt| boundary.contains_pt(*pt)) {
Some(make_object(
cs,
&bldg_lookup,
shape.attributes,
pts,
&dataset_name,
idx,
))
} else {
None
}
},
)
.into_iter()
.flatten()
.collect();
// Enable to write a smaller .bin only with the shapes matching the bounds.
let dump_clipped_shapes = false;
let (dataset_name, objects) = load_objects(app, path, dump_clipped_shapes, &mut timer);
let mut batch = GeomBatch::new();
let mut quadtree = QuadTree::default(app.primary.map.get_bounds().as_bbox());
@ -178,6 +118,20 @@ impl State<App> for ViewKML {
}
}
}
if let Some(idx) = self.selected {
if ctx.normal_left_click() {
self.selected = None;
return Transition::Push(PopupMsg::new(
ctx,
"Parcel",
self.objects[idx]
.attribs
.iter()
.map(|(k, v)| format!("{} = {}", k, v))
.collect(),
));
}
}
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -252,6 +206,92 @@ impl State<App> for ViewKML {
}
}
/// Loads and clips objects to the current map. Also returns the dataset name.
fn load_objects(
app: &App,
path: Option<String>,
dump_clipped_shapes: bool,
timer: &mut Timer,
) -> (String, Vec<Object>) {
let map = &app.primary.map;
let bounds = map.get_gps_bounds();
let raw_shapes = if let Some(ref path) = path {
if path.ends_with(".kml") {
let shapes = kml::load(&path, bounds, true, timer).unwrap();
// Assuming this is some huge file, conveniently convert the extract to .bin.
// The new file will show up as untracked in git, so it'll be obvious this
// happened.
abstutil::write_binary(path.replace(".kml", ".bin"), &shapes);
shapes
} else if path.ends_with(".csv") {
let shapes = ExtraShapes::load_csv(&path, bounds, timer).unwrap();
// Assuming this is some huge file, conveniently convert the extract to .bin.
// The new file will show up as untracked in git, so it'll be obvious this
// happened.
abstutil::write_binary(path.replace(".csv", ".bin"), &shapes);
shapes
} else {
abstutil::read_binary::<ExtraShapes>(path.to_string(), timer)
}
} else {
ExtraShapes { shapes: Vec::new() }
};
let boundary = map.get_boundary_polygon();
let dataset_name = path
.as_ref()
.map(abstutil::basename)
.unwrap_or("no file".to_string());
let bldg_lookup: HashMap<String, BuildingID> = map
.all_buildings()
.iter()
.map(|b| (b.orig_id.inner().to_string(), b.id))
.collect();
let cs = &app.cs;
let pairs: Vec<(Object, ExtraShape)> = timer
.parallelize(
"convert shapes",
Parallelism::Fastest,
raw_shapes.shapes.into_iter().enumerate().collect(),
|(idx, shape)| {
let pts = bounds.convert(&shape.points);
if pts.iter().any(|pt| boundary.contains_pt(*pt)) {
Some((
make_object(
cs,
&bldg_lookup,
shape.attributes.clone(),
pts,
&dataset_name,
idx,
),
shape,
))
} else {
None
}
},
)
.into_iter()
.flatten()
.collect();
let mut objects = Vec::new();
let mut clipped_shapes = Vec::new();
for (obj, shape) in pairs {
objects.push(obj);
clipped_shapes.push(shape);
}
if path.is_some() && dump_clipped_shapes {
abstutil::write_binary(
format!("{}_clipped_for_{}.bin", dataset_name, map.get_name().map),
&clipped_shapes,
);
}
(dataset_name, objects)
}
fn make_object(
cs: &ColorScheme,
bldg_lookup: &HashMap<String, BuildingID>,

View File

@ -71,6 +71,15 @@ fn input(config: &ImporterConfiguration, timer: &mut abstutil::Timer) {
"https://data-seattlecitygis.opendata.arcgis.com/datasets/5b5c745e0f1f48e7a53acec63a0022ab_0");
abstutil::write_binary("data/input/seattle/collisions.bin".to_string(), &collisions);
}
// From https://data-seattlecitygis.opendata.arcgis.com/datasets/parcels-1
download_kml(
"input/seattle/zoning_parcels.bin",
"https://opendata.arcgis.com/datasets/42863f1debdc47488a1c2b9edd38053e_2.kml",
&bounds,
true,
timer,
);
}
pub fn osm_to_raw(name: &str, timer: &mut abstutil::Timer, config: &ImporterConfiguration) {