diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..2bcd70e --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 88 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..477218a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Lint + +on: [push, pull_request, workflow_dispatch] + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - uses: pre-commit/action@v3.0.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8fff9f1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,49 @@ +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v3.4.0 + hooks: + - id: pyupgrade + args: [--py38-plus] + + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: + [flake8-2020, flake8-errmsg, flake8-implicit-str-concat] + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: python-check-blanket-noqa + - id: python-no-log-warn + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-json + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0-alpha.9-for-vscode + hooks: + - id: prettier + args: [--prose-wrap=always, --print-width=88] + +ci: + autoupdate_schedule: quarterly diff --git a/README.md b/README.md index 406c90b..d58902c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # strava_py + Create artistic visualisations with your exercise data (Python version). -This is a port of the [R strava package](https://github.com/marcusvolz/strava) to Python. +This is a port of the [R strava package](https://github.com/marcusvolz/strava) to +Python. ## Installation @@ -29,7 +31,8 @@ stravavis --help ### Facets -A plot of activities as small multiples. The concept behind this plot was originally inspired by [Sisu](https://twitter.com/madewithsisu). +A plot of activities as small multiples. The concept behind this plot was originally +inspired by [Sisu](https://twitter.com/madewithsisu). ![facets](https://raw.githubusercontent.com/marcusvolz/strava_py/main/plots/facets001.png "Facets, showing activity outlines") @@ -53,39 +56,56 @@ Elevation profiles superimposed. ### Calendar -Calendar heatmap showing daily activity distance, using the [calmap](https://pythonhosted.org/calmap/) package. Requires "activities.csv" from the bulk Strava export. +Calendar heatmap showing daily activity distance, using the +[calmap](https://pythonhosted.org/calmap/) package. Requires "activities.csv" from the +bulk Strava export. ![map](https://raw.githubusercontent.com/marcusvolz/strava_py/main/plots/calendar001.png "Calendar heatmap") ### Dumbbell plot -Activities shown as horizontal lines by time of day and day of year, facetted by year. Requires "activities.csv" from the bulk Strava export. +Activities shown as horizontal lines by time of day and day of year, facetted by year. +Requires "activities.csv" from the bulk Strava export. ![map](https://raw.githubusercontent.com/marcusvolz/strava_py/main/plots/dumbbell001.png "Dumbbell plot") ## How to use ### Bulk export from Strava -The process for downloading data is described on the Strava website here: [https://support.strava.com/hc/en-us/articles/216918437-Exporting-your-Data-and-Bulk-Export#Bulk], but in essence, do the following: - + +The process for downloading data is described on the Strava website here: +[https://support.strava.com/hc/en-us/articles/216918437-Exporting-your-Data-and-Bulk-Export#Bulk], +but in essence, do the following: + 1. Log in to [Strava](https://www.strava.com/) -2. Select "[Settings](https://www.strava.com/settings/profile)" from the main drop-down menu at top right of the screen -3. Select "[My Account](https://www.strava.com/account)" from the navigation menu to the left of the screen. -4. Under the "[Download or Delete Your Account](https://www.strava.com/athlete/delete_your_account)" heading, click the "Get Started" button. -5. Under the "Download Request", heading, click the "Request Your Archive" button. ***Don't click anything else on that page, i.e. particularly not the "Request Account Deletion" button.*** +2. Select "[Settings](https://www.strava.com/settings/profile)" from the main drop-down + menu at top right of the screen +3. Select "[My Account](https://www.strava.com/account)" from the navigation menu to the + left of the screen. +4. Under the + "[Download or Delete Your Account](https://www.strava.com/athlete/delete_your_account)" + heading, click the "Get Started" button. +5. Under the "Download Request", heading, click the "Request Your Archive" button. + **_Don't click anything else on that page, i.e. particularly not the "Request Account + Deletion" button._** 6. Wait for an email to be sent 7. Click the link in email to download zipped folder containing activities 8. Unzip files ### Process the data -The main function for importing and processing activity files expects a path to a directory of unzipped GPX and / or FIT files. If required, the [fit2gpx](https://github.com/dodo-saba/fit2gpx) package provides useful tools for pre-processing bulk files exported from Strava, e.g. unzipping activity files (see Use Case 3: Strava Bulk Export Tools). +The main function for importing and processing activity files expects a path to a +directory of unzipped GPX and / or FIT files. If required, the +[fit2gpx](https://github.com/dodo-saba/fit2gpx) package provides useful tools for +pre-processing bulk files exported from Strava, e.g. unzipping activity files (see Use +Case 3: Strava Bulk Export Tools). ```python df = process_data("") ``` -Some plots use the "activities.csv" file from the Strava bulk export zip. For those plots, create an "activities" dataframe using the following function: +Some plots use the "activities.csv" file from the Strava bulk export zip. For those +plots, create an "activities" dataframe using the following function: ```python activities = process_activities("") diff --git a/RELEASING.md b/RELEASING.md index a7de41d..31b0898 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -13,7 +13,7 @@ git clone https://github.com/marcusvolz/strava_py cd strava_py ``` -* [ ] (Optional) Create a distribution and release on **TestPyPI**: +- [ ] (Optional) Create a distribution and release on **TestPyPI**: ```bash python -m pip install -U pip build keyring twine @@ -30,13 +30,13 @@ python -m pip install -U -i https://test.pypi.org/simple/ stravavis stravavis --help ``` -* [ ] Tag with the version number: +- [ ] Tag with the version number: ```bash git tag -a v0.0.1 -m "Release 0.0.1" ``` -* [ ] Create a distribution and release on **live PyPI**: +- [ ] Create a distribution and release on **live PyPI**: ```bash python -m pip install -U pip build keyring twine @@ -45,7 +45,7 @@ python -m build twine check --strict dist/* && twine upload -r pypi dist/* ``` -* [ ] Check installation: +- [ ] Check installation: ```bash python -m pip uninstall -y stravavis @@ -53,13 +53,14 @@ python -m pip install -U stravavis stravavis --help ``` -* [ ] Push tag: - ```bash +- [ ] Push tag: + +```bash git push --tags ``` -* [ ] Create a new release: https://github.com/marcusvolz/strava_py/releases/new +- [ ] Create a new release: https://github.com/marcusvolz/strava_py/releases/new -* [ ] Click "Choose a tag" and select newest. +- [ ] Click "Choose a tag" and select newest. -* [ ] Click "Auto-generate release notes", amend as required and "Publish release". +- [ ] Click "Auto-generate release notes", amend as required and "Publish release". diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5d7bf33 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.isort] +profile = "black" diff --git a/src/stravavis/cli.py b/src/stravavis/cli.py index 8cd2912..29bed7b 100644 --- a/src/stravavis/cli.py +++ b/src/stravavis/cli.py @@ -15,35 +15,57 @@ def main(): parser.add_argument( "--lon_min", type=float, - help="Minimum longitude for plot_map (values less than this are removed from the data)", + help="Minimum longitude for plot_map " + "(values less than this are removed from the data)", ) parser.add_argument( "--lon_max", type=float, - help="Maximum longitude for plot_map (values greater than this are removed from the data)", + help="Maximum longitude for plot_map " + "(values greater than this are removed from the data)", ) parser.add_argument( "--lat_min", type=float, - help="Minimum latitude for plot_map (values less than this are removed from the data)", + help="Minimum latitude for plot_map " + "(values less than this are removed from the data)", ) parser.add_argument( "--lat_max", type=float, - help="Maximum latitude for plot_map (values greater than this are removed from the data)", + help="Maximum latitude for plot_map " + "(values greater than this are removed from the data)", ) parser.add_argument( "--bbox", help="Shortcut for comma-separated LON_MIN,LAT_MIN,LON_MAX,LAT_MAX" ) - parser.add_argument("--alpha", default=0.4, help="Line transparency. 0 = Fully transparent, 1 = No transparency") + parser.add_argument( + "--alpha", + default=0.4, + help="Line transparency. 0 = Fully transparent, 1 = No transparency", + ) parser.add_argument("--linewidth", default=0.4, help="Line width") - parser.add_argument("--activities_path", help="Path to activities.csv from Strava bulk export zip") - parser.add_argument("--year_min", help="The minimum year to use for the calendar heatmap.") - parser.add_argument("--year_max", help="The maximum year to use for the calendar heatmap.") - parser.add_argument("--max_dist", help="Maximum daily distance for the calendar heatmap; values above this will be capped.") + parser.add_argument( + "--activities_path", help="Path to activities.csv from Strava bulk export zip" + ) + parser.add_argument( + "--year_min", help="The minimum year to use for the calendar heatmap." + ) + parser.add_argument( + "--year_max", help="The maximum year to use for the calendar heatmap." + ) + parser.add_argument( + "--max_dist", + help="Maximum daily distance for the calendar heatmap; " + "values above this will be capped.", + ) parser.add_argument("--fig_height", help="Figure height for the calendar heatmap.") parser.add_argument("--fig_width", help="Figure width for the calendar heatmap.") - parser.add_argument("--local_timezone", help="Timezone for determining local times for activities. See pytz.all_timezones for a list of all timezones.") + parser.add_argument( + "--local_timezone", + help="Timezone for determining local times for activities. " + "See pytz.all_timezones for a list of all timezones.", + ) args = parser.parse_args() # Expand "~" or "~user" @@ -114,7 +136,7 @@ def main(): outfile = f"{args.output_prefix}-calendar.png" plot_calendar(activities, output_file=outfile) print(f"Saved to {outfile}") - + print("Plotting dumbbell...") outfile = f"{args.output_prefix}-dumbbell.png" plot_dumbbell(activities, output_file=outfile) diff --git a/src/stravavis/plot_calendar.py b/src/stravavis/plot_calendar.py index 22324ca..4f48333 100644 --- a/src/stravavis/plot_calendar.py +++ b/src/stravavis/plot_calendar.py @@ -2,35 +2,40 @@ import matplotlib.pyplot as plt import pandas as pd - ACTIVITY_FORMAT = "%b %d, %Y, %H:%M:%S %p" -def plot_calendar(activities, year_min=None, year_max=None, max_dist=None, - fig_height = 15, fig_width = 9, output_file='calendar.png'): + +def plot_calendar( + activities, + year_min=None, + year_max=None, + max_dist=None, + fig_height=15, + fig_width=9, + output_file="calendar.png", +): # Create a new figure plt.figure() - + # Process data activities["Activity Date"] = pd.to_datetime( activities["Activity Date"], format=ACTIVITY_FORMAT ) - activities['date'] = activities['Activity Date'].dt.date - activities = activities.groupby(['date'])['Distance'].sum() + activities["date"] = activities["Activity Date"].dt.date + activities = activities.groupby(["date"])["Distance"].sum() activities.index = pd.to_datetime(activities.index) activities.clip(0, max_dist, inplace=True) - + if year_min: - activities = activities[activities.index.year>=year_min] - + activities = activities[activities.index.year >= year_min] + if year_max: - activities = activities[activities.index.year<=year_max] - + activities = activities[activities.index.year <= year_max] + # Create heatmap - fig, ax = calmap.calendarplot( - data = activities - ) - + fig, ax = calmap.calendarplot(data=activities) + # Save plot fig.set_figheight(fig_height) fig.set_figwidth(fig_width) - fig.savefig(output_file, dpi = 600) + fig.savefig(output_file, dpi=600) diff --git a/src/stravavis/plot_dumbbell.py b/src/stravavis/plot_dumbbell.py index a1e998d..1947ec6 100644 --- a/src/stravavis/plot_dumbbell.py +++ b/src/stravavis/plot_dumbbell.py @@ -12,61 +12,84 @@ theme, theme_bw, xlab, - ylab -) + ylab, +) -def plot_dumbbell(activities, year_min=None, year_max=None, local_timezone=None, - fig_height = 34, fig_width = 34, output_file='dumbbell.png'): - + +def plot_dumbbell( + activities, + year_min=None, + year_max=None, + local_timezone=None, + fig_height=34, + fig_width=34, + output_file="dumbbell.png", +): # Convert activity start date to datetime - activities['Activity Date'] = pd.to_datetime(activities['Activity Date']) - + activities["Activity Date"] = pd.to_datetime(activities["Activity Date"]) + # Convert to local timezone (if given) if local_timezone: - activities['Activity Date'] = (pd.to_datetime(activities['Activity Date']) - .dt.tz_localize(tz='UTC', nonexistent='NaT', ambiguous='NaT') - .dt.tz_convert(local_timezone)) - + activities["Activity Date"] = ( + pd.to_datetime(activities["Activity Date"]) + .dt.tz_localize(tz="UTC", nonexistent="NaT", ambiguous="NaT") + .dt.tz_convert(local_timezone) + ) + # Get activity start and end times - activities['start'] = activities['Activity Date'] - activities['duration'] = 0 + activities["start"] = activities["Activity Date"] + activities["duration"] = 0 for i in range(len(activities)): - activities['duration'][i] = pd.Timedelta(activities['Elapsed Time'][i], unit= "s") - activities['end'] = activities['start'] + activities['duration'] - + activities["duration"][i] = pd.Timedelta( + activities["Elapsed Time"][i], unit="s" + ) + activities["end"] = activities["start"] + activities["duration"] + # Remove activities outside the year_min -> year_max window - activities['year'] = activities['Activity Date'].dt.year - + activities["year"] = activities["Activity Date"].dt.year + if year_min: - activities = activities[activities['year']>=year_min] - + activities = activities[activities["year"] >= year_min] + if year_max: - activities = activities[activities['year']<=year_max] - + activities = activities[activities["year"] <= year_max] + # Get day of year and time of day data - activities['dayofyear'] = activities['Activity Date'].dt.dayofyear - activities['start_time'] = activities['start'].dt.time - activities['end_time'] = activities['end'].dt.time - activities['x'] = (activities['start'].dt.hour - + activities['start'].dt.minute/60 - + activities['start'].dt.second/60/60) - activities['xend'] = (activities['end'].dt.hour - + activities['end'].dt.minute/60 - + activities['end'].dt.second/60/60) - + activities["dayofyear"] = activities["Activity Date"].dt.dayofyear + activities["start_time"] = activities["start"].dt.time + activities["end_time"] = activities["end"].dt.time + activities["x"] = ( + activities["start"].dt.hour + + activities["start"].dt.minute / 60 + + activities["start"].dt.second / 60 / 60 + ) + activities["xend"] = ( + activities["end"].dt.hour + + activities["end"].dt.minute / 60 + + activities["end"].dt.second / 60 / 60 + ) + # Create plotnine / ggplot - p = (ggplot(activities) + - geom_segment(aes(x="x", y="dayofyear", xend="xend", yend="dayofyear"), size = 0.1) + - geom_point(aes("x", "dayofyear"), size = 0.05) + - geom_point(aes("xend", "dayofyear"), size = 0.05) + - facet_grid('.~year') + - scale_x_continuous(breaks = [0, 6, 12, 18, 24], labels = ["12am", "6am", "12pm", "6pm", ""]) + - scale_y_continuous(breaks = [1, 100, 200, 300, 365]) + - xlab("Time of Day") + - ylab("Day of Year") + - theme_bw() + - theme(plot_background = element_rect(fill = "white"), panel_grid_major_y = element_blank()) + p = ( + ggplot(activities) + + geom_segment( + aes(x="x", y="dayofyear", xend="xend", yend="dayofyear"), size=0.1 + ) + + geom_point(aes("x", "dayofyear"), size=0.05) + + geom_point(aes("xend", "dayofyear"), size=0.05) + + facet_grid(".~year") + + scale_x_continuous( + breaks=[0, 6, 12, 18, 24], labels=["12am", "6am", "12pm", "6pm", ""] + ) + + scale_y_continuous(breaks=[1, 100, 200, 300, 365]) + + xlab("Time of Day") + + ylab("Day of Year") + + theme_bw() + + theme( + plot_background=element_rect(fill="white"), + panel_grid_major_y=element_blank(), + ) ) - + # Save plot - p.save(output_file, width=fig_width, height=fig_height, units = "cm", dpi=600) + p.save(output_file, width=fig_width, height=fig_height, units="cm", dpi=600) diff --git a/src/stravavis/plot_elevations.py b/src/stravavis/plot_elevations.py index 9da4163..aef3e6a 100644 --- a/src/stravavis/plot_elevations.py +++ b/src/stravavis/plot_elevations.py @@ -1,39 +1,40 @@ import math + import matplotlib.pyplot as plt import seaborn as sns -def plot_elevations(df, output_file = 'elevations.png'): +def plot_elevations(df, output_file="elevations.png"): # Create a new figure plt.figure() # Compute activity start times (for facet ordering) - start_times = df.groupby('name').agg({'time': 'min'}).reset_index().sort_values('time') + start_times = ( + df.groupby("name").agg({"time": "min"}).reset_index().sort_values("time") + ) ncol = math.ceil(math.sqrt(len(start_times))) # Create facets p = sns.FacetGrid( - data = df, - col = 'name', - col_wrap = ncol, - col_order = start_times['name'], - sharex = False, - sharey = True, - ) + data=df, + col="name", + col_wrap=ncol, + col_order=start_times["name"], + sharex=False, + sharey=True, + ) # Add activities - p = p.map( - plt.plot, "dist", "ele", color = 'black', linewidth = 4 - ) + p = p.map(plt.plot, "dist", "ele", color="black", linewidth=4) # Update plot aesthetics - p.set(xlabel = None) - p.set(ylabel = None) - p.set(xticks = []) - p.set(yticks = []) - p.set(xticklabels = []) - p.set(yticklabels = []) - p.set_titles(col_template = '', row_template = '') - sns.despine(left = True, bottom = True) - plt.subplots_adjust(left = 0.05, bottom = 0.05, right = 0.95, top = 0.95) + p.set(xlabel=None) + p.set(ylabel=None) + p.set(xticks=[]) + p.set(yticks=[]) + p.set(xticklabels=[]) + p.set(yticklabels=[]) + p.set_titles(col_template="", row_template="") + sns.despine(left=True, bottom=True) + plt.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95) plt.savefig(output_file) diff --git a/src/stravavis/plot_facets.py b/src/stravavis/plot_facets.py index 8f70e66..19a7df5 100644 --- a/src/stravavis/plot_facets.py +++ b/src/stravavis/plot_facets.py @@ -1,39 +1,40 @@ import math + import matplotlib.pyplot as plt import seaborn as sns -def plot_facets(df, output_file = 'plot.png'): +def plot_facets(df, output_file="plot.png"): # Create a new figure plt.figure() # Compute activity start times (for facet ordering) - start_times = df.groupby('name').agg({'time': 'min'}).reset_index().sort_values('time') + start_times = ( + df.groupby("name").agg({"time": "min"}).reset_index().sort_values("time") + ) ncol = math.ceil(math.sqrt(len(start_times))) - + # Create facets p = sns.FacetGrid( - data = df, - col = 'name', - col_wrap = ncol, - col_order = start_times['name'], - sharex = False, - sharey = False, - ) + data=df, + col="name", + col_wrap=ncol, + col_order=start_times["name"], + sharex=False, + sharey=False, + ) # Add activities - p = p.map( - plt.plot, "lon", "lat", color = 'black', linewidth = 4 - ) + p = p.map(plt.plot, "lon", "lat", color="black", linewidth=4) # Update plot aesthetics - p.set(xlabel = None) - p.set(ylabel = None) - p.set(xticks = []) - p.set(yticks = []) - p.set(xticklabels = []) - p.set(yticklabels = []) - p.set_titles(col_template = '', row_template = '') - sns.despine(left = True, bottom = True) - plt.subplots_adjust(left = 0.05, bottom = 0.05, right = 0.95, top = 0.95) + p.set(xlabel=None) + p.set(ylabel=None) + p.set(xticks=[]) + p.set(yticks=[]) + p.set(xticklabels=[]) + p.set(yticklabels=[]) + p.set_titles(col_template="", row_template="") + sns.despine(left=True, bottom=True) + plt.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95) plt.savefig(output_file) diff --git a/src/stravavis/plot_landscape.py b/src/stravavis/plot_landscape.py index 6ad21e2..f3a846e 100644 --- a/src/stravavis/plot_landscape.py +++ b/src/stravavis/plot_landscape.py @@ -3,36 +3,37 @@ from rich.progress import track -def plot_landscape(df, output_file = 'landscape.png'): - +def plot_landscape(df, output_file="landscape.png"): # Create a new figure plt.figure() # Convert ele to numeric - df['ele'] = pd.to_numeric(df['ele']) - + df["ele"] = pd.to_numeric(df["ele"]) + # Create a list of activity names - activities = df['name'].unique() + activities = df["name"].unique() # Normalize dist processed = [] for activity in track(activities, "Processing tracks"): - df_i = df[df['name'] == activity] - df_i["dist_norm"] = (df_i["dist"] - df_i["dist"].min()) / (df_i["dist"].max() - df_i["dist"].min()) + df_i = df[df["name"] == activity] + df_i["dist_norm"] = (df_i["dist"] - df_i["dist"].min()) / ( + df_i["dist"].max() - df_i["dist"].min() + ) processed.append(df_i) df = pd.concat(processed) # Plot activities one by one for activity in track(activities, "Plotting activities"): - x = df[df['name'] == activity]['dist_norm'] - y = df[df['name'] == activity]['ele'] - plt.fill_between(x, y, color='black', alpha=0.03, linewidth=0) - plt.plot(x, y, color='black', alpha=0.125, linewidth=0.25) + x = df[df["name"] == activity]["dist_norm"] + y = df[df["name"] == activity]["ele"] + plt.fill_between(x, y, color="black", alpha=0.03, linewidth=0) + plt.plot(x, y, color="black", alpha=0.125, linewidth=0.25) # Update plot aesthetics - plt.axis('off') + plt.axis("off") plt.margins(0) - plt.subplots_adjust(left = 0.05, right = 0.95, bottom = 0.05, top = 0.95) - plt.savefig(output_file, dpi = 600) + plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95) + plt.savefig(output_file, dpi=600) diff --git a/src/stravavis/plot_map.py b/src/stravavis/plot_map.py index fb1a075..956196c 100644 --- a/src/stravavis/plot_map.py +++ b/src/stravavis/plot_map.py @@ -24,42 +24,49 @@ def convert_y(lat): return y -def plot_map(df, lon_min=None, lon_max= None, lat_min=None, lat_max=None, - alpha=0.3, linewidth=0.3, output_file="map.png"): - +def plot_map( + df, + lon_min=None, + lon_max=None, + lat_min=None, + lat_max=None, + alpha=0.3, + linewidth=0.3, + output_file="map.png", +): # Create a new figure plt.figure() # Remove data outside the input ranges for lon / lat if lon_min is not None: - df = df[df['lon'] >= lon_min] - + df = df[df["lon"] >= lon_min] + if lon_max is not None: - df = df[df['lon'] <= lon_max] - + df = df[df["lon"] <= lon_max] + if lat_min is not None: - df = df[df['lat'] >= lat_min] - + df = df[df["lat"] >= lat_min] + if lat_max is not None: - df = df[df['lat'] <= lat_max] + df = df[df["lat"] <= lat_max] # Create a list of activity names - activities = df['name'].unique() + activities = df["name"].unique() # Plot activities one by one for activity in track(activities, "Plotting activities"): - x = df[df['name'] == activity]['lon'] - y = df[df['name'] == activity]['lat'] + x = df[df["name"] == activity]["lon"] + y = df[df["name"] == activity]["lat"] # Transform to Mercator projection so maps aren't squashed away from equator x = x.transform(convert_x) y = y.transform(convert_y) - plt.plot(x, y, color='black', alpha=alpha, linewidth=linewidth) + plt.plot(x, y, color="black", alpha=alpha, linewidth=linewidth) # Update plot aesthetics - plt.axis('off') - plt.axis('equal') + plt.axis("off") + plt.axis("equal") plt.margins(0) - plt.subplots_adjust(left = 0.05, right = 0.95, bottom = 0.05, top = 0.95) - plt.savefig(output_file, dpi = 600) + plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95) + plt.savefig(output_file, dpi=600) diff --git a/src/stravavis/process_activities.py b/src/stravavis/process_activities.py index 4a5478b..0a7c0ec 100644 --- a/src/stravavis/process_activities.py +++ b/src/stravavis/process_activities.py @@ -1,9 +1,10 @@ import pandas as pd + def process_activities(activities_path): # Import activities.csv from Strava bulk export zip activities = pd.read_csv(activities_path) - + # Further processing (to come) - + return activities diff --git a/src/stravavis/process_data.py b/src/stravavis/process_data.py index e63be75..342da33 100644 --- a/src/stravavis/process_data.py +++ b/src/stravavis/process_data.py @@ -32,8 +32,8 @@ def process_gpx(gpxfile): name = [] dist = [] - for track in activity.tracks: - for segment in track.segments: + for activity_track in activity.tracks: + for segment in activity_track.segments: x0 = activity.tracks[0].segments[0].points[0].longitude y0 = activity.tracks[0].segments[0].points[0].latitude d0 = 0 @@ -102,7 +102,6 @@ def process_fit(fitfile): # Function for processing (unzipped) GPX and FIT files in a directory (path) def process_data(path): - # Process all files (GPX or FIT) filenames = glob.glob(path) diff --git a/tests/gpx/invalid-lon-lat-missing.gpx b/tests/gpx/invalid-lon-lat-missing.gpx index fe45715..1e8c509 100644 --- a/tests/gpx/invalid-lon-lat-missing.gpx +++ b/tests/gpx/invalid-lon-lat-missing.gpx @@ -39,4 +39,4 @@ - \ No newline at end of file +