mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
Add WMS support and measurement functionality
- 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).
This commit is contained in:
@@ -36,7 +36,12 @@
|
|||||||
"Bash(git log:*)",
|
"Bash(git log:*)",
|
||||||
"WebFetch(domain:meshcore-sar.dz0ny.dev)",
|
"WebFetch(domain:meshcore-sar.dz0ny.dev)",
|
||||||
"Bash(flutter pub get:*)",
|
"Bash(flutter pub get:*)",
|
||||||
"Read(//Users/dz0ny/meshcore-sar/**)"
|
"Read(//Users/dz0ny/meshcore-sar/**)",
|
||||||
|
"WebFetch(domain:docs.fleaflet.dev)",
|
||||||
|
"WebFetch(domain:github.com)",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"WebFetch(domain:prostor.zgs.gov.si)",
|
||||||
|
"Bash(curl:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
2
.github/workflows/build-multiplatform.yml
vendored
2
.github/workflows/build-multiplatform.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
|||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
cache: 'gradle'
|
cache: 'gradle'
|
||||||
|
|
||||||
|
|||||||
231
CLAUDE.md
231
CLAUDE.md
@@ -47,9 +47,11 @@ TX (notify): 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
|
|||||||
### Key Dependencies
|
### Key Dependencies
|
||||||
```yaml
|
```yaml
|
||||||
flutter_blue_plus: ^2.0.0 # BLE communication
|
flutter_blue_plus: ^2.0.0 # BLE communication
|
||||||
flutter_map: ^8.2.2 # Mapping
|
flutter_map: ^8.2.2 # Mapping + WMS support
|
||||||
provider: ^6.1.0 # State management
|
provider: ^6.1.0 # State management
|
||||||
geolocator: ^14.0.2 # GPS tracking
|
geolocator: ^14.0.2 # GPS tracking
|
||||||
|
proj4dart: ^2.1.0 # Coordinate transformations (EPSG:3794)
|
||||||
|
flutter_map_tile_caching: ^10.1.1 # Offline tile caching
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -71,6 +73,7 @@ lib/
|
|||||||
│ ├── ble/ # Connection, commands, responses
|
│ ├── ble/ # Connection, commands, responses
|
||||||
│ ├── location_tracking_service.dart # GPS + broadcast
|
│ ├── location_tracking_service.dart # GPS + broadcast
|
||||||
│ ├── map_marker_service.dart # Marker generation
|
│ ├── map_marker_service.dart # Marker generation
|
||||||
|
│ ├── tile_cache_service.dart # Offline tile caching (WMS + standard)
|
||||||
│ └── validation_service.dart # Form validation
|
│ └── validation_service.dart # Form validation
|
||||||
├── providers/ # State management
|
├── providers/ # State management
|
||||||
│ ├── connection_provider.dart # BLE state
|
│ ├── connection_provider.dart # BLE state
|
||||||
@@ -86,7 +89,8 @@ lib/
|
|||||||
│ └── map/ # Map-specific widgets
|
│ └── map/ # Map-specific widgets
|
||||||
└── utils/ # Utilities
|
└── utils/ # Utilities
|
||||||
├── sar_message_parser.dart
|
├── sar_message_parser.dart
|
||||||
└── drawing_message_parser.dart
|
├── drawing_message_parser.dart
|
||||||
|
└── slovenian_crs.dart # EPSG:3794 CRS for WMS
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -571,6 +575,226 @@ org.gradle.jvmargs=-Xmx4096m
|
|||||||
2. **OpenTopoMap** - Max zoom 17, topographic
|
2. **OpenTopoMap** - Max zoom 17, topographic
|
||||||
3. **ESRI World Imagery** - Max zoom 19, satellite
|
3. **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 definition
|
||||||
|
- `lib/services/tile_cache_service.dart` - getTileProviderForWms() method
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```dart
|
||||||
|
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**:
|
||||||
|
1. **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)
|
||||||
|
|
||||||
|
2. **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`):
|
||||||
|
```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**:
|
||||||
|
1. Query WMTS GetCapabilities:
|
||||||
|
```bash
|
||||||
|
curl "https://prostor.zgs.gov.si/geowebcache/service/wmts?REQUEST=GetCapabilities&SERVICE=WMTS"
|
||||||
|
```
|
||||||
|
2. Find `<TileMatrixSet>` for EPSG:3794
|
||||||
|
3. Extract `<TopLeftCorner>` (origin)
|
||||||
|
4. Extract `<ScaleDenominator>` for each `<TileMatrix>` (convert to resolutions)
|
||||||
|
5. Query WMS GetCapabilities for bounds:
|
||||||
|
```bash
|
||||||
|
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities&SERVICE=WMS"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Caching Strategy
|
||||||
|
|
||||||
|
**Implementation** (`lib/services/tile_cache_service.dart`):
|
||||||
|
```dart
|
||||||
|
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
|
||||||
|
return _store.getTileProvider(
|
||||||
|
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
|
||||||
|
cachedValidDuration: const Duration(days: 30),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
1. **Cache First**: Check local cache before network request
|
||||||
|
2. **30-Day Validity**: Tiles expire after 30 days (suitable for aerial imagery that updates infrequently)
|
||||||
|
3. **Automatic Caching**: All viewed tiles automatically saved to ObjectBox database
|
||||||
|
4. **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`):
|
||||||
|
```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**:
|
||||||
|
```dart
|
||||||
|
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, not `NetworkTileProvider()` or custom providers
|
||||||
|
- **Verify**: Check `tile_cache_service.dart:75` is being called
|
||||||
|
|
||||||
|
**Layer Not Appearing**:
|
||||||
|
- **Cause 1**: Layer name mismatch (e.g., `DOF_2024` vs `pregledovalnik: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.crs` in 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
|
||||||
|
|
||||||
|
1. **Find Layer in GetCapabilities**:
|
||||||
|
```bash
|
||||||
|
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities" | grep "<Name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add to MapLayer** (`lib/models/map_layer.dart`):
|
||||||
|
```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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify CRS Support**: Ensure layer supports EPSG:3794 in GetCapabilities
|
||||||
|
|
||||||
|
4. **Test**: Check for 400 errors, verify tiles load correctly
|
||||||
|
|
||||||
|
#### Technical Notes
|
||||||
|
|
||||||
|
**Why Not Use WMTS Instead of WMS?**
|
||||||
|
- `flutter_map` has excellent WMS support via `WMSTileLayerOptions`
|
||||||
|
- 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: slovenianCrs` is 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
|
### Offline Caching
|
||||||
- Backend: `flutter_map_tile_caching` + ObjectBox
|
- Backend: `flutter_map_tile_caching` + ObjectBox
|
||||||
- Behavior: `CacheBehavior.cacheFirst`, 30-day validity
|
- Behavior: `CacheBehavior.cacheFirst`, 30-day validity
|
||||||
@@ -604,6 +828,9 @@ MapProvider.clearNavigation()
|
|||||||
- [MeshCore FAQ](https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md)
|
- [MeshCore FAQ](https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md)
|
||||||
- [Cayenne LPP Specification](https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload)
|
- [Cayenne LPP Specification](https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload)
|
||||||
- [Provider Package](https://pub.dev/packages/provider)
|
- [Provider Package](https://pub.dev/packages/provider)
|
||||||
|
- [EPSG:3794 Reference](https://epsg.io/3794) - Slovenian CRS definition
|
||||||
|
- [Proj4dart Package](https://pub.dev/packages/proj4dart) - Coordinate transformation library
|
||||||
|
- [OGC WMS Specification](https://www.ogc.org/standards/wms) - Web Map Service standard
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
1725
UNIMPLEMENTED_BLE_COMMANDS.md
Normal file
1725
UNIMPLEMENTED_BLE_COMMANDS.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -649,6 +649,44 @@
|
|||||||
"description": "Beschreibung für Rechteckzeichnungsmodus"
|
"description": "Beschreibung für Rechteckzeichnungsmodus"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"measureDistance": "Entfernung messen",
|
||||||
|
"@measureDistance": {
|
||||||
|
"description": "Kartenzeichnungsmodus: Entfernung messen"
|
||||||
|
},
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Zwei Punkte lang drücken zum Messen",
|
||||||
|
"@measureDistanceDesc": {
|
||||||
|
"description": "Beschreibung für Entfernungsmessungsmodus"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clearMeasurement": "Messung löschen",
|
||||||
|
"@clearMeasurement": {
|
||||||
|
"description": "Tooltip zum Löschen der Messung"
|
||||||
|
},
|
||||||
|
|
||||||
|
"distanceLabel": "Entfernung: {distance}",
|
||||||
|
"@distanceLabel": {
|
||||||
|
"description": "Beschriftung mit gemessener Entfernung",
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Langer Druck für zweiten Punkt",
|
||||||
|
"@longPressForSecondPoint": {
|
||||||
|
"description": "Anweisung wenn erster Messpunkt gesetzt ist"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Langer Druck für ersten Punkt",
|
||||||
|
"@longPressToStartMeasurement": {
|
||||||
|
"description": "Anweisung zum Starten der Messung"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Langer Druck für neue Messung",
|
||||||
|
"@longPressToStartNewMeasurement": {
|
||||||
|
"description": "Anweisung zum Neustarten der Messung nach Abschluss"
|
||||||
|
},
|
||||||
|
|
||||||
"shareDrawings": "Zeichnungen teilen",
|
"shareDrawings": "Zeichnungen teilen",
|
||||||
"@shareDrawings": {
|
"@shareDrawings": {
|
||||||
"description": "Aktion zum Teilen von Zeichnungen im Netzwerk"
|
"description": "Aktion zum Teilen von Zeichnungen im Netzwerk"
|
||||||
@@ -2551,5 +2589,11 @@
|
|||||||
"currentVersion": "Aktuell",
|
"currentVersion": "Aktuell",
|
||||||
"latestVersion": "Neueste",
|
"latestVersion": "Neueste",
|
||||||
"downloadUpdate": "Herunterladen",
|
"downloadUpdate": "Herunterladen",
|
||||||
"updateLater": "Später"
|
"updateLater": "Später",
|
||||||
|
|
||||||
|
"cadastralParcels": "Katasterparzellen",
|
||||||
|
"forestRoads": "Waldwege",
|
||||||
|
"showCadastralParcels": "Katasterparzellen anzeigen",
|
||||||
|
"showForestRoads": "Waldwege anzeigen",
|
||||||
|
"wmsOverlays": "WMS Überlagerungen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,44 @@
|
|||||||
"description": "Description for rectangle drawing mode"
|
"description": "Description for rectangle drawing mode"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"measureDistance": "Measure Distance",
|
||||||
|
"@measureDistance": {
|
||||||
|
"description": "Map drawing mode: measure distance"
|
||||||
|
},
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Long press two points to measure",
|
||||||
|
"@measureDistanceDesc": {
|
||||||
|
"description": "Description for distance measurement mode"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clearMeasurement": "Clear Measurement",
|
||||||
|
"@clearMeasurement": {
|
||||||
|
"description": "Tooltip to clear measurement"
|
||||||
|
},
|
||||||
|
|
||||||
|
"distanceLabel": "Distance: {distance}",
|
||||||
|
"@distanceLabel": {
|
||||||
|
"description": "Label showing measured distance",
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Long press for second point",
|
||||||
|
"@longPressForSecondPoint": {
|
||||||
|
"description": "Instruction when first measurement point is set"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Long press to set first point",
|
||||||
|
"@longPressToStartMeasurement": {
|
||||||
|
"description": "Instruction to start measurement"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Long press to start new measurement",
|
||||||
|
"@longPressToStartNewMeasurement": {
|
||||||
|
"description": "Instruction to restart measurement after completion"
|
||||||
|
},
|
||||||
|
|
||||||
"shareDrawings": "Share Drawings",
|
"shareDrawings": "Share Drawings",
|
||||||
"@shareDrawings": {
|
"@shareDrawings": {
|
||||||
"description": "Action to share drawings to network"
|
"description": "Action to share drawings to network"
|
||||||
@@ -3163,5 +3201,30 @@
|
|||||||
"updateLater": "Later",
|
"updateLater": "Later",
|
||||||
"@updateLater": {
|
"@updateLater": {
|
||||||
"description": "Button to dismiss update dialog"
|
"description": "Button to dismiss update dialog"
|
||||||
|
},
|
||||||
|
|
||||||
|
"cadastralParcels": "Cadastral Parcels",
|
||||||
|
"@cadastralParcels": {
|
||||||
|
"description": "Label for cadastral parcels WMS overlay layer"
|
||||||
|
},
|
||||||
|
|
||||||
|
"forestRoads": "Forest Roads",
|
||||||
|
"@forestRoads": {
|
||||||
|
"description": "Label for forest roads WMS overlay layer"
|
||||||
|
},
|
||||||
|
|
||||||
|
"showCadastralParcels": "Show Cadastral Parcels",
|
||||||
|
"@showCadastralParcels": {
|
||||||
|
"description": "Tooltip for cadastral parcels overlay toggle button"
|
||||||
|
},
|
||||||
|
|
||||||
|
"showForestRoads": "Show Forest Roads",
|
||||||
|
"@showForestRoads": {
|
||||||
|
"description": "Tooltip for forest roads overlay toggle button"
|
||||||
|
},
|
||||||
|
|
||||||
|
"wmsOverlays": "WMS Overlays",
|
||||||
|
"@wmsOverlays": {
|
||||||
|
"description": "Section header for WMS overlay layers in layer selector"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,44 @@
|
|||||||
"description": "Descripción del modo de dibujo de rectángulo"
|
"description": "Descripción del modo de dibujo de rectángulo"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"measureDistance": "Medir distancia",
|
||||||
|
"@measureDistance": {
|
||||||
|
"description": "Modo de dibujo del mapa: medir distancia"
|
||||||
|
},
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Presión prolongada en dos puntos para medir",
|
||||||
|
"@measureDistanceDesc": {
|
||||||
|
"description": "Descripción del modo de medición de distancia"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clearMeasurement": "Borrar medición",
|
||||||
|
"@clearMeasurement": {
|
||||||
|
"description": "Tooltip para borrar la medición"
|
||||||
|
},
|
||||||
|
|
||||||
|
"distanceLabel": "Distancia: {distance}",
|
||||||
|
"@distanceLabel": {
|
||||||
|
"description": "Etiqueta que muestra la distancia medida",
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Presión prolongada para el segundo punto",
|
||||||
|
"@longPressForSecondPoint": {
|
||||||
|
"description": "Instrucción cuando se ha establecido el primer punto de medición"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Presión prolongada para establecer el primer punto",
|
||||||
|
"@longPressToStartMeasurement": {
|
||||||
|
"description": "Instrucción para comenzar la medición"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Presión prolongada para nueva medición",
|
||||||
|
"@longPressToStartNewMeasurement": {
|
||||||
|
"description": "Instrucción para reiniciar la medición después de completarla"
|
||||||
|
},
|
||||||
|
|
||||||
"shareDrawings": "Compartir dibujos",
|
"shareDrawings": "Compartir dibujos",
|
||||||
"@shareDrawings": {
|
"@shareDrawings": {
|
||||||
"description": "Acción para compartir dibujos a la red"
|
"description": "Acción para compartir dibujos a la red"
|
||||||
@@ -2546,5 +2584,11 @@
|
|||||||
"currentVersion": "Actual",
|
"currentVersion": "Actual",
|
||||||
"latestVersion": "Última",
|
"latestVersion": "Última",
|
||||||
"downloadUpdate": "Descargar",
|
"downloadUpdate": "Descargar",
|
||||||
"updateLater": "Más Tarde"
|
"updateLater": "Más Tarde",
|
||||||
|
|
||||||
|
"cadastralParcels": "Parcelas Catastrales",
|
||||||
|
"forestRoads": "Caminos Forestales",
|
||||||
|
"showCadastralParcels": "Mostrar parcelas catastrales",
|
||||||
|
"showForestRoads": "Mostrar caminos forestales",
|
||||||
|
"wmsOverlays": "Superposiciones WMS"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,44 @@
|
|||||||
"description": "Description du mode de dessin de rectangle"
|
"description": "Description du mode de dessin de rectangle"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"measureDistance": "Mesurer la distance",
|
||||||
|
"@measureDistance": {
|
||||||
|
"description": "Mode de dessin de carte : mesurer la distance"
|
||||||
|
},
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Appui long sur deux points pour mesurer",
|
||||||
|
"@measureDistanceDesc": {
|
||||||
|
"description": "Description du mode de mesure de distance"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clearMeasurement": "Effacer la mesure",
|
||||||
|
"@clearMeasurement": {
|
||||||
|
"description": "Infobulle pour effacer la mesure"
|
||||||
|
},
|
||||||
|
|
||||||
|
"distanceLabel": "Distance : {distance}",
|
||||||
|
"@distanceLabel": {
|
||||||
|
"description": "Étiquette affichant la distance mesurée",
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Appui long pour le deuxième point",
|
||||||
|
"@longPressForSecondPoint": {
|
||||||
|
"description": "Instruction lorsque le premier point de mesure est défini"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Appui long pour définir le premier point",
|
||||||
|
"@longPressToStartMeasurement": {
|
||||||
|
"description": "Instruction pour commencer la mesure"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Appui long pour nouvelle mesure",
|
||||||
|
"@longPressToStartNewMeasurement": {
|
||||||
|
"description": "Instruction pour redémarrer la mesure après achèvement"
|
||||||
|
},
|
||||||
|
|
||||||
"shareDrawings": "Partager les dessins",
|
"shareDrawings": "Partager les dessins",
|
||||||
"@shareDrawings": {
|
"@shareDrawings": {
|
||||||
"description": "Action pour partager les dessins sur le réseau"
|
"description": "Action pour partager les dessins sur le réseau"
|
||||||
@@ -2551,5 +2589,11 @@
|
|||||||
"currentVersion": "Actuelle",
|
"currentVersion": "Actuelle",
|
||||||
"latestVersion": "Dernière",
|
"latestVersion": "Dernière",
|
||||||
"downloadUpdate": "Télécharger",
|
"downloadUpdate": "Télécharger",
|
||||||
"updateLater": "Plus Tard"
|
"updateLater": "Plus Tard",
|
||||||
|
|
||||||
|
"cadastralParcels": "Parcelles Cadastrales",
|
||||||
|
"forestRoads": "Chemins Forestiers",
|
||||||
|
"showCadastralParcels": "Afficher les parcelles cadastrales",
|
||||||
|
"showForestRoads": "Afficher les chemins forestiers",
|
||||||
|
"wmsOverlays": "Superpositions WMS"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,20 @@
|
|||||||
|
|
||||||
"drawRectangleDesc": "Nacrtaj pravokutno područje na karti",
|
"drawRectangleDesc": "Nacrtaj pravokutno područje na karti",
|
||||||
|
|
||||||
|
"measureDistance": "Izmjeri udaljenost",
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Dugi pritisak na dvije točke za mjerenje",
|
||||||
|
|
||||||
|
"clearMeasurement": "Očisti mjerenje",
|
||||||
|
|
||||||
|
"distanceLabel": "Udaljenost: {distance}",
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Dugi pritisak za drugu točku",
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Dugi pritisak za prvu točku",
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Dugi pritisak za novo mjerenje",
|
||||||
|
|
||||||
"shareDrawings": "Podijeli crteže",
|
"shareDrawings": "Podijeli crteže",
|
||||||
|
|
||||||
"clearAllDrawings": "Očisti sve crteže",
|
"clearAllDrawings": "Očisti sve crteže",
|
||||||
@@ -955,5 +969,11 @@
|
|||||||
"currentVersion": "Trenutna",
|
"currentVersion": "Trenutna",
|
||||||
"latestVersion": "Najnovija",
|
"latestVersion": "Najnovija",
|
||||||
"downloadUpdate": "Preuzmi",
|
"downloadUpdate": "Preuzmi",
|
||||||
"updateLater": "Kasnije"
|
"updateLater": "Kasnije",
|
||||||
|
|
||||||
|
"cadastralParcels": "Katastarske čestice",
|
||||||
|
"forestRoads": "Šumske ceste",
|
||||||
|
"showCadastralParcels": "Prikaži katastarske čestice",
|
||||||
|
"showForestRoads": "Prikaži šumske ceste",
|
||||||
|
"wmsOverlays": "WMS Prekrivanja"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,44 @@
|
|||||||
"description": "Descrizione per la modalità disegno rettangolo"
|
"description": "Descrizione per la modalità disegno rettangolo"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"measureDistance": "Misura Distanza",
|
||||||
|
"@measureDistance": {
|
||||||
|
"description": "Modalità disegno mappa: misura distanza"
|
||||||
|
},
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Premi a lungo su due punti per misurare",
|
||||||
|
"@measureDistanceDesc": {
|
||||||
|
"description": "Descrizione per la modalità misurazione distanza"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clearMeasurement": "Cancella Misurazione",
|
||||||
|
"@clearMeasurement": {
|
||||||
|
"description": "Tooltip per cancellare la misurazione"
|
||||||
|
},
|
||||||
|
|
||||||
|
"distanceLabel": "Distanza: {distance}",
|
||||||
|
"@distanceLabel": {
|
||||||
|
"description": "Etichetta che mostra la distanza misurata",
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {"type": "String"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Premi a lungo per il secondo punto",
|
||||||
|
"@longPressForSecondPoint": {
|
||||||
|
"description": "Istruzione quando è impostato il primo punto di misurazione"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Premi a lungo per impostare il primo punto",
|
||||||
|
"@longPressToStartMeasurement": {
|
||||||
|
"description": "Istruzione per avviare la misurazione"
|
||||||
|
},
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Premi a lungo per nuova misurazione",
|
||||||
|
"@longPressToStartNewMeasurement": {
|
||||||
|
"description": "Istruzione per riavviare la misurazione dopo il completamento"
|
||||||
|
},
|
||||||
|
|
||||||
"shareDrawings": "Condividi Disegni",
|
"shareDrawings": "Condividi Disegni",
|
||||||
"@shareDrawings": {
|
"@shareDrawings": {
|
||||||
"description": "Azione per condividere disegni sulla rete"
|
"description": "Azione per condividere disegni sulla rete"
|
||||||
@@ -2551,5 +2589,11 @@
|
|||||||
"currentVersion": "Attuale",
|
"currentVersion": "Attuale",
|
||||||
"latestVersion": "Ultima",
|
"latestVersion": "Ultima",
|
||||||
"downloadUpdate": "Scarica",
|
"downloadUpdate": "Scarica",
|
||||||
"updateLater": "Più Tardi"
|
"updateLater": "Più Tardi",
|
||||||
|
|
||||||
|
"cadastralParcels": "Particelle Catastali",
|
||||||
|
"forestRoads": "Strade Forestali",
|
||||||
|
"showCadastralParcels": "Mostra particelle catastali",
|
||||||
|
"showForestRoads": "Mostra strade forestali",
|
||||||
|
"wmsOverlays": "Sovrapposizioni WMS"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -791,6 +791,48 @@ abstract class AppLocalizations {
|
|||||||
/// **'Draw a rectangular area on the map'**
|
/// **'Draw a rectangular area on the map'**
|
||||||
String get drawRectangleDesc;
|
String get drawRectangleDesc;
|
||||||
|
|
||||||
|
/// Map drawing mode: measure distance
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Measure Distance'**
|
||||||
|
String get measureDistance;
|
||||||
|
|
||||||
|
/// Description for distance measurement mode
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Long press two points to measure'**
|
||||||
|
String get measureDistanceDesc;
|
||||||
|
|
||||||
|
/// Tooltip to clear measurement
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Clear Measurement'**
|
||||||
|
String get clearMeasurement;
|
||||||
|
|
||||||
|
/// Label showing measured distance
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Distance: {distance}'**
|
||||||
|
String distanceLabel(String distance);
|
||||||
|
|
||||||
|
/// Instruction when first measurement point is set
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Long press for second point'**
|
||||||
|
String get longPressForSecondPoint;
|
||||||
|
|
||||||
|
/// Instruction to start measurement
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Long press to set first point'**
|
||||||
|
String get longPressToStartMeasurement;
|
||||||
|
|
||||||
|
/// Instruction to restart measurement after completion
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Long press to start new measurement'**
|
||||||
|
String get longPressToStartNewMeasurement;
|
||||||
|
|
||||||
/// Action to share drawings to network
|
/// Action to share drawings to network
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -3374,6 +3416,36 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Later'**
|
/// **'Later'**
|
||||||
String get updateLater;
|
String get updateLater;
|
||||||
|
|
||||||
|
/// Label for cadastral parcels WMS overlay layer
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Cadastral Parcels'**
|
||||||
|
String get cadastralParcels;
|
||||||
|
|
||||||
|
/// Label for forest roads WMS overlay layer
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Forest Roads'**
|
||||||
|
String get forestRoads;
|
||||||
|
|
||||||
|
/// Tooltip for cadastral parcels overlay toggle button
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Show Cadastral Parcels'**
|
||||||
|
String get showCadastralParcels;
|
||||||
|
|
||||||
|
/// Tooltip for forest roads overlay toggle button
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Show Forest Roads'**
|
||||||
|
String get showForestRoads;
|
||||||
|
|
||||||
|
/// Section header for WMS overlay layers in layer selector
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'WMS Overlays'**
|
||||||
|
String get wmsOverlays;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -395,6 +395,29 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Rechteckigen Bereich auf der Karte zeichnen';
|
String get drawRectangleDesc => 'Rechteckigen Bereich auf der Karte zeichnen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Entfernung messen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Zwei Punkte lang drücken zum Messen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Messung löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Entfernung: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Langer Druck für zweiten Punkt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement => 'Langer Druck für ersten Punkt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement => 'Langer Druck für neue Messung';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Zeichnungen teilen';
|
String get shareDrawings => 'Zeichnungen teilen';
|
||||||
|
|
||||||
@@ -1885,4 +1908,19 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Später';
|
String get updateLater => 'Später';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Katasterparzellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Waldwege';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Katasterparzellen anzeigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Waldwege anzeigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'WMS Überlagerungen';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,30 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Draw a rectangular area on the map';
|
String get drawRectangleDesc => 'Draw a rectangular area on the map';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Measure Distance';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Long press two points to measure';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Clear Measurement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Distance: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Long press for second point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement => 'Long press to set first point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement =>
|
||||||
|
'Long press to start new measurement';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Share Drawings';
|
String get shareDrawings => 'Share Drawings';
|
||||||
|
|
||||||
@@ -1864,4 +1888,19 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Later';
|
String get updateLater => 'Later';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Cadastral Parcels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Forest Roads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Show Cadastral Parcels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Show Forest Roads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'WMS Overlays';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,33 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Dibujar un área rectangular en el mapa';
|
String get drawRectangleDesc => 'Dibujar un área rectangular en el mapa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Medir distancia';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc =>
|
||||||
|
'Presión prolongada en dos puntos para medir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Borrar medición';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Distancia: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint =>
|
||||||
|
'Presión prolongada para el segundo punto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement =>
|
||||||
|
'Presión prolongada para establecer el primer punto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement =>
|
||||||
|
'Presión prolongada para nueva medición';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Compartir dibujos';
|
String get shareDrawings => 'Compartir dibujos';
|
||||||
|
|
||||||
@@ -1886,4 +1913,19 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Más Tarde';
|
String get updateLater => 'Más Tarde';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Parcelas Catastrales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Caminos Forestales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Mostrar parcelas catastrales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Mostrar caminos forestales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'Superposiciones WMS';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,6 +395,31 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Tracer une zone rectangulaire sur la carte';
|
String get drawRectangleDesc => 'Tracer une zone rectangulaire sur la carte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Mesurer la distance';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Appui long sur deux points pour mesurer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Effacer la mesure';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Distance : $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Appui long pour le deuxième point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement =>
|
||||||
|
'Appui long pour définir le premier point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement =>
|
||||||
|
'Appui long pour nouvelle mesure';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Partager les dessins';
|
String get shareDrawings => 'Partager les dessins';
|
||||||
|
|
||||||
@@ -1892,4 +1917,19 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Plus Tard';
|
String get updateLater => 'Plus Tard';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Parcelles Cadastrales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Chemins Forestiers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Afficher les parcelles cadastrales';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Afficher les chemins forestiers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'Superpositions WMS';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,29 @@ class AppLocalizationsHr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Nacrtaj pravokutno područje na karti';
|
String get drawRectangleDesc => 'Nacrtaj pravokutno područje na karti';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Izmjeri udaljenost';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Dugi pritisak na dvije točke za mjerenje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Očisti mjerenje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Udaljenost: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Dugi pritisak za drugu točku';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement => 'Dugi pritisak za prvu točku';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement => 'Dugi pritisak za novo mjerenje';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Podijeli crteže';
|
String get shareDrawings => 'Podijeli crteže';
|
||||||
|
|
||||||
@@ -1874,4 +1897,19 @@ class AppLocalizationsHr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Kasnije';
|
String get updateLater => 'Kasnije';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Katastarske čestice';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Šumske ceste';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Prikaži katastarske čestice';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Prikaži šumske ceste';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'WMS Prekrivanja';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,31 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Disegna un\'area rettangolare sulla mappa';
|
String get drawRectangleDesc => 'Disegna un\'area rettangolare sulla mappa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Misura Distanza';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Premi a lungo su due punti per misurare';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Cancella Misurazione';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Distanza: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Premi a lungo per il secondo punto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement =>
|
||||||
|
'Premi a lungo per impostare il primo punto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement =>
|
||||||
|
'Premi a lungo per nuova misurazione';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Condividi Disegni';
|
String get shareDrawings => 'Condividi Disegni';
|
||||||
|
|
||||||
@@ -1883,4 +1908,19 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Più Tardi';
|
String get updateLater => 'Più Tardi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Particelle Catastali';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Strade Forestali';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Mostra particelle catastali';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Mostra strade forestali';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'Sovrapposizioni WMS';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,29 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get drawRectangleDesc => 'Nariši pravokotno področje na zemljevidu';
|
String get drawRectangleDesc => 'Nariši pravokotno področje na zemljevidu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistance => 'Meri razdaljo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get measureDistanceDesc => 'Dolg pritisk na dve točki za merjenje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearMeasurement => 'Počisti meritev';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String distanceLabel(String distance) {
|
||||||
|
return 'Razdalja: $distance';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressForSecondPoint => 'Dolg pritisk za drugo točko';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartMeasurement => 'Dolg pritisk za prvo točko';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToStartNewMeasurement => 'Dolg pritisk za novo meritev';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shareDrawings => 'Deli risbe';
|
String get shareDrawings => 'Deli risbe';
|
||||||
|
|
||||||
@@ -1875,4 +1898,19 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get updateLater => 'Kasneje';
|
String get updateLater => 'Kasneje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cadastralParcels => 'Katastrske parcele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get forestRoads => 'Gozdne ceste';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showCadastralParcels => 'Prikaži katastrske parcele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showForestRoads => 'Prikaži gozdne ceste';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wmsOverlays => 'WMS Prekrivanja';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,20 @@
|
|||||||
|
|
||||||
"drawRectangleDesc": "Nariši pravokotno področje na zemljevidu",
|
"drawRectangleDesc": "Nariši pravokotno področje na zemljevidu",
|
||||||
|
|
||||||
|
"measureDistance": "Meri razdaljo",
|
||||||
|
|
||||||
|
"measureDistanceDesc": "Dolg pritisk na dve točki za merjenje",
|
||||||
|
|
||||||
|
"clearMeasurement": "Počisti meritev",
|
||||||
|
|
||||||
|
"distanceLabel": "Razdalja: {distance}",
|
||||||
|
|
||||||
|
"longPressForSecondPoint": "Dolg pritisk za drugo točko",
|
||||||
|
|
||||||
|
"longPressToStartMeasurement": "Dolg pritisk za prvo točko",
|
||||||
|
|
||||||
|
"longPressToStartNewMeasurement": "Dolg pritisk za novo meritev",
|
||||||
|
|
||||||
"shareDrawings": "Deli risbe",
|
"shareDrawings": "Deli risbe",
|
||||||
|
|
||||||
"clearAllDrawings": "Počisti vse risbe",
|
"clearAllDrawings": "Počisti vse risbe",
|
||||||
@@ -955,5 +969,11 @@
|
|||||||
"currentVersion": "Trenutna",
|
"currentVersion": "Trenutna",
|
||||||
"latestVersion": "Najnovejša",
|
"latestVersion": "Najnovejša",
|
||||||
"downloadUpdate": "Prenesi",
|
"downloadUpdate": "Prenesi",
|
||||||
"updateLater": "Kasneje"
|
"updateLater": "Kasneje",
|
||||||
|
|
||||||
|
"cadastralParcels": "Katastrske parcele",
|
||||||
|
"forestRoads": "Gozdne ceste",
|
||||||
|
"showCadastralParcels": "Prikaži katastrske parcele",
|
||||||
|
"showForestRoads": "Prikaži gozdne ceste",
|
||||||
|
"wmsOverlays": "WMS Prekrivanja"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
enum MapLayerType {
|
enum MapLayerType {
|
||||||
@@ -10,6 +11,7 @@ enum MapLayerType {
|
|||||||
googleRoadmap,
|
googleRoadmap,
|
||||||
googleTerrain,
|
googleTerrain,
|
||||||
vectorMbtiles,
|
vectorMbtiles,
|
||||||
|
wmsBase,
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapLayer {
|
class MapLayer {
|
||||||
@@ -26,6 +28,15 @@ class MapLayer {
|
|||||||
final String? sourceName;
|
final String? sourceName;
|
||||||
final bool? isGzipped;
|
final bool? isGzipped;
|
||||||
|
|
||||||
|
// WMS specific properties
|
||||||
|
final bool isWms;
|
||||||
|
final String? wmsBaseUrl;
|
||||||
|
final List<String>? wmsLayers;
|
||||||
|
final String? wmsFormat;
|
||||||
|
final bool? wmsTransparent;
|
||||||
|
final List<String>? wmsStyles;
|
||||||
|
final Crs? crs;
|
||||||
|
|
||||||
const MapLayer({
|
const MapLayer({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.name,
|
required this.name,
|
||||||
@@ -37,6 +48,13 @@ class MapLayer {
|
|||||||
this.styleUrl,
|
this.styleUrl,
|
||||||
this.sourceName,
|
this.sourceName,
|
||||||
this.isGzipped,
|
this.isGzipped,
|
||||||
|
this.isWms = false,
|
||||||
|
this.wmsBaseUrl,
|
||||||
|
this.wmsLayers,
|
||||||
|
this.wmsFormat,
|
||||||
|
this.wmsTransparent,
|
||||||
|
this.wmsStyles,
|
||||||
|
this.crs,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Get localized name for the layer
|
/// Get localized name for the layer
|
||||||
@@ -58,6 +76,9 @@ class MapLayer {
|
|||||||
case MapLayerType.vectorMbtiles:
|
case MapLayerType.vectorMbtiles:
|
||||||
// For vector tiles, use the name from metadata
|
// For vector tiles, use the name from metadata
|
||||||
return name;
|
return name;
|
||||||
|
case MapLayerType.wmsBase:
|
||||||
|
// For WMS layers, use the name (will be localized separately)
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +131,25 @@ class MapLayer {
|
|||||||
maxZoom: 20, // Google Maps maximum
|
maxZoom: 20, // Google Maps maximum
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Slovenian Aerial Imagery 2024 (Ortofoto) - WMS Base Layer
|
||||||
|
/// Uses EPSG:3794 coordinate system (GeoWebCache tile matrix zoom 0-15)
|
||||||
|
/// Note: CRS is initialized at runtime in getSlovenianAerial2024()
|
||||||
|
static MapLayer getSlovenianAerial2024(Crs slovenianCrs) {
|
||||||
|
return MapLayer(
|
||||||
|
type: MapLayerType.wmsBase,
|
||||||
|
name: 'Ortofoto 2024 (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:DOF_2024'],
|
||||||
|
wmsFormat: 'image/jpeg',
|
||||||
|
wmsTransparent: false,
|
||||||
|
crs: slovenianCrs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static const List<MapLayer> allLayers = [
|
static const List<MapLayer> allLayers = [
|
||||||
openStreetMap,
|
openStreetMap,
|
||||||
openTopoMap,
|
openTopoMap,
|
||||||
@@ -117,6 +157,7 @@ class MapLayer {
|
|||||||
googleHybrid,
|
googleHybrid,
|
||||||
googleRoadmap,
|
googleRoadmap,
|
||||||
googleTerrain,
|
googleTerrain,
|
||||||
|
// Note: Slovenian aerial layer is added dynamically via getSlovenianAerial2024()
|
||||||
];
|
];
|
||||||
|
|
||||||
static MapLayer fromType(MapLayerType type) {
|
static MapLayer fromType(MapLayerType type) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import '../models/map_drawing.dart';
|
|||||||
import '../utils/drawing_message_parser.dart';
|
import '../utils/drawing_message_parser.dart';
|
||||||
|
|
||||||
/// Drawing mode state
|
/// Drawing mode state
|
||||||
enum DrawingMode { none, line, rectangle }
|
enum DrawingMode { none, line, rectangle, measure }
|
||||||
|
|
||||||
/// Provider for managing map drawings
|
/// Provider for managing map drawings
|
||||||
class DrawingProvider with ChangeNotifier {
|
class DrawingProvider with ChangeNotifier {
|
||||||
@@ -26,6 +26,11 @@ class DrawingProvider with ChangeNotifier {
|
|||||||
List<LatLng> _currentLinePoints = [];
|
List<LatLng> _currentLinePoints = [];
|
||||||
LatLng? _rectangleStartPoint;
|
LatLng? _rectangleStartPoint;
|
||||||
|
|
||||||
|
// Distance measurement state
|
||||||
|
LatLng? _measurementPoint1;
|
||||||
|
LatLng? _measurementPoint2;
|
||||||
|
double? _measuredDistance; // in meters
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
DrawingMode get drawingMode => _drawingMode;
|
DrawingMode get drawingMode => _drawingMode;
|
||||||
Color get selectedColor => _selectedColor;
|
Color get selectedColor => _selectedColor;
|
||||||
@@ -46,6 +51,9 @@ class DrawingProvider with ChangeNotifier {
|
|||||||
List<LatLng> get currentLinePoints => List.unmodifiable(_currentLinePoints);
|
List<LatLng> get currentLinePoints => List.unmodifiable(_currentLinePoints);
|
||||||
LatLng? get rectangleStartPoint => _rectangleStartPoint;
|
LatLng? get rectangleStartPoint => _rectangleStartPoint;
|
||||||
bool get isDrawing => _drawingMode != DrawingMode.none;
|
bool get isDrawing => _drawingMode != DrawingMode.none;
|
||||||
|
LatLng? get measurementPoint1 => _measurementPoint1;
|
||||||
|
LatLng? get measurementPoint2 => _measurementPoint2;
|
||||||
|
double? get measuredDistance => _measuredDistance;
|
||||||
|
|
||||||
/// Initialize and load saved drawings
|
/// Initialize and load saved drawings
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
@@ -126,8 +134,9 @@ class DrawingProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Update rectangle end point (for preview)
|
/// Update rectangle end point (for preview)
|
||||||
void updateRectangleEndPoint(LatLng endPoint) {
|
void updateRectangleEndPoint(LatLng endPoint) {
|
||||||
if (_drawingMode != DrawingMode.rectangle || _rectangleStartPoint == null)
|
if (_drawingMode != DrawingMode.rectangle || _rectangleStartPoint == null) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create preview rectangle
|
// Create preview rectangle
|
||||||
_currentDrawing = RectangleDrawing(
|
_currentDrawing = RectangleDrawing(
|
||||||
@@ -195,11 +204,47 @@ class DrawingProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set first measurement point
|
||||||
|
void setMeasurementPoint1(LatLng point) {
|
||||||
|
if (_drawingMode != DrawingMode.measure) return;
|
||||||
|
|
||||||
|
_measurementPoint1 = point;
|
||||||
|
_measurementPoint2 = null;
|
||||||
|
_measuredDistance = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set second measurement point and calculate distance
|
||||||
|
void setMeasurementPoint2(LatLng point) {
|
||||||
|
if (_drawingMode != DrawingMode.measure || _measurementPoint1 == null) return;
|
||||||
|
|
||||||
|
_measurementPoint2 = point;
|
||||||
|
_measuredDistance = _calculateDistance(_measurementPoint1!, point);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate distance between two points using Haversine formula
|
||||||
|
double _calculateDistance(LatLng point1, LatLng point2) {
|
||||||
|
const Distance distance = Distance();
|
||||||
|
return distance.as(LengthUnit.Meter, point1, point2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear measurement points
|
||||||
|
void clearMeasurement() {
|
||||||
|
_measurementPoint1 = null;
|
||||||
|
_measurementPoint2 = null;
|
||||||
|
_measuredDistance = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
/// Cancel current drawing in progress
|
/// Cancel current drawing in progress
|
||||||
void _cancelCurrentDrawing() {
|
void _cancelCurrentDrawing() {
|
||||||
_currentLinePoints = [];
|
_currentLinePoints = [];
|
||||||
_rectangleStartPoint = null;
|
_rectangleStartPoint = null;
|
||||||
_currentDrawing = null;
|
_currentDrawing = null;
|
||||||
|
_measurementPoint1 = null;
|
||||||
|
_measurementPoint2 = null;
|
||||||
|
_measuredDistance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear current drawing (public method)
|
/// Clear current drawing (public method)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/location_trail.dart';
|
import '../models/location_trail.dart';
|
||||||
import '../models/map_drawing.dart';
|
import '../models/map_drawing.dart';
|
||||||
|
|
||||||
@@ -16,6 +17,10 @@ class MapProvider with ChangeNotifier {
|
|||||||
bool _isTrailVisible = true;
|
bool _isTrailVisible = true;
|
||||||
final List<LocationTrail> _trailHistory = [];
|
final List<LocationTrail> _trailHistory = [];
|
||||||
|
|
||||||
|
// WMS overlay toggles
|
||||||
|
bool _showCadastralOverlay = false;
|
||||||
|
bool _showForestRoadsOverlay = false;
|
||||||
|
|
||||||
LatLng? get targetLocation => _targetLocation;
|
LatLng? get targetLocation => _targetLocation;
|
||||||
double? get targetZoom => _targetZoom;
|
double? get targetZoom => _targetZoom;
|
||||||
bool get shouldAnimate => _shouldAnimate;
|
bool get shouldAnimate => _shouldAnimate;
|
||||||
@@ -27,6 +32,10 @@ class MapProvider with ChangeNotifier {
|
|||||||
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
||||||
bool get isTrailActive => _currentTrail?.isActive ?? false;
|
bool get isTrailActive => _currentTrail?.isActive ?? false;
|
||||||
|
|
||||||
|
// WMS overlay getters
|
||||||
|
bool get showCadastralOverlay => _showCadastralOverlay;
|
||||||
|
bool get showForestRoadsOverlay => _showForestRoadsOverlay;
|
||||||
|
|
||||||
void navigateToLocation({
|
void navigateToLocation({
|
||||||
required LatLng location,
|
required LatLng location,
|
||||||
double zoom = 15.0,
|
double zoom = 15.0,
|
||||||
@@ -207,4 +216,33 @@ class MapProvider with ChangeNotifier {
|
|||||||
if (_currentTrail == null) return Duration.zero;
|
if (_currentTrail == null) return Duration.zero;
|
||||||
return _currentTrail!.duration;
|
return _currentTrail!.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Toggle cadastral parcels overlay
|
||||||
|
Future<void> toggleCadastralOverlay() async {
|
||||||
|
_showCadastralOverlay = !_showCadastralOverlay;
|
||||||
|
notifyListeners();
|
||||||
|
await _saveOverlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle forest roads overlay
|
||||||
|
Future<void> toggleForestRoadsOverlay() async {
|
||||||
|
_showForestRoadsOverlay = !_showForestRoadsOverlay;
|
||||||
|
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;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save overlay state to SharedPreferences
|
||||||
|
Future<void> _saveOverlayState() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool('map_show_cadastral_overlay', _showCadastralOverlay);
|
||||||
|
await prefs.setBool('map_show_forest_roads_overlay', _showForestRoadsOverlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:vector_map_tiles/vector_map_tiles.dart';
|
import 'package:vector_map_tiles/vector_map_tiles.dart';
|
||||||
import 'package:vector_tile_renderer/vector_tile_renderer.dart' as vtr;
|
import 'package:vector_tile_renderer/vector_tile_renderer.dart' as vtr;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../utils/slovenian_crs.dart';
|
||||||
import '../providers/contacts_provider.dart';
|
import '../providers/contacts_provider.dart';
|
||||||
import '../providers/messages_provider.dart';
|
import '../providers/messages_provider.dart';
|
||||||
import '../providers/map_provider.dart';
|
import '../providers/map_provider.dart';
|
||||||
@@ -75,6 +76,9 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
// MBTiles layers
|
// MBTiles layers
|
||||||
List<MapLayer> _mbtilesLayers = [];
|
List<MapLayer> _mbtilesLayers = [];
|
||||||
|
|
||||||
|
// WMS layers (Slovenian)
|
||||||
|
late final MapLayer _slovenianAerialLayer;
|
||||||
|
|
||||||
// Vector tile theme
|
// Vector tile theme
|
||||||
vtr.Theme? _vectorTheme;
|
vtr.Theme? _vectorTheme;
|
||||||
bool _isLoadingTheme = false;
|
bool _isLoadingTheme = false;
|
||||||
@@ -110,6 +114,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Initialize Slovenian aerial layer with CRS
|
||||||
|
_slovenianAerialLayer = MapLayer.getSlovenianAerial2024(slovenianCrs);
|
||||||
_loadSettings();
|
_loadSettings();
|
||||||
_loadMbtilesLayers();
|
_loadMbtilesLayers();
|
||||||
_initializeTileCache();
|
_initializeTileCache();
|
||||||
@@ -120,6 +126,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final mapProvider = context.read<MapProvider>();
|
final mapProvider = context.read<MapProvider>();
|
||||||
mapProvider.addListener(_handleMapNavigation);
|
mapProvider.addListener(_handleMapNavigation);
|
||||||
|
// Load WMS overlay state
|
||||||
|
mapProvider.loadOverlayState();
|
||||||
|
|
||||||
// Initialize background location service with BLE service
|
// Initialize background location service with BLE service
|
||||||
final appProvider = context.read<AppProvider>();
|
final appProvider = context.read<AppProvider>();
|
||||||
@@ -239,8 +247,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all available layers (default + MBTiles)
|
/// Get all available layers (default + WMS + MBTiles)
|
||||||
List<MapLayer> get _allLayers => [...MapLayer.allLayers, ..._mbtilesLayers];
|
List<MapLayer> get _allLayers => [...MapLayer.allLayers, _slovenianAerialLayer, ..._mbtilesLayers];
|
||||||
|
|
||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -537,6 +545,21 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
// Slovenian WMS base layer
|
||||||
|
ListTile(
|
||||||
|
leading: _currentLayer == _slovenianAerialLayer
|
||||||
|
? const Icon(Icons.check_circle, color: Colors.green)
|
||||||
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
|
title: Text(_slovenianAerialLayer.name),
|
||||||
|
subtitle: Text(_slovenianAerialLayer.attribution),
|
||||||
|
onTap: () async {
|
||||||
|
setState(() {
|
||||||
|
_currentLayer = _slovenianAerialLayer;
|
||||||
|
});
|
||||||
|
_saveSettings();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Offline MBTiles layers section
|
// Offline MBTiles layers section
|
||||||
if (_mbtilesLayers.isNotEmpty) ...[
|
if (_mbtilesLayers.isNotEmpty) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -576,6 +599,43 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
// WMS Overlays section
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.wmsOverlays,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Consumer<MapProvider>(
|
||||||
|
builder: (context, mapProvider, _) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Icons.grid_on, color: Colors.blue),
|
||||||
|
title: Text(AppLocalizations.of(context)!.cadastralParcels),
|
||||||
|
subtitle: const Text('© GURS'),
|
||||||
|
value: mapProvider.showCadastralOverlay,
|
||||||
|
onChanged: (value) {
|
||||||
|
mapProvider.toggleCadastralOverlay();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Icons.route, color: Colors.green),
|
||||||
|
title: Text(AppLocalizations.of(context)!.forestRoads),
|
||||||
|
subtitle: const Text('© GURS'),
|
||||||
|
value: mapProvider.showForestRoadsOverlay,
|
||||||
|
onChanged: (value) {
|
||||||
|
mapProvider.toggleForestRoadsOverlay();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -855,6 +915,15 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format distance for display
|
||||||
|
String _formatDistance(double meters) {
|
||||||
|
if (meters < 1000) {
|
||||||
|
return '${meters.toStringAsFixed(1)} m';
|
||||||
|
} else {
|
||||||
|
return '${(meters / 1000).toStringAsFixed(2)} km';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Show SAR dialog with pre-populated location from map long press
|
/// Show SAR dialog with pre-populated location from map long press
|
||||||
void _showSarDialogWithLocation(LatLng location) {
|
void _showSarDialogWithLocation(LatLng location) {
|
||||||
// Create a Position object from the LatLng coordinates
|
// Create a Position object from the LatLng coordinates
|
||||||
@@ -1065,6 +1134,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
child: FlutterMap(
|
child: FlutterMap(
|
||||||
mapController: _mapController,
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
|
// Use the layer's CRS if it has one (for WMS layers), otherwise default to EPSG:3857
|
||||||
|
crs: _currentLayer.crs ?? const Epsg3857(),
|
||||||
// Use saved position if available, otherwise use calculated center
|
// Use saved position if available, otherwise use calculated center
|
||||||
initialCenter: _savedMapCenter ?? center,
|
initialCenter: _savedMapCenter ?? center,
|
||||||
initialZoom: _savedMapZoom ?? _defaultZoom,
|
initialZoom: _savedMapZoom ?? _defaultZoom,
|
||||||
@@ -1082,11 +1153,29 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: (tapPosition, point) {
|
onLongPress: (tapPosition, point) {
|
||||||
// Skip if in drawing mode
|
// Handle measurement mode - set measurement points
|
||||||
if (drawingProvider.isDrawing) return;
|
if (drawingProvider.drawingMode == DrawingMode.measure) {
|
||||||
|
if (drawingProvider.measurementPoint1 == null) {
|
||||||
|
// Set first measurement point
|
||||||
|
drawingProvider.setMeasurementPoint1(point);
|
||||||
|
} else if (drawingProvider.measurementPoint2 == null) {
|
||||||
|
// Set second measurement point
|
||||||
|
drawingProvider.setMeasurementPoint2(point);
|
||||||
|
} else {
|
||||||
|
// Clear and start new measurement
|
||||||
|
drawingProvider.clearMeasurement();
|
||||||
|
drawingProvider.setMeasurementPoint1(point);
|
||||||
|
}
|
||||||
|
// Continue to also drop SAR marker pin
|
||||||
|
}
|
||||||
|
|
||||||
// Drop a pin at long press location (if no pin exists)
|
// Skip if in other drawing modes (but not measure mode)
|
||||||
if (_droppedPinLocation == null) {
|
if (drawingProvider.isDrawing && drawingProvider.drawingMode != DrawingMode.measure) return;
|
||||||
|
|
||||||
|
// Drop a pin at long press location
|
||||||
|
// In measurement mode, this allows creating SAR markers at measurement points
|
||||||
|
// The pin moves to the latest long press location
|
||||||
|
if (_droppedPinLocation == null || drawingProvider.drawingMode == DrawingMode.measure) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_droppedPinLocation = point;
|
_droppedPinLocation = point;
|
||||||
});
|
});
|
||||||
@@ -1183,13 +1272,116 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
}),
|
}),
|
||||||
maximumZoom: _currentLayer.maxZoom,
|
maximumZoom: _currentLayer.maxZoom,
|
||||||
)
|
)
|
||||||
else if (!_currentLayer.isVector)
|
else if (_currentLayer.isWms && _currentLayer.wmsBaseUrl != null && _currentLayer.crs != null)
|
||||||
|
// WMS Base Layer (e.g., Slovenian Aerial Imagery)
|
||||||
|
flutter_map.TileLayer(
|
||||||
|
wmsOptions: WMSTileLayerOptions(
|
||||||
|
baseUrl: _currentLayer.wmsBaseUrl!,
|
||||||
|
layers: _currentLayer.wmsLayers ?? [],
|
||||||
|
styles: _currentLayer.wmsStyles ?? [],
|
||||||
|
format: _currentLayer.wmsFormat ?? 'image/jpeg',
|
||||||
|
transparent: _currentLayer.wmsTransparent ?? false,
|
||||||
|
crs: _currentLayer.crs!,
|
||||||
|
),
|
||||||
|
// Use cached tile provider for offline support
|
||||||
|
tileProvider: _tileCache.getTileProviderForWms(_currentLayer),
|
||||||
|
userAgentPackageName: 'com.meshcore.sar',
|
||||||
|
maxZoom: _currentLayer.maxZoom,
|
||||||
|
errorTileCallback: (tile, error, stackTrace) {
|
||||||
|
debugPrint('🔴 WMS Base Layer tile error at ${tile.coordinates}: $error');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else if (!_currentLayer.isVector && !_currentLayer.isWms)
|
||||||
flutter_map.TileLayer(
|
flutter_map.TileLayer(
|
||||||
urlTemplate: _currentLayer.urlTemplate,
|
urlTemplate: _currentLayer.urlTemplate,
|
||||||
tileProvider: _tileCache.getTileProvider(_currentLayer),
|
tileProvider: _tileCache.getTileProvider(_currentLayer),
|
||||||
userAgentPackageName: 'com.meshcore.sar',
|
userAgentPackageName: 'com.meshcore.sar',
|
||||||
maxZoom: _currentLayer.maxZoom,
|
maxZoom: _currentLayer.maxZoom,
|
||||||
),
|
),
|
||||||
|
// WMS Overlays (rendered after base layer, before polylines)
|
||||||
|
// Note: These overlays only work with EPSG:3794 CRS (Slovenian coordinate system)
|
||||||
|
// Cadastral parcels overlay
|
||||||
|
Consumer<MapProvider>(
|
||||||
|
builder: (context, mapProvider, _) {
|
||||||
|
// Only show if enabled and map is using Slovenian CRS
|
||||||
|
if (!mapProvider.showCadastralOverlay || _currentLayer.crs == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return flutter_map.TileLayer(
|
||||||
|
wmsOptions: WMSTileLayerOptions(
|
||||||
|
baseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?',
|
||||||
|
layers: const ['pregledovalnik:kn_parcele'],
|
||||||
|
styles: const ['parcele'],
|
||||||
|
format: 'image/png',
|
||||||
|
transparent: true,
|
||||||
|
crs: slovenianCrs,
|
||||||
|
),
|
||||||
|
tileProvider: _tileCache.getTileProviderForWms(
|
||||||
|
MapLayer(
|
||||||
|
type: MapLayerType.wmsBase,
|
||||||
|
name: 'Cadastral Parcels',
|
||||||
|
urlTemplate: '',
|
||||||
|
attribution: '© GURS',
|
||||||
|
maxZoom: 19,
|
||||||
|
isWms: true,
|
||||||
|
wmsBaseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?',
|
||||||
|
wmsLayers: const ['pregledovalnik:kn_parcele'],
|
||||||
|
wmsFormat: 'image/png',
|
||||||
|
crs: slovenianCrs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userAgentPackageName: 'com.meshcore.sar',
|
||||||
|
maxZoom: 19,
|
||||||
|
errorTileCallback: (tile, error, stackTrace) {
|
||||||
|
debugPrint('🔴 Cadastral overlay tile error at ${tile.coordinates}: $error');
|
||||||
|
if (stackTrace != null) {
|
||||||
|
debugPrint(' StackTrace: $stackTrace');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Forest roads overlay
|
||||||
|
Consumer<MapProvider>(
|
||||||
|
builder: (context, mapProvider, _) {
|
||||||
|
// Only show if enabled and map is using Slovenian CRS
|
||||||
|
if (!mapProvider.showForestRoadsOverlay || _currentLayer.crs == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return flutter_map.TileLayer(
|
||||||
|
wmsOptions: WMSTileLayerOptions(
|
||||||
|
baseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||||
|
layers: const ['pregledovalnik:gozdne_ceste'],
|
||||||
|
styles: const ['gozdne_ceste'],
|
||||||
|
format: 'image/png',
|
||||||
|
transparent: true,
|
||||||
|
crs: slovenianCrs,
|
||||||
|
),
|
||||||
|
tileProvider: _tileCache.getTileProviderForWms(
|
||||||
|
MapLayer(
|
||||||
|
type: MapLayerType.wmsBase,
|
||||||
|
name: 'Forest Roads',
|
||||||
|
urlTemplate: '',
|
||||||
|
attribution: '© GURS',
|
||||||
|
maxZoom: 19,
|
||||||
|
isWms: true,
|
||||||
|
wmsBaseUrl: 'https://prostor.zgs.gov.si/geoserver/wms?',
|
||||||
|
wmsLayers: const ['pregledovalnik:gozdne_ceste'],
|
||||||
|
wmsFormat: 'image/png',
|
||||||
|
crs: slovenianCrs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userAgentPackageName: 'com.meshcore.sar',
|
||||||
|
maxZoom: 19,
|
||||||
|
errorTileCallback: (tile, error, stackTrace) {
|
||||||
|
debugPrint('🔴 Forest roads overlay tile error at ${tile.coordinates}: $error');
|
||||||
|
if (stackTrace != null) {
|
||||||
|
debugPrint(' StackTrace: $stackTrace');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Advertisement path polylines (rendered before markers)
|
// Advertisement path polylines (rendered before markers)
|
||||||
Consumer<MapProvider>(
|
Consumer<MapProvider>(
|
||||||
builder: (context, mapProvider, _) {
|
builder: (context, mapProvider, _) {
|
||||||
@@ -1214,6 +1406,23 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
),
|
),
|
||||||
// Location trail layer (rendered after paths, before drawings)
|
// Location trail layer (rendered after paths, before drawings)
|
||||||
const LocationTrailLayer(),
|
const LocationTrailLayer(),
|
||||||
|
// Measurement line layer (rendered before drawings)
|
||||||
|
if (drawingProvider.measurementPoint1 != null && drawingProvider.measurementPoint2 != null)
|
||||||
|
PolylineLayer(
|
||||||
|
polylines: [
|
||||||
|
Polyline(
|
||||||
|
points: [
|
||||||
|
drawingProvider.measurementPoint1!,
|
||||||
|
drawingProvider.measurementPoint2!,
|
||||||
|
],
|
||||||
|
color: Colors.yellow.withValues(alpha: 0.8),
|
||||||
|
strokeWidth: 3.0,
|
||||||
|
borderColor: Colors.black.withValues(alpha: 0.5),
|
||||||
|
borderStrokeWidth: 1.0,
|
||||||
|
pattern: StrokePattern.dashed(segments: [10, 5]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
// Drawing layer (rendered after paths, before markers)
|
// Drawing layer (rendered after paths, before markers)
|
||||||
DrawingLayer(
|
DrawingLayer(
|
||||||
drawings: drawingProvider.drawings,
|
drawings: drawingProvider.drawings,
|
||||||
@@ -1257,6 +1466,104 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
position: _locationService.currentPosition,
|
position: _locationService.currentPosition,
|
||||||
context: context,
|
context: context,
|
||||||
)!,
|
)!,
|
||||||
|
// Measurement point 1 marker
|
||||||
|
if (drawingProvider.measurementPoint1 != null)
|
||||||
|
Marker(
|
||||||
|
point: drawingProvider.measurementPoint1!,
|
||||||
|
width: 60,
|
||||||
|
height: 80,
|
||||||
|
rotate: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.yellow.shade700,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Start',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.yellow,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Measurement point 2 marker
|
||||||
|
if (drawingProvider.measurementPoint2 != null)
|
||||||
|
Marker(
|
||||||
|
point: drawingProvider.measurementPoint2!,
|
||||||
|
width: 60,
|
||||||
|
height: 80,
|
||||||
|
rotate: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.yellow.shade700,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'End',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.yellow,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
// Dropped pin marker with label
|
// Dropped pin marker with label
|
||||||
if (_droppedPinLocation != null)
|
if (_droppedPinLocation != null)
|
||||||
Marker(
|
Marker(
|
||||||
@@ -1410,6 +1717,90 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
objectCount: messagesProvider.objectMarkers.length,
|
objectCount: messagesProvider.objectMarkers.length,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Measurement distance overlay
|
||||||
|
if (drawingProvider.drawingMode == DrawingMode.measure)
|
||||||
|
Positioned(
|
||||||
|
top: 16,
|
||||||
|
left: 16,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.yellow.shade700.withValues(alpha: 0.95),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.straighten,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.measureDistance,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (drawingProvider.measuredDistance != null)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.distanceLabel(_formatDistance(drawingProvider.measuredDistance!)),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.longPressToStartNewMeasurement,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else if (drawingProvider.measurementPoint1 != null)
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.longPressForSecondPoint,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.longPressToStartMeasurement,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
// Map controls - right side (hidden in fullscreen mode)
|
// Map controls - right side (hidden in fullscreen mode)
|
||||||
if (!_isFullscreen)
|
if (!_isFullscreen)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|||||||
@@ -57,6 +57,26 @@ class TileCacheService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get tile provider for WMS layers with caching support
|
||||||
|
/// WMS layers require special handling because they use WMSTileLayerOptions
|
||||||
|
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
|
||||||
|
if (!_isInitialized) {
|
||||||
|
throw StateError(
|
||||||
|
'TileCacheService not initialized. Call initialize() first.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!layer.isWms) {
|
||||||
|
throw ArgumentError('Layer must be a WMS layer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the same cached tile provider
|
||||||
|
// The WMS URL construction is handled by flutter_map's WMSTileLayerOptions
|
||||||
|
return _store.getTileProvider(
|
||||||
|
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
|
||||||
|
cachedValidDuration: const Duration(days: 30),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> downloadRegion({
|
Future<void> downloadRegion({
|
||||||
required MapLayer layer,
|
required MapLayer layer,
|
||||||
required LatLngBounds bounds,
|
required LatLngBounds bounds,
|
||||||
|
|||||||
73
lib/services/wms_tile_provider.dart
Normal file
73
lib/services/wms_tile_provider.dart
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
/// Custom tile provider that logs WMS URLs for debugging
|
||||||
|
class DebugWmsTileProvider extends TileProvider {
|
||||||
|
final http.Client httpClient;
|
||||||
|
|
||||||
|
DebugWmsTileProvider() : httpClient = http.Client();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) {
|
||||||
|
return DebugNetworkTileProvider(
|
||||||
|
coordinates: coordinates,
|
||||||
|
options: options,
|
||||||
|
httpClient: httpClient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
httpClient.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DebugNetworkTileProvider extends ImageProvider<DebugNetworkTileProvider> {
|
||||||
|
final TileCoordinates coordinates;
|
||||||
|
final TileLayer options;
|
||||||
|
final http.Client httpClient;
|
||||||
|
|
||||||
|
const DebugNetworkTileProvider({
|
||||||
|
required this.coordinates,
|
||||||
|
required this.options,
|
||||||
|
required this.httpClient,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ImageStreamCompleter loadImage(DebugNetworkTileProvider key, ImageDecoderCallback decode) {
|
||||||
|
// Get the WMS URL from the tile layer options
|
||||||
|
final wmsOptions = options.wmsOptions;
|
||||||
|
if (wmsOptions == null) {
|
||||||
|
throw Exception('WMSTileLayerOptions is required for DebugWmsTileProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the WMS URL
|
||||||
|
final url = wmsOptions.getUrl(coordinates, 256, false);
|
||||||
|
|
||||||
|
// Log the URL for debugging
|
||||||
|
debugPrint('🌐 WMS Request URL: $url');
|
||||||
|
|
||||||
|
// Use NetworkImage to load the tile
|
||||||
|
return NetworkImage(url, headers: {'User-Agent': 'MeshCore SAR'})
|
||||||
|
.loadImage(NetworkImage(url), decode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<DebugNetworkTileProvider> obtainKey(ImageConfiguration configuration) {
|
||||||
|
return SynchronousFuture<DebugNetworkTileProvider>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other is DebugNetworkTileProvider &&
|
||||||
|
other.coordinates == coordinates &&
|
||||||
|
other.options == options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(coordinates, options);
|
||||||
|
}
|
||||||
86
lib/utils/slovenian_crs.dart
Normal file
86
lib/utils/slovenian_crs.dart
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import 'dart:math' show Point;
|
||||||
|
import 'dart:ui' show Rect;
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:proj4dart/proj4dart.dart' as proj4;
|
||||||
|
|
||||||
|
/// EPSG:3794 - Slovenia 1996 / Slovene National Grid
|
||||||
|
/// Transverse Mercator projection for Slovenia
|
||||||
|
///
|
||||||
|
/// Official definition from https://epsg.io/3794:
|
||||||
|
/// +proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000
|
||||||
|
/// +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs
|
||||||
|
///
|
||||||
|
/// This CRS is used by Slovenian government WMS services (prostor.zgs.gov.si)
|
||||||
|
|
||||||
|
/// Register and get EPSG:3794 projection
|
||||||
|
proj4.Projection getEpsg3794Projection() {
|
||||||
|
const epsg3794Def = '+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 '
|
||||||
|
'+x_0=500000 +y_0=-5000000 +ellps=GRS80 '
|
||||||
|
'+towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs';
|
||||||
|
|
||||||
|
// Register projection if not already registered
|
||||||
|
try {
|
||||||
|
return proj4.Projection.get('EPSG:3794') ?? proj4.Projection.add('EPSG:3794', epsg3794Def);
|
||||||
|
} catch (e) {
|
||||||
|
// If already registered, get it
|
||||||
|
return proj4.Projection.get('EPSG:3794')!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create Proj4Crs for EPSG:3794
|
||||||
|
///
|
||||||
|
/// Configuration matches the GeoWebCache tile grid used by prostor.zgs.gov.si
|
||||||
|
///
|
||||||
|
/// Official tile grid from WMTS GetCapabilities:
|
||||||
|
/// - TopLeftCorner: 373217.6542445397, 246158.298050262
|
||||||
|
/// - Bounds: X: 373217.65 to 695777.65, Y: 31118.30 to 246158.30
|
||||||
|
/// - Tile size: 256x256 pixels
|
||||||
|
/// - Scale denominators converted to resolutions using: resolution = scaleDenom * 0.00028
|
||||||
|
Crs getSlovenianCrs() {
|
||||||
|
final projection = getEpsg3794Projection();
|
||||||
|
|
||||||
|
// Resolutions calculated from GeoWebCache scale denominators
|
||||||
|
// Formula: resolution (m/px) = scaleDenominator * 0.00028 (OGC standard)
|
||||||
|
final resolutions = <double>[
|
||||||
|
420.0, // Zoom 0 - ScaleDenom: 1500000
|
||||||
|
280.0, // Zoom 1 - ScaleDenom: 1000000
|
||||||
|
210.0, // Zoom 2 - ScaleDenom: 750000
|
||||||
|
140.0, // Zoom 3 - ScaleDenom: 500000
|
||||||
|
70.0, // Zoom 4 - ScaleDenom: 250000
|
||||||
|
28.0, // Zoom 5 - ScaleDenom: 100000
|
||||||
|
14.0, // Zoom 6 - ScaleDenom: 50000
|
||||||
|
7.0, // Zoom 7 - ScaleDenom: 25000
|
||||||
|
4.2, // Zoom 8 - ScaleDenom: 15000
|
||||||
|
2.8, // Zoom 9 - ScaleDenom: 10000
|
||||||
|
1.4, // Zoom 10 - ScaleDenom: 5000
|
||||||
|
0.56, // Zoom 11 - ScaleDenom: 2000
|
||||||
|
0.28, // Zoom 12 - ScaleDenom: 1000
|
||||||
|
0.14, // Zoom 13 - ScaleDenom: 500
|
||||||
|
0.07, // Zoom 14 - ScaleDenom: 250
|
||||||
|
0.028, // Zoom 15 - ScaleDenom: 100
|
||||||
|
];
|
||||||
|
|
||||||
|
// Bounds from WMS capabilities (actual data extent in Slovenia)
|
||||||
|
final bounds = Rect.fromLTRB(
|
||||||
|
373217.65, // min X (west)
|
||||||
|
31118.30, // min Y (south) - top in Rect coordinates
|
||||||
|
695777.65, // max X (east)
|
||||||
|
246158.30, // max Y (north) - bottom in Rect coordinates
|
||||||
|
);
|
||||||
|
|
||||||
|
// Origin from WMTS TileMatrixSet TopLeftCorner
|
||||||
|
// This is the top-left corner of the tile pyramid (min X, max Y)
|
||||||
|
final origin = Point<double>(373217.6542445397, 246158.298050262);
|
||||||
|
|
||||||
|
return Proj4Crs.fromFactory(
|
||||||
|
code: 'EPSG:3794',
|
||||||
|
proj4Projection: projection,
|
||||||
|
resolutions: resolutions,
|
||||||
|
bounds: bounds,
|
||||||
|
origins: [origin],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton instance of Slovenian CRS for reuse
|
||||||
|
final Crs slovenianCrs = getSlovenianCrs();
|
||||||
@@ -127,6 +127,18 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
),
|
||||||
|
// Clear measurement
|
||||||
|
if (drawingProvider.drawingMode == DrawingMode.measure &&
|
||||||
|
drawingProvider.measurementPoint1 != null)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () => drawingProvider.clearMeasurement(),
|
||||||
|
tooltip: AppLocalizations.of(context)!.clearMeasurement,
|
||||||
|
color: Colors.orange,
|
||||||
|
iconSize: 20,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
// Complete line drawing
|
// Complete line drawing
|
||||||
if (drawingProvider.drawingMode == DrawingMode.line &&
|
if (drawingProvider.drawingMode == DrawingMode.line &&
|
||||||
drawingProvider.currentLinePoints.length >= 2)
|
drawingProvider.currentLinePoints.length >= 2)
|
||||||
@@ -207,6 +219,15 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
drawingProvider.setDrawingMode(DrawingMode.rectangle);
|
drawingProvider.setDrawingMode(DrawingMode.rectangle);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.straighten),
|
||||||
|
title: Text(AppLocalizations.of(sheetContext)!.measureDistance),
|
||||||
|
subtitle: Text(AppLocalizations.of(sheetContext)!.measureDistanceDesc),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
drawingProvider.setDrawingMode(DrawingMode.measure);
|
||||||
|
},
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
// Toggle received drawings visibility
|
// Toggle received drawings visibility
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
@@ -465,6 +486,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
return Icons.show_chart;
|
return Icons.show_chart;
|
||||||
case DrawingMode.rectangle:
|
case DrawingMode.rectangle:
|
||||||
return Icons.crop_square;
|
return Icons.crop_square;
|
||||||
|
case DrawingMode.measure:
|
||||||
|
return Icons.straighten;
|
||||||
case DrawingMode.none:
|
case DrawingMode.none:
|
||||||
return Icons.edit;
|
return Icons.edit;
|
||||||
}
|
}
|
||||||
@@ -477,6 +500,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
return AppLocalizations.of(context)!.drawLine;
|
return AppLocalizations.of(context)!.drawLine;
|
||||||
case DrawingMode.rectangle:
|
case DrawingMode.rectangle:
|
||||||
return AppLocalizations.of(context)!.drawRectangle;
|
return AppLocalizations.of(context)!.drawRectangle;
|
||||||
|
case DrawingMode.measure:
|
||||||
|
return AppLocalizations.of(context)!.measureDistance;
|
||||||
case DrawingMode.none:
|
case DrawingMode.none:
|
||||||
return AppLocalizations.of(context)!.drawing;
|
return AppLocalizations.of(context)!.drawing;
|
||||||
}
|
}
|
||||||
@@ -489,6 +514,8 @@ class DrawingToolbar extends StatelessWidget {
|
|||||||
return 'Tap map to add points\nTap ✓ to finish';
|
return 'Tap map to add points\nTap ✓ to finish';
|
||||||
case DrawingMode.rectangle:
|
case DrawingMode.rectangle:
|
||||||
return 'Tap start point, then end point';
|
return 'Tap start point, then end point';
|
||||||
|
case DrawingMode.measure:
|
||||||
|
return 'Long press two points to measure';
|
||||||
case DrawingMode.none:
|
case DrawingMode.none:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -782,7 +782,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
version: "6.0.3"
|
||||||
proj4dart:
|
proj4dart:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: proj4dart
|
name: proj4dart
|
||||||
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ dependencies:
|
|||||||
mbtiles: ^0.4.0
|
mbtiles: ^0.4.0
|
||||||
http: ^1.2.0
|
http: ^1.2.0
|
||||||
|
|
||||||
|
# Coordinate system projections for WMS (EPSG:3794)
|
||||||
|
proj4dart: ^2.1.0
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
permission_handler: ^12.0.1
|
permission_handler: ^12.0.1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user