Last active
October 8, 2019 08:21
-
-
Save SeanDS/4e7c8f532c464f64a5a6817c641341ca to your computer and use it in GitHub Desktop.
Carbon emissions calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Quick and dirty carbon emissions calculation. | |
# | |
# See https://attackllama.com/carbon/. | |
# | |
# Requires Python 3.6+. | |
# Non-core dependencies: requests, numpy, pandas, matplotlib, pytablewriter. | |
# | |
# Note that the input text file "flights.txt" has a "distance" column. This is | |
# obtained manually using online flight distance calculators, and is simply for | |
# statistics. If you don't want to use this data, set each row in this column | |
# to zero. | |
# | |
# | |
# Sean Leavey | |
# <[email protected]> | |
from collections import defaultdict | |
import csv | |
import requests | |
import numpy as np | |
import pandas as pd | |
import matplotlib.pyplot as plt | |
from pytablewriter import RstGridTableWriter | |
API_KEY = "" # get from goclimateneutral.org | |
API_BASE = 'https://api.goclimateneutral.org' | |
# https://trees.org/carboncalculator/, 15.6943 kg/year, assumes 40 year life | |
CO2_KG_ABSORBED_PER_TREE = 15.6943 * 40 | |
def get_carbon(): | |
co2total = 0 | |
nflights = 0 | |
dtotal = 0 | |
fdata = [] | |
headers = ["Year", "From", "To", "Via 1", "Via 2", "Is Return", "Distance (km)", "Footprint (CO2 kg)", "Notes"] | |
with open('flights.txt') as csvfile: | |
reader = csv.reader(csvfile, delimiter='\t') | |
# Skip header. | |
next(reader) | |
for row in reader: | |
(year, airport_from, airport_to, airport_via1, airport_via2, is_return, | |
distance, notes) = row | |
# Fix types. | |
is_return = True if is_return.lower() == "yes" else False | |
distance = float(distance) | |
nflight = 1 | |
payload = { | |
'segments': {}, | |
'cabin_class': 'economy', | |
'currencies[]': 'EUR', | |
} | |
if airport_via1 != "": | |
nflight += 1 | |
payload["segments[0][origin]"] = airport_from | |
payload["segments[0][destination]"] = airport_via1 | |
if airport_via2 != "": | |
nflight += 1 | |
payload["segments[1][origin]"] = airport_via1 | |
payload["segments[1][destination]"] = airport_via2 | |
payload["segments[2][origin]"] = airport_via2 | |
payload["segments[2][destination]"] = airport_to | |
else: | |
payload["segments[1][origin]"] = airport_via1 | |
payload["segments[1][destination]"] = airport_to | |
else: | |
payload["segments[0][origin]"] = airport_from | |
payload["segments[0][destination]"] = airport_to | |
# Get data. | |
response = requests.get(API_BASE + '/v1/flight_footprint', | |
params=payload, | |
auth=(API_KEY, '')) | |
if response.status_code != 200: | |
raise Exception(f"invalid response: {response.text}") | |
data = response.json() | |
scale = 2 if is_return else 1 | |
footprint = scale * float(data["footprint"]) | |
co2total += footprint | |
if is_return: | |
nflight *= 2 | |
nflights += nflight | |
dtotal += distance | |
# Create new data | |
fdata.append( | |
(year, airport_from, airport_to, airport_via1, airport_via2, | |
is_return, distance, footprint, notes) | |
) | |
with open('carbon.csv', 'w') as csvfile: | |
writer = csv.writer(csvfile, delimiter='\t', quotechar='|', | |
quoting=csv.QUOTE_MINIMAL) | |
writer.writerow(headers) | |
writer.writerows(fdata) | |
print(f"Number of flights: {nflights}") | |
print(f"Total distance: {dtotal} km") | |
print(f"Total CO2: {co2total} kg") | |
def load_carbon(): | |
return pd.read_csv('carbon.csv', sep='\t') | |
def to_rst_table(): | |
table = load_carbon() | |
table = table[["Year", "From", "To", "Is Return", "Distance (km)", "Footprint (CO2 kg)"]] | |
writer = RstGridTableWriter() | |
writer.from_dataframe(table) | |
writer.write_table() | |
def plot_timeline(): | |
data = load_carbon() | |
subdata = data[["Year", "Distance (km)", "Footprint (CO2 kg)"]] | |
subsum = subdata.groupby(by=["Year"]).sum().cumsum() | |
distance = subsum["Distance (km)"] | |
earths = distance / 40075.017 | |
earths.rename("Earths") | |
footprint = subsum["Footprint (CO2 kg)"] | |
footprint_tonnes = footprint / 1000 | |
footprint_tonnes.rename("Footprint (CO2 tonnes)") | |
trees = footprint / CO2_KG_ABSORBED_PER_TREE | |
ax1 = distance.plot() | |
ax1.ticklabel_format(axis='y', style='sci', scilimits=(3,3)) | |
lines1, labels1 = ax1.get_legend_handles_labels() | |
ax2 = ax1.twinx() | |
earths.plot(ax=ax2, alpha=0) | |
ax2.spines['left'].set_position(('axes', -0.22)) | |
ax2.spines["left"].set_visible(True) | |
ax2.yaxis.set_label_position('left') | |
ax2.yaxis.set_ticks_position('left') | |
ax3 = ax1.twinx() | |
footprint_tonnes.plot(ax=ax3, color="orange") | |
ax3.spines['right'].set_position(('axes', 1.0)) | |
lines2, labels2 = ax3.get_legend_handles_labels() | |
ax4 = ax1.twinx() | |
trees.plot(ax=ax4, alpha=0) | |
ax4.spines['right'].set_position(('axes', 1.2)) | |
ax1.grid(True) | |
fig = plt.gcf() | |
ax1.legend(lines1+lines2, ["Distance", "Footprint"]) | |
ax1.set_ylabel('Distance (km)') | |
ax2.set_ylabel('Earth circumnavigations') | |
ax3.set_ylabel('Footprint (tonnes CO2)') | |
ax4.set_ylabel('Required trees') | |
ax1.set_title("Lifetime flight footprint") | |
#fig.autofmt_xdate() | |
fig.tight_layout() | |
fig.savefig("carbon.png") | |
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Year From To Via 1 Via 2 Return? Distance (km) Notes | |
1991 GLA YYZ Yes 10600 | |
1993 GLA YYZ Yes 10600 | |
1995 GLA YYZ Yes 10600 | |
1998 GLA BJV Yes 6300 | |
1998 GLA YYZ Yes 10600 | |
1998 YYZ YFC Yes 2100 | |
1999 GLA JSI Yes 5500 | |
2000 GLA LHR Yes 1100 | |
2000 GLA YYZ Yes 10600 | |
2001 GLA BCN Yes 3400 | |
2002 GLA GVA Yes 2600 | |
2003 GLA YYZ Yes 10600 | |
2006 MAN BVA Yes 1800 | |
2006 EDI FNC Yes 5500 | |
2007 GLA YYZ Yes 10600 | |
2009 GLA FNC Yes 5500 | |
2010 GLA YYC Yes 13000 | |
2010 EDI MXP LHR No 1500 | |
2010 AMS EDI No 700 | |
2011 GLA MAD Yes 3400 | |
2011 EDI MUC Yes 2700 | |
2012 GLA LHR Yes 1100 | |
2012 HAJ GLA LHR Yes 2500 | |
2013 GLA FNC LHR Yes 6100 | |
2013 GLA WAW Yes 3300 | |
2013 EDI MUC Yes 2700 | |
2013 GLA HAJ LHR No 1250 | |
2013 HAJ GLA LHR Yes 2500 | |
2014 HAJ MUC Yes 1000 | |
2014 HAJ GLA LHR No 1250 | |
2014 GLA SFO BGR No 9000 | |
2014 LAX MAN PHL No 9400 | |
2014 GLA HAJ LHR Yes 2500 | |
2014 GLA LYS AMS Yes 2900 | |
2015 GLA HAJ LHR Yes 2500 | |
2015 GLA ANC LHR SEA Yes 21200 | |
2015 EDI BRE No 800 | |
2015 FRA GLA No 1100 | |
2015 GLA HAJ LHR Yes 2500 | |
2016 GLA BLQ No 1700 | |
2016 FLR GLA No 1700 | |
2016 EDI FNC LIS No 2900 | |
2016 FNC EDI No 2800 | |
2016 GLA LAX LHR Yes 18600 | |
2016 GLA PMI Yes 3800 | |
2016 GLA IAD LHR No 6500 | |
2016 JFK GLA LHR No 6100 | |
2017 GLA XRY MAD Yes 4400 | |
2017 GLA FRA No 1100 | |
2017 TXL GLA No 1200 | |
2017 GLA HAJ LHR Yes 2500 | |
2017 GLA HAJ LHR No 1250 | |
2017 HAJ GLA LHR Yes 2500 | |
2017 HAJ GLA LHR Yes 2500 | |
2018 HAJ LHR Yes 1400 | |
2018 HAJ FNC Yes 6100 | |
2018 HAJ GLA LHR Yes 2500 | |
2018 HAJ PMI Yes 3100 | |
2018 HAJ GLA LHR Yes 2500 | |
2019 HAJ GLA LHR Yes 2500 | |
2019 HAJ FCO FRA No 1200 | |
2019 FCO HAJ MUC No 1200 | |
2019 HAJ GLA LHR Yes 2500 | |
2019 HAJ LHR Yes 1400 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment