Skip to content

Commit aa409ea

Browse files
ben-29yihong0618
andauthored
feat: Elevation Gain (#719)
* feat: elevation gain - strava/garmin(gpx/tcx/fit) * feat: elevation gain - keep: elevation & gpx type * feat: elevation gain - keep: elevation & gpx type * feat: elevation gain - ui * feat: joyrun gpx: elevation and heart_rate * feat: elevation gain - joyrun * feat: elevation gain - keep * feat: elevation gain - gpx * feat: elevation gain - oppo * feat: elevation gain - tulipsport * feat: elevation gain - field name * feat: elevation gain - db updator * feat: elevation gain - fix * doc: update notice * feat: elevation gain display switch, default not show --------- Signed-off-by: yihong0618 <[email protected]> Co-authored-by: yihong0618 <[email protected]>
1 parent 124816e commit aa409ea

File tree

20 files changed

+188
-39
lines changed

20 files changed

+188
-39
lines changed

.github/workflows/run_data_sync.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ on:
2525

2626
env:
2727
# please change to your own config.
28-
RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo, Please change the 'pass' it to your own
28+
RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo/db_updater, Please change the 'pass' it to your own
2929
ATHLETE: yihong0618
3030
TITLE: Yihong0618 Running
3131
MIN_GRID_DISTANCE: 10 # change min distance here
@@ -204,6 +204,11 @@ jobs:
204204
# If you want to sync fit activity in gpx format, please consider the following script:
205205
# python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-gpx
206206

207+
- name: Run db updater script to add "Elevation Gain" field to db
208+
if: env.RUN_TYPE == 'db_updater'
209+
run: |
210+
python run_page/db_updater.py
211+
207212
- name: Make svg GitHub profile
208213
if: env.RUN_TYPE != 'pass'
209214
env:

README-CN.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@
44
2. python3(python) in README means python3 python
55
3. use v2.0 need change vercel setting from gatsby to vite
66
4. 2023.09.26 garmin need secret_string(and in Actions) get
7+
8+
```bash
9+
python run_page/get_garmin_secret.py ${email} ${password}
10+
# if cn
11+
python run_page/get_garmin_secret.py ${email} ${password} --is-cn
12+
```
713

8-
```bash
9-
python run_page/get_garmin_secret.py ${email} ${password}
10-
# if cn
11-
python run_page/get_garmin_secret.py ${email} ${password} --is-cn
12-
```
14+
5. 2024.09.29: Added `Elevation Gain` field, If you forked the project before this update, please run the following command:
15+
- To resolve errors: `sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: activities.elevation_gain`
16+
- If you don't have a local environment, set `RUN_TYPE` to `db_updater` in the `.github/workflows/run_data_sync.yml` file once then change back.
17+
18+
```bash
19+
python run_page/db_updater.py
20+
```
21+
- For old data: To include `Elevation Gain` for past activities, perform a full reimport.
22+
- To show the 'Elevation Gain' column, modify `SHOW_ELEVATION_GAIN` in `src/utils/const.ts`
23+
- note: `Elevation Gain` may be inaccurate. You can use Strava's "Correct Elevation" or Garmin's "Elev Corrections" feature for more precise data.
1324
1425
![running_page](https://socialify.git.ci/yihong0618/running_page/image?description=1&font=Inter&forks=1&issues=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fshaonianche%2Fgallery%2Fmaster%2Frunning_page%2Frunning_page_logo_150*150.jpg&owner=1&pulls=1&stargazers=1&theme=Light)
1526
@@ -252,10 +263,13 @@ const LINE_OPACITY = 0.4;
252263
// update for now 2024/11/17 the privacy mode is true
253264
// styling: 开启隐私模式 (不显示地图仅显示轨迹): 设置为 `true`
254265
// 注意:此配置仅影响页面显示,数据保护请参考下方的 "隐私保护"
266+
const PRIVACY_MODE = false;
267+
// styling: 默认关灯: 设置为 `false`, 仅在隐私模式关闭时生效(`PRIVACY_MODE` = false)
268+
const LIGHTS_ON = true;
269+
// styling: 是否显示列 ELEVATION_GAIN
270+
const SHOW_ELEVATION_GAIN = false;
255271
const PRIVACY_MODE = true;
256272
// update for now 2024/11/17 the lights on default is false
257-
// styling: 默认关灯:设置为 `false`, 仅在隐私模式关闭时生效 (`PRIVACY_MODE` = false)
258-
const LIGHTS_ON = false;
259273
```
260274
261275
> 隐私保护:设置下面环境变量:

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
1. clone or Fork before vercel 404 need to pull the latest code
44
2. python3(python) in README means python3 python
55
3. use v2.0 need change vercel setting from gatsby to vite
6-
4. 2023.09.26 garmin need secret_string(and in Actions) get
7-
8-
```bash
9-
python run_page/get_garmin_secret.py ${email} ${password}
10-
# if cn
11-
python run_page/get_garmin_secret.py ${email} ${password} --is-cn
12-
```
6+
4. 2023.09.26 garmin need secret_string(and in Actions) get
7+
```bash
8+
python run_page/get_garmin_secret.py ${email} ${password}
9+
# if cn
10+
python run_page/get_garmin_secret.py ${email} ${password} --is-cn
11+
```
12+
13+
5. 2024.09.29: Added `Elevation Gain` field, If you forked the project before this update, please run the following command:
14+
- To resolve errors: `sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: activities.elevation_gain`
15+
- If you don't have a local environment, set `RUN_TYPE` to `db_updater` in the `.github/workflows/run_data_sync.yml` file once then change back.
16+
17+
```bash
18+
python run_page/db_updater.py
19+
```
20+
- For old data: To include `Elevation Gain` for past activities, perform a full reimport.
21+
- To show the 'Elevation Gain' column, modify `SHOW_ELEVATION_GAIN` in `src/utils/const.ts`
22+
- note: `Elevation Gain` may be inaccurate. You can use Strava's "Correct Elevation" or Garmin's "Elev Corrections" feature for more precise data.
1323
1424
<p align="center">
1525
<img width="150" src="https://raw.githubusercontent.com/shaonianche/gallery/master/running_page/running_page_logo.png" />
@@ -243,6 +253,8 @@ const PRIVACY_MODE = true;
243253
// update for now 2024/11/17 the lights on default is false
244254
// styling: set to `false` if you want to make light off as default, only effect when `PRIVACY_MODE` = false
245255
const LIGHTS_ON = false;
256+
// set to `true` if you want to show the 'Elevation Gain' column
257+
const SHOW_ELEVATION_GAIN = true;
246258
```
247259
248260
- To use Google Analytics, you need to modify the configuration in the `src/utils/const.ts` file.

run_page/codoon_sync.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def parse_points_to_gpx(self, run_points_data):
477477
for p in points_dict_list:
478478
point = gpxpy.gpx.GPXTrackPoint(**p)
479479
gpx_segment.points.append(point)
480-
return gpx.to_xml()
480+
return gpx
481481

482482
def get_single_run_record(self, route_id):
483483
print(f"Get single run for codoon id {route_id}")
@@ -528,11 +528,14 @@ def parse_raw_data_to_namedtuple(
528528
p["latitude"] = latlng_data[i][0]
529529
p["longitude"] = latlng_data[i][1]
530530

531-
if with_gpx:
532-
# pass the track no points
533-
if str(log_id) not in old_gpx_ids and run_points_data:
534-
gpx_data = self.parse_points_to_gpx(run_points_data)
535-
download_codoon_gpx(gpx_data, str(log_id))
531+
elevation_gain = None
532+
if run_points_data:
533+
gpx_data = self.parse_points_to_gpx(run_points_data)
534+
elevation_gain = gpx_data.get_uphill_downhill().uphill
535+
if with_gpx:
536+
# pass the track no points
537+
if str(log_id) not in old_gpx_ids:
538+
download_codoon_gpx(gpx_data.to_xml(), str(log_id))
536539
heart_rate_dict = run_data.get("heart_rate")
537540
heart_rate = None
538541
if heart_rate_dict:
@@ -570,6 +573,7 @@ def parse_raw_data_to_namedtuple(
570573
seconds=int((end_date.timestamp() - start_date.timestamp()))
571574
),
572575
"average_speed": run_data["total_length"] / run_data["total_time"],
576+
"elevation_gain": elevation_gain,
573577
"location_country": location_country,
574578
"source": "Codoon",
575579
}

run_page/db_updater.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from generator.db import init_db, Activity
2+
from config import SQL_FILE
3+
import sqlalchemy
4+
from sqlalchemy import text
5+
from config import GPX_FOLDER, JSON_FILE
6+
from utils import make_activities_file
7+
8+
9+
def add_column_elevation_gain(session):
10+
# check if column elevation_gain is already added
11+
# if not add it to the db
12+
try:
13+
session.query(Activity).first()
14+
print("column elevation_gain already added, skipping")
15+
except sqlalchemy.exc.OperationalError:
16+
sql_statement = 'alter TABLE "activities" add column elevation_gain Float after average_heartrate'
17+
session.execute(text(sql_statement))
18+
print("column elevation_gain added successfully")
19+
20+
21+
if __name__ == "__main__":
22+
session = init_db(SQL_FILE)
23+
add_column_elevation_gain(session)
24+
# regenerate activities
25+
make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE)

run_page/endomondo_sync.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def parse_run_endomondo_to_nametuple(en_dict):
6969
"average_speed": en_dict.get("distance_km", 0)
7070
/ en_dict.get("duration_s", 1)
7171
* 1000,
72+
"elevation_gain": None,
7273
"location_country": "",
7374
}
7475
return namedtuple("x", d.keys())(*d.values())

