mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 01:15:12 +03:00
Refactor most of the places extracting polygons from geojson files (#644)
This commit is contained in:
parent
446a21696d
commit
1edbca6509
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -622,7 +622,6 @@ dependencies = [
|
||||
"abstio",
|
||||
"abstutil",
|
||||
"anyhow",
|
||||
"geojson",
|
||||
"geom",
|
||||
"kml",
|
||||
"log",
|
||||
@ -3019,7 +3018,6 @@ dependencies = [
|
||||
"futures",
|
||||
"geo",
|
||||
"geo-booleanop",
|
||||
"geojson",
|
||||
"geom",
|
||||
"geozero-core",
|
||||
"log",
|
||||
|
@ -141,8 +141,6 @@ impl CmdArgs {
|
||||
/// between arguments. So for instance "?--dev&--color_scheme=night%20mode" becomes vec!["--dev",
|
||||
/// "--color_scheme=night mode"].
|
||||
fn parse_args() -> anyhow::Result<Vec<String>> {
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
let window = web_sys::window().ok_or(anyhow!("no window?"))?;
|
||||
let url = window.location().href().map_err(|err| {
|
||||
anyhow!(err
|
||||
|
@ -2,6 +2,8 @@ use std::cmp::Ord;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -258,6 +260,10 @@ impl Tags {
|
||||
self.0.get(k)
|
||||
}
|
||||
|
||||
pub fn get_result(&self, k: &str) -> Result<&String> {
|
||||
self.0.get(k).ok_or_else(|| anyhow!("missing {}", k))
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, k: &str) -> bool {
|
||||
self.0.contains_key(k)
|
||||
}
|
||||
|
@ -6,6 +6,9 @@
|
||||
#![allow(clippy::ptr_arg)] // very noisy
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
// I'm not generally a fan of wildcard exports, but they're more maintable here.
|
||||
pub use crate::serde::*;
|
||||
pub use cli::*;
|
||||
|
@ -8,7 +8,6 @@ edition = "2018"
|
||||
abstio = { path = "../abstio" }
|
||||
abstutil = { path = "../abstutil" }
|
||||
anyhow = "1.0.38"
|
||||
geojson = "0.22"
|
||||
geom = { path = "../geom" }
|
||||
kml = { path = "../kml" }
|
||||
log = "0.4.14"
|
||||
|
@ -7,7 +7,7 @@ use anyhow::Result;
|
||||
|
||||
use abstio::MapName;
|
||||
use abstutil::{Tags, Timer};
|
||||
use geom::{Distance, FindClosest, GPSBounds, LonLat, Pt2D, Ring};
|
||||
use geom::{Distance, FindClosest, GPSBounds, LonLat, Polygon, Pt2D, Ring};
|
||||
use map_model::raw::RawMap;
|
||||
use map_model::{osm, raw, Amenity, MapConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -145,35 +145,10 @@ fn use_amenities(map: &mut RawMap, amenities: Vec<(Pt2D, Amenity)>, timer: &mut
|
||||
}
|
||||
|
||||
fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
|
||||
// TODO Refactor code that just extracts polygons from geojson.
|
||||
let mut polygons = Vec::new();
|
||||
|
||||
let bytes = abstio::slurp_file(path)?;
|
||||
let raw_string = std::str::from_utf8(&bytes)?;
|
||||
let geojson = raw_string.parse::<geojson::GeoJson>()?;
|
||||
|
||||
if let geojson::GeoJson::FeatureCollection(collection) = geojson {
|
||||
for feature in collection.features {
|
||||
if let Some(geom) = feature.geometry {
|
||||
if let geojson::Value::Polygon(raw_pts) = geom.value {
|
||||
// TODO Handle holes, and also, refactor this!
|
||||
let gps_pts: Vec<LonLat> = raw_pts[0]
|
||||
.iter()
|
||||
.map(|pt| LonLat::new(pt[0], pt[1]))
|
||||
.collect();
|
||||
if let Some(pts) = map.gps_bounds.try_convert(&gps_pts) {
|
||||
if let Ok(ring) = Ring::new(pts) {
|
||||
polygons.push(ring.into_polygon());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add these as new buildings, generating a new dummy OSM ID.
|
||||
let require_in_bounds = true;
|
||||
let mut id = -1;
|
||||
for polygon in polygons {
|
||||
for (polygon, _) in Polygon::from_geojson_file(path, &map.gps_bounds, require_in_bounds)? {
|
||||
// Add these as new buildings, generating a new dummy OSM ID.
|
||||
map.buildings.insert(
|
||||
osm::OsmID::Way(osm::WayID(id)),
|
||||
raw::RawBuilding {
|
||||
@ -188,6 +163,5 @@ fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
|
||||
// new OSM IDs.
|
||||
id -= -1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ use maplit::btreemap;
|
||||
use rand::seq::{IteratorRandom, SliceRandom};
|
||||
|
||||
use abstio::MapName;
|
||||
use abstutil::{Tags, Timer};
|
||||
use geom::{Bounds, Circle, Distance, Duration, LonLat, Pt2D, Ring, Time};
|
||||
use abstutil::Timer;
|
||||
use geom::{Bounds, Circle, Distance, Duration, Polygon, Pt2D, Time};
|
||||
use map_gui::colors::ColorScheme;
|
||||
use map_gui::load::FileLoader;
|
||||
use map_gui::options::Options;
|
||||
@ -880,35 +880,13 @@ impl SharedAppState for App {
|
||||
|
||||
/// Load an extra GeoJSON file, and add the area to the map dynamically.
|
||||
fn add_study_area(map: &mut Map, name: &str) -> Result<()> {
|
||||
let bytes = abstio::slurp_file(abstio::path(format!("system/study_areas/{}.geojson", name)))?;
|
||||
let raw_string = std::str::from_utf8(&bytes)?;
|
||||
let geojson = raw_string.parse::<geojson::GeoJson>()?;
|
||||
|
||||
if let geojson::GeoJson::FeatureCollection(collection) = geojson {
|
||||
for feature in collection.features {
|
||||
if let Some(geom) = feature.geometry {
|
||||
if let geojson::Value::Polygon(raw_pts) = geom.value {
|
||||
// TODO Handle holes, and also, refactor this!
|
||||
let gps_pts: Vec<LonLat> = raw_pts[0]
|
||||
.iter()
|
||||
.map(|pt| LonLat::new(pt[0], pt[1]))
|
||||
.collect();
|
||||
if let Some(pts) = map.get_gps_bounds().try_convert(&gps_pts) {
|
||||
let mut tags = Tags::empty();
|
||||
if let Some(props) = feature.properties {
|
||||
for (k, v) in props {
|
||||
tags.insert(k, v.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(ring) = Ring::new(pts) {
|
||||
map.hack_add_area(AreaType::StudyArea, ring.into_polygon(), tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let require_in_bounds = true;
|
||||
for (polygon, tags) in Polygon::from_geojson_file(
|
||||
abstio::path(format!("system/study_areas/{}.geojson", name)),
|
||||
map.get_gps_bounds(),
|
||||
require_in_bounds,
|
||||
)? {
|
||||
map.hack_add_area(AreaType::StudyArea, polygon, tags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use geo::algorithm::area::Area;
|
||||
use geo::algorithm::concave_hull::ConcaveHull;
|
||||
use geo::algorithm::convex_hull::ConvexHull;
|
||||
@ -9,7 +10,11 @@ use geo::algorithm::intersects::Intersects;
|
||||
use geo_booleanop::boolean::BooleanOp;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Angle, Bounds, CornerRadii, Distance, GPSBounds, HashablePt2D, PolyLine, Pt2D, Ring};
|
||||
use abstutil::Tags;
|
||||
|
||||
use crate::{
|
||||
Angle, Bounds, CornerRadii, Distance, GPSBounds, HashablePt2D, LonLat, PolyLine, Pt2D, Ring,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Polygon {
|
||||
@ -473,6 +478,19 @@ impl Polygon {
|
||||
|
||||
geojson::Geometry::new(geojson::Value::MultiPolygon(polygons))
|
||||
}
|
||||
|
||||
/// Extracts all polygons from a GeoJSON file, along with the string key/value properties. Only
|
||||
/// the first polygon from multipolygons is returned. If `require_in_bounds` is set, then the
|
||||
/// polygon must completely fit within the `gps_bounds`.
|
||||
pub fn from_geojson_file<P: AsRef<Path>>(
|
||||
path: P,
|
||||
gps_bounds: &GPSBounds,
|
||||
require_in_bounds: bool,
|
||||
) -> Result<Vec<(Polygon, Tags)>> {
|
||||
let path = path.as_ref();
|
||||
from_geojson_file_inner(path, gps_bounds, require_in_bounds)
|
||||
.with_context(|| format!("polygons from {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Polygon {
|
||||
@ -603,3 +621,53 @@ fn downsize(input: Vec<usize>) -> Vec<u16> {
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn from_geojson_file_inner(
|
||||
path: &Path,
|
||||
gps_bounds: &GPSBounds,
|
||||
require_in_bounds: bool,
|
||||
) -> Result<Vec<(Polygon, Tags)>> {
|
||||
let raw_string = std::fs::read_to_string(path)?;
|
||||
let geojson = raw_string.parse::<geojson::GeoJson>()?;
|
||||
let features = match geojson {
|
||||
geojson::GeoJson::Feature(feature) => vec![feature],
|
||||
geojson::GeoJson::FeatureCollection(collection) => collection.features,
|
||||
_ => anyhow::bail!("Unexpected geojson: {:?}", geojson),
|
||||
};
|
||||
|
||||
let mut results = Vec::new();
|
||||
for feature in features {
|
||||
if let Some(geom) = &feature.geometry {
|
||||
let raw_pts = match &geom.value {
|
||||
geojson::Value::Polygon(pts) => pts,
|
||||
// If there are multiple, just use the first
|
||||
geojson::Value::MultiPolygon(polygons) => &polygons[0],
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// TODO Handle holes
|
||||
let gps_pts: Vec<LonLat> = raw_pts[0]
|
||||
.iter()
|
||||
.map(|pt| LonLat::new(pt[0], pt[1]))
|
||||
.collect();
|
||||
let pts = if !require_in_bounds {
|
||||
gps_bounds.convert(&gps_pts)
|
||||
} else if let Some(pts) = gps_bounds.try_convert(&gps_pts) {
|
||||
pts
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if let Ok(ring) = Ring::new(pts) {
|
||||
let mut tags = Tags::empty();
|
||||
for (key, value) in feature.properties_iter() {
|
||||
if let Some(value) = value.as_str() {
|
||||
tags.insert(key, value);
|
||||
}
|
||||
}
|
||||
results.push((ring.into_polygon(), tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ collisions = { path = "../collisions" }
|
||||
convert_osm = { path = "../convert_osm" }
|
||||
csv = "1.1.4"
|
||||
geo = "0.17.0"
|
||||
geojson = "0.22"
|
||||
geojson = { version = "0.22.0", features = ["geo-types"] }
|
||||
geom = { path = "../geom" }
|
||||
gdal = { version = "0.7.2", optional = true }
|
||||
kml = { path = "../kml" }
|
||||
|
@ -8,7 +8,7 @@ use serde::Deserialize;
|
||||
|
||||
use abstio::path_shared_input;
|
||||
use abstutil::{prettyprint_usize, Timer};
|
||||
use geom::{GPSBounds, LonLat, Polygon, Ring};
|
||||
use geom::{GPSBounds, Polygon};
|
||||
use map_model::raw::RawMap;
|
||||
use map_model::Map;
|
||||
use popdat::od::DesireLine;
|
||||
@ -185,74 +185,25 @@ struct Record {
|
||||
// Transforms all zones into the map's coordinate space, no matter how far out-of-bounds they are.
|
||||
fn parse_zones(gps_bounds: &GPSBounds, path: String) -> Result<HashMap<String, Polygon>> {
|
||||
let mut zones = HashMap::new();
|
||||
|
||||
let bytes = abstio::slurp_file(path)?;
|
||||
let raw_string = std::str::from_utf8(&bytes)?;
|
||||
let geojson = raw_string.parse::<geojson::GeoJson>()?;
|
||||
|
||||
if let geojson::GeoJson::FeatureCollection(collection) = geojson {
|
||||
for feature in collection.features {
|
||||
let zone = feature
|
||||
.property("geo_code")
|
||||
.and_then(|x| x.as_str())
|
||||
.ok_or_else(|| anyhow!("no geo_code"))?
|
||||
.to_string();
|
||||
if let Some(geom) = feature.geometry {
|
||||
if let geojson::Value::MultiPolygon(mut raw_polygons) = geom.value {
|
||||
if raw_polygons.len() != 1 {
|
||||
// We'll just one of them arbitrarily
|
||||
warn!(
|
||||
"Zone {} has a multipolygon with {} members",
|
||||
zone,
|
||||
raw_polygons.len()
|
||||
);
|
||||
}
|
||||
match parse_polygon(raw_polygons.pop().unwrap(), gps_bounds) {
|
||||
Ok(polygon) => {
|
||||
zones.insert(zone, polygon);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Zone {} has bad geometry: {}", zone, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let require_in_bounds = false;
|
||||
for (polygon, tags) in Polygon::from_geojson_file(path, gps_bounds, require_in_bounds)? {
|
||||
zones.insert(tags.get_result("geo_code")?.to_string(), polygon);
|
||||
}
|
||||
|
||||
Ok(zones)
|
||||
}
|
||||
|
||||
// TODO Clean up the exploding number of geojson readers everywhere.
|
||||
fn parse_polygon(input: Vec<Vec<Vec<f64>>>, gps_bounds: &GPSBounds) -> Result<Polygon> {
|
||||
let mut rings = Vec::new();
|
||||
for ring in input {
|
||||
let gps_pts: Vec<LonLat> = ring
|
||||
.into_iter()
|
||||
.map(|pt| LonLat::new(pt[0], pt[1]))
|
||||
.collect();
|
||||
let pts = gps_bounds.convert(&gps_pts);
|
||||
rings.push(Ring::new(pts)?);
|
||||
}
|
||||
Ok(Polygon::from_rings(rings))
|
||||
}
|
||||
|
||||
fn load_study_area(map: &Map) -> Result<Polygon> {
|
||||
let bytes = abstio::slurp_file(abstio::path(format!(
|
||||
"system/study_areas/{}.geojson",
|
||||
map.get_name().city.city.replace("_", "-")
|
||||
)))?;
|
||||
let raw_string = std::str::from_utf8(&bytes)?;
|
||||
let geojson = raw_string.parse::<geojson::GeoJson>()?;
|
||||
|
||||
if let geojson::GeoJson::FeatureCollection(collection) = geojson {
|
||||
for feature in collection.features {
|
||||
if let Some(geom) = feature.geometry {
|
||||
if let geojson::Value::Polygon(raw_pts) = geom.value {
|
||||
return parse_polygon(raw_pts, map.get_gps_bounds());
|
||||
}
|
||||
}
|
||||
}
|
||||
let require_in_bounds = true;
|
||||
let mut list = Polygon::from_geojson_file(
|
||||
abstio::path(format!(
|
||||
"system/study_areas/{}.geojson",
|
||||
map.get_name().city.city.replace("_", "-")
|
||||
)),
|
||||
map.get_gps_bounds(),
|
||||
require_in_bounds,
|
||||
)?;
|
||||
if list.len() != 1 {
|
||||
bail!("study area geojson has {} polygons", list.len());
|
||||
}
|
||||
bail!("no study area");
|
||||
Ok(list.pop().unwrap().0)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ anyhow = "1.0.38"
|
||||
flatgeobuf = { version = "0.4" }
|
||||
futures = "0.3.12"
|
||||
geo = "0.17.0"
|
||||
geojson = { version = "0.22.0", features = ["geo-types"] }
|
||||
geom = { path = "../geom" }
|
||||
geozero-core = { version = "0.6" }
|
||||
log = "0.4.14"
|
||||
|
@ -563,8 +563,8 @@ impl PanelBuilder {
|
||||
};
|
||||
match panel.dims {
|
||||
Dims::ExactPercent(w, h) => {
|
||||
// Don't set size, because then scrolling breaks -- the actual size has to be based on
|
||||
// the contents.
|
||||
// Don't set size, because then scrolling breaks -- the actual size has to be based
|
||||
// on the contents.
|
||||
panel.top_level.layout.style.min_size = Size {
|
||||
width: Dimension::Points((w * ctx.canvas.window_width) as f32),
|
||||
height: Dimension::Points((h * ctx.canvas.window_height) as f32),
|
||||
|
Loading…
Reference in New Issue
Block a user