# Data Visualization

This tutorial introduces visualization options for vessel trajectories processed using **AISdb**, including <mark style="background-color:yellow;">AISdb's integrated web interface</mark> and <mark style="background-color:yellow;">alternative approaches with popular Python visualization packages</mark>. Practical examples were provided for each tool, illustrating how to process and visualize AISdb tracks effectively.

## Internal visualization

**AISdb** provides an integrated data visualization feature through the [`aisdb.web_interface.visualize`](https://aisdb.meridian.cs.dal.ca/doc/api/aisdb.web_interface.html#aisdb.web_interface.visualize) module, which allows users to generate interactive maps displaying vessel tracks. This built-in tool is designed for simplicity and ease of use, offering customizable visualizations directly from AIS data without requiring extensive setup.&#x20;

Here is an example of using the web interface module to show queried data with colors. To display vessel tracks in a single color:

{% code lineNumbers="true" %}

```python
import aisdb
from datetime import datetime
from aisdb.database.dbconn import SQLiteDBConn
from aisdb import DBConn, DBQuery, DomainFromPoints

import nest_asyncio
nest_asyncio.apply()

dbpath='YOUR_DATABASE.db' # Define the path to your database

# Set the start and end times for the query
start_time = datetime.strptime("2018-01-01 00:00:00", '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime("2018-01-03 00:00:00", '%Y-%m-%d %H:%M:%S')

# Define a circle with a 100km radius around the location point
domain = DomainFromPoints(points=[(-63.6, 44.6)], radial_distances=[100000]) 

def color_tracks(tracks):
    """ Set the color of each vessel track using a color name or RGB value. """
    for track in tracks:
        track['color'] = 'yellow'
        yield track

with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    colored_tracks = color_tracks(tracks)

    # Visualization
    aisdb.web_interface.visualize(
        colored_tracks,
        domain=domain,
        visualearth=True,
        open_browser=True,
    )
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FaG0w1nUiqfx1WwCIzapu%2Fimage.png?alt=media&#x26;token=2a6694fd-43aa-479e-bbf3-8f4a99ddf45f" alt=""><figcaption><p>Visualizing queried vessel tracks in a single color</p></figcaption></figure>

If you want to visualize vessel tracks in different colors based on MMSI, here's an example that demonstrates how to color-code tracks for easy identification:

{% code lineNumbers="true" %}

```python
import random

def color_tracks2(tracks):
    colors = {}
    for track in tracks:
        mmsi = track.get('mmsi')
        if mmsi not in colors:
            # Assign a random color to this MMSI if not already assigned
            colors[mmsi] = "#{:06x}".format(random.randint(0, 0xFFFFFF))
            track['color'] = colors[mmsi] # Set the color for the current track
        yield track


with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    colored_tracks = list(color_tracks2(tracks))

    # Visualization
    aisdb.web_interface.visualize(
        colored_tracks,
        domain=domain,
        visualearth=True,
        open_browser=True,
    )
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2F744nTFJ8n0qrhpRu1prZ%2Fimage.png?alt=media&#x26;token=5153b837-95c0-496e-aa8c-43385cfa2903" alt=""><figcaption><p>Visualizing vessel tracks in multiple colors based on MMSIs</p></figcaption></figure>

## Alternative visualization

Several alternative Python packages can be leveraged for users seeking more advanced or specialized visualization capabilities. For instance, Contextily, `Basemap` and `Cartopy` are excellent for creating detailed 2D plots, while `Plotly` offering powerful interactive graphs. Additionally, `Kepler.gl` caters to users needing dynamic, large-scale visualizations or 3D mapping. These alternatives allow for a deeper exploration of AIS data, offering flexibility in how data is presented and analyzed beyond the default capabilities of AISdb.

### Contextily + Matplotlib

<pre class="language-python" data-line-numbers><code class="lang-python">import aisdb
from datetime import datetime
from aisdb.database.dbconn import SQLiteDBConn
from aisdb import DBConn, DBQuery, DomainFromPoints
<strong>import contextily as cx
</strong>import matplotlib.pyplot as plt
import random 
import nest_asyncio
nest_asyncio.apply()

dbpath='YOUR_DATABASE.db' # Define the path to your database

# Set the start and end times for the query
start_time = datetime.strptime("2018-01-01 00:00:00", '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime("2018-01-03 00:00:00", '%Y-%m-%d %H:%M:%S')

# Define a circle with a 100km radius around the location point
domain = DomainFromPoints(points=[(-63.6, 44.6)], radial_distances=[100000])
 
def color_tracks2(tracks):
    colors = {}
    for track in tracks:
        mmsi = track.get('mmsi')
        if mmsi not in colors:
            # Assign a random color to this MMSI if not already assigned
            colors[mmsi] = "#{:06x}".format(random.randint(0, 0xFFFFFF))
            track['color'] = colors[mmsi] # Set the color for the current track
        yield track

def plot_tracks_with_contextily(tracks):
    plt.figure(figsize=(12, 8))

    for track in tracks:
        plt.plot(track['lon'], track['lat'], color=track['color'], linewidth=2)

    # Add basemap
    cx.add_basemap(plt.gca(), crs='EPSG:4326', source=cx.providers.CartoDB.Positron)

    plt.xlabel('Longitude')
    plt.ylabel('Latitude')
    plt.title('Vessel Tracks with Basemap')
    plt.show()
    

with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    colored_tracks = list(color_tracks2(tracks))
    
    plot_tracks_with_contextily(colored_tracks)
</code></pre>

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FRwLFvAg5gfUdWdrYirRx%2Fcontextily.png?alt=media&#x26;token=d9458d53-681f-4581-8349-de141ae2c310" alt="" width="563"><figcaption><p>Visualization of vessel tracks with Contextily</p></figcaption></figure>

### :warning: Basemap + Matplotlib

> Note: mpl\_toolkits.basemap uses numpy v1, therefore, downgrade numpy to v1.26.4 to use Basemap. Else, refer to other alternatives mentioned such as Contextily!

{% code lineNumbers="true" %}

```python
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

def plot_tracks_with_basemap(tracks):
    plt.figure(figsize=(12, 8))
    # Define the geofence boundaries
    llcrnrlat = 42.854329883666175  # Latitude of the southwest corner
    urcrnrlat = 47.13666808816243   # Latitude of the northeast corner
    llcrnrlon = -68.73998377599209  # Longitude of the southwest corner
    urcrnrlon = -56.92378296577808  # Longitude of the northeast corner

    # Create the Basemap object with the geofence
    m = Basemap(projection='merc', 
                llcrnrlat=llcrnrlat, urcrnrlat=urcrnrlat,
                llcrnrlon=llcrnrlon, urcrnrlon=urcrnrlon, resolution='i')
    
    m.drawcoastlines()
    m.drawcountries()
    m.drawmapboundary(fill_color='aqua')
    m.fillcontinents(color='lightgreen', lake_color='aqua')

    for track in tracks:
        lons, lats = track['lon'], track['lat']
        x, y = m(lons, lats)
        m.plot(x, y, color=track['color'], linewidth=2)

    plt.show()
    
with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    colored_tracks = list(color_tracks2(tracks))
    
    plot_tracks_with_basemap(colored_tracks)
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FMHYs5kywMdpsry89fmH4%2Fimage.png?alt=media&#x26;token=54452042-0c79-4b3d-ac1c-7f95d8ca44e2" alt=""><figcaption><p>Visualization of vessel tracks with Basemap</p></figcaption></figure>

### Cartopy

{% code lineNumbers="true" %}

```python
import cartopy.crs as ccrs
import matplotlib.pyplot as plt

def plot_tracks_with_cartopy(tracks):
    plt.figure(figsize=(12, 8))
    ax = plt.axes(projection=ccrs.Mercator())
    ax.coastlines()
    
    for track in tracks:
        lons, lats = track['lon'], track['lat']
        ax.plot(lons, lats, transform=ccrs.PlateCarree(), color=track['color'], linewidth=2)
    
    plt.title('AIS Tracks Visualization with Cartopy')
    plt.show()

with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    colored_tracks = list(color_tracks2(tracks))
    
    plot_tracks_with_cartopy(colored_tracks)
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2Fh8BqLmnAn7yiylt4YjlK%2Fimage.png?alt=media&#x26;token=bbfd6d43-6023-46fd-9b9c-1dd72067e6e7" alt="" width="504"><figcaption><p>Visualization of vessel tracks with Cartopy</p></figcaption></figure>

### Plotly

{% code lineNumbers="true" %}

```python
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd

def track2dataframe(tracks):
    data = []
    # Iterate over each track in the vessels_generator
    for track in tracks:
        # Unpack static information
        mmsi = track['mmsi']
        rot = track['rot']
        maneuver = track['maneuver']
        heading = track['heading']
    
        # Unpack dynamic information
        times = track['time']
        lons = track['lon']
        lats = track['lat']
        cogs = track['cog']
        sogs = track['sog']
        utc_seconds = track['utc_second']
    
        # Iterate over the dynamic arrays and create a row for each time point
        for i in range(len(times)):
            data.append({
                'mmsi': mmsi,
                'rot': rot,
                'maneuver': maneuver,
                'heading': heading,
                'time': times[i],
                'longitude': lons[i],
                'latitude': lats[i],
                'cog': cogs[i],
                'sog': sogs[i],
                'utc_second': utc_seconds[i],
            })
            
    # Convert the list of dictionaries to a pandas DataFrame
    df = pd.DataFrame(data)
    
    return df

def plotly_visualize(data, visual_type='lines'):
    if (visual_type=='scatter'):
        # Create a scatter plot for the vessel data points using scatter_geo
        fig = px.scatter_geo(
            data,
            lat="latitude",
            lon="longitude",
            color="mmsi",  # Color by vessel identifier
            hover_name="mmsi",
            hover_data={"time": True},
            title="Vessel Data Points"
        )
    else:
        # Create a line plot for the vessel trajectory using scatter_geo
        fig = px.line_geo(
            data,
            lat="latitude",
            lon="longitude",
            color="mmsi",  # Color by vessel identifier
            hover_name="mmsi",
            hover_data={"time": True},
        )
    
    # Set the map style and projection
    fig.update_geos(
        projection_type="azimuthal equal area",  # Change this to 'natural earth', 'azimuthal equal area', etc.
        showland=True,
        landcolor="rgb(243, 243, 243)",
        countrycolor="rgb(204, 204, 204)",
        lonaxis=dict(range=[-68.73998377599209, -56.92378296577808]),  # Longitude range (geofence)
        lataxis=dict(range=[42.854329883666175, 47.13666808816243])   # Latitude range (geofence)
    )
    
    # Set the layout to focus on a specific area or zoom level
    fig.update_layout(
        geo=dict(
            projection_type="mercator",
            center={"lat": 44.5, "lon": -63.5},
        ),
        width=900,  # Increase the width of the plot
        height=700,  # Increase the height of the plot
    ) 
    fig.show()

with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)

    df = track2dataframe(tracks)
    plotly_visualize(df, 'lines')
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FNOpcv3bfC3VpBK2nwUAO%2Fimage.png?alt=media&#x26;token=87b9a5dc-3c39-4cc5-8acf-fc5bc39ae85a" alt=""><figcaption><p>Interactive visualization of vessel tracks with Plotly</p></figcaption></figure>

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2F4PCY4LmFdGNt810v1VaT%2Fimage.png?alt=media&#x26;token=1028682a-6e91-4af9-83ac-87d455691034" alt=""><figcaption><p>Interactive visualization of vessel positions with Plotly</p></figcaption></figure>

### Kepler.gl

{% code lineNumbers="true" %}

```python
import pandas as pd
from keplergl import KeplerGl

def visualize_with_kepler(data, config=None):
    map_1 = KeplerGl(height=600)
    map_1.add_data(data=data, name="AIS Data")
    map_1.save_to_html(file_name='./figure/kepler_map.html')
    
    return map_1

with aisdb.SQLiteDBConn(dbpath=dbpath) as dbconn:
    qry = aisdb.DBQuery(
        dbconn=dbconn, start=start_time, end=end_time,
        xmin=domain.boundary['xmin'], xmax=domain.boundary['xmax'],
        ymin=domain.boundary['ymin'], ymax=domain.boundary['ymax'],
        callback=aisdb.database.sqlfcn_callbacks.in_time_bbox_validmmsi,
    )
    rowgen = qry.gen_qry()
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    
    df = track2dataframe(tracks)
    map_1 = visualize_with_kepler(df)
```

{% endcode %}

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FkgrJtQNOdY4bmu7Wo4I3%2Fimage.png?alt=media&#x26;token=fde02f13-49bb-4d6a-b643-8db0ef90fa6f" alt=""><figcaption><p>Interactive visualization of vessel track positions with Kepler.gl</p></figcaption></figure>

<figure><img src="https://3408265823-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVQwdg5yQbjVyGElHIkz5%2Fuploads%2FvK8zkHI4XgBd2AB8c2VK%2Fimage.png?alt=media&#x26;token=372deca9-1c4a-4040-bb10-3e577bcc3fff" alt=""><figcaption><p>Heat map of vessel track density with Kepler.gl</p></figcaption></figure>
