\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
":Overlay\n",
" .Tiles.I :Tiles [x,y]\n",
" .Path.I :Path [Longitude,Latitude]"
]
},
"execution_count": 13,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1004"
}
},
"output_type": "execute_result"
}
],
"source": [
"trajectory.hvplot(frame_height=600, frame_width=800, line_width=3, line_color=\"red\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This map provides much more contextual information and makes it possible to understand that the trajectory is a ship which is approaching or leaving the harbor of Gothenburg (tricky to say at this point). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exploratory analysis with Space Time Cube (STC)\n",
"\n",
"It is also fairly easy to visualize our trajectory in 3D using space time cube, in which the 3rd dimension is based on time. There are different libraries that can be used for plotting data in 3D, but we will use here [plotly](https://plotly.com/python/plotly-express/) library which provides a relatively easy and straightforward API to generate 3D visualizations. For plotting data in 3D Let's first modify our datetime information a bit, and only keep the time because all of our values are from the same date (5th of July, 2017): "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
" \n",
" "
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.plotly.v1+json": {
"config": {
"plotlyServerURL": "https://plot.ly"
},
"data": [
{
"hovertemplate": "MMSI=210035000\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
":Overlay\n",
" .Tiles.I :Tiles [x,y]\n",
" .Path.I :Path [Longitude,Latitude] (weight)\n",
" .Points.I :Points [Longitude,Latitude] (n)"
]
},
"execution_count": 19,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1177"
}
},
"output_type": "execute_result"
}
],
"source": [
"# Plot with Holoviews\n",
"( flows.hvplot(title='Generalized aggregated trajectories', geo=True, hover_cols=['weight'], line_width='weight', alpha=0.5, color='#1f77b3', tiles='OSM', frame_height=400, frame_width=400) * \n",
" clusters.hvplot(geo=True, color='red', size='n') )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great! As a result, we have a map which is quite easy to interpret with width of the line representing the volume of vessels moving between specific locations. We were able to get a sense of these patterns by looking at the movement data in 3D (i.e. where lot's of movement happens), but following this aggregation approach and visualizing the movement flows makes it much easier to get a grasp of the data. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Animating and exploring the trajectory characteristics in 3D \n",
"\n",
"The previous approaches have provided useful ways to extract information from movement data. Next, we will see how we can extract further characteristics based on the data using a WebGL based interactive visualization tool developed by Uber: [Kepler.gl](https://kepler.gl/).\n",
"\n",
"Let's first manipulate our movement data a bit so that we can visualize it easily in 3D using Kepler.gl. Basically, what we want to do is to generate line segments between all observations in our data and convert them into Polygons by making a small buffer around them. This trick is needed to be able to take advantage of the full 3D capabilities of Kepler.gl. Let's start by preparing a couple of helper functions that generates the segments and converts them into a Polygon:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"from shapely.geometry import LineString\n",
"\n",
"def to_linestring(row):\n",
" if row[\"end_geometry\"] is not None:\n",
" return LineString([row[\"geometry\"], row[\"end_geometry\"]])\n",
"\n",
"def prepare_segments(trajectory):\n",
" # Calculate segments for a single trajectory\n",
" df = trajectory.df.join((trajectory.df[\"geometry\"].shift(-1).to_frame().rename(columns={'geometry': 'end_geometry'})))\n",
"\n",
" # Create linestring\n",
" df[\"segment\"] = df[[\"geometry\", \"end_geometry\"]].apply(to_linestring, axis=1)\n",
"\n",
" # Update geometry column with segments\n",
" df[\"geometry\"] = df[\"segment\"]\n",
"\n",
" # Drop empty geoms (there is one after converting from points to lines)\n",
" df = df.dropna(subset=[\"geometry\"])\n",
"\n",
" # Drop unnecessary columns\n",
" df = df.drop([\"segment\", \"end_geometry\"], axis=1)\n",
"\n",
" # Convert lines to polygons, so that volume/height can be adjusted\n",
" df[\"geometry\"] = df.buffer(0.0002)\n",
" return df"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"