<commit_before>
/**
 *
 * @param {number} start
 * @param {number} end
 * @param {?number} step
 * @return {Range<number>}
 */
function range(start, end, step = null) {
  if (!Number.isSafeInteger(start)) {
    throw new TypeError(
      `The starting value must be a safe integer, ${start} was provided`
    )
  }
  if ((typeof end !== 'number') || (Math.floor(end) !== end)) {
    throw new TypeError(
      `The ending value must be an integer or infinity, ${end} was provided`
    )
  }
  if (
    Number.isFinite(end) &&
    ((end < Number.MIN_SAFE_INTEGER) || (end > Number.MAX_SAFE_INTEGER))
  ) {
    throw new RangeError(
      `The ending value must be within the [Number.MIN_SAFE_INTEGER, ` +
      `Number.MAX_SAFE_INTEGER] range or infinite, ${end} was provided`
    )
  }

  if (step === null) {
    step = end >= start ? 1 : -1
  } else {
    if (!Number.isSafeInteger(step)) {
      throw new TypeError(
        `The step must be either a null or a safe integer, ${step} was ` +
        `provided`
      )
    }
    if (!step) {
      throw new RangeError(
        `The step cannot be 0`
      )
    }
  }

  /**
   * @template E
   */
  class Range {
    /**
     * @template P
     * @param {?function(E, number, Range): boolean} filter
     * @param {?function(P, number, Range): *} map
     * @param {?function(E, number, Range): boolean} stop
     * @param {?Range<P>} parentRange
     */
    constructor(filter, map, stop, parentRange) {
      /**
       * @type {?function(E, number, Range): boolean}
       */
      this._filter = filter

      /**
       * @type {?function(*, number, Range): E}
       */
      this._map = map

      /**
       * @type {E}
       */
      this._currentValue = start

      /**
       * @type {number}
       */
      this._index = 0

      /**
       * @type {?function(E, number, Range): boolean}
       */
      this._stop = stop

      /**
       * @type {Range}
       */
      this._parentRange = parentRange

      /**
       * @type {{next: function(): {done: boolean, value: *}}}
       */
      this._iterator = parentRange || createIterator(this)

      /**
       * @type {?number}
       */
      this._length = null

      /**
       * @type {[{done: boolean, value: E}, number][]}
       */
      this._preGeneratedValues = []

      /**
       * @type {[{done: boolean, value: E}, number][]}
       */
      this._originalValues = null

      /**
       * @type {number}
       */
      this._generatedValuesCount = 0

      /**
       * @type {boolean}
       */
      this._isFinite = Number.isFinite(end)
    }

    /**
     * @return {number}
     */
    get length() {
      if (this._length !== null) {
        return this._length
      }

      if (!this._isFinite) {
        let currentRange = this
        let isFilteredRange = !!this._filter
        while (!isFilteredRange && currentRange._parentRange) {
          currentRange = currentRange._parentRange
          isFilteredRange = !!currentRange._filter
        }
        if (isFilteredRange) {
          console.warn(
            'The length of filtered infinite ranges is always assumed to be ' +
            'Infinity, even if that is not the case, because there is no ' +
            'way to reliably determine if an infinitely-generated filtered ' +
            'range would end up having a finite number of elements in a ' +
            'finite time, given the fact that the filter is a callback.'
          )
        }

        this._length = Number.POSITIVE_INFINITY
      } else if (!this._filter && !this._stop) {
        if (this._parentRange) {
          this._length = this._parentRange.length
        } else {
          this._length = Math.floor((end - start) / step) +
              (((end - start) % step) ? 1 : 0)
        }
      } else {
        let nextIteration = getNextValue(this, this._index)
        while (!nextIteration[0].done) {
          this._preGeneratedValues.push(nextIteration)
          nextIteration = getNextValue(this, nextIteration[1])
        }
        this._length =
            this._generatedValuesCount + this._preGeneratedValues.length
      }

      return this._length
    }

    /**
     * @return {{done: boolean, value: E}}
     */
    next() {
      let iteration, nextIndex
      if (this._preGeneratedValues.length) {
        [iteration, nextIndex] = this._preGeneratedValues.shift()
      } else {
        [iteration, nextIndex] = getNextValue(this, this._index)
      }

      this._index = nextIndex
      this._currentValue = iteration.value
      this._generatedValuesCount++

      return iteration
    }

    /**
     * @return {Range<[number, E]>}
     */
    enumerate() {
      return this.map((value, index) => [index, value])
    }

    /**
     * @return {Range<E>}
     * @throws {Error} Thrown if this range is infinite.
     */
    reverse() {
      if (!this._isFinite) {
        throw new Error('Infinite ranges cannot be reversed')
      }

      if (!this._parentRange) {
        let newEnd = this._currentValue
        if (!this._index) {
          newEnd -= step
        }
        return range(end - step, newEnd, -step)
      }

      let values = [...this.clone()]
      let reversedRange = new Range(null, null, null, range(0, 0))
      reversedRange._currentValue = end
      reversedRange._preGeneratedValues = values.reverse().map(
        (value, index) => [{ done: false, value }, index + 1]
      )
      reversedRange._originalValues = reversedRange._preGeneratedValues.slice()
      reversedRange._length = values.length
      return reversedRange
    }

    /**
     * @template R
     * @param {function(E, number, Range): R} mapCallback
     * @return {Range<R>}
     */
    map(mapCallback) {
      return new Range(null, mapCallback, null, this)
    }

    /**
     * @param {function(E, number, Range): boolean} filterCallback
     * @return {Range<E>}
     */
    filter(filterCallback) {
      return new Range(filterCallback, null, null, this)
    }

    /**
     * @param {number} count
     * @return {Range<E>}
     */
    take(count) {
      if (!Number.isSafeInteger(count) || (count < 0)) {
        throw new TypeError(
          `The count limit must be a non-negative safe integer, ${count} ` +
          `was provided`
        )
      }

      let limitedRange = this.takeWhile((_, index) => index <= count)
      limitedRange._isFinite = true
      return limitedRange
    }

    /**
     * @param {function(E, number, Range): boolean} precondition
     * @return {Range<E>}
     */
    takeWhile(precondition) {
      return new Range(null, null, precondition, this)
    }

    /**
     * @template I
     * @template R
     * @param {I} initialValue
     * @param {function((I|R), E, number, Range): R} operation
     * @return {R}
     */
    reduce(initialValue, operation) {
      if (!this._isFinite) {
        throw new Error(
          'The reduce() method cannot be applied to infinite sequences'
        )
      }

      let result = initialValue
      for (let value of this) {
        result = operation(result, value, this._index, this)
      }
      return result
    }

    reset() {
      if (this._parentRange) {
        this._parentRange.reset()
      } else {
        this._currentValue = start
        this._iterator = createIterator(this)
      }
      this._index = 0
      this._generatedValuesCount = 0;
      if (this._originalValues) {
        this._preGeneratedValues = this._originalValues.slice();
      } else {
        this._preGeneratedValues = [];
      }
    }

    /**
     * @return {Range<E>}
     */
    clone() {
      let parentClone = this._parentRange ? this._parentRange.clone() : null
      let clone = new Range(this._filter, this._map, this._stop, parentClone)
      clone._currentValue = this._currentValue
      clone._index = this._index
      if (!this._parentRange && this._index) {
        clone._iterator.next() // shift the iterator after the current value
      }
      clone._length = this._length
      clone._preGeneratedValues = this._preGeneratedValues.slice()
      clone._generatedValuesCount = this._generatedValuesCount
      return clone
    }

    /**
     * Exports the remaining values in this sequence into an array (this
     * sequence will be iterated into its end).
     *
     * Infinite sequences cannot be turned into an array and cause this method
     * to throw an array. This method is therefore a safer way of turning
     * sequences into arrays as compared to {@code [...sequence]}, because
     * using the spread operator (...) on an infinite sequence would enter the
     * code into and infinite operation and effectively freeze the application.
     *
     * @return {E[]} The generated array of values remaining in this sequence.
     * @throws {Error} Thrown if this sequence is infinite.
     */
    toArray() {
      if (!this._isFinite) {
        throw new Error(
          'The infinite sequences cannot be exported to an array'
        )
      }

      return [...this]
    }

    /**
     * Returns the iterator of this {@linkcode Range} instance, which is the
     * instance itself.
     *
     * This method is used to enable compatibility with the for-of cycles.
     *
     * @return {Range<E>} This instance.
     */
    [Symbol.iterator]() {
      return this
    }
  }

  /**
   * Generates the next iteration (the value and a flag whether the end of the
   * sequence has been reached) and the index of the iteration in the sequence
   * of values represented by the provided {@linkcode Range} instance.
   *
   * @template E
   * @param {Range<E>} range The {@linkcode Range} instance representing the
   *        sequence of values for which the next value should be generated.
   * @param {number} currentIndex The index of the previously generated value
   *        in the sequence.
   * @return {[{done: boolean, value: E}, number]} A tuple representing the
   *         next iteration step and the index of the value in the sequence.
   */
  function getNextValue(range, currentIndex) {
    let iteration = range._iterator.next()
    let nextIndex
    if (currentIndex < Number.MAX_SAFE_INTEGER) {
      nextIndex = currentIndex + 1
    } else {
      nextIndex = Number.POSITIVE_INFINITY
    }

    if (iteration.done) {
      return [iteration, nextIndex]
    }

    if (range._filter && !range._filter(iteration.value, nextIndex, range)) {
      return getNextValue(range, currentIndex)
    }

    if (range._map) {
      iteration.value = range._map(iteration.value, nextIndex, range)
    }

    if (range._stop && !range._stop(iteration.value, nextIndex, range)) {
      return [{done: true, value: undefined}, nextIndex]
    }

    return [iteration, nextIndex]
  }

  /**
   * Creates an iterator for generating a sequence of numeric values
   * represented by the provided {@linkcode Range} instance.
   *
   * @param {Range<number>} rangeInstance The {@linkcode Range} instance for
   *        generating a sequence of numbers for which the iterator should be
   *        created.
   * @return {{next: function(): {done: boolean, value: number}}} The created
   *         iterator that will generate the numeric values for the provided
   *         {@linkcode Range} instance.
   */
  function createIterator(rangeInstance) {
    return (function * () {
      while (!isEnded()) {
        yield rangeInstance._currentValue

        let currentSafeLimit = Number.MAX_SAFE_INTEGER - Math.abs(step)
        if (currentSafeLimit < Math.abs(rangeInstance._currentValue)) {
          rangeInstance._currentValue =
              Math.sign(rangeInstance._currentValue) * Infinity
        } else {
          rangeInstance._currentValue += step
        }
      }
    })()

    function isEnded() {
      if (!Number.isFinite(end)) {
        return false
      }

      return (step > 0) ?
          (rangeInstance._currentValue >= end) :
          (rangeInstance._currentValue <= end)
    }
  }

  return new Range(null, null, null, null)
}

