Start a simple GeoJSON export for LTNs. Cells are missing.

This commit is contained in:
Dustin Carlino 2022-01-13 15:37:47 +00:00
parent 412e0d585d
commit 509217b024
4 changed files with 120 additions and 3 deletions

1
Cargo.lock generated
View File

@ -1248,6 +1248,7 @@ dependencies = [
"downcast-rs", "downcast-rs",
"enumset", "enumset",
"futures-channel", "futures-channel",
"geo",
"geojson", "geojson",
"geom", "geom",
"getrandom", "getrandom",

View File

@ -27,6 +27,7 @@ csv = "1.1.4"
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
enumset = "1.0.3" enumset = "1.0.3"
futures-channel = { version = "0.3.12"} futures-channel = { version = "0.3.12"}
geo = "0.18"
geojson = { version = "0.22.0", features = ["geo-types"] } geojson = { version = "0.22.0", features = ["geo-types"] }
geom = { path = "../geom" } geom = { path = "../geom" }
getrandom = { version = "0.2.3", optional = true } getrandom = { version = "0.2.3", optional = true }

View File

@ -1,8 +1,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::Result;
use abstutil::Timer; use abstutil::Timer;
use geom::Distance; use geom::{Distance, PolyLine, Pt2D};
use map_gui::tools::{CityPicker, DrawRoadLabels, Navigator, URLManager}; use map_gui::tools::{CityPicker, DrawRoadLabels, Navigator, PopupMsg, URLManager};
use widgetry::mapspace::{ToggleZoomed, World, WorldOutcome}; use widgetry::mapspace::{ToggleZoomed, World, WorldOutcome};
use widgetry::{ use widgetry::{
lctrl, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, State, TextExt, lctrl, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, State, TextExt,
@ -42,6 +44,10 @@ impl BrowseNeighborhoods {
.align_right(), .align_right(),
]), ]),
Toggle::checkbox(ctx, "highlight boundary roads", Key::H, true), Toggle::checkbox(ctx, "highlight boundary roads", Key::H, true),
ctx.style()
.btn_outline
.text("Export to GeoJSON")
.build_def(ctx),
])) ]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top) .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
.build(ctx); .build(ctx);
@ -77,6 +83,18 @@ impl State<App> for BrowseNeighborhoods {
"search" => { "search" => {
return Transition::Push(Navigator::new_state(ctx, app)); return Transition::Push(Navigator::new_state(ctx, app));
} }
"Export to GeoJSON" => {
return Transition::Push(match export_geojson(app) {
Ok(path) => PopupMsg::new_state(
ctx,
"LTNs exported",
vec![format!("Data exported to {}", path)],
),
Err(err) => {
PopupMsg::new_state(ctx, "Export failed", vec![err.to_string()])
}
});
}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -165,3 +183,100 @@ fn draw_boundary_roads(ctx: &EventCtx, app: &App) -> ToggleZoomed {
} }
batch.build(ctx) batch.build(ctx)
} }
fn export_geojson(app: &App) -> Result<String> {
if cfg!(target_arch = "wasm32") {
bail!("Export only supported in the installed version");
}
use geo::algorithm::map_coords::MapCoordsInplace;
use geojson::{Feature, FeatureCollection, GeoJson, Geometry, Value};
use std::io::Write;
let map = &app.primary.map;
let mut features = Vec::new();
// All neighborhood boundaries
for (_, (block, color)) in &app.session.partitioning.neighborhoods {
let mut feature = Feature {
bbox: None,
geometry: Some(block.polygon.to_geojson(None)),
id: None,
properties: None,
foreign_members: None,
};
feature.set_property("type", "neighborhood");
feature.set_property("fill", color.as_hex());
features.push(feature);
}
// TODO Cells per neighborhood -- contouring the gridded version is hard!
// All modal filters
for (r, dist) in &app.session.modal_filters.roads {
let road = map.get_r(*r);
if let Ok((pt, angle)) = road.center_pts.dist_along(*dist) {
let road_width = road.get_width();
let pl = PolyLine::must_new(vec![
pt.project_away(0.8 * road_width, angle.rotate_degs(90.0)),
pt.project_away(0.8 * road_width, angle.rotate_degs(-90.0)),
]);
let mut feature = Feature {
bbox: None,
geometry: Some(pl.to_geojson(None)),
id: None,
properties: None,
foreign_members: None,
};
feature.set_property("type", "road filter");
feature.set_property("stroke", "red");
features.push(feature);
}
}
for (_, filter) in &app.session.modal_filters.intersections {
let pl = filter.geometry(map).to_polyline();
let mut feature = Feature {
bbox: None,
geometry: Some(pl.to_geojson(None)),
id: None,
properties: None,
foreign_members: None,
};
feature.set_property("type", "diagonal filter");
feature.set_property("stroke", "red");
features.push(feature);
}
// Transform to WGS84
let gps_bounds = map.get_gps_bounds();
for feature in &mut features {
// geojson to geo
// This could be a Polygon, MultiPolygon, LineString
let mut geom: geo::Geometry<f64> = feature.geometry.take().unwrap().value.try_into()?;
geom.map_coords_inplace(|c| {
let gps = Pt2D::new(c.0, c.1).to_gps(gps_bounds);
(gps.x(), gps.y())
});
// geo to geojson
feature.geometry = Some(Geometry {
bbox: None,
value: Value::from(&geom),
foreign_members: None,
});
}
let gj = GeoJson::FeatureCollection(FeatureCollection {
features,
bbox: None,
foreign_members: None,
});
// Don't use abstio::write_json; it writes to local storage in web, where we want to eventually
// make the browser download something
let path = format!("ltn_{}.geojson", map.get_name().map);
let mut file = std::fs::File::create(&path)?;
write!(file, "{}", serde_json::to_string_pretty(&gj)?)?;
Ok(path)
}

View File

@ -186,7 +186,7 @@ impl DiagonalFilter {
} }
/// Physically where is the filter placed? /// Physically where is the filter placed?
fn geometry(&self, map: &Map) -> Line { pub fn geometry(&self, map: &Map) -> Line {
let r1 = map.get_r(self.r1); let r1 = map.get_r(self.r1);
let r2 = map.get_r(self.r2); let r2 = map.get_r(self.r2);