conveyal/modeify

View on GitHub
lib/plan/r5-to-otp.js

Summary

Maintainability
C
1 day
Test Coverage
const polyline = require('polyline')
const populateTransitSegments = require('../otp').populateTransitSegments

module.exports = function (r5) {
  const routeIdxToId = {}
  r5.patterns.forEach((pattern) => {
    routeIdxToId[pattern.routeIdx] = pattern.routeId
  })

  const optionsSplitByAccessAndEgressMode = r5.options.reduce((options, option) => {
    for (let i = 0; i < option.access.length; i++) {
      options.push(Object.assign({}, option, {
        access: [option.access[i]],
        itinerary: [option.itinerary[i] || option.itinerary[0]]
      }))
    }
    return options
  }, [])

  const profile = optionsSplitByAccessAndEgressMode.map((option) => {
    if (option.transit !== null && option.transit.length > 0) {
      const legs = option.transit.length
      const totalWaitingTime = option.itinerary[0].waitingTime
      const totalTransitTime = option.itinerary[0].transitTime
      const totalWalkTime = option.itinerary[0].walkTime
      option.transit = option.transit.map((transitSegment) => {
        if (transitSegment.mode === null) transitSegment.mode = 'SUBWAY'
        if (transitSegment.waitStats === null) transitSegment.waitStats = stats(totalWaitingTime / legs)
        if (transitSegment.rideStats === null) transitSegment.rideStats = stats(totalTransitTime / legs)
        transitSegment.routes = transitSegment.routes.map((route) => Object.assign({}, route, {
          id: routeIdxToId[route.routeIdx]
        }))
        transitSegment.walkTime = totalWalkTime / legs
        transitSegment.walkDistance = 0
        transitSegment.segmentPatterns = transitSegment.segmentPatterns.map((pattern) => Object.assign({}, pattern, {
          routeId: routeIdxToId[pattern.routeIdx]
        }))
        transitSegment.fromName = transitSegment.from.name
        transitSegment.toName = transitSegment.to.name
        return transitSegment
      })
    } else {
      delete option.transit
    }
    formatPortion(option, 'access')
    formatPortion(option, 'egress')

    return option
  })

  const routes = profile
    .filter((option) => option.transit && option.transit.length > 0)
    .reduce((legs, option) => legs.concat(option.transit), [])
    .reduce((routes, leg) => routes.concat(leg.routes), [])
    .map((route) => Object.assign({}, route, {
      id: routeIdxToId[route.routeIdx],
      color: route.routeColor
    }))
  const patterns = r5.patterns.map((pattern) => {
    return Object.assign({}, pattern, {
      id: String(pattern.tripPatternIdx)
    })
  })
  return {
    profile: populateTransitSegments(profile, patterns, routes),
    routes,
    patterns
  }
}

function stats (val) {
  return {
    avg: val,
    min: val,
    max: val,
    num: val
  }
}

function formatPortion (option, portion) {
  if (option[portion] && option[portion].length > 0) {
    option[portion] // access or egress
      .forEach((leg) => {
        leg.time = leg.duration || 0
        if (leg.streetEdges && leg.streetEdges.length > 0) {
          leg.streetEdges = leg.streetEdges.map(formatStreetEdge)
        }
      })
  }
}

function formatStreetEdge (streetEdge) {
  if (streetEdge.geometry === undefined) {
    const line = polyline.decode(streetEdge.geometryPolyline)
    streetEdge.geometry = {
      points: streetEdge.geometryPolyline,
      length: line.length
    }
    delete streetEdge.geometryPolyline
    streetEdge.distance = streetEdge.distance / 1000 // to meters
  }
  return streetEdge
}