Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import new maps on web #1014

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"geom",
"headless",
"importer",
"js-importer",
"kml",
"map_gui",
"map_model",
Expand Down
7 changes: 3 additions & 4 deletions convert_osm/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ use streets_reader::OsmExtract;

pub fn extract_osm(
map: &mut RawMap,
osm_input_path: &str,
clip_path: Option<String>,
osm_xml: String,
is_clipped: bool,
opts: &Options,
timer: &mut Timer,
) -> (OsmExtract, MultiMap<osm::WayID, String>) {
let osm_xml = fs_err::read_to_string(osm_input_path).unwrap();
let mut doc =
streets_reader::osm_reader::read(&osm_xml, &map.streets.gps_bounds, timer).unwrap();

Expand All @@ -39,7 +38,7 @@ pub fn extract_osm(
way.tags.insert("sidewalk", "right");
}

if clip_path.is_none() {
if !is_clipped {
// Use the boundary from .osm.
map.streets.gps_bounds = doc.gps_bounds.clone();
map.streets.boundary_polygon = map.streets.gps_bounds.to_bounds().get_rectangle();
Expand Down
19 changes: 16 additions & 3 deletions convert_osm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,35 @@ pub fn convert(
clip_path: Option<String>,
opts: Options,
timer: &mut Timer,
) -> RawMap {
let osm_xml = fs_err::read_to_string(osm_input_path).unwrap();
let clip_pts = clip_path.map(|path| LonLat::read_osmosis_polygon(&path).unwrap());
convert_bytes(osm_xml, name, clip_pts, opts, timer)
}

/// Create a RawMap from OSM and other input data. This variation has file IO already done. If any
/// paths are specified in Options, they'll only work on native, not web.
pub fn convert_bytes(
osm_xml: String,
name: MapName,
clip_pts: Option<Vec<LonLat>>,
opts: Options,
timer: &mut Timer,
) -> RawMap {
timer.start("create RawMap from input data");

let mut map = RawMap::blank(name);
// Do this early. Calculating Roads uses DrivingSide, for example!
map.streets.config = opts.map_config.clone();

if let Some(ref path) = clip_path {
let pts = LonLat::read_osmosis_polygon(path).unwrap();
if let Some(ref pts) = clip_pts {
let gps_bounds = GPSBounds::from(pts.clone());
map.streets.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
map.streets.gps_bounds = gps_bounds;
}

let (extract, bus_routes_on_roads) =
extract::extract_osm(&mut map, &osm_input_path, clip_path, &opts, timer);
extract::extract_osm(&mut map, osm_xml, clip_pts.is_some(), &opts, timer);
map.bus_routes_on_roads = bus_routes_on_roads;
let split_output = streets_reader::split_ways::split_up_roads(&mut map.streets, extract, timer);
clip_map(&mut map, timer);
Expand Down
25 changes: 25 additions & 0 deletions js-importer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "js-importer"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
abstio = { path = "../abstio" }
abstutil = { path = "../abstutil" }
anyhow = { workspace = true }
console_error_panic_hook = "0.1.6"
convert_osm = { path = "../convert_osm" }
geom = { path = "../geom" }
getrandom = { workspace = true, features = ["js"] }
instant = { workspace = true, features = ["wasm-bindgen"] }
js-sys = "0.3.47"
log = { workspace = true }
map_model = { path = "../map_model" }
serde = { version = "1", features = ["derive"] }
wasm-bindgen = { workspace = true }
# TODO Why do we need this?
wasm-bindgen-futures = "0.4.20"
web-sys = { workspace = true, features=["Document"] }
72 changes: 72 additions & 0 deletions js-importer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use log::info;
use serde::Deserialize;
use wasm_bindgen::prelude::*;

use abstio::MapName;
use abstutil::Timer;
use geom::LonLat;
use map_model::DrivingSide;

// This is a subset of the variation in the cli crate, and geojson_path becomes geojson_boundary
#[derive(Deserialize)]
pub struct OneStepImport {
boundary_polygon: Vec<LonLat>,
map_name: String,
driving_side: DrivingSide,
}

#[wasm_bindgen]
pub async fn one_step_import(input: JsValue) -> Result<JsValue, JsValue> {
// Panics shouldn't happen, but if they do, console.log them.
console_error_panic_hook::set_once();
abstutil::logger::setup();

inner(input)
.await
.map_err(|err| JsValue::from_str(&err.to_string()))
}

async fn inner(input: JsValue) -> anyhow::Result<JsValue> {
let input: OneStepImport = input.into_serde()?;
let mut timer = Timer::new("one_step_import");
timer.start("download from Overpass");
let osm_xml = download_overpass(&input.boundary_polygon).await?;
timer.stop("download from Overpass");

let raw = convert_osm::convert_bytes(
osm_xml,
MapName::new("zz", "oneshot", &input.map_name),
Some(input.boundary_polygon),
convert_osm::Options::default_for_side(input.driving_side),
&mut timer,
);
/*let map =
map_model::Map::create_from_raw(raw, map_model::RawToMapOptions::default(), &mut timer);*/
info!("finished creating map! back to jsvalue pt1. is serializing really so slow?");
// TODO seemingly, yes?! whats breaking here?
//let bytes = abstutil::to_binary(&map);
let bytes = abstutil::to_binary(&raw);
info!("finished creating map! back to jsvalue pt2. is {}", bytes.len());
info!("first bytes... {}, {}", bytes[0], bytes[1]);
info!("last byte... {}", bytes[bytes.len()-1]);
let array = unsafe { js_sys::Uint8Array::view(&bytes) };
let result = JsValue::from(array);
info!("ok great done from rust");
Ok(result)
}

async fn download_overpass(boundary_polygon: &[LonLat]) -> anyhow::Result<String> {
let mut filter = "poly:\"".to_string();
for pt in boundary_polygon {
filter.push_str(&format!("{} {} ", pt.y(), pt.x()));
}
filter.pop();
filter.push('"');
// See https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL
let query = format!(
"(\n nwr({});\n node(w)->.x;\n <;\n);\nout meta;\n",
filter
);
info!("Querying overpass: {query}");
abstio::http_post("https://overpass-api.de/api/interpreter", query).await
}
5 changes: 4 additions & 1 deletion map_gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"

[features]
native = ["built", "subprocess", "widgetry/native-backend"]
wasm = ["wasm-bindgen", "web-sys", "widgetry/wasm-backend"]
wasm = ["js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "widgetry/wasm-backend"]
# A marker to use a named release from S3 instead of dev for updating files
release_s3 = []

Expand All @@ -24,10 +24,12 @@ futures-channel = { version = "0.3.12"}
geojson = { workspace = true }
geom = { path = "../geom" }
instant = { workspace = true }
js-sys = { version = "0.3.51", optional = true }
lazy_static = "1.4.0"
log = { workspace = true }
lyon = "1.0.0"
map_model = { path = "../map_model" }
raw_map = { path = "../raw_map" }
regex = "1.5.5"
rfd = "0.8.0"
serde = { workspace = true }
Expand All @@ -36,6 +38,7 @@ synthpop = { path = "../synthpop" }
structopt = { workspace = true }
subprocess = { git = "https://github.com/hniksic/rust-subprocess", optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { version = "0.4.20", optional = true }
web-sys = { workspace = true, optional = true }
widgetry = { path = "../widgetry" }
fs-err = { workspace = true }
Expand Down
49 changes: 15 additions & 34 deletions map_gui/src/tools/city_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,28 +145,18 @@ impl<A: AppLike + 'static> CityPicker<A> {
Line("Select a district").small_heading().into_widget(ctx),
ctx.style().btn_close_widget(ctx),
]),
if cfg!(target_arch = "wasm32") {
// On web, this is a link, so it's styled appropriately.
Widget::row(vec![
ctx.style()
.btn_outline
.text("Import a new city into A/B Street")
.build_widget(ctx, "import new city"),
ctx.style()
.btn_plain
.btn()
.label_underlined_text("Import a new city into A/B Street")
.build_widget(ctx, "import new city")
} else {
// On native this shows the "import" instructions modal within
// the app
Widget::row(vec![
ctx.style()
.btn_outline
.text("Import a new city into A/B Street")
.build_widget(ctx, "import new city"),
ctx.style()
.btn_outline
.text("Re-import this map with latest OpenStreetMap data")
.tooltip("OSM edits take a few minutes to appear in Overpass. Note this will create a new copy of the map, not overwrite the original.")
.build_widget(ctx, "re-import this city"),
])
},
.btn_outline
.text("Re-import this map with latest OpenStreetMap data")
.tooltip("OSM edits take a few minutes to appear in Overpass. Note this will create a new copy of the map, not overwrite the original.")
.build_widget(ctx, "re-import this city")
.hide(cfg!(target_arch = "wasm32")),
]),
ctx.style()
.btn_outline
.icon_text("system/assets/tools/search.svg", "Search all maps")
Expand Down Expand Up @@ -205,19 +195,10 @@ impl<A: AppLike + 'static> State<A> for CityPicker<A> {
));
}
"import new city" => {
#[cfg(target_arch = "wasm32")]
{
widgetry::tools::open_browser(
"https://a-b-street.github.io/docs/user/new_city.html",
);
}
#[cfg(not(target_arch = "wasm32"))]
{
return Transition::Replace(crate::tools::importer::ImportCity::new_state(
ctx,
self.on_load.take().unwrap(),
));
}
return Transition::Replace(crate::tools::importer::ImportCity::new_state(
ctx,
self.on_load.take().unwrap(),
));
}
"re-import this city" => {
#[cfg(target_arch = "wasm32")]
Expand Down
Loading