{ "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "# Mars Rover & Heli Demo\n", "\n", "\n", "\n", "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/movingpandas/movingpandas-examples/main?filepath=2-analysis-examples/mars-rover.ipynb)\n", "[![IPYNB](https://img.shields.io/badge/view-ipynb-hotpink)](https://github.com/movingpandas/movingpandas-examples/blob/main/2-analysis-examples/mars-rover.ipynb)\n", "[![HTML](https://img.shields.io/badge/view-html-green)](https://movingpandas.github.io/movingpandas-website/2-analysis-examples/mars-rover.html)\n", "\n", "This tutorial uses data published by NASA:\n", "\n", "* https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json\n", "* https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_traverse.json\n", "* https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json\n", "* https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_flight_path.json\n", "\n", "Hat tip to https://fosstodon.org/@65dBnoise/108251277108722231 for providing the pointers\n", "\n", "Known issues:\n", "\n", "1. MovingPandas will calculate movement speeds based on Earth's WGS84 ellipsoid by default" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import geopandas as gpd\n", "import movingpandas as mpd\n", "import shapely as shp\n", "import hvplot.pandas \n", "import matplotlib.pyplot as plt\n", "\n", "from geopandas import GeoDataFrame, read_file\n", "from shapely.geometry import Point, LineString, Polygon\n", "from datetime import datetime, timedelta\n", "from holoviews import opts, dim\n", "from os.path import exists\n", "from urllib.request import urlretrieve\n", "\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}\n", "opts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))\n", "hvplot_defaults = {'tiles':None, 'cmap':'Viridis', 'colorbar':True}\n", "\n", "mpd.show_versions()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading the rover & heli data \n", "\n", "\"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) \n", "\n", "\"One sol lasts about 24 hours and 40 minutes, slightly longer than an Earth day.\" https://www.space.com/perseverance-rover-100-mars-days" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def to_timestamp(row):\n", " start_time = datetime(2021,2,18,0,0,0) # sol 0 \n", " try: \n", " sol = row['sol'] # rover\n", " except KeyError:\n", " sol = row['Sol'] # heli \n", " td = timedelta(hours=24*sol, minutes=40*sol)\n", " return start_time + td\n", "\n", "def get_df_from_url(url):\n", " file = url.split('/')[-1]\n", " if not exists(file):\n", " urlretrieve(url, file)\n", " gdf = read_file(file)\n", " gdf['time'] = gdf.apply(to_timestamp, axis=1)\n", " gdf.set_index('time', inplace=True)\n", " return gdf\n", "\n", "m20_waypoints_json = \"https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json\"\n", "heli_waypoints_json = \"https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json\"\n", "m20_df = get_df_from_url(m20_waypoints_json)\n", "heli_df = get_df_from_url(heli_waypoints_json)\n", "print(f'M20 records: {len(m20_df)}')\n", "print(f'Heli records: {len(heli_df)}')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m20_df.describe()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m20_df.hvplot(title=\"M20 & heli waypoints\", hover_cols=['sol'], **hvplot_defaults) * heli_df.hvplot()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m20_traj = mpd.Trajectory(m20_df, 'm20')\n", "heli_traj = mpd.Trajectory(heli_df, 'heli')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "traj_plot = m20_traj.hvplot(title=\"M20 & heli trajectories\", line_width=3, **hvplot_defaults) * heli_traj.hvplot(line_width=3, **hvplot_defaults)\n", "traj_plot " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m20_traj.hvplot(title=\"Rover speed (only suitable for relative comparison)\", \n", " c='speed', line_width=7, **hvplot_defaults) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m20_detector = mpd.TrajectoryStopDetector(m20_traj)\n", "stop_points = m20_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)\n", "stop_points['duration_days'] = stop_points['duration_s']/(60*60*24)\n", "stop_points.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "heli_detector = mpd.TrajectoryStopDetector(heli_traj)\n", "heli_stop_points = heli_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)\n", "heli_stop_points['duration_days'] = heli_stop_points['duration_s']/(60*60*24)\n", "heli_stop_points.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stop_point_plot = stop_points.hvplot(title='M20 & heli stops ', \n", " geo=True, size=np.log(dim('duration_days'))*10, \n", " hover_cols=['duration_days'], color='blue', alpha=0.5) \n", "heli_stop_plot = heli_stop_points.hvplot(geo=True, size=np.log(dim('duration_days'))*10, \n", " hover_cols=['duration_days'], color='red', alpha=0.5) \n", "stop_point_plot * heli_stop_plot * traj_plot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Mars background map\n", "\n", "Compare to https://mars.nasa.gov/mars2020/mission/where-is-the-rover/\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.models import TMSTileSource\n", "\n", "tile_url = 'http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/celestia_mars-shaded-16k_global/{Z}/{X}/{Y}.png'\n", "\n", "def mars_tiles(plot, element):\n", " plot.state.add_tile(TMSTileSource(url=tile_url), level='underlay')\n", "\n", "traj_map = m20_traj.hvplot(title=\"M20 & heli trajectories\", tiles=None) * heli_traj.hvplot(**hvplot_defaults)\n", "traj_map.opts(hooks=[mars_tiles])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Work in progress:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from geoviews.element import WMTS\n", "\n", "MarsImagery = WMTS(\n", " 'https://trek.nasa.gov/tiles/Mars/EQ/Mars_MGS_MOLA_ClrShade_merge_global_463m/1.0.0/default/default028mm/{Z}/{Y}/{X}.jpg',\n", " name=\"Mars\")\n", "\n", "m20_traj.hvplot(title=\"M20 & heli trajectories\", tiles=MarsImagery) * heli_traj.hvplot(**hvplot_defaults)\n" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Continue exploring MovingPandas\n", "\n", "1. [Bird migration analysis](bird-migration.ipynb)\n", "1. [Ship data analysis](ship-data.ipynb)\n", "1. [Horse collar data exploration](horse-collar.ipynb)\n", "1. [OSM traces](osm-traces.ipynb)\n", "1. [Soccer game](soccer-game.ipynb)\n", "1. [Mars rover & heli](mars-rover.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.8" } }, "nbformat": 4, "nbformat_minor": 4 }