mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
301 lines
9.4 KiB
Dart
301 lines
9.4 KiB
Dart
import 'package:uuid/uuid.dart';
|
|
import 'osm_node.dart';
|
|
|
|
/// Sentinel value for copyWith methods to distinguish between null and not provided
|
|
const Object _notProvided = Object();
|
|
|
|
/// A bundle of preset OSM tags that describe a particular surveillance node model/type.
|
|
class NodeProfile {
|
|
final String id;
|
|
final String name;
|
|
final Map<String, String> tags;
|
|
final bool builtin;
|
|
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,
|
|
required this.name,
|
|
required this.tags,
|
|
this.builtin = false,
|
|
this.requiresDirection = true,
|
|
this.submittable = true,
|
|
this.editable = true,
|
|
this.fov,
|
|
});
|
|
|
|
/// Get all built-in default node profiles
|
|
static List<NodeProfile> getDefaults() => [
|
|
NodeProfile(
|
|
id: 'builtin-generic-alpr',
|
|
name: 'Generic ALPR',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance:type': 'ALPR',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: false,
|
|
editable: false,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-flock',
|
|
name: 'Flock',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Flock Safety',
|
|
'manufacturer:wikidata': 'Q108485435',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-motorola',
|
|
name: 'Motorola/Vigilant',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Motorola Solutions',
|
|
'manufacturer:wikidata': 'Q634815',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-genetec',
|
|
name: 'Genetec',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Genetec',
|
|
'manufacturer:wikidata': 'Q30295174',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-leonardo',
|
|
name: 'Leonardo/ELSAG',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Leonardo',
|
|
'manufacturer:wikidata': 'Q910379',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-neology',
|
|
name: 'Neology',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Neology, Inc.',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-rekor',
|
|
name: 'Rekor',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Rekor',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-axis',
|
|
name: 'Axis Communications',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'ALPR',
|
|
'surveillance:zone': 'traffic',
|
|
'camera:type': 'fixed',
|
|
'camera:mount': '', // Empty value for refinement
|
|
'manufacturer': 'Axis Communications',
|
|
'manufacturer:wikidata': 'Q2347731',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-generic-gunshot',
|
|
name: 'Generic Gunshot Detector',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance:type': 'gunshot_detector',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: false,
|
|
submittable: false,
|
|
editable: false,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-shotspotter',
|
|
name: 'ShotSpotter',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'gunshot_detector',
|
|
'surveillance:brand': 'ShotSpotter',
|
|
'surveillance:brand:wikidata': 'Q107740188',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: false,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
NodeProfile(
|
|
id: 'builtin-flock-raven',
|
|
name: 'Flock Raven',
|
|
tags: const {
|
|
'man_made': 'surveillance',
|
|
'surveillance': 'public',
|
|
'surveillance:type': 'gunshot_detector',
|
|
'brand': 'Flock Safety',
|
|
'brand:wikidata': 'Q108485435',
|
|
},
|
|
builtin: true,
|
|
requiresDirection: false,
|
|
submittable: true,
|
|
editable: true,
|
|
),
|
|
];
|
|
|
|
|
|
|
|
/// Returns true if this profile can be used for submissions
|
|
bool get isSubmittable => submittable;
|
|
|
|
NodeProfile copyWith({
|
|
String? id,
|
|
String? name,
|
|
Map<String, String>? tags,
|
|
bool? builtin,
|
|
bool? requiresDirection,
|
|
bool? submittable,
|
|
bool? editable,
|
|
Object? fov = _notProvided,
|
|
}) =>
|
|
NodeProfile(
|
|
id: id ?? this.id,
|
|
name: name ?? this.name,
|
|
tags: tags ?? this.tags,
|
|
builtin: builtin ?? this.builtin,
|
|
requiresDirection: requiresDirection ?? this.requiresDirection,
|
|
submittable: submittable ?? this.submittable,
|
|
editable: editable ?? this.editable,
|
|
fov: fov == _notProvided ? this.fov : fov as double?,
|
|
);
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'name': name,
|
|
'tags': tags,
|
|
'builtin': builtin,
|
|
'requiresDirection': requiresDirection,
|
|
'submittable': submittable,
|
|
'editable': editable,
|
|
'fov': fov,
|
|
};
|
|
|
|
factory NodeProfile.fromJson(Map<String, dynamic> j) => NodeProfile(
|
|
id: j['id'],
|
|
name: j['name'],
|
|
tags: Map<String, String>.from(j['tags']),
|
|
builtin: j['builtin'] ?? false,
|
|
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
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is NodeProfile &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id;
|
|
|
|
@override
|
|
int get hashCode => id.hashCode;
|
|
|
|
/// Create a temporary empty profile for editing existing nodes
|
|
/// Used as the default "<Existing tags>" option when editing nodes
|
|
/// All existing tags will flow through as additionalExistingTags
|
|
static NodeProfile createExistingTagsProfile(OsmNode node) {
|
|
// Calculate FOV from existing direction ranges if applicable
|
|
double? calculatedFov;
|
|
|
|
// If node has direction/FOV pairs, check if they all have the same FOV
|
|
if (node.directionFovPairs.isNotEmpty) {
|
|
final firstFov = node.directionFovPairs.first.fovDegrees;
|
|
|
|
// If all directions have the same FOV, use it for the profile
|
|
if (node.directionFovPairs.every((df) => df.fovDegrees == firstFov)) {
|
|
calculatedFov = firstFov;
|
|
}
|
|
}
|
|
|
|
return NodeProfile(
|
|
id: 'temp-empty-${node.id}',
|
|
name: '<Existing tags>', // Will be localized in UI
|
|
tags: {}, // Completely empty - all existing tags become additional
|
|
builtin: false,
|
|
requiresDirection: true,
|
|
submittable: true,
|
|
editable: false,
|
|
fov: calculatedFov, // Use calculated FOV from existing direction ranges
|
|
);
|
|
}
|
|
|
|
|
|
}
|
|
|