if ((typeof module !== 'undefined') && (typeof exports !== 'undefined')) {
  exports.default = range
  exports.range = range
}
<commit_msg>documentation update<commit_after>
/**
 *
 * @param {number} start
 * @param {number} end
 * @param {?number} step
 * @return {Range<number>}
 */
function range(start, end, step = null) {
  if (!Number.isSafeInteger(start)) {
    throw new TypeError(
      `The starting value must be a safe integer, ${start} was provided`
    )
  }
  if ((typeof end !== 'number') || (Math.floor(end) !== end)) {
    throw new TypeError(
      `The ending value must be an integer or infinity, ${end} was provided`
    )
  }
  if (
    Number.isFinite(end) &&
    ((end < Number.MIN_SAFE_INTEGER) || (end > Number.MAX_SAFE_INTEGER))
  ) {
    throw new RangeError(
      `The ending value must be within the [Number.MIN_SAFE_INTEGER, ` +
      `Number.MAX_SAFE_INTEGER] range or infinite, ${end} was provided`
    )
  }

  if (step === null) {
    step = end >= start ? 1 : -1
  } else {
    if (!Number.isSafeInteger(step)) {
      throw new TypeError(
        `The step must be either a null or a safe integer, ${step} was ` +
        `provided`
      )
    }
    if (!step) {
      throw new RangeError(
        `The step cannot be 0`
      )
    }
  }

  /**
   * @template E
   */
  class Range {
    /**
     * @template P
     * @param {?function(E, number, Range): boolean} filter
     * @param {?function(P, number, Range): *} map
     * @param {?function(E, number, Range): boolean} stop
     * @param {?Range<P>} parentRange
     */
    constructor(filter, map, stop, parentRange) {
      /**
       * @type {?function(E, number, Range): boolean}
       */
      this._filter = filter

      /**
       * @type {?function(*, number, Range): E}
       */
      this._map = map

      /**
       * @type {E}
       */
      this._currentValue = start

      /**
       * @type {number}
       */
      this._index = 0

      /**
       * @type {?function(E, number, Range): boolean}
       */
      this._stop = stop

      /**
       * @type {Range}
       */
      this._parentRange = parentRange

      /**
       * @type {{next: function(): {done: boolean, value: *}}}
       */
      this._iterator = parentRange || createIterator(this)

      /**
       * @type {?number}
       */
      this._length = null

      /**
       * @type {[{done: boolean, value: E}, number][]}
       */
      this._preGeneratedValues = []

      /**
       * @type {[{done: boolean, value: E}, number][]}
       */
      this._originalValues = null

      /**
       * @type {number}
       */
      this._generatedValuesCount = 0

      /**
       * @type {boolean}
       */
      this._isFinite = Number.isFinite(end)
    }

    /**
     * @return {number}
     */
    get length() {
      if (this._length !== null) {
        return this._length
      }

      if (!this._isFinite) {
        let currentRange = this
        let isFilteredRange = !!this._filter
        while (!isFilteredRange && currentRange._parentRange) {
          currentRange = currentRange._parentRange
          isFilteredRange = !!currentRange._filter
        }
        if (isFilteredRange) {
          console.warn(
            'The length of filtered infinite ranges is always assumed to be ' +
            'Infinity, even if that is not the case, because there is no ' +
            'way to reliably determine if an infinitely-generated filtered ' +
            'range would end up having a finite number of elements in a ' +
            'finite time, given the fact that the filter is a callback.'
          )
        }

        this._length = Number.POSITIVE_INFINITY
      } else if (!this._filter && !this._stop) {
        if (this._parentRange) {
          this._length = this._parentRange.length
        } else {
          this._length = Math.floor((end - start) / step) +
              (((end - start) % step) ? 1 : 0)
        }
      } else {
        let nextIteration = getNextValue(this, this._index)
        while (!nextIteration[0].done) {
          this._preGeneratedValues.push(nextIteration)
          nextIteration = getNextValue(this, nextIteration[1])
        }
        this._length =
            this._generatedValuesCount + this._preGeneratedValues.length
      }

      return this._length
    }

    /**
     * @return {{done: boolean, value: E}}
     */
    next() {
      let iteration, nextIndex
      if (this._preGeneratedValues.length) {
        [iteration, nextIndex] = this._preGeneratedValues.shift()
      } else {
        [iteration, nextIndex] = getNextValue(this, this._index)
      }

      this._index = nextIndex
      this._currentValue = iteration.value
      this._generatedValuesCount++

      return iteration
    }

    /**
     * @return {Range<[number, E]>}
     */
    enumerate() {
      return this.map((value, index) => [index, value])
    }

    /**
     * @return {Range<E>}
     * @throws {Error} Thrown if this range is infinite.
     */
    reverse() {
      if (!this._isFinite) {
        throw new Error('Infinite ranges cannot be reversed')
      }

      if (!this._parentRange) {
        let newEnd = this._currentValue
        if (!this._index) {
          newEnd -= step
        }
        return range(end - step, newEnd, -step)
      }

      let values = [...this.clone()]
      let reversedRange = new Range(null, null, null, range(0, 0))
      reversedRange._currentValue = end
      reversedRange._preGeneratedValues = values.reverse().map(
        (value, index) => [{ done: false, value }, index + 1]
      )
      reversedRange._originalValues = reversedRange._preGeneratedValues.slice()
      reversedRange._length = values.length
      return reversedRange
    }

    /**
     * @template R
     * @param {function(E, number, Range): R} mapCallback
     * @return {Range<R>}
     */
    map(mapCallback) {
      return new Range(null, mapCallback, null, this)
    }

    /**
     * @param {function(E, number, Range): boolean} filterCallback
     * @return {Range<E>}
     */
    filter(filterCallback) {
      return new Range(filterCallback, null, null, this)
    }

    /**
     * Returns a sequence that produces up to the specified number of values
     * from this sequence.
     *
     * The returned sequence will be marked as finite, even if this sequence is
     * infinite.
     *
     * @param {number} count The maximum number of elements the returned
     *        sequence should be able to produce. Must be a safe non-negative
     *        integer.
     * @return {Range<E>} A sequence that will produce only up to the specified
     *         number of elements before terminating.
     */
    take(count) {
      if (!Number.isSafeInteger(count) || (count < 0)) {
        throw new TypeError(
          `The count limit must be a non-negative safe integer, ${count} ` +
          `was provided`
        )
      }

      let limitedRange = this.takeWhile((_, index) => index <= count)
      limitedRange._isFinite = true
      return limitedRange
    }

    /**
     * Returns a sequence that produces values from this sequence while the
     * provided precondition is satisfied.
     *
     * @param {function(E, number, Range): boolean} precondition The
     *        precondition that will be used to test a value to determine
     *        whether to include it in the returned sequence. The first
     *        argument will be the candidate value, the second argument will be
     *        its index in the sequence, and the third will be the returned
     *        sequence itself.
     * @return {Range<E>} A sequence that produces the values from this
     *         sequence while the provided precondition holds true.
     */
    takeWhile(precondition) {
      return new Range(null, null, precondition, this)
    }

    /**
     * Reduces the elements of this sequence using the provided operation into
     * a single value. This method works in the same way as the
     * {@linkcode Array.prototype.reduce} method.
     *
     * The method cannot be used on infinite sequences and will throw an error.
     *
     * @template I
     * @template R
     * @param {I} initialValue The initial value for the first argument of the
     *        provided operation function.
     * @param {function((I|R), E, number, Range): R} operation The operation to
     *        apply on the elements of this sequence in order to produce the
     *        resulting value. The first argument will either be the provided
     *        initial value or a mid-result value, the second argument will be
     *        the currently processed element of the sequence, the third will
     *        be the elements index, and the last argument is this sequence
     *        itself.
     * @return {R} The resulting computed value.
     * @throws {Error} Thrown if called on an infinite sequence.
     */
    reduce(initialValue, operation) {
      if (!this._isFinite) {
        throw new Error(
          'The reduce() method cannot be applied to infinite sequences'
        )
      }

      let result = initialValue
      for (let value of this) {
        result = operation(result, value, this._index, this)
      }
      return result
    }

    /**
     * Resets this sequence to its start, allowing it to generate the same
     * values again.
     */
    reset() {
      if (this._parentRange) {
        this._parentRange.reset()
      } else {
        this._currentValue = start
        this._iterator = createIterator(this)
      }
      this._index = 0
      this._generatedValuesCount = 0;
      if (this._originalValues) {
        this._preGeneratedValues = this._originalValues.slice();
      } else {
        this._preGeneratedValues = [];
      }
    }

    /**
     * Creates a clone of of this sequence. The clone will generate the same
     * values as this sequence from now on. If this sequence has already been
     * used to generate values, these values will not appear in the clone.
     *
     * @return {Range<E>} A clone of this sequence.
     */
    clone() {
      let parentClone = this._parentRange ? this._parentRange.clone() : null
      let clone = new Range(this._filter, this._map, this._stop, parentClone)
      clone._currentValue = this._currentValue
      clone._index = this._index
      if (!this._parentRange && this._index) {
        clone._iterator.next() // shift the iterator after the current value
      }
      clone._length = this._length
      clone._preGeneratedValues = this._preGeneratedValues.slice()
      clone._generatedValuesCount = this._generatedValuesCount
      return clone
    }

    /**
     * Exports the remaining values in this sequence into an array (this
     * sequence will be iterated into its end).
     *
     * Infinite sequences cannot be turned into an array and cause this method
     * to throw an error. This method is therefore a safer way of turning
     * sequences into arrays as compared to {@code [...sequence]}, because
     * using the spread operator (...) on an infinite sequence would enter the
     * code into and infinite operation and effectively freeze the application.
     *
     * @return {E[]} The generated array of values remaining in this sequence.
     * @throws {Error} Thrown if this sequence is infinite.
     */
    toArray() {
      if (!this._isFinite) {
        throw new Error(
          'The infinite sequences cannot be exported to an array'
        )
      }

      return [...this]
    }

    /**
     * Returns the iterator of this {@linkcode Range} instance, which is the
     * instance itself.
     *
     * This method is used to enable compatibility with the for-of cycles.
     *
     * @return {Range<E>} This instance.
     */
    [Symbol.iterator]() {
      return this
    }
  }

  /**
   * Generates the next iteration (the value and a flag whether the end of the
   * sequence has been reached) and the index of the iteration in the sequence
   * of values represented by the provided {@linkcode Range} instance.
   *
   * @template E
   * @param {Range<E>} range The {@linkcode Range} instance representing the
   *        sequence of values for which the next value should be generated.
   * @param {number} currentIndex The index of the previously generated value
   *        in the sequence.
   * @return {[{done: boolean, value: E}, number]} A tuple representing the
   *         next iteration step and the index of the value in the sequence.
   */
  function getNextValue(range, currentIndex) {
    let iteration = range._iterator.next()
    let nextIndex
    if (currentIndex < Number.MAX_SAFE_INTEGER) {
      nextIndex = currentIndex + 1
    } else {
      nextIndex = Number.POSITIVE_INFINITY
    }

    if (iteration.done) {
      return [iteration, nextIndex]
    }

    if (range._filter && !range._filter(iteration.value, nextIndex, range)) {
      return getNextValue(range, currentIndex)
    }

    if (range._map) {
      iteration.value = range._map(iteration.value, nextIndex, range)
    }

    if (range._stop && !range._stop(iteration.value, nextIndex, range)) {
      return [{done: true, value: undefined}, nextIndex]
    }

    return [iteration, nextIndex]
  }

  /**
   * Creates an iterator for generating a sequence of numeric values
   * represented by the provided {@linkcode Range} instance.
   *
   * @param {Range<number>} rangeInstance The {@linkcode Range} instance for
   *        generating a sequence of numbers for which the iterator should be
   *        created.
   * @return {{next: function(): {done: boolean, value: number}}} The created
   *         iterator that will generate the numeric values for the provided
   *         {@linkcode Range} instance.
   */
  function createIterator(rangeInstance) {
    return (function * () {
      while (!isEnded()) {
        yield rangeInstance._currentValue

        let currentSafeLimit = Number.MAX_SAFE_INTEGER - Math.abs(step)
        if (currentSafeLimit < Math.abs(rangeInstance._currentValue)) {
          rangeInstance._currentValue =
              Math.sign(rangeInstance._currentValue) * Infinity
        } else {
          rangeInstance._currentValue += step
        }
      }
    })()

    function isEnded() {
      if (!Number.isFinite(end)) {
        return false
      }

      return (step > 0) ?
          (rangeInstance._currentValue >= end) :
          (rangeInstance._currentValue <= end)
    }
  }

  return new Range(null, null, null, null)
}

