Mars Rover & Heli DemoΒΆ

No description has been provided for this image

Binder IPYNB HTML

This tutorial uses data published by NASA:

  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_traverse.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json
  • https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_flight_path.json

Hat tip to https://fosstodon.org/@65dBnoise/108251277108722231 for providing the pointers

Known issues:

  1. MovingPandas will calculate movement speeds based on Earth's WGS84 ellipsoid by default
InΒ [1]:
import numpy as np
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt

from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim
from os.path import exists
from urllib.request import urlretrieve

import warnings

warnings.filterwarnings("ignore")

plot_defaults = {"linewidth": 5, "capstyle": "round", "figsize": (9, 3), "legend": True}
opts.defaults(
    opts.Overlay(active_tools=["wheel_zoom"], frame_width=500, frame_height=400)
)
hvplot_defaults = {"tiles": None, "cmap": "Viridis", "colorbar": True}

mpd.show_versions()
MovingPandas 0.20.0

SYSTEM INFO
-----------
python     : 3.10.15 | packaged by conda-forge | (main, Oct 16 2024, 01:15:49) [MSC v.1941 64 bit (AMD64)]
executable : c:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19045-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : None
GDAL data dir: None
PROJ       : 9.5.0
PROJ data dir: C:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 1.0.1
pandas     : 2.2.3
fiona      : None
numpy      : 1.23.1
shapely    : 2.0.6
pyproj     : 3.7.0
matplotlib : 3.9.2
mapclassify: 2.8.1
geopy      : 2.4.1
holoviews  : 1.20.0
hvplot     : 0.11.1
geoviews   : 1.13.0
stonesoup  : 1.4

Loading the rover & heli dataΒΆ

"The car-sized Perseverance and its little helicopter buddy Ingenuity landed together inside Mars' Jezero Crater on Feb. 18." https://www.space.com/perseverance-rover-100-mars-days (by Mike Wall published June 02, 2021)

"One sol lasts about 24 hours and 40 minutes, slightly longer than an Earth day." https://www.space.com/perseverance-rover-100-mars-days

InΒ [2]:
def to_timestamp(row):
    start_time = datetime(2021, 2, 18, 0, 0, 0)  #  sol 0
    try:
        sol = row["sol"]  # rover
    except KeyError:
        sol = row["Sol"]  # heli
    td = timedelta(hours=24 * sol, minutes=40 * sol)
    return start_time + td


def get_df_from_url(url):
    file = url.split("/")[-1]
    if not exists(file):
        urlretrieve(url, file)
    gdf = read_file(file)
    gdf["time"] = gdf.apply(to_timestamp, axis=1)
    gdf.set_index("time", inplace=True)
    return gdf


