intervals-summits 0.1.0__tar.gz
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.
- intervals_summits-0.1.0/LICENSE +21 -0
- intervals_summits-0.1.0/PKG-INFO +223 -0
- intervals_summits-0.1.0/README.md +204 -0
- intervals_summits-0.1.0/pyproject.toml +34 -0
- intervals_summits-0.1.0/setup.cfg +4 -0
- intervals_summits-0.1.0/src/intervals_summits/__init__.py +2 -0
- intervals_summits-0.1.0/src/intervals_summits/__main__.py +5 -0
- intervals_summits-0.1.0/src/intervals_summits/cli.py +266 -0
- intervals_summits-0.1.0/src/intervals_summits/config.py +142 -0
- intervals_summits-0.1.0/src/intervals_summits/fields.py +156 -0
- intervals_summits-0.1.0/src/intervals_summits/intervals.py +111 -0
- intervals_summits-0.1.0/src/intervals_summits/match.py +63 -0
- intervals_summits-0.1.0/src/intervals_summits/osm.py +160 -0
- intervals_summits-0.1.0/src/intervals_summits/page.py +164 -0
- intervals_summits-0.1.0/src/intervals_summits/store.py +163 -0
- intervals_summits-0.1.0/src/intervals_summits/sync.py +239 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/PKG-INFO +223 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/SOURCES.txt +24 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/dependency_links.txt +1 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/entry_points.txt +2 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/requires.txt +4 -0
- intervals_summits-0.1.0/src/intervals_summits.egg-info/top_level.txt +1 -0
- intervals_summits-0.1.0/tests/test_cli.py +76 -0
- intervals_summits-0.1.0/tests/test_fields.py +85 -0
- intervals_summits-0.1.0/tests/test_match_osm.py +62 -0
- intervals_summits-0.1.0/tests/test_sync_page.py +194 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Schnederle-Wagner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: intervals-summits
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Summits, huts and passes on your Intervals.icu activities, from your GPS tracks and OpenStreetMap – with a summit log page
|
|
5
|
+
Author: Andreas Schnederle-Wagner
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/futureweb/intervals-summits
|
|
8
|
+
Keywords: intervals.icu,summits,peaks,openstreetmap,hiking,ski touring
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests>=2.31
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# intervals-summits
|
|
21
|
+
|
|
22
|
+
[](https://github.com/futureweb/intervals-summits/actions/workflows/tests.yml)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+

|
|
25
|
+
[](https://github.com/futureweb/garmin-intervals-bridge)
|
|
26
|
+
[](https://github.com/futureweb/intervals-mcp-server)
|
|
27
|
+
|
|
28
|
+
**Summits, huts and passes on your [Intervals.icu](https://intervals.icu) activities**, found in your
|
|
29
|
+
own GPS tracks with OpenStreetMap, plus your personal **summit log**.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
- **In Intervals:** every activity gets its summits in a field, e.g. `Maukspitze (2231 m), Ackerlspitze (2329 m)`.
|
|
34
|
+
Two more fields: the number of summits, which you can chart per week, month or year, and the
|
|
35
|
+
highest point of the activity.
|
|
36
|
+
- **The summit log page:**
|
|
37
|
+
- your summits, huts and passes over time;
|
|
38
|
+
- your top 10/20/50/100 (most visited or highest);
|
|
39
|
+
- a count per year;
|
|
40
|
+
- a searchable log, every entry linked to its activity in Intervals.
|
|
41
|
+
- **It works with every activity that has a GPS track**, whatever it came from: Garmin, Wahoo, Coros,
|
|
42
|
+
Suunto, Polar, a Strava import or an upload. Old ones too: one backfill matches your whole history.
|
|
43
|
+
- It needs nothing but your Intervals API key.
|
|
44
|
+
|
|
45
|
+
**Sister projects** (each works on its own, together they do more):
|
|
46
|
+
|
|
47
|
+
- [**Garmin Intervals Bridge**](https://github.com/futureweb/garmin-intervals-bridge) puts back what
|
|
48
|
+
Garmin strips from the FIT files it sends to Intervals.icu and syncs the wellness data the official
|
|
49
|
+
sync misses. intervals-summits runs next to it with the same settings, and the bridge tells it when
|
|
50
|
+
there is something new ([how](#next-to-garmin-intervals-bridge-the-bridge-says-when-there-is-something-new)).
|
|
51
|
+
- [**Futureweb Intervals MCP**](https://github.com/futureweb/intervals-mcp-server) lets ChatGPT, Claude
|
|
52
|
+
and any MCP client read and analyse your Intervals data. It reads the `Summits` fields like any other,
|
|
53
|
+
so you can ask your assistant which summits you climbed this year, or how often.
|
|
54
|
+
|
|
55
|
+
## How a summit is found
|
|
56
|
+
|
|
57
|
+
- **Peaks** (`natural=peak`/`volcano`): the track comes within **100 m**, *and* the track's altitude
|
|
58
|
+
there is at most **80 m below** the summit. A path along the slope below a summit does not count;
|
|
59
|
+
a gondola ride to the top does.
|
|
60
|
+
- **Huts** (`tourism=alpine_hut`/`wilderness_hut`) and **passes** (`mountain_pass=yes`,
|
|
61
|
+
`natural=saddle`): within **60 m**.
|
|
62
|
+
- One entry per name and activity, in the order of the track.
|
|
63
|
+
|
|
64
|
+
These rules were calibrated against peakproject's summit lines on 25 activities: 38 of 40 summits
|
|
65
|
+
were found. A summit that OpenStreetMap does not have, or has under another name, cannot be found.
|
|
66
|
+
Improve the map, and `recompute` picks it up.
|
|
67
|
+
|
|
68
|
+
**Stricter or more generous:** the four numbers can be set (`SUMMITS_PEAK_RADIUS`,
|
|
69
|
+
`SUMMITS_PEAK_BELOW`, `SUMMITS_HUT_RADIUS`, `SUMMITS_PASS_RADIUS`, see [Settings](#settings)). For example
|
|
70
|
+
`SUMMITS_PEAK_RADIUS=50` and `SUMMITS_PEAK_BELOW=30` count only summits you were really on. Best set
|
|
71
|
+
them before the first backfill. Changed later, they apply to new activities at once, and
|
|
72
|
+
`recompute --from 2010-01-01` applies them to your past (from the stored tracks, without downloading them
|
|
73
|
+
again). An activity left with no summit at all keeps the values written before; clear those by hand.
|
|
74
|
+
|
|
75
|
+
## What lands in Intervals
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
| Field (code) | Type | Content |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `Summits` | text | `Maukspitze (2231 m), Ackerlspitze (2329 m)`: the summits in the order you reached them |
|
|
82
|
+
| `SummitCount` | number, summed per day | how many summits; chart them per week, month or year |
|
|
83
|
+
| `HighestPoint` | number, max per day | **only where empty.** Intervals fills it from a FIT file itself; for GPX/TCX and old activities the tool takes the highest point of the track |
|
|
84
|
+
| `HutsPasses` | text | huts and passes. Off by default: `SUMMITS_FIELDS=…,HutsPasses` |
|
|
85
|
+
| description | one line | optional, `SUMMITS_DESCRIPTION=true`: `⛰️ Summits: Maukspitze (2231 m), …`, **so Intervals' search finds them** (see below) |
|
|
86
|
+
|
|
87
|
+
The tool creates missing fields itself.
|
|
88
|
+
- **Codes are the same for everyone**, so charts and scripts can be shared.
|
|
89
|
+
- **Names** are English, or German with `SUMMITS_LANGUAGE=de`. You may rename them in Intervals freely:
|
|
90
|
+
the tool goes by the code.
|
|
91
|
+
|
|
92
|
+
**Your edits stay.** A value you typed, or another tool wrote, is never overwritten (unless
|
|
93
|
+
`--force`). The tool only updates what it wrote itself. In the description it only ever touches its
|
|
94
|
+
own line: it reads the description again right before writing, adds the line at the end after a blank
|
|
95
|
+
line, and leaves every other character as it is. If the description already names all the summits
|
|
96
|
+
(peakproject's lines, for example), it adds nothing.
|
|
97
|
+
|
|
98
|
+
### Seeing them in Intervals
|
|
99
|
+
|
|
100
|
+
- **On the activity:** the fields show above the charts. Which fields show is chosen per sport, and
|
|
101
|
+
they can go into the summary layout.
|
|
102
|
+
- **Filters:** in the activity list and on the totals page, filters can use custom fields, e.g. `SummitCount` at least 1 for all your summit days.
|
|
103
|
+
- **Fitness charts:** `SummitCount` (summed per day) and `HighestPoint` (maximum per day) can be
|
|
104
|
+
plotted; on a weekly or monthly scale you get summits per week or month.
|
|
105
|
+
- **Search:** Intervals' search looks at names and descriptions, not at custom fields. Turn on
|
|
106
|
+
`SUMMITS_DESCRIPTION=true`, and searching a summit finds every activity that reached it, also
|
|
107
|
+
where it is not in the name:
|
|
108
|
+
|
|
109
|
+

|
|
110
|
+
|
|
111
|
+
## Installing and running
|
|
112
|
+
|
|
113
|
+
### Docker (NAS, server, next to the bridge)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
curl -O https://raw.githubusercontent.com/futureweb/intervals-summits/main/compose.yaml
|
|
117
|
+
curl -o .env https://raw.githubusercontent.com/futureweb/intervals-summits/main/.env.example # put your key in
|
|
118
|
+
docker compose up -d # a dry run: the summit log fills, nothing is written
|
|
119
|
+
docker compose logs -f # what it would write
|
|
120
|
+
docker compose run --rm summits backfill --from 2015-01-01 --apply # your past
|
|
121
|
+
# then command: ["run", "--apply", "--ui", "--host", "0.0.0.0"] in compose.yaml and up -d again
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The summit log is at **http://localhost:8766/**. Next to the bridge, add the service to the bridge's
|
|
125
|
+
`compose.yaml` and use its `.env`: the same key works.
|
|
126
|
+
|
|
127
|
+
### Home Assistant
|
|
128
|
+
|
|
129
|
+
Add `https://github.com/futureweb/intervals-summits` as an app repository (Settings → Apps → App store
|
|
130
|
+
→ ⋮ → Repositories). Then install **Intervals Summits**, enter your Intervals API key, optionally
|
|
131
|
+
"Backfill from", and start it. The summit log appears in the sidebar. Switch on *Write to Intervals*
|
|
132
|
+
once the dry run looks right.
|
|
133
|
+
|
|
134
|
+
### On your computer (Windows, Mac, Linux), Python 3.12+
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install intervals-summits # Windows: py -m pip install intervals-summits
|
|
138
|
+
intervals-summits backfill --from 2015-01-01 # dry run over your past
|
|
139
|
+
intervals-summits backfill --from 2015-01-01 --apply # … and write
|
|
140
|
+
intervals-summits run --apply --ui # keep running; the summit log on http://127.0.0.1:8766/
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Settings come from the environment, else from `./.env`, `~/IntervalsSummits/.env` or the bridge's
|
|
144
|
+
`~/GarminBridge/.env`. A bridge set up on the same computer is enough: nothing to configure twice.
|
|
145
|
+
Instead of keeping it running, `intervals-summits sync --apply` once a day (Task Scheduler, cron)
|
|
146
|
+
works as well. It catches up on the days since its last run.
|
|
147
|
+
|
|
148
|
+
### Next to garmin-intervals-bridge: the bridge says when there is something new
|
|
149
|
+
|
|
150
|
+
The bridge looks for new activities in Intervals every few minutes anyway, whatever their source. From
|
|
151
|
+
version 0.12.9 on it passes that on: set `BRIDGE_NOTIFY_URL` in the bridge's settings, and summits
|
|
152
|
+
syncs right after the bridge instead of looking every 15 minutes itself. On its own it then looks only
|
|
153
|
+
every 3 hours, in case the bridge stops. This needs `run --ui`, as in the Docker and Home Assistant
|
|
154
|
+
setups.
|
|
155
|
+
|
|
156
|
+
| The bridge runs … | `BRIDGE_NOTIFY_URL` |
|
|
157
|
+
|---|---|
|
|
158
|
+
| on the same computer | `http://127.0.0.1:8766/sync` |
|
|
159
|
+
| in the same `compose.yaml` | `http://summits:8766/sync` |
|
|
160
|
+
| as a Home Assistant app | `http://<hostname>:8766/sync`, the hostname is on the *Info* tab of the Intervals Summits app |
|
|
161
|
+
|
|
162
|
+
`POST /sync` only says "something is new": it takes no data, and summits keeps it to one sync a
|
|
163
|
+
minute. `intervals-summits status` shows the bridge's last note. On a Linux server with systemd,
|
|
164
|
+
[`deploy/intervals-summits-run.service`](deploy/intervals-summits-run.service) keeps it running
|
|
165
|
+
(instead of the timer in `deploy/`).
|
|
166
|
+
|
|
167
|
+
### Commands
|
|
168
|
+
|
|
169
|
+
| Command | |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `sync [--apply] [--force]` | The recent days; after a pause it reads back to the last run (at most 14 days) |
|
|
172
|
+
| `backfill --from DATE [--to DATE] [--apply]` | The past: tracks first, then OpenStreetMap for all of them at once, then matching and writing. Resumable. |
|
|
173
|
+
| `recompute --from DATE [--apply]` | Match again, e.g. after OpenStreetMap got better. Writes only differences. |
|
|
174
|
+
| `run [--apply] [--ui] [--host H] [--port 8766]` | Keeps running: a sync every 15 minutes (every 3 hours while the bridge reports new activities), optionally with the summit log page |
|
|
175
|
+
| `ui [--host H] [--port 8766]` | Only the summit log page |
|
|
176
|
+
| `page [--out FILE] [--language de]` | The summit log as one self-contained HTML file, to keep or share |
|
|
177
|
+
| `status [--check] [--fields]` | Counts and the last sync; `--check` for health checks (exit 1 when no sync came for long) |
|
|
178
|
+
| `fields [--apply]` | Create the activity fields now |
|
|
179
|
+
|
|
180
|
+
Without `--apply`, nothing is written to Intervals. The summit log page still fills.
|
|
181
|
+
|
|
182
|
+
`python examples/demo.py demo.html` builds a summit log with made-up data (the screenshot above).
|
|
183
|
+
|
|
184
|
+
## Settings
|
|
185
|
+
|
|
186
|
+
| Variable | Default | |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `INTERVALS_API_KEY` | – | Intervals → Settings → Developer Settings |
|
|
189
|
+
| `INTERVALS_ATHLETE_ID` | `0` | `0` = the key's owner |
|
|
190
|
+
| `SUMMITS_DATA_DIR` | `~/GarminBridge/summits` next to a bridge, else `~/IntervalsSummits` | database, tracks, `summits.html` |
|
|
191
|
+
| `SUMMITS_LANGUAGE` | `en` | `en` or `de`: names of new fields, the page, the description line |
|
|
192
|
+
| `SUMMITS_NAME_LANGUAGE` | – | e.g. `en`: OpenStreetMap's `name:en` where there is one, else the local name |
|
|
193
|
+
| `SUMMITS_FIELDS` | `Summits,SummitCount,HighestPoint` | add `HutsPasses` for huts and passes |
|
|
194
|
+
| `SUMMITS_DESCRIPTION` | `false` | `true`: the `⛰️ Summits:` line in the description (searchable) |
|
|
195
|
+
| `SUMMITS_TYPES` | every outdoor type | only these activity types, e.g. `Hike,BackcountrySki` |
|
|
196
|
+
| `SUMMITS_PEAK_RADIUS` | `100` | metres (10–200): how close the track must come to a summit |
|
|
197
|
+
| `SUMMITS_PEAK_BELOW` | `80` | metres (0–500): how far below the summit the track may be there |
|
|
198
|
+
| `SUMMITS_HUT_RADIUS` | `60` | metres (10–200) for huts |
|
|
199
|
+
| `SUMMITS_PASS_RADIUS` | `60` | metres (10–200) for passes |
|
|
200
|
+
| `SUMMITS_LOOKBACK_DAYS` | `3` | how far `sync` looks back |
|
|
201
|
+
| `SUMMITS_INTERVAL_MINUTES` | `15` | `run`: minutes between syncs |
|
|
202
|
+
| `SUMMITS_KEEP_TRACKS` | `true` | keep the tracks: `recompute` without downloading them again (~20 KB per activity) |
|
|
203
|
+
| `SUMMITS_OVERPASS_URL` | overpass-api.de | another Overpass server |
|
|
204
|
+
| `SUMMITS_PAGE_TITLE` | Summit log / Gipfelbuch | |
|
|
205
|
+
|
|
206
|
+
## Fair to Intervals and OpenStreetMap
|
|
207
|
+
|
|
208
|
+
- **Intervals:** the API key is usually shared with the bridge. The tool paces itself (at most
|
|
209
|
+
about 1.5 requests a second) and waits whenever Intervals answers 429. A sync with nothing new is
|
|
210
|
+
one small request.
|
|
211
|
+
- **OpenStreetMap:** points come from the Overpass API and are cached in 0.2° cells for 180 days,
|
|
212
|
+
so your usual tours cost no query after the first time. A backfill asks for whole regions at
|
|
213
|
+
once (16 years and 1,800 tours, in the Alps and on a few trips abroad: 18 queries). One query at a time, with a pause between
|
|
214
|
+
queries, and patience on 429/5xx.
|
|
215
|
+
|
|
216
|
+
## Privacy
|
|
217
|
+
|
|
218
|
+
Your data stays on your computer and in your Intervals account. Overpass learns which 0.2° cells
|
|
219
|
+
(about 22 × 15 km) you need, never a track or an activity. Details: [PRIVACY.md](PRIVACY.md).
|
|
220
|
+
|
|
221
|
+
## Licence
|
|
222
|
+
|
|
223
|
+
MIT. Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors, ODbL.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# intervals-summits
|
|
2
|
+
|
|
3
|
+
[](https://github.com/futureweb/intervals-summits/actions/workflows/tests.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+
[](https://github.com/futureweb/garmin-intervals-bridge)
|
|
7
|
+
[](https://github.com/futureweb/intervals-mcp-server)
|
|
8
|
+
|
|
9
|
+
**Summits, huts and passes on your [Intervals.icu](https://intervals.icu) activities**, found in your
|
|
10
|
+
own GPS tracks with OpenStreetMap, plus your personal **summit log**.
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
- **In Intervals:** every activity gets its summits in a field, e.g. `Maukspitze (2231 m), Ackerlspitze (2329 m)`.
|
|
15
|
+
Two more fields: the number of summits, which you can chart per week, month or year, and the
|
|
16
|
+
highest point of the activity.
|
|
17
|
+
- **The summit log page:**
|
|
18
|
+
- your summits, huts and passes over time;
|
|
19
|
+
- your top 10/20/50/100 (most visited or highest);
|
|
20
|
+
- a count per year;
|
|
21
|
+
- a searchable log, every entry linked to its activity in Intervals.
|
|
22
|
+
- **It works with every activity that has a GPS track**, whatever it came from: Garmin, Wahoo, Coros,
|
|
23
|
+
Suunto, Polar, a Strava import or an upload. Old ones too: one backfill matches your whole history.
|
|
24
|
+
- It needs nothing but your Intervals API key.
|
|
25
|
+
|
|
26
|
+
**Sister projects** (each works on its own, together they do more):
|
|
27
|
+
|
|
28
|
+
- [**Garmin Intervals Bridge**](https://github.com/futureweb/garmin-intervals-bridge) puts back what
|
|
29
|
+
Garmin strips from the FIT files it sends to Intervals.icu and syncs the wellness data the official
|
|
30
|
+
sync misses. intervals-summits runs next to it with the same settings, and the bridge tells it when
|
|
31
|
+
there is something new ([how](#next-to-garmin-intervals-bridge-the-bridge-says-when-there-is-something-new)).
|
|
32
|
+
- [**Futureweb Intervals MCP**](https://github.com/futureweb/intervals-mcp-server) lets ChatGPT, Claude
|
|
33
|
+
and any MCP client read and analyse your Intervals data. It reads the `Summits` fields like any other,
|
|
34
|
+
so you can ask your assistant which summits you climbed this year, or how often.
|
|
35
|
+
|
|
36
|
+
## How a summit is found
|
|
37
|
+
|
|
38
|
+
- **Peaks** (`natural=peak`/`volcano`): the track comes within **100 m**, *and* the track's altitude
|
|
39
|
+
there is at most **80 m below** the summit. A path along the slope below a summit does not count;
|
|
40
|
+
a gondola ride to the top does.
|
|
41
|
+
- **Huts** (`tourism=alpine_hut`/`wilderness_hut`) and **passes** (`mountain_pass=yes`,
|
|
42
|
+
`natural=saddle`): within **60 m**.
|
|
43
|
+
- One entry per name and activity, in the order of the track.
|
|
44
|
+
|
|
45
|
+
These rules were calibrated against peakproject's summit lines on 25 activities: 38 of 40 summits
|
|
46
|
+
were found. A summit that OpenStreetMap does not have, or has under another name, cannot be found.
|
|
47
|
+
Improve the map, and `recompute` picks it up.
|
|
48
|
+
|
|
49
|
+
**Stricter or more generous:** the four numbers can be set (`SUMMITS_PEAK_RADIUS`,
|
|
50
|
+
`SUMMITS_PEAK_BELOW`, `SUMMITS_HUT_RADIUS`, `SUMMITS_PASS_RADIUS`, see [Settings](#settings)). For example
|
|
51
|
+
`SUMMITS_PEAK_RADIUS=50` and `SUMMITS_PEAK_BELOW=30` count only summits you were really on. Best set
|
|
52
|
+
them before the first backfill. Changed later, they apply to new activities at once, and
|
|
53
|
+
`recompute --from 2010-01-01` applies them to your past (from the stored tracks, without downloading them
|
|
54
|
+
again). An activity left with no summit at all keeps the values written before; clear those by hand.
|
|
55
|
+
|
|
56
|
+
## What lands in Intervals
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
| Field (code) | Type | Content |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `Summits` | text | `Maukspitze (2231 m), Ackerlspitze (2329 m)`: the summits in the order you reached them |
|
|
63
|
+
| `SummitCount` | number, summed per day | how many summits; chart them per week, month or year |
|
|
64
|
+
| `HighestPoint` | number, max per day | **only where empty.** Intervals fills it from a FIT file itself; for GPX/TCX and old activities the tool takes the highest point of the track |
|
|
65
|
+
| `HutsPasses` | text | huts and passes. Off by default: `SUMMITS_FIELDS=…,HutsPasses` |
|
|
66
|
+
| description | one line | optional, `SUMMITS_DESCRIPTION=true`: `⛰️ Summits: Maukspitze (2231 m), …`, **so Intervals' search finds them** (see below) |
|
|
67
|
+
|
|
68
|
+
The tool creates missing fields itself.
|
|
69
|
+
- **Codes are the same for everyone**, so charts and scripts can be shared.
|
|
70
|
+
- **Names** are English, or German with `SUMMITS_LANGUAGE=de`. You may rename them in Intervals freely:
|
|
71
|
+
the tool goes by the code.
|
|
72
|
+
|
|
73
|
+
**Your edits stay.** A value you typed, or another tool wrote, is never overwritten (unless
|
|
74
|
+
`--force`). The tool only updates what it wrote itself. In the description it only ever touches its
|
|
75
|
+
own line: it reads the description again right before writing, adds the line at the end after a blank
|
|
76
|
+
line, and leaves every other character as it is. If the description already names all the summits
|
|
77
|
+
(peakproject's lines, for example), it adds nothing.
|
|
78
|
+
|
|
79
|
+
### Seeing them in Intervals
|
|
80
|
+
|
|
81
|
+
- **On the activity:** the fields show above the charts. Which fields show is chosen per sport, and
|
|
82
|
+
they can go into the summary layout.
|
|
83
|
+
- **Filters:** in the activity list and on the totals page, filters can use custom fields, e.g. `SummitCount` at least 1 for all your summit days.
|
|
84
|
+
- **Fitness charts:** `SummitCount` (summed per day) and `HighestPoint` (maximum per day) can be
|
|
85
|
+
plotted; on a weekly or monthly scale you get summits per week or month.
|
|
86
|
+
- **Search:** Intervals' search looks at names and descriptions, not at custom fields. Turn on
|
|
87
|
+
`SUMMITS_DESCRIPTION=true`, and searching a summit finds every activity that reached it, also
|
|
88
|
+
where it is not in the name:
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
## Installing and running
|
|
93
|
+
|
|
94
|
+
### Docker (NAS, server, next to the bridge)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
curl -O https://raw.githubusercontent.com/futureweb/intervals-summits/main/compose.yaml
|
|
98
|
+
curl -o .env https://raw.githubusercontent.com/futureweb/intervals-summits/main/.env.example # put your key in
|
|
99
|
+
docker compose up -d # a dry run: the summit log fills, nothing is written
|
|
100
|
+
docker compose logs -f # what it would write
|
|
101
|
+
docker compose run --rm summits backfill --from 2015-01-01 --apply # your past
|
|
102
|
+
# then command: ["run", "--apply", "--ui", "--host", "0.0.0.0"] in compose.yaml and up -d again
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The summit log is at **http://localhost:8766/**. Next to the bridge, add the service to the bridge's
|
|
106
|
+
`compose.yaml` and use its `.env`: the same key works.
|
|
107
|
+
|
|
108
|
+
### Home Assistant
|
|
109
|
+
|
|
110
|
+
Add `https://github.com/futureweb/intervals-summits` as an app repository (Settings → Apps → App store
|
|
111
|
+
→ ⋮ → Repositories). Then install **Intervals Summits**, enter your Intervals API key, optionally
|
|
112
|
+
"Backfill from", and start it. The summit log appears in the sidebar. Switch on *Write to Intervals*
|
|
113
|
+
once the dry run looks right.
|
|
114
|
+
|
|
115
|
+
### On your computer (Windows, Mac, Linux), Python 3.12+
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install intervals-summits # Windows: py -m pip install intervals-summits
|
|
119
|
+
intervals-summits backfill --from 2015-01-01 # dry run over your past
|
|
120
|
+
intervals-summits backfill --from 2015-01-01 --apply # … and write
|
|
121
|
+
intervals-summits run --apply --ui # keep running; the summit log on http://127.0.0.1:8766/
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Settings come from the environment, else from `./.env`, `~/IntervalsSummits/.env` or the bridge's
|
|
125
|
+
`~/GarminBridge/.env`. A bridge set up on the same computer is enough: nothing to configure twice.
|
|
126
|
+
Instead of keeping it running, `intervals-summits sync --apply` once a day (Task Scheduler, cron)
|
|
127
|
+
works as well. It catches up on the days since its last run.
|
|
128
|
+
|
|
129
|
+
### Next to garmin-intervals-bridge: the bridge says when there is something new
|
|
130
|
+
|
|
131
|
+
The bridge looks for new activities in Intervals every few minutes anyway, whatever their source. From
|
|
132
|
+
version 0.12.9 on it passes that on: set `BRIDGE_NOTIFY_URL` in the bridge's settings, and summits
|
|
133
|
+
syncs right after the bridge instead of looking every 15 minutes itself. On its own it then looks only
|
|
134
|
+
every 3 hours, in case the bridge stops. This needs `run --ui`, as in the Docker and Home Assistant
|
|
135
|
+
setups.
|
|
136
|
+
|
|
137
|
+
| The bridge runs … | `BRIDGE_NOTIFY_URL` |
|
|
138
|
+
|---|---|
|
|
139
|
+
| on the same computer | `http://127.0.0.1:8766/sync` |
|
|
140
|
+
| in the same `compose.yaml` | `http://summits:8766/sync` |
|
|
141
|
+
| as a Home Assistant app | `http://<hostname>:8766/sync`, the hostname is on the *Info* tab of the Intervals Summits app |
|
|
142
|
+
|
|
143
|
+
`POST /sync` only says "something is new": it takes no data, and summits keeps it to one sync a
|
|
144
|
+
minute. `intervals-summits status` shows the bridge's last note. On a Linux server with systemd,
|
|
145
|
+
[`deploy/intervals-summits-run.service`](deploy/intervals-summits-run.service) keeps it running
|
|
146
|
+
(instead of the timer in `deploy/`).
|
|
147
|
+
|
|
148
|
+
### Commands
|
|
149
|
+
|
|
150
|
+
| Command | |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `sync [--apply] [--force]` | The recent days; after a pause it reads back to the last run (at most 14 days) |
|
|
153
|
+
| `backfill --from DATE [--to DATE] [--apply]` | The past: tracks first, then OpenStreetMap for all of them at once, then matching and writing. Resumable. |
|
|
154
|
+
| `recompute --from DATE [--apply]` | Match again, e.g. after OpenStreetMap got better. Writes only differences. |
|
|
155
|
+
| `run [--apply] [--ui] [--host H] [--port 8766]` | Keeps running: a sync every 15 minutes (every 3 hours while the bridge reports new activities), optionally with the summit log page |
|
|
156
|
+
| `ui [--host H] [--port 8766]` | Only the summit log page |
|
|
157
|
+
| `page [--out FILE] [--language de]` | The summit log as one self-contained HTML file, to keep or share |
|
|
158
|
+
| `status [--check] [--fields]` | Counts and the last sync; `--check` for health checks (exit 1 when no sync came for long) |
|
|
159
|
+
| `fields [--apply]` | Create the activity fields now |
|
|
160
|
+
|
|
161
|
+
Without `--apply`, nothing is written to Intervals. The summit log page still fills.
|
|
162
|
+
|
|
163
|
+
`python examples/demo.py demo.html` builds a summit log with made-up data (the screenshot above).
|
|
164
|
+
|
|
165
|
+
## Settings
|
|
166
|
+
|
|
167
|
+
| Variable | Default | |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `INTERVALS_API_KEY` | – | Intervals → Settings → Developer Settings |
|
|
170
|
+
| `INTERVALS_ATHLETE_ID` | `0` | `0` = the key's owner |
|
|
171
|
+
| `SUMMITS_DATA_DIR` | `~/GarminBridge/summits` next to a bridge, else `~/IntervalsSummits` | database, tracks, `summits.html` |
|
|
172
|
+
| `SUMMITS_LANGUAGE` | `en` | `en` or `de`: names of new fields, the page, the description line |
|
|
173
|
+
| `SUMMITS_NAME_LANGUAGE` | – | e.g. `en`: OpenStreetMap's `name:en` where there is one, else the local name |
|
|
174
|
+
| `SUMMITS_FIELDS` | `Summits,SummitCount,HighestPoint` | add `HutsPasses` for huts and passes |
|
|
175
|
+
| `SUMMITS_DESCRIPTION` | `false` | `true`: the `⛰️ Summits:` line in the description (searchable) |
|
|
176
|
+
| `SUMMITS_TYPES` | every outdoor type | only these activity types, e.g. `Hike,BackcountrySki` |
|
|
177
|
+
| `SUMMITS_PEAK_RADIUS` | `100` | metres (10–200): how close the track must come to a summit |
|
|
178
|
+
| `SUMMITS_PEAK_BELOW` | `80` | metres (0–500): how far below the summit the track may be there |
|
|
179
|
+
| `SUMMITS_HUT_RADIUS` | `60` | metres (10–200) for huts |
|
|
180
|
+
| `SUMMITS_PASS_RADIUS` | `60` | metres (10–200) for passes |
|
|
181
|
+
| `SUMMITS_LOOKBACK_DAYS` | `3` | how far `sync` looks back |
|
|
182
|
+
| `SUMMITS_INTERVAL_MINUTES` | `15` | `run`: minutes between syncs |
|
|
183
|
+
| `SUMMITS_KEEP_TRACKS` | `true` | keep the tracks: `recompute` without downloading them again (~20 KB per activity) |
|
|
184
|
+
| `SUMMITS_OVERPASS_URL` | overpass-api.de | another Overpass server |
|
|
185
|
+
| `SUMMITS_PAGE_TITLE` | Summit log / Gipfelbuch | |
|
|
186
|
+
|
|
187
|
+
## Fair to Intervals and OpenStreetMap
|
|
188
|
+
|
|
189
|
+
- **Intervals:** the API key is usually shared with the bridge. The tool paces itself (at most
|
|
190
|
+
about 1.5 requests a second) and waits whenever Intervals answers 429. A sync with nothing new is
|
|
191
|
+
one small request.
|
|
192
|
+
- **OpenStreetMap:** points come from the Overpass API and are cached in 0.2° cells for 180 days,
|
|
193
|
+
so your usual tours cost no query after the first time. A backfill asks for whole regions at
|
|
194
|
+
once (16 years and 1,800 tours, in the Alps and on a few trips abroad: 18 queries). One query at a time, with a pause between
|
|
195
|
+
queries, and patience on 429/5xx.
|
|
196
|
+
|
|
197
|
+
## Privacy
|
|
198
|
+
|
|
199
|
+
Your data stays on your computer and in your Intervals account. Overpass learns which 0.2° cells
|
|
200
|
+
(about 22 × 15 km) you need, never a track or an activity. Details: [PRIVACY.md](PRIVACY.md).
|
|
201
|
+
|
|
202
|
+
## Licence
|
|
203
|
+
|
|
204
|
+
MIT. Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors, ODbL.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "intervals-summits"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Summits, huts and passes on your Intervals.icu activities, from your GPS tracks and OpenStreetMap – with a summit log page"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Andreas Schnederle-Wagner" }]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
dependencies = ["requests>=2.31"]
|
|
14
|
+
keywords = ["intervals.icu", "summits", "peaks", "openstreetmap", "hiking", "ski touring"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/futureweb/intervals-summits"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
intervals-summits = "intervals_summits.cli:main"
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=8"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["tests"]
|