if ((typeof module !== 'undefined') && (typeof exports !== 'undefined')) {
  exports.default = range
  exports.range = range
}
<|endoftext|>


<commit_before>/**
 * Created by Andreas on 14.05.2014.
 */
import navobjects from './navobjects';
import LatLon from 'geodesy/latlon-spherical';
let NavCompute={
};

/**
 * compute the distances between 2 points
 * @param {navobjects.Point} src
 * @param {navobjects.Point} dst
 * @returns {navobjects.Distance}
 */
NavCompute.computeDistance=function(src,dst){
    let srcll=src;
    let dstll=dst;
    let rt=new navobjects.Distance();
    //use the movable type stuff for computations
    let llsrc=new LatLon(srcll.lat,srcll.lon);
    let lldst=new LatLon(dstll.lat,dstll.lon);
    rt.dts=llsrc.distanceTo(lldst);
    rt.course=llsrc.initialBearingTo(lldst);
    return rt;
};

NavCompute.computeXte=function(start,destination,current){
    //use the movable type stuff for computations
    let llsrc=new LatLon(start.lat,start.lon);
    let lldst=new LatLon(destination.lat,destination.lon);
    let llcur=new LatLon(current.lat,current.lon);
    let xte=llsrc.crossTrackDistanceTo(lldst,llcur);
    return xte;
};

/**
 * check if a course is closer to our own course or to ownCourse + 180
 * return 1 if closer to own or -1 if closer to +180
 * @param myCourse
 * @param otherCourse
 */
NavCompute.checkInverseCourse=function(myCourse,otherCourse){
    let inversCourse=(myCourse+180) % 360;
    if (Math.abs(otherCourse-myCourse) < Math.abs(otherCourse-inversCourse)) return 1;
    return -1;
};

/**
 * compute the CPA point
 * returns src.lon,src.lat,dst.lon,dst.lat,cpa(m),cpanm(nm),tcpa(s),front (true if src reaches intersect point first)
 * each of the objects must have: lon,lat,course,speed
 * lon/lat in decimal degrees, speed in kn
 * we still have to check if the computed tm is bigger then our configured one
 * //update
 * if one of the partners has no real speed minAISSpeed
 * we need to change the computation - just compute the orthogonal distance of the point to the other
 * course line
 * @param src
 * @param dst
 * @param properties - settings
 * @returns {navobjects.Cpa}
 */
NavCompute.computeCpa=function(src,dst,properties){
    let rt = new navobjects.Cpa();
    let llsrc = new LatLon(src.lat, src.lon);
    let lldst = new LatLon(dst.lat, dst.lon);
    let curdistance=llsrc.distanceTo(lldst); //m
    if (curdistance < 0.1){
        let x=curdistance;
    }
    rt.curdistance=curdistance;
    let courseToTarget=llsrc.initialBearingTo(lldst); //in deg
    //default to our current distance
    rt.tcpa=0;
    rt.cpa=curdistance;
    let maxDistance=6371e3*1000*Math.PI; //half earth
    let appr=NavCompute.computeApproach(courseToTarget,curdistance,src.course,src.speed,dst.course,dst.speed,properties.minAISspeed,maxDistance);
    if (appr.dd !== undefined && appr.ds !== undefined) {
        let xpoint = llsrc.destinationPoint(src.course, appr.dd / 1000);
        rt.crosspoint = new navobjects.Point(xpoint.lon, xpoint.lat);
    }
    if (!appr.tm){
        rt.tcpa=0; //better undefined
        rt.cpa=curdistance;
        rt.front=undefined;
        return rt;
    }
    let cpasrc = llsrc.destinationPoint(src.course, appr.dms/1000);
    let cpadst = lldst.destinationPoint(dst.course, appr.dmd/1000);
    rt.src.lon=cpasrc.lon;
    rt.src.lat=cpasrc.lat;
    rt.dst.lon=cpadst.lon;
    rt.dst.lat=cpadst.lat;
    rt.cpa = cpasrc.distanceTo(cpadst);
    rt.tcpa = appr.tm;
    if (rt.cpa > curdistance || appr.tm < 0){
        rt.cpa=curdistance;
        //rt.tcpa=0;
    }
    if (appr.td !==undefined && appr.ts!==undefined){
        rt.front=(appr.ts<appr.td)?1:0;
    }
    else{
        if (appr.tm >0) rt.front=-1; //we do not cross but will still aproach
        else rt.front=undefined;
    }
    return rt;
};

/**
 * do the computation of the cross point and the closest approach
 * all units are Si units (m, s,...), angles in degrees
 * @param courseToTarget
 * @param curdistance
 * @param srcCourse
 * @param srcSpeed
 * @param dstCourse
 * @param dstSpeed
 * @param minAisSpeed - minimal speed we allow for crossing computation
 * @param maxDistance
 * @returns {object} an object with the properties
 *        td - time dest to crosspoint (if crossing)
 *        ts - time src to crosspoint (if crossing)
 *        dd - distance destination to crosspoint
 *        ds - distance src to crosspoint
 *        tm - TCPA
 *        dms - distance src to cpa point
 *        dmd - distance dest to cpa point
 */
NavCompute.computeApproach=function(courseToTarget,curdistance,srcCourse,srcSpeed,dstCourse,dstSpeed,minAisSpeed,maxDistance){
    //courses
    let rt={};
    let ca=(courseToTarget-srcCourse)/180*Math.PI; //rad
    let cb=(courseToTarget-dstCourse)/180*Math.PI;
    let cosa=Math.cos(ca);
    let sina=Math.sin(ca);
    let cosb=Math.cos(cb);
    let sinb=Math.sin(cb);
    if (dstSpeed > minAisSpeed && srcSpeed > minAisSpeed ){
        //compute crossing
        try {
            rt.td = curdistance / (dstSpeed * (cosa / sina * sinb - cosb));
            rt.ts=curdistance/(srcSpeed*(cosa-sina*cosb/sinb));
        }catch(e){
            //TODO: exception handling
        }
        if (rt.td !== undefined && rt.ts !== undefined){
            rt.ds=srcSpeed*rt.ts; //in m
            rt.dd=dstSpeed*rt.td; //in m
            if (maxDistance !== undefined){
                if (Math.abs(rt.ds) > maxDistance || Math.abs(rt.dd) > maxDistance){
                    rt.td=undefined;
                    rt.ts=undefined;
                    rt.ds=undefined;
                    rt.dd=undefined;
                }
            }
        }
    }
    let quot=(srcSpeed*srcSpeed+dstSpeed*dstSpeed-2*srcSpeed*dstSpeed*(cosa*cosb+sina*sinb));
    if (quot < 1e-6 && quot > -1e-6){
        rt.tm=undefined;
        return rt;
    }
    rt.tm=curdistance*(cosa*srcSpeed-cosb*dstSpeed)/quot;
    rt.dms=srcSpeed*rt.tm;
    rt.dmd=dstSpeed*rt.tm;
    return rt;
};