m20_waypoints_json = (
    "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json"
)
heli_waypoints_json = (
    "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json"
)
m20_df = get_df_from_url(m20_waypoints_json)
heli_df = get_df_from_url(heli_waypoints_json)
print(f"M20 records: {len(m20_df)}")
print(f"Heli records: {len(heli_df)}")
M20 records: 497
Heli records: 73
InΒ [3]:
m20_df.describe()
Out[3]:
site drive sol easting northing elev_geoid elev_radii radius lon lat roll pitch yaw yaw_rad tilt dist_m dist_total_m dist_km dist_mi
count 497.000000 497.000000 497.000000 4.970000e+02 4.970000e+02 497.000000 497.000000 4.970000e+02 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000 497.000000
mean 33.265594 1667.835010 714.156942 4.350538e+06 1.094584e+06 -2453.160767 -4138.887966 3.392051e+06 77.380516 18.466302 0.069158 2.304528 -43.397976 -0.757437 5.719657 61.664979 12020.495105 12.012773 7.464414
std 18.341130 1629.013945 384.867289 3.055007e+03 1.266350e+03 126.795920 125.520535 1.255206e+02 0.054338 0.021364 5.060485 5.518405 99.027564 1.728359 8.501640 77.437150 10764.778606 10.752641 6.681458
min 3.000000 0.000000 13.000000 4.345509e+06 1.092270e+06 -2585.869629 -4266.522461 3.391923e+06 77.291067 18.427253 -16.503758 -15.895000 -179.864359 -3.139225 -148.482000 0.000000 0.000000 0.000000 0.000000
25% 20.000000 400.000000 404.000000 4.347451e+06 1.093661e+06 -2551.550537 -4236.529000 3.391953e+06 77.325614 18.450726 -2.097687 -0.512600 -119.315800 -2.082500 1.621186 2.401000 288.274000 0.288000 0.179000
50% 33.000000 1222.000000 707.000000 4.351669e+06 1.094613e+06 -2517.544189 -4201.832031 3.391988e+06 77.400638 18.466787 0.007872 1.088109 -77.732483 -1.356688 4.661634 27.434000 11688.190000 11.688000 7.263000
75% 51.000000 2448.000000 1101.000000 4.353081e+06 1.095915e+06 -2375.036133 -4063.509521 3.392126e+06 77.425757 18.488754 2.382374 4.346000 22.324000 0.389600 9.242036 98.099000 22590.684000 22.591000 14.037000
max 61.000000 9436.000000 1318.000000 4.355183e+06 1.096562e+06 -2033.547363 -3717.454102 3.392473e+06 77.463133 18.499663 17.605792 19.742318 179.950349 3.140726 20.334536 473.921000 30414.464000 30.410000 18.900000
InΒ [4]:
m20_df.hvplot(
    title="M20 & heli waypoints", hover_cols=["sol"], **hvplot_defaults
) * heli_df.hvplot()
Out[4]:
InΒ [5]:
m20_traj = mpd.Trajectory(m20_df, "m20")
heli_traj = mpd.Trajectory(heli_df, "heli")
InΒ [6]:
traj_plot = m20_traj.hvplot(
    title="M20 & heli trajectories", line_width=3, **hvplot_defaults
) * heli_traj.hvplot(line_width=3, color="red", **hvplot_defaults)
traj_plot
Out[6]:
InΒ [7]:
m20_traj.hvplot(
    title="Rover speed (only suitable for relative comparison)",
    c="speed",
    line_width=7,
    **hvplot_defaults
)
Out[7]:
InΒ [8]:
m20_detector = mpd.TrajectoryStopDetector(m20_traj)
stop_points = m20_detector.get_stop_points(
    min_duration=timedelta(seconds=60), max_diameter=100
)
stop_points["duration_days"] = stop_points["duration_s"] / (60 * 60 * 24)
stop_points.head()
Out[8]:
geometry start_time end_time traj_id duration_s duration_days
stop_id
m20_2021-03-03 08:40:00 POINT (77.45095 18.44463) 2021-03-03 08:40:00 2021-03-05 10:00:00 m20 177600.0 2.055556
m20_2021-03-06 10:40:00 POINT (77.45164 18.44517) 2021-03-06 10:40:00 2021-03-22 21:20:00 m20 1420800.0 16.444444
m20_2021-03-23 22:00:00 POINT (77.45102 18.44487) 2021-03-23 22:00:00 2021-04-08 08:00:00 m20 1332000.0 15.416667
m20_2021-04-09 08:40:00 POINT (77.45228 18.44453) 2021-04-09 08:40:00 2021-05-15 08:00:00 m20 3108000.0 35.972222
m20_2021-05-17 09:20:00 POINT (77.45221 18.44388) 2021-05-17 09:20:00 2021-05-31 18:40:00 m20 1243200.0 14.388889
InΒ [9]:
heli_detector = mpd.TrajectoryStopDetector(heli_traj)
heli_stop_points = heli_detector.get_stop_points(
    min_duration=timedelta(seconds=60), max_diameter=100
)
heli_stop_points["duration_days"] = heli_stop_points["duration_s"] / (60 * 60 * 24)
heli_stop_points.head()
Out[9]:
geometry start_time end_time traj_id duration_s duration_days
stop_id
heli_2021-04-03 04:40:00 POINT (77.45102 18.44486) 2021-04-03 04:40:00 2021-04-29 22:00:00 heli 2308800.0 26.722222
heli_2021-08-04 12:40:00 POINT (77.43916 18.43277) 2021-08-04 12:40:00 2021-10-23 16:40:00 heli 6926400.0 80.166667
heli_2022-03-23 18:40:00 POINT (77.44288 18.45068) 2022-03-23 18:40:00 2022-04-03 01:20:00 heli 888000.0 10.277778
heli_2022-06-10 22:00:00 POINT (77.41766 18.45597) 2022-06-10 22:00:00 2022-08-19 19:20:00 heli 6038400.0 69.888889
heli_2022-09-23 18:00:00 POINT (77.41218 18.45572) 2022-09-23 18:00:00 2022-12-09 20:00:00 heli 6660000.0 77.083333
InΒ [10]:
stop_point_plot = stop_points.hvplot(
    title="M20 & heli stops ",
    geo=True,
    size=np.log(dim("duration_days")) * 10,
    hover_cols=["duration_days"],
    color="blue",
    alpha=0.5,
)
heli_stop_plot = heli_stop_points.hvplot(
    geo=True,
    size=np.log(dim("duration_days")) * 10,
    hover_cols=["duration_days"],
    color="red",
    alpha=0.5,
)
stop_point_plot * heli_stop_plot * traj_plot
Out[10]:

Mars background mapΒΆ

Compare to https://mars.nasa.gov/mars2020/mission/where-is-the-rover/

InΒ [11]:
from bokeh.models import TMSTileSource

tile_url = "http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/celestia_mars-shaded-16k_global/{Z}/{X}/{Y}.png"


def mars_tiles(plot, element):
    plot.state.add_tile(TMSTileSource(url=tile_url), level="underlay")


traj_map = m20_traj.hvplot(
    title="M20 & heli trajectories", tiles=None
) * heli_traj.hvplot(color="red", **hvplot_defaults)
traj_map.opts(hooks=[mars_tiles])
Out[11]:

Work in progress:ΒΆ

InΒ [12]:
from geoviews.element import WMTS

MarsImagery = WMTS(
    "https://trek.nasa.gov/tiles/Mars/EQ/Mars_MGS_MOLA_ClrShade_merge_global_463m/1.0.0/default/default028mm/{Z}/{Y}/{X}.jpg",
    name="Mars",
)

m20_traj.hvplot(title="M20 & heli trajectories", tiles=MarsImagery) * heli_traj.hvplot(
    color="red", **hvplot_defaults
)
Out[12]:

Continue exploring MovingPandasΒΆ

  1. Bird migration analysis
  2. Ship data analysis
  3. Horse collar data exploration
  4. OSM traces
  5. Soccer game
  6. Mars rover & heli
  7. Ever Given
  8. Iceberg
  9. Pollution data