All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vesalainen.navi.AnchorWatch Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Timo Vesalainen
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.vesalainen.navi;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.ejml.data.DenseMatrix64F;
import org.vesalainen.math.AbstractCircle;
import org.vesalainen.math.AbstractPoint;
import org.vesalainen.math.Circle;
import org.vesalainen.math.CircleFitter;
import org.vesalainen.math.Circles;
import org.vesalainen.math.ConvexPolygon;
import org.vesalainen.math.Point;

/**
 *
 * @author Timo Vesalainen
 */
public class AnchorWatch implements Serializable, LocationObserver
{
    private static final long serialVersionUID = 1L;
    private static final double DegreeToMeters = 36.0 / 4000000.0;
    private ConvexPolygon area;
    private DenseMatrix64F points;
    private DenseMatrix64F tempCenter;
    private Point center;
    private AbstractCircle estimated;
    private ConvexPolygon outer;
    private CircleFitter fitter;
    private final List watchers = new ArrayList<>();
    private double chainLength = 60 * DegreeToMeters;
    private SafeSector safeSector;
    private LocalLongitude localLongitude;
    private double lastLongitude = Double.NaN;  // internal
    private double lastLatitude = Double.NaN;
    private long lastTime = -1;

    public AnchorWatch()
    {
        reset();
    }

    public final void reset()
    {
        tempCenter = new DenseMatrix64F(2, 1);
        points = new DenseMatrix64F(0, 2);
        area = new ConvexPolygon();
        outer = new ConvexPolygon();
        center = null;
        estimated = null;
        fitter = null;
        safeSector = null;
        localLongitude = null;
        lastLongitude = Double.NaN;  // internal
        lastLatitude = Double.NaN;
        lastTime = -1;
        
    }

    @Override
    public void update(double longitude, double latitude, long time)
    {
        update(longitude, latitude, time, Double.NaN);
    }
    @Override
    public void update(double longitude, double latitude, long time, double accuracy)
    {
        if (localLongitude == null)
        {
            localLongitude = LocalLongitude.getInstance(longitude, latitude);
        }
        double internal = localLongitude.getInternal(longitude);
        if (Double.isNaN(lastLongitude))
        {
            doUpdate(internal, latitude, time, accuracy, 0);
        }
        else
        {
            double distance = Math.hypot(internal-lastLongitude, latitude-lastLatitude);
            double dTime = (time-lastTime)/1000.0;
            double speed = toMeters(distance/dTime);
            doUpdate(internal, latitude, time, accuracy, speed);
        }
        lastLongitude = internal;
        lastLatitude = latitude;
        lastTime = time;
    }
    @Override
    public void update(double longitude, double latitude, long time, double accuracy, double speed)
    {
        if (localLongitude == null)
        {
            localLongitude = LocalLongitude.getInstance(longitude, latitude);
        }
        double internal = localLongitude.getInternal(longitude);
        doUpdate(internal, latitude, time, accuracy, speed);
        lastLongitude = internal;
        lastLatitude = latitude;
        lastTime = time;
    }
    private void doUpdate(double internal, double latitude, long time, double accuracy, double speed)
    {
        if (fitter != null && !safeSector.isInside(internal, latitude))
        {
            double distance = Circles.distanceFromCenter(safeSector, internal, latitude);
            fireAlarm(toMeters(distance));
        }
        fireLocation(internal, latitude, time, accuracy, speed);
        if (area.addPoint(internal, latitude))
        {
            fireArea(area);
            points.setReshape(area.points);
            if (fitter == null)
            {
                double radius = CircleFitter.initialCenter(points, tempCenter);
                if (!Double.isNaN(radius))
                {
                    fitter = new CircleFitter();
                    center = new AbstractPoint(tempCenter.data[0], tempCenter.data[1]);
                    estimated = new AbstractCircle(center, chainLength);
                    safeSector = new SafeSector(estimated);
                }
            }
            if (fitter != null)
            {
                if (!area.isInside(center))
                {
                    area.getOuterBoundary(safeSector, outer);
                    fireOuter(outer.points);
                    fitter.fit(safeSector, outer.points);
                }
                else
                {
                    fitter.fit(safeSector, area.points);
                }
                estimated.set(fitter);
                fireEstimated(estimated);
                fireSafeSector(safeSector);
            }
        }
        else
        {
            double minimumDistance = area.getMinimumDistance(internal, latitude);
            if (!Double.isNaN(accuracy) && !Double.isInfinite(accuracy))
            {
                minimumDistance = Math.max(0, minimumDistance-accuracy);
            }
            fireSuggestNextUpdateIn(minimumDistance/speed, minimumDistance);
        }
    }

    public boolean setAnchorLocation()
    {
        if (safeSector != null && !Double.isNaN(lastLongitude))
        {
            safeSector.set(lastLongitude, lastLatitude);
            return true;
        }
        else
        {
            return false;
        }
    }

    public Point getCenter()
    {
        return center;
    }

    public double getRadius()
    {
        return fitter.getRadius();
    }

    public ConvexPolygon getArea()
    {
        return area;
    }

    public void setChainLength(int meters)
    {
        this.chainLength = meters * DegreeToMeters;
    }

    public static double toMeters(double degrees)
    {
        return degrees / DegreeToMeters;
    }
    
    public void addWatcher(Watcher watcher)
    {
        if (watcher == null)
        {
            throw new NullPointerException();
        }
        watchers.add(watcher);
    }
    
    public void removeWatcher(Watcher watcher)
    {
        watchers.remove(watcher);
    }
    
    private void fireLocation(double x, double y, long time, double accuracy, double speed)
    {
        for (Watcher watcher : watchers)
        {
            watcher.location(x, y, time, accuracy, speed);
        }
    }
    private void fireAlarm(double distance)
    {
        for (Watcher watcher : watchers)
        {
            watcher.alarm(distance);
        }
    }
    private void fireArea(ConvexPolygon area)
    {
        for (Watcher watcher : watchers)
        {
            watcher.area(area);
        }
    }
    private void fireOuter(DenseMatrix64F path)
    {
        for (Watcher watcher : watchers)
        {
            watcher.outer(path);
        }
    }
    private void fireEstimated(Circle estimated)
    {
        for (Watcher watcher : watchers)
        {
            watcher.estimated(estimated);
        }
    }
    private void fireSafeSector(SafeSector safe)
    {
        for (Watcher watcher : watchers)
        {
            watcher.safeSector(safe);
        }
    }
    private void fireSuggestNextUpdateIn(double seconds, double meters)
    {
        for (Watcher watcher : watchers)
        {
            watcher.suggestNextUpdateIn(seconds, meters);
        }
    }

    /**
     * Anchor updates.
     * 

Note! All coordinates are projected for geometry. Y-coordinate is the * same as latitude while x-coordinate is cos(latitude) * longitude. */ public interface Watcher { void alarm(double distance); void location(double x, double y, long time, double accuracy, double speed); void area(ConvexPolygon area); void outer(DenseMatrix64F path); void estimated(Circle estimated); void safeSector(SafeSector safe); void suggestNextUpdateIn(double seconds, double meters); } public class Center implements Point { private double[] data; public Center(DenseMatrix64F m) { data = m.data; } @Override public double getX() { return data[0]; } @Override public double getY() { return data[1]; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy