fix: detect direction notation edits

This commit is contained in:
Anthony Maio
2026-08-16 18:33:57 -04:00
parent bdc5399c9f
commit 60e7e59ae9
5 changed files with 187 additions and 133 deletions
@@ -0,0 +1,32 @@
import '../models/node_profile.dart';
/// Formats editor direction values exactly as they will be queued for upload.
class DirectionSubmissionFormatter {
const DirectionSubmissionFormatter._();
static Object format(List<double> directions, NodeProfile? profile) {
if (directions.isEmpty) return 0.0;
final fov = profile?.fov;
if (fov != null && fov > 0) {
final ranges = directions
.map((center) => _formatDirectionWithFov(center, fov))
.toList();
return ranges.length == 1 ? ranges.first : ranges.join(';');
}
if (directions.length == 1) return directions.first;
return directions
.map((direction) => direction.round().toString())
.join(';');
}
static String _formatDirectionWithFov(double center, double fov) {
if (fov >= 360) return '0-360';
final halfFov = fov / 2;
final start = (center - halfFov + 360) % 360;
final end = (center + halfFov) % 360;
return '${start.round()}-${end.round()}';
}
}
@@ -0,0 +1,77 @@
import '../models/pending_upload.dart';
import '../state/session_state.dart';
import '../state/settings_state.dart';
import 'direction_submission_formatter.dart';
/// Detects whether an edit would serialize to a different OSM node.
class EditNodeChangeDetector {
const EditNodeChangeDetector._();
static bool hasActualChanges(EditNodeSession session) {
if (session.extractFromWay) return true;
const tolerance = 0.0000001;
if ((session.target.latitude - session.originalNode.coord.latitude).abs() >
tolerance ||
(session.target.longitude - session.originalNode.coord.longitude)
.abs() >
tolerance) {
return true;
}
final originalTags = session.originalNode.tags;
final submittedTags = _getSubmittedTags(session);
final originalDirection = _directionValue(originalTags);
final submittedDirection =
originalDirection == null && session.directions.isEmpty
? null
: _directionValue(submittedTags);
if (originalDirection != submittedDirection) {
return true;
}
return !_tagsEqual(originalTags, submittedTags);
}
static Map<String, String> _getSubmittedTags(EditNodeSession session) {
if (session.profile == null) return <String, String>{};
return PendingUpload(
coord: session.target,
direction: DirectionSubmissionFormatter.format(
session.directions,
session.profile,
),
profile: session.profile,
operatorProfile: session.operatorProfile,
refinedTags: session.refinedTags,
additionalExistingTags: session.additionalExistingTags,
changesetComment: session.changesetComment,
uploadMode: UploadMode.production,
operation: UploadOperation.modify,
originalNodeId: session.originalNode.id,
).getCombinedTags();
}
static String? _directionValue(Map<String, String> tags) =>
tags['direction'] ?? tags['camera:direction'];
static bool _tagsEqual(
Map<String, String> originalTags,
Map<String, String> submittedTags,
) {
final original = Map<String, String>.from(originalTags)
..remove('direction')
..remove('camera:direction');
final submitted = Map<String, String>.from(submittedTags)
..remove('direction')
..remove('camera:direction');
if (original.length != submitted.length) return false;
for (final entry in original.entries) {
if (submitted[entry.key] != entry.value) return false;
}
return true;
}
}