mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-09 22:18:38 +02:00
Support FOV range notation: 0-360, 90-270, 10-45;90-125
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/// Represents a direction with its associated field-of-view (FOV) cone.
|
||||
class DirectionFov {
|
||||
/// The center direction in degrees (0-359, where 0 is north)
|
||||
final double centerDegrees;
|
||||
|
||||
/// The field-of-view width in degrees (e.g., 35, 90, 180, 360)
|
||||
final double fovDegrees;
|
||||
|
||||
DirectionFov(this.centerDegrees, this.fovDegrees);
|
||||
|
||||
@override
|
||||
String toString() => 'DirectionFov(center: ${centerDegrees}°, fov: ${fovDegrees}°)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DirectionFov &&
|
||||
runtimeType == other.runtimeType &&
|
||||
centerDegrees == other.centerDegrees &&
|
||||
fovDegrees == other.fovDegrees;
|
||||
|
||||
@override
|
||||
int get hashCode => centerDegrees.hashCode ^ fovDegrees.hashCode;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class NodeProfile {
|
||||
final bool requiresDirection;
|
||||
final bool submittable;
|
||||
final bool editable;
|
||||
final double? fov; // Field-of-view in degrees (null means use dev_config default)
|
||||
|
||||
NodeProfile({
|
||||
required this.id,
|
||||
@@ -18,6 +19,7 @@ class NodeProfile {
|
||||
this.requiresDirection = true,
|
||||
this.submittable = true,
|
||||
this.editable = true,
|
||||
this.fov,
|
||||
});
|
||||
|
||||
/// Get all built-in default node profiles
|
||||
@@ -50,6 +52,7 @@ class NodeProfile {
|
||||
requiresDirection: true,
|
||||
submittable: true,
|
||||
editable: true,
|
||||
fov: 45.0, // Flock cameras typically have narrow FOV
|
||||
),
|
||||
NodeProfile(
|
||||
id: 'builtin-motorola',
|
||||
@@ -67,6 +70,7 @@ class NodeProfile {
|
||||
requiresDirection: true,
|
||||
submittable: true,
|
||||
editable: true,
|
||||
fov: 60.0, // Motorola cameras typically have moderate FOV
|
||||
),
|
||||
NodeProfile(
|
||||
id: 'builtin-genetec',
|
||||
@@ -84,6 +88,7 @@ class NodeProfile {
|
||||
requiresDirection: true,
|
||||
submittable: true,
|
||||
editable: true,
|
||||
fov: 50.0, // Genetec cameras typically have moderate FOV
|
||||
),
|
||||
NodeProfile(
|
||||
id: 'builtin-leonardo',
|
||||
@@ -101,6 +106,7 @@ class NodeProfile {
|
||||
requiresDirection: true,
|
||||
submittable: true,
|
||||
editable: true,
|
||||
fov: 55.0, // Leonardo cameras typically have moderate FOV
|
||||
),
|
||||
NodeProfile(
|
||||
id: 'builtin-neology',
|
||||
@@ -150,6 +156,7 @@ class NodeProfile {
|
||||
requiresDirection: true,
|
||||
submittable: true,
|
||||
editable: true,
|
||||
fov: 90.0, // Axis cameras can have wider FOV
|
||||
),
|
||||
NodeProfile(
|
||||
id: 'builtin-generic-gunshot',
|
||||
@@ -208,6 +215,7 @@ class NodeProfile {
|
||||
bool? requiresDirection,
|
||||
bool? submittable,
|
||||
bool? editable,
|
||||
double? fov,
|
||||
}) =>
|
||||
NodeProfile(
|
||||
id: id ?? this.id,
|
||||
@@ -217,6 +225,7 @@ class NodeProfile {
|
||||
requiresDirection: requiresDirection ?? this.requiresDirection,
|
||||
submittable: submittable ?? this.submittable,
|
||||
editable: editable ?? this.editable,
|
||||
fov: fov ?? this.fov,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -227,6 +236,7 @@ class NodeProfile {
|
||||
'requiresDirection': requiresDirection,
|
||||
'submittable': submittable,
|
||||
'editable': editable,
|
||||
'fov': fov,
|
||||
};
|
||||
|
||||
factory NodeProfile.fromJson(Map<String, dynamic> j) => NodeProfile(
|
||||
@@ -237,6 +247,7 @@ class NodeProfile {
|
||||
requiresDirection: j['requiresDirection'] ?? true, // Default to true for backward compatibility
|
||||
submittable: j['submittable'] ?? true, // Default to true for backward compatibility
|
||||
editable: j['editable'] ?? true, // Default to true for backward compatibility
|
||||
fov: j['fov']?.toDouble(), // Can be null for backward compatibility
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'direction_fov.dart';
|
||||
import '../dev_config.dart';
|
||||
|
||||
class OsmNode {
|
||||
final int id;
|
||||
@@ -36,9 +38,10 @@ class OsmNode {
|
||||
);
|
||||
}
|
||||
|
||||
bool get hasDirection => directionDeg.isNotEmpty;
|
||||
bool get hasDirection => directionFovPairs.isNotEmpty;
|
||||
|
||||
List<double> get directionDeg {
|
||||
/// Get direction and FOV pairs, supporting range notation like "90-270" or "10-45;90-125;290"
|
||||
List<DirectionFov> get directionFovPairs {
|
||||
final raw = tags['direction'] ?? tags['camera:direction'];
|
||||
if (raw == null) return [];
|
||||
|
||||
@@ -50,17 +53,35 @@ class OsmNode {
|
||||
'W': 270.0, 'WNW': 292.5, 'NW': 315.0, 'NNW': 337.5,
|
||||
};
|
||||
|
||||
// Split on semicolons and parse each direction
|
||||
final directions = <double>[];
|
||||
final directionFovList = <DirectionFov>[];
|
||||
final parts = raw.split(';');
|
||||
|
||||
for (final part in parts) {
|
||||
final trimmed = part.trim().toUpperCase();
|
||||
final trimmed = part.trim();
|
||||
if (trimmed.isEmpty) continue;
|
||||
|
||||
// Check if this part contains a range (e.g., "90-270")
|
||||
if (trimmed.contains('-') && RegExp(r'^\d+\.?\d*-\d+\.?\d*$').hasMatch(trimmed)) {
|
||||
final rangeParts = trimmed.split('-');
|
||||
if (rangeParts.length == 2) {
|
||||
final start = double.tryParse(rangeParts[0]);
|
||||
final end = double.tryParse(rangeParts[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
final normalized = _calculateRangeCenter(start, end);
|
||||
directionFovList.add(normalized);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not a range, handle as single direction
|
||||
final trimmedUpper = trimmed.toUpperCase();
|
||||
|
||||
// First try compass direction lookup
|
||||
if (compassDirections.containsKey(trimmed)) {
|
||||
directions.add(compassDirections[trimmed]!);
|
||||
if (compassDirections.containsKey(trimmedUpper)) {
|
||||
final degrees = compassDirections[trimmedUpper]!;
|
||||
directionFovList.add(DirectionFov(degrees, kDirectionConeHalfAngle * 2));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,9 +95,35 @@ class OsmNode {
|
||||
|
||||
// Normalize: wrap negative or >360 into 0‑359 range
|
||||
final normalized = ((val % 360) + 360) % 360;
|
||||
directions.add(normalized);
|
||||
directionFovList.add(DirectionFov(normalized, kDirectionConeHalfAngle * 2));
|
||||
}
|
||||
|
||||
return directions;
|
||||
return directionFovList;
|
||||
}
|
||||
|
||||
/// Calculate center and width for a range like "90-270" or "270-90"
|
||||
DirectionFov _calculateRangeCenter(double start, double end) {
|
||||
// Normalize start and end to 0-359 range
|
||||
start = ((start % 360) + 360) % 360;
|
||||
end = ((end % 360) + 360) % 360;
|
||||
|
||||
double width, center;
|
||||
|
||||
if (start > end) {
|
||||
// Wrapping case: 270-90
|
||||
width = (end + 360) - start;
|
||||
center = ((start + end + 360) / 2) % 360;
|
||||
} else {
|
||||
// Normal case: 90-270
|
||||
width = end - start;
|
||||
center = (start + end) / 2;
|
||||
}
|
||||
|
||||
return DirectionFov(center, width);
|
||||
}
|
||||
|
||||
/// Legacy getter for backward compatibility - returns just center directions
|
||||
List<double> get directionDeg {
|
||||
return directionFovPairs.map((df) => df.centerDegrees).toList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user