mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 01:13:53 +03:00
Start a simple GeoJSON export for LTNs. Cells are missing.
This commit is contained in:
parent
412e0d585d
commit
509217b024
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1248,6 +1248,7 @@ dependencies = [
|
|||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"enumset",
|
"enumset",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
"geo",
|
||||||
"geojson",
|
"geojson",
|
||||||
"geom",
|
"geom",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
@ -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 }
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user