Skip to content

Commit

Permalink
Added ability to use other countries and subset locations (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgecardleitao authored Jan 27, 2024
1 parent 428d9fc commit 2f80521
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 64 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ As of today, the flag `--azure-sas-token` is only available when the code is exe
from `main`, as writing to the blob storage must be done through a controlled code base
that preserves data integrity.

### Examples:

```bash
# Story about Danish private jets that flew to Davos between two dates
cargo run --example country -- --from=2024-01-13 --to=2024-01-21 --country=denmark --location=davos
# Story about Danish private jets that flew between two dates
cargo run --example country -- --from=2024-01-13 --to=2024-01-21 --country=denmark
# Story about Portuguese private jets that flew between two dates
cargo run --example country -- --from=2024-01-13 --to=2024-01-21 --country=portugal
```

## Methodology

The methodology used to extract information is available at [`methodology.md`](./methodology.md).
Expand Down
27 changes: 27 additions & 0 deletions examples/country.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use of {country.possessive} private jets{location}

Between {from_date} and {to_date}, {country.possessive} private jets{location} emitted
{emissions_tons.claim} tons of CO2e[^1], or what **{citizen_years.claim} {country.plural}** emit in a year[^2].
{number_of_private_jets.claim} private jets alone[^3] jeopardize the efforts of millions
of {country.plural} that every day act to reduce their emissions.

Of these,
* {number_of_legs_less_300km} trips were less than 300 km[^4] and could have been replaced by
a limusine or first class train ticket, which would have emitted ~17x less CO2[^5].
* {number_of_legs_more_300km} trips[^4] could have been replaced by
a first class ticket on a commercial aircraft, which would have emitted
~{ratio_commercial_300km}x less CO2[^1].

The use of private jets by billionaires and large companies alike is an insult
to {country.plural} and {country.possessive} companies alike that are doing everything they can
to reduce emissions.

> Ban private jets _now_
## References

[^1]: {emissions_tons.source}, in {number_of_legs.claim} trips[^4] - retrieved on {emissions_tons.date}
[^2]: {citizen_years.source} - retrieved on {citizen_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}
[^5]: 10x vs a commercial flight[^1] plus 7x vs a train, as per https://ourworldindata.org/travel-carbon-footprint (UK data, vary by country) - retrieved on 2024-01-20
180 changes: 147 additions & 33 deletions examples/dk_jets.rs → examples/country.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ use clap::Parser;
use num_format::{Locale, ToFormattedString};
use simple_logger::SimpleLogger;

use flights::{emissions, load_aircraft_types, load_aircrafts, Class, Fact, Leg};
use flights::{emissions, load_aircrafts, load_private_jet_types, Class, Fact, Leg, Position};
use time::Date;

fn render(context: &Context) -> Result<(), Box<dyn Error>> {
let path = "all_dk_jets.md";
let path = format!("{}_story.md", context.country.name.to_lowercase());

let template = std::fs::read_to_string("examples/dk_jets.md")?;
let template = std::fs::read_to_string("examples/country.md")?;

let mut tt = tinytemplate::TinyTemplate::new();
tt.set_default_formatter(&tinytemplate::format_unescaped);
Expand All @@ -23,14 +23,23 @@ fn render(context: &Context) -> Result<(), Box<dyn Error>> {
Ok(())
}

#[derive(serde::Serialize)]
pub struct CountryContext {
pub name: String,
pub plural: String,
pub possessive: String,
}

#[derive(serde::Serialize)]
pub struct Context {
pub country: CountryContext,
pub location: String,
pub from_date: String,
pub to_date: String,
pub number_of_private_jets: Fact<String>,
pub number_of_legs: Fact<String>,
pub emissions_tons: Fact<String>,
pub dane_years: Fact<String>,
pub citizen_years: Fact<String>,
pub number_of_legs_less_300km: String,
pub number_of_legs_more_300km: String,
pub ratio_commercial_300km: String,
Expand All @@ -49,27 +58,119 @@ fn parse_date(arg: &str) -> Result<time::Date, time::error::Parse> {
)
}

#[derive(clap::ValueEnum, Debug, Clone)]
enum Country {
Denmark,
Portugal,
}

#[derive(clap::ValueEnum, Debug, Clone, Copy)]
enum Location {
Davos,
}

impl Location {
fn name(&self) -> &'static str {
match self {
Self::Davos => "Davos airport (LSZR)",
}
}

fn region(&self) -> [[f64; 2]; 2] {
match self {
Self::Davos => [[47.482, 9.538], [47.490, 9.568]],
}
}
}

impl Country {
fn to_context(&self) -> CountryContext {
CountryContext {
name: self.name().to_string(),
plural: self.plural().to_string(),
possessive: self.possessive().to_string(),
}
}

fn possessive(&self) -> &'static str {
match self {
Self::Denmark => "Danish",
Self::Portugal => "Portuguese",
}
}

fn plural(&self) -> &'static str {
match self {
Self::Denmark => "Danes",
Self::Portugal => "Portugueses",
}
}

