mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-18 00:47:12 +02:00
Fixes for 360-deg FOVs
This commit is contained in:
@@ -104,7 +104,8 @@ cp lib/keys.dart.example lib/keys.dart
|
|||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
### Needed Bugfixes
|
### Needed Bugfixes
|
||||||
- Fix rendering of 0-360 FOV ring
|
- Make submission guide scarier
|
||||||
|
- "More..." button in profiles dropdown -> identify page
|
||||||
- Node data fetching super slow; retries not working?
|
- Node data fetching super slow; retries not working?
|
||||||
- Clean up tile cache; implement some max size or otherwise trim unused / old tiles to prevent infinite memory growth
|
- Clean up tile cache; implement some max size or otherwise trim unused / old tiles to prevent infinite memory growth
|
||||||
- Filter NSI suggestions based on what has already been typed in
|
- Filter NSI suggestions based on what has already been typed in
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"2.4.3": {
|
||||||
|
"content": [
|
||||||
|
"• Fixed 360° FOV rendering - devices with full circle coverage now render as complete rings instead of having a wedge cut out or being a line",
|
||||||
|
"• Fixed 360° FOV submission - now correctly submits '0-360' to OpenStreetMap instead of incorrect '180-180' values, disables direction slider"
|
||||||
|
]
|
||||||
|
},
|
||||||
"2.4.1": {
|
"2.4.1": {
|
||||||
"content": [
|
"content": [
|
||||||
"• Save button moved to top-right corner of profile editor screens",
|
"• Save button moved to top-right corner of profile editor screens",
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ class OsmNode {
|
|||||||
start = ((start % 360) + 360) % 360;
|
start = ((start % 360) + 360) % 360;
|
||||||
end = ((end % 360) + 360) % 360;
|
end = ((end % 360) + 360) % 360;
|
||||||
|
|
||||||
|
// Special case: if start equals end, this represents 360° FOV
|
||||||
|
if (start == end) {
|
||||||
|
return DirectionFov(start, 360.0);
|
||||||
|
}
|
||||||
|
|
||||||
double width, center;
|
double width, center;
|
||||||
|
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
|
|||||||
@@ -743,6 +743,11 @@ class UploadQueueState extends ChangeNotifier {
|
|||||||
|
|
||||||
// Convert a center direction and FOV to range notation (e.g., 180° center with 90° FOV -> "135-225")
|
// Convert a center direction and FOV to range notation (e.g., 180° center with 90° FOV -> "135-225")
|
||||||
String _formatDirectionWithFov(double center, double fov) {
|
String _formatDirectionWithFov(double center, double fov) {
|
||||||
|
// Handle 360-degree FOV as special case
|
||||||
|
if (fov >= 360) {
|
||||||
|
return '0-360';
|
||||||
|
}
|
||||||
|
|
||||||
final halfFov = fov / 2;
|
final halfFov = fov / 2;
|
||||||
final start = (center - halfFov + 360) % 360;
|
final start = (center - halfFov + 360) % 360;
|
||||||
final end = (center + halfFov) % 360;
|
final end = (center + halfFov) % 360;
|
||||||
|
|||||||
@@ -170,7 +170,10 @@ class DirectionConesBuilder {
|
|||||||
bool isActiveDirection = true,
|
bool isActiveDirection = true,
|
||||||
}) {
|
}) {
|
||||||
// Handle full circle case (360-degree FOV)
|
// Handle full circle case (360-degree FOV)
|
||||||
if (halfAngleDeg >= 180) {
|
// Use 179.5 threshold to account for floating point precision
|
||||||
|
print("DEBUG: halfAngleDeg = $halfAngleDeg, bearing = $bearingDeg");
|
||||||
|
if (halfAngleDeg >= 179.5) {
|
||||||
|
print("DEBUG: Using full circle for 360° FOV");
|
||||||
return _buildFullCircle(
|
return _buildFullCircle(
|
||||||
origin: origin,
|
origin: origin,
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
@@ -179,6 +182,7 @@ class DirectionConesBuilder {
|
|||||||
isActiveDirection: isActiveDirection,
|
isActiveDirection: isActiveDirection,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
print("DEBUG: Using normal cone for FOV = ${halfAngleDeg * 2}°");
|
||||||
|
|
||||||
// Calculate pixel-based radii
|
// Calculate pixel-based radii
|
||||||
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
||||||
@@ -232,6 +236,7 @@ class DirectionConesBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build a full circle for 360-degree FOV cases
|
/// Build a full circle for 360-degree FOV cases
|
||||||
|
/// Returns just the outer circle - we'll handle the donut effect differently
|
||||||
static Polygon _buildFullCircle({
|
static Polygon _buildFullCircle({
|
||||||
required LatLng origin,
|
required LatLng origin,
|
||||||
required double zoom,
|
required double zoom,
|
||||||
@@ -239,17 +244,19 @@ class DirectionConesBuilder {
|
|||||||
bool isSession = false,
|
bool isSession = false,
|
||||||
bool isActiveDirection = true,
|
bool isActiveDirection = true,
|
||||||
}) {
|
}) {
|
||||||
|
print("DEBUG: Building full circle - isSession: $isSession, isActiveDirection: $isActiveDirection");
|
||||||
|
|
||||||
// Calculate pixel-based radii
|
// Calculate pixel-based radii
|
||||||
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength);
|
||||||
final innerRadiusPx = kNodeIconDiameter + (2 * getNodeRingThickness(context));
|
|
||||||
|
|
||||||
// Convert pixels to coordinate distances with zoom scaling
|
// Convert pixels to coordinate distances with zoom scaling
|
||||||
final pixelToCoordinate = 0.00001 * math.pow(2, 15 - zoom);
|
final pixelToCoordinate = 0.00001 * math.pow(2, 15 - zoom);
|
||||||
final outerRadius = outerRadiusPx * pixelToCoordinate;
|
final outerRadius = outerRadiusPx * pixelToCoordinate;
|
||||||
final innerRadius = innerRadiusPx * pixelToCoordinate;
|
|
||||||
|
|
||||||
// Create full circle with many points for smooth rendering
|
print("DEBUG: Outer radius: $outerRadius, zoom: $zoom");
|
||||||
const int circlePoints = 36;
|
|
||||||
|
// Create simple filled circle - no donut complexity
|
||||||
|
const int circlePoints = 60;
|
||||||
final points = <LatLng>[];
|
final points = <LatLng>[];
|
||||||
|
|
||||||
LatLng project(double deg, double distance) {
|
LatLng project(double deg, double distance) {
|
||||||
@@ -260,17 +267,13 @@ class DirectionConesBuilder {
|
|||||||
return LatLng(origin.latitude + dLat, origin.longitude + dLon);
|
return LatLng(origin.latitude + dLat, origin.longitude + dLon);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add outer circle points
|
// Add outer circle points - simple complete circle
|
||||||
for (int i = 0; i < circlePoints; i++) {
|
for (int i = 0; i <= circlePoints; i++) { // Note: <= to ensure closure
|
||||||
final angle = i * 360.0 / circlePoints;
|
final angle = (i * 360.0 / circlePoints) % 360.0;
|
||||||
points.add(project(angle, outerRadius));
|
points.add(project(angle, outerRadius));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add inner circle points in reverse order to create donut
|
print("DEBUG: Created ${points.length} points for full circle");
|
||||||
for (int i = circlePoints - 1; i >= 0; i--) {
|
|
||||||
final angle = i * 360.0 / circlePoints;
|
|
||||||
points.add(project(angle, innerRadius));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust opacity based on direction state
|
// Adjust opacity based on direction state
|
||||||
double opacity = kDirectionConeOpacity;
|
double opacity = kDirectionConeOpacity;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: deflockapp
|
name: deflockapp
|
||||||
description: Map public surveillance infrastructure with OpenStreetMap
|
description: Map public surveillance infrastructure with OpenStreetMap
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 2.4.1+39 # The thing after the + is the version code, incremented with each release
|
version: 2.4.3+41 # The thing after the + is the version code, incremented with each release
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+
|
sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:deflockapp/models/osm_node.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('OsmNode Direction Parsing', () {
|
||||||
|
test('should parse 360-degree FOV from X-X notation', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '180-180'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(180.0));
|
||||||
|
expect(directionFovPairs[0].fovDegrees, equals(360.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse 360-degree FOV from 0-0 notation', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '0-0'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(0.0));
|
||||||
|
expect(directionFovPairs[0].fovDegrees, equals(360.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse 360-degree FOV from 270-270 notation', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '270-270'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(270.0));
|
||||||
|
expect(directionFovPairs[0].fovDegrees, equals(360.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse normal range notation correctly', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '90-270'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(180.0));
|
||||||
|
expect(directionFovPairs[0].fovDegrees, equals(180.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse wrapping range notation correctly', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '270-90'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(0.0));
|
||||||
|
expect(directionFovPairs[0].fovDegrees, equals(180.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse single direction correctly', () {
|
||||||
|
final node = OsmNode(
|
||||||
|
id: 1,
|
||||||
|
coord: const LatLng(0, 0),
|
||||||
|
tags: {'direction': '90'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final directionFovPairs = node.directionFovPairs;
|
||||||
|
|
||||||
|
expect(directionFovPairs, hasLength(1));
|
||||||
|
expect(directionFovPairs[0].centerDegrees, equals(90.0));
|
||||||
|
// Default FOV from dev_config (kDirectionConeHalfAngle * 2)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user