;;; A 'Kalman' filter is a much more sophisticated filter than a simple
;;; 'low-pass' filter.
;;;
;;; It takes a theoretically well founded approach, which means: it makes an
;;; 'educated guess' of the next position to be expected, and 'corrects' it
;;; with actual GPS data, considering the accuracy and the speed of current raw
;;; GPS data.
;;;
;;; For most use cases this is already sufficiently precise.
;;;
;;; If you need more precision, you would need to integrate e.g. accelerometer
;;; data into the Kalman filter.

(defpackage :kalman
  (:use :cl)
  (:nicknames :kal)
  (:export
   #:*lat*
   #:*lon*
   #:*timestamp*
   #:*variance*
   #:filter
   #:reset))

(in-package :kal)

(defvar *lat*       nil)
(defvar *lon*       nil)
(defvar *timestamp* nil)
(defvar *variance*  nil)

(defun reset ()
  (setf *variance* nil))

(defun filter (lat lon accuracy speed)
  (when (and lat lon accuracy speed)
    (setf accuracy (max 1 accuracy))
    (let ((timestamp (/ (get-internal-real-time) 1000.0)))
      (if *variance*
          (let ((time-inc (- timestamp *timestamp*)))
            (incf *variance* (/ (* time-inc (expt speed 2)) 1000))
            (setf *timestamp* timestamp)
            (let ((k (/ *variance* (+ *variance* (expt accuracy 2)))))
              (incf *lat* (* k (- lat *lat*)))
              (incf *lon* (* k (- lon *lon*)))
              (setf *variance* (* (- 1 k) *variance*))))
          (setf *lat*       lat
                *lon*       lon
                *timestamp* timestamp
                *variance*  (expt accuracy 2))))))