/**
 * compute a new point (in lon/lat) traveling from a given point
 * @param {navobjects.Point} src
 * @param {number} brg in degrees
 * @param {number} dist in m
*/
NavCompute.computeTarget=function(src,brg,dist){
    let llsrc = new LatLon(src.lat, src.lon);
    let llrt=llsrc.destinationPoint(brg,dist/1000);
    let rt=new navobjects.Point(llrt.lon,llrt.lat);
    return rt;
};

/**
 * compute the data for a leg
 * @param {navobjects.Point} target the waypoint destination
 * @param {{valid:Boolean,speed:Number,lon:Number, lat: Number, course: Number,rtime: Date}}gps the current gps data
 * @param opt_start
 * @returns {{markerCourse: Number, markerDistance: Number, markerVmg: Number, markerEta: Date, markerXte: Number}}
 */
NavCompute.computeLegInfo=function(target,gps,opt_start){
    let rt={
        markerCourse:undefined,
        markerDistance: undefined,
        markerVmg: undefined,
        markerEta: undefined,
        markerXte: undefined
    };
    rt.markerWp=target;
    if (gps.valid) {
        let markerdst = NavCompute.computeDistance(gps, target);
        rt.markerCourse = markerdst.course;
        rt.markerDistance = markerdst.dts;
        let coursediff = Math.min(Math.abs(markerdst.course - gps.course), Math.abs(markerdst.course + 360 - gps.course),
            Math.abs(markerdst.course - (gps.course + 360)));
        if (gps.rtime && coursediff <= 85) {
            //TODO: is this really correct for VMG?
            let vmgapp = gps.speed * Math.cos(Math.PI / 180 * coursediff);
            //vmgapp is in m/s
            let targettime = gps.rtime.getTime();
            rt.markerVmg = vmgapp;
            if (vmgapp > 0) {
                targettime += rt.markerDistance / vmgapp  * 1000; //time in ms
                let targetDate = new Date(Math.round(targettime));
                rt.markerEta = targetDate;
            }
            else {
                rt.markerEta = null;
            }
        }
        else {
            rt.markerEta = null;
            rt.markerVmg = 0;
        }
        if (opt_start) {
            rt.markerXte = NavCompute.computeXte(opt_start,target, gps);
        }
        else{
            rt.markerXte=0;
        }
    }
    return rt;
};
NavCompute.NM=1852; //m per NM

export default NavCompute;

<commit_msg>correctly use destinationPoint (boat vector, nav circles, ais)<commit_after>/**
 * Created by Andreas on 14.05.2014.
 */
import navobjects from './navobjects';
import LatLon from 'geodesy/latlon-spherical';
let NavCompute={
};

/**
 * compute the distances between 2 points
 * @param {navobjects.Point} src
 * @param {navobjects.Point} dst
 * @returns {navobjects.Distance}
 */
NavCompute.computeDistance=function(src,dst){
    let srcll=src;
    let dstll=dst;
    let rt=new navobjects.Distance();
    //use the movable type stuff for computations
    let llsrc=new LatLon(srcll.lat,srcll.lon);
    let lldst=new LatLon(dstll.lat,dstll.lon);
    rt.dts=llsrc.distanceTo(lldst);
    rt.course=llsrc.initialBearingTo(lldst);
    return rt;
};

NavCompute.computeXte=function(start,destination,current){
    //use the movable type stuff for computations
    let llsrc=new LatLon(start.lat,start.lon);
    let lldst=new LatLon(destination.lat,destination.lon);
    let llcur=new LatLon(current.lat,current.lon);
    let xte=llsrc.crossTrackDistanceTo(lldst,llcur);
    return xte;
};

/**
 * check if a course is closer to our own course or to ownCourse + 180
 * return 1 if closer to own or -1 if closer to +180
 * @param myCourse
 * @param otherCourse
 */
NavCompute.checkInverseCourse=function(myCourse,otherCourse){
    let inversCourse=(myCourse+180) % 360;
    if (Math.abs(otherCourse-myCourse) < Math.abs(otherCourse-inversCourse)) return 1;
    return -1;
};

/**
 * compute the CPA point
 * returns src.lon,src.lat,dst.lon,dst.lat,cpa(m),cpanm(nm),tcpa(s),front (true if src reaches intersect point first)
 * each of the objects must have: lon,lat,course,speed
 * lon/lat in decimal degrees, speed in kn
 * we still have to check if the computed tm is bigger then our configured one
 * //update
 * if one of the partners has no real speed minAISSpeed
 * we need to change the computation - just compute the orthogonal distance of the point to the other
 * course line
 * @param src
 * @param dst
 * @param properties - settings
 * @returns {navobjects.Cpa}
 */
NavCompute.computeCpa=function(src,dst,properties){
    let rt = new navobjects.Cpa();
    let llsrc = new LatLon(src.lat, src.lon);
    let lldst = new LatLon(dst.lat, dst.lon);
    let curdistance=llsrc.distanceTo(lldst); //m
    if (curdistance < 0.1){
        let x=curdistance;
    }
    rt.curdistance=curdistance;
    let courseToTarget=llsrc.initialBearingTo(lldst); //in deg
    //default to our current distance
    rt.tcpa=0;
    rt.cpa=curdistance;
    let maxDistance=6371e3*1000*Math.PI; //half earth
    let appr=NavCompute.computeApproach(courseToTarget,curdistance,src.course,src.speed,dst.course,dst.speed,properties.minAISspeed,maxDistance);
    if (appr.dd !== undefined && appr.ds !== undefined) {
        let xpoint = llsrc.destinationPoint(appr.dd,src.course);
        rt.crosspoint = new navobjects.Point(xpoint.lon, xpoint.lat);
    }
    if (!appr.tm){
        rt.tcpa=0; //better undefined
        rt.cpa=curdistance;
        rt.front=undefined;
        return rt;
    }
    let cpasrc = llsrc.destinationPoint(appr.dms,src.course );
    let cpadst = lldst.destinationPoint(appr.dmd,dst.course);
    rt.src.lon=cpasrc.lon;
    rt.src.lat=cpasrc.lat;
    rt.dst.lon=cpadst.lon;
    rt.dst.lat=cpadst.lat;
    rt.cpa = cpasrc.distanceTo(cpadst);
    rt.tcpa = appr.tm;
    if (rt.cpa > curdistance || appr.tm < 0){
        rt.cpa=curdistance;
        //rt.tcpa=0;
    }
    if (appr.td !==undefined && appr.ts!==undefined){
        rt.front=(appr.ts<appr.td)?1:0;
    }
    else{
        if (appr.tm >0) rt.front=-1; //we do not cross but will still aproach
        else rt.front=undefined;
    }
    return rt;
};

/**
 * do the computation of the cross point and the closest approach
 * all units are Si units (m, s,...), angles in degrees
 * @param courseToTarget
 * @param curdistance
 * @param srcCourse
 * @param srcSpeed
 * @param dstCourse
 * @param dstSpeed
 * @param minAisSpeed - minimal speed we allow for crossing computation
 * @param maxDistance
 * @returns {object} an object with the properties
 *        td - time dest to crosspoint (if crossing)
 *        ts - time src to crosspoint (if crossing)
 *        dd - distance destination to crosspoint
 *        ds - distance src to crosspoint
 *        tm - TCPA
 *        dms - distance src to cpa point
 *        dmd - distance dest to cpa point
 */
NavCompute.computeApproach=function(courseToTarget,curdistance,srcCourse,srcSpeed,dstCourse,dstSpeed,minAisSpeed,maxDistance){
    //courses
    let rt={};
    let ca=(courseToTarget-srcCourse)/180*Math.PI; //rad
    let cb=(courseToTarget-dstCourse)/180*Math.PI;
    let cosa=Math.cos(ca);
    let sina=Math.sin(ca);
    let cosb=Math.cos(cb);
    let sinb=Math.sin(cb);
    if (dstSpeed > minAisSpeed && srcSpeed > minAisSpeed ){
        //compute crossing
        try {
            rt.td = curdistance / (dstSpeed * (cosa / sina * sinb - cosb));
            rt.ts=curdistance/(srcSpeed*(cosa-sina*cosb/sinb));
        }catch(e){
            //TODO: exception handling
        }
        if (rt.td !== undefined && rt.ts !== undefined){
            rt.ds=srcSpeed*rt.ts; //in m
            rt.dd=dstSpeed*rt.td; //in m
            if (maxDistance !== undefined){
                if (Math.abs(rt.ds) > maxDistance || Math.abs(rt.dd) > maxDistance){
                    rt.td=undefined;
                    rt.ts=undefined;
                    rt.ds=undefined;
                    rt.dd=undefined;
                }
            }
        }
    }
    let quot=(srcSpeed*srcSpeed+dstSpeed*dstSpeed-2*srcSpeed*dstSpeed*(cosa*cosb+sina*sinb));
    if (quot < 1e-6 && quot > -1e-6){
        rt.tm=undefined;
        return rt;
    }
    rt.tm=curdistance*(cosa*srcSpeed-cosb*dstSpeed)/quot;
    rt.dms=srcSpeed*rt.tm;
    rt.dmd=dstSpeed*rt.tm;
    return rt;
};

/**
 * compute a new point (in lon/lat) traveling from a given point
 * @param {navobjects.Point} src
 * @param {number} brg in degrees
 * @param {number} dist in m
*/
NavCompute.computeTarget=function(src,brg,dist){
    let llsrc = new LatLon(src.lat, src.lon);
    let llrt=llsrc.destinationPoint(dist,brg);
    let rt=new navobjects.Point(llrt.lon,llrt.lat);
    return rt;
};

/**
 * compute the data for a leg
 * @param {navobjects.Point} target the waypoint destination
 * @param {{valid:Boolean,speed:Number,lon:Number, lat: Number, course: Number,rtime: Date}}gps the current gps data
 * @param opt_start
 * @returns {{markerCourse: Number, markerDistance: Number, markerVmg: Number, markerEta: Date, markerXte: Number}}
 */
