-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
169 lines (134 loc) · 4.91 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import requests
import json
import pandas as pd
import geopandas as gpd
from flask import Flask
import folium
import re
COUNTRY_NAME_LOOKUP = {
"Côte d'Ivoire": "Ivory Coast",
"Tanzania": "United Republic of Tanzania",
"USA": "United States of America",
"Serbia": "Republic of Serbia",
"North Macedonia": "Macedonia",
"Myanmar (Burma)": "Myanmar",
"The Gambia": "Gambia",
"Guinea-Bissau": "Guinea Bissau",
"Democratic Republic of the Congo": "Democratic Republic of the Congo",
}
app = Flask(__name__)
def extract_covid_requirements(string: str) -> str:
"""
Extracts entry requirements to a country from string
Args:
String (str): HTML formatted as a str
Returns:
str: Extracted entry requirements for a given country
"""
try:
covid_entry_requirements = (
'<h3 id="entry-to-'
+ re.findall(
r'h3 id="entry-to-(.+?)</p>\n\n<h3',
string,
re.DOTALL,
)[0]
+ "</p>"
)
except IndexError:
covid_entry_requirements = (
"No entry rules in response to coronavirus are listed"
)
return covid_entry_requirements
def build_foreign_travel_advice_dataset(country_urls: dict) -> pd.DataFrame:
"""
Builds pd.DataFrame containing foreign travel advice for each country
Args:
urls (dict): Dictionary containing the name of the country and the url
containing the travel advice
Returns:
pd.DataFrame: pd.DataFrame where each country is a row and each column
represents travel advice about a specific topic eg. Terrorism
Notes:
NaNs will be filled with an empty string
"""
# Extract data for each category for each country
dataset = pd.DataFrame()
country_list = []
for link in country_urls.values():
try:
html = requests.get(link)
res = json.loads(html.content.decode())
country_list.append(res["details"]["country"]["name"])
country_content = {}
for part in res["details"]["parts"]:
country_content[part["slug"]] = part["body"]
country_data = pd.DataFrame(country_content, index=[0])
dataset = pd.concat([dataset, country_data], ignore_index=True)
except requests.exceptions.MissingSchema:
continue
# Update country lists to remove naming inconsistancies between countries
country_list = [COUNTRY_NAME_LOOKUP.get(item, item) for item in country_list]
dataset["name"] = country_list
# Fill missing values in dataset
dataset = dataset.fillna("")
return dataset
# Call data from API
travel_advice_html = requests.get(
"https://www.gov.uk/api/content/foreign-travel-advice"
)
travel_advice_res = json.loads(travel_advice_html.content)
# Parse through JSON to find country links
countries = {}
for doc in travel_advice_res["links"]["children"]:
countries[doc["details"]["country"]["name"]] = doc["api_url"]
# Build dataset of foreign travel advice
travel_advice_dataset = build_foreign_travel_advice_dataset(countries)
# Extract COVID entry requirements from html
travel_advice_dataset["entry-requirements"] = travel_advice_dataset[
"entry-requirements"
].apply(lambda x: extract_covid_requirements(x))
# Extract basic values to visualise on map
travel_advice_dataset["value"] = travel_advice_dataset["entry-requirements"].apply(
lambda x: 0 if x == "No entry rules in response to coronavirus are listed" else 100
)
@app.route("/")
def index():
# Get map data
url = "https://raw.githubusercontent.com/python-visualization/folium/master/examples/data"
country_shapes = f"{url}/world-countries.json"
# Parse geodata and combine with foreign office travel advice data
geoJSON_df = gpd.read_file(country_shapes)
final_df = geoJSON_df.merge(
travel_advice_dataset[["name", "value", "entry-requirements"]], on="name"
)
# Instantiate map
the_map = folium.Map(tiles="cartodbpositron", location=[40, 34], zoom_start=2)
# Add choropleth layer
choropleth = folium.Choropleth(
geo_data=final_df,
name="choropleth",
data=final_df,
columns=["name", "value"],
key_on="feature.properties.name",
fill_color="YlOrBr",
nan_fill_color="black",
fill_opacity=0.7,
line_opacity=0.2,
).add_to(the_map)
# Add layer control for tooltip
folium.LayerControl().add_to(the_map)
# Add tool tip HTML
choropleth.geojson.add_child(
folium.features.GeoJsonTooltip(
["name", "entry-requirements"],
labels=False,
style=(
"overflow-wrap: break-word; background-color: white; color: black; margin: auto;"
),
localize=True,
)
)
return the_map._repr_html_()
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=888)