splitting into two Bounds types

This commit is contained in:
Dustin Carlino 2018-10-29 12:35:39 -07:00
parent cc9f0289ab
commit bb9c98b54e
22 changed files with 187 additions and 154 deletions

View File

@ -2,16 +2,16 @@ use aabb_quadtree::geom::Rect;
use aabb_quadtree::QuadTree;
use abstutil::MultiMap;
use geo;
use geom::{Bounds, LonLat};
use geom::{GPSBounds, LonLat};
use map_model::raw_data;
use std::collections::BTreeSet;
// Slight cheat
type ParcelIdx = usize;
pub fn group_parcels(map_bounds: &Bounds, parcels: &mut Vec<raw_data::Parcel>) {
pub fn group_parcels(gps_bounds: &GPSBounds, parcels: &mut Vec<raw_data::Parcel>) {
// Make a quadtree to quickly prune intersections between parcels
let mut quadtree = QuadTree::default(map_bounds.as_bbox());
let mut quadtree = QuadTree::default(gps_bounds.as_bbox());
for (idx, p) in parcels.iter().enumerate() {
quadtree.insert_with_box(idx, get_bbox(&p.points));
}
@ -107,9 +107,9 @@ fn polygons_intersect(pts1: &Vec<LonLat>, pts2: &Vec<LonLat>) -> bool {
}
fn get_bbox(pts: &Vec<LonLat>) -> Rect {
let mut b = Bounds::new();
for p in pts.iter() {
b.update(p.longitude, p.latitude);
let mut b = GPSBounds::new();
for pt in pts.iter() {
b.update(*pt)
}
b.as_bbox()
}

View File

@ -24,7 +24,6 @@ mod split_ways;
mod srtm;
mod traffic_signals;
use geom::LonLat;
use map_model::raw_data;
use ordered_float::NotNaN;
use srtm::Elevation;
@ -71,7 +70,7 @@ pub fn convert(flags: &Flags) -> raw_data::Map {
let raw_map = osm::osm_to_raw_roads(&flags.osm);
let mut map = split_ways::split_up_roads(&raw_map, &elevation);
remove_disconnected::remove_disconnected_roads(&mut map);
let bounds = map.get_gps_bounds();
let gps_bounds = map.get_gps_bounds();
println!("Loading parcels from {}", flags.parcels);
let parcels_map: raw_data::Map =
@ -83,21 +82,20 @@ pub fn convert(flags: &Flags) -> raw_data::Map {
for p in parcels_map.parcels {
if p.points
.iter()
.find(|pt| !bounds.contains(pt.longitude, pt.latitude))
.find(|pt| !gps_bounds.contains(**pt))
.is_none()
{
map.parcels.push(p);
}
}
group_parcels::group_parcels(&bounds, &mut map.parcels);
group_parcels::group_parcels(&gps_bounds, &mut map.parcels);
for coord in
&traffic_signals::extract(&flags.traffic_signals).expect("loading traffic signals failed")
for pt in traffic_signals::extract(&flags.traffic_signals)
.expect("loading traffic signals failed")
.into_iter()
{
if bounds.contains(coord.longitude, coord.latitude) {
let distance = |i: &raw_data::Intersection| {
coord.gps_dist_meters(LonLat::new(i.point.longitude, i.point.latitude))
};
if gps_bounds.contains(pt) {
let distance = |i: &raw_data::Intersection| pt.gps_dist_meters(i.point);
// TODO use a quadtree or some better way to match signals to the closest
// intersection
@ -124,7 +122,7 @@ pub fn convert(flags: &Flags) -> raw_data::Map {
.to_os_string()
.into_string()
.unwrap();
neighborhoods::convert(&flags.neighborhoods, map_name, &bounds);
neighborhoods::convert(&flags.neighborhoods, map_name, &gps_bounds);
timer.done();

View File

@ -1,9 +1,9 @@
use abstutil;
use geojson::{GeoJson, PolygonType, Value};
use geom::{Bounds, LonLat, Pt2D};
use geom::{GPSBounds, LonLat, Pt2D};
use sim::Neighborhood;
pub fn convert(geojson_path: &str, map_name: String, bounds: &Bounds) {
pub fn convert(geojson_path: &str, map_name: String, gps_bounds: &GPSBounds) {
println!("Extracting neighborhoods from {}...", geojson_path);
let document: GeoJson = abstutil::read_json(geojson_path).unwrap();
match document {
@ -11,14 +11,14 @@ pub fn convert(geojson_path: &str, map_name: String, bounds: &Bounds) {
let name = f.properties.unwrap()["name"].as_str().unwrap().to_string();
match f.geometry.unwrap().value {
Value::Polygon(p) => {
convert_polygon(p, name, map_name.clone(), bounds);
convert_polygon(p, name, map_name.clone(), gps_bounds);
}
Value::MultiPolygon(polygons) => for (idx, p) in polygons.into_iter().enumerate() {
convert_polygon(
p,
format!("{} portion #{}", name, idx + 1),
map_name.clone(),
bounds,
gps_bounds,
);
},
x => panic!("Unexpected GeoJson value {:?}", x),
@ -28,7 +28,7 @@ pub fn convert(geojson_path: &str, map_name: String, bounds: &Bounds) {
}
}
fn convert_polygon(input: PolygonType, name: String, map_name: String, bounds: &Bounds) {
fn convert_polygon(input: PolygonType, name: String, map_name: String, gps_bounds: &GPSBounds) {
if input.len() > 1 {
println!("{} has a polygon with an inner ring, skipping", name);
return;
@ -37,7 +37,7 @@ fn convert_polygon(input: PolygonType, name: String, map_name: String, bounds: &
let mut points: Vec<Pt2D> = Vec::new();
for pt in &input[0] {
assert_eq!(pt.len(), 2);
if let Some(pt) = Pt2D::from_gps(LonLat::new(pt[0], pt[1]), bounds) {
if let Some(pt) = Pt2D::from_gps(LonLat::new(pt[0], pt[1]), gps_bounds) {
points.push(pt);
} else {
println!(

View File

@ -1,4 +1,4 @@
use geom::{Bounds, LonLat, PolyLine, Pt2D};
use geom::{GPSBounds, LonLat, PolyLine, Pt2D};
use quick_xml::events::Event;
use quick_xml::reader::Reader;
use std::collections::BTreeMap;
@ -27,7 +27,7 @@ pub enum ExtraShapeGeom {
Points(PolyLine),
}
pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::Error> {
pub fn load(path: &String, gps_bounds: &GPSBounds) -> Result<Vec<ExtraShape>, io::Error> {
info!("Opening {}", path);
let f = File::open(path).unwrap();
let mut reader = Reader::from_reader(io::BufReader::new(f));
@ -122,7 +122,7 @@ pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::E
return Ok(shapes);
}
fn parse_pt(input: &str, gps_bounds: &Bounds) -> Option<Pt2D> {
fn parse_pt(input: &str, gps_bounds: &GPSBounds) -> Option<Pt2D> {
let coords: Vec<&str> = input.split(",").collect();
if coords.len() != 2 {
return None;

View File

@ -55,8 +55,8 @@ impl Renderable for DrawBuilding {
fn get_bounds(&self) -> Bounds {
let mut b = self.fill_polygon.get_bounds();
b.update_pt(self.front_path.pt1());
b.update_pt(self.front_path.pt2());
b.update(self.front_path.pt1());
b.update(self.front_path.pt2());
b
}

View File

@ -171,7 +171,7 @@ impl Renderable for DrawLane {
map.get_source_intersection(self.id).elevation,
map.get_destination_intersection(self.id).elevation,
),
format!("Lane is {}m long", l.length()),
format!("Lane is {} long", l.length()),
];
for (k, v) in &r.osm_tags {
lines.push(format!("{} = {}", k, v));

View File

@ -1,60 +0,0 @@
use aabb_quadtree::geom::{Point, Rect};
use std::f64;
use {LonLat, Pt2D};
// TODO argh, use this in kml too
// TODO this maybe represents LonLat only?
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Bounds {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
// TODO hack to easily construct test maps
pub represents_world_space: bool,
}
impl Bounds {
pub fn new() -> Bounds {
Bounds {
min_x: f64::MAX,
min_y: f64::MAX,
max_x: f64::MIN,
max_y: f64::MIN,
represents_world_space: false,
}
}
pub fn update(&mut self, x: f64, y: f64) {
self.min_x = self.min_x.min(x);
self.max_x = self.max_x.max(x);
self.min_y = self.min_y.min(y);
self.max_y = self.max_y.max(y);
}
pub fn update_pt(&mut self, pt: Pt2D) {
self.update(pt.x(), pt.y());
}
pub fn update_coord(&mut self, coord: LonLat) {
self.update(coord.longitude, coord.latitude);
}
pub fn contains(&self, x: f64, y: f64) -> bool {
x >= self.min_x && x <= self.max_x && y >= self.min_y && y <= self.max_y
}
pub fn as_bbox(&self) -> Rect {
Rect {
top_left: Point {
x: self.min_x as f32,
y: self.min_y as f32,
},
bottom_right: Point {
x: self.max_x as f32,
y: self.max_y as f32,
},
}
}
}

View File

@ -24,7 +24,6 @@ impl Circle {
max_x: self.center.x() + self.radius,
min_y: self.center.y() - self.radius,
max_y: self.center.y() + self.radius,
represents_world_space: false,
}
}
}

View File

@ -1,4 +1,6 @@
use pt::HashablePt2D;
use aabb_quadtree::geom::{Point, Rect};
use std::f64;
use HashablePt2D;
// longitude is x, latitude is y
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
@ -37,3 +39,53 @@ impl LonLat {
HashablePt2D::new(self.longitude, self.latitude)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GPSBounds {
pub min_lon: f64,
pub min_lat: f64,
pub max_lon: f64,
pub max_lat: f64,
// TODO hack to easily construct test maps
pub represents_world_space: bool,
}
impl GPSBounds {
pub fn new() -> GPSBounds {
GPSBounds {
min_lon: f64::MAX,
min_lat: f64::MAX,
max_lon: f64::MIN,
max_lat: f64::MIN,
represents_world_space: false,
}
}
pub fn update(&mut self, pt: LonLat) {
self.min_lon = self.min_lon.min(pt.longitude);
self.max_lon = self.max_lon.max(pt.longitude);
self.min_lat = self.min_lat.min(pt.latitude);
self.max_lat = self.max_lat.max(pt.latitude);
}
pub fn contains(&self, pt: LonLat) -> bool {
pt.longitude >= self.min_lon
&& pt.longitude <= self.max_lon
&& pt.latitude >= self.min_lat
&& pt.latitude <= self.max_lat
}
pub fn as_bbox(&self) -> Rect {
Rect {
top_left: Point {
x: self.min_lon as f32,
y: self.min_lat as f32,
},
bottom_right: Point {
x: self.max_lon as f32,
y: self.max_lat as f32,
},
}
}
}

View File

@ -7,7 +7,6 @@ extern crate serde;
extern crate serde_derive;
mod angle;
mod bounds;
mod circle;
mod gps;
mod line;
@ -16,14 +15,13 @@ mod polyline;
mod pt;
pub use angle::Angle;
pub use bounds::Bounds;
pub use circle::Circle;
use dimensioned::si;
pub use gps::LonLat;
pub use gps::{GPSBounds, LonLat};
pub use line::Line;
pub use polygon::{Polygon, Triangle};
pub use polyline::PolyLine;
pub use pt::{HashablePt2D, Pt2D};
pub use pt::{Bounds, HashablePt2D, Pt2D};
use std::marker;
// About 0.4 inches... which is quite tiny on the scale of things. :)

View File

@ -95,9 +95,9 @@ impl Polygon {
pub fn get_bounds(&self) -> Bounds {
let mut b = Bounds::new();
for tri in &self.triangles {
b.update_pt(tri.pt1);
b.update_pt(tri.pt2);
b.update_pt(tri.pt3);
b.update(tri.pt1);
b.update(tri.pt2);
b.update(tri.pt3);
}
b
}

View File

@ -1,7 +1,8 @@
use aabb_quadtree::geom::{Point, Rect};
use ordered_float::NotNaN;
use std::f64;
use std::fmt;
use {Angle, Bounds, LonLat};
use {Angle, GPSBounds, LonLat};
// This represents world-space in meters.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@ -28,18 +29,18 @@ impl Pt2D {
Pt2D { x, y }
}
pub fn from_gps(gps: LonLat, b: &Bounds) -> Option<Pt2D> {
pub fn from_gps(gps: LonLat, b: &GPSBounds) -> Option<Pt2D> {
// TODO hack to construct test maps more easily
if b.represents_world_space {
return Some(Pt2D::new(gps.longitude, gps.latitude));
}
if !b.contains(gps.longitude, gps.latitude) {
if !b.contains(gps) {
return None;
}
// Invert y, so that the northernmost latitude is 0. Screen drawing order, not Cartesian grid.
let base = LonLat::new(b.min_x, b.max_y);
let base = LonLat::new(b.min_lon, b.max_lat);
// Apparently the aabb_quadtree can't handle 0, so add a bit.
// TODO epsilon or epsilon - 1.0?
@ -50,7 +51,7 @@ impl Pt2D {
Some(Pt2D::new(dx, dy))
}
pub fn to_gps(&self, gps_bounds: &Bounds) -> LonLat {
pub fn to_gps(&self, _gps_bounds: &GPSBounds) -> LonLat {
// TODO wait, this is going to be more complicated
LonLat::new(0.0, 0.0)
}
@ -107,6 +108,7 @@ impl From<HashablePt2D> for Pt2D {
}
// This isn't opinionated about what the (x, y) represents -- could be lat/lon or world space.
// TODO So rename it HashablePair or something
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct HashablePt2D {
x_nan: NotNaN<f64>,
@ -135,3 +137,46 @@ impl From<Pt2D> for HashablePt2D {
HashablePt2D::new(pt.x(), pt.y())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Bounds {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
}
impl Bounds {
pub fn new() -> Bounds {
Bounds {
min_x: f64::MAX,
min_y: f64::MAX,
max_x: f64::MIN,
max_y: f64::MIN,
}
}
pub fn update(&mut self, pt: Pt2D) {
self.min_x = self.min_x.min(pt.x);
self.max_x = self.max_x.max(pt.x);
self.min_y = self.min_y.min(pt.y);
self.max_y = self.max_y.max(pt.y);
}
pub fn contains(&self, pt: Pt2D) -> bool {
pt.x >= self.min_x && pt.x <= self.max_x && pt.y >= self.min_y && pt.y <= self.max_y
}
pub fn as_bbox(&self) -> Rect {
Rect {
top_left: Point {
x: self.min_x as f32,
y: self.min_y as f32,
},
bottom_right: Point {
x: self.max_x as f32,
y: self.max_y as f32,
},
}
}
}

View File

@ -141,8 +141,8 @@ impl DrawBuilding {
fn get_bounds(&self) -> Bounds {
// The bbox only shrinks; the original position is the worst case.
let mut b = self.polygon.get_bounds();
b.update_pt(self.line.pt1());
b.update_pt(self.line.pt2());
b.update(self.line.pt1());
b.update(self.line.pt2());
b
}
}

View File

@ -1,16 +1,13 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use geom;
use geom::{GPSBounds, LonLat};
use map_model;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
use std::fs::File;
use std::{f64, io};
pub fn load(
path: &String,
b: &geom::Bounds,
) -> Result<Vec<map_model::raw_data::Parcel>, io::Error> {
pub fn load(path: &String, b: &GPSBounds) -> Result<Vec<map_model::raw_data::Parcel>, io::Error> {
println!("Opening {}", path);
let f = File::open(path).unwrap();
let mut reader = Reader::from_reader(io::BufReader::new(f));
@ -40,10 +37,10 @@ pub fn load(
points: Vec::new(),
block: 0,
};
for pt in text.split(" ") {
if let Some((lon, lat)) = parse_pt(pt) {
if b.contains(lon, lat) {
parcel.points.push(geom::LonLat::new(lon, lat));
for raw_pt in text.split(" ") {
if let Some(pt) = parse_pt(raw_pt) {
if b.contains(pt) {
parcel.points.push(pt);
} else {
ok = false;
}
@ -69,13 +66,13 @@ pub fn load(
return Ok(parcels);
}
fn parse_pt(input: &str) -> Option<(f64, f64)> {
fn parse_pt(input: &str) -> Option<LonLat> {
let coords: Vec<&str> = input.split(",").collect();
if coords.len() != 2 {
return None;
}
return match (coords[0].parse::<f64>(), coords[1].parse::<f64>()) {
(Ok(lon), Ok(lat)) => Some((lon, lat)),
(Ok(lon), Ok(lat)) => Some(LonLat::new(lon, lat)),
_ => None,
};
}

View File

@ -18,11 +18,11 @@ fn main() {
}
// TODO don't hardcode
let bounds = geom::Bounds {
min_x: -122.4416,
max_x: -122.2421,
min_y: 47.5793,
max_y: 47.7155,
let bounds = geom::GPSBounds {
min_lon: -122.4416,
max_lon: -122.2421,
min_lat: 47.5793,
max_lat: 47.7155,
represents_world_space: false,
};
// TODO could use a better output format now

View File

@ -1,6 +1,6 @@
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Line, PolyLine, Pt2D};
use geom::{Bounds, GPSBounds, HashablePt2D, Line, PolyLine, Pt2D};
use make::sidewalk_finder::find_sidewalk_points;
use raw_data;
use std::collections::HashSet;
@ -9,7 +9,7 @@ use {Building, BuildingID, FrontPath, Lane};
pub fn make_all_buildings(
results: &mut Vec<Building>,
input: &Vec<raw_data::Building>,
gps_bounds: &Bounds,
gps_bounds: &GPSBounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
timer: &mut Timer,

View File

@ -1,6 +1,6 @@
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Pt2D};
use geom::{Bounds, GPSBounds, HashablePt2D, Pt2D};
use gtfs;
use make::sidewalk_finder::find_sidewalk_points;
use multimap::MultiMap;
@ -13,7 +13,7 @@ pub fn make_bus_stops(
lanes: &mut Vec<Lane>,
roads: &Vec<Road>,
bus_routes: &Vec<gtfs::Route>,
gps_bounds: &Bounds,
gps_bounds: &GPSBounds,
bounds: &Bounds,
timer: &mut Timer,
) -> (BTreeMap<BusStopID, BusStop>, Vec<BusRoute>) {

View File

@ -1,6 +1,6 @@
use abstutil::Timer;
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Pt2D};
use geom::{Bounds, GPSBounds, HashablePt2D, Pt2D};
use make::sidewalk_finder::find_sidewalk_points;
use raw_data;
use std::collections::HashSet;
@ -9,7 +9,7 @@ use {Lane, Parcel, ParcelID};
pub fn make_all_parcels(
results: &mut Vec<Parcel>,
input: &Vec<raw_data::Parcel>,
gps_bounds: &Bounds,
gps_bounds: &GPSBounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
timer: &mut Timer,

View File

@ -88,7 +88,7 @@ fn lane_to_line_string(l: &Lane) -> geo::LineString<f64> {
fn lane_to_rect(l: &Lane) -> Rect {
let mut b = Bounds::new();
for pt in l.lane_center_pts.points() {
b.update_pt(*pt);
b.update(*pt);
}
b.as_bbox()
}

View File

@ -3,7 +3,7 @@
use abstutil;
use abstutil::{Error, Timer};
use edits::RoadEdits;
use geom::{Bounds, HashablePt2D, LonLat, PolyLine, Pt2D};
use geom::{Bounds, GPSBounds, HashablePt2D, LonLat, PolyLine, Pt2D};
use make;
use raw_data;
use std::collections::{BTreeMap, BTreeSet, HashMap};
@ -26,8 +26,8 @@ pub struct Map {
bus_routes: Vec<BusRoute>,
areas: Vec<Area>,
// TODO maybe dont need to retain GPS stuff later
gps_bounds: Bounds,
gps_bounds: GPSBounds,
bounds: Bounds,
name: String,
road_edits: RoadEdits,
@ -57,11 +57,24 @@ impl Map {
) -> Map {
timer.start("raw_map to Map");
let gps_bounds = data.get_gps_bounds();
let bounds = gps_to_map_bounds(&gps_bounds);
let bounds = {
// min_y here due to the wacky y inversion
let max_screen_pt = Pt2D::from_gps(
LonLat::new(gps_bounds.max_lon, gps_bounds.min_lat),
&gps_bounds,
).unwrap();
let mut b = Bounds::new();
b.update(Pt2D::new(0.0, 0.0));
b.update(max_screen_pt);
b
};
let mut m = Map {
name,
road_edits,
gps_bounds,
gps_bounds: gps_bounds.clone(),
bounds: bounds.clone(),
roads: Vec::new(),
lanes: Vec::new(),
intersections: Vec::new(),
@ -412,12 +425,12 @@ impl Map {
}
// TODO can we return a borrow?
pub fn get_gps_bounds(&self) -> Bounds {
pub fn get_gps_bounds(&self) -> GPSBounds {
self.gps_bounds.clone()
}
pub fn get_bounds(&self) -> Bounds {
gps_to_map_bounds(&self.gps_bounds)
self.bounds.clone()
}
pub fn get_driving_lane_from_bldg(&self, bldg: BuildingID) -> Result<LaneID, Error> {
@ -491,12 +504,3 @@ impl Map {
info!("Saved {}", path);
}
}
fn gps_to_map_bounds(gps: &Bounds) -> Bounds {
// min_y here due to the wacky y inversion
let max_screen_pt = Pt2D::from_gps(LonLat::new(gps.max_x, gps.min_y), gps).unwrap();
let mut b = Bounds::new();
b.update_pt(Pt2D::new(0.0, 0.0));
b.update_pt(max_screen_pt);
b
}

View File

@ -1,5 +1,5 @@
use dimensioned::si;
use geom::{Bounds, HashablePt2D, LonLat};
use geom::{GPSBounds, HashablePt2D, LonLat};
use gtfs::Route;
use std::collections::BTreeMap;
use AreaType;
@ -29,30 +29,30 @@ impl Map {
}
}
pub fn get_gps_bounds(&self) -> Bounds {
let mut bounds = Bounds::new();
pub fn get_gps_bounds(&self) -> GPSBounds {
let mut bounds = GPSBounds::new();
for r in &self.roads {
for pt in &r.points {
bounds.update_coord(*pt);
bounds.update(*pt);
}
}
for i in &self.intersections {
bounds.update_coord(i.point);
bounds.update(i.point);
}
for b in &self.buildings {
for pt in &b.points {
bounds.update_coord(*pt);
bounds.update(*pt);
}
}
for a in &self.areas {
for pt in &a.points {
bounds.update_coord(*pt);
bounds.update(*pt);
}
}
for p in &self.parcels {
for pt in &p.points {
bounds.update_coord(*pt);
bounds.update(*pt);
}
}

View File

@ -1,5 +1,5 @@
use abstutil;
use geom::{Bounds, Polygon, Pt2D};
use geom::{GPSBounds, Polygon, Pt2D};
use map_model::{BuildingID, Map, RoadID};
use rand::Rng;
use std::collections::{BTreeSet, HashMap, HashSet};
@ -88,7 +88,7 @@ impl Neighborhood {
}
// https://wiki.openstreetmap.org/wiki/Osmosis/Polygon_Filter_File_Format
pub fn save_as_osmosis(&self, gps_bounds: &Bounds) -> Result<(), Error> {
pub fn save_as_osmosis(&self, gps_bounds: &GPSBounds) -> Result<(), Error> {
let path = format!("{}.poly", self.name);
let mut f = File::create(&path)?;