run_page/generator/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def sync(self, force):
7272
activity.map.summary_polyline = filter_out(
7373
activity.map.summary_polyline
7474
)
75+
# strava use total_elevation_gain as elevation_gain
76+
activity.elevation_gain = activity.total_elevation_gain
7577
activity.subtype = activity.type
7678
created = update_or_create_activity(self.session, activity)
7779
if created:

run_page/generator/db.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def randomword():
4444
"summary_polyline",
4545
"average_heartrate",
4646
"average_speed",
47+
"elevation_gain",
4748
]
4849

4950

@@ -63,6 +64,7 @@ class Activity(Base):
6364
summary_polyline = Column(String)
6465
average_heartrate = Column(Float)
6566
average_speed = Column(Float)
67+
elevation_gain = Column(Float)
6668
streak = None
6769

6870
def to_dict(self):
@@ -122,6 +124,7 @@ def update_or_create_activity(session, run_activity):
122124
location_country=location_country,
123125
average_heartrate=run_activity.average_heartrate,
124126
average_speed=float(run_activity.average_speed),
127+
elevation_gain=float(run_activity.elevation_gain),
125128
summary_polyline=(
126129
run_activity.map and run_activity.map.summary_polyline or ""
127130
),
@@ -137,6 +140,7 @@ def update_or_create_activity(session, run_activity):
137140
activity.subtype = run_activity.subtype
138141
activity.average_heartrate = run_activity.average_heartrate
139142
activity.average_speed = float(run_activity.average_speed)
143+
activity.elevation_gain = float(run_activity.elevation_gain)
140144
activity.summary_polyline = (
141145
run_activity.map and run_activity.map.summary_polyline or ""
142146
)

