Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion 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 @@ -30,6 +30,7 @@ flate2 = "1.0"
rayon = "1.5"
include_dir = "0.6"
rstar = "0.8"
geo-booleanop = "0.3.0"

[dev-dependencies]
approx = "0.3"
2 changes: 2 additions & 0 deletions cosmogony/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ mod model;
pub mod mutable_slice;
mod read;
mod zone;
mod postcode;

pub use model::{Cosmogony, CosmogonyMetadata, CosmogonyStats};
pub use read::{load_cosmogony_from_file, read_zones_from_file};
pub use zone::{Coord, Zone, ZoneIndex, ZoneType};
pub use postcode::Postcode;
41 changes: 41 additions & 0 deletions cosmogony/src/postcode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::mutable_slice::MutableSlice;
use geo_types::{Coordinate, Geometry, MultiPolygon, Point, Rect, Polygon};
use log::warn;
use osmpbfreader::objects::Tags;
use serde::Serialize;
use serde_derive::*;
use std::collections::BTreeMap;
use std::fmt;

pub type Coord = Point<f64>;

#[derive(Debug, Clone)]
pub struct Postcode {
pub osm_id: String,
pub zipcode: String,
pub boundary: geo_types::MultiPolygon<f64>,
pub area: f64
}

impl Postcode {
pub fn get_boundary(&self) -> &geo_types::MultiPolygon<f64> {
return &self.boundary
}

pub fn unsigned_area(&self) -> f64 {
return self.area;
}
}

impl Default for Postcode {
fn default() -> Self {
Postcode {
osm_id: "".into(),
boundary: MultiPolygon(vec![]),
zipcode: "".into(),
area: 0.0
}
}
}

impl Postcode {}
68 changes: 62 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ mod hierarchy_builder;
pub mod merger;
mod zone_ext;
pub mod zone_typer;
mod postcode_ext;

use crate::country_finder::CountryFinder;
use crate::hierarchy_builder::{build_hierarchy, find_inclusions};
use additional_zones::compute_additional_cities;
use cosmogony::mutable_slice::MutableSlice;
use cosmogony::{Cosmogony, CosmogonyMetadata, CosmogonyStats};
use cosmogony::{Cosmogony, CosmogonyMetadata, CosmogonyStats, Postcode};
use failure::Error;
use failure::ResultExt;
use log::{debug, info};
Expand All @@ -24,6 +25,9 @@ use std::path::Path;
use cosmogony::{Zone, ZoneIndex};

use crate::zone_ext::ZoneExt;
use crate::postcode_ext::{PostcodeExt, PostcodeBbox};
use rstar::RTree;
use geo::bounding_rect::BoundingRect;

#[rustfmt::skip]
pub fn is_admin(obj: &OsmObj) -> bool {
Expand All @@ -39,6 +43,21 @@ pub fn is_admin(obj: &OsmObj) -> bool {
}
}

#[rustfmt::skip]
pub fn is_postal_code(obj: &OsmObj) -> bool {
match *obj {
OsmObj::Relation(ref rel) => {
rel.tags
.get("boundary")
.map_or(false, |v| v == "postal_code")
&&
rel.tags.get("postal_code").is_some()
}
_ => false,
}
}


