Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NadarayaWatsonMultilateralizer #754

Merged
merged 1 commit into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/Locators/NadarayaWatsonMultilateralizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using ESPresense.Extensions;
using ESPresense.Models;
using MathNet.Spatial.Euclidean;
using Serilog;

namespace ESPresense.Locators;

public class NadarayaWatsonMultilateralizer(Device device, Floor floor, State state) : ILocate
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved
{
public bool Locate(Scenario scenario)
{
var confidence = scenario.Confidence;

var nodes = device.Nodes.Values
.Where(a => a.Current && (a.Node?.Floors?.Contains(floor) ?? false))
.OrderBy(a => a.Distance)
.ToArray();

var positions = nodes.Select(a => a.Node!.Location).ToArray();
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved

scenario.Minimum = nodes.Min(a => (double?)a.Distance);
scenario.LastHit = nodes.Max(a => a.LastHit);
scenario.Fixes = positions.Length;

if (positions.Length <= 1)
{
scenario.Room = null;
scenario.Confidence = 0;
scenario.Error = null;
scenario.Floor = null;
return false;
}

scenario.Floor = floor;

var guess = confidence < 5
? Point3D.MidPoint(positions[0], positions[1])
: scenario.Location;

try
{
if (positions.Length < 3 || floor.Bounds == null)
{
confidence = 1;
scenario.UpdateLocation(guess);
}
else
{
// Nadaraya-Watson estimator implementation
double epsilon = 1e-6;

var weights = nodes.Select(dn =>
{
return 1.0 / (Math.Pow(dn.Distance, 2) + epsilon);
}).ToArray();

var totalWeight = weights.Sum();

var weightedX = nodes.Zip(weights, (dn, w) => dn.Node!.Location.X * w).Sum();
var weightedY = nodes.Zip(weights, (dn, w) => dn.Node!.Location.Y * w).Sum();
var weightedZ = nodes.Zip(weights, (dn, w) => dn.Node!.Location.Z * w).Sum();

var estimatedLocation = new Point3D(
weightedX / totalWeight,
weightedY / totalWeight,
weightedZ / totalWeight
);

scenario.UpdateLocation(estimatedLocation);

// Calculate weighted error
var weightedError = nodes.Zip(weights, (dn, w) =>
{
var estimatedDistance = estimatedLocation.DistanceTo(dn.Node!.Location);
var residual = estimatedDistance - dn.Distance;
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved
return w * Math.Pow(residual, 2);
}).Sum() / totalWeight;

scenario.Error = weightedError;
scenario.Iterations = null;
//scenario.ReasonForExit = ;

confidence = (int)Math.Min(100, Math.Max(10, 100.0 - (weightedError * 10)));
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved
}
}
catch (Exception ex)
{
confidence = 0;
scenario.UpdateLocation(new Point3D());
Log.Error("Error finding location for {0}: {1}", device, ex.Message);
}
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved

scenario.Confidence = confidence;

if (confidence <= 0) return false;
if (Math.Abs(scenario.Location.DistanceTo(scenario.LastLocation)) < 0.1) return false;
DTTerastar marked this conversation as resolved.
Show resolved Hide resolved

scenario.Room = floor.Rooms.Values.FirstOrDefault(a =>
a.Polygon?.EnclosesPoint(scenario.Location.ToPoint2D()) ?? false);

return true;
}
}
2 changes: 1 addition & 1 deletion src/ui/src/lib/CalibrationMatrix.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
const errorText = await response.text();
throw new Error(`Server error ${response.status}: ${errorText}`);
}
} catch (error) {
} catch (error: any) {
console.error('Error resetting calibration:', error);
toastStore.trigger({
message: `Failed to reset calibration: ${error.message}`,
Expand Down
Loading