NavCompute.computeLegInfo=function(target,gps,opt_start){
    let rt={
        markerCourse:undefined,
        markerDistance: undefined,
        markerVmg: undefined,
        markerEta: undefined,
        markerXte: undefined
    };
    rt.markerWp=target;
    if (gps.valid) {
        let markerdst = NavCompute.computeDistance(gps, target);
        rt.markerCourse = markerdst.course;
        rt.markerDistance = markerdst.dts;
        let coursediff = Math.min(Math.abs(markerdst.course - gps.course), Math.abs(markerdst.course + 360 - gps.course),
            Math.abs(markerdst.course - (gps.course + 360)));
        if (gps.rtime && coursediff <= 85) {
            //TODO: is this really correct for VMG?
            let vmgapp = gps.speed * Math.cos(Math.PI / 180 * coursediff);
            //vmgapp is in m/s
            let targettime = gps.rtime.getTime();
            rt.markerVmg = vmgapp;
            if (vmgapp > 0) {
                targettime += rt.markerDistance / vmgapp  * 1000; //time in ms
                let targetDate = new Date(Math.round(targettime));
                rt.markerEta = targetDate;
            }
            else {
                rt.markerEta = null;
            }
        }
        else {
            rt.markerEta = null;
            rt.markerVmg = 0;
        }
        if (opt_start) {
            rt.markerXte = NavCompute.computeXte(opt_start,target, gps);
        }
        else{
            rt.markerXte=0;
        }
    }
    return rt;
};
NavCompute.NM=1852; //m per NM

export default NavCompute;

<|endoftext|>


<commit_before>import path from 'path';
import _ from 'lodash';
import chokidar from 'chokidar';
import WebpackInfoPlugin from 'webpack-info-plugin';

import { glob } from '../util/glob';
import createCompiler from '../webpack/compiler/createCompiler';
import createWatchCompiler from '../webpack/compiler/createWatchCompiler';
import registerInMemoryCompiler from '../webpack/compiler/registerInMemoryCompiler';
import registerReadyCallback from '../webpack/compiler/registerReadyCallback';
import { EntryConfig, KEY as ENTRY_CONFIG_KEY } from '../webpack/loader/entryLoader';
import configureMocha from './configureMocha';
import getBuildStats from '../webpack/util/getBuildStats';

import type { MochaWebpackOptions } from '../MochaWebpack';
import type { BuildStats } from '../webpack/util/getBuildStats';
import type { WatchCompiler } from '../webpack/compiler/createWatchCompiler';
import type { Compiler, Stats } from '../webpack/types';

const entryPath = path.resolve(__dirname, '../entry.js');
const entryLoaderPath = path.resolve(__dirname, '../webpack/loader/entryLoader.js');
const includeLoaderPath = path.resolve(__dirname, '../webpack/loader/includeFilesLoader.js');
const noop = () => void 0;

type MochaRunner = {
  abort: () => void,
};
type Mocha = {
  run: (cb: (failures: number) => void) => MochaRunner,
};

export default class TestRunner {

  entries: Array<string>;
  includes: Array<string>;
  tmpPath: string;
  options: MochaWebpackOptions;
  outputFilePath: string;

  constructor(entries: Array<string>, includes: Array<String>, options: MochaWebpackOptions) {
    this.entries = entries;
    this.includes = includes;

    this.options = options;
    this.tmpPath = path.join(this.options.cwd, '.tmp', 'mocha-webpack');
    this.outputFilePath = path.join(this.tmpPath, 'bundle.js');
  }

  prepareMocha(webpackConfig: Object, stats: Stats): Mocha {
    const mocha: Mocha = configureMocha(this.options);
    const outputPath = webpackConfig.output.path;
    const buildStats: BuildStats = getBuildStats(stats, outputPath);

    global.__webpackManifest__ = buildStats.affectedModules; // eslint-disable-line

    // clear up require cache for changed files to make sure that we get the latest changes
    buildStats.affectedFiles.forEach((filePath) => {
      delete require.cache[filePath];
    });
    // pass webpack's entry files to mocha
    mocha.files = buildStats.entries;
    return mocha;
  }

  async run(): Promise<number> {
    const config = await this.createWebpackConfig();
    let failures = 0;
    const compiler: Compiler = createCompiler(config);
    const dispose = registerInMemoryCompiler(compiler);
    try {
      failures = await new Promise((resolve, reject) => {
        registerReadyCallback(compiler, (err?: Error, stats?: Stats) => {
          if (err) {
            reject(err);
            return;
          }
          const mocha = this.prepareMocha(config, stats);
          mocha.run(resolve);
        });
        compiler.run(noop);
      });
    } finally {
      // clean up single run
      dispose();
    }
    return failures;
  }

  async watch(): void {
    const config = await this.createWebpackConfig();
    const entryConfig: EntryConfig = config[ENTRY_CONFIG_KEY];

    let runAgain = false;
    let mochaRunner: MochaRunner = null;
    let stats: Stats = null;

    const uncaughtExceptionListener = (err) => {
      // mocha catches uncaughtException only while tests are running,
      // that's why we register a custom error handler to keep this process alive
      console.error('An uncaught exception occurred: %s', err.message); // eslint-disable-line no-console
      console.error(err.stack); // eslint-disable-line no-console
    };

    const runMocha = () => {
      const mocha = this.prepareMocha(config, stats);
      runAgain = false;

      try {
        // unregister our custom exception handler (see declaration)
        process.removeListener('uncaughtException', uncaughtExceptionListener);

        // run tests
        mochaRunner = mocha.run(() => {
          // register custom exception handler to catch all errors that may happen after mocha think tests are done
          process.on('uncaughtException', uncaughtExceptionListener);

          // need to wait until next tick, otherwise mochaRunner = null doesn't work..
          process.nextTick(() => {
            mochaRunner = null;
            if (runAgain) {
              runMocha();
            }
          });
        });
      } catch (err) {
        console.error('An exception occurred while loading tests: %s', err.message); // eslint-disable-line no-console
        console.error(err.stack); // eslint-disable-line no-console
      }
    };

    const compiler = createCompiler(config);
    registerInMemoryCompiler(compiler);
    registerReadyCallback(compiler, (err?: Error, webpackStats?: Stats) => {
      if (err) {
        // wait for fixed tests
        return;
      }

      stats = webpackStats;
      runAgain = true;
      if (mochaRunner) {
        mochaRunner.abort();
        // make sure that the current running test will be aborted when timeouts are disabled for async tests
        if (mochaRunner.currentRunnable) {
          mochaRunner.currentRunnable.retries(0);
          mochaRunner.currentRunnable.enableTimeouts(true);
          mochaRunner.currentRunnable.timeout(1);
          mochaRunner.currentRunnable.resetTimeout(1);
        }
      } else {
        runMocha();
      }
    });

    const watchCompiler: WatchCompiler = createWatchCompiler(compiler, config.watchOptions);
    // start webpack build immediately
    watchCompiler.watch();

    // webpack enhances watch options, that's why we use them instead
    const watchOptions = watchCompiler.getWatchOptions();
    // create own file watcher for entry files to detect created or deleted files
    const watcher = chokidar.watch(this.entries, {
      cwd: this.options.cwd,
      // see https://github.com/webpack/watchpack/blob/e5305b53ac3cf2a70d49a772912b115fa77665c2/lib/DirectoryWatcher.js
      ignoreInitial: true,
      persistent: true,
      followSymlinks: false,
      ignorePermissionErrors: true,
      ignored: watchOptions.ignored,
      usePolling: watchOptions.poll ? true : undefined,
      interval: typeof watchOptions.poll === 'number' ? watchOptions.poll : undefined,
    });

    const restartWebpackBuild = _.debounce(() => watchCompiler.watch(), watchOptions.aggregateTimeout);
    const fileDeletedOrAdded = (file, deleted) => {
      const filePath = path.join(this.options.cwd, file);
      if (deleted) {
        entryConfig.removeFile(filePath);
      } else {
        entryConfig.addFile(filePath);
      }

      // pause webpack watch immediately before webpack will be notified
      watchCompiler.pause();
      // call debounced webpack runner to rebuild files
      restartWebpackBuild();
    };

    // add listener for entry creation & deletion events
    watcher.on('add', file => fileDeletedOrAdded(file, false));
    watcher.on('unlink', file => fileDeletedOrAdded(file, true));

    return new Promise(() => void 0); // never ending story
  }

