Skip to content

Add OSM service policy architecture with per-provider tile caching#128

Open
dougborg wants to merge 7 commits intoFoggedLens:mainfrom
dougborg:feat/osm-service-policy
Open

Add OSM service policy architecture with per-provider tile caching#128
dougborg wants to merge 7 commits intoFoggedLens:mainfrom
dougborg:feat/osm-service-policy

Conversation

@dougborg
Copy link
Collaborator

@dougborg dougborg commented Feb 25, 2026

Summary

Service Policy System

  • Introduces a unified service policy system (ServiceType, ServicePolicy, ServicePolicyResolver, ServiceRateLimiter) that resolves compliance rules per-URL, covering OSMF official services and third-party tile providers. Custom/self-hosted endpoints get permissive defaults.
  • Nominatim: Adds 1-req/sec rate limiting and client-side result caching with 5-minute TTL as required by Nominatim usage policy
  • OSM tile server: Blocks offline tile downloads (explicitly prohibited by tile usage policy) with user-facing dialog
  • Attribution dialog: Adds tappable license link to openstreetmap.org/copyright when using OSM-based tile providers (ODbL requirement)
  • OSM editing API: Enforces max 2 concurrent download threads via semaphore

Per-Provider Tile Caching

  • Each tile provider/type combination gets its own DeflockTileProvider instance with frozen config (no AppState lookups at request time) and a dedicated ProviderTileCacheStore implementing flutter_map's MapCachingProvider
  • Policy-driven TTL enforcement: OSM tiles get a 7-day minimum cache TTL as required by tile usage policy; other providers use server-provided headers
  • Isolated cache directories: Each provider caches under {appCacheDir}/tile_cache/{providerId}/{tileTypeId}/ — separate size limits, independent eviction
  • Configurable cache size limits with LRU eviction (default 500MB per provider, evicts to 80% when exceeded)
  • TileLayerManager refactored into a provider registry — providers are created lazily and cached for instant switching

Builds on top of PR #123 (UserAgentClient) and PR #127 (NetworkTileProvider). Compatible with PR #114 (Overpass parallelization) — Overpass policy defers to NodeDataManager's _AsyncSemaphore.

Test plan

  • 120 unit tests passing (38 service policy + 15 cache store + 10 tile provider + rest of suite)
  • flutter analyze — zero issues
  • Manual: verify Nominatim search respects 1-req/sec rate limit (visible in debug output)
  • Manual: verify offline tile download dialog blocks OSM tiles but allows Bing/Mapbox
  • Manual: verify attribution link opens openstreetmap.org/copyright
  • Manual: switch between tile providers — verify instant swap, separate cache dirs created under {appCacheDir}/tile_cache/
  • Manual: offline mode with downloaded area still works

🤖 Generated with Claude Code

claude and others added 2 commits February 25, 2026 11:16
Introduces a unified service policy system (ServiceType, ServicePolicy,
ServicePolicyResolver, ServiceRateLimiter) that resolves compliance rules
per-URL, covering OSMF official services and third-party tile providers.
Custom/self-hosted endpoints get permissive defaults.

Key changes:
- ServicePolicyResolver maps URLs to policies (OSM tile server, Nominatim,
  editing API, Overpass, Bing, Mapbox, custom)
- Nominatim: adds 1-req/sec rate limiting and client-side result caching
  with 5-minute TTL as required by Nominatim usage policy
- OSM tile server: blocks offline tile downloads (explicitly prohibited by
  tile usage policy) with user-facing dialog
- Attribution dialog: adds tappable license link to openstreetmap.org/copyright
  when using OSM-based tile providers (ODbL requirement)
- OSM editing API: enforces max 2 concurrent download threads via semaphore
- 23 unit tests covering policy resolution, URL template parsing, custom
  overrides, rate limiting, and all policy values

Builds on top of PR FoggedLens#123 (UserAgentClient) and PR FoggedLens#127 (NetworkTileProvider).
Compatible with PR FoggedLens#114 (Overpass parallelization) — Overpass policy defers
to NodeDataManager's _AsyncSemaphore.

https://claude.ai/code/session_01XyRTrax1tmtjcuT7CMoJhD
…e guard

- Fix wrong package name in test (deflock -> deflockapp) that prevented compilation
- Fix double-release of Nominatim semaphore on non-200 responses using try/finally
- Remove silent _currentCount > 0 guard in semaphore release to surface mismatched calls
- Add public resolveByType() to avoid private member access from ServiceRateLimiter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 18:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a unified “service policy” layer to enforce OSM ecosystem compliance rules (rate limiting, caching, offline download restrictions, attribution links, and concurrency caps) across URL-based services like Nominatim, OSM tiles, and the OSM editing API.

Changes:

  • Added ServicePolicy/ServicePolicyResolver and a global ServiceRateLimiter to enforce per-service compliance constraints.
  • Integrated compliance into UX and services: block offline downloads for restricted tile servers, add attribution link, and enforce OSM API + Nominatim limits.
  • Added a comprehensive unit test suite for policy resolution and rate limiting behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
lib/services/service_policy.dart Adds policy definitions, URL/type resolution, and shared rate limiter/semaphore implementation.
lib/services/search_service.dart Adds Nominatim client-side caching + uses ServiceRateLimiter for 1 req/sec enforcement.
lib/services/map_data_submodules/nodes_from_osm_api.dart Enforces OSM editing API concurrency limit via ServiceRateLimiter.
lib/models/tile_provider.dart Exposes allowsOfflineDownload and servicePolicy from tile URL templates.
lib/widgets/download_area_dialog.dart Blocks offline downloads when the selected tile type’s policy disallows it.
lib/widgets/map/map_overlays.dart Adds attribution dialog license link opening via url_launcher.
test/services/service_policy_test.dart Adds tests for policy resolution, overrides, and rate limiting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Each tile provider/type gets its own DeflockTileProvider with frozen
config and a dedicated ProviderTileCacheStore (MapCachingProvider impl).
This enforces policy-driven TTL (e.g., OSM 7-day minimum), configurable
per-provider cache size limits with LRU eviction, and eliminates AppState
lookups at request time.

Also fixes: unused import in map_overlays, untyped variable in
nodes_from_osm_api, and isOfflineOnly excluded from offline tile
ImageProvider equality (prevented Flutter image cache from distinguishing
online vs offline tile requests).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dougborg dougborg changed the title Add OSM service policy architecture and compliance fixes Add OSM service policy architecture with per-provider tile caching Feb 25, 2026
@dougborg dougborg requested a review from Copilot February 25, 2026 20:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dougborg and others added 2 commits February 25, 2026 13:19
…issues

- Fix custom policy override using substring matching (now exact/subdomain)
- Reorder rate limiter to acquire semaphore before checking interval
- Add StateError guard for semaphore over-release
- Add error handling for attribution URL launch
- Make Nominatim result cache static across SearchService instances
- Fix Navigator.pop context deactivation in download area dialog
- Remove flaky timing assertion; add concurrent caller rate-limit test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cache TileType.servicePolicy as late final field to avoid repeated URL parsing
- Make TileLayerManager.dispose() synchronous to match Flutter's State.dispose contract
- Add clarifying comments on _ensureDirectory Completer latch and _evictIfNeeded staleness
- Internationalize offline-not-permitted dialog message across all 11 locales
- Add test verifying 3rd semaphore acquire blocks until a slot is released
- Remove extra blank line in nodes_from_osm_api.dart

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dougborg and others added 2 commits February 25, 2026 13:49
- Fix eviction accounting: only count freed bytes after successful delete
- Fix TTL override to enforce minimum (use later of server staleAt vs override)
- Fix _lastRequestTime doc comment to reflect it tracks acquire time, not completion
- Use unawaited() with catchError in dispose() to avoid dropped exceptions
- Use non-empty urlTemplate in fallback provider to avoid empty tile URLs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard rate-limit delay with try/catch to release semaphore slot on failure
- Fix cache size estimate: increment _estimatedSize on putTile writes
- Clean up orphan .meta files during eviction (no matching .tile)
- Make resolveType() check custom overrides for consistency with resolve()
- Simplify UUID to const Uuid() (MathRNG unnecessary for deterministic v5)
- Fix misleading doc comment about flutter_map UUID matching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stopflock
Copy link
Collaborator

Let's put a pin in this one for a week or so; we may end up being able to just move to vector tiles. That'll be a massive change, but worthy if a box of crabs can pull it off.

dougborg added a commit to dougborg/deflock-app that referenced this pull request Mar 2, 2026
Combines per-provider disk caching (PR FoggedLens#128) with error retry via
reset stream and silenceExceptions: false (PR FoggedLens#132). Merge conflicts
in tile_layer_manager.dart resolved to keep both the multi-provider
cache map and the reset stream / retry timer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants