lib/get-transitive-data.js
function clusterJourneys ({
  journeys,
  log,
  patterns,
  stops
}: {
  journeys: Array<Journey>,
  log: Function,
  patterns: Array<Pattern>,
  stops: Array<Stop>
}) {
  // perform hierarchical clustering on journeys
  // see e.g. James et al., _An Introduction to Statistical Learning, with Applications in R_. New York: Springer, 2013, pg. 395.
  // convert to arrays
  const clusters = journeys.map((j) => [j])
  const inputSize = journeys.length

  // prevent infinite loop, makes sense only to loop until there's just one cluster left
  while (clusters.length > 1) {
    // find the minimum dissimilarity
    let minDis = Infinity
    let minI = 0
    let minJ = 0

    for (let i = 1; i < clusters.length; i++) {
      for (let j = 0; j < i; j++) {
        const d = getClusterDissimilarity(clusters[i], clusters[j], {patterns, stops})

        if (d < minDis) {
          minDis = d
          minI = i
          minJ = j
        }
      }
    }

    log(`dissimilarity ${minDis}`)
    if (minDis > MAX_DISSIMILARITY) break

    // cluster the least dissimilar clusters
    clusters[minI] = clusters[minI].concat(clusters[minJ])
    clusters.splice(minJ, 1) // remove clusters[j]
  }

  log(`replaced ${inputSize} journeys with ${clusters.length} clusters`)
  // merge journeys together
  return clusters.map((c) => {
    return c.reduce((j1, j2) => {
      for (let i = 0; i < j1.segments.length; i++) {
        if (j1.segments[i].type !== 'TRANSIT') continue

        // convert to pattern groups
        if (!j1.segments[i].patterns) {
          j1.segments[i].patterns = [{...j1.segments[i]}]
          j1.segments[i].pattern_id =
            j1.segments[i].from_stop_index =
            j1.segments[i].to_stop_index =
            undefined
        }

        // don't modify from and to indices, Transitive will use the stops from the first pattern
        // TODO replace with "places" (e.g. "Farragut Square Area")
        j1.segments[i].patterns.push({...j2.segments[i]})
      }

      return j1
    })
  })
}
function clusterJourneys ({
  journeys,
  log,
  patterns,
  stops
}: {
  journeys: Array<Journey>,
  log: Function,
  patterns: Array<Pattern>,
  stops: Array<Stop>
}) {
  // perform hierarchical clustering on journeys
  // see e.g. James et al., _An Introduction to Statistical Learning, with Applications in R_. New York: Springer, 2013, pg. 395.
  // convert to arrays
  const clusters = journeys.map((j) => [j])
  const inputSize = journeys.length

  // prevent infinite loop, makes sense only to loop until there's just one cluster left
  while (clusters.length > 1) {
    // find the minimum dissimilarity
    let minDis = Infinity
    let minI = 0
    let minJ = 0

    for (let i = 1; i < clusters.length; i++) {
      for (let j = 0; j < i; j++) {
        const d = getClusterDissimilarity(clusters[i], clusters[j], {patterns, stops})

        if (d < minDis) {
          minDis = d
          minI = i
          minJ = j
        }
      }
    }

    log(`dissimilarity ${minDis}`)
    if (minDis > MAX_DISSIMILARITY) break

    // cluster the least dissimilar clusters
    clusters[minI] = clusters[minI].concat(clusters[minJ])
    clusters.splice(minJ, 1) // remove clusters[j]
  }

  log(`replaced ${inputSize} journeys with ${clusters.length} clusters`)
  // merge journeys together
  return clusters.map((c) => {
    return c.reduce((j1, j2) => {
      for (let i = 0; i < j1.segments.length; i++) {
        if (j1.segments[i].type !== 'TRANSIT') continue

        // convert to pattern groups
        if (!j1.segments[i].patterns) {
          j1.segments[i].patterns = [{...j1.segments[i]}]
          j1.segments[i].pattern_id =
            j1.segments[i].from_stop_index =
            j1.segments[i].to_stop_index =
            undefined
        }

        // don't modify from and to indices, Transitive will use the stops from the first pattern
        // TODO replace with "places" (e.g. "Farragut Square Area")
        j1.segments[i].patterns.push({...j2.segments[i]})
      }

      return j1
    })
  })
}
Bundle similar journeys in transitive data together. Works by computing a
score for each segment based on where the endpoints are relative to each
other. It might also make sense to use a metric based on speed so that a very
slow bus isn't bundled with a fast train, but we don't currently do this.