Skip to content

Commit dd87218

Browse files
Merge pull request #71 from NREL/rjf/omf-edges
Rjf/omf edges
2 parents ea5cfaf + 6359e72 commit dd87218

File tree

19 files changed

+443
-141
lines changed

19 files changed

+443
-141
lines changed

rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ kdam = "0.6.2"
4242
log = "0.4.19"
4343
num-traits = "0.2.19"
4444
object_store = { "version" = "0.12.0", features = ["aws"] }
45+
ordered-float = { version = "5.1.0", features = ["serde"] }
4546
osmio = "0.14.0"
4647
osmpbf = "0.3.4"
4748
parquet = { version = "55.0.0", features = ["snap", "async", "object_store"] }

rust/bambam-omf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ kdam = { workspace = true }
4141
log = { workspace = true }
4242
object_store = { workspace = true }
4343
parquet = { workspace = true }
44+
ordered-float = { workspace = true }
4445
rayon = { workspace = true }
4546
reqwest = { workspace = true }
4647
serde = { workspace = true }

rust/bambam-omf/src/app/omf_app.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::Path;
22

33
use clap::{Parser, Subcommand};
4+
use routee_compass_core::model::network::EdgeListId;
45
use serde::{Deserialize, Serialize};
56

67
use crate::{
@@ -23,13 +24,18 @@ pub struct OmfApp {
2324
#[derive(Debug, Clone, Serialize, Deserialize, Subcommand)]
2425
pub enum OmfOperation {
2526
/// download all of the OMF transportation data
26-
Download,
27+
Download {
28+
/// location on disk to write output files. if not provided,
29+
/// use the current working directory.
30+
#[arg(short, long)]
31+
output_directory: Option<String>,
32+
},
2733
}
2834

2935
impl OmfOperation {
30-
pub fn run(self) -> Result<(), OvertureMapsCollectionError> {
36+
pub fn run(&self) -> Result<(), OvertureMapsCollectionError> {
3137
match self {
32-
OmfOperation::Download => {
38+
OmfOperation::Download { output_directory } => {
3339
let collector =
3440
OvertureMapsCollectorConfig::new(ObjectStoreSource::AmazonS3, 128).build()?;
3541
let release = ReleaseVersion::Latest;
@@ -45,8 +51,12 @@ impl OmfOperation {
4551
release,
4652
Some(row_filter_config),
4753
)?;
48-
let vectorized_graph = OmfGraphVectorized::try_from_collection(collection, 0)?;
49-
vectorized_graph.write_compass(Path::new("./"), true)?;
54+
let vectorized_graph = OmfGraphVectorized::new(collection, EdgeListId(0))?;
55+
let output_path = match output_directory {
56+
Some(o) => Path::new(o),
57+
None => Path::new(""),
58+
};
59+
vectorized_graph.write_compass(output_path, true)?;
5060

5161
Ok(())
5262
}

rust/bambam-omf/src/collection/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ pub enum OvertureMapsCollectionError {
3434
SerializationError(String),
3535
#[error("Segment connectors vector is invalid or not specified: {0}")]
3636
InvalidSegmentConnectors(String),
37+
#[error("linear reference {0} must be in range [0, 1]")]
38+
InvalidLinearReference(f64),
3739
#[error("Invalid or empty geometry: {0}")]
3840
InvalidGeometry(String),
3941
#[error("Error writing to csv: {0}")]
4042
CsvWriteError(String),
43+
#[error("{0}")]
44+
InternalError(String),
4145
}

rust/bambam-omf/src/collection/record/building.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::collection::OvertureMapsCollectionError;
99
pub struct BuildingsRecord {
1010
id: Option<String>,
1111
#[serde(deserialize_with = "deserialize_geometry")]
12-
geometry: Option<Geometry>,
12+
geometry: Option<Geometry<f32>>,
1313
bbox: OvertureMapsBbox,
1414
version: i32,
1515
sources: Option<Vec<Option<OvertureMapsSource>>>,
@@ -38,7 +38,7 @@ impl BuildingsRecord {
3838
self.class.clone()
3939
}
4040

41-
pub fn get_geometry(&self) -> Option<Geometry> {
41+
pub fn get_geometry(&self) -> Option<Geometry<f32>> {
4242
self.geometry.clone()
4343
}
4444
}

rust/bambam-omf/src/collection/record/common.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
use geo::Geometry;
2+
use geo::MapCoords;
3+
use geozero::error::GeozeroError;
24
use geozero::{wkb::Wkb, ToGeo};
35
use serde::de::Deserializer;
46
use serde::de::Error;
57
use serde::{Deserialize, Serialize};
68
use std::collections::HashMap;
79

8-
pub fn deserialize_geometry<'de, D>(deserializer: D) -> Result<Option<Geometry>, D::Error>
10+
pub fn deserialize_geometry<'de, D>(deserializer: D) -> Result<Option<Geometry<f32>>, D::Error>
911
where
1012
D: Deserializer<'de>,
1113
{
12-
// Assumption that this data is binary and not string
14+
// Assumption that this data is binary and not string.
15+
// convert here at the boundary of the program into f32 values.
1316
Option::<Vec<u8>>::deserialize(deserializer)?
14-
.map(|v| Wkb(v).to_geo())
17+
.map(|v| {
18+
let g = Wkb(v).to_geo()?;
19+
20+
g.try_map_coords(|geo::Coord { x, y }| {
21+
Ok(geo::Coord {
22+
x: x as f32,
23+
y: y as f32,
24+
})
25+
})
26+
})
1527
.transpose()
16-
.map_err(|e| D::Error::custom(format!("Could not decode wkb: {e}")))
28+
.map_err(|e: GeozeroError| D::Error::custom(format!("Could not decode wkb: {e}")))
1729
}
1830

1931
#[derive(Debug, Serialize, Deserialize)]

rust/bambam-omf/src/collection/record/place.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::collection::OvertureMapsCollectionError;
1111
#[derive(Debug, Serialize, Deserialize)]
1212
pub struct PlacesRecord {
1313
#[serde(deserialize_with = "deserialize_geometry")]
14-
geometry: Option<Geometry>,
14+
geometry: Option<Geometry<f32>>,
1515
categories: Option<OvertureMapsPlacesCategories>,
1616
id: Option<String>,
1717
bbox: OvertureMapsBbox,
@@ -63,7 +63,7 @@ impl PlacesRecord {
6363
}
6464
}
6565

66-
pub fn get_geometry(&self) -> Option<Geometry> {
66+
pub fn get_geometry(&self) -> Option<Geometry<f32>> {
6767
self.geometry.clone()
6868
}
6969
}

rust/bambam-omf/src/collection/record/transportation_connector.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
1313
pub struct TransportationConnectorRecord {
1414
pub id: String,
1515
#[serde(deserialize_with = "deserialize_geometry")]
16-
geometry: Option<Geometry>,
16+
geometry: Option<Geometry<f32>>,
1717
bbox: OvertureMapsBbox,
1818
version: i32,
1919
sources: Option<Vec<Option<OvertureMapsSource>>>,
@@ -33,7 +33,7 @@ impl TryFrom<OvertureRecord> for TransportationConnectorRecord {
3333
}
3434

3535
impl TransportationConnectorRecord {
36-
pub fn get_geometry(&self) -> Option<&Geometry> {
36+
pub fn get_geometry(&self) -> Option<&Geometry<f32>> {
3737
self.geometry.as_ref()
3838
}
3939

@@ -52,6 +52,6 @@ impl TransportationConnectorRecord {
5252
))),
5353
}?;
5454

55-
Ok(Vertex::new(idx, x as f32, y as f32))
55+
Ok(Vertex::new(idx, x, y))
5656
}
5757
}

rust/bambam-omf/src/collection/record/transportation_segment.rs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use geo::{Geometry, Haversine, Length};
1+
use geo::{Coord, Geometry, Haversine, InterpolatableLine, Length, LineString};
22
use serde::{Deserialize, Serialize};
33

44
use crate::collection::{OvertureMapsCollectionError, OvertureRecord};
@@ -12,9 +12,10 @@ use super::{OvertureMapsBbox, OvertureMapsNames, OvertureMapsSource};
1212
/// and other attributes relevant to routing and mapping.
1313
#[derive(Debug, Serialize, Deserialize)]
1414
pub struct TransportationSegmentRecord {
15+
/// GERS identifier for this segment record
1516
pub id: String,
1617
#[serde(deserialize_with = "deserialize_geometry")]
17-
pub geometry: Option<Geometry>,
18+
pub geometry: Option<Geometry<f32>>,
1819
bbox: OvertureMapsBbox,
1920
version: i32,
2021
sources: Option<Vec<Option<OvertureMapsSource>>>,
@@ -50,21 +51,43 @@ impl TryFrom<OvertureRecord> for TransportationSegmentRecord {
5051
}
5152

5253
impl TransportationSegmentRecord {
53-
pub fn get_distance_at(&self, at: f64) -> Result<f64, OvertureMapsCollectionError> {
54-
let geometry =
55-
self.geometry
56-
.as_ref()
57-
.ok_or(OvertureMapsCollectionError::InvalidGeometry(
58-
"empty geometry".to_string(),
59-
))?;
60-
54+
pub fn get_linestring(&self) -> Result<&LineString<f32>, OvertureMapsCollectionError> {
55+
let geometry = self.geometry.as_ref().ok_or_else(|| {
56+
OvertureMapsCollectionError::InvalidGeometry("empty geometry".to_string())
57+
})?;
6158
match geometry {
62-
Geometry::LineString(line_string) => Ok(Haversine.length(line_string) * at),
59+
Geometry::LineString(line_string) => Ok(line_string),
6360
_ => Err(OvertureMapsCollectionError::InvalidGeometry(format!(
6461
"geometry was not a linestring {geometry:?}"
6562
))),
6663
}
6764
}
65+
66+
pub fn get_distance_at(&self, at: f64) -> Result<f32, OvertureMapsCollectionError> {
67+
if !(0.0..=1.0).contains(&at) {
68+
return Err(OvertureMapsCollectionError::InvalidLinearReference(at));
69+
}
70+
let linestring = self.get_linestring()?;
71+
Ok(Haversine.length(linestring) * at as f32)
72+
}
73+
74+
/// gets a coordinate from this linestring at some linear reference.
75+
pub fn get_coord_at(&self, at: f64) -> Result<Coord<f32>, OvertureMapsCollectionError> {
76+
if !(0.0..=1.0).contains(&at) {
77+
return Err(OvertureMapsCollectionError::InvalidLinearReference(at));
78+
}
79+
let linestring = self.get_linestring()?;
80+
match linestring.point_at_ratio_from_start(&Haversine, at as f32) {
81+
Some(pt) => Ok(pt.0),
82+
None => {
83+
let msg = format!(
84+
"unexpected error getting point for segment {} at {at}",
85+
self.id
86+
);
87+
Err(OvertureMapsCollectionError::InternalError(msg))
88+
}
89+
}
90+
}
6891
}
6992

7093
#[derive(Debug, Serialize, Deserialize)]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use ordered_float::OrderedFloat;
2+
use serde::{Deserialize, Serialize};
3+
4+
/// represents a connector found within a segment
5+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
6+
pub struct ConnectorInSegment {
7+
pub segment_id: String,
8+
pub connector_id: String,
9+
pub linear_reference: OrderedFloat<f64>,
10+
}
11+
12+
impl ConnectorInSegment {
13+
/// records an existing connector record as a connector within a segment.
14+
pub fn new(segment_id: String, connector_id: String, linear_reference: f64) -> Self {
15+
Self {
16+
segment_id,
17+
connector_id,
18+
linear_reference: OrderedFloat(linear_reference),
19+
}
20+
}
21+
22+
/// creates a new connector within a segment by concatenating its segment id and reference.
23+
///
24+
/// this follows the pattern described by OvertureMaps when assigning unique
25+
/// identifiers to sub-segments by their segment id along with linear reference ranges.
26+
/// see <https://docs.overturemaps.org/guides/transportation/#transportation-splitter>
27+
pub fn new_without_connector_id(segment_id: String, linear_reference: f64) -> Self {
28+
let connector_id = format!("{}@{}", segment_id, linear_reference);
29+
Self {
30+
segment_id,
31+
connector_id,
32+
linear_reference: OrderedFloat(linear_reference),
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)