  async createWebpackConfig(): {} {
    const webpackConfig = this.options.webpackConfig;

    const files = await glob(this.entries, {
      cwd: this.options.cwd,
      absolute: true,
    });

    const entryConfig = new EntryConfig();
    files.forEach((f) => entryConfig.addFile(f));

    const includeLoaderQuery = {
      include: this.includes,
    };

    const entry = `!!${includeLoaderPath}?${JSON.stringify(includeLoaderQuery)}!${entryLoaderPath}!${entryPath}`;

    const outputFileName = path.basename(this.outputFilePath);
    const outputPath = path.dirname(this.outputFilePath);

    const webpackInfoPlugin = new WebpackInfoPlugin({
      stats: {
        // pass options from http://webpack.github.io/docs/node.js-api.html#stats-tostring
        // context: false,
        hash: false,
        version: false,
        timings: false,
        assets: false,
        chunks: false,
        chunkModules: false,
        modules: false,
        children: false,
        cached: false,
        reasons: false,
        source: false,
        errorDetails: true,
        chunkOrigins: false,
        colors: this.options.colors,
      },
      state: false, // show bundle valid / invalid
    });

    const plugins = [
      webpackInfoPlugin,
    ];

    return {
      ...webpackConfig,
      [ENTRY_CONFIG_KEY]: entryConfig,
      entry,
      output: {
        ...webpackConfig.output,
        filename: outputFileName,
        path: outputPath,
      },
      plugins: [
        ...(webpackConfig.plugins || []),
        ...plugins,
      ],
    };
  }

}
<commit_msg>abort running mocha tests when webpack starts to compile (#94)<commit_after>import path from 'path';
import _ from 'lodash';
import chokidar from 'chokidar';
import WebpackInfoPlugin from 'webpack-info-plugin';

import { glob } from '../util/glob';
import createCompiler from '../webpack/compiler/createCompiler';
import createWatchCompiler from '../webpack/compiler/createWatchCompiler';
import registerInMemoryCompiler from '../webpack/compiler/registerInMemoryCompiler';
import registerReadyCallback from '../webpack/compiler/registerReadyCallback';
import { EntryConfig, KEY as ENTRY_CONFIG_KEY } from '../webpack/loader/entryLoader';
import configureMocha from './configureMocha';
import getBuildStats from '../webpack/util/getBuildStats';

import type { MochaWebpackOptions } from '../MochaWebpack';
import type { BuildStats } from '../webpack/util/getBuildStats';
import type { WatchCompiler } from '../webpack/compiler/createWatchCompiler';
import type { Compiler, Stats } from '../webpack/types';

const entryPath = path.resolve(__dirname, '../entry.js');
const entryLoaderPath = path.resolve(__dirname, '../webpack/loader/entryLoader.js');
const includeLoaderPath = path.resolve(__dirname, '../webpack/loader/includeFilesLoader.js');
const noop = () => void 0;

type MochaRunner = {
  abort: () => void,
};
type Mocha = {
  run: (cb: (failures: number) => void) => MochaRunner,
};

export default class TestRunner {

  entries: Array<string>;
  includes: Array<string>;
  tmpPath: string;
  options: MochaWebpackOptions;
  outputFilePath: string;

  constructor(entries: Array<string>, includes: Array<String>, options: MochaWebpackOptions) {
    this.entries = entries;
    this.includes = includes;

    this.options = options;
    this.tmpPath = path.join(this.options.cwd, '.tmp', 'mocha-webpack');
    this.outputFilePath = path.join(this.tmpPath, 'bundle.js');
  }

  prepareMocha(webpackConfig: Object, stats: Stats): Mocha {
    const mocha: Mocha = configureMocha(this.options);
    const outputPath = webpackConfig.output.path;
    const buildStats: BuildStats = getBuildStats(stats, outputPath);

    global.__webpackManifest__ = buildStats.affectedModules; // eslint-disable-line

    // clear up require cache for changed files to make sure that we get the latest changes
    buildStats.affectedFiles.forEach((filePath) => {
      delete require.cache[filePath];
    });
    // pass webpack's entry files to mocha
    mocha.files = buildStats.entries;
    return mocha;
  }

  async run(): Promise<number> {
    const config = await this.createWebpackConfig();
    let failures = 0;
    const compiler: Compiler = createCompiler(config);
    const dispose = registerInMemoryCompiler(compiler);
    try {
      failures = await new Promise((resolve, reject) => {
        registerReadyCallback(compiler, (err?: Error, stats?: Stats) => {
          if (err) {
            reject(err);
            return;
          }
          const mocha = this.prepareMocha(config, stats);
          mocha.run(resolve);
        });
        compiler.run(noop);
      });
    } finally {
      // clean up single run
      dispose();
    }
    return failures;
  }

  async watch(): void {
    const config = await this.createWebpackConfig();
    const entryConfig: EntryConfig = config[ENTRY_CONFIG_KEY];

    let mochaRunner: MochaRunner = null;
    let stats: Stats = null;
    let compilationScheduler: () => void = null;

    const uncaughtExceptionListener = (err) => {
      // mocha catches uncaughtException only while tests are running,
      // that's why we register a custom error handler to keep this process alive
      console.error('An uncaught exception occurred: %s', err.message); // eslint-disable-line no-console
      console.error(err.stack); // eslint-disable-line no-console
    };

    const runMocha = () => {
      const mocha = this.prepareMocha(config, stats);

      try {
        // unregister our custom exception handler (see declaration)
        process.removeListener('uncaughtException', uncaughtExceptionListener);

        // run tests
        mochaRunner = mocha.run(() => {
          // register custom exception handler to catch all errors that may happen after mocha think tests are done
          process.on('uncaughtException', uncaughtExceptionListener);

          // need to wait until next tick, otherwise mochaRunner = null doesn't work..
          process.nextTick(() => {
            mochaRunner = null;
            if (compilationScheduler !== null) {
              compilationScheduler();
              compilationScheduler = null;
            }
          });
        });
      } catch (err) {
        console.error('An exception occurred while loading tests: %s', err.message); // eslint-disable-line no-console
        console.error(err.stack); // eslint-disable-line no-console
      }
    };

    const compiler = createCompiler(config);
    registerInMemoryCompiler(compiler);
    // register webpack start callback
    compiler.plugin('watch-run', (w, cb) => {
      // check if mocha tests are still running, abort them and start compiling
      if (mochaRunner) {
        compilationScheduler = () => {
          cb();
        };

        mochaRunner.abort();
        // make sure that the current running test will be aborted when timeouts are disabled for async tests
        if (mochaRunner.currentRunnable) {
          mochaRunner.currentRunnable.retries(0);
          mochaRunner.currentRunnable.enableTimeouts(true);
          mochaRunner.currentRunnable.timeout(1);
          mochaRunner.currentRunnable.resetTimeout(1);
        }
      } else {
        cb();
      }
    });
    // register webpack ready callback
    registerReadyCallback(compiler, (err?: Error, webpackStats?: Stats) => {
      if (err) {
        // wait for fixed tests
        return;
      }
      stats = webpackStats;
      runMocha();
    });

    const watchCompiler: WatchCompiler = createWatchCompiler(compiler, config.watchOptions);
    // start webpack build immediately
    watchCompiler.watch();

    // webpack enhances watch options, that's why we use them instead
    const watchOptions = watchCompiler.getWatchOptions();
    // create own file watcher for entry files to detect created or deleted files
    const watcher = chokidar.watch(this.entries, {
      cwd: this.options.cwd,
      // see https://github.com/webpack/watchpack/blob/e5305b53ac3cf2a70d49a772912b115fa77665c2/lib/DirectoryWatcher.js
      ignoreInitial: true,
      persistent: true,
      followSymlinks: false,
      ignorePermissionErrors: true,
      ignored: watchOptions.ignored,
      usePolling: watchOptions.poll ? true : undefined,
      interval: typeof watchOptions.poll === 'number' ? watchOptions.poll : undefined,
    });

    const restartWebpackBuild = _.debounce(() => watchCompiler.watch(), watchOptions.aggregateTimeout);
    const fileDeletedOrAdded = (file, deleted) => {
      const filePath = path.join(this.options.cwd, file);
      if (deleted) {
        entryConfig.removeFile(filePath);
      } else {
        entryConfig.addFile(filePath);
      }

      // pause webpack watch immediately before webpack will be notified
      watchCompiler.pause();
      // call debounced webpack runner to rebuild files
      restartWebpackBuild();
    };

    // add listener for entry creation & deletion events
    watcher.on('add', file => fileDeletedOrAdded(file, false));
    watcher.on('unlink', file => fileDeletedOrAdded(file, true));

    return new Promise(() => void 0); // never ending story
  }

  async createWebpackConfig(): {} {
    const webpackConfig = this.options.webpackConfig;

    const files = await glob(this.entries, {
      cwd: this.options.cwd,
      absolute: true,
    });

    const entryConfig = new EntryConfig();
    files.forEach((f) => entryConfig.addFile(f));

    const includeLoaderQuery = {
      include: this.includes,
    };

    const entry = `!!${includeLoaderPath}?${JSON.stringify(includeLoaderQuery)}!${entryLoaderPath}!${entryPath}`;

    const outputFileName = path.basename(this.outputFilePath);
    const outputPath = path.dirname(this.outputFilePath);

    const webpackInfoPlugin = new WebpackInfoPlugin({
      stats: {
        // pass options from http://webpack.github.io/docs/node.js-api.html#stats-tostring
        // context: false,
        hash: false,
        version: false,
        timings: false,
        assets: false,
        chunks: false,
        chunkModules: false,
        modules: false,
        children: false,
        cached: false,
        reasons: false,
        source: false,
        errorDetails: true,
        chunkOrigins: false,
        colors: this.options.colors,
      },
      state: false, // show bundle valid / invalid
    });

    const plugins = [
      webpackInfoPlugin,
    ];

    return {
      ...webpackConfig,
      [ENTRY_CONFIG_KEY]: entryConfig,
      entry,
      output: {
        ...webpackConfig.output,
        filename: outputFileName,
        path: outputPath,
      },
      plugins: [
        ...(webpackConfig.plugins || []),
        ...plugins,
      ],
    };
  }

}
<|endoftext|>


<commit_before>Discourse.Topic.reopen({
  // locationCount
  // Save any changes we've made to the model
  save: function() {
    // I set category id automatically based on location and this messes with that
    // will set title and category when setting/updating location
    return;
    // Don't save unless we can
    // if (!this.get('details.can_edit')) return;

    // return Discourse.ajax(this.get('url'), {
    //   type: 'PUT',
    //   data: { title: this.get('title'), category_id: this.get('category.id') }
    // });
  },
  // activePost: {},
  // locationMeta: function() {
  //   // TODO - set a locationMeta property for each topic
  //   // eventually all locationTopics will have to have this
  //   // as this object will come from db, probably should use snake case names - hot_from etc
  //   // debugger;
  //   if (this.get('geo')) {
  //     return this.get('geo');
  //   }
  //   // else {
  //   //   return {
  //   //     displayString: 'Berlin',
  //   //     value: 'berlin',
  //   //     longitude: "13.4060912",
  //   //     latitude: "52.519171",
  //   //     hot_from: "",
  //   //     scope_type: "city"
  //   //   };
  //   // }
  // }.property('geo'),
  isLocationTopic: function() {
    if (this.get('location') || this.get('geo')) {
      return true;
    } else {
      return false;
    }
  }.property('location', 'geo'),

  // hasLocation: function() {
  //   if (this.get('location')) {
  //     return true;
  //   } else {
  //     return false;
  //   }
  // }.property('location'),

  hasGeo: function() {
    if (this.get('geo')) {
      return true;
    } else {
      return false;
    }
  }.property('geo'),

  locationDetails: function() {
    if (this.get('location')) {
      return Discourse.Location.create(this.get('location'));
    } else {
      return undefined;
    }
  }.property("location"),

  // below gets fed to topics map component
  markers: function() {
    // var currentMarkerValuesOld = [];
    // var locations = this.get('locations');
    // debugger;
    // if (locations) {
    //   locations.forEach(function(loc) {
    //     var markerInfo = {
    //       context: 'topic_view',
    //       location: loc,
    //       location_id: loc.id
    //     };
    //     currentMarkerValuesOld.push(markerInfo);
    //   });
    // }
    var can_edit = Discourse.User.current().admin;
    // this.get('details.can_edit');
    var currentMarkerValues = [];
    var places = this.get('geo.places');
    if (places && places.sorted_ids) {
      places.sorted_ids.forEach(function(id){
        var markerInfo = {
          context: 'topic_view',
          location: places[id],
          location_id: String(id),
          can_edit: can_edit
        };
        // currently have some errors in db where duplicates have been saved
        // shouldn't need below once that is fixed
        if(!currentMarkerValues.findBy('location_id', String(id))){
          currentMarkerValues.push(markerInfo);
        }
      })
    };

    if (this.get('postStream.posts')) {
      var posts = this.get('postStream.posts');
      posts.forEach(function(p) {
        if (p.get('location')) {
          // var markerInfo = currentMarkerValues.findBy('location.id', p.get('location.id'));
          // above works but I suspect this is more efficient:
          var markerInfo = currentMarkerValues.findBy('location_id', p.get('location.id').toString());
          // currently some posts have locations that are not in the topic locations collection
          // so this is a workaround that should not be needed in the future:
          if (!markerInfo) {
            debugger;
            var loc = p.get('location');
            var markerInfo = {
              context: 'topic_view',
              location: loc,
              location_id: loc.id
            };
            currentMarkerValues.push(markerInfo);
          }

          if (!markerInfo.posts) {
            markerInfo.posts = [];
          }
          markerInfo.posts.pushObject(p);
          // debugger;

          // markerInfo.post = p;

          // if(p.post_number === 1){
          //   below would result in this.get('model.topic.markers.firstObject.topic.markers.firstObject.topic.markers.firstObject');
          //   markerInfo.topic = p.topic;
          // }
          // else{
          //   markerInfo.post = p;
          // }
          // currentMarkerValues.push(markerInfo);
        };

      });
    }

    return currentMarkerValues;
    // locationCount below is not accurate, just a value that increments each time
    // a new reply with a location is added (done in extension to composer model)
  }.property('locations', 'location', 'locationCount')

})
<commit_msg>Fix bug with getting admin status<commit_after>Discourse.Topic.reopen({
  // locationCount
  // Save any changes we've made to the model
  save: function() {
    // I set category id automatically based on location and this messes with that
    // will set title and category when setting/updating location
    return;
    // Don't save unless we can
    // if (!this.get('details.can_edit')) return;

    // return Discourse.ajax(this.get('url'), {
    //   type: 'PUT',
    //   data: { title: this.get('title'), category_id: this.get('category.id') }
    // });
  },
  // activePost: {},
  // locationMeta: function() {
  //   // TODO - set a locationMeta property for each topic
  //   // eventually all locationTopics will have to have this
  //   // as this object will come from db, probably should use snake case names - hot_from etc
  //   // debugger;
  //   if (this.get('geo')) {
  //     return this.get('geo');
  //   }
  //   // else {
  //   //   return {
  //   //     displayString: 'Berlin',
  //   //     value: 'berlin',
  //   //     longitude: "13.4060912",
  //   //     latitude: "52.519171",
  //   //     hot_from: "",
  //   //     scope_type: "city"
  //   //   };
  //   // }
  // }.property('geo'),
  isLocationTopic: function() {
    if (this.get('location') || this.get('geo')) {
      return true;
    } else {
      return false;
    }
  }.property('location', 'geo'),

  // hasLocation: function() {
  //   if (this.get('location')) {
  //     return true;
  //   } else {
  //     return false;
  //   }
  // }.property('location'),

  hasGeo: function() {
    if (this.get('geo')) {
      return true;
    } else {
      return false;
    }
  }.property('geo'),

  locationDetails: function() {
    if (this.get('location')) {
      return Discourse.Location.create(this.get('location'));
    } else {
      return undefined;
    }
  }.property("location"),

  // below gets fed to topics map component
  markers: function() {
    // var currentMarkerValuesOld = [];
    // var locations = this.get('locations');
    // debugger;
    // if (locations) {
    //   locations.forEach(function(loc) {
    //     var markerInfo = {
    //       context: 'topic_view',
    //       location: loc,
    //       location_id: loc.id
    //     };
    //     currentMarkerValuesOld.push(markerInfo);
    //   });
    // }
    var can_edit = Discourse.User.current() && Discourse.User.current().admin;
    // this.get('details.can_edit');
    var currentMarkerValues = [];
    var places = this.get('geo.places');
    if (places && places.sorted_ids) {
      places.sorted_ids.forEach(function(id){
        var markerInfo = {
          context: 'topic_view',
          location: places[id],
          location_id: String(id),
          can_edit: can_edit
        };
        // currently have some errors in db where duplicates have been saved
        // shouldn't need below once that is fixed
        if(!currentMarkerValues.findBy('location_id', String(id))){
          currentMarkerValues.push(markerInfo);
        }
      })
    };

    if (this.get('postStream.posts')) {
      var posts = this.get('postStream.posts');
      posts.forEach(function(p) {
        if (p.get('location')) {
          // var markerInfo = currentMarkerValues.findBy('location.id', p.get('location.id'));
          // above works but I suspect this is more efficient:
          var markerInfo = currentMarkerValues.findBy('location_id', p.get('location.id').toString());
          // currently some posts have locations that are not in the topic locations collection
          // so this is a workaround that should not be needed in the future:
          if (!markerInfo) {
            debugger;
            var loc = p.get('location');
            var markerInfo = {
              context: 'topic_view',
              location: loc,
              location_id: loc.id
            };
            currentMarkerValues.push(markerInfo);
          }

          if (!markerInfo.posts) {
            markerInfo.posts = [];
          }
          markerInfo.posts.pushObject(p);
          // debugger;

          // markerInfo.post = p;

          // if(p.post_number === 1){
          //   below would result in this.get('model.topic.markers.firstObject.topic.markers.firstObject.topic.markers.firstObject');
          //   markerInfo.topic = p.topic;
          // }
          // else{
          //   markerInfo.post = p;
          // }
          // currentMarkerValues.push(markerInfo);
        };

      });
    }

    return currentMarkerValues;
    // locationCount below is not accurate, just a value that increments each time
    // a new reply with a location is added (done in extension to composer model)
  }.property('locations', 'location', 'locationCount')

})
<|endoftext|>


<commit_before>'use strict';

let fs = require('fs');
let path = require('path');
let resolvePath = path.resolve;
let globSync = require('glob').sync;
let moment = require('moment');
let pad = require('pad');
let dateTimeFormat = require('./date-time-format');
let parseDate = require('./parse-date');
let parseParameter = require('./parse-parameter');

let handlers = globSync('cli-parsers/*.js').map(function(handlerPath) {
    return require(resolvePath(handlerPath));
});

module.exports = function(args) {
    let event = {};

    event.type = args.shift();

    let dateTime = (function() {
        let dateTime = parseDate(args[0]);

        if(dateTime) {
            dateTime = moment(dateTime);
            args.shift();
        }
        else {
            dateTime = moment();
        }

        return dateTime;
    })();

    args.forEach(function(arg) {
        var keyValue = /^([^:]+):(.+)$/.exec(arg);

        if(keyValue) {
            event[keyValue[1]] = parseParameter(keyValue[2]);
        }
        else {
            event.positionals = event.positionals || [];
            event.positionals.push(parseParameter(arg));
        }
    });

    handlers.forEach(function(handler) {
        try {
            handler.call(event);
        }
        catch(error) {
            if(!error.assertionError) {
                throw error;
            }

            console.error(error.message);
            process.exit(-1);
        }
    });

    {
        let fileName;
        let maxCount = 999;

        for(let i = 0; i < maxCount; ++i) {
            fileName = (
                dateTime.format(dateTimeFormat) +
                '.' + pad(3, i, '0') + '.ev.json'
            );

            if(!fs.existsSync(fileName)) {
                break;
            }
            else
            if(i === maxCount - 1) {
                throw new Error("Too many events for a second.");
            }
        }

        fs.writeFileSync(fileName, JSON.stringify(event, null, 4));
    }
};
<commit_msg>Move date time parameter before event type parameter.<commit_after>'use strict';

let fs = require('fs');
let path = require('path');
let resolvePath = path.resolve;
let globSync = require('glob').sync;
let moment = require('moment');
let pad = require('pad');
let dateTimeFormat = require('./date-time-format');
let parseDate = require('./parse-date');
let parseParameter = require('./parse-parameter');

let handlers = globSync('cli-parsers/*.js').map(function(handlerPath) {
    return require(resolvePath(handlerPath));
});

module.exports = function(args) {
    let event = {};

    let dateTime = (function() {
        let dateTime = parseDate(args[0]);

        if(dateTime) {
            dateTime = moment(dateTime);
            args.shift();
        }
        else {
            dateTime = moment();
        }

        return dateTime;
    })();

    event.type = args.shift();

    args.forEach(function(arg) {
        var keyValue = /^([^:]+):(.+)$/.exec(arg);

        if(keyValue) {
            event[keyValue[1]] = parseParameter(keyValue[2]);
        }
        else {
            event.positionals = event.positionals || [];
            event.positionals.push(parseParameter(arg));
        }
    });

    handlers.forEach(function(handler) {
        try {
            handler.call(event);
        }
        catch(error) {
            if(!error.assertionError) {
                throw error;
            }

            console.error(error.message);
            process.exit(-1);
        }
    });

    {
        let fileName;
        let maxCount = 999;

        for(let i = 0; i < maxCount; ++i) {
            fileName = (
                dateTime.format(dateTimeFormat) +
                '.' + pad(3, i, '0') + '.ev.json'
            );

            if(!fs.existsSync(fileName)) {
                break;
            }
            else
            if(i === maxCount - 1) {
                throw new Error("Too many events for a second.");
            }
        }

        fs.writeFileSync(fileName, JSON.stringify(event, null, 4));
    }
};
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const debug = require('@tryghost/debug')('api:v2:utils:validators:input:users');
const i18n = require('../../../../../../shared/i18n');
const errors = require('@tryghost/errors');

module.exports = {
    changePassword(apiConfig, frame) {
        debug('changePassword');

        const data = frame.data.password[0];

        if (data.newPassword !== data.ne2Password) {
            return Promise.reject(new errors.ValidationError({
                message: i18n.t('errors.models.user.newPasswordsDoNotMatch')
            }));
        }
    }
};
<commit_msg>Replace i18n with tpl in api/v2/utils/validators/input/users.js (#13589)<commit_after>const Promise = require('bluebird');
const debug = require('@tryghost/debug')('api:v2:utils:validators:input:users');
const tpl = require('@tryghost/tpl');
const errors = require('@tryghost/errors');
const messages = {
    newPasswordsDoNotMatch: 'Your new passwords do not match'
};

module.exports = {
    changePassword(apiConfig, frame) {
        debug('changePassword');

        const data = frame.data.password[0];

        if (data.newPassword !== data.ne2Password) {
            return Promise.reject(new errors.ValidationError({
                message: tpl(messages.newPasswordsDoNotMatch)
            }));
        }
    }
};
<|endoftext|>


<commit_before>var GENEMAP = GENEMAP || {};

GENEMAP.GeneMap = function(userConfig) {
    var defaultConfig = {
      width: "100%",
      height: "100%",
      longestChromosomeHeight: 500,
      chromosomePerRow: 6,
      chromosomeWidth: 24,
      annotationWidth: 200,
      labelHeight: 20,
      margin: {top: 20, left: 20, bottom: 20, right: 20},
      spacing: {horizontal: 20, vertical: 20},
    };

    var config = _.merge({}, defaultConfig, userConfig);

    // An SVG representation of a chromosome with banding data. This won't create an SVG
    // element, it expects that to already have been created.
    function my(selection) {
      selection.each(function(d, i){

        // var y = d3.scale.linear().range([0, config.height]).domain([0, +d.length]);
        svg = d3.select(this).selectAll("svg").data([d]);

        var svgEnter = svg.enter().append("svg").append("g").classed("zoom_window", true).append("rect").classed('drawing_outline', true);

        svg.attr("width", config.width)
           .attr("height", config.height)
           .attr("style", "background-color:none");

        var container = svg.select(".zoom_window");

        // setup the containers for each of the chromosomes
        var bbox = svg.node().getBoundingClientRect();

        // update the layout object with the new settings
        var layoutConfig = _.pick(config, ['margin', 'spacing', 'longestChromosomeHeight', 'chromosomeWidth', 'annotationWidth', 'chromosomePerRow', 'labelHeight']);
        var layoutGenerator = GENEMAP.MapLayout(layoutConfig)
          .width(bbox.width)
          .height(bbox.height);

        var layout = layoutGenerator.generateLayout(d.chromosomes.length);

        container.select(".drawing_outline").attr({
          width: layout.drawing.width,
          height: layout.drawing.height
        }).style({
          fill:'#fafafa',
          "stroke": "#ccc",
          "stroke-width": 0.5
        })

        // basic zooming functionality
        var zoom = d3.behavior.zoom().scaleExtent([1, 8]);
        var onZoom = function() {
          var translate = d3.event.translate;

          // padding the size of the drawing with 1/2 the bbox so you can
          // center on any point of the drawing. Also taking margins into
          // account to further center the view.
          var minx = -layout.drawing.width * d3.event.scale + (bbox.width + config.margin.right)/2;
          var maxx = bbox.width/2 - config.margin.left;
          translate[0] = _.clamp(translate[0], minx, maxx);

          var miny = -layout.drawing.height * d3.event.scale + (bbox.height + config.margin.bottom)/2 ;
          var maxy = bbox.height/2 - config.margin.top;
          translate[1] = _.clamp(translate[1], miny, maxy);

          zoom.translate(translate);
          container.attr("transform", "translate(" + zoom.translate() + ")scale(" + d3.event.scale + ")");
        }
        zoom.on("zoom", onZoom);

        svg.call(zoom)

        var chromosomeContainers = container.selectAll("g.container").data(d.chromosomes)

        chromosomeContainers.enter().append("g").classed("container", true);

        chromosomeContainers.attr({
          transform: function(d, i){
            var xPos = layout.chromosomes[i].x;
            var yPos = layout.chromosomes[i].y;
            return "translate(" + xPos + ","+ yPos +")";
          }
        });

        // draw the chromosomes
        var longest = Math.max.apply(null, d.chromosomes.map(function(c){ return c.length; }));
        var chromosomeScale = d3.scale.linear().range([0, config.longestChromosomeHeight]).domain([0, longest]);

        var chromosomeDrawer = GENEMAP.Chromosome({ labelHeight: config.labelHeight })
          .width(config.chromosomeWidth)
          .yScale(chromosomeScale);

        chromosomeContainers.call(chromosomeDrawer);

        chromosomeContainers.exit().remove();
      });
    }

    my.width = function(value) {
      if (!arguments.length) return config.width;
      config.width = value;
      return my;
    }

    my.height = function(value) {
      if (!arguments.length) return config.height;
      config.height = value;
      return my;
    }

    my.maxChromosomeHeight = function(value) {
      if (!arguments.length) return config.maxChromosomeHeight;
      config.maxChromosomeHeight = value;
      return my;
    }

    return my;
};
<commit_msg>Refactored the genemap code to separate out some functions.<commit_after>var GENEMAP = GENEMAP || {};

GENEMAP.GeneMap = function(userConfig) {
    var defaultConfig = {
      width: "100%",
      height: "100%",
      longestChromosomeHeight: 500,
      chromosomePerRow: 6,
      chromosomeWidth: 24,
      annotationWidth: 200,
      labelHeight: 20,
      margin: {top: 20, left: 20, bottom: 20, right: 20},
      spacing: {horizontal: 20, vertical: 20},
    };

    var config = _.merge({}, defaultConfig, userConfig);

    var layout; // the layout to be used;
    var svg; // the top SVG element
    var zoom; // the zoom behaviour
    var container; // the g container that performs the zooming

    var onZoom = function() {
      var translate = d3.event.translate;

      if (layout) {
        var bbox = svg.node().getBoundingClientRect();

        // padding the size of the drawing with 1/2 the bbox so you can
        // center on any point of the drawing. Also taking margins into
        // account to further center the view.
        var minx = -layout.drawing.width * d3.event.scale + (bbox.width + config.margin.right)/2;
        var maxx = bbox.width/2 - config.margin.left;
        translate[0] = _.clamp(translate[0], minx, maxx);

        var miny = -layout.drawing.height * d3.event.scale + (bbox.height + config.margin.bottom)/2 ;
        var maxy = bbox.height/2 - config.margin.top;
        translate[1] = _.clamp(translate[1], miny, maxy);
      }

      zoom.translate(translate);
      container.attr("transform", "translate(" + zoom.translate() + ")scale(" + d3.event.scale + ")");
    };

    // Sets the attributes on the .drawing_outline rectangle for the outline
    var drawDocumentOutline = function() {

      container.select(".drawing_outline").attr({
        width: layout.drawing.width,
        height: layout.drawing.height,
        'vector-effect': 'non-scaling-stroke'
      }).style({
        fill:'#fafafa',
        "stroke": "#ccc",
        "stroke-width": 0.5
      })
    }

    var constructSkeletonChart = function(mapContainer) {
      mapContainer
        .append("svg")
        .append("g").classed("zoom_window", true)
        .append("rect").classed('drawing_outline', true);

      // basic zooming functionality
      zoom = d3.behavior.zoom().scaleExtent([0.01, 8]);
      zoom.on("zoom", onZoom);
      mapContainer.select('svg').call(zoom);
    }

    // An SVG representation of a chromosome with banding data. This won't create an SVG
    // element, it expects that to already have been created.
    function my(selection) {
      selection.each(function(d, i){

        if (!d3.select(this).select('svg').node()){
          constructSkeletonChart(d3.select(this));
        }

        svg = d3.select(this).select("svg").datum(d);

        svg.attr("width", config.width)
           .attr("height", config.height)
           .attr("style", "background-color:none");

        container = svg.select(".zoom_window");

        // setup the containers for each of the chromosomes
        var bbox = svg.node().getBoundingClientRect();

        // update the layout object with the new settings
        var layoutConfig = _.pick(config, ['margin', 'spacing', 'longestChromosomeHeight', 'chromosomeWidth', 'annotationWidth', 'chromosomePerRow', 'labelHeight']);
        var layoutGenerator = GENEMAP.MapLayout(layoutConfig)
          .width(bbox.width)
          .height(bbox.height);

        layout = layoutGenerator.generateLayout(d.chromosomes.length);

        drawDocumentOutline();

        var chromosomeContainers = container.selectAll("g.container").data(d.chromosomes)

        chromosomeContainers.enter().append("g").classed("container", true);

        chromosomeContainers.attr({
          transform: function(d, i){
            var xPos = layout.chromosomes[i].x;
            var yPos = layout.chromosomes[i].y;
            return "translate(" + xPos + ","+ yPos +")";
          }
        });

        // draw the chromosomes
        var longest = Math.max.apply(null, d.chromosomes.map(function(c){ return c.length; }));
        var chromosomeScale = d3.scale.linear().range([0, config.longestChromosomeHeight]).domain([0, longest]);

        var chromosomeDrawer = GENEMAP.Chromosome({ labelHeight: config.labelHeight })
          .width(config.chromosomeWidth)
          .yScale(chromosomeScale);

        chromosomeContainers.call(chromosomeDrawer);

        chromosomeContainers.exit().remove();
      });
    }

    my.width = function(value) {
      if (!arguments.length) return config.width;
      config.width = value;
      return my;
    }

    my.height = function(value) {
      if (!arguments.length) return config.height;
      config.height = value;
      return my;
    }

    my.maxChromosomeHeight = function(value) {
      if (!arguments.length) return config.maxChromosomeHeight;
      config.maxChromosomeHeight = value;
      return my;
    }

    return my;
};
<|endoftext|>