Source: animation/Interval.js

import Time from "./Time";


/**
 * @summary アニメーション時刻の区間
 *
 * @classdesc
 * <p>アニメーション時刻の区間を表現するクラスである。</p>
 * <p>このクラスのインスタンスはイミュータブルである。</p>
 *
 * @memberof mapray.animation
 */
class Interval
{

    /**
     * @desc
     * <p>下限 lower と上限 upper の時刻区間を生成する。</p>
     * <p>端点である lower と upper が区間に含まれるかどうかは l_open と u_open により指定する。</p>
     *
     * <pre>
     *  interval       | l_open  u_open
     * ----------------+----------------
     *  [lower, upper] | false   false
     *  [lower, upper) | false   true
     *  (lower, upper] | true    false
     *  (lower, upper) | true    true
     * </pre>
     *
     * @param {mapray.animation.Time} lower  区間の下限時刻
     * @param {mapray.animation.Time} upper  区間の上限時刻
     * @param {boolean}      [l_open=false]  lower が区間にが含まれるとき false, 含まれないとき true
     * @param {boolean}      [u_open=false]  upper が区間にが含まれるとき false, 含まれないとき true
     */
    constructor( lower, upper, l_open, u_open )
    {
        this._lower  = lower;
        this._upper  = upper;
        this._l_open = (l_open === undefined) ? false : l_open;
        this._u_open = (u_open === undefined) ? false : u_open;
    }


    /**
     * @summary 全時刻区間
     *
     * @type {mapray.animation.Interval}
     * @readonly
     */
    static get
    UNIVERSAL() { return INTERVAL_UNIVERSAL; }


    /**
     * @summary 下限時刻
     *
     * @type {mapray.animation.Time}
     * @readonly
     */
    get
    lower() { return this._lower; }


    /**
     * @summary 上限時刻
     *
     * @type {mapray.animation.Time}
     * @readonly
     */
    get
    upper() { return this._upper; }


    /**
     * @summary 下限時刻は除外されるか?
     *
     * @type {boolean}
     * @readonly
     */
    get
    l_open() { return this._l_open; }


    /**
     * @summary 上限時刻は除外されるか?
     *
     * @type {boolean}
     * @readonly
     */
    get
    u_open() { return this._u_open; }


    /**
     * @summary 空時刻区間か?
     *
     * @desc
     * <p>this が空の時刻区間かどうかを返す。</p>
     * <p>空時刻区間の場合、区間内に 1 つも時刻が存在しない。</p>
     *
     * @return {boolean}  空時刻区間のとき true, それ以外のとき false
     */
    isEmpty()
    {
        let lower = this._lower;
        let upper = this._upper;

        return upper.lessThan( lower ) || upper.equals( lower ) && (this._l_open || this._u_open);
    }


    /**
     * @summary 単一時刻区間か?
     *
     * @desc
     * <p>this が単一時刻の時刻区間かどうかを返す。</p>
     * <p>単一時刻区間の場合、区間内にただ 1 つの時刻が存在する。</p>
     * <p>単一時刻区間であるなら lower == upper であり、逆は必ずしも成り立たない。</p>
     *
     * @return {boolean}  単一時刻区間のとき true, それ以外のとき false
     */
    isSingle()
    {
        return this._lower.equals( this._upper ) && !(this._l_open || this._u_open);
    }


    /**
     * @summary 通常時刻区間か?
     *
     * @desc
     * <p>this が通常の時刻区間かどうかを返す。</p>
     * <p>通常時刻区間の場合、区間内に無限個の時刻が存在する。</p>
     * <p>通常時刻区間であるなら lower < upper であり、逆も成り立つ。</p>
     *
     * @return {boolean}  通常時刻区間のとき true, それ以外のとき false
     */
    isProper()
    {
        return this._lower.lessThan( this._upper );
    }


    /**
     * @summary 先行しているか?
     *
     * @desc
     * <p>this のすべての時刻が rhs のすべての時刻より先行しているときに true, それ以外のときは false を返す。</p>
     * <p>this または rhs のどちらか、または両方が空時刻区間のときは true を返す。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {boolean}  this が rhs に先行しているとき true, それ以外のとき false
     */
    precedes( rhs )
    {
        if ( this.isEmpty() || rhs.isEmpty() ) {
            // this または rhs のどちらか、または両方が空時刻区間のときの仕様
            return true;
        }
        else {
            let ut1 = this._upper;
            let uo1 = this._u_open;
            let lt2 = rhs._lower;
            let lo2 = rhs._l_open;
            return ut1.lessThan( lt2 ) || ut1.equals( lt2 ) && (uo1 || lo2);
        }
    }