pub fn is_place(obj: &OsmObj) -> bool {
match *obj {
OsmObj::Node(ref node) => node
Expand All @@ -49,19 +68,47 @@ pub fn is_place(obj: &OsmObj) -> bool {
}
}

pub fn get_postcodes(
pbf: &BTreeMap<OsmId, OsmObj>,
) -> Result<(RTree<PostcodeBbox>, CosmogonyStats), Error> {


let mut postcodes: Vec<PostcodeBbox> = Vec::with_capacity(1000);

let stats = CosmogonyStats::default();

for obj in pbf.values() {
if let OsmObj::Relation(ref relation) = *obj {
if let Some(postcode) = Postcode::from_osm_relation(relation, pbf) {
// Ignore zone without boundary polygon for the moment
let bbox = postcode.boundary.bounding_rect().unwrap();
postcodes.push(PostcodeBbox::new(
postcode,
&bbox
));
};
}
}

let mut tree = RTree::bulk_load(postcodes);



Ok((tree, stats))
}


pub fn get_zones_and_stats(
pbf: &BTreeMap<OsmId, OsmObj>,
postcodes: &RTree<PostcodeBbox>
) -> Result<(Vec<Zone>, CosmogonyStats), Error> {
let stats = CosmogonyStats::default();
let mut zones = Vec::with_capacity(1000);

for obj in pbf.values() {
if !is_admin(obj) {
continue;
}
if let OsmObj::Relation(ref relation) = *obj {
let next_index = ZoneIndex { index: zones.len() };
if let Some(zone) = Zone::from_osm_relation(relation, pbf, next_index) {
if let Some(zone) = Zone::from_osm_relation(relation, pbf, next_index, postcodes) {
// Ignore zone without boundary polygon for the moment
if zone.boundary.is_some() {
zones.push(zone);
Expand Down Expand Up @@ -216,7 +263,16 @@ pub fn build_cosmogony(
.context("invalid osm file")?;
info!("reading pbf done.");

let (mut zones, mut stats) = get_zones_and_stats(&parsed_pbf)?;
info!("Reading postal codes");
let file = File::open(&path).context("no pbf file")?;
let parsed_postal_code = OsmPbfReader::new(file)
.get_objs_and_deps(|o| is_postal_code(o))
.context("invalid osm file")?;
info!("reading postal code from pbf done.");

let (postcodes, mut stats2) = get_postcodes(&parsed_postal_code)?;

let (mut zones, mut stats) = get_zones_and_stats(&parsed_pbf, &postcodes)?;

create_ontology(
&mut zones,
Expand Down
87 changes: 87 additions & 0 deletions src/postcode_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// extends Zones to add some capabilities
// The Zone's capabilities have been split in order to hide some functions specific to cosmogony
// and that we do not want to expose in the model

use cosmogony::{mutable_slice::MutableSlice, Coord, Zone, ZoneIndex, ZoneType, Postcode};
use osm_boundaries_utils::build_boundary;
use osmpbfreader::objects::{OsmId, OsmObj, Relation};
use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryInto;
use rstar::{RTreeObject, AABB};
use geo::{Point, Rect};
use geo::algorithm::area::Area;


#[derive(Debug)]
pub struct PostcodeBbox {
postcode: Postcode,
bbox: AABB<Point<f64>>,
}

impl PostcodeBbox {
pub fn new(postcode: Postcode, bbox: &Rect<f64>) -> Self {
PostcodeBbox {
postcode,
bbox: envelope(&bbox),
}
}

pub fn get_postcode(&self) -> &Postcode {
return &self.postcode;
}
}


impl RTreeObject for PostcodeBbox {
type Envelope = AABB<Point<f64>>;
fn envelope(&self) -> Self::Envelope {
self.bbox
}
}


fn envelope(bbox: &Rect<f64>) -> AABB<Point<f64>> {
AABB::from_corners(bbox.min().into(), bbox.max().into())
}

pub trait PostcodeExt {
/// create a zone from an osm relation and a geometry
fn from_osm_relation(
relation: &Relation,
objects: &BTreeMap<OsmId, OsmObj>,
) -> Option<Postcode>;
}

impl PostcodeExt for Postcode {

fn from_osm_relation(
relation: &Relation,
objects: &BTreeMap<OsmId, OsmObj>,
) -> Option<Self> {
// Skip postcode withjout postcode
let zipcode = match relation.tags.get("postal_code") {
Some(val) => val,
None => {
debug!(
"relation/{}: postcode region without name, skipped",
relation.id.0
);
""
}
};

let osm_id = format!("relation:{}", relation.id.0.to_string());

let boundary = build_boundary(relation, objects);

boundary.map(|boundary| {
let area = boundary.unsigned_area();
Postcode {
osm_id,
zipcode: zipcode.to_string(),
boundary,
area
}
})
}
}
Loading