diff --git a/Cargo.toml b/Cargo.toml index fda8cd0..5808a3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,10 @@ geoutils = {version="*", default_features = false} # read airport names csv = {version="*", default_features = false} -# +# async utilities async-trait = "*" +async-recursion = "1.0" +futures = "0.3" # logging log = "*" @@ -35,12 +37,11 @@ log = "*" azure_storage = "*" azure_storage_blobs = "*" azure_core = "*" -futures = "0.3" bytes = "1.5" -async-recursion = "1.0" [dev-dependencies] tinytemplate = "1.1" clap = { version = "4.4.6", features = ["derive"] } tokio = {version="1.0", features=["rt", "macros", "rt-multi-thread"]} simple_logger = "*" +num-format = "*" diff --git a/examples/dk_jets.md b/examples/dk_jets.md index 1db713c..7be322a 100644 --- a/examples/dk_jets.md +++ b/examples/dk_jets.md @@ -1,9 +1,9 @@ # Use of Danish private jets -{from_date} to {to_date}, Danish private jets emitted -{emissions_tons.claim} tons of CO2e[^1], the equivalent of what **{dane_years.claim} Danes** -emit in a year[^2]. All of this was done by only {number_of_private_jets.claim} private jets[^3] -in {number_of_legs.claim} trips[^4]. +{from_date} to {to_date}, all Danish private jets emitted +{emissions_tons.claim} tons of CO2e[^1], or what **{dane_years.claim} Danes** emit in a year[^2]. +{number_of_private_jets.claim} private jets alone[^3] jeopardize the efforts of millions +of Danes that every day act to reduce their emissions. Of these, * {number_of_legs_less_300km} trips were less than 300 km and could have been replaced by @@ -12,15 +12,15 @@ Of these, a first class ticket on a commercial aircraft, which would have emitted ~{ratio_commercial_300km}x less CO2[^5]. -Billionaires and companies alike are incapable of regulating their emissions, -recklessly destroying the common good. -Ban private jets now and until they emit what equivalent means of transportation would emit. +The use of private jets by billionaires and large companies alike is an insult +to Danes and Danish companies alike that are doing everything they can to +keep for our future. + +> Ban private jets _now_ ## References -[^1]: {emissions_tons.source} - retrieved on {emissions_tons.date} +[^1]: {emissions_tons.source}, in {number_of_legs.claim} trips[^4] - retrieved on {emissions_tons.date} [^2]: {dane_years.source} - retrieved on {dane_years.date} [^3]: {number_of_private_jets.source} on {number_of_private_jets.date} [^4]: {number_of_legs.source} - retrieved on {number_of_legs.date} - -Copyright Jorge Leitão, released under [CC0](https://creativecommons.org/public-domain/cc0/) - No Rights Reserved. diff --git a/examples/dk_jets.rs b/examples/dk_jets.rs index b0ecb41..29c976b 100644 --- a/examples/dk_jets.rs +++ b/examples/dk_jets.rs @@ -1,11 +1,12 @@ -use std::{collections::HashMap, error::Error, sync::Arc}; +use std::{collections::HashMap, error::Error}; use clap::Parser; use futures::{StreamExt, TryStreamExt}; +use num_format::{Locale, ToFormattedString}; use simple_logger::SimpleLogger; -use flights::{emissions, load_aircraft_types, load_aircrafts, Class, Fact, Position}; -use time::macros::date; +use flights::{emissions, load_aircraft_types, load_aircrafts, Aircraft, Class, Fact, Leg}; +use time::{macros::date, Date}; fn render(context: &Context) -> Result<(), Box> { let path = "all_dk_jets.md"; @@ -27,12 +28,12 @@ fn render(context: &Context) -> Result<(), Box> { pub struct Context { pub from_date: String, pub to_date: String, - pub number_of_private_jets: Fact, - pub number_of_legs: Fact, - pub emissions_tons: Fact, + pub number_of_private_jets: Fact, + pub number_of_legs: Fact, + pub emissions_tons: Fact, pub dane_years: Fact, - pub number_of_legs_less_300km: usize, - pub number_of_legs_more_300km: usize, + pub number_of_legs_less_300km: String, + pub number_of_legs_more_300km: String, pub ratio_commercial_300km: String, } @@ -52,6 +53,35 @@ struct Cli { backend: Backend, } +async fn legs( + from: Date, + to: Date, + aircraft: &Aircraft, + client: Option<&flights::fs_azure::ContainerClient>, +) -> Result, Box> { + let dates = flights::DateIter { + from, + to, + increment: time::Duration::days(1), + }; + + let tasks = dates.map(|date| async move { + Result::<_, Box>::Ok( + flights::positions(&aircraft.icao_number, date, 1000.0, client) + .await? + .collect::>(), + ) + }); + + let positions = futures::stream::iter(tasks) + // limit concurrent tasks + .buffered(50) + .try_collect::>() + .await?; + + Ok(flights::real_legs(positions.into_iter().flatten())) +} + #[tokio::main] async fn main() -> Result<(), Box> { SimpleLogger::new() @@ -88,7 +118,7 @@ async fn main() -> Result<(), Box> { .collect::>(); let number_of_private_jets = Fact { - claim: private_jets.len(), + claim: private_jets.len().to_formatted_string(&Locale::en), source: format!( "All aircrafts in [adsbexchange.com](https://globe.adsbexchange.com) whose model is a private jet and tail number starts with \"OY-\"" ), @@ -101,56 +131,28 @@ async fn main() -> Result<(), Box> { let from_date = from.to_string(); let to_date = to.to_string(); - let dates = flights::DateIter { - from, - to, - increment: time::Duration::days(1), - }; - - let iter = dates - .map(|date| { - let client = client.as_ref(); - private_jets - .iter() - .map(move |(_, a)| flights::positions(&a.icao_number, date.clone(), 1000.0, client)) - }) - .flatten(); - - let positions = futures::stream::iter(iter) - // limit to 5 concurrent tasks - .buffer_unordered(100) - .try_collect::>() - .await?; - let positions = positions.into_iter().flatten().collect::>(); - - // group by aircraft - let mut positions = positions.into_iter().fold( - HashMap::, Vec>::default(), - |mut acc, v| { - acc.entry(v.icao().clone()) - .and_modify(|positions| positions.push(v.clone())) - .or_insert_with(|| vec![v]); - acc - }, - ); - // sort positions by datetime - positions.iter_mut().for_each(|(_, positions)| { - positions.sort_unstable_by_key(|x| x.datetime()); + let client = client.as_ref(); + let legs = private_jets.iter().map(|(_, aircraft)| async { + legs(from, to, aircraft, client) + .await + .map(|legs| (aircraft.icao_number.clone(), legs)) }); - // compute legs - let legs = positions - .into_iter() - .map(|(icao, positions)| (icao, flights::real_legs(positions.into_iter()))) - .collect::>(); + let legs = futures::future::join_all(legs).await; + let legs = legs.into_iter().collect::, _>>()?; let number_of_legs = Fact { - claim: legs.iter().map(|(_, legs)| legs.len()).sum::(), - source: format!("[adsbexchange.com](https://globe.adsbexchange.com) between {from_date} and {to_date} and all aircraft whose tail number starts with \"OY-\" and model is a private jet"), - date: to.to_string() + claim: legs + .iter() + .map(|(_, legs)| legs.len()) + .sum::() + .to_formatted_string(&Locale::en), + source: format!( + "[adsbexchange.com](https://globe.adsbexchange.com) between {from_date} and {to_date}" + ), + date: to.to_string(), }; - let commercial_to_private_ratio = 10.0; let commercial_emissions_tons = legs .iter() .map(|(_, legs)| { @@ -159,8 +161,10 @@ async fn main() -> Result<(), Box> { .sum::() }) .sum::(); + let commercial_to_private_ratio = 10.0; + let emissions_tons_value = commercial_emissions_tons * commercial_to_private_ratio; let emissions_tons = Fact { - claim: (commercial_emissions_tons * commercial_to_private_ratio) as usize, + claim: (emissions_tons_value as usize).to_formatted_string(&Locale::en), source: format!("Commercial flights would have emitted {commercial_emissions_tons:.1} tons of CO2e (based on [myclimate.org](https://www.myclimate.org/en/information/about-myclimate/downloads/flight-emission-calculator/) - retrieved on 2023-10-19). Private jets emit 5-14x times. 10x was used based on [transportenvironment.org](https://www.transportenvironment.org/discover/private-jets-can-the-super-rich-supercharge-zero-emission-aviation/)"), date: "2023-10-05, from 2021-05-27".to_string(), }; @@ -180,12 +184,9 @@ async fn main() -> Result<(), Box> { date: "2023-10-08".to_string(), }; - let dane_years = format!( - "{:.0}", - emissions_tons.claim as f32 / dane_emissions_tons.claim as f32 - ); + let dane_years = (emissions_tons_value / dane_emissions_tons.claim) as usize; let dane_years = Fact { - claim: dane_years, + claim: dane_years.to_formatted_string(&Locale::en), source: "https://ourworldindata.org/co2/country/denmark Denmark emits 5.1 t CO2/person/year in 2019.".to_string(), date: "2023-10-08".to_string(), }; @@ -197,8 +198,8 @@ async fn main() -> Result<(), Box> { number_of_legs, emissions_tons, dane_years, - number_of_legs_less_300km: short_legs, - number_of_legs_more_300km: long_legs, + number_of_legs_less_300km: short_legs.to_formatted_string(&Locale::en), + number_of_legs_more_300km: long_legs.to_formatted_string(&Locale::en), ratio_commercial_300km: format!("{:.0}", commercial_to_private_ratio), }; diff --git a/src/lib.rs b/src/lib.rs index 1da58f7..ed87b2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ fn distance(from: (f64, f64), to: (f64, f64)) -> f64 { } /// An iterator between two [`time::Date`]s in increments +#[derive(Clone, Copy)] pub struct DateIter { pub from: time::Date, pub to: time::Date,