gistools/readers/gbfs/schema_v2/
mod.rs

1mod free_bike_status;
2mod gbfs;
3mod gbfs_versions;
4mod geofencing_zones;
5mod station_information;
6mod station_status;
7mod system_alerts;
8mod system_calendar;
9mod system_hours;
10mod system_information;
11mod system_pricing_plans;
12mod system_regions;
13mod vehicle_types;
14
15use crate::{
16    geometry::{ConvertFeature, convert_geometry_to_vector},
17    parsers::FeatureReader,
18    util::fetch_url,
19};
20use alloc::{format, string::String, vec, vec::Vec};
21pub use free_bike_status::*;
22pub use gbfs::*;
23pub use gbfs_versions::*;
24pub use geofencing_zones::*;
25use s2json::{Features, MValue, Properties, VectorFeature, VectorGeometry, VectorPoint};
26use serde::{Deserialize, Serialize};
27pub use station_information::*;
28pub use station_status::*;
29pub use system_alerts::*;
30pub use system_calendar::*;
31pub use system_hours::*;
32pub use system_information::*;
33pub use system_pricing_plans::*;
34pub use system_regions::*;
35pub use vehicle_types::*;
36
37/// Geofencing Feature */
38pub type GBFSGeofencingFeatureV2 = VectorFeature<(), GBFSGeofencingZonesV2Properties, MValue>;
39
40/// Station Information feature properties
41#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, MValue)]
42pub struct GBFSStationV2FeaturesProperties {
43    /// Station ID
44    pub station_id: String,
45    /// Name
46    pub name: String,
47    /// Short name
48    pub short_name: Option<String>,
49    /// Address
50    pub address: Option<String>,
51    /// Cross street
52    pub cross_street: Option<String>,
53    /// Region ID
54    pub region_id: Option<String>,
55    /// Post code
56    pub post_code: Option<String>,
57    /// Is virtual
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 From<&GBFSStationInformationStationV23> for GBFSStationV2FeaturesProperties {
73    fn from(station: &GBFSStationInformationStationV23) -> Self {
74        GBFSStationV2FeaturesProperties {
75            station_id: station.station_id.clone(),
76            name: station.name.clone(),
77            short_name: station.short_name.clone(),
78            address: station.address.clone(),
79            cross_street: station.cross_street.clone(),
80            region_id: station.region_id.clone(),
81            post_code: station.post_code.clone(),
82            is_virtual_station: station.is_virtual_station,
83            parking_type: station.parking_type.clone().map(|s| serde_json::to_string(&s).unwrap()),
84            parking_hoop: station.parking_hoop,
85            contact_phone: station.contact_phone.clone(),
86            capacity: station.capacity,
87            is_valet_station: station.is_valet_station,
88            is_charging_station: station.is_charging_station,
89        }
90    }
91}
92
93/// Station Information Point Feature */
94pub type GBFSStationFeatureV2 = VectorFeature<(), GBFSStationV2FeaturesProperties, MValue>;
95
96/// Bike Information Point Feature
97pub type GBFSBikeFeatureV2 = VectorFeature<(), GBFSFreeBikeStatusBikeV23, MValue>;
98
99/// GBFS Version 2 Reader
100#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
101pub struct GBFSReaderV2 {
102    /// Free Bike Status
103    pub free_bike_status: Option<GBFSFreeBikeStatusV2>,
104    /// GBFS
105    pub gbfs: GBFSV2,
106    /// GBFS Versions
107    pub gbfs_versions: Option<GBFSVersionsV2>,
108    /// Geofencing
109    pub geofencing_zones: Option<GBFSGeofencingZonesV2>,
110    /// Station Information
111    pub station_information: Option<GBFSStationInformationV2>,
112    /// Station Status
113    pub station_status: Option<GBFSStationStatusV2>,
114    /// System Alerts
115    pub system_alerts: Option<GBFSSystemAlertsV2>,
116    /// System Calendar
117    pub system_calendar: Option<GBFSSystemCalendarV2>,
118    /// System Hours
119    pub system_hours: Option<GBFSSystemHoursV2>,
120    /// System Information
121    pub system_information: GBFSSystemInformationV2,
122    /// System Pricing Plans
123    pub system_pricing_plans: Option<GBFSSystemPricingPlansV2>,
124    /// System Regions
125    pub system_regions: Option<GBFSSystemRegionsV2>,
126    /// Vehicle Types
127    pub vehicle_types: Option<GBFSVehicleTypesV2>,
128}
129impl GBFSReaderV2 {
130    /// Get all features from the GBFS V2 data
131    pub fn features(&self) -> Vec<VectorFeature> {
132        let mut res = vec![];
133
134        res.extend(self.station_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
135        res.extend(self.bike_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
136        res.extend(self.geofencing_features().iter().map(|f| f.to_m_vector_feature(|_| None)));
137
138        res
139    }
140
141    /// Get all station features from the GBFS V2 data
142    pub fn station_features(&self) -> Vec<GBFSStationFeatureV2> {
143        let mut res = vec![];
144
145        if let Some(station_information) = &self.station_information {
146            for station in &station_information.data.stations {
147                let properties = GBFSStationV2FeaturesProperties::from(station);
148                res.push(VectorFeature::new_wm(
149                    None,
150                    properties.clone(),
151                    VectorGeometry::new_point(VectorPoint::from_xy(station.lon, station.lat), None),
152                    None,
153                ));
154                if let Some(station_area) = &station.station_area {
155                    res.push(VectorFeature::new_wm(
156                        None,
157                        properties,
158                        convert_geometry_to_vector(station_area, true),
159                        None,
160                    ));
161                }
162            }
163        }
164
165        res
166    }
167
168    /// Get Geofencing features from the GBFS V2 data
169    pub fn geofencing_features(&self) -> Vec<GBFSGeofencingFeatureV2> {
170        let mut res = vec![];
171
172        if let Some(geofencing_zones) = &self.geofencing_zones {
173            for feature in &geofencing_zones.data.geofencing_zones.features {
174                match feature {
175                    Features::Feature(f) => {
176                        res.push(f.to_vector(Some(true)));
177                    }
178                    Features::VectorFeature(vf) => res.push(vf.clone()),
179                }
180            }
181        }
182
183        res
184    }
185
186    /// Get all bike features from the GBFS V2 data
187    pub fn bike_features(&self) -> Vec<GBFSBikeFeatureV2> {
188        let mut res = vec![];
189
190        if let Some(free_bike_status) = &self.free_bike_status {
191            for bike in &free_bike_status.data.bikes {
192                if let (Some(lon), Some(lat)) = (bike.lon, bike.lat) {
193                    res.push(VectorFeature::new_wm(
194                        None,
195                        bike.clone(),
196                        VectorGeometry::new_point(VectorPoint::from_xy(lon, lat), None),
197                        None,
198                    ));
199                }
200            }
201        }
202
203        res
204    }
205}
206
207/// The GBFS V1 Iterator tool
208#[derive(Debug)]
209pub struct GBFSIteratorV2 {
210    features: Vec<VectorFeature>,
211    index: usize,
212    len: usize,
213}
214impl Iterator for GBFSIteratorV2 {
215    type Item = VectorFeature;
216
217    fn next(&mut self) -> Option<Self::Item> {
218        if self.index >= self.len {
219            return None;
220        }
221        self.index += 1;
222        self.features.get(self.index - 1).cloned()
223    }
224}
225/// A feature reader trait with a callback-based approach
226impl FeatureReader<(), Properties, MValue> for GBFSReaderV2 {
227    type FeatureIterator<'a> = GBFSIteratorV2;
228
229    fn iter(&self) -> Self::FeatureIterator<'_> {
230        let features = self.features();
231        let len = features.len();
232        GBFSIteratorV2 { features, index: 0, len }
233    }
234
235    fn par_iter(&self, pool_size: usize, thread_id: usize) -> Self::FeatureIterator<'_> {
236        let features = self.features();
237        let start = features.len() * thread_id / pool_size;
238        let end = features.len() * (thread_id + 1) / pool_size;
239        GBFSIteratorV2 { features, index: start, len: end }
240    }
241}
242
243/// Parse a GBFS V2 schema and build a V2 GBFS reader
244///
245/// ## Parameters
246/// - `gbfs`: the GBFS schema to parse
247/// - `locale`: the locale to use if provided, otherwise default to en
248/// - `path`: if provided, will use this path instead of the url (for testing)
249///
250/// ## Returns
251/// The GBFS reader
252pub async fn build_gbfs_reader_v2(
253    gbfs: &GBFSV2,
254    locale: Option<String>,
255    path: Option<String>,
256) -> GBFSReaderV2 {
257    let locale = locale.unwrap_or("en".into());
258    let data = &gbfs.data;
259    let first_locale = data.keys().next().unwrap();
260    let feeds = data.get(&locale).unwrap_or(data.get(first_locale).unwrap());
261    let feeds = feeds.feeds.clone();
262
263    let mut reader = GBFSReaderV2 { gbfs: gbfs.clone(), ..Default::default() };
264
265    for feed in feeds {
266        let name = serde_json::to_string(&feed.name).unwrap();
267        if &name == "gbfs" {
268            continue;
269        }
270        let url = if let Some(ref path) = path {
271            format!("{}/{}.json", path, name.trim_matches('"'))
272        } else {
273            feed.url
274        };
275
276        if let Ok(url_data) = fetch_url::<()>(&url, &[], None, None).await {
277            match feed.name {
278                GBFSV21FeedsName::FreeBikeStatus => {
279                    reader.free_bike_status = Some(serde_json::from_slice(&url_data).unwrap());
280                }
281                GBFSV21FeedsName::Gbfs => {}
282                GBFSV21FeedsName::GbfsVersions => {
283                    reader.gbfs_versions = Some(serde_json::from_slice(&url_data).unwrap());
284                }
285                GBFSV21FeedsName::StationInformation => {
286                    reader.station_information = Some(serde_json::from_slice(&url_data).unwrap());
287                }
288                GBFSV21FeedsName::StationStatus => {
289                    reader.station_status = Some(serde_json::from_slice(&url_data).unwrap());
290                }
291                GBFSV21FeedsName::SystemAlerts => {
292                    reader.system_alerts = Some(serde_json::from_slice(&url_data).unwrap());
293                }
294                GBFSV21FeedsName::SystemCalendar => {
295                    reader.system_calendar = Some(serde_json::from_slice(&url_data).unwrap());
296                }
297                GBFSV21FeedsName::SystemHours => {
298                    reader.system_hours = Some(serde_json::from_slice(&url_data).unwrap());
299                }
300                GBFSV21FeedsName::SystemInformation => {
301                    reader.system_information = serde_json::from_slice(&url_data).unwrap();
302                }
303                GBFSV21FeedsName::SystemPricingPlans => {
304                    reader.system_pricing_plans = Some(serde_json::from_slice(&url_data).unwrap());
305                }
306                GBFSV21FeedsName::SystemRegions => {
307                    reader.system_regions = Some(serde_json::from_slice(&url_data).unwrap());
308                }
309                GBFSV21FeedsName::VehicleTypes => {
310                    reader.vehicle_types = Some(serde_json::from_slice(&url_data).unwrap());
311                }
312                GBFSV21FeedsName::GeofencingZones => {
313                    reader.geofencing_zones = Some(serde_json::from_slice(&url_data).unwrap());
314                }
315            }
316        }
317    }
318
319    reader
320}