Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Richer activity title with more sport types and locations #733

Merged
merged 10 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions run_page/garmin_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ async def get_activities(self, start, limit):
url = url + "&activityType=running"
return await self.fetch_data(url)

async def get_activity_summary(self, activity_id):
"""
Fetch activity summary
"""
url = f"{self.modern_url}/activity-service/activity/{activity_id}"
return await self.fetch_data(url)

async def download_activity(self, activity_id, file_type="gpx"):
url = f"{self.modern_url}/download-service/export/{file_type}/activity/{activity_id}"
if file_type == "fit":
Expand Down Expand Up @@ -287,6 +294,16 @@ async def download_new_activities(
to_generate_garmin_ids = list(set(activity_ids) - set(downloaded_ids))
print(f"{len(to_generate_garmin_ids)} new activities to be downloaded")

to_generate_garmin_id2title = {}
for id in to_generate_garmin_ids:
try:
activity_summary = await client.get_activity_summary(id)
activity_title = activity_summary.get("activityName", "none")
to_generate_garmin_id2title[id] = activity_title
except Exception as e:
print(f"Failed to get activity summary {id}: {str(e)}")
continue

start_time = time.time()
await gather_with_concurrency(
10,
Expand All @@ -298,7 +315,7 @@ async def download_new_activities(
print(f"Download finished. Elapsed {time.time()-start_time} seconds")

await client.req.aclose()
return to_generate_garmin_ids
return to_generate_garmin_ids, to_generate_garmin_id2title


if __name__ == "__main__":
Expand Down Expand Up @@ -362,7 +379,16 @@ async def download_new_activities(
)
)
loop.run_until_complete(future)
new_ids, id2title = future.result()
# fit may contain gpx(maybe upload by user)
if file_type == "fit":
make_activities_file(SQL_FILE, FOLDER_DICT["gpx"], JSON_FILE, file_suffix="gpx")
make_activities_file(SQL_FILE, folder, JSON_FILE, file_suffix=file_type)
make_activities_file(
SQL_FILE,
FOLDER_DICT["gpx"],
JSON_FILE,
file_suffix="gpx",
activity_title_dict=id2title,
)
make_activities_file(
SQL_FILE, folder, JSON_FILE, file_suffix=file_type, activity_title_dict=id2title
)
10 changes: 7 additions & 3 deletions run_page/garmin_sync_cn_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
)
)
loop.run_until_complete(future)
new_ids = future.result()
new_ids, id2title = future.result()

to_upload_files = []
for i in new_ids:
Expand Down Expand Up @@ -101,5 +101,9 @@

# Step 2:
# Generate track from fit/gpx file
make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE, file_suffix="gpx")
make_activities_file(SQL_FILE, FIT_FOLDER, JSON_FILE, file_suffix="fit")
make_activities_file(
SQL_FILE, GPX_FOLDER, JSON_FILE, file_suffix="gpx", activity_title_dict=id2title
)
make_activities_file(
SQL_FILE, FIT_FOLDER, JSON_FILE, file_suffix="fit", activity_title_dict=id2title
)
2 changes: 1 addition & 1 deletion run_page/garmin_to_strava_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
)
)
loop.run_until_complete(future)
new_ids = future.result()
new_ids, id2title = future.result()
print(f"To upload to strava {len(new_ids)} files")
index = 1
for i in new_ids:
Expand Down
6 changes: 4 additions & 2 deletions run_page/generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ def sync(self, force):
sys.stdout.flush()
self.session.commit()

def sync_from_data_dir(self, data_dir, file_suffix="gpx"):
def sync_from_data_dir(self, data_dir, file_suffix="gpx", activity_title_dict={}):
loader = track_loader.TrackLoader()
tracks = loader.load_tracks(data_dir, file_suffix=file_suffix)
tracks = loader.load_tracks(
data_dir, file_suffix=file_suffix, activity_title_dict=activity_title_dict
)
print(f"load {len(tracks)} tracks")
if not tracks:
print("No tracks found.")
Expand Down
4 changes: 4 additions & 0 deletions run_page/generator/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def randomword():
"distance",
"moving_time",
"type",
"subtype",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new column is not present in the SQLite file if the user syncs before this pull request. Users may face difficulties adding this column manually, or else syncing will fail. Perhaps you should add instructions in the readme or include a script to assist users in doing this easily, as seen in this ref1 ref2. There might be a simpler solution that I'm unaware of.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The db checks whether the new column exists after commit c86d040. If any column defined in class Activity does not exist, it will add a new column in data.db.

