geo-activity-playground 0.22.0__py3-none-any.whl → 0.23.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,6 +78,11 @@ class ActivityRepository:
78
78
  f"Adding {len(self._loose_activities)} activities to the repository …"
79
79
  )
80
80
  new_df = pd.DataFrame(self._loose_activities)
81
+ if not pd.api.types.is_dtype_equal(
82
+ new_df["start"].dtype, "datetime64[ns, UTC]"
83
+ ):
84
+ new_df["start"] = new_df["start"].dt.tz_localize("UTC")
85
+ new_df["start"] = new_df["start"].dt.tz_convert("UTC")
81
86
  if len(self.meta):
82
87
  new_ids_set = set(new_df["id"])
83
88
  is_kept = [
@@ -120,10 +125,11 @@ class ActivityRepository:
120
125
  def activity_ids(self) -> set[int]:
121
126
  return set(self.meta.index)
122
127
 
123
- def iter_activities(self, new_to_old=True) -> Iterator[ActivityMeta]:
128
+ def iter_activities(self, new_to_old=True, dropna=False) -> Iterator[ActivityMeta]:
124
129
  direction = -1 if new_to_old else 1
125
130
  for index, row in self.meta[::direction].iterrows():
126
- yield row
131
+ if not dropna or not pd.isna(row["start"]):
132
+ yield row
127
133
 
128
134
  @functools.lru_cache()
129
135
  def get_activity_by_id(self, id: int) -> ActivityMeta:
@@ -12,6 +12,7 @@ import numpy as np
12
12
  import pandas as pd
13
13
  import tcxreader.tcxreader
14
14
  import xmltodict
15
+ from pandas._libs import NaTType
15
16
 
16
17
  from geo_activity_playground.core.activities import ActivityMeta
17
18
  from geo_activity_playground.core.activities import embellish_single_time_series
@@ -126,8 +127,12 @@ def read_fit_activity(path: pathlib.Path, open) -> tuple[ActivityMeta, pd.DataFr
126
127
  and values.get("position_long", None)
127
128
  ):
128
129
  time = values["timestamp"]
129
- assert isinstance(time, datetime.datetime)
130
- time = time.astimezone(datetime.timezone.utc)
130
+ if isinstance(time, datetime.datetime):
131
+ time = time.astimezone(datetime.timezone.utc)
132
+ elif time is None or isinstance(time, int):
133
+ time = pd.NaT
134
+ else:
135
+ raise RuntimeError(f"Cannot parse time: {time} in {path}.")
131
136
  row = {
132
137
  "time": time,
133
138
  "latitude": values["position_lat"] / ((2**32) / 360),
@@ -202,10 +207,13 @@ def read_gpx_activity(path: pathlib.Path, open) -> pd.DataFrame:
202
207
  for point in segment.points:
203
208
  if isinstance(point.time, datetime.datetime):
204
209
  time = point.time
205
- else:
210
+ time = time.astimezone(datetime.timezone.utc)
211
+ elif isinstance(point.time, str):
206
212
  time = dateutil.parser.parse(str(point.time))
207
- assert isinstance(time, datetime.datetime)
208
- time = time.astimezone(datetime.timezone.utc)
213
+ time = time.astimezone(datetime.timezone.utc)
214
+ else:
215
+ time = pd.NaT
216
+ time.tz_localize("UTC")
209
217
  points.append((time, point.latitude, point.longitude, point.elevation))
210
218
 
211
219
  df = pd.DataFrame(points, columns=["time", "latitude", "longitude", "altitude"])
@@ -233,6 +241,7 @@ def read_tcx_activity(path: pathlib.Path, opener) -> pd.DataFrame:
233
241
  content = f.read().strip()
234
242
 
235
243
  stripped_file = pathlib.Path("Cache/temp.tcx")
244
+ stripped_file.parent.mkdir(exist_ok=True)
236
245
  with open(stripped_file, "wb") as f:
237
246
  f.write(content)
238
247
  data = tcx_reader.read(str(stripped_file))
@@ -94,6 +94,11 @@ def compute_tile_visits(
94
94
  if activity_ids_to_process:
95
95
  for zoom, new_rows in new_tile_history_rows.items():
96
96
  new_df = pd.DataFrame(new_rows)
97
+ if not pd.api.types.is_dtype_equal(
98
+ new_df["time"].dtype, "datetime64[ns, UTC]"
99
+ ):
100
+ new_df["time"] = new_df["time"].dt.tz_localize("UTC")
101
+ new_df["time"] = new_df["time"].dt.tz_convert("UTC")
97
102
  new_df.sort_values("time", inplace=True)
98
103
  tile_visits_accessor.histories[zoom] = pd.concat(
99
104
  [tile_visits_accessor.histories[zoom], new_df]
@@ -2,6 +2,7 @@ import datetime
2
2
  import logging
3
3
  import pathlib
4
4
  import shutil
5
+ import sys
5
6
  import traceback
6
7
  from typing import Optional
7
8
  from typing import Union
@@ -133,6 +134,13 @@ def import_from_strava_checkout(repository: ActivityRepository) -> None:
133
134
  dayfirst = False
134
135
  if activities.columns[0] == "Aktivitäts-ID":
135
136
  activities = pd.read_csv(checkout_path / "activities.csv", decimal=",")
137
+ if len(activities.columns) != len(EXPECTED_COLUMNS):
138
+ logger.error(
139
+ f"You are trying to import a Strava checkout where the `activities.csv` contains German column headers. In order to import this, we need to map these to the English ones. Unfortunately Strava has changed the number of columns. Your file has {len(activities.columns)} but we expect {len(EXPECTED_COLUMNS)}. This means that the program needs to be updated to match the new Strava export format. Please go to https://github.com/martin-ueding/geo-activity-playground/issues and open a new issue and share the following output in the ticket:"
140
+ )
141
+ print(activities.columns)
142
+ print(activities.dtypes)
143
+ sys.exit(1)
136
144
  activities.columns = EXPECTED_COLUMNS
137
145
  dayfirst = True
138
146
 
@@ -1,3 +1,6 @@
1
+ import json
2
+ import pathlib
3
+ import secrets
1
4
  import urllib
2
5
 
3
6
  from flask import Flask
@@ -328,6 +331,18 @@ def route_upload(
328
331
  return upload_controller.receive()
329
332
 
330
333
 
334
+ def get_secret_key():
335
+ secret_file = pathlib.Path("Cache/flask-secret.json")
336
+ if secret_file.exists():
337
+ with open(secret_file) as f:
338
+ secret = json.load(f)
339
+ else:
340
+ secret = secrets.token_hex()
341
+ with open(secret_file, "w") as f:
342
+ json.dump(secret, f)
343
+ return secret
344
+
345
+
331
346
  def webui_main(
332
347
  repository: ActivityRepository,
333
348
  tile_visit_accessor: TileVisitAccessor,
@@ -354,5 +369,6 @@ def webui_main(
354
369
  route_upload(app, repository, tile_visit_accessor, config)
355
370
 
356
371
  app.config["UPLOAD_FOLDER"] = "Activities"
372
+ app.secret_key = get_secret_key()
357
373
 
358
374
  app.run(host=host, port=port)
@@ -20,7 +20,9 @@ class EntryController:
20
20
  "latest_activities": [],
21
21
  }
22
22
 
23
- for activity in itertools.islice(self._repository.iter_activities(), 15):
23
+ for activity in itertools.islice(
24
+ self._repository.iter_activities(dropna=True), 15
25
+ ):
24
26
  time_series = self._repository.get_time_series(activity["id"])
25
27
  result["latest_activities"].append(
26
28
  {
@@ -112,8 +112,12 @@ def get_three_color_tiles(
112
112
  cmap_last = matplotlib.colormaps["plasma"]
113
113
  tile_dict = {}
114
114
  for tile, tile_data in tile_visits.items():
115
- first_age_days = (today - tile_data["first_time"].date()).days
116
- last_age_days = (today - tile_data["last_time"].date()).days
115
+ if not pd.isna(tile_data["first_time"]):
116
+ first_age_days = (today - tile_data["first_time"].date()).days
117
+ last_age_days = (today - tile_data["last_time"].date()).days
118
+ else:
119
+ first_age_days = 10000
120
+ last_age_days = 10000
117
121
  tile_dict[tile] = {
118
122
  "first_activity_id": str(tile_data["first_id"]),
119
123
  "first_activity_name": repository.get_activity_by_id(tile_data["first_id"])[
@@ -1,11 +1,9 @@
1
1
  import io
2
2
  import logging
3
3
  import pathlib
4
- import pickle
5
4
 
6
5
  import matplotlib.pylab as pl
7
6
  import numpy as np
8
- import pandas as pd
9
7
  from PIL import Image
10
8
  from PIL import ImageDraw
11
9
 
@@ -42,7 +40,7 @@ class HeatmapController:
42
40
  def render(self) -> dict:
43
41
  zoom = 14
44
42
  tiles = self.tile_histories[zoom]
45
- medians = tiles.median()
43
+ medians = tiles.median(skipna=True)
46
44
  median_lat, median_lon = get_tile_upper_left_lat_lon(
47
45
  medians["tile_x"], medians["tile_y"], zoom
48
46
  )
@@ -22,12 +22,12 @@
22
22
  <tbody>
23
23
  {% for year, month_data in monthly_distances.items() %}
24
24
  <tr>
25
- <td>{{ year }}</td>
25
+ <td>{{ year|round(0)|int }}</td>
26
26
  {% for month in range(1, 13) %}
27
27
  <td align="right">
28
28
  {% set distance = month_data[month] %}
29
29
  {% if distance %}
30
- <a href="/calendar/{{ year }}/{{ month }}">{{ distance|int() }} km</a>
30
+ <a href="/calendar/{{ year|round(0)|int }}/{{ month }}">{{ distance|int() }} km</a>
31
31
  {% else %}
32
32
  0 km
33
33
  {% endif %}
@@ -106,6 +106,16 @@
106
106
  </script>
107
107
  {% endmacro %}
108
108
 
109
+ {% with messages = get_flashed_messages(with_categories=true) %}
110
+ {% if messages %}
111
+ {% for category, message in messages %}
112
+ <div class="alert alert-{{ category }}" role="alert">
113
+ {{ message }}
114
+ </div>
115
+ {% endfor %}
116
+ {% endif %}
117
+ {% endwith %}
118
+
109
119
  {% block container %}
110
120
  {% endblock %}
111
121
 
@@ -5,17 +5,12 @@
5
5
  <div class="col">
6
6
  <h1>Upload Activity</h1>
7
7
 
8
+ {% if has_upload %}
8
9
  <form method="post" enctype="multipart/form-data" action="/upload/receive">
9
-
10
-
11
-
12
-
13
10
  <div class="mb-3">
14
11
  <label for="file1" class="form-label">Activity file</label>
15
12
  <input type="file" name="file" id="file1" class="form-control">
16
13
  </div>
17
-
18
-
19
14
  <div class="mb-3">
20
15
  <label for="directory" class="form-label">Target directory</label>
21
16
  <select name="directory" id="directory" class="form-select" aria-label="Default select example">
@@ -24,13 +19,19 @@
24
19
  {% endfor %}
25
20
  </select>
26
21
  </div>
27
-
28
-
22
+ <div class="mb-3">
23
+ <label for="password" class="form-label">Password</label>
24
+ <input type="password" name="password" id="password" class="form-control">
25
+ </div>
29
26
  <button type="submit" class="btn btn-primary">Upload</button>
30
-
31
27
  </form>
28
+ {% else %}
29
+ <p>You don't have an upload password set. In order to use this feature, add the following to your configuration
30
+ file:</p>
31
+ <code><pre>[upload]
32
+ password = "your unique password here"</pre></code>
33
+ {% endif %}
32
34
  </div>
33
35
  </div>
34
36
 
35
-
36
37
  {% endblock %}
@@ -7,7 +7,6 @@ from flask import flash
7
7
  from flask import redirect
8
8
  from flask import request
9
9
  from flask import Response
10
- from flask import url_for
11
10
  from werkzeug.utils import secure_filename
12
11
 
13
12
  from geo_activity_playground.core.activities import ActivityRepository
@@ -42,19 +41,27 @@ class UploadController:
42
41
  for root, dirs, files in os.walk("Activities"):
43
42
  directories.append(root)
44
43
  directories.sort()
45
- return {"directories": directories}
44
+ return {
45
+ "directories": directories,
46
+ "has_upload": "password" in self._config.get("upload", {}),
47
+ }
46
48
 
47
49
  def receive(self) -> Response:
48
50
  # check if the post request has the file part
49
51
  if "file" not in request.files:
50
- flash("No file part")
51
- return redirect(request.url)
52
+ flash("No file could be found. Did you select a file?", "warning")
53
+ return redirect("/upload")
54
+
55
+ if request.form["password"] != self._config["upload"]["password"]:
56
+ flash("Incorrect upload password!", "danger")
57
+ return redirect("/upload")
58
+
52
59
  file = request.files["file"]
53
60
  # If the user does not select a file, the browser submits an
54
61
  # empty file without a filename.
55
62
  if file.filename == "":
56
- flash("No selected file")
57
- return redirect(request.url)
63
+ flash("No selected file", "warning")
64
+ return redirect("/upload")
58
65
  if file:
59
66
  filename = secure_filename(file.filename)
60
67
  target_path = pathlib.Path(request.form["directory"]) / filename
@@ -76,6 +83,7 @@ class UploadController:
76
83
  skip_strava=True,
77
84
  )
78
85
  activity_id = get_file_hash(target_path)
86
+ flash(f"Activity was saved with ID {activity_id}.", "success")
79
87
  return redirect(f"/activity/{activity_id}")
80
88
 
81
89
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -1,8 +1,8 @@
1
1
  geo_activity_playground/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  geo_activity_playground/__main__.py,sha256=ssK4XW5RsyV_guZEVslRgOr-eYW3mKp29rOc8ZWwfqg,4131
3
3
  geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- geo_activity_playground/core/activities.py,sha256=w6wCPFCsSjM_WLYIzPGTDyAoXJcQ0FXa2PDZOyj_d2U,11016
5
- geo_activity_playground/core/activity_parsers.py,sha256=GYDaKi5lFo5YIThoRc0enmwwgWkTfJKCxiPsSkpHBxw,11440
4
+ geo_activity_playground/core/activities.py,sha256=gxn0-qU4HwuAFDChAUBylc_zf5GcyTwP2DrktgCW8ME,11357
5
+ geo_activity_playground/core/activity_parsers.py,sha256=2j6QZHjDYJR_w0baYUHHlzSVI1v1fOcCqdRuAkRHfhA,11887
6
6
  geo_activity_playground/core/cache_migrations.py,sha256=cz7zwoYtjAcFbUQee1UqeyHT0K2oiyfpPVh5tXkzk0U,3479
7
7
  geo_activity_playground/core/config.py,sha256=YjqCiEmIAa-GM1-JfBctMEsl8-I56pZyyDdTyPduOzw,477
8
8
  geo_activity_playground/core/coordinates.py,sha256=tDfr9mlXhK6E_MMIJ0vYWVCoH0Lq8uyuaqUgaa8i0jg,966
@@ -14,22 +14,22 @@ geo_activity_playground/core/test_tiles.py,sha256=zce1FxNfsSpOQt66jMehdQRVoNdl-o
14
14
  geo_activity_playground/core/tiles.py,sha256=VxPu9vdfKnxDxaYo5JSYmka9Dt3TDxg0zo3cYVJXVHc,3359
15
15
  geo_activity_playground/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  geo_activity_playground/explorer/grid_file.py,sha256=k6j6KBEk2a2BY-onE8SV5TJsERGGyOrlY4as__meWpA,3304
17
- geo_activity_playground/explorer/tile_visits.py,sha256=nlhxEMCiDXz9GXw1Q-rqa21Wgzafg51B3se6z3PMddE,11233
17
+ geo_activity_playground/explorer/tile_visits.py,sha256=698V43KsmSx_VZPThHqQ9XkvVwbULQu_qGydu0FVo0U,11495
18
18
  geo_activity_playground/explorer/video.py,sha256=ROAmV9shfJyqTgnXVD41KFORiwnRgVpEWenIq4hMCRM,4389
19
19
  geo_activity_playground/importers/directory.py,sha256=9jaryMpxUYA_mUdcHs79CvSsAATkQ7ZwnZpVuOXgesE,4321
20
20
  geo_activity_playground/importers/strava_api.py,sha256=iCxhDykJIazNdxm7EyNNZXuheddUZTYN6BSvAd9k-38,7602
21
- geo_activity_playground/importers/strava_checkout.py,sha256=0lR4aV1yGjIOcmkt4Govrei-mRttbajfXl-yKEgSdLs,7243
21
+ geo_activity_playground/importers/strava_checkout.py,sha256=4C5yvo3-f9kQF1y_8rSq4dxWnReRXBLYuCz6L4_pWMs,8008
22
22
  geo_activity_playground/importers/test_directory.py,sha256=ljXokx7q0OgtHvEdHftcQYEmZJUDVv3OOF5opklxdT4,724
23
23
  geo_activity_playground/importers/test_strava_api.py,sha256=4vX7wDr1a9aRh8myxNrIq6RwDBbP8ZeoXXPc10CAbW4,431
24
24
  geo_activity_playground/webui/activity_controller.py,sha256=yM7-EPcoKen-e9mt4D7UWo-E7Zk7Y39_-kr2IV7Km6U,12515
25
- geo_activity_playground/webui/app.py,sha256=K7VUvnVH8YWJHxMdjpVJd6iecXc9FgVBYeSceATlekg,12154
25
+ geo_activity_playground/webui/app.py,sha256=nimykpmtEeiQh3-wJ-QNbmute1e3J1c3DFmDRW45Qkk,12556
26
26
  geo_activity_playground/webui/calendar_controller.py,sha256=gRc0KjJ5r7dEZeiU1JHnJ-f_MBNlhRev2EZyqlHybko,2754
27
27
  geo_activity_playground/webui/config_controller.py,sha256=4M8mQc58Hkm-ssfYF1gKRepiAXFIzkZdIMRSbX-aI1U,320
28
28
  geo_activity_playground/webui/eddington_controller.py,sha256=86HISbRxmnkiCxclVacYzUe-PLgQ9Db7YuVN6F0a33M,2607
29
- geo_activity_playground/webui/entry_controller.py,sha256=_Qi7jDHBMv7xA-e5ZnY9g4Ala3Miq8bJBpYJm1unXYo,1866
29
+ geo_activity_playground/webui/entry_controller.py,sha256=AbF17UA7ySKV5WXsF6GlGCLpQiH97TmG2RPHM0XdW4U,1899
30
30
  geo_activity_playground/webui/equipment_controller.py,sha256=mDFQutTLE8H4w29rcfv-TDMXb448fRGEuVuKLR4ZQlM,2290
31
- geo_activity_playground/webui/explorer_controller.py,sha256=HU7EKpdS3Pt1y7_fByGJRMFIxPMb2Gz-hILxi8HlZns,10494
32
- geo_activity_playground/webui/heatmap_controller.py,sha256=JrXnMYr3rD8pHP_BPef19SoFweUBeZs0-x74RjLQAIw,5451
31
+ geo_activity_playground/webui/explorer_controller.py,sha256=8d0FFrG55ZlPQ5seQC2DegxIkPGIW7pvw8Jm4eJt3fg,10634
32
+ geo_activity_playground/webui/heatmap_controller.py,sha256=BDH8J3-RbQY0MPwsgkkYpS1o4RgfJVMGgKASAjowt88,5428
33
33
  geo_activity_playground/webui/locations_controller.py,sha256=xTIm-MN0B_0TDN6_J13HCkCnKLhipjYvlNWynGEpWuE,950
34
34
  geo_activity_playground/webui/search_controller.py,sha256=WG5TTVDFBqamrWKHbiEoZXo-73WqO5SynLAoDeU8o2A,859
35
35
  geo_activity_playground/webui/square_planner_controller.py,sha256=wYcNEviDgqyYxSrnwMD_5LnYXIazVH9plGX8RxG6oco,3464
@@ -51,7 +51,7 @@ geo_activity_playground/webui/templates/activity-lines.html.j2,sha256=5gB1aDjRgi
51
51
  geo_activity_playground/webui/templates/activity-name.html.j2,sha256=opHCj_zY3Xz1l3jIXUQvVdxBNi_D9C-Mdnbx2nQqTTQ,2381
52
52
  geo_activity_playground/webui/templates/activity.html.j2,sha256=ncj0K1471nRHtbBL9fqhGZ7a9DoJLyqt6ckX8rnhS28,4946
53
53
  geo_activity_playground/webui/templates/calendar-month.html.j2,sha256=rV96gOXS0eZU3Dokg8Wb7AJVXJvTPsw61OJoj8lRvt4,1767
54
- geo_activity_playground/webui/templates/calendar.html.j2,sha256=x3E1R6KoscVxfcndFePEA855tYz5UoHDSrDbjkhuOOs,1349
54
+ geo_activity_playground/webui/templates/calendar.html.j2,sha256=kJjJ0s0dSYV2DjtiUrcbfjTlR02qiHO7VoQbLgIx7Bo,1375
55
55
  geo_activity_playground/webui/templates/config.html.j2,sha256=pmec-TqSl5CVznQlyHuC91o18qa0ZQWHXxSBrlV4au4,796
56
56
  geo_activity_playground/webui/templates/eddington.html.j2,sha256=yl75IzWeIkFpwPj8FjTrzJsz_f-qdETPmNnAGLPJuL8,487
57
57
  geo_activity_playground/webui/templates/equipment.html.j2,sha256=dNuezVKJNJZbQ0y2-AYlXNHpncTbtOSDNLqNswRTxaI,1320
@@ -59,16 +59,16 @@ geo_activity_playground/webui/templates/explorer.html.j2,sha256=ORaBlDxqg9YTxnOC
59
59
  geo_activity_playground/webui/templates/heatmap.html.j2,sha256=VQZBLv5Rw6QAGosA50darHdpOjSUsYfx7HkWhqt0YnQ,1208
60
60
  geo_activity_playground/webui/templates/index.html.j2,sha256=5eDy9F9HGGAZRZB9siFeqgkrGS86ZoV4JvM8ahU739M,2163
61
61
  geo_activity_playground/webui/templates/locations.html.j2,sha256=uexZpLjd9bm3gt3YZQA2WTPlwln7TuOc6OkIOOfJ6ik,1060
62
- geo_activity_playground/webui/templates/page.html.j2,sha256=X0W_ti-0rhiCcmFPvSobX3PxsnezXBT01M51i4lHTro,6804
62
+ geo_activity_playground/webui/templates/page.html.j2,sha256=SVlT0gs6CvDTMTjMlGquxjivROf5DOaIT_3-pwpRtv8,7118
63
63
  geo_activity_playground/webui/templates/search.html.j2,sha256=1UGCQU49oQgSJUB1OJ2kgAOuFMPVp0XXAQtmUnffzBI,1066
64
64
  geo_activity_playground/webui/templates/square-planner.html.j2,sha256=aIB0ql5qW4HXfp0ENksYYOk9vTgBitwyHJX5W7bqkeY,6512
65
65
  geo_activity_playground/webui/templates/strava-connect.html.j2,sha256=vLMqTnTV-DZJ1FHRjpm4OMgbABMwZQvbs8Ru9baKeBg,1111
66
66
  geo_activity_playground/webui/templates/summary.html.j2,sha256=eEwcPOURJ-uT89jeJGZHq_5pSq56_fTC7z-j_m5nQiA,471
67
- geo_activity_playground/webui/templates/upload.html.j2,sha256=VOiUdxBJD6ueO7T-AnZjIjPs-vof9MnAX25qlN0TsWA,967
67
+ geo_activity_playground/webui/templates/upload.html.j2,sha256=BodcWv4PaiooAyyNRuLeNQEzclk_eRETZIBQhMO6ACw,1461
68
68
  geo_activity_playground/webui/tile_controller.py,sha256=PISh4vKs27b-LxFfTARtr5RAwHFresA1Kw1MDcERSRU,1221
69
- geo_activity_playground/webui/upload_controller.py,sha256=QvQp2uuVhWxG07ZpT4r_hl4cEBBlS9X8V3O5glpbiG8,3686
70
- geo_activity_playground-0.22.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
71
- geo_activity_playground-0.22.0.dist-info/METADATA,sha256=4Vg5FqiZEA2kkKktTVpLbmC2lDo22A20RaKKZrcvpwY,1625
72
- geo_activity_playground-0.22.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
73
- geo_activity_playground-0.22.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
74
- geo_activity_playground-0.22.0.dist-info/RECORD,,
69
+ geo_activity_playground/webui/upload_controller.py,sha256=V8Lksi7pHhLXg3TinzxyBTt8fwoG5DO5y7dZi0fY9iI,4056
70
+ geo_activity_playground-0.23.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
71
+ geo_activity_playground-0.23.0.dist-info/METADATA,sha256=wWjwOEawy_aLmKLALINY0vhszfp1c0WIe56YMmkwET8,1625
72
+ geo_activity_playground-0.23.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
73
+ geo_activity_playground-0.23.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
74
+ geo_activity_playground-0.23.0.dist-info/RECORD,,