|
1 | | -# qfit |
| 1 | +# QGIS Mapbox GL Style |
2 | 2 |
|
3 | | -Explore fitness activity data spatially in QGIS. |
| 3 | +QGIS plugin and validation harness for loading Mapbox vector tiles in QGIS and adapting Mapbox GL JS styles to the QGIS styling engine. |
4 | 4 |
|
5 | | -qfit is a QGIS plugin that turns synced fitness activities into GeoPackage-backed layers, analysis views, and atlas-ready publish data for mapping and print workflows. |
| 5 | +The first target style is Mapbox Outdoors (`mapbox/outdoors-v12`). The project keeps the qfit history that built the current QGIS symbology, then continues that work in a smaller repository focused only on Mapbox vector tiles and QGIS style parity. |
6 | 6 |
|
7 | | - |
| 7 | +## What It Does |
8 | 8 |
|
9 | | -*qfit in QGIS, showing synced activity tracks and points with the plugin dock open.* |
| 9 | +- adds a QGIS action to load Mapbox Outdoors as a vector tile layer |
| 10 | +- fetches the Mapbox style JSON and sprite sheet at runtime |
| 11 | +- simplifies Mapbox GL expressions that QGIS cannot convert directly |
| 12 | +- applies the converted renderer and labeling with QGIS' `QgsMapBoxGlStyleConverter` |
| 13 | +- post-processes labels, line styles, terrain fills, road shields, contour labels, paths, and other Outdoors-specific details from the qfit work |
| 14 | +- keeps an iteration manifest so historical and future style snapshots can be rendered and compared |
10 | 15 |
|
11 | | -This README is split into two parts: |
| 16 | +No Mapbox token, tile payload, sprite payload, downloaded style JSON, or render output is committed. |
12 | 17 |
|
13 | | -1. **Part 1, for human readers** using qfit in QGIS |
14 | | -2. **Part 2, for contributors and AI coding agents** changing the codebase |
| 18 | +## QGIS Plugin Use |
15 | 19 |
|
16 | | ---- |
| 20 | +Install or package the plugin as `qgis_mapbox_gl_style`. |
17 | 21 |
|
18 | | -## Part 1, for human readers |
| 22 | +The plugin reads the Mapbox token from QGIS settings first, then from environment variables: |
19 | 23 |
|
20 | | -### What qfit does today |
21 | | - |
22 | | -qfit currently supports: |
23 | | - |
24 | | -- connecting to Strava with `client_id`, `client_secret`, and `refresh_token` |
25 | | -- opening the Strava authorize page from the plugin and exchanging an auth code for a refresh token |
26 | | -- fetching activities from Strava in the background |
27 | | -- previewing fetched activities in the dock before writing anything to disk |
28 | | -- storing a canonical local GeoPackage sync store |
29 | | -- loading QGIS layers for tracks, start points, and optional sampled stream points |
30 | | -- filtering by activity type, text search, date range, distance, and detailed-stream availability |
31 | | -- applying visualization presets, optional temporal wiring, and an optional Mapbox basemap |
32 | | -- running analysis workflows such as frequent starting points and activity heatmaps |
33 | | -- generating atlas-ready publish layers and exporting a PDF atlas from the dock |
34 | | - |
35 | | -### Main outputs |
36 | | - |
37 | | -qfit uses a GeoPackage as both local sync store and QGIS data source. |
38 | | - |
39 | | -**Internal tables** |
40 | | -- `activity_registry` |
41 | | -- `sync_state` |
42 | | - |
43 | | -**Visible layers** |
44 | | -- `activity_tracks` |
45 | | -- `activity_starts` |
46 | | -- `activity_points` (optional, derived from detailed streams) |
47 | | -- `activity_atlas_pages` |
48 | | - |
49 | | -**Atlas helper tables** |
50 | | -- `atlas_document_summary` |
51 | | -- `atlas_cover_highlights` |
52 | | -- `atlas_page_detail_items` |
53 | | -- `atlas_toc_entries` |
54 | | -- `atlas_profile_samples` |
55 | | - |
56 | | -### Typical workflow |
57 | | - |
58 | | -1. Configure Strava credentials. |
59 | | -2. Fetch activities and preview the result in the dock. |
60 | | -3. Choose an output `.gpkg` and store the data. |
61 | | -4. Load the qfit layers into QGIS. |
62 | | -5. Apply visualization or analysis workflows. |
63 | | -6. Optionally generate atlas-ready publish data and export a PDF atlas. |
64 | | - |
65 | | -### Strava credentials |
66 | | - |
67 | | -You need: |
68 | | - |
69 | | -- `client_id` |
70 | | -- `client_secret` |
71 | | -- `refresh_token` |
72 | | - |
73 | | -qfit includes a built-in OAuth helper in `qfit` → `Configuration` for the refresh-token step. |
74 | | - |
75 | | -See: |
76 | | -- `docs/strava-setup.md` |
77 | | - |
78 | | -### Background maps and styling |
79 | | - |
80 | | -qfit can load an optional Mapbox basemap and keep it below the qfit layers in the QGIS layer tree. |
81 | | - |
82 | | -For Mapbox basemaps, raster mode is the recommended choice when you need the closest visual match to Mapbox's own rendering. Vector mode is rendered natively by QGIS and should be treated as a practical local approximation: it can be more interactive and inspectable inside QGIS, but fonts, labels, sprites, antialiasing, and zoom interpolation will not be pixel-identical to Mapbox GL JS. |
83 | | - |
84 | | -The current visualization flow supports: |
85 | | - |
86 | | -- semantic activity styling by activity type |
87 | | -- simpler line-based presets |
88 | | -- track points / start points / heatmap-oriented views |
89 | | -- temporal timestamp wiring when timestamp fields are available |
90 | | - |
91 | | -See: |
92 | | -- `docs/map-style-guide.md` |
93 | | -- `docs/mapbox-outdoors-comparison-harness.md` for manual Mapbox Outdoors browser-vs-QGIS parity checks |
94 | | - |
95 | | -### Publish and atlas support |
96 | | - |
97 | | -qfit can generate atlas-ready layers and helper tables for print layouts, then export a PDF atlas from the plugin. |
98 | | - |
99 | | -The current publish flow supports: |
100 | | - |
101 | | -- atlas page extent planning |
102 | | -- cover and summary helper tables |
103 | | -- TOC-ready helper rows |
104 | | -- route-profile sample tables for layout charts |
105 | | -- programmatic PDF export through the atlas/export subsystem |
106 | | - |
107 | | -For validation notes and rendering-sensitive workflow details, see: |
108 | | -- `docs/atlas-validation-harness.md` |
109 | | - |
110 | | -### More docs |
111 | | - |
112 | | -- `docs/strava-setup.md` |
113 | | -- `docs/schema.md` |
114 | | -- `docs/map-style-guide.md` |
115 | | -- `docs/mapbox-outdoors-comparison-harness.md` |
116 | | -- `docs/qgis-testing.md` |
117 | | - |
118 | | ---- |
119 | | - |
120 | | -## Part 2, for contributors and AI coding agents |
121 | | - |
122 | | -### Read these first |
123 | | - |
124 | | -If you are changing internals, start here: |
125 | | - |
126 | | -- `CONTRIBUTING.md` |
127 | | -- `docs/architecture.md` |
128 | | -- `docs/qgis-plugin-architecture-principles.md` |
129 | | -- `docs/refactoring-roadmap.md` |
130 | | -- `docs/qgis-testing.md` |
131 | | -- `docs/atlas-validation-harness.md` for atlas rendering/export-sensitive work |
132 | | -- `docs/mapbox-outdoors-comparison-harness.md` for manual Mapbox Outdoors browser-vs-QGIS visual comparison work |
133 | | - |
134 | | -### Current architecture snapshot |
135 | | - |
136 | | -qfit is being evolved as a **modular monolith** with pragmatic **ports-and-adapters** boundaries. |
137 | | - |
138 | | -Preferred dependency direction: |
139 | | - |
140 | | -```text |
141 | | -UI -> application/workflow -> domain + ports -> infrastructure adapters |
| 24 | +```bash |
| 25 | +export QGIS_MAPBOX_GL_STYLE_MAPBOX_TOKEN="pk..." |
142 | 26 | ``` |
143 | 27 |
|
144 | | -In practice, that means: |
145 | | - |
146 | | -- keep `qfit_dockwidget.py` focused on UI glue |
147 | | -- move workflow orchestration into feature-owned application modules |
148 | | -- keep provider-neutral logic easier to test than QGIS-heavy code |
149 | | -- keep QGIS, Strava, GeoPackage, settings, and PDF assembly details in infrastructure/adapters when that improves clarity |
150 | | -- add ports/gateways only when they earn their keep |
151 | | - |
152 | | -### Current repo shape |
153 | | - |
154 | | -**Plugin entrypoints and UI host** |
155 | | -- `qfit_plugin.py` |
156 | | -- `qfit_dockwidget.py` |
157 | | -- `qfit_config_dialog.py` |
158 | | -- `qfit_dockwidget_base.ui` |
159 | | - |
160 | | -**Feature-owned packages** |
161 | | -- `activities/` for fetch/sync/load workflows and provider-neutral activity logic |
162 | | -- `analysis/` for analysis workflows, request/result shaping, and QGIS-backed analysis adapters |
163 | | -- `atlas/` for publish/export workflows, runtime preparation, and PDF assembly |
164 | | -- `configuration/` for settings, connection status, and dock-settings binding helpers |
165 | | -- `providers/` for provider contracts and Strava-backed adapters |
166 | | -- `ui/` for dock-widget dependency assembly and UI-only coordination helpers |
167 | | -- `visualization/` for render planning, basemap workflows, temporal wiring, and QGIS layer adapters |
168 | | - |
169 | | -**Root-level modules** |
170 | | - |
171 | | -The top-level Python module layer is now mostly limited to: |
172 | | - |
173 | | -- plugin/bootstrap entrypoints |
174 | | -- a few small shared helpers such as `polyline_utils.py`, `time_utils.py`, `mapbox_config.py`, and `qfit_cache.py` |
175 | | -- transitional compatibility shims such as `activity_query.py`, `activity_classification.py`, `models.py`, `activity_storage.py`, and `layer_manager.py` that still exist only to cushion package migration |
176 | | - |
177 | | -Rule of thumb: |
| 28 | +The generic `MAPBOX_ACCESS_TOKEN` variable is also supported. `QFIT_MAPBOX_ACCESS_TOKEN` remains supported only for old validation scripts and local migration continuity. |
178 | 29 |
|
179 | | -> Do not add new feature-specific top-level modules. |
| 30 | +In QGIS: |
180 | 31 |
|
181 | | -If new code belongs to one feature, it should usually live under that feature package. |
| 32 | +1. Enable the plugin. |
| 33 | +2. Open `QGIS Mapbox GL Style -> Settings`. |
| 34 | +3. Enter a Mapbox token, style owner, style id, and tile mode. |
| 35 | +4. Run `QGIS Mapbox GL Style -> Load Mapbox Outdoors`. |
182 | 36 |
|
183 | | -### Current architectural priorities |
| 37 | +The default style is `mapbox/outdoors-v12` in vector mode. |
184 | 38 |
|
185 | | -1. Keep thinning `QfitDockWidget`. |
186 | | -2. Preserve strict feature ownership. |
187 | | -3. Move policy into application/domain while leaving mechanics in adapters. |
188 | | -4. Keep request/result seams explicit where they reduce UI or framework coupling. |
189 | | -5. Delete compatibility shims once in-repo callers are migrated. |
| 39 | +## Iterations |
190 | 40 |
|
191 | | -### Practical coding rules |
| 41 | +Iterations are stored in `iterations/index.json`. Each entry records: |
192 | 42 |
|
193 | | -- Prefer small, reviewable PR-sized slices. |
194 | | -- Every behavior change needs tests. |
195 | | -- New workflow logic should not accumulate in `QfitDockWidget`. |
196 | | -- Prefer feature-owned modules over generic root-level helpers. |
197 | | -- Prefer explicit request/result dataclasses when they replace long parameter lists or messy widget-state handoff. |
198 | | -- Keep provider-neutral logic free of PyQGIS when practical. |
199 | | -- Rendering/export-sensitive changes need artifact proof, not only green CI. |
| 43 | +- a stable iteration id |
| 44 | +- the original qfit commit |
| 45 | +- the rewritten commit in this repository |
| 46 | +- source pull requests and issue references |
| 47 | +- the style owner/id and camera set |
| 48 | +- notes about the visual/style milestone |
200 | 49 |
|
201 | | -### Working conventions by area |
202 | | - |
203 | | -**activities/** |
204 | | -- `activities/domain/` holds provider-neutral activity logic. |
205 | | -- `activities/application/` owns fetch/sync/load workflows, preview helpers, and task wrappers. |
206 | | -- GeoPackage-backed activity persistence belongs under infrastructure-oriented paths. |
207 | | - |
208 | | -**analysis/** |
209 | | -- dock-facing analysis entrypoints are intentionally being thinned behind workflow-oriented application seams |
210 | | -- request building, dispatch, result shaping, and status policy belong in application modules, not in the dock |
211 | | -- QGIS-backed layer creation stays in analysis infrastructure |
212 | | - |
213 | | -**visualization/** |
214 | | -- render planning, temporal intent, and user-facing visualization policy belong in `visualization/application/` |
215 | | -- layer mutation, renderer construction, basemap loading, and QGIS project wiring belong in `visualization/infrastructure/` |
216 | | - |
217 | | -**atlas/** |
218 | | -- `atlas/` owns publish/export workflows |
219 | | -- keep naming and ownership crisp across request building, runtime preparation, task execution, and PDF assembly |
220 | | -- avoid adding abstraction layers that do not clarify responsibility |
221 | | - |
222 | | -**ui/** |
223 | | -- `ui/` is for dock-widget support code, dependency assembly, UI-only coordination, and similar glue |
224 | | -- UI modules should call workflows and render results, not absorb more business logic |
225 | | - |
226 | | -### Testing |
227 | | - |
228 | | -Run the main test suite with: |
| 50 | +List iterations: |
229 | 51 |
|
230 | 52 | ```bash |
231 | | -python3 -m pytest tests/ -x -q --tb=short |
| 53 | +python -m qgis_mapbox_gl_style.iterations list |
232 | 54 | ``` |
233 | 55 |
|
234 | | -Run unittest discovery with: |
| 56 | +Render one historical iteration: |
235 | 57 |
|
236 | 58 | ```bash |
237 | | -python3 -m unittest discover -s tests -v |
| 59 | +python -m qgis_mapbox_gl_style.iterations render --iteration 006 --camera-set issue-949 |
238 | 60 | ``` |
239 | 61 |
|
240 | | -Run the PyQGIS smoke test with: |
| 62 | +The render command creates a detached git worktree at the selected filtered commit and runs the retained comparison harness from that snapshot. Artifacts are written under `debug/iterations/<iteration>/<timestamp>/`, which is ignored by git. |
| 63 | + |
| 64 | +Use `--dry-run` to inspect the exact worktree and render command without executing QGIS/browser rendering. |
| 65 | + |
| 66 | +Accept a future agent run as the next iteration: |
241 | 67 |
|
242 | 68 | ```bash |
243 | | -python3 -m unittest tests.test_qgis_smoke -v |
| 69 | +python -m qgis_mapbox_gl_style.iterations accept --from-run debug/iterations/012/20260620T120000Z --label "Improve contour label placement" |
244 | 70 | ``` |
245 | 71 |
|
246 | | -On machines without PyQGIS installed, the smoke test skips automatically. |
247 | | - |
248 | | -### Local install and packaging |
| 72 | +That appends the next manifest entry against the current repository `HEAD`. |
249 | 73 |
|
250 | | -Install qfit into a local QGIS profile for testing with: |
| 74 | +## Validation Harness |
251 | 75 |
|
252 | | -```bash |
253 | | -python3 scripts/install_plugin.py --plugins-dir <QGIS plugins dir> --mode copy |
254 | | -``` |
| 76 | +The retained validation scripts live under `validation/` and are intentionally close to their qfit originals. Some report fields still say `qfit_*` or "qfit preprocessing" because those names are part of the historical audit output and tests. |
255 | 77 |
|
256 | | -Build a release-style plugin archive with: |
| 78 | +The main visual comparison harness is: |
257 | 79 |
|
258 | 80 | ```bash |
259 | | -python -m pip install pypdf |
260 | | -python3 scripts/package_plugin.py |
| 81 | +python validation/mapbox_outdoors_comparison.py --all-cameras --output-root debug/mapbox-outdoors-comparison |
261 | 82 | ``` |
262 | 83 |
|
263 | | -The release ZIP is written to `dist/`. |
| 84 | +See [docs/mapbox-outdoors-comparison-harness.md](docs/mapbox-outdoors-comparison-harness.md) for the full harness workflow. |
264 | 85 |
|
265 | | -### CI and review expectations |
| 86 | +## Packaging |
266 | 87 |
|
267 | | -Before treating a change as done: |
| 88 | +Build a QGIS install ZIP: |
268 | 89 |
|
269 | | -- run the relevant tests |
270 | | -- keep SonarCloud green |
271 | | -- keep CodeQL/CI green |
272 | | -- address meaningful review feedback |
273 | | -- for export/rendering work, verify the final artifact, not just object construction |
274 | | - |
275 | | -### Short version for agents |
| 90 | +```bash |
| 91 | +python scripts/package_plugin.py |
| 92 | +``` |
276 | 93 |
|
277 | | -If you only remember a few rules, remember these: |
| 94 | +The package is written to `dist/qgis_mapbox_gl_style-<version>.zip`. |
278 | 95 |
|
279 | | -- make `QfitDockWidget` thinner, not heavier |
280 | | -- keep real feature logic inside feature-owned packages |
281 | | -- keep QGIS-heavy mechanics out of provider-neutral workflow code |
282 | | -- prefer small, behavior-preserving slices |
283 | | -- delete migration shims once callers are gone |
| 96 | +## Provenance |
284 | 97 |
|
285 | | -## License |
| 98 | +This repository was seeded from the Mapbox/QGIS parts of `ebelo/qfit` with `git filter-repo`, so the style work remains inspectable as real git history instead of a copied final snapshot. |
286 | 99 |
|
287 | | -GPL-2.0-or-later. See [LICENSE](LICENSE). |
| 100 | +More detail is in [docs/provenance.md](docs/provenance.md). |
0 commit comments