run_page/gpxtrackposter/track.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(self):
4949
self.length = 0
5050
self.special = False
5151
self.average_heartrate = None
52+
self.elevation_gain = None
5253
self.moving_dict = {}
5354
self.run_id = 0
5455
self.start_latlng = []
@@ -171,6 +172,7 @@ def _load_tcx_data(self, tcx, file_name):
171172
except:
172173
pass
173174
self.polyline_str = polyline.encode(polyline_container)
175+
self.elevation_gain = tcx.ascent
174176
self.moving_dict = {
175177
"distance": self.length,
176178
"moving_time": datetime.timedelta(seconds=moving_time),
@@ -236,6 +238,7 @@ def _load_gpx_data(self, gpx):
236238
sum(heart_rate_list) / len(heart_rate_list) if heart_rate_list else None
237239
)
238240
self.moving_dict = self._get_moving_data(gpx)
241+
self.elevation_gain = gpx.get_uphill_downhill().uphill
239242

240243
def _load_fit_data(self, fit: dict):
241244
_polylines = []
@@ -319,6 +322,10 @@ def append(self, other):
319322
)
320323
self.file_names.extend(other.file_names)
321324
self.special = self.special or other.special
325+
self.average_heartrate = self.average_heartrate or other.average_heartrate
326+
self.elevation_gain = (
327+
self.elevation_gain if self.elevation_gain else 0
328+
) + (other.elevation_gain if other.elevation_gain else 0)
322329
except:
323330
print(
324331
f"something wrong append this {self.end_time},in files {str(self.file_names)}"
@@ -355,6 +362,7 @@ def to_namedtuple(self, run_from="gpx"):
355362
"average_heartrate": (
356363
int(self.average_heartrate) if self.average_heartrate else None
357364
),
365+
"elevation_gain": (int(self.elevation_gain) if self.elevation_gain else 0),
358366
"map": run_map(self.polyline_str),
359367
"start_latlng": self.start_latlng,
360368
}

