- Implemented WMS layer handling for Slovenian aerial imagery and overlays for cadastral parcels and forest roads. - Added distance measurement feature allowing users to measure distances on the map with long press interactions. - Updated localization files for Croatian, Italian, and Slovenian languages to include new measurement and WMS-related strings. - Enhanced the map layer model to support WMS-specific properties. - Introduced a new tile provider for debugging WMS requests. - Updated the drawing toolbar to include measurement mode and clear measurement functionality. - Integrated SharedPreferences to persist overlay visibility states across app sessions. - Added utility functions for handling the Slovenian coordinate reference system (EPSG:3794).
27 KiB
CLAUDE.md - MeshCore SAR Technical Reference
AI assistant guide for the MeshCore SAR Flutter application.
Table of Contents
- Critical Development Rules
- Quick Reference
- Project Structure
- Protocol Reference
- Architecture
- Common Tasks
- Build & Troubleshooting
Critical Development Rules
Flutter Process Management
NEVER run or kill Flutter processes:
- ❌ DO NOT execute
flutter runcommand - ❌ DO NOT kill Flutter processes (
pkill flutter,killall flutter) - ✅ User manages Flutter development server - only make code changes
- ✅ Hot reload happens automatically when files are saved
Key Architecture Constraints
- Echo Detection: Uses DJB2-style hash (NO crypto dependency needed)
- Channel Messages: NO ACKs (fire-and-forget flood routing)
- Direct Messages: Automatic ACKs via
PUSH_CODE_SEND_CONFIRMED - SAR Markers: MUST be sent to rooms, NOT public channel
Quick Reference
Project: Flutter Mobile App (iOS 13+, Android API 21+) Architecture: Provider pattern + BLE communication Protocol: MeshCore BLE Companion Radio (Little Endian) Repository: https://github.com/meshcore-dev/meshcore.js
BLE UUIDs
Service: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
RX (write): 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
TX (notify): 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
Key Dependencies
flutter_blue_plus: ^2.0.0 # BLE communication
flutter_map: ^8.2.2 # Mapping + WMS support
provider: ^6.1.0 # State management
geolocator: ^14.0.2 # GPS tracking
proj4dart: ^2.1.0 # Coordinate transformations (EPSG:3794)
flutter_map_tile_caching: ^10.1.1 # Offline tile caching
Project Structure
lib/
├── l10n/ # Internationalization (en, hr, sl)
│ ├── app_localizations.dart # Generated (DO NOT EDIT)
│ └── app_*.arb # Translation files
├── models/ # Data models
│ ├── contact.dart, message.dart, sar_marker.dart
│ ├── map_drawing.dart # Drawing shapes
│ └── sent_message_tracker.dart # Echo detection
├── services/ # Business logic
│ ├── meshcore_ble_service.dart # BLE coordinator
│ ├── protocol/ # Frame parsing & building
│ ├── ble/ # Connection, commands, responses
│ ├── location_tracking_service.dart # GPS + broadcast
│ ├── map_marker_service.dart # Marker generation
│ ├── tile_cache_service.dart # Offline tile caching (WMS + standard)
│ └── validation_service.dart # Form validation
├── providers/ # State management
│ ├── connection_provider.dart # BLE state
│ ├── contacts_provider.dart # Contact list
│ ├── messages_provider.dart # Messages + SAR
│ ├── map_provider.dart # Map navigation
│ ├── drawing_provider.dart # Map drawings
│ └── app_provider.dart # Coordinator
├── screens/ # UI screens
│ └── (home, messages, contacts, map, settings, etc.)
├── widgets/ # Reusable components
│ ├── map_markers.dart
│ └── map/ # Map-specific widgets
└── utils/ # Utilities
├── sar_message_parser.dart
├── drawing_message_parser.dart
└── slovenian_crs.dart # EPSG:3794 CRS for WMS
Protocol Reference
Message Formats
SAR Marker Format
S:<emoji>:<latitude>,<longitude>:<optional_message>
Emojis:
🧑 or 👤 → Found Person
🔥 → Fire Location
🏕️ or ⛺ → Staging Area
Examples:
S:🧑:37.7749,-122.4194
S:🔥:40.7128,-74.0060:Large wildfire spreading rapidly
Map Drawing Format
D:<json>
Line: D:{"t":0,"c":0,"p":[lat1,lon1,lat2,lon2]}
Rectangle: D:{"t":1,"c":1,"b":[topLat,topLon,botLat,botLon]}
Fields:
t = type (0=line, 1=rectangle)
c = color index (0-7: red,blue,green,yellow,orange,purple,pink,cyan)
p = points array (flat)
b = bounds array (rectangles only)
Cayenne LPP Format
[Channel][Type][Data...]
Types:
0x88 (136) → GPS: lat/lon/alt (int32/10000, int32/10000, int32/100)
0x67 (103) → Temperature (int16/10 for °C)
0x02 (2) → Analog Input (uint16/100 for volts/battery)
Command Quick Reference
| Code | Command | Response | Description |
|---|---|---|---|
| 1 | CMD_APP_START | RESP_CODE_SELF_INFO (5) | First command after connection |
| 2 | CMD_SEND_TXT_MSG | RESP_CODE_SENT (6) | Send DM to contact (with ACK) |
| 3 | CMD_SEND_CHANNEL_TXT_MSG | RESP_CODE_SENT (6) | Broadcast to channel (NO ACK) |
| 4 | CMD_GET_CONTACTS | RESP_CODE_CONTACTS_START (2) | Sync contact list |
| 10 | CMD_SYNC_NEXT_MESSAGE | RESP_CODE_CONTACT_MSG_RECV (7) | Pull next message from queue |
| 22 | CMD_DEVICE_QUERY | RESP_CODE_DEVICE_INFO (13) | Get device firmware/hardware info |
| 26 | CMD_SEND_LOGIN | PUSH_CODE_LOGIN_SUCCESS (0x85) | Login to room server |
Push Notifications (Async)
| Code | Name | Purpose |
|---|---|---|
| 0x80 | PUSH_CODE_ADVERT | New advertisement received |
| 0x82 | PUSH_CODE_SEND_CONFIRMED | Message ACK received (DMs only) |
| 0x83 | PUSH_CODE_MSG_WAITING | New message in queue → sync it |
| 0x88 | PUSH_CODE_LOG_RX_DATA | Raw packet capture (always-on diagnostic) |
| 0x85 | PUSH_CODE_LOGIN_SUCCESS | Room login successful |
| 0x86 | PUSH_CODE_LOGIN_FAIL | Room login failed |
Constants
Contact Types (ADV_TYPE)
0 = ADV_TYPE_NONE # Unknown/invalid
1 = ADV_TYPE_CHAT # Team member (shown on map)
2 = ADV_TYPE_REPEATER # Network repeater
3 = ADV_TYPE_ROOM # Communication room/server
Message Types (TXT_TYPE)
0 = TXT_TYPE_PLAIN # Plain text
1 = TXT_TYPE_CLI_DATA # CLI command
2 = TXT_TYPE_SIGNED_PLAIN # Text + 4-byte pubkey signature
Error Codes (ERR_CODE)
1 = ERR_CODE_UNSUPPORTED_CMD
2 = ERR_CODE_NOT_FOUND
3 = ERR_CODE_TABLE_FULL
4 = ERR_CODE_BAD_STATE
5 = ERR_CODE_FILE_IO_ERROR
6 = ERR_CODE_ILLEGAL_ARG
Architecture
Provider Hierarchy
MultiProvider
├── ConnectionProvider # BLE connection state
├── ContactsProvider # Contact list management
├── MessagesProvider # Messages + SAR markers
├── MapProvider # Map navigation state
├── DrawingProvider # Map drawing state
└── AppProvider # Coordinator (wires everything)
Event Flow
BLE Device → MeshCoreBleService → ConnectionProvider → AppProvider
↓
ContactsProvider
MessagesProvider
DrawingProvider
↓
UI
Contact Path Status
outPathLen indicates routing mode:
- -1 (0xFF): Path unknown → Flood mode (broadcast to all neighbors)
- 0: Direct connection → Direct mode (zero hops, best quality)
- 1-64: Multi-hop path → Direct mode (uses learned routing)
Map Display: Only ContactType.chat contacts with valid GPS coordinates shown.
Channels vs. Rooms
Channels (numeric identifiers):
- Channel 0 = "Public Channel" (default broadcast)
- Ephemeral - messages NOT persisted
- Uses
CMD_SEND_CHANNEL_TXT_MSG(3) - NO ACKs - fire-and-forget flood routing
- Pre-configured with secret:
8b3387e9c5cdea6ac9e5edbaa115cd72(hex)
Rooms (ADV_TYPE_ROOM contacts):
- Named contacts with public keys
- Persistent storage on room server
- Uses
CMD_SEND_TXT_MSG(2) with room's public key - Automatic ACKs when messages stored
- Login via
CMD_SEND_LOGIN(26) to receive stored messages
SAR Routing: SAR markers MUST go to rooms for reliable delivery.
Room Login Protocol
1. Send CMD_SEND_LOGIN (26)
↓
2. Radio generates sender_timestamp & sync_since
↓
3. Room validates password, stores sync_since
↓
4. Receive PUSH_CODE_LOGIN_SUCCESS (0x85) or PUSH_CODE_LOGIN_FAIL (0x86)
↓
5. Room auto-pushes messages (1200ms intervals)
↓
6. Receive PUSH_CODE_MSG_WAITING (0x83) → call CMD_SYNC_NEXT_MESSAGE (10)
CRITICAL: Do NOT call syncAllMessages() after login. Wait for push notifications.
Echo Detection Feature
Overview
Status: ✅ Fully implemented and production-ready Purpose: Detect when broadcast messages are rebroadcast by mesh nodes
How It Works
-
Packet Identification (via
PUSH_CODE_LOG_RX_DATA0x88):Packet Structure (PAYLOAD_TYPE_GRP_TXT 0x05): [Byte 0] = Header (route + payload type + version) [Byte 1] = Path length [Byte 2] = Sender's node hash (first byte of public key) ✅ [Byte 3+] = Rest of path + encrypted payload -
On Connection:
- Receive
RESP_CODE_SELF_INFOwith our public key - Extract our node hash (byte 0 of public key)
- Store for packet matching
- Receive
-
Sending a Message:
- Call
trackSentMessage(messageId)when user sends channel message - Status = "pending" (waiting for packet capture)
- Call
-
Packet Capture (50-200ms later):
- Radio sends
PUSH_CODE_LOG_RX_DATAwith raw packet - Extract sender hash from packet[2]
- If sender hash == our node hash → This is our packet!
- Calculate DJB2-style hash of entire packet (no crypto dependency)
- Store tracker by packet hash for echo detection
- Radio sends
-
Echo Detection:
- Future
PUSH_CODE_LOG_RX_DATApackets arrive - Calculate packet hash and lookup in tracker map (O(1))
- If match found → Echo detected! Increment counter
- Track SNR/RSSI signature for path diversity
- UI auto-updates to show "Rebroadcast by X nodes"
- Future
Implementation Details
Files:
lib/models/sent_message_tracker.dart- Tracker modellib/models/message.dart- echoCount, firstEchoAt fieldslib/services/ble/ble_response_handler.dart- Detection enginelib/services/meshcore_ble_service.dart- Callback wiringlib/providers/connection_provider.dart- Provider callbacklib/providers/messages_provider.dart- handleMessageEcho()
Performance:
- Packet ID: O(1) - byte comparison at offset 2
- Hash calc: O(n) where n = packet length (~38-200 bytes)
- Echo lookup: O(1) via HashMap
- Memory: ~150 bytes/message, max 100 messages = ~15KB
- TTL: 5-minute expiry, auto-cleanup
Limitations:
- Echo count ≠ exact receiver count (one node can echo multiple times)
- Only detects echoes while app connected
- Network topology dependent (dense networks → more echoes)
Common Tasks
Adding a New BLE Command
- Add code to
lib/services/meshcore_constants.dart - Build frame in
lib/services/protocol/frame_builder.dart - Add API method in
lib/services/meshcore_ble_service.dart - Parse response in
lib/services/protocol/frame_parser.dart - Handle in
lib/services/ble/ble_response_handler.dart - Add callback in
lib/services/meshcore_ble_service.dart
Adding a New SAR Marker Type
- Update enum in
lib/models/sar_marker.dart - Add parser logic in
lib/utils/sar_message_parser.dart - Add color mapping in
lib/widgets/map_markers.dart - Update handling in
lib/providers/messages_provider.dart
Adding Localized Strings
-
Add to
lib/l10n/app_en.arb:{ "myNewString": "My new text", "@myNewString": { "description": "What this string is for" } } -
Add translations to
app_hr.arbandapp_sl.arb -
Generate:
flutter gen-l10n -
Use in code:
import '../l10n/app_localizations.dart'; Text(AppLocalizations.of(context)!.myNewString)
IMPORTANT: Always use relative import path '../l10n/app_localizations.dart'
Importing/Exporting Map Tiles
The app supports importing and exporting cached map tiles using the flutter_map_tile_caching library's archive format (.fmtc files).
Export Workflow:
- Navigate to Map Management screen
- Tap "Export Tiles to File"
- Archive is created in temporary directory with gzip compression
- System share sheet appears (iOS/Android)
- Choose where to save: Files app, email, cloud storage, etc.
- File named:
meshcore_tiles_<timestamp>.fmtc
Import Workflow:
- Navigate to Map Management screen
- Tap "Import Tiles from File"
- Select
.fmtcarchive file - Tiles are merged with existing cache
- Cache statistics refreshed automatically
API Usage:
// Export current cache to file
final tileCount = await tileCacheService.exportStore(
'/path/to/export.fmtc',
);
// Import tiles from archive (with merge strategy)
final result = await tileCacheService.importStore(
'/path/to/import.fmtc',
storeNames: null, // null = import all stores
strategy: ImportConflictStrategy.merge, // default: merge
);
// Preview stores in archive before importing
final stores = await tileCacheService.listArchiveStores(
'/path/to/archive.fmtc',
);
Use Cases:
- Backup: Export tiles before clearing cache or reinstalling app
- Sharing: Pre-download maps once, distribute to team devices
- Disaster Recovery: Restore offline maps after device reset
- Bandwidth Saving: Reduce cellular data usage by sharing cached tiles
Technical Details:
- Archive format:
.fmtc(FMTC native format) - Compression: gzip (built-in by FMTC)
- Import strategy: Merge (tiles combined with existing cache)
- Export location: Temporary directory → system share sheet
- Import source: User-selected via
file_pickerpackage - Conflict handling: Automatic merge of tile stores
- Cross-platform: Works on Android and iOS using native share mechanisms
File: lib/services/tile_cache_service.dart - Export/import methods
UI: lib/screens/map_management_screen.dart - Import/export card
Map Drawing Workflow
User selects mode → DrawingProvider.setDrawingMode()
↓
User taps map → DrawingLayer captures touches
↓
Preview rendered → DrawingProvider.getPreviewDrawing()
↓
User completes → Saved to DrawingProvider._drawings
↓
User shares → DrawingToolbar._shareDrawingsToChannel/Room()
↓
BLE send → ConnectionProvider.sendChannelMessage/sendTextMessage()
↓
Receiver parses → DrawingMessageParser.parseDrawingMessage()
↓
Display → DrawingProvider.addReceivedDrawing()
Build & Troubleshooting
Build Commands
# Dependencies
flutter pub get
flutter gen-l10n # After ARB file changes
# Development
flutter run # Debug mode (user controls this)
# Hot reload: save file | Hot restart: press 'R' in terminal
# Code Quality
flutter analyze
flutter test
dart format lib/
flutter clean
# Release
flutter build ios --release
flutter build ipa
flutter build apk --release
flutter build appbundle --release
Common Issues
BLE Connection Failed:
- Check Bluetooth enabled and permissions granted
- Verify device in range (<10m)
- Check service UUID matches:
6E400001-B5A3-F393-E0A9-E50E24DCCA9E
MissingPluginException (after adding dependencies):
cd ios && pod install && cd ..
flutter clean
flutter pub get
flutter run
iOS Pod Install Fails:
cd ios
rm Podfile.lock
rm -rf Pods/
pod install --repo-update
cd ..
Android Gradle Timeout - Add to android/gradle.properties:
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096m
Performance Tips
BLE:
- Buffer partial packets
- Throttle telemetry (max 1/sec per contact)
- Use
notifyListeners()sparingly
Map:
- Cluster markers if >100 visible
- Use
RepaintBoundaryfor marker widgets - Implement marker virtualization for large datasets
Memory:
- Dispose controllers in
dispose()methods - Limit message history to 1000 messages
- Set tile cache size limits
Services Reference
LocationTrackingService (Singleton)
Purpose: GPS tracking + intelligent mesh location broadcasting
Callbacks: onPositionUpdate, onError, onBroadcastSent, onTrackingStateChanged
Thresholds:
- Min distance: 5.0m
- Max distance: 100.0m
- Min time: 30s
Logic:
- First update → broadcast immediately
- ≥100m moved → broadcast immediately
- ≥5m + ≥30s → broadcast
File: lib/services/location_tracking_service.dart (501 lines)
MapMarkerService (Singleton)
Purpose: Map marker generation + geodesic calculations
Features:
- Pure functions (testable)
- Contact markers with battery badge, distance
- SAR markers (color-coded by type)
- Distance/bearing calculations (Haversine)
- "Time ago" formatting
File: lib/services/map_marker_service.dart (518 lines)
ValidationService (Singleton)
Purpose: Form validation + input parsing
Returns: Structured ValidationResult<T> or ParseResult<T>
Validates:
- Coordinates (lat: -90 to +90, lon: -180 to +180)
- Radio params (freq: 137-1020 MHz, bw: 7.8-500 kHz, sf: 5-12, cr: 5-8, tx: -9 to +22 dBm)
- Text/names, zoom levels (0-19)
File: lib/services/validation_service.dart (511 lines)
Map Implementation
Tile Layers
- OpenStreetMap (default) - Max zoom 19
- OpenTopoMap - Max zoom 17, topographic
- ESRI World Imagery - Max zoom 19, satellite
WMS Support
Purpose: Integration with Web Map Service (WMS) providers for specialized mapping data
Slovenian CRS (EPSG:3794):
- Projection: Transverse Mercator (Slovenia 1996 / Slovene National Grid)
- Ellipsoid: GRS80
- Usage: Slovenian government WMS/WMTS services (prostor.zgs.gov.si)
- Zoom levels: 0-15 (GeoWebCache tile matrix)
- Bounds: X: 373217.65-695777.65m, Y: 31118.30-246158.30m
- Origin: Top-left (373217.65, 246158.30)
- Resolutions: Calculated from scale denominators (420m/px at zoom 0 to 0.028m/px at zoom 15)
Tile Caching:
- All WMS layers (base + overlays) use
flutter_map_tile_caching - Cache strategy:
cacheFirst(offline-first with 30-day validity) - Same caching infrastructure as standard tile layers
Files:
lib/utils/slovenian_crs.dart- EPSG:3794 CRS definitionlib/services/tile_cache_service.dart- getTileProviderForWms() method
Example:
import 'package:meshcore_sar_app/utils/slovenian_crs.dart';
// All WMS layers automatically use the cached tile provider
TileLayer(
wmsOptions: WMSTileLayerOptions(
baseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?',
layers: ['pregledovalnik:DOF_2024'],
format: 'image/jpeg',
crs: slovenianCrs,
),
tileProvider: tileCacheService.getTileProviderForWms(layer),
)
WMS Implementation Details
Overview
The app integrates Slovenian government WMS (Web Map Service) layers using a custom EPSG:3794 coordinate reference system. This enables high-resolution aerial imagery and specialized overlays (cadastral parcels, forest roads) for SAR operations in Slovenia.
Architecture
Layer Types:
-
Base Layer: Slovenian Aerial Imagery 2024 (DOF_2024)
- Source:
https://prostor.zgs.gov.si/geowebcache/service/wms - Format: JPEG (better compression for aerial photos)
- Transparency: False (opaque base layer)
- Max zoom: 15 (GeoWebCache limit)
- Source:
-
Overlay Layers: Cadastral Parcels, Forest Roads
- Format: PNG (supports transparency)
- Transparency: True (overlays on base layer)
- Max zoom: 19
Coordinate System (EPSG:3794):
- Official name: Slovenia 1996 / Slovene National Grid
- Projection: Transverse Mercator
- Parameters:
+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000 +ellps=GRS80 - Why needed: Slovenian government services use this instead of standard Web Mercator (EPSG:3857)
Tile Grid Alignment: The critical challenge with WMS layers is aligning the client-side tile grid with the server's GeoWebCache configuration. Misalignment causes 400 Bad Request errors.
Correct Configuration (lib/utils/slovenian_crs.dart):
// Origin MUST match WMTS TileMatrixSet TopLeftCorner
final origin = Point<double>(373217.6542445397, 246158.298050262);
// Bounds MUST match WMS capabilities extent
final bounds = Rect.fromLTRB(
373217.65, // min X (west)
31118.30, // min Y (south)
695777.65, // max X (east)
246158.30, // max Y (north)
);
// Resolutions MUST be calculated from scale denominators
// Formula: resolution = scaleDenominator * 0.00028 (OGC standard)
final resolutions = [
420.0, // Zoom 0: 1,500,000 * 0.00028
280.0, // Zoom 1: 1,000,000 * 0.00028
// ... through zoom 15
];
How to Get Correct Values:
- Query WMTS GetCapabilities:
curl "https://prostor.zgs.gov.si/geowebcache/service/wmts?REQUEST=GetCapabilities&SERVICE=WMTS" - Find
<TileMatrixSet>for EPSG:3794 - Extract
<TopLeftCorner>(origin) - Extract
<ScaleDenominator>for each<TileMatrix>(convert to resolutions) - Query WMS GetCapabilities for bounds:
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities&SERVICE=WMS"
Caching Strategy
Implementation (lib/services/tile_cache_service.dart):
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
return _store.getTileProvider(
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
cachedValidDuration: const Duration(days: 30),
);
}
Behavior:
- Cache First: Check local cache before network request
- 30-Day Validity: Tiles expire after 30 days (suitable for aerial imagery that updates infrequently)
- Automatic Caching: All viewed tiles automatically saved to ObjectBox database
- Offline Support: Cached tiles available when device offline
Storage Location:
- Backend: ObjectBox (embedded database)
- Store name: 'meshcore_tiles' (shared with standard tile layers)
- Format: Binary tile data + metadata (URL, timestamp, headers)
Usage in Map
Base Layer (lib/screens/map_tab.dart):
if (_currentLayer.isWms && _currentLayer.crs != null) {
TileLayer(
wmsOptions: WMSTileLayerOptions(
baseUrl: _currentLayer.wmsBaseUrl!,
layers: _currentLayer.wmsLayers ?? [],
format: _currentLayer.wmsFormat ?? 'image/jpeg',
crs: _currentLayer.crs!, // EPSG:3794
),
tileProvider: _tileCache.getTileProviderForWms(_currentLayer),
maxZoom: _currentLayer.maxZoom, // 15 for Slovenian layers
);
}
Map Options:
MapOptions(
// CRITICAL: Use layer's CRS, not default EPSG:3857
crs: _currentLayer.crs ?? const Epsg3857(),
// ... other options
)
Troubleshooting
400 Bad Request Errors:
- Cause: Tile grid misalignment (wrong origin, bounds, or resolutions)
- Fix: Verify values match WMTS GetCapabilities exactly
- Debug: Check WMS URL in error logs for out-of-bounds coordinates
Tiles Not Caching:
- Cause: Using wrong tile provider (e.g., NetworkTileProvider instead of FMTC)
- Fix: Ensure
getTileProviderForWms()is used, notNetworkTileProvider()or custom providers - Verify: Check
tile_cache_service.dart:75is being called
Layer Not Appearing:
- Cause 1: Layer name mismatch (e.g.,
DOF_2024vspregledovalnik:DOF_2024) - Cause 2: Wrong CRS in MapOptions (using EPSG:3857 instead of EPSG:3794)
- Fix: Verify layer name in WMS GetCapabilities, ensure
crs: _currentLayer.crsin MapOptions
Performance Issues:
- Issue: Slow tile loading on first view
- Expected: WMS tile generation is slower than pre-rendered tiles (100-500ms per tile)
- Mitigation: Pre-download regions using Map Management screen
Adding New WMS Layers
-
Find Layer in GetCapabilities:
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities" | grep "<Name>" -
Add to MapLayer (
lib/models/map_layer.dart):static MapLayer getMyNewLayer(Crs slovenianCrs) { return MapLayer( type: MapLayerType.wmsBase, // or create new enum value name: 'My New Layer', urlTemplate: '', // Not used for WMS attribution: '© Data Provider', maxZoom: 15, // Match GeoWebCache capability isWms: true, wmsBaseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?', wmsLayers: const ['workspace:layername'], wmsFormat: 'image/png', // or 'image/jpeg' wmsTransparent: true, // true for overlays, false for base crs: slovenianCrs, ); } -
Verify CRS Support: Ensure layer supports EPSG:3794 in GetCapabilities
-
Test: Check for 400 errors, verify tiles load correctly
Technical Notes
Why Not Use WMTS Instead of WMS?
flutter_maphas excellent WMS support viaWMSTileLayerOptions- WMS and WMTS use same GeoWebCache backend (identical tiles)
- WMS is simpler to configure (no manual tile URL template)
- Caching abstracts the protocol difference
Proj4dart Integration:
- Handles coordinate transformation from EPSG:4326 (GPS) to EPSG:3794 (map)
- Projection registered once at app startup:
proj4.Projection.add('EPSG:3794', ...) - Flutter Map uses it automatically when
crs: slovenianCrsis set
Memory Considerations:
- Each CRS instance stores transformation matrices and bounds
- Use singleton pattern:
final Crs slovenianCrs = getSlovenianCrs(); - Shared across all WMS layers
Offline Caching
- Backend:
flutter_map_tile_caching+ ObjectBox - Behavior:
CacheBehavior.cacheFirst, 30-day validity - Downloads:
RectangleRegion(bounds).download.startForeground()
Marker Types
Team Member: Blue circle, battery badge, name, distance, tap for details SAR Event: Color-coded (green=person, red=fire, orange=staging), time ago, tap for details
Navigation Flow
Messages tab → tap SAR marker
↓
MapProvider.navigateToLocation()
↓
Switch to Map tab
↓
MapTab._handleMapNavigation() → animate to location
↓
MapProvider.clearNavigation()
References
- Flutter Documentation
- flutter_blue_plus API
- flutter_map Documentation
- MeshCore Protocol
- MeshCore FAQ
- Cayenne LPP Specification
- Provider Package
- EPSG:3794 Reference - Slovenian CRS definition
- Proj4dart Package - Coordinate transformation library
- OGC WMS Specification - Web Map Service standard
Security Considerations
- BLE: No authentication in current protocol - consider encryption for production
- Permissions: Request minimum required permissions only
- Logging: No sensitive data in logs
- Network: HTTPS for all tile sources
- Raw Packets:
PUSH_CODE_LOG_RX_DATAexposes all radio traffic (diagnostic feature)