"start_date",
"start_date_local",
"location_country",
Expand All @@ -47,6 +48,7 @@ class Activity(Base):
moving_time = Column(Interval)
elapsed_time = Column(Interval)
type = Column(String)
subtype = Column(String)
start_date = Column(String)
start_date_local = Column(String)
location_country = Column(String)
Expand Down Expand Up @@ -106,6 +108,7 @@ def update_or_create_activity(session, run_activity):
moving_time=run_activity.moving_time,
elapsed_time=run_activity.elapsed_time,
type=run_activity.type,
subtype=run_activity.subtype,
start_date=run_activity.start_date,
start_date_local=run_activity.start_date_local,
location_country=location_country,
Expand All @@ -123,6 +126,7 @@ def update_or_create_activity(session, run_activity):
activity.moving_time = run_activity.moving_time
activity.elapsed_time = run_activity.elapsed_time
activity.type = run_activity.type
activity.subtype = run_activity.subtype
activity.average_heartrate = run_activity.average_heartrate
activity.average_speed = float(run_activity.average_speed)
activity.summary_polyline = (
Expand Down
17 changes: 12 additions & 5 deletions run_page/gpxtrackposter/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self):
self.file_names = []
self.polylines = []
self.polyline_str = ""
self.track_name = None
self.start_time = None
self.end_time = None
self.start_time_local = None
Expand All @@ -52,6 +53,7 @@ def __init__(self):
self.run_id = 0
self.start_latlng = []
self.type = "Run"
self.subtype = None # for fit file
self.device = ""

