gistools/readers/gbfs/schema_v3/
mod.rs

1mod gbfs;
2mod gbfs_versions;
3mod geofencing_zones;
4mod manifest;
5mod station_information;
6mod station_status;
7mod system_alerts;
8mod system_information;
9mod system_pricing_plans;
10mod system_regions;
11mod vehicle_status;
12mod vehicle_types;
13
14use crate::{
15    geometry::{ConvertFeature, convert_geometry_to_vector},
16    parsers::FeatureReader,
17    util::fetch_url,
18};
19use alloc::{format, string::String, vec, vec::Vec};
20pub use gbfs::*;
21pub use gbfs_versions::*;
22pub use geofencing_zones::*;
23pub use manifest::*;
24use s2json::{Features, MValue, Properties, VectorFeature, VectorGeometry, VectorPoint};
25use serde::{Deserialize, Serialize};
26pub use station_information::*;
27pub use station_status::*;
28pub use system_alerts::*;
29pub use system_information::*;
30pub use system_pricing_plans::*;
31pub use system_regions::*;
32pub use vehicle_status::*;
33pub use vehicle_types::*;
34
35/// Geofencing Feature
36pub type GBFSGeofencingFeatureV3 = VectorFeature<(), GBFSGeofencingZonesV3Properties, MValue>;
37
38/// Station Information feature properties
39#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, MValue)]
40pub struct GBFSStationV3FeaturesProperties {
41    /// Station ID
42    pub station_id: String,
43    /// Name
44    pub name: String,
45    /// Short name
46    pub short_name: Option<String>,
47    /// Address
48    pub address: Option<String>,
49    /// Cross Street
50    pub cross_street: Option<String>,
51    /// Region ID
52    pub region_id: Option<String>,
53    /// Post Code
54    pub post_code: Option<String>,
55    /// Station Opening Hours
56    pub station_opening_hours: Option<String>,
57    /// Is Virtual Station
58    pub is_virtual_station: Option<bool>,
59    /// Parking Type
60    pub parking_type: Option<String>,
61    /// Parking Hoop
62    pub parking_hoop: Option<bool>,
63    /// Contact Phone
64    pub contact_phone: Option<String>,
65    /// Capacity
66    pub capacity: Option<u64>,
67    /// Is Valet Station
68    pub is_valet_station: Option<bool>,
69    /// Is Charging Station
70    pub is_charging_station: Option<bool>,
71}
72impl GBFSStationV3FeaturesProperties {
73    /// Create a new GBFSStationV3FeaturesProperties
74    pub fn new(station: &GBFSStationV3, locale: &str) -> Self {
75        // station.name is an array of objects with `language` and `text` params. Filter by language == locale and return `text`
76        let name = &station
77            .name
78            .iter()
79            .find(|n| n.language == locale)
80            .or_else(|| station.name.first())
81            .map_or(String::new(), |n| n.text.clone());
82        let short_name = station.short_name.as_ref().and_then(|s| {
83            s.iter().find(|name| name.language == locale).map(|name| name.text.clone())
84        });
85        GBFSStationV3FeaturesProperties {
86            station_id: station.station_id.clone(),
87            name: name.clone(),
88            short_name,
89            address: station.address.clone(),
90            cross_street: station.cross_street.clone(),
91            region_id: station.region_id.clone(),
92            post_code: station.post_code.clone(),
93            station_opening_hours: station.station_opening_hours.clone(),
94            is_virtual_station: station.is_virtual_station,
95            parking_type: station.parking_type.clone().map(|s| serde_json::to_string(&s).unwrap()),
96            parking_hoop: station.parking_hoop,
97            contact_phone: station.contact_phone.clone(),
98            capacity: station.capacity,
99            is_valet_station: station.is_valet_station,
100            is_charging_station: station.is_charging_station,
101        }
102    }
103}
104
105/// Station Information Point Feature
106pub type GBFSStationFeatureV3 = VectorFeature<(), GBFSStationV3FeaturesProperties, MValue>;
107
108/// Vehicle Point Feature
109pub type GBFSVehicleFeatureV3 = VectorFeature<(), GBFSVehicleV3, MValue>;
110
111/// GBFS Version 3 Reader
112#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
113pub struct GBFSReaderV3 {
114    /// User defined locale (defaults to "en")
115    pub locale: String,
116    /// The GBFS information
117    pub gbfs: GBFSV3,
118    /// The feeds for the GBFS
119    pub gbfs_versions: Option<GBFSVersionsV3>,
120    /// The system information
121    pub system_information: GBFSSystemInformationV3,
122    /// The station information
123    pub station_information: Option<GBFSStationInformationV3>,
124    /// The station status
125    pub station_status: Option<GBFSStationStatusV3>,
126    /// The vehicle status
127    pub vehicle_status: Option<GBFSVehicleStatusV3>,
128    /// The vehicle types
129    pub vehicle_types: Option<GBFSVehicleTypesV3>,
130    /// The system alerts
131    pub system_alerts: Option<GBFSSystemAlertsV3>,
132    /// The system regions
133    pub system_regions: Option<GBFSSystemRegionsV3>,
134    /// The system pricing plans
135    pub system_pricing_plans: Option<GBFSSystemPricingPlansV3>,
136    /// The geofencing zones
137    pub geofencing_zones: Option<GBFSGeofencingZonesV3>,
138    /// The manifest
139    pub manifest: Option<GBFSManifestV3>,
140}
141impl GBFSReaderV3 {
142    /// Get all features from the GBFS V3 data
143    pub fn features(&self) -> Vec<VectorFeature> {
144        let mut res = vec![];
145
146        res.extend(self.station_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
147        res.extend(self.geofencing_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
148        res.extend(self.vehicle_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
149
150        res
151    }
152
153    /// Get all station features from the GBFS V3 data
154    pub fn station_features(&self) -> Vec<GBFSStationFeatureV3> {
155        let mut res = vec![];
156
157        if let Some(station_information) = &self.station_information {
158            for station in &station_information.data.stations {
159                let properties = GBFSStationV3FeaturesProperties::new(station, &self.locale);
160                res.push(VectorFeature::new_wm(
161                    None,
162                    properties.clone(),
163                    VectorGeometry::new_point(VectorPoint::from_xy(station.lon, station.lat), None),
164                    None,
165                ));
166                if let Some(station_area) = &station.station_area {
167                    res.push(VectorFeature::new_wm(
168                        None,
169                        properties,
170                        convert_geometry_to_vector(station_area, true),
171                        None,
172                    ));
173                }
174            }
175        }
176
177        res
178    }
179
180    /// Get Geofencing features from the GBFS V3 data
181    pub fn geofencing_features(&self) -> Vec<GBFSGeofencingFeatureV3> {
182        let mut res = vec![];
183
184        if let Some(geofencing_zones) = &self.geofencing_zones {
185            for feature in &geofencing_zones.data.geofencing_zones.features {
186                match feature {
187                    Features::Feature(f) => {
188                        res.push(f.to_vector(Some(true)));
189                    }
190                    Features::VectorFeature(vf) => res.push(vf.clone()),
191                }
192            }
193        }
194
195        res
196    }
197
198    /// Get vehicle features for the GBFS V3 data
199    pub fn vehicle_features(&self) -> Vec<GBFSVehicleFeatureV3> {
200        let mut res = vec![];
201
202        if let Some(vehicle_status) = &self.vehicle_status {
203            for vehicle in &vehicle_status.data.vehicles {
204                if let (Some(lat), Some(lon)) = (vehicle.lat, vehicle.lon) {
205                    res.push(VectorFeature::new_wm(
206                        None,
207                        vehicle.clone(),
208                        VectorGeometry::new_point(VectorPoint::from_xy(lon, lat), None),
209                        None,
210                    ));
211                }
212            }
213        }
214
215        res
216    }
217}
218
219/// The GBFS V1 Iterator tool
220#[derive(Debug)]
221pub struct GBFSIteratorV3 {
222    features: Vec<VectorFeature>,
223    index: usize,
224    len: usize,
225}
226impl Iterator for GBFSIteratorV3 {
227    type Item = VectorFeature;
228
229    fn next(&mut self) -> Option<Self::Item> {
230        if self.index >= self.len {
231            return None;
232        }
233        self.index += 1;
234        self.features.get(self.index - 1).cloned()
235    }
236}
237/// A feature reader trait with a callback-based approach
238impl FeatureReader<(), Properties, MValue> for GBFSReaderV3 {
239    type FeatureIterator<'a> = GBFSIteratorV3;
240
241    fn iter(&self) -> Self::FeatureIterator<'_> {
242        let features = self.features();
243        let len = features.len();
244        GBFSIteratorV3 { features, index: 0, len }
245    }
246
247    fn par_iter(&self, pool_size: usize, thread_id: usize) -> Self::FeatureIterator<'_> {
248        let features = self.features();
249        let start = features.len() * thread_id / pool_size;
250        let end = features.len() * (thread_id + 1) / pool_size;
251        GBFSIteratorV3 { features, index: start, len: end }
252    }
253}
254
255/// Parse a GBFS V3 schema and build a V3 GBFS reader
256///
257/// ## Parameters
258/// - `gbfs`: the GBFS schema to parse
259/// - `locale`: the locale to use if provided, otherwise default to en
260/// - `path`: if provided, will use this path instead of the url (for testing)
261///
262/// ## Returns
263/// The GBFS reader
264pub async fn build_gbfs_reader_v3(
265    gbfs: &GBFSV3,
266    locale: Option<String>,
267    path: Option<String>,
268) -> GBFSReaderV3 {
269    let feeds = gbfs.data.feeds.clone();
270
271    let mut reader = GBFSReaderV3 {
272        locale: locale.unwrap_or("en".into()),
273        gbfs: gbfs.clone(),
274        ..Default::default()
275    };
276
277    for feed in feeds {
278        let name = serde_json::to_string(&feed.name).unwrap();
279        if &name == "gbfs" {
280            continue;
281        }
282        let url = if let Some(ref path) = path {
283            format!("{}/{}.json", path, name.trim_matches('"'))
284        } else {
285            feed.url
286        };
287
288        if let Ok(url_data) = fetch_url::<()>(&url, &[], None, None).await {
289            match feed.name {
290                GBFSV30FeedsName::Gbfs => {}
291                GBFSV30FeedsName::GbfsVersions => {
292                    reader.gbfs_versions = Some(serde_json::from_slice(&url_data).unwrap());
293                }
294                GBFSV30FeedsName::StationInformation => {
295                    reader.station_information = Some(serde_json::from_slice(&url_data).unwrap());
296                }
297                GBFSV30FeedsName::StationStatus => {
298                    reader.station_status = Some(serde_json::from_slice(&url_data).unwrap());
299                }
300                GBFSV30FeedsName::SystemAlerts => {
301                    reader.system_alerts = Some(serde_json::from_slice(&url_data).unwrap());
302                }
303                GBFSV30FeedsName::SystemInformation => {
304                    reader.system_information = serde_json::from_slice(&url_data).unwrap();
305                }
306                GBFSV30FeedsName::SystemPricingPlans => {
307                    reader.system_pricing_plans = Some(serde_json::from_slice(&url_data).unwrap());
308                }
309                GBFSV30FeedsName::SystemRegions => {
310                    reader.system_regions = Some(serde_json::from_slice(&url_data).unwrap());
311                }
312                GBFSV30FeedsName::VehicleStatus => {
313                    reader.vehicle_status = Some(serde_json::from_slice(&url_data).unwrap());
314                }
315                GBFSV30FeedsName::VehicleTypes => {
316                    reader.vehicle_types = Some(serde_json::from_slice(&url_data).unwrap());
317                }
318                GBFSV30FeedsName::GeofencingZones => {
319                    reader.geofencing_zones = Some(serde_json::from_slice(&url_data).unwrap());
320                }
321            }
322        }
323    }
324
325    if let Some(manifest_url) = reader.system_information.data.manifest_url.clone() {
326        let manifest_url = if let Some(ref path) = path {
327            format!("{}/manifest.json", path)
328        } else {
329            manifest_url
330        };
331        let manifest_data = fetch_url::<()>(&manifest_url, &[], None, None).await.unwrap();
332        reader.manifest = serde_json::from_slice(&manifest_data).unwrap_or(None);
333    }
334
335    reader
336}