fn tail_number(&self) -> &'static str {
match self {
Country::Denmark => "OY-",
Country::Portugal => "CS-",
}
}

fn name(&self) -> &'static str {
match self {
Country::Denmark => "Denmark",
Country::Portugal => "Portugal",
}
}

fn emissions(&self) -> Fact<f64> {
match self {
Country::Denmark => Fact {
claim: 5.1,
source: "A dane emitted 5.1 t CO2/person/year in 2019 according to [work bank data](https://ourworldindata.org/co2/country/denmark).".to_string(),
date: "2023-10-08".to_string(),
},
Country::Portugal => Fact {
claim: 4.1,
source: "A portuguese emitted 4.1 t CO2/person/year in 2022 according to [work bank data](https://ourworldindata.org/co2/country/denmark).".to_string(),
date: "2024-01-23".to_string(),
},
}
}
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// The Azure token
#[arg(short, long)]
#[arg(long)]
azure_sas_token: Option<String>,
#[arg(short, long, value_enum, default_value_t=Backend::Azure)]
#[arg(long, value_enum, default_value_t=Backend::Azure)]
backend: Backend,

/// Name of the country to compute on
#[arg(long)]
country: Country,

/// A date in format `yyyy-mm-dd`
#[arg(long, value_parser = parse_date)]
from: time::Date,
/// Optional end date in format `yyyy-mm-dd` (else it is to today)
#[arg(long, value_parser = parse_date)]
to: Option<time::Date>,

/// Optional location to restrict the search geographically. Currently only
#[arg(long)]
location: Option<Location>,
}

pub fn in_box(position: &Position, region: [[f64; 2]; 2]) -> bool {
return (position.latitude() >= region[0][0] && position.latitude() < region[1][0])
&& (position.longitude() >= region[0][1] && position.longitude() < region[1][1]);
}