def load_gpx(self, file_name):
Expand Down Expand Up @@ -190,6 +192,8 @@ def _load_gpx_data(self, gpx):
polyline_container = []
heart_rate_list = []
for t in gpx.tracks:
if self.track_name is None:
self.track_name = t.name
for s in t.segments:
try:
extensions = [
Expand Down Expand Up @@ -246,7 +250,11 @@ def _load_fit_data(self, fit: dict):
self.average_heartrate = (
message["avg_heart_rate"] if "avg_heart_rate" in message else None
)
self.type = message["sport"].lower()
if message["sport"].lower() == "running":
self.type = "Run"
else:
self.type = message["sport"].lower()
self.subtype = message["sub_sport"] if "sub_sport" in message else None

# moving_dict
self.moving_dict["distance"] = message["total_distance"]
Expand Down Expand Up @@ -334,11 +342,10 @@ def to_namedtuple(self, run_from="gpx"):
d = {
"id": self.run_id,
"name": (
f"run from {run_from} by {self.device}"
if self.device
else f"run from {run_from}"
self.track_name if self.track_name else "none"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the default value, can we use "" instead of "none"?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, for names already synced in the db, please include instructions in the readme on enabling the feature for all data, possibly with a SQL, to reset names to default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the default value, can we use "" instead of "none"?

no problem

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, for names already synced in the db, please include instructions in the readme on enabling the feature for all data, possibly with a SQL, to reset names to default.

It works if I remove all the caches and regenerate the running page completely. I am trying resolving problems on updating synced activities.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the default value, can we use "" instead of "none"?

resolved in d9ae3ac

), # maybe change later
"type": "Run", # Run for now only support run for now maybe change later
"type": self.type,
"subtype": (self.subtype if self.subtype else "none"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in d9ae3ac

"start_date": self.start_time.strftime("%Y-%m-%d %H:%M:%S"),
"end": self.end_time.strftime("%Y-%m-%d %H:%M:%S"),
"start_date_local": self.start_time_local.strftime("%Y-%m-%d %H:%M:%S"),
Expand Down
25 changes: 18 additions & 7 deletions run_page/gpxtrackposter/track_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,33 @@
log = logging.getLogger(__name__)


def load_gpx_file(file_name):
def load_gpx_file(file_name, activity_title_dict={}):
"""Load an individual GPX file as a track by using Track.load_gpx()"""
t = Track()
t.load_gpx(file_name)
file_id = os.path.basename(file_name).split(".")[0]
if activity_title_dict:
t.track_name = activity_title_dict.get(file_id, t.track_name)
return t


def load_tcx_file(file_name):
def load_tcx_file(file_name, activity_title_dict={}):
"""Load an individual TCX file as a track by using Track.load_tcx()"""
t = Track()
t.load_tcx(file_name)
file_id = os.path.basename(file_name).split(".")[0]
if activity_title_dict:
t.track_name = activity_title_dict.get(file_id, t.track_name)
return t


def load_fit_file(file_name):
def load_fit_file(file_name, activity_title_dict={}):
"""Load an individual FIT file as a track by using Track.load_fit()"""
t = Track()
t.load_fit(file_name)
file_id = os.path.basename(file_name).split(".")[0]
if activity_title_dict:
t.track_name = activity_title_dict.get(file_id, t.track_name)
return t


Expand All @@ -66,15 +75,17 @@ def __init__(self):
"fit": load_fit_file,
}

def load_tracks(self, data_dir, file_suffix="gpx"):
def load_tracks(self, data_dir, file_suffix="gpx", activity_title_dict={}):
"""Load tracks data_dir and return as a List of tracks"""
file_names = [x for x in self._list_data_files(data_dir, file_suffix)]
print(f"{file_suffix.upper()} files: {len(file_names)}")

tracks = []

loaded_tracks = self._load_data_tracks(
file_names, self.load_func_dict.get(file_suffix, load_gpx_file)
file_names,
self.load_func_dict.get(file_suffix, load_gpx_file),
activity_title_dict,
)

tracks.extend(loaded_tracks.values())
Expand Down Expand Up @@ -146,14 +157,14 @@ def _merge_tracks(tracks):
return merged_tracks

@staticmethod
def _load_data_tracks(file_names, load_func=load_gpx_file):
def _load_data_tracks(file_names, load_func=load_gpx_file, activity_title_dict={}):
"""
TODO refactor with _load_tcx_tracks
"""
tracks = {}
with concurrent.futures.ProcessPoolExecutor() as executor:
future_to_file_name = {
executor.submit(load_func, file_name): file_name
executor.submit(load_func, file_name, activity_title_dict): file_name
for file_name in file_names
}
for future in concurrent.futures.as_completed(future_to_file_name):
Expand Down
1 change: 0 additions & 1 deletion run_page/keep_to_strava_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@


def run_keep_sync(email, password, keep_sports_data_api, with_download_gpx=False):

if not os.path.exists(KEEP2STRAVA_BK_PATH):
file = open(KEEP2STRAVA_BK_PATH, "w")
file.close()
Expand Down
8 changes: 6 additions & 2 deletions run_page/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ def to_date(ts):
raise ValueError(f"cannot parse timestamp {ts} into date with fmts: {ts_fmts}")


def make_activities_file(sql_file, data_dir, json_file, file_suffix="gpx"):
def make_activities_file(
sql_file, data_dir, json_file, file_suffix="gpx", activity_title_dict={}
):
generator = Generator(sql_file)
generator.sync_from_data_dir(data_dir, file_suffix=file_suffix)
generator.sync_from_data_dir(
data_dir, file_suffix=file_suffix, activity_title_dict=activity_title_dict
)
activities_list = generator.load()
with open(json_file, "w") as f:
json.dump(activities_list, f)
Expand Down
21 changes: 21 additions & 0 deletions src/utils/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const PRIVACY_MODE = false;
// update for now 2024/11/17 the lights on default is false
//set to `false` if you want to make light off as default, only effect when `PRIVACY_MODE` = false
const LIGHTS_ON =false;
// richer title for the activity types (like garmin style)
const RICH_TITLE = false;

// IF you outside China please make sure IS_CHINESE = false
const IS_CHINESE = true;
Expand All @@ -68,6 +70,23 @@ const MIDDAY_RUN_TITLE = IS_CHINESE ? '午间跑步' : 'Midday Run';
const AFTERNOON_RUN_TITLE = IS_CHINESE ? '午后跑步' : 'Afternoon Run';
const EVENING_RUN_TITLE = IS_CHINESE ? '傍晚跑步' : 'Evening Run';
const NIGHT_RUN_TITLE = IS_CHINESE ? '夜晚跑步' : 'Night Run';
const RUN_GENERIC_TITLE = IS_CHINESE ? '跑步' : 'Run';
const RUN_TRAIL_TITLE = IS_CHINESE ? '越野跑' : 'Trail Run';
const RUN_TREADMILL_TITLE = IS_CHINESE ? '跑步机' : 'Treadmill Run';
const HIKING_TITLE = IS_CHINESE ? '徒步' : 'Hiking';
const CYCLING_TITLE = IS_CHINESE ? '骑行' : 'Cycling';
const SKIING_TITLE = IS_CHINESE ? '滑雪' : 'Skiing';
const WALKING_TITLE = IS_CHINESE ? '步行' : 'Walking';

const ACTIVITY_TYPES = {
RUN_GENERIC_TITLE,
RUN_TRAIL_TITLE,
RUN_TREADMILL_TITLE,
HIKING_TITLE,
CYCLING_TITLE,
SKIING_TITLE,
WALKING_TITLE,
}

const RUN_TITLES = {
FULL_MARATHON_RUN_TITLE,
Expand Down Expand Up @@ -97,6 +116,8 @@ export {
MAP_HEIGHT,
PRIVACY_MODE,
LIGHTS_ON,
RICH_TITLE,
ACTIVITY_TYPES,
};

const nike = 'rgb(224,237,94)'; // if you want change the main color change here src/styles/variables.scss
Expand Down
Loading