gistools/readers/gtfs/schedule/shapes.rs
1use crate::readers::csv::parse_csv_as_record;
2use alloc::{collections::BTreeMap, string::String, vec::Vec};
3use s2json::{
4 BBox3D, MValue, MValueCompatible, VectorFeature, VectorGeometry, VectorLineString, VectorPoint,
5};
6
7/// # Shape Properties
8#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
9pub struct GTFSShapeProperties {
10 /// The ID of the shape
11 pub shape_id: String,
12}
13impl From<String> for GTFSShapeProperties {
14 fn from(shape_id: String) -> Self {
15 GTFSShapeProperties { shape_id }
16 }
17}
18
19/// # Shape MValue
20#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
21pub struct GTFSShapeMValue {
22 /// May be missing
23 pub shape_dist_traveled: f64,
24}
25impl From<f64> for GTFSShapeMValue {
26 fn from(shape_dist_traveled: f64) -> Self {
27 GTFSShapeMValue { shape_dist_traveled }
28 }
29}
30
31/// # Shape Feature
32impl From<GTFSShapes> for VectorFeature {
33 fn from(shapes: GTFSShapes) -> Self {
34 let bbox = BBox3D::from_linestring(&shapes.shapes);
35 let props: GTFSShapeProperties = shapes.shape_id.into();
36 VectorFeature {
37 properties: props.into(),
38 geometry: VectorGeometry::new_linestring(shapes.shapes, Some(bbox)),
39 ..Default::default()
40 }
41 }
42}
43
44/// # Shapes
45///
46/// A collection of shapes all with the same shape_id and ordered by shape_pt_sequence
47#[derive(Debug, Default, Clone, PartialEq)]
48pub struct GTFSShapes {
49 /// The ID of the shape
50 pub shape_id: String,
51 /// The collection of shapes
52 pub shapes: VectorLineString,
53}
54impl From<&Vec<GTFSShape>> for GTFSShapes {
55 fn from(shapes: &Vec<GTFSShape>) -> Self {
56 let mut res = GTFSShapes::default();
57 if shapes.is_empty() {
58 return res;
59 }
60 // set id
61 let id = shapes[0].shape_id.clone();
62 res.shape_id = id;
63 // store shapes
64 for shape in shapes {
65 let mvalue: Option<GTFSShapeMValue> = shape.shape_dist_traveled.map(Into::into);
66 let mvalue: Option<MValue> = mvalue.map(Into::into);
67 res.shapes.push(VectorPoint::new_xy(shape.shape_pt_lon, shape.shape_pt_lat, mvalue));
68 }
69 res
70 }
71}
72
73/// # Shapes
74///
75/// ## Details
76/// **Optional** - Primary key (shape_id, shape_pt_sequence)
77///
78/// Shapes describe the path that a vehicle travels along a route alignment, and are defined in the
79/// file shapes.txt. Shapes are associated with Trips, and consist of a sequence of points through
80/// which the vehicle passes in order. Shapes do not need to intercept the location of Stops
81/// exactly, but all Stops on a trip should lie within a small distance of the shape for that trip,
82/// i.e. close to straight line segments connecting the shape points. The shapes.txt file should be
83/// included for all route-based services (not for zone-based demand-responsive services).
84#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
85pub struct GTFSShape {
86 /// **Required**
87 /// Identifies a shape. E.g. "A_shp"
88 pub shape_id: String,
89 /// **Required**
90 /// Latitude of a shape point. Each record in shapes.txt represents a shape point used to define
91 /// the shape.
92 pub shape_pt_lat: f64,
93 /// **Required**
94 /// Longitude of a shape point.
95 pub shape_pt_lon: f64,
96 /// **Required**
97 /// Sequence in which the shape points connect to form the shape. Values must increase along the
98 /// trip but do not need to be consecutive.
99 ///
100 /// Example: If the shape "A_shp" has three points in its definition, the shapes.txt file might contain these records to define the shape:
101 /// shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence
102 /// A_shp,37.61956,-122.48161,0
103 /// A_shp,37.64430,-122.41070,6
104 /// A_shp,37.65863,-122.30839,11
105 pub shape_pt_sequence: u64,
106 /// **Optional**
107 /// Actual distance traveled along the shape from the first shape point to the point specified
108 /// in this record. Used by trip planners to show the correct portion of the shape on a map.
109 /// Values must increase along with shape_pt_sequence; they must not be used to show reverse
110 /// travel along a route. Distance units must be consistent with those used in stop_times.txt.
111 ///
112 /// Recommended for routes that have looping or inlining (the vehicle crosses or travels over
113 /// the same portion of alignment in one trip).
114 ///
115 /// If a vehicle retraces or crosses the route alignment at points in the course of a trip,
116 /// shape_dist_traveled is important to clarify how portions of the points in shapes.txt line
117 /// up correspond with records in stop_times.txt.
118 ///
119 /// Example: If a bus travels along the three points defined above for A_shp, the additional shape_dist_traveled values (shown here in kilometers) would look like this:
120 /// shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
121 /// A_shp,37.61956,-122.48161,0,0
122 /// A_shp,37.64430,-122.41070,6,6.8310
123 /// A_shp,37.65863,-122.30839,11,15.8765
124 pub shape_dist_traveled: Option<f64>,
125}
126impl GTFSShape {
127 /// Create a new GTFSShape
128 pub fn new(source: &str) -> BTreeMap<String, Vec<GTFSShape>> {
129 let mut res = BTreeMap::new();
130 for record in parse_csv_as_record::<GTFSShape>(source, None, None) {
131 let v = res.entry(record.shape_id.clone()).or_insert(Vec::new());
132 v.push(record);
133 }
134 // iterate through each and sort by shape_pt_sequence
135 for shapes in res.values_mut() {
136 shapes.sort_by(|a, b| a.shape_pt_sequence.cmp(&b.shape_pt_sequence));
137 }
138 res
139 }
140}