run_page/joyrun_sync.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def parse_points_to_gpx(
324324
)
325325
)
326326

327-
return gpx.to_xml()
327+
return gpx
328328

329329
def get_single_run_record(self, fid):
330330
payload = {
@@ -363,13 +363,33 @@ def parse_raw_data_to_nametuple(self, run_data, old_gpx_ids, with_gpx=False):
363363
eval(run_data["heartrate"]) if run_data["heartrate"] else None
364364
)
365365
except:
366-
print(f"Heart Rate: can not eval for {str(heart_rate_list)}")
366+
print(f"Heart Rate: can not eval for {str(run_data['heartrate'''])}")
367+
try:
368+
altitude_list = eval(altitude_list) if altitude_list else None
369+
except:
370+
print(f"Altitude: can not eval for {str(altitude_list)}")
367371
heart_rate = None
368372
if heart_rate_list:
369373
heart_rate = int(sum(heart_rate_list) / len(heart_rate_list))
370374
# fix #66
371375
if heart_rate < 0:
372376
heart_rate = None
377+
elevation_gain = None
378+
# pass the track no points
379+
if run_points_data:
380+
gpx_data = self.parse_points_to_gpx(
381+
run_points_data,
382+
start_time,
383+
end_time,
384+
heart_rate_list,
385+
altitude_list,
386+
pause_list,
387+
)
388+
elevation_gain = gpx_data.get_uphill_downhill().uphill
389+
if with_gpx:
390+
# pass the track no points
391+
if str(joyrun_id) not in old_gpx_ids:
392+
download_joyrun_gpx(gpx_data.to_xml(), str(joyrun_id))
373393

374394
polyline_str = polyline.encode(run_points_data) if run_points_data else ""
375395
start_latlng = start_point(*run_points_data[0]) if run_points_data else None
@@ -404,6 +424,7 @@ def parse_raw_data_to_nametuple(self, run_data, old_gpx_ids, with_gpx=False):
404424
seconds=int((run_data["endtime"] - run_data["starttime"]))
405425
),
406426
"average_speed": run_data["meter"] / run_data["second"],
427+
"elevation_gain": elevation_gain,
407428
"location_country": location_country,
408429
}
409430
return namedtuple("x", d.keys())(*d.values())

0 commit comments

Comments
 (0)