16 KiB
dpg-map Rebuild Steps
This file replaces the old long step list.
There is no Step 0. Initial setup is listed separately, then implementation starts at Step 1.
Workflow rules
- Use
uvfor all Python package and dependency management. - Always read
FEATURES.md,ARCHITECTURE.md, andAGENTS.mdbefore making code changes. - Keep
AGENTS.mdas a rolling log of what has been done, what is broken, and what comes next. - Update
README.mdwhenever public behaviour or examples change. - After every step:
- update
AGENTS.md - run relevant checks
- commit to git
- update
- Do not casually change public API once introduced.
- Public runtime APIs must be safe to call from background threads unless explicitly documented otherwise.
- Dear PyGui calls must happen on the GUI thread only.
- Prefer stable, boring implementation over clever rendering tricks.
Initial setup instructions
Run these before Step 1 when starting from a clean repository:
mkdir dpg-map
cd dpg-map
git init
uv init --package dpg-map
uv add dearpygui pillow platformdirs requests
uv add --dev pytest ruff pyright
Create this structure:
src/dpg_map/
__init__.py
api.py
widget.py
state.py
commands.py
renderer.py
draw_layers.py
interaction.py
overlays.py
providers.py
projection.py
tiles.py
cache.py
sizing.py
diagnostics.py
types.py
exceptions.py
examples/
tests/
FEATURES.md
ARCHITECTURE.md
STEPS.md
AGENTS.md
README.md
Recommended pyproject.toml additions:
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
[tool.pyright]
typeCheckingMode = "basic"
Create AGENTS.md with:
# AGENTS.md
## Current status
Rebuild initialized.
## Completed steps
None yet.
## Current step
Step 1 — Public API contract and pure core.
## Design decisions
- Package is managed with uv.
- Public import is `import dpg_map as dpgm`.
- Dear PyGui calls are GUI-thread-only.
- Runtime public calls enqueue commands or update logical state.
- Overlay updates must not reset center/zoom.
- The widget uses child_window + measured-size drawlist.
- Tiles use a memory cache and persistent disk cache.
- Tile providers are interchangeable.
## Known issues
None yet.
## Commands used
None yet.
## Next action
Implement Step 1.
Initial commit:
uv run python -c "import dpg_map; print(dpg_map.__name__)"
uv run ruff check .
uv run ruff format .
git add .
git commit -m "initial uv project setup"
Step 1 — Public API contract and pure core
Goal: lock the public API surface and implement pure non-DPG components first.
Tasks:
- Define public exports in
__init__.py.
Required API names:
configure
TileProvider
register_provider
unregister_provider
get_provider
list_providers
map_widget
set_center
get_center
set_zoom
get_zoom
set_view
fit_bounds
screen_to_latlon
latlon_to_screen
add_marker
add_polyline
add_trajectory
update_marker
update_polyline
update_trajectory
set_marker_position
set_marker_label
set_polyline_points
set_overlay_show
delete_overlay
add_layer
show_layer
hide_layer
clear_layer
clear_map
set_provider
clear_memory_cache
clear_disk_cache
get_cache_stats
get_map_debug_state
-
Add exceptions in
exceptions.py. -
Add common types in
types.py. -
Implement
TileProviderand provider registry inproviders.py. -
Implement Web Mercator projection in
projection.py. -
Implement initial cache dataclasses and
CacheStats. -
Stub GUI-dependent public functions so imports succeed.
-
Add tests for:
- package imports
- provider registration
- provider URL building
- invalid provider templates
- projection roundtrips
- cache stats dataclass construction
Checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- public API imports cleanly
- provider system works
- projection tests pass
- no Dear PyGui context is required for tests
- API names are fixed before renderer work begins
Commit:
git add .
git commit -m "step 1: lock public api and pure core"
Step 2 — Thread-safe state, commands, overlays, and cache model
Goal: build the logical state model before rendering anything.
Tasks:
-
Implement
DpgMapConfigandconfigure(...). -
Implement
MapState. -
Implement global map registry and current map context stack.
-
Implement
DirtyFlags. -
Implement
MapCommand,CommandKind, andMapCommandQueue. -
Implement coalescing rules:
- latest overlay update per overlay wins
- latest view update wins
- structural commands preserve order
-
Implement logical overlay dataclasses:
OverlayMarkerOverlayPolylineOverlayTrajectoryOverlayLayerState
-
Implement public overlay creation/update functions so they:
- resolve map context or
map_tag - validate inputs
- copy coordinate sequences
- enqueue commands or mutate logical model under lock
- never call Dear PyGui
- resolve map context or
-
Implement cache model:
- memory cache metadata
- disk path generation
- disk metadata read/write
- disk size scanning
- prune planning without deleting active files yet
-
Add tests for:
- command coalescing
- overlay update does not alter center/zoom
- trajectory input lists are copied
- mismatched lat/lon lengths raise
- layer state
- disk cache path generation
- disk cache prune ordering
Checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- runtime overlay update functions are thread-safe at the logical level
- no public overlay function calls Dear PyGui
- center/zoom cannot be changed by overlay commands
- cache has separate memory and disk configuration
- persistent disk cache layout is decided
Commit:
git add .
git commit -m "step 2: add thread safe state commands and cache model"
Step 3 — Widget shell, sizing system, and GUI-thread frame pump
Goal: create a stable Dear PyGui widget shell before loading real tiles.
Tasks:
-
Implement
map_widget(...). -
Internally create:
child_window
drawlist
-
Child window receives requested sizing values.
-
Drawlist receives concrete measured size only.
-
Implement
sizing.py:- measure child content region
- track last non-zero size
- resize drawlist
- detect hidden/show-later transitions
- avoid permanent zero-size collapse
-
Implement the renderer frame pump:
- schedules frame callbacks
- drains commands on GUI thread
- updates size
- draws background and attribution placeholder
- exposes debug state
-
Implement map interaction hit-test rectangle calculation, but not full panning yet.
-
Add examples:
examples/basic_map.pyexamples/sizing_window.pyexamples/sizing_child.pyexamples/sizing_table.pyexamples/hidden_tab.py
-
Add tests where possible without opening a GUI:
- sizing state transitions
- zero-size does not overwrite last non-zero size
- dirty flags from resize
- command drain ordering
Manual checks:
uv run python examples/basic_map.py
uv run python examples/sizing_window.py
uv run python examples/sizing_child.py
uv run python examples/sizing_table.py
uv run python examples/hidden_tab.py
Automated checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- widget appears
- map area fills its child container correctly
- drawlist size matches measured child content size
- hidden tab example loads/render placeholder after becoming visible
- no map gets stuck at zero size
- public API remains unchanged
Commit:
git add .
git commit -m "step 3: add widget shell sizing and frame pump"
Step 4 — Tile manager, persistent cache, and asynchronous loading
Goal: implement stable tile loading and both cache limits before advanced overlays.
Tasks:
-
Implement
TileID,TileStatus,Tile, andTileManager. -
Implement visible tile calculation.
-
Implement persistent disk cache:
- provider-namespaced paths
- metadata files
last_accessed_at- disk size scanning
- LRU pruning to
disk_cache_max_bytes
-
Implement memory cache:
- LRU or approximate LRU
memory_cache_max_tiles- visible-tile protection
- deferred GUI-thread texture deletion
-
Implement worker thread/tile queue:
- read disk cache
- fetch HTTP if missing
- decode tile image
- return tile result with map generation and provider name
- never call Dear PyGui
-
Renderer GUI thread:
- processes tile results
- ignores stale generation/provider results
- creates textures
- deletes evicted textures
- draws tile layer
-
Ensure OpenStreetMap provider uses configured User-Agent or warns/raises clearly.
-
Add
examples/cache_stress.py.
Manual checks:
uv run python examples/basic_map.py
uv run python examples/cache_stress.py
Automated checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- OSM tiles display
- GUI does not freeze while tiles load
- only visible tiles plus margin are requested
- returning to recently viewed area uses memory cache
- restarting app uses disk cache
- disk cache prunes to configured limit
- stale tile results are ignored after provider/generation change
- texture deletion happens on GUI thread
Commit:
git add .
git commit -m "step 4: add async tiles and persistent cache"
Step 5 — Interaction: pan, zoom, and view commands
Goal: make interaction stable before adding live overlays.
Tasks:
-
Implement left mouse drag panning.
-
Implement mouse wheel zoom.
-
Zoom around cursor where practical.
-
Implement programmatic view commands:
set_centerset_zoomset_viewfit_boundsscreen_to_latlonlatlon_to_screen
-
Interaction must:
- use concrete map rectangle
- work in nested containers
- not depend only on child-window hover state
- not reset when overlay commands are queued
- not recenter from tile results
-
Add debug fields for:
- active drag
- last mouse position
- current center
- current zoom
- dirty flags
Manual checks:
uv run python examples/basic_map.py
uv run python examples/sizing_child.py
uv run python examples/sizing_table.py
Automated checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- dragging pans smoothly
- mouse wheel zooms
- tile grid remains aligned
- map works in nested containers
- view commands work
- no unexpected recenter occurs from tile loading
Commit:
git add .
git commit -m "step 5: add stable pan zoom and view commands"
Step 6 — Overlay rendering and runtime update stress tests
Goal: implement overlays in a way that cannot break interaction.
Tasks:
-
Implement marker rendering.
-
Implement polyline rendering.
-
Implement trajectory rendering.
-
Implement overlay layer rendering separate from tile layer.
-
Overlay updates must set only
OVERLAYSdirty unless a structural map operation requires more. -
Overlay draw rebuild must not:
- clear tile textures
- clear tile draw state
- reset center
- reset zoom
- reset active drag state
- rebuild the whole map unnecessarily
-
Implement:
add_markeradd_polylineadd_trajectoryupdate_markerupdate_polylineupdate_trajectoryset_marker_positionset_marker_labelset_polyline_pointsset_overlay_showdelete_overlay
-
Add examples:
examples/markers_live_thread.pyexamples/trajectory_live_thread.py
The live examples must update overlays from a background thread while the user drags and zooms.
Manual stress checks:
uv run python examples/markers_live_thread.py
uv run python examples/trajectory_live_thread.py
While examples run:
- drag continuously for at least 30 seconds
- zoom in and out
- verify no flicker through blank map
- verify no random recentering
- verify overlay remains geographically attached
- verify update thread does not block badly
Automated checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- marker updates at 20–60 Hz do not break dragging
- trajectory updates at 10–30 Hz do not break dragging
- overlay updates are coalesced
- center/zoom remain stable unless explicitly changed
- overlay update APIs are safe from background threads
- tile layer is not destroyed by overlay updates
Commit:
git add .
git commit -m "step 6: add stable overlays and live update stress tests"
Step 7 — Layers, provider switching, and clearing APIs
Goal: add structural map operations after rendering stability is proven.
Tasks:
-
Implement layer operations:
add_layershow_layerhide_layerclear_layerclear_map
-
Implement provider switching:
set_provider
-
Provider switch must:
- keep overlays
- keep center
- clamp zoom if needed
- increment map generation
- clear old provider tile draw state
- ignore stale tile results
- redraw attribution
- not perform Dear PyGui work from caller thread
-
Implement cache control:
clear_memory_cacheclear_disk_cacheget_cache_stats
-
Add examples:
examples/custom_provider.py- extend
examples/cache_stress.py
Manual checks:
uv run python examples/custom_provider.py
uv run python examples/cache_stress.py
Automated checks:
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- layers can be shown/hidden/cleared
- provider switch keeps overlays
- provider switch does not render stale old-provider tiles
- cache clear functions do not call Dear PyGui from non-GUI threads
- memory/disk cache stats are usable
Commit:
git add .
git commit -m "step 7: add layers provider switching and cache controls"
Step 8 — Documentation, hardening, and internal release
Goal: make the package usable as a dependency in DataFlux or another project.
Tasks:
-
Update
README.mdwith:- uv install
- local editable dependency
- basic map
- sizing examples
- live marker update
- live trajectory update
- custom provider
- memory/disk cache config
- OpenStreetMap attribution/User-Agent note
- thread-safety contract
-
Add docstrings for all public functions.
-
Review public API exports.
-
Add tests for failure cases:
- unknown map
- unknown overlay
- unknown provider
- invalid coordinates
- mismatched lat/lon lengths
- empty trajectory
- clear deleted overlay
- provider switch while tiles loading
- overlay update while dragging model state
-
Run all examples manually.
-
Test local editable install from another project:
cd ../some-test-project
uv add -e ../dpg-map
uv run python -c "import dpg_map as dpgm; print(dpgm.list_providers())"
- Bump version.
Suggested initial rebuilt beta:
0.3.0b1
- Tag release:
git tag v0.3.0b1
Final checks:
uv sync
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
Acceptance criteria:
- all examples run
- local editable dependency works
- live overlay stress examples are stable
- cache survives restart
- disk cache limit works
- sizing examples work
- README accurately documents thread-safety and cache behaviour
- AGENTS.md accurately describes status
Commit:
git add .
git commit -m "step 8: harden docs and prepare rebuilt beta"
git tag v0.3.0b1