mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add WMS overlays for hiking trails, main roads, house numbers, fire hazard zones, historical fires, firebreaks, Kras fire zones, place names, and municipality borders
- Updated localization files for German, English, Spanish, French, Croatian, Italian, and Slovenian to include new overlay labels. - Implemented new WMS layers in the map provider and map tab for displaying hiking trails, main roads, house numbers, fire hazard zones, historical fires, firebreaks, Kras fire zones, place names, and municipality borders. - Added toggle functionality for each new overlay in the map provider. - Enhanced the map tab UI to include checkboxes for the new overlays, allowing users to enable or disable them. - Integrated the new WMS layers into the map rendering logic.
This commit is contained in:
@@ -41,7 +41,9 @@
|
||||
"WebFetch(domain:github.com)",
|
||||
"Bash(find:*)",
|
||||
"WebFetch(domain:prostor.zgs.gov.si)",
|
||||
"Bash(curl:*)"
|
||||
"Bash(curl:*)",
|
||||
"Read(//private/tmp/**)",
|
||||
"Read(///**)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
463
WMS_IMPLEMENTATION_PLAN.md
Normal file
463
WMS_IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Implementation Plan: Generic WMS Server Support
|
||||
|
||||
## Overview
|
||||
Refactor the hardcoded Slovenian WMS implementation to support adding custom WMS servers while preserving existing functionality.
|
||||
|
||||
## Goals
|
||||
1. **Add WMS Server Management** - UI to add/edit/delete custom WMS servers via GetCapabilities URL
|
||||
2. **Layer Selection** - Allow users to select base layers and overlays from any WMS server
|
||||
3. **Preserve Slovenian Layers** - Keep existing EPSG:3794 support and language filtering
|
||||
4. **Dynamic CRS** - Support common coordinate systems (EPSG:3857, 4326, 3794)
|
||||
5. **Base Layer Switching** - Allow switching base layers on top of other layers
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Core Models & Parsing (Foundation)
|
||||
|
||||
**1.1 Create WMS Server Model** (`lib/models/wms_server.dart`)
|
||||
- `WmsServer` class with id, name, capabilitiesUrl, version, layers list
|
||||
- `WmsLayer` class with name, title, abstract, styles, CRS support, bounds
|
||||
- JSON serialization for persistence
|
||||
|
||||
**1.2 Implement WMS Capabilities Parser** (`lib/services/wms_capabilities_parser.dart`)
|
||||
- Add `xml: ^7.0.0` dependency to pubspec.yaml
|
||||
- Parse WMS 1.3.0 GetCapabilities XML
|
||||
- Extract service metadata, supported formats, CRS list
|
||||
- Recursively parse layer hierarchy (handle groups vs leaf layers)
|
||||
- Extract bounding boxes, styles, and metadata
|
||||
|
||||
**1.3 Create CRS Factory** (`lib/utils/crs_factory.dart`)
|
||||
- Registry for common CRS: EPSG:3857 (Web Mercator), EPSG:4326 (WGS84), EPSG:3794 (Slovenian)
|
||||
- Cache CRS instances to avoid re-creating projection objects
|
||||
- Support for retrieving CRS by EPSG code
|
||||
|
||||
**1.4 Create Storage Repository** (`lib/services/wms_server_repository.dart`)
|
||||
- Store custom WMS servers in SharedPreferences as JSON
|
||||
- CRUD operations: save, load, delete servers
|
||||
- Persist layer visibility state per server/layer
|
||||
|
||||
### Phase 2: UI Implementation
|
||||
|
||||
**2.1 WMS Server Management Screen** (`lib/screens/wms_server_management_screen.dart`)
|
||||
- List of saved WMS servers (edit/delete actions)
|
||||
- "Add WMS Server" button → capabilities URL input dialog
|
||||
- Show loading indicator while fetching GetCapabilities
|
||||
- Parse and preview available layers
|
||||
- Layer picker with checkboxes (base layers vs overlays)
|
||||
- Validate CRS compatibility (warn if unsupported)
|
||||
- Save button stores server + selected layers
|
||||
|
||||
**2.2 Refactor Layer Selector** (`lib/screens/map_tab.dart`)
|
||||
- Keep existing sections: Standard Online Layers, Built-in WMS (Slovenian - language filtered), MBTiles
|
||||
- Add new section: "Custom WMS Servers" (expandable)
|
||||
- Each custom server shows its base layers as selectable tiles
|
||||
- "Manage WMS Servers" button at bottom → opens management screen
|
||||
|
||||
**2.3 Overlay Management**
|
||||
- Extend existing cadastral/forest roads overlay pattern to custom WMS layers
|
||||
- Store overlay visibility per server/layer combination
|
||||
- Add overlay toggles to layer selector when custom WMS base layer active
|
||||
|
||||
### Phase 3: Integration & Testing
|
||||
|
||||
**3.1 Refactor MapLayer Model** (`lib/models/map_layer.dart`)
|
||||
- Add fields: `wmsServerId` (reference to custom server), `crsCode` (EPSG string)
|
||||
- Add `isBuiltIn` flag to distinguish Slovenian layers from custom
|
||||
- Factory method: `MapLayer.fromWmsServer(WmsServer, WmsLayer, Crs)`
|
||||
- Keep existing Slovenian layer factories unchanged
|
||||
|
||||
**3.2 Update MapProvider** (`lib/providers/map_provider.dart`)
|
||||
- Load custom WMS servers on init
|
||||
- Handle base layer switching with CRS changes
|
||||
- Automatically clamp zoom level when switching to layers with lower maxZoom
|
||||
- Persist selected custom WMS layer to SharedPreferences
|
||||
|
||||
**3.3 Tile Caching Integration**
|
||||
- Verify existing `getTileProviderForWms()` works with custom servers
|
||||
- No changes needed (already uses `flutter_map`'s WMS tile URL generation)
|
||||
|
||||
**3.4 Testing**
|
||||
- Test with known WMS servers: NASA GIBS, USGS, OpenStreetMap WMS
|
||||
- Test Slovenian layers still work (no regression)
|
||||
- Test CRS switching (3857 ↔ 4326 ↔ 3794)
|
||||
- Test offline caching with custom WMS tiles
|
||||
- Test error cases: invalid URL, timeout, unsupported CRS, malformed XML
|
||||
|
||||
### Phase 4: Polish & Documentation
|
||||
|
||||
**4.1 Localization**
|
||||
- Add strings to `lib/l10n/app_*.arb` files:
|
||||
- "Add WMS Server", "Capabilities URL", "Custom WMS Servers"
|
||||
- Error messages: "Invalid URL", "Parsing failed", "Unsupported CRS"
|
||||
- Generate with `flutter gen-l10n`
|
||||
|
||||
**4.2 Error Handling**
|
||||
- Network timeout (30s) with user-friendly error
|
||||
- XML parsing errors with "Invalid GetCapabilities response"
|
||||
- Unsupported CRS warning with fallback suggestion
|
||||
- Handle missing layer names, invalid bounds gracefully
|
||||
|
||||
**4.3 Update Documentation** (`CLAUDE.md`)
|
||||
- Document WMS server management workflow
|
||||
- Add custom WMS section to "Common Tasks"
|
||||
- Update architecture diagram with new models/services
|
||||
- Add troubleshooting section for common WMS issues
|
||||
|
||||
## Technical Decisions
|
||||
|
||||
### Preservation of Slovenian Layers
|
||||
- **Approach**: Keep existing code paths intact, add `isBuiltIn: true` flag
|
||||
- **Language Filtering**: Remains unchanged (sl/hr only see Slovenian layers)
|
||||
- **CRS**: EPSG:3794 remains as singleton in `slovenian_crs.dart`
|
||||
|
||||
### Storage Strategy
|
||||
- **Choice**: SharedPreferences (JSON serialization)
|
||||
- **Rationale**: Estimated 700KB for 10 servers × 50 layers (under 1MB limit)
|
||||
- **Migration Path**: Can move to SQLite if users need >10 servers
|
||||
|
||||
### CRS Support (Initial Release)
|
||||
- **Supported**: EPSG:3857 (Web Mercator), EPSG:4326 (WGS84), EPSG:3794 (Slovenian)
|
||||
- **Unsupported**: Show warning, prevent selection
|
||||
- **Future**: Add manual CRS configuration for advanced users
|
||||
|
||||
### WMS Version Support
|
||||
- **Phase 1**: WMS 1.3.0 only (most common)
|
||||
- **Future**: Add WMS 1.1.1 (requires axis order handling)
|
||||
|
||||
## Key Files to Modify
|
||||
|
||||
**New Files**:
|
||||
- `lib/models/wms_server.dart`
|
||||
- `lib/services/wms_capabilities_parser.dart`
|
||||
- `lib/services/wms_server_repository.dart`
|
||||
- `lib/utils/crs_factory.dart`
|
||||
- `lib/screens/wms_server_management_screen.dart`
|
||||
|
||||
**Modified Files**:
|
||||
- `lib/models/map_layer.dart` (add fields, factory method)
|
||||
- `lib/screens/map_tab.dart` (layer selector UI)
|
||||
- `lib/providers/map_provider.dart` (load custom servers, persistence)
|
||||
- `lib/l10n/app_*.arb` (localized strings)
|
||||
- `pubspec.yaml` (add `xml: ^7.0.0`)
|
||||
- `CLAUDE.md` (documentation update)
|
||||
|
||||
**Unchanged Files** (critical for backward compatibility):
|
||||
- `lib/utils/slovenian_crs.dart` ✅
|
||||
- `lib/services/tile_cache_service.dart` ✅
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Slow GetCapabilities responses (5-30s) | Show loading indicator, 30s timeout, cache for 24h |
|
||||
| Unsupported CRS breaks map | Validate CRS before saving, show warning, fallback to 3857 |
|
||||
| Tile matrix mismatch causes 400 errors | Document limitation, suggest WMTS if available |
|
||||
| Breaking existing Slovenian functionality | Keep all existing code paths, add `isBuiltIn` flag, extensive testing |
|
||||
|
||||
## Estimated Timeline
|
||||
- **Phase 1**: 1-2 weeks (models, parser, storage)
|
||||
- **Phase 2**: 1 week (UI screens)
|
||||
- **Phase 3**: 1-2 weeks (integration, testing)
|
||||
- **Phase 4**: 1 week (polish, docs)
|
||||
- **Total**: 4-6 weeks (single developer, full-time)
|
||||
|
||||
## Success Criteria
|
||||
✅ Users can add custom WMS servers via GetCapabilities URL
|
||||
✅ Users can select base layers and overlays from custom servers
|
||||
✅ Slovenian layers continue to work with language filtering
|
||||
✅ Base layer switching works (including CRS changes)
|
||||
✅ Custom WMS tiles are cached for offline use
|
||||
✅ Clear error messages for invalid/unsupported servers
|
||||
✅ No regressions in existing map functionality
|
||||
|
||||
---
|
||||
|
||||
## Detailed Implementation Notes
|
||||
|
||||
### WMS Server Model Structure
|
||||
|
||||
```dart
|
||||
class WmsServer {
|
||||
final String id; // UUID for persistence
|
||||
final String name; // User-friendly name
|
||||
final String capabilitiesUrl; // GetCapabilities endpoint
|
||||
final String version; // WMS version (1.1.1, 1.3.0)
|
||||
final List<WmsLayer> availableLayers;
|
||||
final List<String> supportedCrs;
|
||||
final DateTime lastUpdated; // Cache invalidation
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
factory WmsServer.fromJson(Map<String, dynamic> json);
|
||||
}
|
||||
|
||||
class WmsLayer {
|
||||
final String name; // Layer identifier
|
||||
final String title; // Human-readable title
|
||||
final String? abstract;
|
||||
final List<String> styles;
|
||||
final LatLngBounds? boundingBox;
|
||||
final List<String> supportedCrs;
|
||||
final bool supportsTransparency;
|
||||
final String? legendUrl;
|
||||
}
|
||||
```
|
||||
|
||||
### WMS GetCapabilities XML Parsing
|
||||
|
||||
Key elements to extract:
|
||||
|
||||
```xml
|
||||
<WMS_Capabilities version="1.3.0">
|
||||
<Service>
|
||||
<Title>Server Name</Title>
|
||||
</Service>
|
||||
<Capability>
|
||||
<Layer>
|
||||
<Title>Root Layer</Title>
|
||||
<CRS>EPSG:4326</CRS>
|
||||
<CRS>EPSG:3857</CRS>
|
||||
<Layer queryable="1">
|
||||
<Name>layer_name</Name>
|
||||
<Title>Layer Title</Title>
|
||||
<CRS>EPSG:3857</CRS>
|
||||
<EX_GeographicBoundingBox>
|
||||
<westBoundLongitude>-180</westBoundLongitude>
|
||||
<eastBoundLongitude>180</eastBoundLongitude>
|
||||
<southBoundLatitude>-90</southBoundLatitude>
|
||||
<northBoundLatitude>90</northBoundLatitude>
|
||||
</EX_GeographicBoundingBox>
|
||||
<Style>
|
||||
<Name>default</Name>
|
||||
</Style>
|
||||
</Layer>
|
||||
</Layer>
|
||||
</Capability>
|
||||
</WMS_Capabilities>
|
||||
```
|
||||
|
||||
### CRS Factory Implementation
|
||||
|
||||
```dart
|
||||
class CrsFactory {
|
||||
static final Map<String, Crs> _crsCache = {
|
||||
'EPSG:3794': getSlovenianCrs(),
|
||||
'EPSG:3857': const Epsg3857(),
|
||||
'EPSG:4326': const Epsg4326(),
|
||||
};
|
||||
|
||||
static Crs? getCrs(String epsgCode) {
|
||||
return _crsCache[epsgCode];
|
||||
}
|
||||
|
||||
static bool isSupported(String epsgCode) {
|
||||
return _crsCache.containsKey(epsgCode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Storage JSON Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid-here",
|
||||
"name": "My WMS Server",
|
||||
"capabilitiesUrl": "https://example.com/wms?SERVICE=WMS&REQUEST=GetCapabilities",
|
||||
"version": "1.3.0",
|
||||
"lastUpdated": "2025-10-28T12:00:00Z",
|
||||
"layers": [
|
||||
{
|
||||
"name": "layer_name",
|
||||
"title": "Layer Title",
|
||||
"abstract": "Description...",
|
||||
"supportedCrs": ["EPSG:3857", "EPSG:4326"],
|
||||
"boundingBox": {
|
||||
"south": -90, "west": -180,
|
||||
"north": 90, "east": 180
|
||||
},
|
||||
"styles": ["default"],
|
||||
"supportsTransparency": true
|
||||
}
|
||||
],
|
||||
"supportedCrs": ["EPSG:3857", "EPSG:4326"]
|
||||
}
|
||||
```
|
||||
|
||||
### UI Flow Diagrams
|
||||
|
||||
**Adding a WMS Server**:
|
||||
1. User taps "Manage WMS Servers" in Map Management screen
|
||||
2. User taps "Add WMS Server" button
|
||||
3. Dialog appears with text field for capabilities URL
|
||||
4. User enters URL (e.g., `https://prostor.zgs.gov.si/geoserver/wms?SERVICE=WMS&REQUEST=GetCapabilities`)
|
||||
5. App fetches and parses GetCapabilities
|
||||
6. Layer picker shows available layers with checkboxes
|
||||
7. User selects layers to use as base layers or overlays
|
||||
8. User taps "Save" → server stored in SharedPreferences
|
||||
9. Layers appear in map layer selector
|
||||
|
||||
**Using a Custom WMS Layer**:
|
||||
1. User opens layer selector in Map tab
|
||||
2. User scrolls to "Custom WMS Servers" section
|
||||
3. User taps custom layer → map switches to that layer
|
||||
4. If CRS differs from previous layer, map CRS updates
|
||||
5. If zoom level exceeds layer's maxZoom, zoom is clamped
|
||||
6. Overlay toggles appear if custom server has overlay layers
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
**Functional Tests**:
|
||||
- [ ] Add WMS server with valid GetCapabilities URL
|
||||
- [ ] Parse layers with nested hierarchy (group layers)
|
||||
- [ ] Select base layer from custom WMS server
|
||||
- [ ] Switch between standard tile layer and custom WMS layer
|
||||
- [ ] Switch between custom WMS layers with different CRS
|
||||
- [ ] Toggle overlay layers from custom WMS server
|
||||
- [ ] Delete custom WMS server
|
||||
- [ ] Edit custom WMS server (re-fetch capabilities)
|
||||
- [ ] Persist selected custom WMS layer across app restarts
|
||||
|
||||
**Error Handling Tests**:
|
||||
- [ ] Invalid URL (malformed)
|
||||
- [ ] Network timeout (30s)
|
||||
- [ ] Invalid XML (not a GetCapabilities response)
|
||||
- [ ] Empty layer list
|
||||
- [ ] Unsupported CRS (show warning, prevent selection)
|
||||
- [ ] Missing required fields (layer name, title)
|
||||
|
||||
**Regression Tests**:
|
||||
- [ ] Slovenian aerial imagery still loads
|
||||
- [ ] Slovenian overlays (cadastral, forest roads) still work
|
||||
- [ ] Language filtering (sl/hr) still hides WMS layers for other locales
|
||||
- [ ] EPSG:3794 CRS still works correctly
|
||||
- [ ] Standard tile layers (OSM, OpenTopoMap) still work
|
||||
- [ ] MBTiles offline layers still work
|
||||
- [ ] Tile caching still works for WMS tiles
|
||||
|
||||
**Performance Tests**:
|
||||
- [ ] GetCapabilities fetch completes within 30s
|
||||
- [ ] Large layer lists (>100 layers) render without lag
|
||||
- [ ] Switching layers is smooth (no UI freeze)
|
||||
- [ ] SharedPreferences storage under 1MB for 10 servers
|
||||
|
||||
### Example WMS Servers for Testing
|
||||
|
||||
**Public WMS Servers**:
|
||||
1. **NASA GIBS** (satellite imagery):
|
||||
- URL: `https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi?SERVICE=WMS&REQUEST=GetCapabilities`
|
||||
- CRS: EPSG:4326
|
||||
- Layers: MODIS, VIIRS, Landsat
|
||||
|
||||
2. **USGS National Map** (US topographic):
|
||||
- URL: `https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer?SERVICE=WMS&REQUEST=GetCapabilities`
|
||||
- CRS: EPSG:3857
|
||||
- Layers: US Topo
|
||||
|
||||
3. **OpenStreetMap WMS** (reference):
|
||||
- URL: `https://ows.terrestris.de/osm/service?SERVICE=WMS&REQUEST=GetCapabilities`
|
||||
- CRS: EPSG:3857, EPSG:4326
|
||||
- Layers: OSM-WMS
|
||||
|
||||
4. **Slovenian Government** (current built-in):
|
||||
- URL: `https://prostor.zgs.gov.si/geowebcache/service/wms?SERVICE=WMS&REQUEST=GetCapabilities`
|
||||
- CRS: EPSG:3794, EPSG:3857
|
||||
- Layers: DOF_2024, kn_parcele, gozdne_ceste
|
||||
|
||||
### Critical Gotchas
|
||||
|
||||
**WMS Version Differences**:
|
||||
- WMS 1.1.1 uses `<SRS>`, WMS 1.3.0 uses `<CRS>`
|
||||
- EPSG:4326 axis order differs (lon,lat vs lat,lon)
|
||||
- bbox parameter order changes between versions
|
||||
|
||||
**Layer Inheritance**:
|
||||
- Child layers inherit CRS from parent if not specified
|
||||
- Root layer CRS applies to all children unless overridden
|
||||
|
||||
**Group vs Leaf Layers**:
|
||||
- Only layers with `<Name>` can be requested in GetMap
|
||||
- Layers without `<Name>` are groups (organizational only)
|
||||
|
||||
**Namespace Prefixes**:
|
||||
- Layer names may include workspace namespace (e.g., `pregledovalnik:DOF_2024`)
|
||||
- Must be included in GetMap request exactly as in GetCapabilities
|
||||
|
||||
**Tile Grid Alignment**:
|
||||
- WMS uses arbitrary bounding boxes (not aligned tile grids)
|
||||
- Works for dynamic rendering but may have caching issues
|
||||
- WMTS is better for tile caching but requires separate implementation
|
||||
|
||||
### Future Enhancements (Post-MVP)
|
||||
|
||||
**Phase 5: Advanced Features** (not in initial scope):
|
||||
- WMS 1.1.1 support (axis order handling)
|
||||
- WMTS support (better tile caching)
|
||||
- Custom CRS registration (manual proj4 definition input)
|
||||
- Layer groups (hierarchical tree view)
|
||||
- Legend display for overlay layers
|
||||
- GetFeatureInfo support (tap on map to query layer attributes)
|
||||
- Layer metadata viewer (abstract, attribution, keywords)
|
||||
- Batch import/export of WMS server configurations
|
||||
|
||||
**Phase 6: Performance Optimization**:
|
||||
- Background GetCapabilities refresh (update layer list without blocking UI)
|
||||
- Lazy loading of layer metadata (only fetch when user expands server)
|
||||
- Thumbnail preview for layers (GetMap with small bbox)
|
||||
- Server health check (ping before adding)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **WMS 1.3.0 Specification**: https://www.ogc.org/standards/wms
|
||||
- **EPSG Registry**: https://epsg.io/
|
||||
- **Proj4 Definitions**: https://proj4.org/
|
||||
- **flutter_map WMS Docs**: https://docs.fleaflet.dev/layers/tile-layer/wms-tile-layer
|
||||
- **xml package**: https://pub.dev/packages/xml
|
||||
- **Slovenian WMS**: https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities&SERVICE=WMS
|
||||
- **NASA GIBS**: https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers
|
||||
- **OGC WMS Best Practices**: https://www.ogc.org/standards/wms
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Foundation
|
||||
- [ ] Add `xml: ^7.0.0` to pubspec.yaml
|
||||
- [ ] Create `lib/models/wms_server.dart` with JSON serialization
|
||||
- [ ] Create `lib/services/wms_capabilities_parser.dart` with XML parsing
|
||||
- [ ] Create `lib/utils/crs_factory.dart` with CRS registry
|
||||
- [ ] Create `lib/services/wms_server_repository.dart` with SharedPreferences storage
|
||||
- [ ] Write unit tests for capabilities parser
|
||||
|
||||
### Phase 2: UI
|
||||
- [ ] Create `lib/screens/wms_server_management_screen.dart`
|
||||
- [ ] Add "Manage WMS Servers" button to Map Management screen
|
||||
- [ ] Implement "Add WMS Server" dialog with URL input
|
||||
- [ ] Implement layer picker with checkbox selection
|
||||
- [ ] Add custom WMS section to layer selector in `map_tab.dart`
|
||||
- [ ] Add "Manage WMS Servers" button to layer selector
|
||||
|
||||
### Phase 3: Integration
|
||||
- [ ] Add `wmsServerId`, `crsCode`, `isBuiltIn` fields to `MapLayer`
|
||||
- [ ] Add `MapLayer.fromWmsServer()` factory method
|
||||
- [ ] Update `MapProvider` to load custom WMS servers on init
|
||||
- [ ] Update layer switching logic to handle CRS changes
|
||||
- [ ] Update zoom clamping logic for custom layers
|
||||
- [ ] Add overlay management for custom WMS layers
|
||||
- [ ] Verify tile caching works with custom WMS
|
||||
|
||||
### Phase 4: Polish
|
||||
- [ ] Add localized strings to all `app_*.arb` files (en, hr, sl, de, es, fr, it)
|
||||
- [ ] Add error handling with user-friendly messages
|
||||
- [ ] Add loading indicators for GetCapabilities fetch
|
||||
- [ ] Add CRS compatibility warnings
|
||||
- [ ] Update `CLAUDE.md` with WMS documentation
|
||||
- [ ] Test with multiple public WMS servers
|
||||
- [ ] Regression test Slovenian layers
|
||||
- [ ] Performance test with large layer lists
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Last Updated**: 2025-10-28
|
||||
**Status**: Ready for Implementation
|
||||
304
docs/wms_layer_analysis.md
Normal file
304
docs/wms_layer_analysis.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# Slovenian WMS Layers - SAR Application Analysis
|
||||
|
||||
**Total Layers Found: 108**
|
||||
|
||||
## HIGH PRIORITY - Critical for SAR Operations
|
||||
|
||||
### Aerial Imagery & Base Maps (5 layers)
|
||||
1. **pregledovalnik:DOF_2024** - Digital Orthophoto 2024 (Latest aerial imagery)
|
||||
- **USE CASE**: Primary visual reference, current terrain conditions
|
||||
- **ALREADY IMPLEMENTED** in the app
|
||||
|
||||
2. **pregledovalnik:DOF25** - Digital Orthophoto 25cm resolution
|
||||
- **USE CASE**: High-resolution aerial imagery for detailed terrain analysis
|
||||
|
||||
3. **pregledovalnik:DOF_IR** - Infrared Orthophoto
|
||||
- **USE CASE**: Thermal/infrared imagery for detecting heat signatures, useful for night searches
|
||||
|
||||
4. **pregledovalnik:DTK25** - Topographic Map 1:25,000
|
||||
- **USE CASE**: Traditional topographic reference with contours, trails, landmarks
|
||||
|
||||
5. **pregledovalnik:dof025_2022_2024** - Combined orthophoto 2022-2024
|
||||
- **USE CASE**: Multi-year aerial imagery comparison
|
||||
|
||||
### Administrative Boundaries (6 layers)
|
||||
6. **pregledovalnik:NEP_RPE_OBCINE** - Municipalities (občine)
|
||||
- **USE CASE**: Jurisdiction boundaries for coordinating with local authorities
|
||||
- **RECOMMENDED FOR OVERLAY**
|
||||
|
||||
7. **pregledovalnik:NEP_RPE_NASELJA** - Settlements
|
||||
- **USE CASE**: Identify populated areas, evacuation points, staging areas
|
||||
- **RECOMMENDED FOR OVERLAY**
|
||||
|
||||
8. **pregledovalnik:NEP_HISNE_STEVILKE** - House Numbers
|
||||
- **USE CASE**: Precise location identification for emergency response
|
||||
|
||||
9. **pregledovalnik:NEP_RPE_UPRAVNE_ENOTE** - Administrative Units
|
||||
- **USE CASE**: Regional administration boundaries
|
||||
|
||||
10. **pregledovalnik:NEP_RPE_STATISTICNE_REGIJE** - Statistical Regions
|
||||
- **USE CASE**: Broader regional planning
|
||||
|
||||
11. **pregledovalnik:drzavna_meja** - State Border
|
||||
- **USE CASE**: International coordination for cross-border operations
|
||||
|
||||
### Roads & Transportation (4 layers)
|
||||
12. **pregledovalnik:KGI_LINIJE_CESTE_G** - Roads
|
||||
- **USE CASE**: Primary access routes, evacuation routes, vehicle navigation
|
||||
- **HIGH PRIORITY OVERLAY**
|
||||
|
||||
13. **pregledovalnik:gozdne_ceste** - Forest Roads
|
||||
- **USE CASE**: Access to remote forest areas, critical for SAR vehicles
|
||||
- **HIGH PRIORITY OVERLAY**
|
||||
|
||||
14. **pregledovalnik:LINIJE_ZELEZNICE_G** - Railways
|
||||
- **USE CASE**: Alternative access routes, landmarks, coordination with rail authorities
|
||||
|
||||
15. **pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G** - Mountain/Hiking Trails
|
||||
- **USE CASE**: Common search areas, lost hiker routes, access to remote areas
|
||||
- **HIGH PRIORITY OVERLAY**
|
||||
|
||||
### Fire & Emergency Hazards (5 layers)
|
||||
16. **pregledovalnik:gozdni_pozari** - Forest Fires (historical)
|
||||
- **USE CASE**: Historical fire locations, high-risk areas
|
||||
- **RECOMMENDED FOR OVERLAY**
|
||||
|
||||
17. **pregledovalnik:pozarna_ogrozenost** - Fire Hazard/Risk Areas
|
||||
- **USE CASE**: Identify high fire risk zones, plan safe evacuation routes
|
||||
- **HIGH PRIORITY OVERLAY**
|
||||
|
||||
18. **pregledovalnik:pozarisce_goriski_kras** - Fire Site - Goriški Kras
|
||||
- **USE CASE**: Specific fire hazard area in Karst region
|
||||
|
||||
19. **pregledovalnik:pozarisce_kras** - Fire Site - Kras
|
||||
- **USE CASE**: Karst region fire zones
|
||||
|
||||
20. **pregledovalnik:protipozarne_preseke** - Firebreaks
|
||||
- **USE CASE**: Fire containment lines, safe zones
|
||||
|
||||
### Geographic Names & Navigation (1 layer)
|
||||
21. **pregledovalnik:zemljepisna_imena** - Geographic Names
|
||||
- **USE CASE**: Place names for communication, location identification
|
||||
- **RECOMMENDED FOR OVERLAY**
|
||||
|
||||
---
|
||||
|
||||
## MEDIUM PRIORITY - Useful for Planning & Context
|
||||
|
||||
### Protected Areas & Natural Features (9 layers)
|
||||
22. **pregledovalnik:natura2000** - Natura 2000 Protected Areas
|
||||
- **USE CASE**: Environmental restrictions, sensitive areas
|
||||
|
||||
23. **pregledovalnik:zavarovana_obmocja_poligoni** - Protected Areas (polygons)
|
||||
- **USE CASE**: National parks, nature reserves, access restrictions
|
||||
|
||||
24. **pregledovalnik:zavarovana_obmocja_tocke** - Protected Areas (points)
|
||||
- **USE CASE**: Point-based protected sites
|
||||
|
||||
25. **pregledovalnik:zavarovana_obmocja_conacija** - Protected Areas (zoning)
|
||||
- **USE CASE**: Zoning within protected areas
|
||||
|
||||
26. **pregledovalnik:naravne_vrednote_poligoni** - Natural Heritage (polygons)
|
||||
- **USE CASE**: Notable natural features, landmarks
|
||||
|
||||
27. **pregledovalnik:naravne_vrednote_tocke** - Natural Heritage (points)
|
||||
- **USE CASE**: Specific natural landmarks (caves, waterfalls, etc.)
|
||||
|
||||
28. **pregledovalnik:epo_poligoni** - Single Trees/Monuments (polygons)
|
||||
- **USE CASE**: Notable landmark trees
|
||||
|
||||
29. **pregledovalnik:epo_tocke** - Single Trees/Monuments (points)
|
||||
- **USE CASE**: Point landmarks
|
||||
|
||||
30. **pregledovalnik:gozdni_rezervati** - Forest Reserves
|
||||
- **USE CASE**: Old-growth forests, restricted access areas
|
||||
|
||||
### Cadastral & Land Parcels (2 layers)
|
||||
31. **pregledovalnik:kn_parcele** - Cadastral Parcels
|
||||
- **USE CASE**: Land ownership boundaries, legal jurisdictions
|
||||
|
||||
32. **pregledovalnik:KN_KATASTRSKE_OBCINE** - Cadastral Municipalities
|
||||
- **USE CASE**: Cadastral administrative units
|
||||
|
||||
### Terrain & Elevation (2 layers)
|
||||
33. **pregledovalnik:DMK** - Digital Cartographic Model
|
||||
- **USE CASE**: Contours, terrain features, elevation data
|
||||
|
||||
34. **pregledovalnik:DMR** - Digital Relief Model
|
||||
- **USE CASE**: Shaded relief, terrain visualization
|
||||
|
||||
### Forest Management Areas (10 layers)
|
||||
35. **pregledovalnik:gge** - Forest Management Units (GGE)
|
||||
- **USE CASE**: Forest administrative units, contact local foresters
|
||||
|
||||
36. **pregledovalnik:ggo** - Forest Management Districts (GGO)
|
||||
- **USE CASE**: Larger forest districts
|
||||
|
||||
37. **pregledovalnik:revirji** - Forest Ranger Districts
|
||||
- **USE CASE**: Local ranger contact areas
|
||||
|
||||
38. **pregledovalnik:krajevne_enote** - Local Forest Units
|
||||
- **USE CASE**: Smallest administrative forest units
|
||||
|
||||
39. **pregledovalnik:odseki** - Forest Compartments
|
||||
- **USE CASE**: Forest subdivisions for management
|
||||
|
||||
40. **pregledovalnik:odseki_gozdni** - Forest Compartments (variant)
|
||||
- **USE CASE**: Alternative compartment layer
|
||||
|
||||
41. **pregledovalnik:sestoji** - Forest Stands
|
||||
- **USE CASE**: Specific tree stand types, vegetation density
|
||||
|
||||
42. **pregledovalnik:sestoji_druga_gozdna_zemljisca** - Other Forest Lands
|
||||
- **USE CASE**: Non-productive forest areas
|
||||
|
||||
43. **pregledovalnik:varovalni_gozdovi** - Protection Forests
|
||||
- **USE CASE**: Forests with protective function (avalanche, erosion)
|
||||
|
||||
44. **pregledovalnik:lovisca** - Hunting Grounds
|
||||
- **USE CASE**: Contact with hunting associations for local knowledge
|
||||
|
||||
### Historical Disasters (6 layers)
|
||||
45. **pregledovalnik:vetrolom_2017** - Windthrow 2017
|
||||
- **USE CASE**: Storm damage areas, difficult terrain
|
||||
|
||||
46. **pregledovalnik:vetrolom_2018** - Windthrow 2018
|
||||
- **USE CASE**: Storm damage areas from 2018
|
||||
|
||||
47. **pregledovalnik:zled_2014** - Ice Storm 2014
|
||||
- **USE CASE**: Ice damage areas
|
||||
|
||||
48. **pregledovalnik:zled_drugi_gozdovi_2014** - Ice Storm 2014 (other forests)
|
||||
- **USE CASE**: Ice damage in secondary forests
|
||||
|
||||
49. **pregledovalnik:podlubniki_2015_2019** - Bark Beetle Damage 2015-2019
|
||||
- **USE CASE**: Dead wood areas, fire hazard, difficult terrain
|
||||
|
||||
50. **pregledovalnik:krcitve** - Clearcuts
|
||||
- **USE CASE**: Open areas, recent harvest sites, potential staging areas
|
||||
|
||||
### Agricultural & Land Use (2 layers)
|
||||
51. **pregledovalnik:povrsine_v_zarascanju** - Overgrown Areas
|
||||
- **USE CASE**: Abandoned agricultural land, changing terrain
|
||||
|
||||
52. **pregledovalnik:skupna_kmetijska_politika_2023_2027** - Common Agricultural Policy
|
||||
- **USE CASE**: Agricultural land use planning
|
||||
|
||||
---
|
||||
|
||||
## LOW PRIORITY - Technical/Specialized Layers
|
||||
|
||||
### Forest Function Layers (ON21 series - 45 layers)
|
||||
These are highly specialized forest function layers from the 2021 forest management plan. Each has variants for lines (_l), polygons (_p), and points (_t):
|
||||
|
||||
**Categories:**
|
||||
- **Biotska** (Biodiversity): on21_fun_biotska_l/p/t
|
||||
- **Druge gozdne dobrine** (Other forest goods): on21_fun_druge_gozdne_dobrine_l/p/t
|
||||
- **Estetska** (Aesthetic): on21_fun_estetska_l/p/t
|
||||
- **Hidroloska** (Hydrological): on21_fun_hidroloska_l/p/t
|
||||
- **Higiensko-zdravstvena** (Health/hygiene): on21_fun_higiensko_zdravstvena_p
|
||||
- **Klimatska** (Climate): on21_fun_klimatska_l/p
|
||||
- **Kulturna** (Cultural): on21_fun_kulturna_l/p/t
|
||||
- **Lesnoproizvodna** (Timber production): on21_fun_lesnoproizvodna_p
|
||||
- **Lovnogospodarska** (Hunting management): on21_fun_lovnogospodarska_p/t
|
||||
- **Obrambna** (Defense): on21_fun_obrambna_p/t
|
||||
- **Poucna** (Educational): on21_fun_poucna_l/p/t
|
||||
- **Raziskovalna** (Research): on21_fun_raziskovalna_p/t
|
||||
- **Rekreacijska** (Recreation): on21_fun_rekreacijska_l/p/t
|
||||
- **Skupaj** (Combined): on21_fun_skupaj_l/p/t
|
||||
- **Turisticna** (Tourism): on21_fun_turisticna_l/p/t
|
||||
- **Varovalna** (Protection): on21_fun_varovalna_p
|
||||
- **Varovanja naravnih vrednot** (Natural heritage protection): on21_fun_varovanja_naravnih_vrednot_l/p/t
|
||||
- **Zascitna** (Conservation): on21_fun_zascitna_l/p
|
||||
|
||||
**USE CASE**: Very specialized forest planning data. May be useful for:
|
||||
- **Rekreacijska**: Popular recreation areas (lost hikers)
|
||||
- **Turisticna**: Tourist areas (search priority)
|
||||
- **Varovalna**: Avalanche/erosion protection forests (hazard awareness)
|
||||
|
||||
### Miscellaneous Technical (6 layers)
|
||||
- **pregledovalnik:conacija_gp** - Zonation (technical)
|
||||
- **pregledovalnik:evrd** - Single-tree selection forests
|
||||
- **pregledovalnik:koridorji** - Corridors (ecological)
|
||||
- **pregledovalnik:luo** - Forest landscape units
|
||||
- **pregledovalnik:pobude_gge** - GGE initiatives
|
||||
- **pregledovalnik:provenience** - Seed provenance areas
|
||||
- **pregledovalnik:uvhvvr** - High conservation value forests
|
||||
- **pregledovalnik:gozdni_sklad_ekocelice** - Forest fund eco-cells
|
||||
- **pregledovalnik:gozdni_sklad_habitatna_drevesa** - Habitat trees
|
||||
|
||||
### Layer Groups (2 layers)
|
||||
- **pregledovalnik:ttn_group** - Group layer (container)
|
||||
- **pregledovalnik:zemljevid_group** - Map group layer (container)
|
||||
|
||||
---
|
||||
|
||||
## RECOMMENDED IMPLEMENTATION PLAN
|
||||
|
||||
### Phase 1: Critical Overlays (Immediate)
|
||||
Add these layers as toggleable overlays in the Map Options screen:
|
||||
|
||||
1. **Forest Roads** (`gozdne_ceste`) - PNG, transparent
|
||||
- Critical for vehicle access in remote areas
|
||||
|
||||
2. **Hiking/Mountain Trails** (`KGI_LINIJE_PLANINSKE_POTI_G`) - PNG, transparent
|
||||
- Common search areas for lost hikers
|
||||
|
||||
3. **Fire Hazard Zones** (`pozarna_ogrozenost`) - PNG, transparent
|
||||
- Safety planning, risk assessment
|
||||
|
||||
4. **Settlements** (`NEP_RPE_NASELJA`) - PNG, transparent
|
||||
- Populated areas, staging areas
|
||||
|
||||
5. **Municipalities** (`NEP_RPE_OBCINE`) - PNG, transparent
|
||||
- Administrative boundaries
|
||||
|
||||
### Phase 2: Additional Useful Layers
|
||||
6. **Geographic Names** (`zemljepisna_imena`)
|
||||
7. **Forest Fires Historical** (`gozdni_pozari`)
|
||||
8. **Protected Areas** (`zavarovana_obmocja_poligoni`)
|
||||
9. **Topographic Map** (`DTK25`) - Alternative base layer
|
||||
10. **Infrared Imagery** (`DOF_IR`) - Alternative base layer
|
||||
|
||||
### Phase 3: Specialized Layers (On Demand)
|
||||
11. **Windthrow/Disaster Areas** (for post-disaster operations)
|
||||
12. **Recreation/Tourism Areas** (for prioritizing search areas)
|
||||
13. **Protection Forests** (avalanche/erosion hazard awareness)
|
||||
|
||||
---
|
||||
|
||||
## TECHNICAL NOTES
|
||||
|
||||
### CRS Compatibility
|
||||
- All layers from `prostor.zgs.gov.si` support **EPSG:3794** (Slovenian National Grid)
|
||||
- The app already has the correct CRS implementation in `lib/utils/slovenian_crs.dart`
|
||||
|
||||
### Recommended Formats
|
||||
- **Base Layers**: JPEG (better compression for imagery)
|
||||
- **Overlays**: PNG with transparency=true (for stacking)
|
||||
|
||||
### Caching Strategy
|
||||
- All layers should use the existing FMTC caching infrastructure
|
||||
- 30-day validity is appropriate for most layers
|
||||
- Consider longer validity for static layers (administrative boundaries)
|
||||
|
||||
### Performance Considerations
|
||||
- Limit active overlays to 3-4 simultaneously to avoid performance issues
|
||||
- Use appropriate zoom level restrictions (some layers only useful at close zoom)
|
||||
- Consider pre-downloading critical layers for offline SAR operations
|
||||
|
||||
---
|
||||
|
||||
## LAYER NAMING CONVENTIONS
|
||||
|
||||
**Slovenian Terms Reference:**
|
||||
- **DOF** = Digitalni Ortofoto (Digital Orthophoto)
|
||||
- **DTK** = Državna Topografska Karta (State Topographic Map)
|
||||
- **DMK** = Digitalni Kartografski Model (Digital Cartographic Model)
|
||||
- **DMR** = Digitalni Model Reliefa (Digital Relief Model)
|
||||
- **NEP** = Nacionalni Evidenčni Portal (National Registry Portal)
|
||||
- **RPE** = Register Prostorskih Enot (Spatial Units Register)
|
||||
- **GGE** = Gozdnogospodarska Enota (Forest Management Unit)
|
||||
- **GGO** = Gozdnogospodarska Območje (Forest Management District)
|
||||
- **ON21** = Območni Načrt 2021 (Regional Plan 2021)
|
||||
|
||||
Binary file not shown.
BIN
ios/Runner.ipa
BIN
ios/Runner.ipa
Binary file not shown.
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -511,7 +511,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -530,7 +530,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -547,7 +547,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -679,7 +679,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -702,7 +702,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 45;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>45</string>
|
||||
<string>46</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000217">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000198">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.249301">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.341425">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="122.764243">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="125.982137">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="302.318367">
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="848.446582">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -2712,6 +2712,17 @@
|
||||
"showForestRoads": "Waldwege anzeigen",
|
||||
"wmsOverlays": "WMS Überlagerungen",
|
||||
|
||||
"hikingTrails": "Wanderwege",
|
||||
"mainRoads": "Hauptstraßen",
|
||||
"houseNumbers": "Hausnummern",
|
||||
"fireHazardZones": "Brandgefährdungszonen",
|
||||
"historicalFires": "Historische Brände",
|
||||
"firebreaks": "Brandschneisen",
|
||||
"krasFireZones": "Kras-Brandzonen",
|
||||
"placeNames": "Ortsnamen",
|
||||
"municipalityBorders": "Gemeindegrenzen",
|
||||
"topographicMap": "Topographische Karte 1:25000",
|
||||
|
||||
"recentMessages": "Aktuelle Nachrichten",
|
||||
|
||||
"addChannel": "Kanal hinzufügen",
|
||||
|
||||
@@ -3343,6 +3343,56 @@
|
||||
"description": "Section header for WMS overlay layers in layer selector"
|
||||
},
|
||||
|
||||
"hikingTrails": "Hiking Trails",
|
||||
"@hikingTrails": {
|
||||
"description": "Label for hiking/mountain trails WMS overlay layer"
|
||||
},
|
||||
|
||||
"mainRoads": "Main Roads",
|
||||
"@mainRoads": {
|
||||
"description": "Label for main roads WMS overlay layer"
|
||||
},
|
||||
|
||||
"houseNumbers": "House Numbers",
|
||||
"@houseNumbers": {
|
||||
"description": "Label for house numbers WMS overlay layer"
|
||||
},
|
||||
|
||||
"fireHazardZones": "Fire Hazard Zones",
|
||||
"@fireHazardZones": {
|
||||
"description": "Label for fire hazard risk zones WMS overlay layer"
|
||||
},
|
||||
|
||||
"historicalFires": "Historical Fires",
|
||||
"@historicalFires": {
|
||||
"description": "Label for historical forest fires WMS overlay layer"
|
||||
},
|
||||
|
||||
"firebreaks": "Firebreaks",
|
||||
"@firebreaks": {
|
||||
"description": "Label for firebreaks WMS overlay layer"
|
||||
},
|
||||
|
||||
"krasFireZones": "Kras Fire Zones",
|
||||
"@krasFireZones": {
|
||||
"description": "Label for Kras fire zones WMS overlay layer"
|
||||
},
|
||||
|
||||
"placeNames": "Place Names",
|
||||
"@placeNames": {
|
||||
"description": "Label for geographic place names WMS overlay layer"
|
||||
},
|
||||
|
||||
"municipalityBorders": "Municipality Borders",
|
||||
"@municipalityBorders": {
|
||||
"description": "Label for municipality borders WMS overlay layer"
|
||||
},
|
||||
|
||||
"topographicMap": "Topographic Map 1:25000",
|
||||
"@topographicMap": {
|
||||
"description": "Label for DTK25 topographic base map layer"
|
||||
},
|
||||
|
||||
"recentMessages": "Recent Messages",
|
||||
"@recentMessages": {
|
||||
"description": "Header for recent messages overlay on map in fullscreen mode"
|
||||
|
||||
@@ -2707,6 +2707,17 @@
|
||||
"showForestRoads": "Mostrar caminos forestales",
|
||||
"wmsOverlays": "Superposiciones WMS",
|
||||
|
||||
"hikingTrails": "Senderos de Montaña",
|
||||
"mainRoads": "Carreteras Principales",
|
||||
"houseNumbers": "Números de Casa",
|
||||
"fireHazardZones": "Zonas de Riesgo de Incendio",
|
||||
"historicalFires": "Incendios Históricos",
|
||||
"firebreaks": "Cortafuegos",
|
||||
"krasFireZones": "Zonas de Incendio Kras",
|
||||
"placeNames": "Nombres de Lugares",
|
||||
"municipalityBorders": "Límites Municipales",
|
||||
"topographicMap": "Mapa Topográfico 1:25000",
|
||||
|
||||
"recentMessages": "Mensajes Recientes",
|
||||
|
||||
"addChannel": "Agregar Canal",
|
||||
|
||||
@@ -2712,6 +2712,17 @@
|
||||
"showForestRoads": "Afficher les chemins forestiers",
|
||||
"wmsOverlays": "Superpositions WMS",
|
||||
|
||||
"hikingTrails": "Sentiers de Randonnée",
|
||||
"mainRoads": "Routes Principales",
|
||||
"houseNumbers": "Numéros de Maison",
|
||||
"fireHazardZones": "Zones à Risque d'Incendie",
|
||||
"historicalFires": "Incendies Historiques",
|
||||
"firebreaks": "Coupe-feu",
|
||||
"krasFireZones": "Zones d'Incendie Kras",
|
||||
"placeNames": "Noms de Lieux",
|
||||
"municipalityBorders": "Limites Municipales",
|
||||
"topographicMap": "Carte Topographique 1:25000",
|
||||
|
||||
"recentMessages": "Messages Récents",
|
||||
"@recentMessages": {
|
||||
"description": "Header for recent messages overlay on map in fullscreen mode"
|
||||
|
||||
@@ -1010,6 +1010,17 @@
|
||||
"showForestRoads": "Prikaži šumske ceste",
|
||||
"wmsOverlays": "WMS prekrivanja",
|
||||
|
||||
"hikingTrails": "Planinske staze",
|
||||
"mainRoads": "Glavne ceste",
|
||||
"houseNumbers": "Kućni brojevi",
|
||||
"fireHazardZones": "Požarna ugroženost",
|
||||
"historicalFires": "Povijesni požari",
|
||||
"firebreaks": "Protupožarni pojasi",
|
||||
"krasFireZones": "Kraška požarišta",
|
||||
"placeNames": "Zemljopisna imena",
|
||||
"municipalityBorders": "Općinske granice",
|
||||
"topographicMap": "Topografska karta 1:25000",
|
||||
|
||||
"recentMessages": "Nedavne poruke",
|
||||
|
||||
"addChannel": "Dodaj kanal",
|
||||
|
||||
@@ -2712,6 +2712,17 @@
|
||||
"showForestRoads": "Mostra strade forestali",
|
||||
"wmsOverlays": "Sovrapposizioni WMS",
|
||||
|
||||
"hikingTrails": "Sentieri Escursionistici",
|
||||
"mainRoads": "Strade Principali",
|
||||
"houseNumbers": "Numeri Civici",
|
||||
"fireHazardZones": "Zone a Rischio Incendio",
|
||||
"historicalFires": "Incendi Storici",
|
||||
"firebreaks": "Fasce Tagliafuoco",
|
||||
"krasFireZones": "Zone di Incendio Kras",
|
||||
"placeNames": "Nomi di Luoghi",
|
||||
"municipalityBorders": "Confini Comunali",
|
||||
"topographicMap": "Carta Topografica 1:25000",
|
||||
|
||||
"recentMessages": "Messaggi Recenti",
|
||||
"@recentMessages": {
|
||||
"description": "Header for recent messages overlay on map in fullscreen mode"
|
||||
|
||||
@@ -3555,6 +3555,66 @@ abstract class AppLocalizations {
|
||||
/// **'WMS Overlays'**
|
||||
String get wmsOverlays;
|
||||
|
||||
/// Label for hiking/mountain trails WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hiking Trails'**
|
||||
String get hikingTrails;
|
||||
|
||||
/// Label for main roads WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Main Roads'**
|
||||
String get mainRoads;
|
||||
|
||||
/// Label for house numbers WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'House Numbers'**
|
||||
String get houseNumbers;
|
||||
|
||||
/// Label for fire hazard risk zones WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fire Hazard Zones'**
|
||||
String get fireHazardZones;
|
||||
|
||||
/// Label for historical forest fires WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Historical Fires'**
|
||||
String get historicalFires;
|
||||
|
||||
/// Label for firebreaks WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Firebreaks'**
|
||||
String get firebreaks;
|
||||
|
||||
/// Label for Kras fire zones WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Kras Fire Zones'**
|
||||
String get krasFireZones;
|
||||
|
||||
/// Label for geographic place names WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Place Names'**
|
||||
String get placeNames;
|
||||
|
||||
/// Label for municipality borders WMS overlay layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Municipality Borders'**
|
||||
String get municipalityBorders;
|
||||
|
||||
/// Label for DTK25 topographic base map layer
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Topographic Map 1:25000'**
|
||||
String get topographicMap;
|
||||
|
||||
/// Header for recent messages overlay on map in fullscreen mode
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -1989,6 +1989,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'WMS Überlagerungen';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Wanderwege';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Hauptstraßen';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Hausnummern';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Brandgefährdungszonen';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Historische Brände';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Brandschneisen';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Kras-Brandzonen';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Ortsnamen';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Gemeindegrenzen';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Topographische Karte 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Aktuelle Nachrichten';
|
||||
|
||||
|
||||
@@ -1969,6 +1969,36 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'WMS Overlays';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Hiking Trails';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Main Roads';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'House Numbers';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Fire Hazard Zones';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Historical Fires';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Firebreaks';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Kras Fire Zones';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Place Names';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Municipality Borders';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Topographic Map 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Recent Messages';
|
||||
|
||||
|
||||
@@ -1994,6 +1994,36 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'Superposiciones WMS';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Senderos de Montaña';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Carreteras Principales';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Números de Casa';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Zonas de Riesgo de Incendio';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Incendios Históricos';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Cortafuegos';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Zonas de Incendio Kras';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Nombres de Lugares';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Límites Municipales';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Mapa Topográfico 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Mensajes Recientes';
|
||||
|
||||
|
||||
@@ -1998,6 +1998,36 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'Superpositions WMS';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Sentiers de Randonnée';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Routes Principales';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Numéros de Maison';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Zones à Risque d\'Incendie';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Incendies Historiques';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Coupe-feu';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Zones d\'Incendie Kras';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Noms de Lieux';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Limites Municipales';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Carte Topographique 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Messages Récents';
|
||||
|
||||
|
||||
@@ -1979,6 +1979,36 @@ class AppLocalizationsHr extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'WMS prekrivanja';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Planinske staze';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Glavne ceste';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Kućni brojevi';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Požarna ugroženost';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Povijesni požari';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Protupožarni pojasi';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Kraška požarišta';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Zemljopisna imena';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Općinske granice';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Topografska karta 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Nedavne poruke';
|
||||
|
||||
|
||||
@@ -1989,6 +1989,36 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'Sovrapposizioni WMS';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Sentieri Escursionistici';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Strade Principali';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Numeri Civici';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Zone a Rischio Incendio';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Incendi Storici';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Fasce Tagliafuoco';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Zone di Incendio Kras';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Nomi di Luoghi';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Confini Comunali';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Carta Topografica 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Messaggi Recenti';
|
||||
|
||||
|
||||
@@ -1979,6 +1979,36 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get wmsOverlays => 'WMS prekrivanja';
|
||||
|
||||
@override
|
||||
String get hikingTrails => 'Planinske poti';
|
||||
|
||||
@override
|
||||
String get mainRoads => 'Glavne ceste';
|
||||
|
||||
@override
|
||||
String get houseNumbers => 'Hišne številke';
|
||||
|
||||
@override
|
||||
String get fireHazardZones => 'Požarna ogroženost';
|
||||
|
||||
@override
|
||||
String get historicalFires => 'Zgodovinski požari';
|
||||
|
||||
@override
|
||||
String get firebreaks => 'Protipožarne preseke';
|
||||
|
||||
@override
|
||||
String get krasFireZones => 'Kraška požarišča';
|
||||
|
||||
@override
|
||||
String get placeNames => 'Zemljepisna imena';
|
||||
|
||||
@override
|
||||
String get municipalityBorders => 'Občinske meje';
|
||||
|
||||
@override
|
||||
String get topographicMap => 'Topografska karta 1:25000';
|
||||
|
||||
@override
|
||||
String get recentMessages => 'Nedavna sporočila';
|
||||
|
||||
|
||||
@@ -1014,6 +1014,17 @@
|
||||
"showForestRoads": "Prikaži gozdne ceste",
|
||||
"wmsOverlays": "WMS prekrivanja",
|
||||
|
||||
"hikingTrails": "Planinske poti",
|
||||
"mainRoads": "Glavne ceste",
|
||||
"houseNumbers": "Hišne številke",
|
||||
"fireHazardZones": "Požarna ogroženost",
|
||||
"historicalFires": "Zgodovinski požari",
|
||||
"firebreaks": "Protipožarne preseke",
|
||||
"krasFireZones": "Kraška požarišča",
|
||||
"placeNames": "Zemljepisna imena",
|
||||
"municipalityBorders": "Občinske meje",
|
||||
"topographicMap": "Topografska karta 1:25000",
|
||||
|
||||
"recentMessages": "Nedavna sporočila",
|
||||
|
||||
"addChannel": "Dodaj kanal",
|
||||
|
||||
@@ -150,6 +150,25 @@ class MapLayer {
|
||||
);
|
||||
}
|
||||
|
||||
/// Slovenian Topographic Map 1:25000 (DTK25) - WMS Base Layer
|
||||
/// Uses EPSG:3794 coordinate system (GeoWebCache tile matrix zoom 0-15)
|
||||
/// Note: CRS is initialized at runtime in getDTK25()
|
||||
static MapLayer getDTK25(Crs slovenianCrs) {
|
||||
return MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'DTK25 (Slovenija)',
|
||||
urlTemplate: '', // Not used for WMS
|
||||
attribution: '© GURS (Geodetska uprava Republike Slovenije)',
|
||||
maxZoom: 15, // GeoWebCache tile matrix maximum
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?',
|
||||
wmsLayers: const ['pregledovalnik:DTK25'],
|
||||
wmsFormat: 'image/jpeg',
|
||||
wmsTransparent: false,
|
||||
crs: slovenianCrs,
|
||||
);
|
||||
}
|
||||
|
||||
static const List<MapLayer> allLayers = [
|
||||
openStreetMap,
|
||||
openTopoMap,
|
||||
|
||||
@@ -21,6 +21,15 @@ class MapProvider with ChangeNotifier {
|
||||
// WMS overlay toggles
|
||||
bool _showCadastralOverlay = false;
|
||||
bool _showForestRoadsOverlay = false;
|
||||
bool _showHikingTrailsOverlay = false;
|
||||
bool _showMainRoadsOverlay = false;
|
||||
bool _showHouseNumbersOverlay = false;
|
||||
bool _showFireHazardZonesOverlay = false;
|
||||
bool _showHistoricalFiresOverlay = false;
|
||||
bool _showFirebreaksOverlay = false;
|
||||
bool _showKrasFireZonesOverlay = false;
|
||||
bool _showPlaceNamesOverlay = false;
|
||||
bool _showMunicipalityBordersOverlay = false;
|
||||
|
||||
// Contact trail toggles
|
||||
bool _showAllContactTrails = false;
|
||||
@@ -46,6 +55,15 @@ class MapProvider with ChangeNotifier {
|
||||
// WMS overlay getters
|
||||
bool get showCadastralOverlay => _showCadastralOverlay;
|
||||
bool get showForestRoadsOverlay => _showForestRoadsOverlay;
|
||||
bool get showHikingTrailsOverlay => _showHikingTrailsOverlay;
|
||||
bool get showMainRoadsOverlay => _showMainRoadsOverlay;
|
||||
bool get showHouseNumbersOverlay => _showHouseNumbersOverlay;
|
||||
bool get showFireHazardZonesOverlay => _showFireHazardZonesOverlay;
|
||||
bool get showHistoricalFiresOverlay => _showHistoricalFiresOverlay;
|
||||
bool get showFirebreaksOverlay => _showFirebreaksOverlay;
|
||||
bool get showKrasFireZonesOverlay => _showKrasFireZonesOverlay;
|
||||
bool get showPlaceNamesOverlay => _showPlaceNamesOverlay;
|
||||
bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay;
|
||||
|
||||
// Contact trail getters
|
||||
bool get showAllContactTrails => _showAllContactTrails;
|
||||
@@ -252,11 +270,83 @@ class MapProvider with ChangeNotifier {
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle hiking trails overlay
|
||||
Future<void> toggleHikingTrailsOverlay() async {
|
||||
_showHikingTrailsOverlay = !_showHikingTrailsOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle main roads overlay
|
||||
Future<void> toggleMainRoadsOverlay() async {
|
||||
_showMainRoadsOverlay = !_showMainRoadsOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle house numbers overlay
|
||||
Future<void> toggleHouseNumbersOverlay() async {
|
||||
_showHouseNumbersOverlay = !_showHouseNumbersOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle fire hazard zones overlay
|
||||
Future<void> toggleFireHazardZonesOverlay() async {
|
||||
_showFireHazardZonesOverlay = !_showFireHazardZonesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle historical fires overlay
|
||||
Future<void> toggleHistoricalFiresOverlay() async {
|
||||
_showHistoricalFiresOverlay = !_showHistoricalFiresOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle firebreaks overlay
|
||||
Future<void> toggleFirebreaksOverlay() async {
|
||||
_showFirebreaksOverlay = !_showFirebreaksOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle Kras fire zones overlay
|
||||
Future<void> toggleKrasFireZonesOverlay() async {
|
||||
_showKrasFireZonesOverlay = !_showKrasFireZonesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle place names overlay
|
||||
Future<void> togglePlaceNamesOverlay() async {
|
||||
_showPlaceNamesOverlay = !_showPlaceNamesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle municipality borders overlay
|
||||
Future<void> toggleMunicipalityBordersOverlay() async {
|
||||
_showMunicipalityBordersOverlay = !_showMunicipalityBordersOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Load overlay state from SharedPreferences
|
||||
Future<void> loadOverlayState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_showCadastralOverlay = prefs.getBool('map_show_cadastral_overlay') ?? false;
|
||||
_showForestRoadsOverlay = prefs.getBool('map_show_forest_roads_overlay') ?? false;
|
||||
_showHikingTrailsOverlay = prefs.getBool('map_show_hiking_trails_overlay') ?? false;
|
||||
_showMainRoadsOverlay = prefs.getBool('map_show_main_roads_overlay') ?? false;
|
||||
_showHouseNumbersOverlay = prefs.getBool('map_show_house_numbers_overlay') ?? false;
|
||||
_showFireHazardZonesOverlay = prefs.getBool('map_show_fire_hazard_zones_overlay') ?? false;
|
||||
_showHistoricalFiresOverlay = prefs.getBool('map_show_historical_fires_overlay') ?? false;
|
||||
_showFirebreaksOverlay = prefs.getBool('map_show_firebreaks_overlay') ?? false;
|
||||
_showKrasFireZonesOverlay = prefs.getBool('map_show_kras_fire_zones_overlay') ?? false;
|
||||
_showPlaceNamesOverlay = prefs.getBool('map_show_place_names_overlay') ?? false;
|
||||
_showMunicipalityBordersOverlay = prefs.getBool('map_show_municipality_borders_overlay') ?? false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -265,6 +355,15 @@ class MapProvider with ChangeNotifier {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('map_show_cadastral_overlay', _showCadastralOverlay);
|
||||
await prefs.setBool('map_show_forest_roads_overlay', _showForestRoadsOverlay);
|
||||
await prefs.setBool('map_show_hiking_trails_overlay', _showHikingTrailsOverlay);
|
||||
await prefs.setBool('map_show_main_roads_overlay', _showMainRoadsOverlay);
|
||||
await prefs.setBool('map_show_house_numbers_overlay', _showHouseNumbersOverlay);
|
||||
await prefs.setBool('map_show_fire_hazard_zones_overlay', _showFireHazardZonesOverlay);
|
||||
await prefs.setBool('map_show_historical_fires_overlay', _showHistoricalFiresOverlay);
|
||||
await prefs.setBool('map_show_firebreaks_overlay', _showFirebreaksOverlay);
|
||||
await prefs.setBool('map_show_kras_fire_zones_overlay', _showKrasFireZonesOverlay);
|
||||
await prefs.setBool('map_show_place_names_overlay', _showPlaceNamesOverlay);
|
||||
await prefs.setBool('map_show_municipality_borders_overlay', _showMunicipalityBordersOverlay);
|
||||
}
|
||||
|
||||
/// Toggle all contact trails on/off
|
||||
|
||||
@@ -30,7 +30,6 @@ import '../services/map_marker_service.dart';
|
||||
import '../services/mbtiles_service.dart';
|
||||
import '../services/trail_color_service.dart';
|
||||
import '../widgets/map_debug_info.dart';
|
||||
import '../widgets/map/map_legend.dart';
|
||||
import '../widgets/map/compass_widget.dart';
|
||||
import '../widgets/map/detailed_compass_dialog.dart';
|
||||
import '../widgets/map/drawing_layer.dart';
|
||||
@@ -68,7 +67,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
MapLayer _currentLayer = MapLayer.openStreetMap;
|
||||
double? _compassHeading; // Compass sensor heading
|
||||
bool _rotateMarkerWithHeading = false; // Toggle for rotation
|
||||
bool _showLegend = false;
|
||||
bool _showMapDebugInfo = false; // Toggle for debug info
|
||||
bool _isFullscreen = false; // Toggle for fullscreen mode
|
||||
double _gpsUpdateDistance = 3.0; // meters
|
||||
@@ -82,6 +80,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
|
||||
// WMS layers (Slovenian)
|
||||
late final MapLayer _slovenianAerialLayer;
|
||||
late final MapLayer _dtk25Layer;
|
||||
|
||||
// Vector tile theme
|
||||
vtr.Theme? _vectorTheme;
|
||||
@@ -118,8 +117,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize Slovenian aerial layer with CRS
|
||||
// Initialize Slovenian WMS layers with CRS
|
||||
_slovenianAerialLayer = MapLayer.getSlovenianAerial2024(slovenianCrs);
|
||||
_dtk25Layer = MapLayer.getDTK25(slovenianCrs);
|
||||
_loadSettings();
|
||||
_loadMbtilesLayers();
|
||||
_initializeTileCache();
|
||||
@@ -260,7 +260,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
|
||||
/// Get all available layers (default + WMS + MBTiles)
|
||||
List<MapLayer> get _allLayers => [...MapLayer.allLayers, _slovenianAerialLayer, ..._mbtilesLayers];
|
||||
List<MapLayer> get _allLayers => [...MapLayer.allLayers, _slovenianAerialLayer, _dtk25Layer, ..._mbtilesLayers];
|
||||
|
||||
Future<void> _loadSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -275,7 +275,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
final lastLayerName = prefs.getString('map_last_layer_name');
|
||||
|
||||
setState(() {
|
||||
_showLegend = prefs.getBool('map_show_legend') ?? false;
|
||||
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
||||
_showMapDebugInfo = prefs.getBool('map_show_debug_info') ?? false;
|
||||
_isFullscreen = prefs.getBool('map_fullscreen') ?? false;
|
||||
@@ -326,7 +325,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
|
||||
Future<void> _saveSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('map_show_legend', _showLegend);
|
||||
await prefs.setBool('map_rotate_with_heading', _rotateMarkerWithHeading);
|
||||
await prefs.setBool('map_show_debug_info', _showMapDebugInfo);
|
||||
await prefs.setBool('map_fullscreen', _isFullscreen);
|
||||
@@ -464,18 +462,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset map rotation to north (0 degrees)
|
||||
void _resetMapRotation() {
|
||||
if (!_isMapReady) return;
|
||||
try {
|
||||
final camera = _mapController.camera;
|
||||
_mapController.moveAndRotate(camera.center, camera.zoom, 0);
|
||||
} catch (e) {
|
||||
// Silently fail if map controller not ready
|
||||
debugPrint('Failed to reset map rotation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
LatLng _calculateCenter(List<Contact> contacts, List<SarMarker> sarMarkers) {
|
||||
return _markerService.calculateCenter(
|
||||
contacts: contacts,
|
||||
@@ -592,9 +578,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
// Slovenian WMS base layer (only for Slovenian/Croatian regions)
|
||||
// Slovenian WMS base layers (only for Slovenian/Croatian regions)
|
||||
if (AppLocalizations.of(context)!.localeName == 'sl' ||
|
||||
AppLocalizations.of(context)!.localeName == 'hr')
|
||||
AppLocalizations.of(context)!.localeName == 'hr') ...[
|
||||
ListTile(
|
||||
leading: _currentLayer == _slovenianAerialLayer
|
||||
? const Icon(Icons.check_circle, color: Colors.green)
|
||||
@@ -617,6 +603,29 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: _currentLayer == _dtk25Layer
|
||||
? const Icon(Icons.check_circle, color: Colors.green)
|
||||
: const Icon(Icons.radio_button_unchecked),
|
||||
title: Text(AppLocalizations.of(context)!.topographicMap),
|
||||
subtitle: Text(_dtk25Layer.attribution),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_currentLayer = _dtk25Layer;
|
||||
// Clamp zoom level if current zoom exceeds new layer's max
|
||||
// For WMS layers, use a middle zoom (11) instead of max zoom to avoid extreme close-up
|
||||
if (_isMapReady && _mapController.camera.zoom > _dtk25Layer.maxZoom) {
|
||||
_mapController.move(
|
||||
_mapController.camera.center,
|
||||
11.0, // Middle zoom for WMS
|
||||
);
|
||||
}
|
||||
});
|
||||
_saveSettings();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
// Offline MBTiles layers section
|
||||
if (_mbtilesLayers.isNotEmpty) ...[
|
||||
const Divider(),
|
||||
@@ -699,6 +708,87 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
mapProvider.toggleForestRoadsOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.hiking, color: Colors.brown),
|
||||
title: Text(AppLocalizations.of(context)!.hikingTrails),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showHikingTrailsOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleHikingTrailsOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.alt_route, color: Colors.grey),
|
||||
title: Text(AppLocalizations.of(context)!.mainRoads),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showMainRoadsOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleMainRoadsOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.numbers, color: Colors.purple),
|
||||
title: Text(AppLocalizations.of(context)!.houseNumbers),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showHouseNumbersOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleHouseNumbersOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.warning_amber, color: Colors.orange),
|
||||
title: Text(AppLocalizations.of(context)!.fireHazardZones),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showFireHazardZonesOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleFireHazardZonesOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.local_fire_department, color: Colors.red),
|
||||
title: Text(AppLocalizations.of(context)!.historicalFires),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showHistoricalFiresOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleHistoricalFiresOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.forest, color: Colors.teal),
|
||||
title: Text(AppLocalizations.of(context)!.firebreaks),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showFirebreaksOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleFirebreaksOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.warning, color: Colors.deepOrange),
|
||||
title: Text(AppLocalizations.of(context)!.krasFireZones),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showKrasFireZonesOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleKrasFireZonesOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.place, color: Colors.indigo),
|
||||
title: Text(AppLocalizations.of(context)!.placeNames),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showPlaceNamesOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.togglePlaceNamesOverlay();
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Icons.border_outer, color: Colors.cyan),
|
||||
title: Text(AppLocalizations.of(context)!.municipalityBorders),
|
||||
subtitle: const Text('© GURS'),
|
||||
value: mapProvider.showMunicipalityBordersOverlay,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleMunicipalityBordersOverlay();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -755,54 +845,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
// Legend toggle
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.info_outline),
|
||||
title: Text(AppLocalizations.of(context)!.showLegend),
|
||||
subtitle: Text(AppLocalizations.of(context)!.displayMarkerTypeCounts),
|
||||
value: _showLegend,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_showLegend = value;
|
||||
});
|
||||
setModalState(() {});
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
// Compass rotation toggle
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.explore),
|
||||
title: Text(AppLocalizations.of(context)!.rotateMapWithHeading),
|
||||
subtitle: Text(AppLocalizations.of(context)!.mapFollowsDirection),
|
||||
value: _rotateMarkerWithHeading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_rotateMarkerWithHeading = value;
|
||||
// Reset map rotation when disabling (only if map is ready)
|
||||
if (_isMapReady) {
|
||||
try {
|
||||
final camera = _mapController.camera;
|
||||
if (!_rotateMarkerWithHeading) {
|
||||
_mapController.moveAndRotate(camera.center, camera.zoom, 0);
|
||||
} else if (_currentHeading != null) {
|
||||
// Apply current heading rotation when enabling (if heading is valid)
|
||||
_mapController.moveAndRotate(
|
||||
camera.center,
|
||||
camera.zoom,
|
||||
-_currentHeading!,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Map not ready yet, ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
setModalState(() {});
|
||||
_saveSettings();
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
// Map Debug Info toggle
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.developer_mode),
|
||||
@@ -1442,6 +1484,331 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
);
|
||||
},
|
||||
),
|
||||
// Hiking trails overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showHikingTrailsOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Hiking Trails',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:KGI_LINIJE_PLANINSKE_POTI_G'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Hiking trails overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Main roads overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showMainRoadsOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:KGI_LINIJE_CESTE_G'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Main Roads',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:KGI_LINIJE_CESTE_G'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Main roads overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// House numbers overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showHouseNumbersOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:NEP_HISNE_STEVILKE'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'House Numbers',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:NEP_HISNE_STEVILKE'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 House numbers overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Fire hazard zones overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showFireHazardZonesOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:pozarna_ogrozenost'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Fire Hazard Zones',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:pozarna_ogrozenost'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Fire hazard zones overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Historical fires overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showHistoricalFiresOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:gozdni_pozari'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Historical Fires',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:gozdni_pozari'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Historical fires overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Firebreaks overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showFirebreaksOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:protipozarne_preseke'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Firebreaks',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:protipozarne_preseke'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Firebreaks overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Kras fire zones overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showKrasFireZonesOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:pozarisce_kras'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Kras Fire Zones',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:pozarisce_kras'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Kras fire zones overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Place names overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showPlaceNamesOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:zemljepisna_imena'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Place Names',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:zemljepisna_imena'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Place names overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Municipality borders overlay
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
if (!mapProvider.showMunicipalityBordersOverlay || _currentLayer.crs == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return flutter_map.TileLayer(
|
||||
wmsOptions: WMSTileLayerOptions(
|
||||
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
layers: const ['pregledovalnik:NEP_RPE_OBCINE'],
|
||||
styles: const ['obcine'],
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
tileProvider: _tileCache.getTileProviderForWms(
|
||||
MapLayer(
|
||||
type: MapLayerType.wmsBase,
|
||||
name: 'Municipality Borders',
|
||||
urlTemplate: '',
|
||||
attribution: '© GURS',
|
||||
maxZoom: 19,
|
||||
isWms: true,
|
||||
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||
wmsLayers: const ['pregledovalnik:NEP_RPE_OBCINE'],
|
||||
wmsFormat: 'image/png',
|
||||
crs: slovenianCrs,
|
||||
),
|
||||
),
|
||||
userAgentPackageName: 'com.meshcore.sar',
|
||||
maxZoom: 19,
|
||||
errorTileCallback: (tile, error, stackTrace) {
|
||||
debugPrint('🔴 Municipality borders overlay tile error at ${tile.coordinates}: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// Imported trail layer (rendered at bottom for reference)
|
||||
Consumer<MapProvider>(
|
||||
builder: (context, mapProvider, _) {
|
||||
@@ -1904,19 +2271,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Map legend overlay (hidden in fullscreen mode)
|
||||
if (_showLegend && !_isFullscreen)
|
||||
Positioned(
|
||||
top: 80, // Position below compass
|
||||
left: 16,
|
||||
child: MapLegend(
|
||||
teamMemberCount: contactsWithLocation.length,
|
||||
foundPersonCount: messagesProvider.foundPersonMarkers.length,
|
||||
fireCount: messagesProvider.fireMarkers.length,
|
||||
stagingAreaCount: messagesProvider.stagingAreaMarkers.length,
|
||||
objectCount: messagesProvider.objectMarkers.length,
|
||||
),
|
||||
),
|
||||
// Measurement distance overlay
|
||||
if (drawingProvider.drawingMode == DrawingMode.measure)
|
||||
Positioned(
|
||||
@@ -2017,6 +2371,82 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
return const DrawingToolbar();
|
||||
},
|
||||
),
|
||||
// Hide other buttons when in drawing mode
|
||||
if (!drawingProvider.isDrawing) ...[
|
||||
// Current Location - always center to GPS
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'center_map',
|
||||
onPressed: !_isMapReady ? null : () async {
|
||||
// Get current zoom to retain it
|
||||
final currentZoom = _mapController.camera.zoom;
|
||||
|
||||
// Force update GPS location and jump to it
|
||||
final position = await _locationService.getCurrentPosition();
|
||||
if (position != null && mounted) {
|
||||
setState(() {
|
||||
// Position updated in service
|
||||
});
|
||||
_mapController.move(
|
||||
LatLng(position.latitude, position.longitude),
|
||||
currentZoom,
|
||||
);
|
||||
} else {
|
||||
// Fallback to cached position or default center
|
||||
final currentPosition = _locationService.currentPosition;
|
||||
if (currentPosition != null) {
|
||||
_mapController.move(
|
||||
LatLng(
|
||||
currentPosition.latitude,
|
||||
currentPosition.longitude,
|
||||
),
|
||||
currentZoom,
|
||||
);
|
||||
} else {
|
||||
_mapController.move(center, currentZoom);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Map Rotation Lock - toggle rotate with heading
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'rotation_lock',
|
||||
backgroundColor: _rotateMarkerWithHeading
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
onPressed: !_isMapReady ? null : () {
|
||||
setState(() {
|
||||
_rotateMarkerWithHeading = !_rotateMarkerWithHeading;
|
||||
// Reset map rotation when disabling
|
||||
if (_isMapReady) {
|
||||
try {
|
||||
final camera = _mapController.camera;
|
||||
if (!_rotateMarkerWithHeading) {
|
||||
// Disable: reset to north
|
||||
_mapController.moveAndRotate(camera.center, camera.zoom, 0);
|
||||
} else if (_currentHeading != null) {
|
||||
// Enable: apply current heading rotation
|
||||
_mapController.moveAndRotate(
|
||||
camera.center,
|
||||
camera.zoom,
|
||||
-_currentHeading!,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Failed to toggle rotation lock: $e');
|
||||
}
|
||||
}
|
||||
});
|
||||
_saveSettings();
|
||||
},
|
||||
child: Icon(
|
||||
Icons.screen_lock_rotation,
|
||||
color: _rotateMarkerWithHeading ? Colors.white : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
// In simple mode: show ruler FAB directly
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, _) {
|
||||
@@ -2053,52 +2483,10 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
);
|
||||
},
|
||||
),
|
||||
// Hide other buttons when in drawing mode
|
||||
// Continue with other buttons when not in drawing mode
|
||||
if (!drawingProvider.isDrawing) ...[
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'center_map',
|
||||
onPressed: !_isMapReady ? null : () async {
|
||||
// First tap: reset rotation if not 0
|
||||
// Second tap (or if rotation is 0): jump to current location
|
||||
if (_getMapRotation() != 0) {
|
||||
_resetMapRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current zoom to retain it
|
||||
final currentZoom = _mapController.camera.zoom;
|
||||
|
||||
// Force update GPS location and jump to it
|
||||
final position = await _locationService.getCurrentPosition();
|
||||
if (position != null && mounted) {
|
||||
setState(() {
|
||||
// Position updated in service
|
||||
});
|
||||
_mapController.move(
|
||||
LatLng(position.latitude, position.longitude),
|
||||
currentZoom,
|
||||
);
|
||||
} else {
|
||||
// Fallback to cached position or default center
|
||||
final currentPosition = _locationService.currentPosition;
|
||||
if (currentPosition != null) {
|
||||
_mapController.move(
|
||||
LatLng(
|
||||
currentPosition.latitude,
|
||||
currentPosition.longitude,
|
||||
),
|
||||
currentZoom,
|
||||
);
|
||||
} else {
|
||||
_mapController.move(center, currentZoom);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Trail controls button
|
||||
const TrailControls(),
|
||||
// Trail controls button
|
||||
const TrailControls(),
|
||||
const SizedBox(height: 8),
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'layer_selector',
|
||||
|
||||
Reference in New Issue
Block a user