mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
Improve one-shot map importing UI (partly motivated by #760):
- stop overwriting the one zz/overpass map by naming them differently - allow a user-specified name too - move the buttons to search all maps and import a new place to the top of the ever-growing country list
This commit is contained in:
parent
f17f4c2731
commit
87dd029513
@ -146,8 +146,12 @@ enum Command {
|
|||||||
/// Imports a one-shot A/B Street map in a single command.
|
/// Imports a one-shot A/B Street map in a single command.
|
||||||
OneStepImport {
|
OneStepImport {
|
||||||
/// The path to a GeoJSON file with a boundary
|
/// The path to a GeoJSON file with a boundary
|
||||||
#[structopt()]
|
#[structopt(long)]
|
||||||
geojson_path: String,
|
geojson_path: String,
|
||||||
|
/// What to name the new imported map. The country will always be "zz" (a fake country
|
||||||
|
/// code), with the city as "oneshot." This name shouldn't contain spaces or be empty.
|
||||||
|
#[structopt(long)]
|
||||||
|
map_name: String,
|
||||||
/// Do people drive on the left side of the road in this map?
|
/// Do people drive on the left side of the road in this map?
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
drive_on_left: bool,
|
drive_on_left: bool,
|
||||||
@ -206,9 +210,10 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Command::OneStepImport {
|
Command::OneStepImport {
|
||||||
geojson_path,
|
geojson_path,
|
||||||
|
map_name,
|
||||||
drive_on_left,
|
drive_on_left,
|
||||||
use_geofabrik,
|
use_geofabrik,
|
||||||
} => one_step_import::run(geojson_path, drive_on_left, use_geofabrik).await?,
|
} => one_step_import::run(geojson_path, map_name, drive_on_left, use_geofabrik).await?,
|
||||||
Command::Import { raw_args } => importer::run(raw_args).await,
|
Command::Import { raw_args } => importer::run(raw_args).await,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -5,7 +5,19 @@ use anyhow::Result;
|
|||||||
use abstio::CityName;
|
use abstio::CityName;
|
||||||
use geom::LonLat;
|
use geom::LonLat;
|
||||||
|
|
||||||
pub async fn run(geojson_path: String, drive_on_left: bool, use_geofabrik: bool) -> Result<()> {
|
pub async fn run(
|
||||||
|
geojson_path: String,
|
||||||
|
name: String,
|
||||||
|
drive_on_left: bool,
|
||||||
|
use_geofabrik: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if name.contains(" ") || name.is_empty() {
|
||||||
|
panic!(
|
||||||
|
"--map_name must be non-empty and contain no spaces: {}",
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to a boundary polygon.
|
// Convert to a boundary polygon.
|
||||||
{
|
{
|
||||||
println!("Converting GeoJSON to Osmosis boundary");
|
println!("Converting GeoJSON to Osmosis boundary");
|
||||||
@ -22,11 +34,9 @@ pub async fn run(geojson_path: String, drive_on_left: bool, use_geofabrik: bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
let city = CityName::new("zz", "oneshot");
|
let city = CityName::new("zz", "oneshot");
|
||||||
let name;
|
|
||||||
let osm;
|
let osm;
|
||||||
if !use_geofabrik {
|
if !use_geofabrik {
|
||||||
// No easy guess on this without looking at the XML file
|
println!("Downloading OSM data from Overpass...");
|
||||||
name = "overpass".to_string();
|
|
||||||
osm = city.input_path(format!("osm/{}.osm", name));
|
osm = city.input_path(format!("osm/{}.osm", name));
|
||||||
|
|
||||||
let geojson = abstio::slurp_file(geojson_path)?;
|
let geojson = abstio::slurp_file(geojson_path)?;
|
||||||
@ -48,11 +58,6 @@ pub async fn run(geojson_path: String, drive_on_left: bool, use_geofabrik: bool)
|
|||||||
println!("Figuring out what Geofabrik file contains your boundary");
|
println!("Figuring out what Geofabrik file contains your boundary");
|
||||||
let url = crate::pick_geofabrik::run("boundary0.poly".to_string()).await?;
|
let url = crate::pick_geofabrik::run("boundary0.poly".to_string()).await?;
|
||||||
|
|
||||||
// Name the temporary map based on the Geofabrik region.
|
|
||||||
name = abstutil::basename(&url)
|
|
||||||
.strip_suffix("-latest.osm")
|
|
||||||
.unwrap()
|
|
||||||
.to_string();
|
|
||||||
let pbf = city.input_path(format!("osm/{}.pbf", abstutil::basename(&url)));
|
let pbf = city.input_path(format!("osm/{}.pbf", abstutil::basename(&url)));
|
||||||
osm = city.input_path(format!("osm/{}.osm", name));
|
osm = city.input_path(format!("osm/{}.osm", name));
|
||||||
std::fs::create_dir_all(std::path::Path::new(&osm).parent().unwrap())
|
std::fs::create_dir_all(std::path::Path::new(&osm).parent().unwrap())
|
||||||
@ -87,8 +92,5 @@ pub async fn run(geojson_path: String, drive_on_left: bool, use_geofabrik: bool)
|
|||||||
// debugging.
|
// debugging.
|
||||||
abstio::delete_file("boundary0.poly");
|
abstio::delete_file("boundary0.poly");
|
||||||
|
|
||||||
// For the sake of the UI, print the name of the new map as the last line of output.
|
|
||||||
println!("{}", name);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ use abstio::{CityName, Manifest, MapName};
|
|||||||
use geom::{Distance, Percent};
|
use geom::{Distance, Percent};
|
||||||
use map_model::City;
|
use map_model::City;
|
||||||
use widgetry::{
|
use widgetry::{
|
||||||
Autocomplete, ClickOutcome, ControlState, DrawBaselayer, DrawWithTooltips, EventCtx, GeomBatch,
|
lctrl, Autocomplete, ClickOutcome, ControlState, DrawBaselayer, DrawWithTooltips, EventCtx,
|
||||||
GfxCtx, Image, Key, Line, Outcome, Panel, RewriteColor, State, Text, TextExt, Transition,
|
GeomBatch, GfxCtx, Image, Key, Line, Outcome, Panel, RewriteColor, State, Text, TextExt,
|
||||||
Widget,
|
Transition, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::load::{FileLoader, MapLoader};
|
use crate::load::{FileLoader, MapLoader};
|
||||||
@ -135,13 +135,6 @@ impl<A: AppLike + 'static> CityPicker<A> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other_places.push(
|
|
||||||
ctx.style()
|
|
||||||
.btn_outline
|
|
||||||
.text("Search all maps")
|
|
||||||
.hotkey(Key::Tab)
|
|
||||||
.build_def(ctx),
|
|
||||||
);
|
|
||||||
|
|
||||||
Transition::Replace(Box::new(CityPicker {
|
Transition::Replace(Box::new(CityPicker {
|
||||||
on_load: Some(on_load),
|
on_load: Some(on_load),
|
||||||
@ -150,12 +143,6 @@ impl<A: AppLike + 'static> CityPicker<A> {
|
|||||||
Line("Select a district").small_heading().into_widget(ctx),
|
Line("Select a district").small_heading().into_widget(ctx),
|
||||||
ctx.style().btn_close_widget(ctx),
|
ctx.style().btn_close_widget(ctx),
|
||||||
]),
|
]),
|
||||||
Widget::row(vec![
|
|
||||||
Widget::col(other_places).centered_vert(),
|
|
||||||
district_picker,
|
|
||||||
Widget::col(this_city).centered_vert(),
|
|
||||||
]),
|
|
||||||
"Don't see the place you want?".text_widget(ctx),
|
|
||||||
if cfg!(target_arch = "wasm32") {
|
if cfg!(target_arch = "wasm32") {
|
||||||
// On web, this is a link, so it's styled appropriately.
|
// On web, this is a link, so it's styled appropriately.
|
||||||
ctx.style()
|
ctx.style()
|
||||||
@ -171,6 +158,16 @@ impl<A: AppLike + 'static> CityPicker<A> {
|
|||||||
.text("Import a new city into A/B Street")
|
.text("Import a new city into A/B Street")
|
||||||
.build_widget(ctx, "import new city")
|
.build_widget(ctx, "import new city")
|
||||||
},
|
},
|
||||||
|
ctx.style()
|
||||||
|
.btn_outline
|
||||||
|
.icon_text("system/assets/tools/search.svg", "Search all maps")
|
||||||
|
.hotkey(lctrl(Key::F))
|
||||||
|
.build_def(ctx),
|
||||||
|
Widget::row(vec![
|
||||||
|
Widget::col(other_places).centered_vert(),
|
||||||
|
district_picker,
|
||||||
|
Widget::col(this_city).centered_vert(),
|
||||||
|
]),
|
||||||
]))
|
]))
|
||||||
.build(ctx),
|
.build(ctx),
|
||||||
}))
|
}))
|
||||||
|
@ -5,7 +5,7 @@ use clipboard::{ClipboardContext, ClipboardProvider};
|
|||||||
|
|
||||||
use abstio::MapName;
|
use abstio::MapName;
|
||||||
use widgetry::{
|
use widgetry::{
|
||||||
EventCtx, GfxCtx, Line, Outcome, Panel, State, TextExt, Toggle, Transition, Widget,
|
EventCtx, GfxCtx, Line, Outcome, Panel, State, TextBox, TextExt, Toggle, Transition, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::load::MapLoader;
|
use crate::load::MapLoader;
|
||||||
@ -49,29 +49,24 @@ impl<A: AppLike + 'static> ImportCity<A> {
|
|||||||
"Copy the JSON text on the right into your clipboard".text_widget(ctx),
|
"Copy the JSON text on the right into your clipboard".text_widget(ctx),
|
||||||
])
|
])
|
||||||
.margin_below(16),
|
.margin_below(16),
|
||||||
|
Toggle::choice(
|
||||||
|
ctx,
|
||||||
|
"left handed driving",
|
||||||
|
"drive on the left",
|
||||||
|
"right",
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
Toggle::choice(ctx, "source", "GeoFabrik", "Overpass (faster)", None, false),
|
||||||
Widget::row(vec![
|
Widget::row(vec![
|
||||||
"Step 4)".text_widget(ctx).centered_vert(),
|
"Name the map:".text_widget(ctx).centered_vert(),
|
||||||
Toggle::choice(
|
TextBox::widget(ctx, "new_map_name", generate_new_map_name(), true, 20),
|
||||||
ctx,
|
|
||||||
"left handed driving",
|
|
||||||
"drive on the left",
|
|
||||||
"right",
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
Widget::row(vec![
|
ctx.style()
|
||||||
"Step 5)".text_widget(ctx).centered_vert(),
|
.btn_solid_primary
|
||||||
Toggle::choice(ctx, "source", "GeoFabrik", "Overpass (faster)", None, false),
|
.text("Import the area from your clipboard")
|
||||||
]),
|
.build_def(ctx)
|
||||||
Widget::row(vec![
|
.margin_below(32),
|
||||||
"Step 6)".text_widget(ctx).centered_vert(),
|
|
||||||
ctx.style()
|
|
||||||
.btn_solid_primary
|
|
||||||
.text("Import the area from your clipboard")
|
|
||||||
.build_def(ctx),
|
|
||||||
])
|
|
||||||
.margin_below(32),
|
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_plain
|
.btn_plain
|
||||||
.btn()
|
.btn()
|
||||||
@ -102,23 +97,26 @@ impl<A: AppLike + 'static> State<A> for ImportCity<A> {
|
|||||||
Transition::Keep
|
Transition::Keep
|
||||||
}
|
}
|
||||||
"Import the area from your clipboard" => {
|
"Import the area from your clipboard" => {
|
||||||
|
let name = sanitize_name(self.panel.text_box("new_map_name"));
|
||||||
|
|
||||||
let mut args = vec![
|
let mut args = vec![
|
||||||
find_exe("cli"),
|
find_exe("cli"),
|
||||||
"one-step-import".to_string(),
|
"one-step-import".to_string(),
|
||||||
"boundary.geojson".to_string(),
|
"--geojson-path=boundary.geojson".to_string(),
|
||||||
|
format!("--map-name={}", name),
|
||||||
];
|
];
|
||||||
if self.panel.is_checked("left handed driving") {
|
if self.panel.is_checked("left handed driving") {
|
||||||
args.push("--drive_on_left".to_string());
|
args.push("--drive-on-left".to_string());
|
||||||
}
|
}
|
||||||
if self.panel.is_checked("source") {
|
if self.panel.is_checked("source") {
|
||||||
args.push("--use_geofabrik".to_string());
|
args.push("--use-geofabrik".to_string());
|
||||||
}
|
}
|
||||||
match grab_geojson_from_clipboard() {
|
match grab_geojson_from_clipboard() {
|
||||||
Ok(()) => Transition::Push(crate::tools::RunCommand::new_state(
|
Ok(()) => Transition::Push(crate::tools::RunCommand::new_state(
|
||||||
ctx,
|
ctx,
|
||||||
true,
|
true,
|
||||||
args,
|
args,
|
||||||
Box::new(|_, _, success, mut lines| {
|
Box::new(|_, _, success, _| {
|
||||||
if success {
|
if success {
|
||||||
abstio::delete_file("boundary.geojson");
|
abstio::delete_file("boundary.geojson");
|
||||||
|
|
||||||
@ -126,11 +124,8 @@ impl<A: AppLike + 'static> State<A> for ImportCity<A> {
|
|||||||
let mut state =
|
let mut state =
|
||||||
state.downcast::<ImportCity<A>>().ok().unwrap();
|
state.downcast::<ImportCity<A>>().ok().unwrap();
|
||||||
let on_load = state.on_load.take().unwrap();
|
let on_load = state.on_load.take().unwrap();
|
||||||
// one_step_import prints the name of the map as the last
|
let map_name = MapName::new("zz", "oneshot", &name);
|
||||||
// line.
|
vec![MapLoader::new_state(ctx, app, map_name, on_load)]
|
||||||
let name =
|
|
||||||
MapName::new("zz", "oneshot", &lines.pop().unwrap());
|
|
||||||
vec![MapLoader::new_state(ctx, app, name, on_load)]
|
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// The popup already explained the failure
|
// The popup already explained the failure
|
||||||
@ -179,3 +174,18 @@ fn grab_geojson_from_clipboard() -> Result<()> {
|
|||||||
write!(f, "{}", contents)?;
|
write!(f, "{}", contents)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sanitize_name(x: String) -> String {
|
||||||
|
x.replace(" ", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_new_map_name() -> String {
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let name = format!("imported_{}", i);
|
||||||
|
if !abstio::file_exists(MapName::new("zz", "oneshot", &name).path()) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user