com.almende.pi5.common.PowerTimeLine.java Source code

Java tutorial

Introduction

Here is the source code for com.almende.pi5.common.PowerTimeLine.java

Source

/*
 * Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
 * License: The Apache Software License, Version 2.0
 */
package com.almende.pi5.common;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Interval;

import com.almende.util.jackson.JOM;
import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * The Class PowerTimeLine.
 */
public class PowerTimeLine {
    private DateTime timestamp = DateTime.now();
    private ArrayList<PowerTime> series = new ArrayList<PowerTime>();

    /**
     * Instantiates a new power time line.
     */
    public PowerTimeLine() {
    }

    /**
     * Instantiates a new power time line.
     *
     * @param timestamp
     *            the timestamp
     */
    public PowerTimeLine(final DateTime timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * Gets the timestamp.
     *
     * @return the timestamp
     */
    public DateTime getTimestamp() {
        return timestamp;
    }

    /**
     * Sets the timestamp.
     *
     * @param timestamp
     *            the new timestamp
     */
    public void setTimestamp(DateTime timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * Gets the series.
     *
     * @return the series
     */
    public ArrayList<PowerTime> getSeries() {
        return series;
    }

    /**
     * Gets the value at.
     *
     * @param timestamp
     *            the timestamp
     * @return the value at
     */
    @JsonIgnore
    public Double getValueAt(DateTime timestamp) {
        long offset = new Duration(this.timestamp, timestamp).getMillis();
        PowerTime oldVal = null;
        for (PowerTime pt : series) {
            if (pt.getOffset() > offset) {
                return (oldVal == null) ? 0 : oldVal.getValue();
            }
            oldVal = pt;
        }
        return (oldVal == null) ? 0 : oldVal.getValue();
    }

    /**
     * Adds the value at.
     *
     * @param timestamp
     *            the timestamp
     * @param value
     *            the value
     * @return the power time line
     */
    public PowerTimeLine addValueAt(final DateTime timestamp, final double value) {
        long offset = new Duration(this.timestamp, timestamp).getMillis();

        if (series.isEmpty()) {
            series.add(new PowerTime(offset, value));
        } else {
            PowerTime last = series.get(series.size() - 1);
            if (last.getOffset() < offset) {
                series.add(new PowerTime(offset, value));
            } else {
                for (int i = 0; i < series.size(); i++) {
                    PowerTime pt = series.get(i);
                    if (pt.getOffset() < offset) {
                        continue;
                    }
                    if (pt.getOffset() == offset) {
                        pt.setValue(value);
                    } else {
                        series.add(i, new PowerTime(offset, value));
                    }
                    break;
                }
            }
        }
        return this;
    }

    /**
     * Make this TL discrete, returning the TL;
     * As a side effect, removes values outside the start and end times.
     *
     * @param start
     *            the start
     * @param end
     *            the end
     * @param stepSize
     *            the step size
     * @return this
     */
    @JsonIgnore
    public PowerTimeLine discrete(final DateTime start, final DateTime end, final Duration stepSize) {
        final PowerTimeLine newTL = new PowerTimeLine();
        newTL.timestamp = this.timestamp;
        final ArrayList<PowerTime> steps = new ArrayList<PowerTime>();

        long offset = new Duration(timestamp, start).getMillis();
        Interval interval = stepSize.toIntervalFrom(start);
        while (interval.getEnd().isBefore(end) || interval.getEnd().isEqual(end)) {
            steps.add(new PowerTime(offset, 0));
            offset += interval.toDurationMillis();
            interval = stepSize.toIntervalFrom(timestamp.plusMillis((int) offset));
        }
        newTL.setSeries(steps);
        this.add(newTL).zeroBefore(start).zeroFrom(end);

        final Duration diff = new Duration(start, end);
        if (series.size() > (diff.getMillis() / stepSize.getMillis())) {
            int index = 0;
            long expectedOffset = new Duration(timestamp, start).getMillis() + stepSize.getMillis();
            while (index < series.size() - 1) {
                PowerTime pt = series.get(index);
                ArrayList<PowerTime> temp = new ArrayList<PowerTime>();

                int nextIndex = index + 1;
                PowerTime next = series.get(nextIndex);
                while (next.getOffset() < expectedOffset) {
                    temp.add(next);
                    series.remove(nextIndex);
                    if (nextIndex == series.size()) {
                        break;
                    }
                    next = series.get(nextIndex);
                }
                if (temp.size() > 0) {
                    temp.add(0, pt);
                    double integral = getIntegral(pt.getOffset(), pt.getOffset() + stepSize.getMillis(), temp);
                    series.set(index, new PowerTime(pt.getOffset(), integral / stepSize.getMillis()));
                }
                index++;
                expectedOffset += stepSize.getMillis();
            }
        }

        return this;
    }

    /**
     * Compact.
     *
     * @return the power time line
     */
    @JsonIgnore
    public PowerTimeLine compact() {
        if (series.size() == 0) {
            return this;
        }
        int index = 0;
        while (index < series.size()) {
            PowerTime pt = series.get(index);
            while ((index < series.size() - 1) && pt.getValue() == series.get(index + 1).getValue()) {
                series.remove(index + 1);
            }
            index++;
        }
        return this;
    }

    /**
     * Gets the discrete series.
     *
     * @param start
     *            the start
     * @param end
     *            the end
     * @param stepSize
     *            the step size
     * @return the discrete series
     */
    @JsonIgnore
    public ArrayList<PowerTime> getDiscreteSeries(DateTime start, DateTime end, Duration stepSize) {
        return this.clone().discrete(start, end, stepSize).getSeries();
    }

    /**
     * Sets the series.
     *
     * @param series
     *            the new series
     */
    public void setSeries(ArrayList<PowerTime> series) {
        this.series = series;
    }

    /**
     * Gets the average watts.
     *
     * @param fromDateTime
     *            the from date time
     * @param untilDateTime
     *            the until date time
     * @return the average watts
     */
    @JsonIgnore
    public double getAverageWatts(final DateTime fromDateTime, final DateTime untilDateTime) {
        final double difference = new Duration(fromDateTime, untilDateTime).getMillis() / 1000.0;
        if (difference > 0) {
            return getIntegral(fromDateTime, untilDateTime) / difference;
        } else {
            return 0;
        }
    }

    private double getIntegral(final long from, final long until, List<PowerTime> items) {

        if (items.size() == 0) {
            return 0;
        }

        double result = 0;
        long oldOffset = from;
        double val = 0;
        for (PowerTime pt : items) {

            if (pt.getOffset() < from) {
                val = pt.getValue();
                continue;
            }
            if (pt.getOffset() > until) {
                break;
            }
            result += val * (pt.getOffset() - oldOffset);
            val = pt.getValue();
            oldOffset = pt.getOffset();
        }
        result += val * (until - oldOffset);
        return result;

    }

    /**
     * Gets the integral.
     *
     * @param fromDateTime
     *            the from date time
     * @param untilDateTime
     *            the until date time
     * @return the integral
     */
    @JsonIgnore
    public double getIntegral(final DateTime fromDateTime, final DateTime untilDateTime) {
        final long from = new Duration(timestamp, fromDateTime).getMillis();
        final long until = new Duration(timestamp, untilDateTime).getMillis();
        return getIntegral(from, until, this.series) / 1000.0;
    }

    /**
     * Merge the two timelines (adding values), returning this one;.
     *
     * @param other
     *            the other
     * @return the power time line
     */
    public PowerTimeLine add(final PowerTimeLine other) {
        return operation(new addition(), other);
    }

    /**
     * Merge the two timelines (minus values), returning this one;.
     *
     * @param other
     *            the other
     * @return the power time line
     */
    public PowerTimeLine minus(final PowerTimeLine other) {
        return operation(new difference(), other);
    }

    /**
     * Merge the two timelines (multiplying values), returning this one;.
     *
     * @param other
     *            the other
     * @return the power time line
     */
    public PowerTimeLine multi(final PowerTimeLine other) {
        return operation(new multiply(), other);
    }

    /**
     * Merge the two timelines (maximum value), returning this one;.
     *
     * @param other
     *            the other
     * @return the power time line
     */
    public PowerTimeLine max(final PowerTimeLine other) {
        return operation(new maximum(), other);
    }

    /**
     * Merge the two timelines (minimum value), returning this one;.
     *
     * @param other
     *            the other
     * @return the power time line
     */
    public PowerTimeLine min(final PowerTimeLine other) {
        return operation(new minimum(), other);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    public PowerTimeLine clone() {
        final PowerTimeLine result = new PowerTimeLine();
        result.timestamp = this.timestamp;
        for (PowerTime pt : this.series) {
            result.series.add(new PowerTime(pt.getOffset(), pt.getValue()));
        }
        return result;
    }

    private PowerTimeLine operation(operator op, PowerTimeLine other) {
        if (other.series.size() == 0) {
            return this;
        }
        final long offset = new Duration(this.timestamp, other.timestamp).getMillis();

        final ArrayList<PowerTime> result = new ArrayList<PowerTime>();
        if (this.series.size() == 0) {
            for (PowerTime pt : other.series) {
                result.add(new PowerTime(pt.getOffset() + offset, op.doOp(0, pt.getValue())));
            }
            this.series = result;
            return this;
        }

        PowerTime val_mine = null;
        PowerTime val_other = null;
        int index_mine = 0;
        int index_other = 0;
        double value_mine = 0;
        double value_other = 0;

        while (index_mine < this.series.size() && index_other < other.series.size()) {
            val_mine = this.series.get(index_mine);
            val_other = other.series.get(index_other);
            switch ((int) Math.signum(val_other.getOffset() + offset - val_mine.getOffset())) {
            case 1:
                value_mine = val_mine.getValue();
                result.add(new PowerTime(val_mine.getOffset(), op.doOp(value_mine, value_other)));
                index_mine++;
                break;

            case 0:
                value_mine = val_mine.getValue();
                value_other = val_other.getValue();
                result.add(new PowerTime(val_mine.getOffset(), op.doOp(value_mine, value_other)));
                index_mine++;
                index_other++;
                break;
            case -1:
                value_other = val_other.getValue();
                result.add(new PowerTime(val_other.getOffset() + offset, op.doOp(value_mine, value_other)));
                index_other++;
                break;
            }
        }

        for (int p = index_mine; p < this.series.size(); p++) {
            PowerTime pt = this.series.get(p);
            result.add(new PowerTime(pt.getOffset(), op.doOp(pt.getValue(), value_other)));
        }
        for (int p = index_other; p < other.series.size(); p++) {
            PowerTime pt = other.series.get(p);
            result.add(new PowerTime(pt.getOffset() + offset, op.doOp(value_mine, pt.getValue())));
        }

        this.series = result;
        return this;
    }

    private interface operator {
        double doOp(final double left, final double right);
    }

    class addition implements operator {
        @Override
        public double doOp(double left, double right) {
            return left + right;
        }
    }

    class difference implements operator {

        @Override
        public double doOp(double left, double right) {
            return left - right;
        }

    }

    class multiply implements operator {

        @Override
        public double doOp(double left, double right) {
            return left * right;
        }

    }

    class maximum implements operator {

        @Override
        public double doOp(double left, double right) {
            return Math.max(left, right);
        }

    }

    class minimum implements operator {

        @Override
        public double doOp(double left, double right) {
            return Math.min(left, right);
        }

    }

    /**
     * Clear to zero between start (inclusive) and end (exclusive).
     *
     * @param start
     *            the start
     * @param end
     *            the end
     * @return this for chaining
     */
    public PowerTimeLine zeroBetween(final DateTime start, final DateTime end) {
        if (this.series.size() == 0) {
            return this;
        }
        final long startOffset = new Duration(this.timestamp, start).getMillis();
        final long endOffset = new Duration(this.timestamp, end).getMillis();
        // get current value at end
        final double endVal = getValueAt(end);
        // remove all values with index between start incl and end incl.
        final Iterator<PowerTime> iter = this.series.iterator();
        int index = 0;
        while (iter.hasNext()) {
            final PowerTime item = iter.next();
            final long offset = item.getOffset();
            if (offset < startOffset) {
                index++;
            } else if (offset >= startOffset && offset <= endOffset) {
                iter.remove();
            } else if (offset > endOffset) {
                break;
            }
        }
        // Add zero at start
        this.series.add(index, new PowerTime(startOffset, 0));
        // add current value at end
        this.series.add(index + 1, new PowerTime(endOffset, endVal));

        return this;
    }

    /**
     * Clear to zero starting at start (inclusive).
     *
     * @param start
     *            the start
     * @return this for chaining
     */
    public PowerTimeLine zeroFrom(final DateTime start) {
        if (this.series.size() == 0) {
            return this;
        }
        final long startOffset = new Duration(this.timestamp, start).getMillis();
        // remove all values with index after start incl
        final Iterator<PowerTime> iter = this.series.iterator();
        int index = 0;
        while (iter.hasNext()) {
            final PowerTime item = iter.next();
            final long offset = item.getOffset();
            if (offset < startOffset) {
                index++;
            } else if (offset >= startOffset) {
                iter.remove();
            }
        }
        // Add zero at start
        this.series.add(index, new PowerTime(startOffset, 0));
        return this;
    }

    /**
     * Clear to zero before start (exclusive).
     *
     * @param start
     *            the start
     * @return this for chaining
     */
    public PowerTimeLine zeroBefore(final DateTime start) {
        if (this.series.size() == 0) {
            return this;
        }
        final long startOffset = new Duration(this.timestamp, start).getMillis();
        final double startVal = getValueAt(start);
        // remove all values with index before start incl
        final Iterator<PowerTime> iter = this.series.iterator();
        while (iter.hasNext()) {
            final PowerTime item = iter.next();
            final long offset = item.getOffset();
            if (offset <= startOffset) {
                iter.remove();
            } else {
                break;
            }
        }
        // Add value at start
        this.series.add(0, new PowerTime(startOffset, startVal));
        return this;
    }

    /**
     * Merge other timeline into this this timeline, replacing all between start
     * and end timestamps.
     *
     * @param other
     *            the other
     * @param start
     *            the start
     * @param end
     *            the end
     * @return this for chaining
     */
    public PowerTimeLine merge(final PowerTimeLine other, final DateTime start, final DateTime end) {
        final PowerTimeLine copy = other.clone().zeroBefore(start).zeroFrom(end);
        this.zeroBetween(start, end).add(copy);
        return this;
    }

    /**
     * With timestamp.
     *
     * @param timestamp
     *            the timestamp
     * @return the power time line
     */
    public PowerTimeLine withTimestamp(DateTime timestamp) {
        final DateTime oldTimestamp = this.timestamp;
        this.timestamp = timestamp;
        if (oldTimestamp != null && !oldTimestamp.equals(timestamp) && series.size() > 0) {
            long diff = new Duration(timestamp, oldTimestamp).getMillis();
            for (PowerTime pt : series) {
                pt.setOffset(pt.getOffset() + diff);
            }
        }
        return this;
    }

    @Override
    public String toString() {
        return JOM.getInstance().valueToTree(this).toString();
    }
}