    /**
     * @summary 包含しているか?
     *
     * @desc
     * <p>rhs のすべての時刻が this に含まれるとき true, それ以外のときは false を返す。</p>
     * <p>rhs が空時刻区間のときは true を返す。</p>
     * <p>これは rhs ⊆ this と等価である。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {boolean}  this が rhs を包含しているとき true, それ以外のとき false
     */
    includes( rhs )
    {
        if ( rhs.isEmpty() ) {
            // rhs が空時刻区間のときの仕様
            return true;
        }
        else {
            let lt1 = this._lower;
            let lt2 = rhs._lower;
            let lo1 = this._l_open;
            let lo2 = rhs._l_open;
            let inc_l = lt1.lessThan( lt2 ) || lt1.equals( lt2 ) && (!lo1 || lo2);

            let ut1 = this._upper;
            let ut2 = rhs._upper;
            let uo1 = this._u_open;
            let uo2 = rhs._u_open;
            let inc_u = ut2.lessThan( ut1 ) || ut2.equals( ut1 ) && (uo2 || !uo1);

            return inc_l && inc_u;
        }
    }


    /**
     * @summary 時刻を包含しているか?
     *
     * @desc
     * <p>rhs の時刻が this に含まれるとき true, それ以外のときは false を返す。</p>
     * <p>このメソッドは this.includes( new Interval( rhs, rhs ) ) と同等である。</p>
     *
     * @param {mapray.animation.Time} rhs  時刻
     *
     * @return {boolean}  this が rhs を包含しているとき true, それ以外のとき false
     */
    includesTime( rhs )
    {
        let lower = this._lower;
        let inc_l = lower.lessThan( rhs ) || lower.equals( rhs ) && !this._l_open;

        let upper = this._upper;
        let inc_u = rhs.lessThan( upper ) || rhs.equals( upper ) && !this._u_open;

        return inc_l && inc_u;
    }


    /**
     * @summary 共通時刻区間は存在するか?
     *
     * @desc
     * <p>!this.getIntersection( rhs ).isEmpty() と同じである。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {boolean}  共通時刻区間
     *
     * @see {@link mapray.animation.Interval#getIntersection}
     */
    hasIntersection( rhs )
    {
        // todo: オブジェクトを生成しないように最適化
        return !this.getIntersection( rhs ).isEmpty();
    }


    /**
     * @summary 先行時刻区間を取得
     *
     * @desc
     * <p>this のすべての時刻に対して、先の時刻となるすべての時刻を含む先行時刻区間を返す。</p>
     * <p>this が空時刻区間のときは全時刻区間を返し、this
     *    に表現可能な最初の時刻が含まれるときは空時刻区間を返す。</p>
     * <p>this.getPrecedings().precedes( this ) は常に true を返す。</p>
     *
     * @return {mapray.animation.Interval}  先行時刻区間
     */
    getPrecedings()
    {
        if ( this.isEmpty() ) {
            // 空時刻区間のときは全時刻区間を返す仕様
            return INTERVAL_UNIVERSAL;
        }
        else {
            return new Interval( Time.MIN_TIME, this._lower, false, !this._l_open );
        }
    }


    /**
     * @summary 後続時刻区間を取得
     *
     * @desc
     * <p>this のすべての時刻に対して、後の時刻となるすべての時刻を含む後続時刻区間を返す。</p>
     * <p>this が空時刻区間のときは全時刻区間を返し、this
     *    に表現可能な最後の時刻が含まれるときは空時刻区間を返す。</p>
     * <p>this.precedes( this.getFollowings() ) は常に true を返す。</p>
     *
     * @return {mapray.animation.Interval}  後続時刻区間
     */
    getFollowings()
    {
        if ( this.isEmpty() ) {
            // 空時刻区間のときは全時刻区間を返す仕様
            return INTERVAL_UNIVERSAL;
        }
        else {
            return new Interval( this._upper, Time.MAX_TIME, !this._u_open, false );
        }
    }


    /**
     * @summary 共通時刻区間を取得
     *
     * @desc
     * <p>this と rhs の共通時刻区間 (this ∩ rhs) を返す。</p>
     * <p>this と rhs に共通の時刻が存在しなければ空時刻区間を返す。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {mapray.animation.Interval}  共通時刻区間
     *
     * @see {@link mapray.animation.Interval#hasIntersection}
     */
    getIntersection( rhs )
    {
        // B = Lb ∩ Ub とするとき
        // A ∩ B = A ∩ Lb ∩ Ub

        // A ∩ Lb
        let cross = this._getIntersectionByLower( rhs._lower, rhs._l_open );
        // (A ∩ Lb) ∩ Ub
        return cross._getIntersectionByUpper( rhs._upper, rhs._u_open );
    }


    /**
     * @summary 合併時刻区間を取得
     *
     * @desc
     * <p>this と rhs を合併した時刻集合 (this ∪ rhs) を時刻区間の配列として返す。</p>
     * <p>0 から 2 個の時刻区間を含む配列を返す。配列の要素に空時刻区間は含まれない。</p>
     * <p>2 要素の配列 v が返されたとき、v[0] と v[1] の間に時刻が存在し、さらに
     *    v[0].precedes( v[1] ) は true となる。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {mapray.animation.Interval[]}  合併時刻区間
     */
    getUnion( rhs )
    {
        if ( this.isEmpty() ) {
            return rhs.isEmpty() ? [] : [rhs];
        }
        else if ( rhs.isEmpty() ) {
            // Assert: !this.isEmpty() && rhs.isEmpty()
            return [this];
        }
        // Assert: !this.isEmpty() && !rhs.isEmpty()

        let lt1 = this._lower;
        let ut1 = this._upper;
        let lo1 = this._l_open;
        let uo1 = this._u_open;

        let lt2 = rhs._lower;
        let ut2 = rhs._upper;
        let lo2 = rhs._l_open;
        let uo2 = rhs._u_open;

        if ( ut1.lessThan( lt2 ) || (ut1.equals( lt2 ) && uo1 && lo2) ) {
            // Assert: this と rhs は離れている、かつ this が先行
            return [this, rhs];
        }
        else if ( ut2.lessThan( lt1 ) || (lt1.equals( ut2 ) && lo1 && uo2) ) {
            // Assert: this と rhs は離れている、かつ rhs が先行
            return [rhs, this];
        }
        // Assert: this と rhs は交差または隣接している (単一の時刻区間に合併できる)

        let [lower, l_open] =
            (lt1.lessThan( lt2 ) || lt1.equals( lt2 ) && lo2) ?
            [lt1, lo1] : [lt2, lo2];

        let [upper, u_open] =
            (ut2.lessThan( ut1 ) || ut2.equals( ut1 ) && uo2) ?
            [ut1, uo1] : [ut2, uo2];

        return [new Interval( lower, upper, l_open, u_open )];
    }


    /**
     * @summary 時刻区間の差を取得
     *
     * @desc
     * <p>this から rhs を差し引いた時刻集合 (this - rhs) を時刻区間の配列として返す。</p>
     * <p>0 から 2 個の時刻区間を含む配列を返す。配列の要素に空時刻区間は含まれない。</p>
     * <p>2 要素の配列 v が返されたとき、v[0] と v[1] の間に時刻が存在し、さらに
     *    v[0].precedes( v[1] ) は true となる。</p>
     *
     * @param {mapray.animation.Interval} rhs  時刻区間
     *
     * @return {mapray.animation.Interval[]}  時刻区間の差
     */
    getDifference( rhs )
    {
        // B = Lb ∩ Ub とするとき
        // A - B = A ∩ ~B
        //       = (A ∩ ~Lb) ∪ (A ∩ ~Ub)

        // A ∩ ~Lb
        let i1 = this._getIntersectionByUpper( rhs._lower, !rhs._l_open );
        // A ∩ ~Ub
        let i2 = this._getIntersectionByLower( rhs._upper, !rhs._u_open );
        // (A ∩ ~Lb) ∪ (A ∩ ~Ub)
        return i1.getUnion( i2 );
    }


    /**
     * @summary 補時刻区間を取得
     *
     * @desc
     * <p>全時刻区間 から this を差し引いた時刻集合を時刻区間の配列として返す。</p>
     * <p>0 から 2 個の時刻区間を含む配列を返す。配列の要素に空時刻区間は含まれない。</p>
     * <p>2 要素の配列 v が返されたとき、v[0] と v[1] の間に時刻が存在し、さらに
     *    v[0].precedes( v[1] ) は true となる。</p>
     *
     * @return {mapray.animation.Interval[]}  補時刻区間
     */
    getComplement()
    {
        return INTERVAL_UNIVERSAL.getDifference( this );
    }


    /**
     * @summary 下限時刻区間との共通時刻区間を取得
     *
     * @desc
     * <p>this ∩ Lower(bound, open) → Interval<p>
     *
     * @param {mapray.animation.Time} bound
     * @param {boolean}               open
     *
     * @return {mapray.animation.Interval}  共通時刻区間
     *
     * @private
     */
    _getIntersectionByLower( bound, open )
    {
        if ( bound.lessThan( this._lower ) || bound.equals( this._lower ) && this._l_open ) {
            return this;
        }
        else {
            return new Interval( bound, this._upper, open, this._u_open );
        }
    }


    /**
     * @summary 上限時刻区間との共通時刻区間を取得
     *
     * @desc
     * <p>this ∩ Upper(bound, open) → Interval<p>
     *
     * @param {mapray.animation.Time} bound
     * @param {boolean}               open
     *
     * @return {mapray.animation.Interval}  共通時刻区間
     *
     * @private
     */
    _getIntersectionByUpper( bound, open )
    {
        if ( this._upper.lessThan( bound ) || this._upper.equals( bound ) && this._u_open ) {
            return this;
        }
        else {
            return new Interval( this._lower, bound, this._l_open, open );
        }
    }

}


const INTERVAL_UNIVERSAL = new Interval( Time.MIN_TIME, Time.MAX_TIME );


export default Interval;