How I created an API that can work out your shipping emissions
Inspiration
I’m developing an API in python that will calculate emissions produced when shipping products around the world. I got this idea from a website called patch. The company offsets carbon by working out the emissions generated when shipping a product.
Calculations
To work out the carbon emissions, I will need to collect the distance travelled. Mass of the item. And type of transport used. The distance and mass are needed to work out the unit of tonne/mile or tonne/km. This is a simple measure that shows how one kilogram or short ton moves for one mile or kilometre.
For calculating a truck delivery, I used a formula from the Environmental Defence Fund an American non-profit.
Step 1: Determine the total amount of ton-miles. Multiply 1,000 miles times 20 tons, which gives us a total of 20,000 ton-miles.
Step 2: Get the weight-based truck emissions factor for a freight truck. The average freight truck in the U.S. emits 161.8 grams of CO2 per ton-mile.
Step 3: Multiply this emissions factor with the total ton-miles {161.8 X 20,000), which gives us a total of 3,236,000 grams of CO2.
Step 4: Convert the total grams into metric tons. Metric tons are the standard measurement unit for corporate emissions of greenhouse gases. There are 1,000,000 grams in a metric ton. To convert our answer from step three we divide it by 1,000,000. This gives us 3.24 metric tons of CO2 for this one move.
For the next section of the API. I calculated a maritime freight journey. I used a formula by the European Chemical Transport Association.
Here is a template below showing how the formula can be used for different modes of transport.
I will be using the Deep-sea container example for a carbon factor. As that closely resembles maritime transport.
Flask RESTful
To develop the API I will be using Flask-RESTful. Flask-RESTful is a Flask extension that helps abstract away some of the work of building an API. To start, you need a class that will define your API application. Flask-RESTful uses an object called resource which accesses multiple HTTP methods that you can use for developing your API. I’m going to partly explain the example code they show on their website:
The code is highly based on normal Flask. I’m going to skip to part where it is the defining class TodoSimple(Resource). The TodoSimple is the name of the class therefore the API. The class uses the Resource object mentioned above. Next, the example defines the HTTP methods we want to use.
def get(self, todo_id):
return {todo_id: todos[todo_id]}
This defines the get method. Which fetches data from the API. The method calls the instance with self. And todo id. The class method returns a dictionary with todo_id as the key. And content with accessing the todos directory earlier with the todo_id. Having the content return as dictionary means it can be accessed as a JSON file later.
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
This method is very similar to the get method above. Only thing added is allowing a user to send data to the API. todos[todo_id] = request.form['data']
adds a todo item using a request form.
api.add_resource(TodoSimple, '/<string:todo_id>')
Sets the routing for the class developed. /<string:todo_id>'
means to access the class you will need to input a todo ID. The string: sets up a variable that accepts text.
To access the API. Use a HTTP service of your choice that allows you sent and receive requests.
Here’s an example from the website:
Code for road freight journey
More work has been done under the put method. As we want the user to add data into the API and return the number of emissions produced.
A piece of code I did not explain in the example is called reqparse. The function allows you set validate requests. By setting arguments.
parser = reqparse.RequestParser()
parser.add_argument('miles')
parser.add_argument('tons')
parser.add_argument('ton_miles', type=float)
args = parser.parse_args()
The parser is set up by saving reqparse.RequestParser() in a variable. After, you can use it to add new arguments. I used the parser.add_argument for miles and tones. So, the user can input the number of miles and tons. parser.add_argument('ton_miles', type=float) sets type to float to allow decimal numbers.
args = parser.parse_args() folds all of these arguments into a variable. Now the argument can be accessed with a key. The name of the argument is the key. Ex: args['tons'], args[miles].
This is how I used the args variable in the code:
if args['miles'] or args['tons'] is None:
ton_miles = args['ton_miles']
The code sets up an if statement which checks if the miles and tons arguments are empty. Then proceeds to store the ton_miles argument into a variable. This is done if the user uses ton_miles argument which does not need either tons or miles. As ton/miles calculation of distance times mass.
if args['ton_miles'] is None:
miles = float(args['miles'])
tons = float(args['tons'])
ton_miles = miles * tons
The if statement checks if the ton_miles is empty. Then proceeds to take the miles and tons arguments. And calculates the ton/miles by using numbers provided by the user.
grams_of_CO2 = 161.8 * ton_miles
metric_tons = grams_of_CO2 / 1000000
Emmisons = round(metric_tons, 2)
Now we start calculating the carbon footprint. By using the formula from the environmental defence fund mentioned earlier in the blog post. grams_of_CO2 = 161.8 * ton_miles
calculate the amount of CO2 grams produced. This is done by multiplying the ton/miles with a carbon factor. The grams of CO2 are then converted into metric tons. This is done by dividing the grams of CO2 by 1,000,000.
Emmisons = round(metric_tons, 2)
round the metric tons by two decimal points. To avoid trailing decimal points.
calculations = {'Emmisons': str(Emmisons) + ' metric tons CO2'}
return calculations, 201
The emissions are shown in a dictionary. The emissions variable is changed to a string. So the text ‘metric tons CO2’ is appended next the emissions. Now the calculations variable which holds the information of the emissions is returned. With the status code 201. Status code 201 means request has been successfully created.
Using Postman
To test the API, we are going to use a program called postman. Postman is a program that allows you test APIs by using various HTTP methods. Postman has a lot more features like making documentation, automated testing, designing APIs etc. But I’m sticking to using the put method so I can send input to the API.
I’m going to use the numbers provided from the environmental defence fund example. Which is a truck that has travelled 1,000 miles and carrying 20 short tons. Following all the steps from the website and earlier in the blog post. It should give the final result of 3.24 metric tons of CO2.
Using Postman, we send a put method to the address of the API. The request is raw body data in the format of a JSON file. Containing the miles and tons.
Now we sent the request:
We get the right number. We also generated a 201-status code.
Code for Boat freight journey
The code for boat freight allows a lot more arguments. This is because I started to expand from the road freight class after I started getting the hang of Flask-RESTful. So, the code for boat freight allows for more features. The new feature is that allows the user to measurements in imperil and metric.
This was also done as the formula I was basing the calculations on were European. Which I mentioned in the beginning of the blog post.
parser = reqparse.RequestParser()
parser.add_argument('miles', type=float)
parser.add_argument('km', type=float)
parser.add_argument('tonnes', type=float)
parser.add_argument('km/tonnes', type=float)
parser.add_argument('miles/tonnes', type=float)
args = parser.parse_args()
The arguments their type set to float. This is done so I do not need to use the float function turn the arguments into decimal numbers.
if args['miles'] is not None:
args['km'] = args['miles'] / 0.621371
args['km/tonnes'] = args['km'] * args['tonnes']
This if statement checks if the miles argument has data. Then proceeds to covert the miles into kilometres. Then calculates the tonne-kilometre unit. By multiplying the mass and distance.
if args['miles/tonnes'] is not None:
args['km/tonnes'] = args['miles/tonnes'] * 1.459972
The second if statement checks if the miles/tonnes is not empty. Then coverts the miles/tonnes into tonne-kilometre.
if args['km/tonnes'] is None:
args['km/tonnes'] = args['km'] * args['tonnes']
The next if statement checks if km/tonne is empty. If so, the km/tonnes are calculated using km and tonne arguments.
g_CO2_tonne_km = args['km/tonnes'] * 8
metric_tons = g_CO2_tonne_km / 1000000
Emmisons = round(metric_tons, 2)
Like calculating the carbon footprint of the road freight from earlier. The grams CO2 is calculated by using the carbon factor of 8. Provided by the ECTA. (see beginning of post). Then the grams of CO2 are then converted to metric tons by dividing by one million. The emissions are then rounded by two decimal numbers.
calculations = {'Emmisons': str(Emmisons) + ' tonnes of CO2'}
return calculations, 201
Same as the road freight class. The emissions are turned into text. So, they can be combined with the statement ‘tonnes of CO2’ And the class returns the calculations. Also a 201-status code.
Using Postman, we can test the API. Using the example of the deep-sea container from the ECTA template. We want the API to return 4.000 tonnes of CO2.
Inputting the distance and weight into postman like so:
{
"km": 5000,
"tonnes": 100000
}
We got this result:
The API gives the correct number.
Accessing the API with python requests
I’m going to show a more practical example how a user can use the API. By using python requests to sent put methods to the API. To sent data into the API and getting their emissions.
import requests
r = requests.put('http://127.0.0.1:5000/boat_freight', data={'km': 5000,
'tonnes': 100000})
print(r.status_code)
print(r.json())
Running the code gets us this:
201
{'Emmisons': '4000.0 tonnes of CO2'}
This is good. So, a user can simply use the requests library and get their carbon footprint. With a few lines of code.
Improvements
This API is a very basic. So, want to add a few more features and fix a few issues. One of the main issues is that the road fright function does not have metric system support. It made sense at the time as that was the first bit of the API I was making. But I think support of metric and imperil will be a good idea.
The second is that if something was to be implemented for basic shipping. Then the measurement for mass will need to change. For example, if you are buying a few books from an online store it will only weigh a few kilograms. Not many tons. Which is the scale implemented in the API. So that is something to think about in the near future.
Conclusion
Though out the project I have been using my local host for the server of the API. If you are interested in having the API in some type of production, so you can personally use the API. Sent me a message, so I could gauge interest in the service. You can check out the code of the project on GitHub. If you feel like you can add feature to the API, please feel free to sent a pull request my way.