conference_footprint

compute the CO2 footprint of an academic conference
git clone https://a3nm.net/git/conference_footprint/
Log | Files | Refs

commit 75ea1d1d52247755eafe6cc791ec2727655a101b
parent 5603b487228061a85a837c3fb2b503f84dd8f554
Author: Antoine Amarilli <a3nm@a3nm.net>
Date:   Fri,  8 Nov 2024 12:31:03 +0100

2023 scripts by León Bohn

Diffstat:
2023/.gitignore | 5+++++
2023/LICENSE | 18++++++++++++++++++
2023/README.md | 3+++
2023/addnoise.py | 14++++++++++++++
2023/co2.py | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2023/compute_trips.py | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2023/run.sh | 17+++++++++++++++++
2023/transform.py | 31+++++++++++++++++++++++++++++++
2023/trips_anonymized.csv | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 513 insertions(+), 0 deletions(-)

diff --git a/2023/.gitignore b/2023/.gitignore @@ -0,0 +1,5 @@ +trips +trips_with_footprint +trips_with_dist +map.geojson +registrations*.csv diff --git a/2023/LICENSE b/2023/LICENSE @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/2023/README.md b/2023/README.md @@ -0,0 +1,3 @@ +This code computed the carbon footprint of the Highlights'23 conference. It was +written by León Bohn. + diff --git a/2023/addnoise.py b/2023/addnoise.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys +from random import uniform + +noise = float(sys.argv[1]) + +for l in sys.stdin.readlines(): + f = l.strip().split(',') + mode = f[0] + dist = float(f[3]) + dist_anon = round(uniform(dist * (1-noise), dist * (1+noise))) + print(','.join((mode, str(dist_anon)))) + diff --git a/2023/co2.py b/2023/co2.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +# output: two fields, distance, inferred mode, CO2e emission in kg +# also output a map (map.geojson) + +import json + +import sys +from collections import defaultdict + +places = defaultdict(lambda: (0, 0, None)) + +n_trips = 0 +total_dist = 0 +total_co2 = 0 +trips_by_type = defaultdict(lambda : 0) +dist_by_type = defaultdict(lambda : 0) +co2_by_type = defaultdict(lambda : 0) + + +for l in sys.stdin.readlines(): + f = l.strip().split(',') + mode = f[0] + lat = f[1] + lon = f[2] + + distance = float(f[3]) + + if mode.strip() not in ['plane', 'train', 'bus/coach', 'car']: + if distance > 400000: + mode = "plane" + trips_by_type['plane_assumed'] += 1 + else: + mode = "train" + trips_by_type['train_assumed'] += 1 + else: + trips_by_type[mode] += 1 + + k = (lat,lon) + plane = mode == "plane" + places[k] = (places[k][0] + (1 if plane else 0), places[k][1] + 1) + + dist_by_type[mode] += distance + n_trips += 1 + total_dist += distance + g_km_person = None + if mode == "train": + g_km_person = 37 + if mode == "bus/coach": + g_km_person = 28 + if mode == "car": + g_km_person = 192 + if mode == "plane": + if distance <= 1000000: + g_km_person = 258 + elif 1000000 < distance <= 3500000: + g_km_person = 187 + elif 3500000 < distance: + g_km_person = 152 + co2 = (g_km_person * (distance / 1000.))/1000. + co2_by_type[mode] += co2 + total_co2 += co2 + print (','.join([str(distance), mode, str(co2)])) + + +## OUTPUT GEOJSON + +features = [] +for k in places.keys(): + red = int(255.*places[k][0]/places[k][1]) + green = 0 + blue = int(255.*(places[k][1]-places[k][0])/places[k][1]) + color = '#%02X%02X%02X' % (red, green, blue) + feature = { + "type": "Feature", + "properties": { + #"name":places[k][2], + "_umap_options": {"color": color} + }, + "geometry": { + "type": "Point", + "coordinates": [ + k[1], k[0] + ] + } + } + features.append(feature) + +output = { + "type": "FeatureCollection", + "features": features +} + +with open("map.geojson", 'w') as f: + print (json.dumps(output), file=f) + +print("%d total trips" % n_trips, file=sys.stderr) +print("%d total distance" % total_dist, file=sys.stderr) +print("%f total CO2" % total_co2, file=sys.stderr) +for k in trips_by_type: + print("%d trips by %s" % (trips_by_type[k], k), file=sys.stderr) +for k in dist_by_type: + print("%d distance by %s" % (dist_by_type[k], k), file=sys.stderr) +for k in co2_by_type: + print("%f kgCO2e by %s" % (co2_by_type[k], k), file=sys.stderr) + diff --git a/2023/compute_trips.py b/2023/compute_trips.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +import csv +import sys +import math +from geopy.geocoders import Nominatim +from functools import cache + +# Define carbon emissions data for each mode of transportation (in gCO2e per passenger-kilometer) +emissions_data = { + "train": 41, + "plane": 160, + "bike": 0, + "bus": 105, + "car": 192, + "car_shared": 0 +} + +if len(sys.argv) != 4: + print("Usage: python script.py <input_csv_file> <country> <city>") + sys.exit(1) + +geolocator = Nominatim(user_agent='Highlights Conference 2023') + +@cache +def get_lat_lon(country, city): + location = geolocator.geocode(country + ", " + city) + if location is None: + print(f"got None response for {country} and {city}") + return location.latitude, location.longitude + +target_lat, target_lon = get_lat_lon(sys.argv[2], sys.argv[3]) + +def haversine_distance(lat1, lon1, lat2, lon2): + # Convert latitude and longitude from degrees to radians + lat1 = math.radians(lat1) + lon1 = math.radians(lon1) + lat2 = math.radians(lat2) + lon2 = math.radians(lon2) + + # Radius of the Earth in kilometers (mean value) + earth_radius = 6371.0 # km + + # Haversine formula + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + distance = earth_radius * c + + return distance + +def estimate_distance(country, city): + lat, lon = get_lat_lon(country, city) + return haversine_distance(lat, lon, target_lat, target_lon) + + +total_carbon_footprint = 0 +total_carbon_footprint2 = 0 +# Available fields are +# timestamp,firstname,lastname,email,affiliation,no_zulip,level,presenting,extra,physical,hcrw,country,city,mode +with open(sys.argv[1], 'r') as csv_file: + reader = csv.DictReader(csv_file) + for row in reader: + if row['physical'] == "true" and emissions_data.get(row['mode']) is not None: + # Calculate the carbon footprint for the round trip + mode = None + match row['mode']: + case "car": + mode = "car" + case "train": + mode = "train" + case "bus": + mode = "bus" + case "plane": + mode = "plane" + case _: + mode = "none" + + lat, lon = get_lat_lon(row['country'], row['city']) + distance = haversine_distance(lat, lon, target_lat, target_lon) + if distance != 0 and mode != "none": + print(f"{mode}, {lat}, {lon}, {distance*1000}") + print(f"{mode}, {lat}, {lon}, {distance*1000}") + + diff --git a/2023/run.sh b/2023/run.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +cd "$DIR" + +FILE="$1" +COUNTRY="$2" +CITY="$3" +NOISE="$4" + +>&2 echo "computing trips" +./compute_trips.py "$FILE" "$COUNTRY" "$CITY" > trips_with_dist +>&2 echo "adding noise" +./addnoise.py "$NOISE" < trips_with_dist | sort -t',' -k2,2n | + cat <(echo 'mode,distance in meters') - > trips_anonymized.csv +python3 co2.py < trips_with_dist > trips_with_footprint + diff --git a/2023/transform.py b/2023/transform.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import json +import sys + +year = int(sys.argv[1]) +people = int(sys.argv[2]) +total = int(sys.argv[3]) +location = sys.argv[4] + +trips = [] +for l in sys.stdin.readlines(): + f = l.strip().split(',') + mode = f[0] + dist = f[1] + trip = { + "mode": mode, + "dist": int(dist) + } + trips.append(trip) + +edition_data = { + "year": year, + "location": location, + "participants": people, + "emissions": total, + "trips": trips +} + +print(json.dumps(edition_data))+ \ No newline at end of file diff --git a/2023/trips_anonymized.csv b/2023/trips_anonymized.csv @@ -0,0 +1,234 @@ +train,40442 +train,45579 +train,68361 +train,71473 +train,79562 +train,80487 +train,84153 +train,84310 +train,134654 +car,137170 +car,143119 +train,143277 +train,143980 +car,145419 +car,145424 +train,148908 +train,150953 +train,152292 +train,153107 +train,159332 +train,161652 +train,162322 +train,171985 +train,172383 +train,190030 +train,195807 +train,197601 +train,202542 +train,220599 +train,220740 +train,221664 +train,222744 +train,223906 +train,232625 +train,233717 +train,234301 +train,237533 +train,239465 +train,240026 +train,241647 +train,244242 +car,244414 +train,244432 +train,246899 +train,250056 +train,251657 +train,251964 +car,252797 +train,254312 +train,254766 +train,254973 +train,258107 +train,260730 +train,261788 +train,261919 +train,262756 +train,262903 +train,263013 +train,263325 +train,263877 +train,268639 +train,275892 +train,278298 +train,285034 +train,286962 +train,296400 +train,302812 +train,303821 +train,311182 +train,316972 +train,318624 +train,323317 +train,332718 +plane,337316 +plane,342487 +train,343262 +train,344396 +train,352986 +train,353783 +train,361156 +train,361919 +train,367915 +train,374289 +train,379405 +train,384594 +train,385807 +train,386876 +train,388557 +train,392017 +train,396339 +train,398152 +train,400144 +train,402171 +train,404400 +train,405291 +train,415104 +train,416829 +train,417423 +train,428333 +train,433436 +train,461498 +car,469781 +train,485684 +car,501208 +train,521055 +train,526773 +train,528867 +train,530789 +train,535997 +train,537496 +train,543608 +train,544019 +train,544213 +car,549273 +train,550990 +train,551594 +plane,554125 +train,556013 +car,557227 +train,559462 +train,563082 +plane,564758 +train,566065 +train,571465 +train,579358 +train,579460 +train,584085 +train,585752 +train,585816 +train,588813 +train,594422 +train,596516 +plane,598343 +train,601072 +train,608674 +train,610385 +train,611610 +train,614637 +train,619434 +train,620090 +train,625873 +plane,627076 +train,631931 +train,633509 +train,636224 +train,637377 +train,647130 +train,648076 +plane,658330 +train,660866 +train,665122 +train,671055 +train,673368 +plane,677997 +plane,680011 +train,680320 +plane,682538 +train,688114 +train,694705 +plane,697537 +plane,705632 +train,716695 +train,718754 +train,719200 +plane,727637 +train,728786 +train,729802 +train,732765 +train,734480 +plane,740395 +train,751389 +plane,759494 +train,761557 +train,762762 +plane,762883 +train,762971 +train,764176 +train,774882 +plane,775586 +train,777230 +train,787546 +train,788824 +train,791106 +plane,799268 +train,801830 +train,851880 +plane,853435 +plane,855522 +train,863394 +train,863960 +train,871373 +plane,872078 +train,891796 +train,901518 +plane,908116 +plane,912851 +plane,913437 +plane,918838 +plane,923204 +plane,926731 +plane,931541 +train,934890 +plane,946823 +train,955409 +plane,955970 +train,958250 +train,981357 +train,1005947 +train,1060376 +train,1094812 +plane,1097071 +train,1103612 +plane,1159316 +train,1161401 +plane,1481186 +plane,1512669 +plane,1518530 +plane,1728065 +plane,2633310 +plane,2687854 +plane,2729170 +plane,2731108 +plane,2902517 +plane,2936940 +plane,3185977 +plane,3221225 +plane,6849671 +plane,7164057 +plane,7383622 +plane,7503125 +plane,9502737 +plane,9821515 +plane,9933396 +plane,10428282