async fn legs(
from: Date,
to: Date,
icao_number: &str,
location: Option<Location>,
client: Option<&flights::fs_azure::ContainerClient>,
) -> Result<Vec<Leg>, Box<dyn Error>> {
let positions = flights::aircraft_positions(from, to, icao_number, client).await?;
Expand All @@ -81,7 +182,18 @@ async fn legs(
positions.sort_unstable_by_key(|p| p.datetime());

log::info!("Computing legs {}", icao_number);
Ok(flights::legs(positions.into_iter()))
let legs = flights::legs(positions.into_iter());

// filter by location
if let Some(location) = location {
let region = location.region();
Ok(legs
.into_iter()
.filter(|leg| leg.positions().iter().any(|p| in_box(p, region)))
.collect())
} else {
Ok(legs)
}
}

#[tokio::main]
Expand Down Expand Up @@ -109,40 +221,41 @@ async fn main() -> Result<(), Box<dyn Error>> {

// load datasets to memory
let aircrafts = load_aircrafts(client.as_ref()).await?;
let types = load_aircraft_types()?;
let types = load_private_jet_types()?;

let private_jets = aircrafts
.into_iter()
// is private jet
.filter(|(_, a)| types.contains_key(&a.model))
// is from DK
.filter(|(a, _)| a.starts_with("OY-"))
// from country
.filter(|(a, _)| a.starts_with(cli.country.tail_number()))
.collect::<HashMap<_, _>>();

let number_of_private_jets = Fact {
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-\""
),
date: "2023-11-06".to_string(),
};

let now = time::OffsetDateTime::now_utc().date();
let from = cli.from;
let to = cli.to.unwrap_or(time::OffsetDateTime::now_utc().date());
let to = cli.to.unwrap_or(now);

let from_date = from.to_string();
let to_date = to.to_string();

let client = client.as_ref();
let legs = private_jets.iter().map(|(_, aircraft)| async {
legs(from, to, &aircraft.icao_number, client)
legs(from, to, &aircraft.icao_number, cli.location, client)
.await
.map(|legs| (aircraft.icao_number.clone(), legs))
});

let legs = futures::future::join_all(legs).await;
let legs = legs.into_iter().collect::<Result<HashMap<_, _>, _>>()?;

let number_of_private_jets = Fact {
claim: legs.iter().filter(|x| x.1.len() > 0).count().to_formatted_string(&Locale::en),
source: format!(
"All aircrafts in [adsbexchange.com](https://globe.adsbexchange.com) whose model is a private jet, registered in {}, and with at least one leg - ", cli.country.name()
),
date: "2023-11-06".to_string(),
};

let number_of_legs = Fact {
claim: legs
.iter()
Expand All @@ -152,7 +265,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
source: format!(
"[adsbexchange.com](https://globe.adsbexchange.com) between {from_date} and {to_date}"
),
date: to.to_string(),
date: now.to_string(),
};

let commercial_emissions_tons = legs
Expand Down Expand Up @@ -180,26 +293,27 @@ async fn main() -> Result<(), Box<dyn Error>> {
.map(|(_, legs)| legs.iter().filter(|leg| leg.distance() >= 300.0).count())
.sum::<usize>();

let dane_emissions_tons = Fact {
claim: 5.1,
source: "A dane emitted 5.1 t CO2/person/year in 2019 according to [work bank data](https://ourworldindata.org/co2/country/denmark).".to_string(),
date: "2023-10-08".to_string(),
};

let dane_years = (emissions_tons_value / dane_emissions_tons.claim) as usize;
let dane_years = Fact {
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(),
let citizen_emissions_tons = cli.country.emissions();

let citizen_years = (emissions_tons_value / citizen_emissions_tons.claim) as usize;
let citizen_years = Fact {
claim: citizen_years.to_formatted_string(&Locale::en),
source: citizen_emissions_tons.source,
date: citizen_emissions_tons.date,
};

let context = Context {
country: cli.country.to_context(),
location: cli
.location
.map(|l| format!(" at {}", l.name()))
.unwrap_or_default(),
from_date,
to_date,
number_of_private_jets,
number_of_legs,
emissions_tons,
dane_years,
citizen_years,
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),
Expand Down
26 changes: 0 additions & 26 deletions examples/dk_jets.md

This file was deleted.

4 changes: 2 additions & 2 deletions examples/export_private_jets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use clap::Parser;
use simple_logger::SimpleLogger;

use flights::BlobStorageProvider;
use flights::{load_aircraft_types, load_aircrafts};
use flights::{load_aircrafts, load_private_jet_types};

#[derive(clap::ValueEnum, Debug, Clone)]
enum Backend {
Expand Down Expand Up @@ -52,7 +52,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

// load datasets to memory
let aircrafts = load_aircrafts(client.as_ref()).await?;
let types = load_aircraft_types()?;
let types = load_private_jet_types()?;

let private_jets = aircrafts
.values()
Expand Down
Loading

0 comments on commit 2f80521

Please sign in to comment.