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

au.gov.amsa.risky.format.OperatorMinEffectiveSpeedThreshold Maven / Gradle / Ivy

There is a newer version: 0.6.19
Show newest version
package au.gov.amsa.risky.format;

import java.util.Iterator;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import com.github.davidmoten.grumpy.core.Position;

import au.gov.amsa.risky.format.OperatorMinEffectiveSpeedThreshold.FixWithPreAndPostEffectiveSpeed;
import au.gov.amsa.util.RingBuffer;
import rx.Observable.Operator;
import rx.Subscriber;

public final class OperatorMinEffectiveSpeedThreshold implements
        Operator {

    private long deltaMs;
    private final long smallestReportingIntervalMs = 1000;
    private final RingBuffer buffer;

    public OperatorMinEffectiveSpeedThreshold(long deltaMs) {
        this.deltaMs = deltaMs;
        int maxSize = (int) (deltaMs / smallestReportingIntervalMs) + 1;
        this.buffer = RingBuffer.create(maxSize);
    }

    @Override
    public Subscriber call(
            final Subscriber child) {
        return new Subscriber(child) {

            private Optional middle = Optional.empty();

            @Override
            public void onCompleted() {
                child.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                child.onError(e);
            }

            @Override
            public void onNext(HasFix fix) {
                boolean emitted = false;
                // if mmsi changes then clear the fix history
                if (!buffer.isEmpty() && buffer.peek().fix().mmsi() != fix.fix().mmsi()) {
                    buffer.clear();
                    middle = Optional.empty();
                }
                buffer.add(fix);
                HasFix latest = fix;
                if (!middle.isPresent()) {
                    HasFix first = buffer.peek();
                    if (latest.fix().time() - first.fix().time() >= deltaMs) {
                        middle = Optional.of(latest);
                    }
                } else
                    while (latest.fix().time() - middle.get().fix().time() >= deltaMs) {

                        // now can emit middle with its pre and post effective
                        // speed and reliability measure (time difference minus
                        // deltaMs)

                        HasFix first = buffer.peek();

                        // measure distance from first to middle
                        double distanceFirstToMiddleKm = 0;
                        Iterator en = buffer.iterator();
                        {
                            Optional previous = Optional.empty();
                            boolean keepGoing = en.hasNext();
                            while (keepGoing) {
                                HasFix f = en.next();
                                if (previous.isPresent())
                                    distanceFirstToMiddleKm += distanceKm(previous.get(), f);
                                previous = Optional.of(f);
                                keepGoing = en.hasNext() && f != middle.get();
                            }
                        }

                        // measure distance from middle to latest
                        double distanceMiddleToLatestKm = 0;
                        Optional firstAfterMiddle = Optional.empty();
                        {
                            Optional previous = middle;
                            boolean keepGoing = en.hasNext();
                            while (keepGoing) {
                                HasFix f = en.next();
                                if (!firstAfterMiddle.isPresent())
                                    firstAfterMiddle = Optional.of(f);
                                distanceMiddleToLatestKm += distanceKm(previous.get(), f);
                                previous = Optional.of(f);
                                keepGoing = en.hasNext();
                            }
                        }

                        // time from first to middle
                        long timeFirstToMiddleMs = middle.get().fix().time() - first.fix().time();
                        long timeMiddleToLatestMs = latest.fix().time() - middle.get().fix().time();

                        // speed calcs
                        double preSpeedKnots = distanceFirstToMiddleKm
                                / (double) timeFirstToMiddleMs / 1.852 * TimeUnit.HOURS.toMillis(1);

                        double postSpeedKnots = distanceMiddleToLatestKm
                                / (double) timeMiddleToLatestMs / 1.852
                                * TimeUnit.HOURS.toMillis(1);

                        double preError = Math.abs(middle.get().fix().time() - first.fix().time()
                                - deltaMs)
                                / (double) TimeUnit.MINUTES.toMillis(1);
                        double postError = Math.abs(latest.fix().time() - middle.get().fix().time()
                                - deltaMs)
                                / (double) TimeUnit.MINUTES.toMillis(1);

                        // emit what we have!
                        child.onNext(new FixWithPreAndPostEffectiveSpeed(middle.get(),
                                preSpeedKnots, preError, postSpeedKnots, postError));
                        emitted = true;

                        // drop values from front of buffer
                        en = buffer.iterator();
                        // skip first
                        en.next();
                        while (en.hasNext()) {
                            HasFix next = en.next();
                            if (firstAfterMiddle.get().fix().time() - next.fix().time() < deltaMs)
                                break;
                            else
                                buffer.remove();
                        }
                        middle = firstAfterMiddle;
                    }
                if (!emitted) {
                    // value submitted to this method did not result in an
                    // emission so request another one
                    request(1);
                }
            }

        };
    }

    private static double distanceKm(HasFix a, HasFix b) {
        return toPosition(a).getDistanceToKm(toPosition(b));
    }

    private static Position toPosition(HasFix f) {
        return Position.create(f.fix().lat(), f.fix().lon());
    }

    public static final class FixWithPreAndPostEffectiveSpeed implements HasFix {

        private final double preEffectiveSpeedKnots;

        private final double preError;
        private final double postEffectiveSpeedKnots;
        private final double postError;
        private final HasFix fix;

        public FixWithPreAndPostEffectiveSpeed(HasFix fix, double preEffectiveSpeedKnots,
                double preError, double postEffectiveSpeedKnots, double postError) {
            this.preEffectiveSpeedKnots = preEffectiveSpeedKnots;
            this.preError = preError;
            this.postEffectiveSpeedKnots = postEffectiveSpeedKnots;
            this.postError = postError;
            this.fix = fix;
        }

        @Override
        public Fix fix() {
            return fix.fix();
        }

        public HasFix fixWrapper() {
            return fix;
        }

        public double preEffectiveSpeedKnots() {
            return preEffectiveSpeedKnots;
        }

        public double preError() {
            return preError;
        }

        public double postEffectiveSpeedKnots() {
            return postEffectiveSpeedKnots;
        }

        public double postError() {
            return postError;
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder();
            b.append("FixWithPreAndPostEffectiveSpeed [preEffectiveSpeedKnots=");
            b.append(preEffectiveSpeedKnots);
            b.append(", preError=");
            b.append(preError);
            b.append(", postEffectiveSpeedKnots=");
            b.append(postEffectiveSpeedKnots);
            b.append(", postError=");
            b.append(postError);
            b.append(", fix=");
            b.append(fix);
            b.append("]");
            return b.toString();
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy