Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to use other countries and subset locations #18

Merged
merged 2 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading