Simple tutorial on Flask
In this tutorial, I will show how to create a simple user interface UI) using flask and google maps. Highly indebted to this site: https://github.com/bev-a-tron/MyFlaskTutorial since reading that tutorial took me just a few hours to learn the basics.
I start with the problem at-hand. I want a UI to accept inputs such as origin port, destination port, ship dimensions (length x width) and draught (measure of how deep the ship is submerged in water) then display the predicted result and a mini map of the origin and destination ports (as shown in Figure 1 below).
Figure 1: start page
Once "Submit" button is pressed, display the predicted estimated time and show mini-map for origin port and destination port and a line connected the locations (see Figure 2 below).
Figure 2. Resulting page
Folder Structure
Start with a folder structure that looks like below.
---MyApp
--> static
--> templates
The static folder will contain the css file. CSS stands for Cascading Style Sheets which will describe how the page will look like fonts, heading styles, alignment and so on. Copy below css codes and save it as style.css (or any filename of your choice) then put in static folder.
body { font-family: sans-serif; background: #eee; } a, h1, h2 { color: #377BA8; } h1, h2 { font-family: 'Georgia', serif; margin: 0; } h1 { border-bottom: 2px solid #eee; } h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc; padding: 0.8em; background: white; } .entries { list-style: none; margin: 0; padding: 0; } .entries li { margin: 0.8em 1.2em; } .entries li h2 { margin-left: -1em; } .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } .add-entry dl { font-weight: bold; } .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa; } .flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; } .error { background: #F0D6D6; padding: 0.5em; }
Code 1 (CSS file): Copy and save as style.css in static folder
To create the first page we create a html file and save as inputpage.html and put in templates folder.
<!doctype html> <title>PREDICT ETA</title> <link rel=stylesheet type=text/css href='{{ url_for('static',filename='style.css')}}'> <div class=page> <h2>PREDICT ESTIMATED ARRIVAL TIME OF VESSELS</h1> <div class=metanav>
<h4> Input origin and destination ports (eg SHA HKG) </h4>
<form id='inputpage' method='post' action='index' > <p> Origin: <input type='text' name='origin' /> Destination: <input type='text' name='destination' /> </p> <p> Ship Length: <input type="range" name="shiplen" min="0" max="10"> Ship Width: <input type="range" name="shipwid" min="0" max="10"> </p> <p> Draught: <input type="range" name="teu" min="0" max="10"> </p> <p> <input type='submit' name='sub' value='Submit' /> </p> </form>
</div> </div> </html>
Code 2. (inputpage.html) copy then save into templates folder
When you try to open this html page in a browser, you will notice that the fonts and styles are not the same with what we are trying to do because it is not using the css file yet. Also, if you click on the submit button, it will complain that the index file is not found. This is ok at this point since we are not yet using the html file together with Flask.
Application.py
We will now create the python code that will call the inputpage.html and css file that we created earlier. Copy and save as application.py then put in MyApp folder (on the same level with static and templates folder). In case that you need to install flask, you may run "pip install Flask".
Line 1 imports flask and libraries that we need. Line 2 instantiates app as a flask application. Line 3 tells flask to open the page at the default address: http://127.0.0.1:5000/ in your browser. Later, we will talk about the other method that we need aside from GET. Get is the most common and initial method to use. It also means to send data to a form to the server. Line 4 defines the function index() to run when http://127.0.0.1:5000/ is open. Line 6 opens the inputpage.html that we created earlier. The code at the bottom means to run the application in debug mode. This will help you to trace errors and fix it during development.
from flask import Flask,render_template,request,url_for app = Flask(__name__) @app.route('/',methods=['GET']) def init(): if request.method == 'GET': return render_template('inputpage.html')
if __name__ == "__main__":
app.run(debug=True)
Code 3. (application.py) copy then save into MyApp folder
Running the application
To run the application, open a command prompt and execute below:
python application.py
C:\MyApp\>python application.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat
Open chrome or mozilla browser at http://127.0.0.1:5000/ and you should be able to see the inputpage.html with all the styles and fonts we want. At this point clicking the submit button will again result to an error since we need another html page to display the results.
Notice that in inputpage.html, we have below codes that will make use of a POST method and
<form id='inputpage' method='post' action='index' >
Adding the results.html
We will now add the step where we display the result and a mini-map of the origin and destination port. Notice that this is similar to inputpage.html since we are keeping the same inputs on the results page. To accept values from application.py, we use a variable and enclose it with {{ <param> }}. For example, we keep the input of origin in the previous page into results page which is {{forig}} which is origin take from the previous form.
<!doctype html> <title>PREDICT ETA</title> <link rel=stylesheet type=text/css href='{{ url_for('static',filename='style.css')}}'> <div class=page>
<h2>PREDICT ESTIMATED ARRIVAL TIME OF VESSELS </h1> <div class=metanav>
<h4> Input origin and destination ports (eg SHA HKG) </h4>
<form id='inputpage'> <p> Origin: <input type='text' name='origin' value= {{forig}} /> Destination: <input type='text' name='destination' value= {{fdest}} /> </p> <p> Ship Length: <input type="range" name="shiplen" value= {{fshiplen}} > Ship Width: <input type="range" name="shipwid" value= {{fshipwid}} > </p> <p> Draught: <input type="range" name="teu" value= {{fteu}} > </p> <p> <input type='submit' name='sub' value='Submit' /> </p> </form>
</div> </div> </div>
Code 4. (results.html) save as results.html and put in templates folder
Update application.py
Add below codes in application.py to
from flask import Flask,render_template,request,url_for app = Flask(__name__) app.vars={}
@app.route('/',methods=['GET']) def init(): if request.method == 'GET': return render_template('inputpage.html') @app.route('/index',methods=['POST']) def result(): if request.method == 'POST':
app.vars['origin'] = request.form['origin'].upper() app.vars['destination'] = request.form['destination'].upper()
app.vars['shiplen'] = int(request.form['shiplen'])*10
app.vars['shipwid'] = int(request.form['shipwid'])*10
app.vars['teu'] = int(request.form['teu'])*10
return render_template('results.html', forig=app.vars['origin'], \ fshiplen=app.vars['shiplen'], \ fshipwid=app.vars['shipwid'], fteu=app.vars['teu'], \ fdest=app.vars['destination'])
if __name__ == "__main__": app.run(debug=True)
Code 5 update on application.py
Save this file and notice that in the command prompt, it will reload the application.py since it has been changed.
* Detected change in 'C:\\MyApp\\application.py', reloading * Restarting with stat
Notice that when you input the origin and destination ports then change the range bars, it will save and keep the values on the results page.
Displaying the mini-map using Google map
Before you make use of google map, you need to request for an API key. Go to this link: https://developers.google.com/maps/documentation/javascript/get-api-key.
Once you have the api key, update results.html and insert below codes at the bottom. Notice that we have new parameters such as dLat, dLng, oLat, oLng, mLat and mLng which will be passed from application.py.
<div id="map" style="width:100%;height:240px"></div>
<script> function myMap() { var destination = new google.maps.LatLng({{dLat}},{{dLng}}); var origin = new google.maps.LatLng({{oLat}},{{oLng}}); var middle = new google.maps.LatLng({{mLat}},{{mLng}});
var mapCanvas = document.getElementById("map"); var mapOptions = {center: middle, zoom: 4}; var map = new google.maps.Map(mapCanvas,mapOptions);
var flightPath = new google.maps.Polyline({ path: [origin, destination], strokeColor: "#0000FF", strokeOpacity: 0.8, strokeWeight: 2 }); flightPath.setMap(map); } </script>
<script src="https://maps.googleapis.com/maps/api/js?key=<API-KEY>&callback=myMap"></script>
Code 6 update on results.html
More updates on application.py
We need the latitude and longitude of the origin and destination ports. I download the port codes including port name, country code and lat/lon from this site: http://data.okfn.org/data/core/un-locode
Create a new folder named: data and save this file under this folder. We will read this file, convert into a pandas dataframe and extract the lat/long information.
Add below codes in application.py
import pandas as pd df = pd.read_csv('data/portcodes.csv', header=0) app.vars['origin'] = request.form['origin'].upper() app.vars['destination'] = request.form['destination'].upper() orLat = df[df.PORTCODE==app.vars['origin']].LATITUDE.values[0] orLng = df[df.PORTCODE==app.vars['origin']].LONGITUDE.values[0] dsLat = df[df.PORTCODE==app.vars['destination']].LATITUDE.values[0] dsLng = df[df.PORTCODE==app.vars['destination']].LONGITUDE.values[0] mdLat = (orLat + dsLat) / 2 mdLng = (orLng + dsLng) / 2 app.vars['shiplen'] = int(request.form['shiplen'])*10
app.vars['shipwid'] = int(request.form['shipwid'])*10
app.vars['teu'] = int(request.form['teu'])*10
return render_template('results.html', forig=app.vars['origin'], \ fshiplen=app.vars['shiplen'], \ fshipwid=app.vars['shipwid'], fteu=app.vars['teu'], \ fdest=app.vars['destination'],mLat=mdLat, mLng=mdLng,oLat=orLat,oLng=orLng, \ dLat=dsLat , dLng=dsLng)
Pls note that PORTCODE, LATITUDE and LONGITUDE are the column names of the csv file.
Disclaimer:
1) Note that mdLat and mdLng is a simple way of getting the middle point between the origin and destination. This computation will not work on all cases since the world is not flat. Take the case of Asian Port and a port in America. The middle point will be somewhere in middle east but in fact should be in the pacific.
2) Does not handle the cases when the origin and/or destination port is not found
3) Does not handle the case when you want to go back to the starting page (inputpage.html)