---
title: "Me and the world"
format:
html:
code-tools: true
title-block-banner: "#DD633F"
description: "Last updated: May. 2025"
resources:
- files/
include-in-header:
- files/maplibre.html
jupyter: base
---
## My Travel History
Click country shapes to see more information.
<!-- <iframe src="./files/my_travel_history.html" width="100%" height="400px" style="border: none;" scrolling="no"></iframe> -->
```{python}
#| echo: false
import geopandas as gpd
import pandas as pd
import numpy as np
import folium
import altair as alt
country_boundaries = gpd.read_file('./files/world-administrative-boundaries.zip')
my_travel_df = pd.read_excel('./files/Travel History.xlsx')
iso3_unique = my_travel_df.iso3.unique()
country_visited = country_boundaries[country_boundaries.iso3.isin(iso3_unique)]
def find_short_name_subdivision(full_name: str, short_list:list):
for i in short_list:
if i in full_name:
return i
return None
province_shape_CN = gpd.read_file('./files/chn_adm_ocha_2020_shp.zip', layer='chn_admbnda_adm1_ocha_2020')
province_shape_CN = province_shape_CN[~province_shape_CN.ADM1_EN.str.contains('Taiwan')]
province_shape_CN = province_shape_CN[~province_shape_CN.ADM1_EN.str.contains('Macao')]
province_shape_CN = province_shape_CN[~province_shape_CN.ADM1_EN.str.contains('Hong Kong')]
my_travel_df_CN = my_travel_df[my_travel_df.iso3 == 'CHN']
province_shape_CN['short'] = province_shape_CN.ADM1_EN.apply(lambda x: find_short_name_subdivision(full_name=x, short_list= my_travel_df_CN.Subdivisions.unique()))
iso32popup = {}
gdf_CN = my_travel_df_CN.merge(province_shape_CN, left_on='Subdivisions',right_on='short', how='outer')
gdf_CN.rename(columns={'ADM1_EN': 'Subdivision'}, inplace=True)
gdf_CN = gpd.GeoDataFrame(gdf_CN, geometry=gdf_CN.geometry, crs=province_shape_CN.crs)
gdf_CN = gdf_CN[['Subdivision', 'First visit', 'geometry']].to_crs(epsg = 4326)
# Simplify geometries
tolerance = 0.05 # smaller values retain more detail
gdf_CN['geometry'] = gdf_CN['geometry'].simplify(tolerance, preserve_topology=True)
# gdf_CH.columns = ['Canton', 'First visit', 'geometry']
gdf_CN['color'] = np.where(gdf_CN['First visit'].notna(), 'pink', 'lightgray')
gdf = gdf_CN # Replace with the actual file path
# Ensure the GeoDataFrame has a column for canton names and geometries
gdf_json = gdf.to_json()
# Create an Altair Chart
chart = alt.Chart(alt.Data(values=gdf.__geo_interface__['features'])).mark_geoshape(
# fill='pink',
stroke='black',
strokeWidth=0.5
).encode(
color=alt.Color(
'properties.color:N', # Use the color column
scale=None, # No color scale since we're using fixed values
legend=None # Remove legend for simplicity
),
tooltip=[
alt.Tooltip('properties.Subdivision:N', title='Subdivision'),
alt.Tooltip('properties.First visit:N', title='First Visit')
]
).properties(
width=300,
height=170,
).project(
type='mercator'
).properties(title=f"China Mainland: {(~gdf_CN['First visit'].isna()).sum()} of {len(gdf_CN)} subdivisions visited")
# Display the chart
vega_lite = folium.VegaLite(
chart,
width="100%",
height="100%",
)
cn_popup = folium.Popup()
vega_lite.add_to(cn_popup)
# chart.show()
iso32popup['CHN'] = cn_popup
canton_shape_CH = gpd.read_file('./files/swissBOUNDARIES.zip').set_index('name')
my_travel_df_CH = my_travel_df[my_travel_df.Country == 'Switzerland']
gdf_CH = my_travel_df_CH.merge(canton_shape_CH, left_on='Subdivisions',right_index=True, how='outer')
gdf_CH = gpd.GeoDataFrame(gdf_CH, geometry=canton_shape_CH.loc[gdf_CH['Subdivisions'].values].geometry.values, crs=canton_shape_CH.crs)
gdf_CH = gdf_CH[['Subdivisions', 'First visit', 'geometry']].to_crs(epsg = 4326)
# Simplify geometries
tolerance = 0.005 # smaller values retain more detail
gdf_CH['geometry'] = gdf_CH['geometry'].simplify(tolerance, preserve_topology=True)
gdf_CH.columns = ['Canton', 'First visit', 'geometry']
gdf_CH['color'] = np.where(gdf_CH['First visit'].notna(), 'pink', 'lightgray')
gdf = gdf_CH # Replace with the actual file path
# Ensure the GeoDataFrame has a column for canton names and geometries
# Convert GeoDataFrame to GeoJSON format
# gdf = gdf.to_crs("EPSG:4326") # Ensure WGS84 CRS for web visualizations
gdf_json = gdf.to_json()
# Create an Altair Chart
chart = alt.Chart(alt.Data(values=gdf.__geo_interface__['features'])).mark_geoshape(
# fill='pink',
stroke='black',
strokeWidth=0.5
).encode(
color=alt.Color(
'properties.color:N', # Use the color column
scale=None, # No color scale since we're using fixed values
legend=None # Remove legend for simplicity
),
tooltip=[
alt.Tooltip('properties.Canton:N', title='Canton'),
alt.Tooltip('properties.First visit:N', title='First Visit')
]
).properties(
width=300,
height=150,
# title="Swiss Cantons Map"
).project(
type='mercator'
).properties(title=f"Switzerland: {(~gdf_CH['First visit'].isna()).sum()} of {len(gdf_CH)} cantons visited")
# Display the chart
vega_lite = folium.VegaLite(
chart,
width="100%",
height="100%",
)
ch_popup = folium.Popup()
vega_lite.add_to(ch_popup)
# chart.show()
iso32popup['CHE'] = ch_popup
m = folium.Map([40, 50], zoom_start=2, tiles="cartodbpositron", height=400)
for country_code in iso3_unique:
country_gdf = country_boundaries[country_boundaries.iso3 == country_code]
df_show = my_travel_df[my_travel_df.iso3 == country_code].drop(columns=['iso3'])
df_show.index = np.arange(len(df_show))+1
html = df_show.to_html(
classes="table table-striped table-hover table-condensed table-responsive"
)
folium.GeoJson(
country_gdf,
# name="lines",
style_function=lambda x: {
"fillColor": "red" if x['properties']['iso3'] in iso32popup.keys() else "orange",
"color": 'black',
"weight": 1.0,
"opacity": 0.8,
"fillOpacity": 0.5,
},
popup=folium.Popup(html=html,max_width="300") if country_code not in iso32popup.keys() else iso32popup[country_code],
highlight_function=lambda x: {"fillOpacity": 0.9},
# zoom_on_click=True,
).add_to(m)
m
```
## My Flight Map
```{=html}
<div id="map" style="width: 100%; height: 400px;"></div>
<script>
const map = new maplibregl.Map({
container: "map", // the id of the div element
style: `https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json`,
zoom: 1, // starting zoom
center: [50, 40] // starting location [longitude, latitude]
});
map.on('load', () => {
map.addSource('airports', {
type: 'geojson',
data: './files/my_airports_gdf.geojson'
});
map.addLayer({
id: 'airports',
type: 'circle',
source: 'airports',
paint: {
'circle-radius': ["+",["get", "No. visit"], 3],
'circle-color': 'orange',
'circle-opacity': 0.6,
'circle-stroke-color': 'black',
'circle-stroke-width': 1
}
});
map.on('click', 'airports', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const { Name, 'No. visit': noVisit } = e.features[0].properties;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<strong>${Name}</strong><br>Total visit: ${noVisit}`)
.addTo(map);
});
map.on("mouseenter", "airports", () => {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "airports", () => {
map.getCanvas().style.cursor = "";
});
map.addSource('flights', {
type: 'geojson',
data: './files/my_flights_gdf.geojson'
});
map.addLayer({
id: 'flights',
type: 'line',
source: 'flights',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': 'black',
'line-width': ["*", ["get", "No. flight"], 1.2]
}
});
map.on('click', 'flights', (e) => {
const coordinates = e.features[0].geometry.coordinates[0];
const { Route, 'No. flight': noFlight } = e.features[0].properties;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<strong>${Route}</strong><br>Total flight: ${noFlight}`)
.addTo(map);
});
map.on("mouseenter", "flights", () => {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "flights", () => {
map.getCanvas().style.cursor = "";
});
});
</script>
```
<!-- <iframe src="./files/my_flight_log.html" width="100%" height="400px" style="border: none;" scrolling="no"></iframe> -->
<!-- <iframe src="./files/mpl_test_flight.html" width="100%" height="400px" style="border: none;" scrolling="no"></iframe> -->
### Records
```{python}
#| echo: false
import pandas as pd
import requests
import geopandas as gpd
from itables import show
my_flights_gdf = gpd.read_file('./files/my_flights_gdf.geojson')
airlines_json = requests.get('https://cdn.jsdelivr.net/gh/besrourms/airlines@latest/airlines.json').json()
airlines = pd.read_json('https://cdn.jsdelivr.net/gh/besrourms/airlines@latest/airlines.json')
code2fullname = dict(zip(airlines.code, airlines.name))
code2logo = dict(zip(airlines.code, airlines.logo))
my_flights_gdf['Airlines'] = my_flights_gdf['Flight Number'].str[:2].apply(lambda x: code2fullname[x])
my_flights_gdf['Airlines logo'] = my_flights_gdf['Flight Number'].str[:2].apply(lambda x: code2logo[x])
my_flights_gdf.rename(columns={'Aircraft Registration': 'Tail', 'Flight Number': 'Flight', 'Departure': 'DEP', 'Arrival': 'ARR'}, inplace=True)
my_flights_df = my_flights_gdf[['Date', 'Airlines', 'Flight', 'DEP', 'ARR', 'Aircraft', 'Tail', 'Distance']]
my_flights_df.index += 1#my_flights_df.index
# display(my_flights_df.to_html(escape=False))
show(my_flights_df, buttons=["pageLength" ,"copyHtml5", "csvHtml5", "excelHtml5"], searching = True, lengthMenu=[20, 50, 100], select=True)
```
### Top Airlines
```{python}
#| echo: false
my_flights_gdf['Airlines logo'] = "<img src=\"" + my_flights_gdf['Airlines logo']+ " Logo\" width=\"25\" height=\"25\">"
airline_counts = my_flights_gdf.groupby('Airlines',as_index=False).count().iloc[:,[0,1]]
airline_counts.columns = [' ','Flights']
airline_counts['Airline'] = my_flights_gdf.groupby('Airlines',as_index=False).apply(lambda x : x['Airlines logo']).unique()
airline_counts = airline_counts.sort_values(by='Flights',ascending=False).set_index('Airline')
airline_counts.index.rename(None,inplace=True)
# display(airline_counts.to_html(escape=False))
airline_counts.columns = ['Airline','Total flights']
show(airline_counts, buttons=["copyHtml5", "csvHtml5", "excelHtml5"], searching = False, paging=False, select=True)
```
### Top Aircrafts
```{python}
#| echo: false
aircraft_counts = my_flights_df.groupby('Aircraft',as_index=False).count().iloc[:,[0,1]]
aircraft_counts.columns = [' ','Flights']
com2con = {'Airbus':'eu', 'Boeing':'us', 'COMAC': 'cn'}
aircraft_counts['Aircraft'] = [com2con[i[0]] for i in aircraft_counts[' '].str.split()]
aircraft_counts['Aircraft'] = "<img src=\"https://flagicons.lipis.dev/flags/4x3/" + aircraft_counts['Aircraft'] + ".svg\" width=\"25\" height=\"25\">"
aircraft_counts = aircraft_counts.sort_values(by='Flights',ascending=False).set_index('Aircraft')
aircraft_counts.index.rename(None,inplace=True)
# display(aircraft_counts.to_html(escape=False))
aircraft_counts.columns = ['Aircraft','Total flights']
show(aircraft_counts , buttons=["copyHtml5", "csvHtml5", "excelHtml5"], searching = False, paging=False, select=True)
```