diff --git a/__tests__/util/__mocks__/itinerary.json b/__tests__/util/__mocks__/itinerary.json
deleted file mode 100644
index c00a605a4..000000000
--- a/__tests__/util/__mocks__/itinerary.json
+++ /dev/null
@@ -1,91 +0,0 @@
-{
- "route1": {
- "longName": "Across town",
- "mode": "BUS",
- "shortName": "10",
- "sortOrder": 10
- },
- "route2": {
- "longName": "Around town",
- "mode": "BUS",
- "shortName": "20",
- "sortOrder": 2
- },
- "route3": {
- "longName": "Around another town",
- "shortName": "3",
- "sortOrder": -999,
- "type": 3
- },
- "route4": {
- "longName": "Loop route",
- "mode": "BUS",
- "shortName": "2",
- "sortOrder": -999
- },
- "route5": {
- "longName": "A-line",
- "mode": "BUS",
- "shortName": "A",
- "sortOrder": -999
- },
- "route6": {
- "longName": "B-line",
- "mode": "BUS",
- "shortName": "B",
- "sortOrder": -999
- },
- "route7": {
- "longName": "A meandering route",
- "mode": "BUS",
- "sortOrder": -999
- },
- "route8": {
- "longName": "Zig-zagging route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2
- },
- "route9": {
- "longName": "Express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2
- },
- "route10": {
- "longName": "Variation of express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2
- },
- "route11": {
- "longName": "Local route",
- "mode": "BUS",
- "shortName": "6",
- "sortOrder": 2
- },
- "route12": {
- "longName": "Intercity Train",
- "mode": "RAIL",
- "shortName": "IC",
- "sortOrder": 2
- },
- "route13": {
- "longName": "Yellow line Subway",
- "mode": "SUBWAY",
- "shortName": "Yellow",
- "sortOrder": 2
- },
- "route14": {
- "longName": "Xpress route C",
- "mode": "BUS",
- "shortName": "30C",
- "sortOrder": 2
- },
- "route15": {
- "longName": "Express route X",
- "mode": "BUS",
- "shortName": "30X",
- "sortOrder": 2
- }
-}
diff --git a/__tests__/util/__snapshots__/itinerary.js.snap b/__tests__/util/__snapshots__/itinerary.js.snap
deleted file mode 100644
index 6887d0868..000000000
--- a/__tests__/util/__snapshots__/itinerary.js.snap
+++ /dev/null
@@ -1,247 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`util > itinerary routeComparator should prioritize routes with integer shortNames over alphabetic shortNames 1`] = `
-Array [
- Object {
- "longName": "A-line",
- "mode": "BUS",
- "shortName": "A",
- "sortOrder": -999,
- },
- Object {
- "longName": "Loop route",
- "mode": "BUS",
- "shortName": "2",
- "sortOrder": -999,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should prioritize routes with shortNames over those with just longNames 1`] = `
-Array [
- Object {
- "longName": "B-line",
- "mode": "BUS",
- "shortName": "B",
- "sortOrder": -999,
- },
- Object {
- "longName": "A meandering route",
- "mode": "BUS",
- "sortOrder": -999,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should prioritize routes with valid sortOrder 1`] = `
-Array [
- Object {
- "longName": "Around town",
- "mode": "BUS",
- "shortName": "20",
- "sortOrder": 2,
- },
- Object {
- "longName": "Around another town",
- "shortName": "3",
- "sortOrder": -999,
- "type": 3,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort based off of route type 1`] = `
-Array [
- Object {
- "longName": "Yellow line Subway",
- "mode": "SUBWAY",
- "shortName": "Yellow",
- "sortOrder": 2,
- },
- Object {
- "longName": "Intercity Train",
- "mode": "RAIL",
- "shortName": "IC",
- "sortOrder": 2,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes based off of integer shortName 1`] = `
-Array [
- Object {
- "longName": "Loop route",
- "mode": "BUS",
- "shortName": "2",
- "sortOrder": -999,
- },
- Object {
- "longName": "Around another town",
- "shortName": "3",
- "sortOrder": -999,
- "type": 3,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes based off of longNames 1`] = `
-Array [
- Object {
- "longName": "Express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2,
- },
- Object {
- "longName": "Variation of express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes based off of shortNames 1`] = `
-Array [
- Object {
- "longName": "A-line",
- "mode": "BUS",
- "shortName": "A",
- "sortOrder": -999,
- },
- Object {
- "longName": "B-line",
- "mode": "BUS",
- "shortName": "B",
- "sortOrder": -999,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes based off of sortOrder 1`] = `
-Array [
- Object {
- "longName": "Around town",
- "mode": "BUS",
- "shortName": "20",
- "sortOrder": 2,
- },
- Object {
- "longName": "Across town",
- "mode": "BUS",
- "shortName": "10",
- "sortOrder": 10,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes on all of the criteria at once 1`] = `
-Array [
- Object {
- "longName": "Yellow line Subway",
- "mode": "SUBWAY",
- "shortName": "Yellow",
- "sortOrder": 2,
- },
- Object {
- "longName": "Intercity Train",
- "mode": "RAIL",
- "shortName": "IC",
- "sortOrder": 2,
- },
- Object {
- "longName": "Local route",
- "mode": "BUS",
- "shortName": "6",
- "sortOrder": 2,
- },
- Object {
- "longName": "Around town",
- "mode": "BUS",
- "shortName": "20",
- "sortOrder": 2,
- },
- Object {
- "longName": "Express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2,
- },
- Object {
- "longName": "Variation of express route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2,
- },
- Object {
- "longName": "Zig-zagging route",
- "mode": "BUS",
- "shortName": "30",
- "sortOrder": 2,
- },
- Object {
- "longName": "Xpress route C",
- "mode": "BUS",
- "shortName": "30C",
- "sortOrder": 2,
- },
- Object {
- "longName": "Express route X",
- "mode": "BUS",
- "shortName": "30X",
- "sortOrder": 2,
- },
- Object {
- "longName": "Across town",
- "mode": "BUS",
- "shortName": "10",
- "sortOrder": 10,
- },
- Object {
- "longName": "A-line",
- "mode": "BUS",
- "shortName": "A",
- "sortOrder": -999,
- },
- Object {
- "longName": "B-line",
- "mode": "BUS",
- "shortName": "B",
- "sortOrder": -999,
- },
- Object {
- "longName": "Loop route",
- "mode": "BUS",
- "shortName": "2",
- "sortOrder": -999,
- },
- Object {
- "longName": "Around another town",
- "shortName": "3",
- "sortOrder": -999,
- "type": 3,
- },
- Object {
- "longName": "A meandering route",
- "mode": "BUS",
- "sortOrder": -999,
- },
-]
-`;
-
-exports[`util > itinerary routeComparator should sort routes with alphanumeric shortNames 1`] = `
-Array [
- Object {
- "longName": "Xpress route C",
- "mode": "BUS",
- "shortName": "30C",
- "sortOrder": 2,
- },
- Object {
- "longName": "Express route X",
- "mode": "BUS",
- "shortName": "30X",
- "sortOrder": 2,
- },
-]
-`;
diff --git a/__tests__/util/itinerary.js b/__tests__/util/itinerary.js
deleted file mode 100644
index 74e5d5a56..000000000
--- a/__tests__/util/itinerary.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import {isTransit, routeComparator} from '../../lib/util/itinerary'
-
-const {
- route1,
- route2,
- route3,
- route4,
- route5,
- route6,
- route7,
- route8,
- route9,
- route10,
- route11,
- route12,
- route13,
- route14,
- route15
-} = require('./__mocks__/itinerary.json')
-
-function sortRoutes (...routes) {
- routes.sort(routeComparator)
- return routes
-}
-
-describe('util > itinerary', () => {
- it('isTransit should work', () => {
- expect(isTransit('CAR')).toBeFalsy()
- })
-
- describe('routeComparator', () => {
- it('should sort routes based off of sortOrder', () => {
- expect(sortRoutes(route1, route2)).toMatchSnapshot()
- })
-
- it('should prioritize routes with valid sortOrder', () => {
- expect(sortRoutes(route2, route3)).toMatchSnapshot()
- })
-
- it('should sort routes based off of integer shortName', () => {
- expect(sortRoutes(route3, route4)).toMatchSnapshot()
- })
-
- it('should prioritize routes with integer shortNames over alphabetic shortNames', () => {
- expect(sortRoutes(route4, route5)).toMatchSnapshot()
- })
-
- it('should sort routes based off of shortNames', () => {
- expect(sortRoutes(route5, route6)).toMatchSnapshot()
- })
-
- it('should sort routes with alphanumeric shortNames', () => {
- expect(sortRoutes(route14, route15)).toMatchSnapshot()
- })
-
- it('should prioritize routes with shortNames over those with just longNames', () => {
- expect(sortRoutes(route6, route7)).toMatchSnapshot()
- })
-
- it('should sort routes based off of longNames', () => {
- expect(sortRoutes(route9, route10)).toMatchSnapshot()
- })
-
- it('should sort routes on all of the criteria at once', () => {
- expect(sortRoutes(
- route1,
- route2,
- route3,
- route4,
- route5,
- route6,
- route7,
- route8,
- route9,
- route10,
- route11,
- route12,
- route13,
- route14,
- route15
- )).toMatchSnapshot()
- })
-
- it('should sort based off of route type', () => {
- expect(sortRoutes(route12, route13)).toMatchSnapshot()
- })
- })
-})
diff --git a/lib/actions/api.js b/lib/actions/api.js
index 705822ecb..988e9900e 100644
--- a/lib/actions/api.js
+++ b/lib/actions/api.js
@@ -1,20 +1,22 @@
/* globals fetch */
import { push, replace } from 'connected-react-router'
+import haversine from 'haversine'
+import moment from 'moment'
import hash from 'object-hash'
+import coreUtils from '@opentripplanner/core-utils'
+import queryParams from '@opentripplanner/core-utils/lib/query-params'
import { createAction } from 'redux-actions'
import qs from 'qs'
-import moment from 'moment'
-import haversine from 'haversine'
import { rememberPlace } from './map'
-import { hasCar } from '../util/itinerary'
-import { getTripOptionsFromQuery, getUrlParams } from '../util/query'
-import queryParams from '../util/query-params'
import { getStopViewerConfig, queryIsValid } from '../util/state'
-import { randId } from '../util/storage'
-import { OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT } from '../util/time'
if (typeof (fetch) === 'undefined') require('isomorphic-fetch')
+const { hasCar } = coreUtils.itinerary
+const { getTripOptionsFromQuery, getUrlParams } = coreUtils.query
+const { randId } = coreUtils.storage
+const { OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT } = coreUtils.time
+
// Generic API actions
export const nonRealtimeRoutingResponse = createAction('NON_REALTIME_ROUTING_RESPONSE')
diff --git a/lib/actions/form.js b/lib/actions/form.js
index 66860f1a4..abaa4ff00 100644
--- a/lib/actions/form.js
+++ b/lib/actions/form.js
@@ -1,18 +1,10 @@
import debounce from 'lodash.debounce'
+import isEqual from 'lodash.isequal'
import moment from 'moment'
+import coreUtils from '@opentripplanner/core-utils'
import { createAction } from 'redux-actions'
-import isEqual from 'lodash.isequal'
-import {
- getDefaultQuery,
- getTripOptionsFromQuery,
- getUrlParams,
- planParamsToQuery
-} from '../util/query'
-import { getItem, randId } from '../util/storage'
import { queryIsValid } from '../util/state'
-import { OTP_API_TIME_FORMAT } from '../util/time'
-import { isMobile } from '../util/ui'
import {
MobileScreens,
setMainPanelContent,
@@ -21,6 +13,13 @@ import {
import { routingQuery } from './api'
+const {
+ getDefaultQuery,
+ getTripOptionsFromQuery,
+ getUrlParams,
+ planParamsToQuery
+} = coreUtils.query
+
export const settingQueryParam = createAction('SET_QUERY_PARAM')
export const clearActiveSearch = createAction('CLEAR_ACTIVE_SEARCH')
export const setActiveSearch = createAction('SET_ACTIVE_SEARCH')
@@ -35,7 +34,7 @@ export function resetForm () {
dispatch(settingQueryParam(otpState.user.defaults))
} else {
// Get user overrides and apply to default query
- const userOverrides = getItem('defaultQuery', {})
+ const userOverrides = coreUtils.storage.getItem('defaultQuery', {})
const defaultQuery = Object.assign(
getDefaultQuery(otpState.config),
userOverrides
@@ -69,7 +68,7 @@ export function parseUrlQueryString (params = getUrlParams()) {
Object.keys(params).forEach(key => {
if (!key.startsWith('ui_')) planParams[key] = params[key]
})
- const searchId = params.ui_activeSearch || randId()
+ const searchId = params.ui_activeSearch || coreUtils.storage.randId()
// Convert strings to numbers/objects and dispatch
dispatch(
setQueryParam(
@@ -89,10 +88,11 @@ let lastDebouncePlanTimeMs
export function formChanged (oldQuery, newQuery) {
return function (dispatch, getState) {
const otpState = getState().otp
+ const isMobile = coreUtils.ui.isMobile()
// If departArrive is set to 'NOW', update the query time to current
if (otpState.currentQuery && otpState.currentQuery.departArrive === 'NOW') {
- dispatch(settingQueryParam({ time: moment().format(OTP_API_TIME_FORMAT) }))
+ dispatch(settingQueryParam({ time: moment().format(coreUtils.time.OTP_API_TIME_FORMAT) }))
}
// Determine if either from/to location has changed
@@ -111,7 +111,7 @@ export function formChanged (oldQuery, newQuery) {
// either location changes only if not currently on welcome screen (otherwise
// when the current position is auto-set the screen will change unexpectedly).
if (
- isMobile() &&
+ isMobile &&
(fromChanged || toChanged) &&
otpState.ui.mobileScreen !== MobileScreens.WELCOME_SCREEN
) {
@@ -123,8 +123,8 @@ export function formChanged (oldQuery, newQuery) {
const { autoPlan, debouncePlanTimeMs } = otpState.config
const updatePlan =
autoPlan ||
- (!isMobile() && oneLocationChanged) || // TODO: make autoplan configurable at the parameter level?
- (isMobile() && fromChanged && toChanged)
+ (!isMobile && oneLocationChanged) || // TODO: make autoplan configurable at the parameter level?
+ (isMobile && fromChanged && toChanged)
if (updatePlan && queryIsValid(otpState)) { // trip plan should be made
// check if debouncing function needs to be (re)created
if (!debouncedPlanTrip || lastDebouncePlanTimeMs !== debouncePlanTimeMs) {
diff --git a/lib/actions/map.js b/lib/actions/map.js
index fce9deb91..f3ec18447 100644
--- a/lib/actions/map.js
+++ b/lib/actions/map.js
@@ -1,9 +1,9 @@
+import coreUtils from '@opentripplanner/core-utils'
import getGeocoder from '@opentripplanner/geocoder'
import { createAction } from 'redux-actions'
import { routingQuery } from './api'
import { clearActiveSearch } from './form'
-import { constructLocation } from '../util/map'
/* SET_LOCATION action creator. Updates a from or to location in the store
*
@@ -116,7 +116,7 @@ export const setElevationPoint = createAction('SET_ELEVATION_POINT')
export const setMapPopupLocation = createAction('SET_MAP_POPUP_LOCATION')
export function setMapPopupLocationAndGeocode (mapEvent) {
- const location = constructLocation(mapEvent.latlng)
+ const location = coreUtils.map.constructLocation(mapEvent.latlng)
return function (dispatch, getState) {
dispatch(setMapPopupLocation({ location }))
getGeocoder(getState().otp.config.geocoder)
diff --git a/lib/actions/narrative.js b/lib/actions/narrative.js
index 8f016a400..56cc5279c 100644
--- a/lib/actions/narrative.js
+++ b/lib/actions/narrative.js
@@ -1,14 +1,14 @@
+import coreUtils from '@opentripplanner/core-utils'
import { createAction } from 'redux-actions'
import { setUrlSearch } from './api'
-import { getUrlParams } from '../util/query'
export function setActiveItinerary (payload) {
return function (dispatch, getState) {
// Trigger change in store.
dispatch(settingActiveitinerary(payload))
// Update URL params.
- const urlParams = getUrlParams()
+ const urlParams = coreUtils.query.getUrlParams()
urlParams.ui_activeItinerary = payload.index
dispatch(setUrlSearch(urlParams))
}
diff --git a/lib/actions/ui.js b/lib/actions/ui.js
index ad04d2805..9dc8decfe 100644
--- a/lib/actions/ui.js
+++ b/lib/actions/ui.js
@@ -1,13 +1,14 @@
+import { push } from 'connected-react-router'
+import coreUtils from '@opentripplanner/core-utils'
import { createAction } from 'redux-actions'
import { matchPath } from 'react-router'
-import { push } from 'connected-react-router'
import { findRoute } from './api'
import { setMapCenter, setMapZoom, setRouterId } from './config'
import { clearActiveSearch, parseUrlQueryString, setActiveSearch } from './form'
import { clearLocation } from './map'
import { setActiveItinerary } from './narrative'
-import { getUiUrlParams, getUrlParams } from '../util/query'
+import { getUiUrlParams } from '../util/state'
/**
* Wrapper function for history#push that preserves the current search or, if
@@ -103,7 +104,7 @@ export function handleBackButtonPress (e) {
const uiUrlParams = getUiUrlParams(otpState)
// Get new search ID from URL after back button pressed.
// console.log('back button pressed', e)
- const urlParams = getUrlParams()
+ const urlParams = coreUtils.query.getUrlParams()
const previousSearchId = urlParams.ui_activeSearch
const previousItinIndex = +urlParams.ui_activeItinerary || 0
const previousSearch = otpState.searches[previousSearchId]
diff --git a/lib/components/app/print-layout.js b/lib/components/app/print-layout.js
index 2fda1815f..724db3ee3 100644
--- a/lib/components/app/print-layout.js
+++ b/lib/components/app/print-layout.js
@@ -1,4 +1,4 @@
-import TriMetLegIcon from '@opentripplanner/icons/lib/trimet-leg-icon'
+import { TriMetLegIcon } from '@opentripplanner/icons'
import PrintableItinerary from '@opentripplanner/printable-itinerary'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js
index 284200975..eea5f49a7 100644
--- a/lib/components/app/responsive-webapp.js
+++ b/lib/components/app/responsive-webapp.js
@@ -1,20 +1,21 @@
-import React, { Component } from 'react'
+import { ConnectedRouter } from 'connected-react-router'
+import { createHashHistory } from 'history'
+import isEqual from 'lodash.isequal'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { connect } from 'react-redux'
-import isEqual from 'lodash.isequal'
import { Route, Switch, withRouter } from 'react-router'
-import { createHashHistory } from 'history'
-import { ConnectedRouter } from 'connected-react-router'
import PrintLayout from './print-layout'
import { setMapCenter, setMapZoom } from '../../actions/config'
-import { setLocationToCurrent } from '../../actions/map'
-import { getCurrentPosition, receivedPositionResponse } from '../../actions/location'
import { formChanged, parseUrlQueryString } from '../../actions/form'
+import { getCurrentPosition, receivedPositionResponse } from '../../actions/location'
+import { setLocationToCurrent } from '../../actions/map'
import { handleBackButtonPress, matchContentToUrl } from '../../actions/ui'
-import { getUrlParams } from '../../util/query'
-import { getTitle, isMobile } from '../../util/ui'
-import { getActiveItinerary } from '../../util/state'
+import { getActiveItinerary, getTitle } from '../../util/state'
+
+const { isMobile } = coreUtils.ui
class ResponsiveWebapp extends Component {
static propTypes = {
@@ -29,7 +30,7 @@ class ResponsiveWebapp extends Component {
componentDidUpdate (prevProps) {
const { currentPosition, location, query, title } = this.props
document.title = title
- const urlParams = getUrlParams()
+ const urlParams = coreUtils.query.getUrlParams()
const newSearchId = urlParams.ui_activeSearch
// Determine if trip is being replanned by checking the active search ID
// against the ID found in the URL params. If they are different, a new one
diff --git a/lib/components/form/date-time-modal.js b/lib/components/form/date-time-modal.js
index 6b54176c4..0d934e8e5 100644
--- a/lib/components/form/date-time-modal.js
+++ b/lib/components/form/date-time-modal.js
@@ -1,8 +1,7 @@
-// import necessary React/Redux libraries
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { connect } from 'react-redux'
-import { getTimeFormat, getDateFormat } from '@opentripplanner/core-utils/lib/time'
import { setQueryParam } from '../../actions/form'
@@ -41,14 +40,15 @@ class DateTimeModal extends Component {
const mapStateToProps = (state, ownProps) => {
const { departArrive, date, time } = state.otp.currentQuery
+ const config = state.otp.config
return {
- config: state.otp.config,
+ config,
departArrive,
date,
time,
// These props below are for legacy browsers (see render method above).
- timeFormatLegacy: getTimeFormat(state.otp.config),
- dateFormatLegacy: getDateFormat(state.otp.config)
+ timeFormatLegacy: coreUtils.time.getTimeFormat(config),
+ dateFormatLegacy: coreUtils.time.getDateFormat(config)
}
}
diff --git a/lib/components/form/date-time-preview.js b/lib/components/form/date-time-preview.js
index 08ce1d25b..846c8b974 100644
--- a/lib/components/form/date-time-preview.js
+++ b/lib/components/form/date-time-preview.js
@@ -1,15 +1,16 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
import moment from 'moment'
-import { connect } from 'react-redux'
+import coreUtils from '@opentripplanner/core-utils'
+import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
+import { connect } from 'react-redux'
-import {
+const {
OTP_API_DATE_FORMAT,
OTP_API_TIME_FORMAT,
getTimeFormat,
getDateFormat
-} from '../../util/time'
+} = coreUtils.time
class DateTimePreview extends Component {
static propTypes = {
@@ -82,16 +83,17 @@ class DateTimePreview extends Component {
const mapStateToProps = (state, ownProps) => {
const { departArrive, date, time, routingType, startTime, endTime } = state.otp.currentQuery
+ const config = state.otp.config
return {
- config: state.otp.config,
+ config,
routingType,
departArrive,
date,
time,
startTime,
endTime,
- timeFormat: getTimeFormat(state.otp.config),
- dateFormat: getDateFormat(state.otp.config)
+ timeFormat: getTimeFormat(config),
+ dateFormat: getDateFormat(config)
}
}
diff --git a/lib/components/form/default-search-form.js b/lib/components/form/default-search-form.js
index 1f2578231..98be08f8a 100644
--- a/lib/components/form/default-search-form.js
+++ b/lib/components/form/default-search-form.js
@@ -1,9 +1,9 @@
-import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import LocationField from './connected-location-field'
-import SwitchButton from './switch-button'
import TabbedFormPanel from './tabbed-form-panel'
+import SwitchButton from './switch-button'
import defaultIcons from '../icons'
export default class DefaultSearchForm extends Component {
diff --git a/lib/components/form/plan-trip-button.js b/lib/components/form/plan-trip-button.js
index b619e647b..e2a091d0c 100644
--- a/lib/components/form/plan-trip-button.js
+++ b/lib/components/form/plan-trip-button.js
@@ -1,11 +1,11 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import { routingQuery } from '../../actions/api'
import { setMainPanelContent } from '../../actions/ui'
-import { isMobile } from '../../util/ui'
class PlanTripButton extends Component {
static propTypes = {
@@ -23,7 +23,7 @@ class PlanTripButton extends Component {
_onClick = () => {
this.props.routingQuery()
if (typeof this.props.onClick === 'function') this.props.onClick()
- if (!isMobile()) this.props.setMainPanelContent(null)
+ if (!coreUtils.ui.isMobile()) this.props.setMainPanelContent(null)
}
render () {
diff --git a/lib/components/form/settings-preview.js b/lib/components/form/settings-preview.js
index 1cefa4d98..337c5d2b6 100644
--- a/lib/components/form/settings-preview.js
+++ b/lib/components/form/settings-preview.js
@@ -1,10 +1,9 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
-import { isNotDefaultQuery } from '../../util/query'
-
class SettingsPreview extends Component {
static propTypes = {
// component props
@@ -28,7 +27,7 @@ class SettingsPreview extends Component {
render () {
const { config, query, caret, editButtonText } = this.props
// Show dot indicator if the current query differs from the default query.
- let showDot = isNotDefaultQuery(query, config)
+ let showDot = coreUtils.query.isNotDefaultQuery(query, config)
const button = (
diff --git a/lib/components/form/user-settings.js b/lib/components/form/user-settings.js
index fe9e88cb4..71b80e41a 100644
--- a/lib/components/form/user-settings.js
+++ b/lib/components/form/user-settings.js
@@ -1,15 +1,17 @@
-import React, { Component } from 'react'
-import { connect } from 'react-redux'
import moment from 'moment'
+import coreUtils from '@opentripplanner/core-utils'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
+import { connect } from 'react-redux'
import Icon from '../narrative/icon'
import { forgetSearch, toggleTracking } from '../../actions/api'
import { setQueryParam } from '../../actions/form'
import { forgetPlace, forgetStop, setLocation } from '../../actions/map'
import { setViewedStop } from '../../actions/ui'
-import { getDetailText, formatStoredPlaceName, matchLatLon } from '../../util/map'
-import { summarizeQuery } from '../../util/query'
+
+const { getDetailText, formatStoredPlaceName, matchLatLon } = coreUtils.map
+const { summarizeQuery } = coreUtils.query
const BUTTON_WIDTH = 40
diff --git a/lib/components/form/user-trip-settings.js b/lib/components/form/user-trip-settings.js
index 6e5ea2383..130992ccc 100644
--- a/lib/components/form/user-trip-settings.js
+++ b/lib/components/form/user-trip-settings.js
@@ -1,3 +1,4 @@
+import coreUtils from '@opentripplanner/core-utils'
import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
@@ -8,7 +9,6 @@ import {
resetForm,
storeDefaultSettings
} from '../../actions/form'
-import { getTripOptionsFromQuery, isNotDefaultQuery } from '../../util/query'
/**
* This component contains the `Remember/Forget my trip options` and `Restore defaults` commands
@@ -18,7 +18,7 @@ import { getTripOptionsFromQuery, isNotDefaultQuery } from '../../util/query'
*/
class UserTripSettings extends Component {
_toggleStoredSettings = () => {
- const options = getTripOptionsFromQuery(this.props.query)
+ const options = coreUtils.query.getTripOptionsFromQuery(this.props.query)
// If user defaults are set, clear them. Otherwise, store them.
if (this.props.defaults) this.props.clearDefaultSettings()
else this.props.storeDefaultSettings(options)
@@ -34,7 +34,7 @@ class UserTripSettings extends Component {
// Do not permit remembering trip options if they do not differ from the
// defaults and nothing has been stored
- const queryIsDefault = !isNotDefaultQuery(query, config)
+ const queryIsDefault = !coreUtils.query.isNotDefaultQuery(query, config)
const rememberIsDisabled = queryIsDefault && !defaults
return (
diff --git a/lib/components/map/bounds-updating-overlay.js b/lib/components/map/bounds-updating-overlay.js
index 710b7e2d6..6472f62df 100644
--- a/lib/components/map/bounds-updating-overlay.js
+++ b/lib/components/map/bounds-updating-overlay.js
@@ -1,11 +1,11 @@
import isEqual from 'lodash.isequal'
-import { isMobile } from '@opentripplanner/core-utils/lib/ui'
-import { connect } from 'react-redux'
+import coreUtils from '@opentripplanner/core-utils'
import { MapLayer, withLeaflet } from 'react-leaflet'
+import { connect } from 'react-redux'
import {
- getItineraryBounds,
- getLegBounds
+ getLeafletItineraryBounds,
+ getLeafletLegBounds
} from '../../util/itinerary'
import { getActiveItinerary, getActiveSearch } from '../../util/state'
@@ -46,10 +46,10 @@ class BoundsUpdatingOverlay extends MapLayer {
// Fit map to to entire itinerary if active itinerary bounds changed
const newFrom = newProps.query && newProps.query.from
- const newItinBounds = newProps.itinerary && getItineraryBounds(newProps.itinerary)
+ const newItinBounds = newProps.itinerary && getLeafletItineraryBounds(newProps.itinerary)
const newTo = newProps.query && newProps.query.to
const oldFrom = oldProps.query && oldProps.query.from
- const oldItinBounds = oldProps.itinerary && getItineraryBounds(oldProps.itinerary)
+ const oldItinBounds = oldProps.itinerary && getLeafletItineraryBounds(oldProps.itinerary)
const oldTo = oldProps.query && oldProps.query.to
const fromChanged = !isEqual(oldFrom, newFrom)
const toChanged = !isEqual(oldTo, newTo)
@@ -65,7 +65,7 @@ class BoundsUpdatingOverlay extends MapLayer {
newProps.activeLeg !== null
) {
map.fitBounds(
- getLegBounds(newProps.itinerary.legs[newProps.activeLeg]),
+ getLeafletLegBounds(newProps.itinerary.legs[newProps.activeLeg]),
{ padding }
)
@@ -80,7 +80,7 @@ class BoundsUpdatingOverlay extends MapLayer {
// more info.
// TODO: Fix this so mobile devices will also update the bounds to the
// from/to locations.
- if (!isMobile()) {
+ if (!coreUtils.ui.isMobile()) {
map.fitBounds([
[newFrom.lat, newFrom.lon],
[newTo.lat, newTo.lon]
diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js
index 11f8c1f0e..aa7843fd2 100644
--- a/lib/components/map/connected-transitive-overlay.js
+++ b/lib/components/map/connected-transitive-overlay.js
@@ -1,4 +1,4 @@
-import { itineraryToTransitive } from '@opentripplanner/core-utils/lib/map'
+import coreUtils from '@opentripplanner/core-utils'
import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay'
import { connect } from 'react-redux'
@@ -17,7 +17,7 @@ const mapStateToProps = (state, ownProps) => {
) {
const itins = getActiveItineraries(state.otp)
// TODO: prevent itineraryToTransitive() from being called more than needed
- transitiveData = itineraryToTransitive(itins[activeSearch.activeItinerary])
+ transitiveData = coreUtils.map.itineraryToTransitive(itins[activeSearch.activeItinerary])
} else if (
activeSearch &&
activeSearch.response &&
diff --git a/lib/components/map/leg-diagram.js b/lib/components/map/leg-diagram.js
index 901c49608..917d1f3bf 100644
--- a/lib/components/map/leg-diagram.js
+++ b/lib/components/map/leg-diagram.js
@@ -1,12 +1,14 @@
import memoize from 'lodash.memoize'
-import React, {Component} from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, {Component} from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import ReactResizeDetector from 'react-resize-detector'
import { setElevationPoint, showLegDiagram } from '../../actions/map'
-import { getElevationProfile, getTextWidth, legElevationAtDistance } from '../../util/itinerary'
+
+const { getElevationProfile, getTextWidth, legElevationAtDistance } = coreUtils.itinerary
// Fixed dimensions for chart
const height = 160
diff --git a/lib/components/map/stylized-map.js b/lib/components/map/stylized-map.js
index cf4a124fa..54e25509c 100644
--- a/lib/components/map/stylized-map.js
+++ b/lib/components/map/stylized-map.js
@@ -1,13 +1,12 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
import { select, event } from 'd3-selection'
import { zoom } from 'd3-zoom'
-
+import coreUtils from '@opentripplanner/core-utils'
+import PropTypes from 'prop-types'
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
import Transitive from 'transitive-js'
import { getActiveSearch, getActiveItineraries } from '../../util/state'
-import { isBikeshareStation, itineraryToTransitive } from '../../util/map'
var STYLES = {}
@@ -16,7 +15,7 @@ STYLES.places = {
if (
place.getId() !== 'from' &&
place.getId() !== 'to' &&
- !isBikeshareStation(place)
+ !coreUtils.map.isBikeshareStation(place)
) {
return 'none'
}
@@ -117,7 +116,7 @@ const mapStateToProps = (state, ownProps) => {
activeSearch.response.plan
) {
const itins = getActiveItineraries(state.otp)
- transitiveData = itineraryToTransitive(itins[activeSearch.activeItinerary])
+ transitiveData = coreUtils.map.itineraryToTransitive(itins[activeSearch.activeItinerary])
} else if (
activeSearch &&
activeSearch.response &&
diff --git a/lib/components/mobile/results-screen.js b/lib/components/mobile/results-screen.js
index b8c73cc47..e62aa0a7c 100644
--- a/lib/components/mobile/results-screen.js
+++ b/lib/components/mobile/results-screen.js
@@ -1,3 +1,4 @@
+import coreUtils from '@opentripplanner/core-utils'
import LocationIcon from '@opentripplanner/location-icon'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
@@ -16,7 +17,6 @@ import { MobileScreens, setMobileScreen } from '../../actions/ui'
import { setUseRealtimeResponse } from '../../actions/narrative'
import { clearActiveSearch } from '../../actions/form'
import { getActiveSearch, getRealtimeEffects } from '../../util/state'
-import { enableScrollForSelector } from '../../util/ui'
const LocationContainer = styled.div`
font-weight: 300;
@@ -68,7 +68,7 @@ class MobileResultsScreen extends Component {
// Get the target element that we want to persist scrolling for
// FIXME Do we need to add something that removes the listeners when
// component unmounts?
- enableScrollForSelector('.mobile-narrative-container')
+ coreUtils.ui.enableScrollForSelector('.mobile-narrative-container')
}
componentDidUpdate (prevProps) {
diff --git a/lib/components/narrative/connected-trip-details.js b/lib/components/narrative/connected-trip-details.js
index 16183ea1e..f34205233 100644
--- a/lib/components/narrative/connected-trip-details.js
+++ b/lib/components/narrative/connected-trip-details.js
@@ -1,8 +1,7 @@
+import coreUtils from '@opentripplanner/core-utils'
+import TripDetailsBase from '@opentripplanner/trip-details'
import { connect } from 'react-redux'
import styled from 'styled-components'
-import TripDetailsBase from '@opentripplanner/trip-details'
-
-import { getTimeFormat, getLongDateFormat } from '../../util/time'
const TripDetails = styled(TripDetailsBase)`
border: 2px solid gray;
@@ -17,8 +16,8 @@ const mapStateToProps = (state, ownProps) => {
return {
routingType: state.otp.currentQuery.routingType,
tnc: state.otp.tnc,
- timeFormat: getTimeFormat(state.otp.config),
- longDateFormat: getLongDateFormat(state.otp.config)
+ timeFormat: coreUtils.time.getTimeFormat(state.otp.config),
+ longDateFormat: coreUtils.time.getLongDateFormat(state.otp.config)
}
}
diff --git a/lib/components/narrative/default/access-leg.js b/lib/components/narrative/default/access-leg.js
index 9a4258581..1b541635a 100644
--- a/lib/components/narrative/default/access-leg.js
+++ b/lib/components/narrative/default/access-leg.js
@@ -1,11 +1,10 @@
+import coreUtils from '@opentripplanner/core-utils'
import { humanizeDistanceString } from '@opentripplanner/humanize-distance'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
import Icon from '../icon'
import LegDiagramPreview from '../leg-diagram-preview'
-import { getStepInstructions } from '../../../util/itinerary'
-import { formatDuration } from '../../../util/time'
/**
* Default access leg component for narrative itinerary.
@@ -47,7 +46,7 @@ export default class AccessLeg extends Component {
{leg.mode}
{' '}
- {formatDuration(leg.duration)}
+ {coreUtils.time.formatDuration(leg.duration)}
{' '}
({humanizeDistanceString(leg.distance)})
@@ -62,7 +61,7 @@ export default class AccessLeg extends Component {
className={`step ${stepIsActive ? 'active' : ''}`}
onClick={(e) => this._onStepClick(e, step, stepIndex)}>
{humanizeDistanceString(step.distance)}
-
{getStepInstructions(step)}
+
{coreUtils.itinerary.getStepInstructions(step)}
)
})}
diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js
index 57f2717de..efc740c3a 100644
--- a/lib/components/narrative/default/default-itinerary.js
+++ b/lib/components/narrative/default/default-itinerary.js
@@ -1,3 +1,4 @@
+import coreUtils from '@opentripplanner/core-utils'
import React from 'react'
import NarrativeItinerary from '../narrative-itinerary'
@@ -5,7 +6,8 @@ import ItinerarySummary from './itinerary-summary'
import ItineraryDetails from './itinerary-details'
import TripDetails from '../connected-trip-details'
import TripTools from '../trip-tools'
-import { formatDuration, formatTime } from '../../../util/time'
+
+const { formatDuration, formatTime } = coreUtils.time
export default class DefaultItinerary extends NarrativeItinerary {
render () {
diff --git a/lib/components/narrative/default/itinerary-details.js b/lib/components/narrative/default/itinerary-details.js
index 2eec48805..91537b8b6 100644
--- a/lib/components/narrative/default/itinerary-details.js
+++ b/lib/components/narrative/default/itinerary-details.js
@@ -1,9 +1,9 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import AccessLeg from './access-leg'
import TransitLeg from './transit-leg'
-import { isTransit } from '../../../util/itinerary'
export default class ItineraryDetails extends Component {
static propTypes = {
@@ -16,7 +16,7 @@ export default class ItineraryDetails extends Component {
{itinerary.legs.map((leg, index) => {
const legIsActive = activeLeg === index
- return isTransit(leg.mode)
+ return coreUtils.itinerary.isTransit(leg.mode)
?
if (!itineraries) return null
- let views = []
- if (showProfileSummary) {
- views.push(
-
Your Best Options (Swipe to View All)
-
-
)
- }
- views = views.concat(itineraries.map((itinerary, index) => {
+ const views = itineraries.map((itinerary, index) => {
return React.createElement(itineraryClass, {
itinerary,
index,
@@ -74,7 +63,7 @@ class ItineraryCarousel extends Component {
onClick: this._onItineraryClick,
...this.props
})
- }))
+ })
return (
@@ -112,28 +101,18 @@ class ItineraryCarousel extends Component {
const mapStateToProps = (state, ownProps) => {
const activeSearch = getActiveSearch(state.otp)
- let itineraries = null
- let profileOptions = null
- let showProfileSummary = false
- if (activeSearch && activeSearch.response && activeSearch.response.plan) {
- itineraries = getActiveItineraries(state.otp)
- } else if (activeSearch && activeSearch.response && activeSearch.response.otp) {
- profileOptions = activeSearch.response.otp.profile
- itineraries = profileOptionsToItineraries(profileOptions)
- showProfileSummary = true
- }
+ const itineraries = activeSearch && activeSearch.response && activeSearch.response.plan
+ ? getActiveItineraries(state.otp)
+ : null
- const pending = activeSearch && activeSearch.pending
return {
itineraries,
- profileOptions,
- pending,
- showProfileSummary,
+ pending: activeSearch && activeSearch.pending,
activeItinerary: activeSearch && activeSearch.activeItinerary,
activeLeg: activeSearch && activeSearch.activeLeg,
activeStep: activeSearch && activeSearch.activeStep,
companies: state.otp.currentQuery.companies,
- timeFormat: getTimeFormat(state.otp.config)
+ timeFormat: coreUtils.time.getTimeFormat(state.otp.config)
}
}
diff --git a/lib/components/narrative/leg-diagram-preview.js b/lib/components/narrative/leg-diagram-preview.js
index 5a736e5b9..fd624d807 100644
--- a/lib/components/narrative/leg-diagram-preview.js
+++ b/lib/components/narrative/leg-diagram-preview.js
@@ -1,10 +1,10 @@
-import React, {Component} from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, {Component} from 'react'
import { connect } from 'react-redux'
import ReactResizeDetector from 'react-resize-detector'
import { showLegDiagram } from '../../actions/map'
-import { getElevationProfile } from '../../util/itinerary'
const METERS_TO_FEET = 3.28084
@@ -45,7 +45,7 @@ class LegDiagramPreview extends Component {
render () {
const { leg, showElevationProfile } = this.props
if (!showElevationProfile) return null
- const profile = getElevationProfile(leg.steps)
+ const profile = coreUtils.itinerary.getElevationProfile(leg.steps)
// Don't show for very short legs
if (leg.distance < 500 || leg.mode === 'CAR') return null
diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js
index 0c8774da3..e4c92a4b6 100644
--- a/lib/components/narrative/line-itin/connected-itinerary-body.js
+++ b/lib/components/narrative/line-itin/connected-itinerary-body.js
@@ -1,5 +1,5 @@
import isEqual from 'lodash.isequal'
-import TriMetLegIcon from '@opentripplanner/icons/lib/trimet-leg-icon'
+import { TriMetLegIcon } from '@opentripplanner/icons'
import TransitLegSummary from '@opentripplanner/itinerary-body/lib/defaults/transit-leg-summary'
import ItineraryBody from '@opentripplanner/itinerary-body/lib/otp-react-redux/itinerary-body'
import LineColumnContent from '@opentripplanner/itinerary-body/lib/otp-react-redux/line-column-content'
diff --git a/lib/components/narrative/line-itin/itin-summary.js b/lib/components/narrative/line-itin/itin-summary.js
index 2e74575a7..955491975 100644
--- a/lib/components/narrative/line-itin/itin-summary.js
+++ b/lib/components/narrative/line-itin/itin-summary.js
@@ -4,12 +4,8 @@ import React, { Component } from 'react'
import styled from 'styled-components'
import {
- calculateFares,
- calculatePhysicalActivity,
- getLegIcon,
- isTransit
+ getLegIcon
} from '../../../util/itinerary'
-import { formatDuration, formatTime } from '../../../util/time'
// TODO: make this a prop
const defaultRouteColor = '#008'
@@ -48,7 +44,7 @@ const NonTransitSpacer = styled.div`
overflow: hidden
`
-const RoutePreivew = styled.div`
+const RoutePreview = styled.div`
display: inline-block;
margin-left: 8px;
vertical-align: top;
@@ -93,22 +89,22 @@ export default class ItinerarySummary extends Component {
maxTNCFare,
minTNCFare,
transitFare
- } = calculateFares(itinerary)
+ } = coreUtils.itinerary.calculateFares(itinerary)
// TODO: support non-USD
const minTotalFare = minTNCFare * 100 + transitFare
const maxTotalFare = maxTNCFare * 100 + transitFare
- const { caloriesBurned } = calculatePhysicalActivity(itinerary)
+ const { caloriesBurned } = coreUtils.itinerary.calculatePhysicalActivity(itinerary)
return (
{/* Travel time in hrs/mins */}
- {formatDuration(itinerary.duration)}
+ {coreUtils.time.formatDuration(itinerary.duration)}
{/* Duration as time range */}
- {formatTime(itinerary.startTime, timeOptions)} - {formatTime(itinerary.endTime, timeOptions)}
+ {coreUtils.time.formatTime(itinerary.startTime, timeOptions)} - {coreUtils.time.formatTime(itinerary.endTime, timeOptions)}
{/* Fare / Calories */}
@@ -134,9 +130,9 @@ export default class ItinerarySummary extends Component {
return !(leg.mode === 'WALK' && itinerary.transitTime > 0)
}).map((leg, k) => {
return (
-
+
{getLegIcon(leg, customIcons)}
- {isTransit(leg.mode)
+ {coreUtils.itinerary.isTransit(leg.mode)
? (
{getRouteNameForBadge(leg)}
@@ -144,7 +140,7 @@ export default class ItinerarySummary extends Component {
)
: ( )
}
-
+
)
})}
diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js
index 989d726c5..baf6034bd 100644
--- a/lib/components/narrative/line-itin/line-itinerary.js
+++ b/lib/components/narrative/line-itin/line-itinerary.js
@@ -1,3 +1,4 @@
+import coreUtils from '@opentripplanner/core-utils'
import React from 'react'
import styled from 'styled-components'
@@ -5,7 +6,8 @@ import ItineraryBody from './connected-itinerary-body'
import ItinerarySummary from './itin-summary'
import NarrativeItinerary from '../narrative-itinerary'
import SimpleRealtimeAnnotation from '../simple-realtime-annotation'
-import { getLegModeLabel, getTimeZoneOffset, isTransit } from '../../../util/itinerary'
+
+const { getLegModeLabel, getTimeZoneOffset, isTransit } = coreUtils.itinerary
export const LineItineraryContainer = styled.div`
margin-bottom: 20px;
diff --git a/lib/components/narrative/narrative-profile-options.js b/lib/components/narrative/narrative-profile-options.js
deleted file mode 100644
index 405d6609a..000000000
--- a/lib/components/narrative/narrative-profile-options.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
-
-import { setActiveItinerary, setActiveLeg, setActiveStep } from '../../actions/narrative'
-import DefaultItinerary from './default/default-itinerary'
-import NarrativeProfileSummary from './narrative-profile-summary'
-import Loading from './loading'
-import { getActiveSearch } from '../../util/state'
-import { profileOptionsToItineraries } from '../../util/profile'
-
-class NarrativeProfileOptions extends Component {
- static propTypes = {
- options: PropTypes.array,
- query: PropTypes.object,
- itineraryClass: PropTypes.func,
- pending: PropTypes.bool,
- activeOption: PropTypes.number,
- setActiveItinerary: PropTypes.func,
- setActiveLeg: PropTypes.func,
- setActiveStep: PropTypes.func,
- customIcons: PropTypes.object
- }
-
- static defaultProps = {
- itineraryClass: DefaultItinerary
- }
-
- render () {
- const { pending, itineraryClass, query, activeItinerary } = this.props
- if (pending) return
-
- const options = this.props.options
- if (!options) return null
-
- const itineraries = profileOptionsToItineraries(options, query)
-
- return (
-
-
Your best options:
-
-
We found {options.length} total options:
- {itineraries.map((itinerary, index) => {
- return React.createElement(itineraryClass, {
- itinerary,
- index,
- key: index,
- active: index === activeItinerary,
- routingType: 'PROFILE',
- ...this.props
- })
- })}
-
- )
- }
-}
-
-// connect to the redux store
-const mapStateToProps = (state, ownProps) => {
- const activeSearch = getActiveSearch(state.otp)
- // const { activeItinerary, activeLeg, activeStep } = activeSearch ? activeSearch.activeItinerary : {}
- const pending = activeSearch && activeSearch.pending
- return {
- options:
- activeSearch &&
- activeSearch.response &&
- activeSearch.response.otp
- ? activeSearch.response.otp.profile
- : null,
- pending,
- activeItinerary: activeSearch && activeSearch.activeItinerary,
- activeLeg: activeSearch && activeSearch.activeLeg,
- activeStep: activeSearch && activeSearch.activeStep,
- query: activeSearch && activeSearch.query
- }
-}
-
-const mapDispatchToProps = (dispatch, ownProps) => {
- return {
- setActiveItinerary: (index) => { dispatch(setActiveItinerary({ index })) },
- setActiveLeg: (index, leg) => { dispatch(setActiveLeg({ index, leg })) },
- setActiveStep: (index, step) => { dispatch(setActiveStep({ index, step })) }
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(NarrativeProfileOptions)
diff --git a/lib/components/narrative/narrative-profile-summary.js b/lib/components/narrative/narrative-profile-summary.js
deleted file mode 100644
index cf8db6c6e..000000000
--- a/lib/components/narrative/narrative-profile-summary.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-import { getIcon } from '../../util/itinerary'
-
-export default class NarrativeProfileSummary extends Component {
- static propTypes = {
- options: PropTypes.array,
- customIcons: PropTypes.object
- }
-
- render () {
- const { options } = this.props
-
- let bestTransit = 0
- let walk = 0
- let bicycle = 0
- let bicycleRent = 0
-
- options.forEach((option, i) => {
- if (option.transit) {
- if (option.time < bestTransit || bestTransit === 0) {
- bestTransit = option.time
- }
- } else {
- if (option.modes.length === 1 && option.modes[0] === 'bicycle') bicycle = option.time
- else if (option.modes.length === 1 && option.modes[0] === 'walk') walk = option.time
- else if (option.modes.indexOf('bicycle_rent') !== -1) bicycleRent = option.time
- }
- })
-
- const summary = [
- {
- icon: 'BUS',
- title: 'Transit',
- time: bestTransit
- }, {
- icon: 'BICYCLE',
- title: 'Bicycle',
- time: bicycle
- }, {
- icon: 'BICYCLE_RENT',
- title: 'Bikeshare',
- time: bicycleRent
- }, {
- icon: 'WALK',
- title: 'Walk',
- time: walk
- }
- ]
-
- return (
-
- {summary.map((option, k) => {
- return (
-
0 ? '#084C8D' : '#bbb',
- width: '22%',
- display: 'inline-block',
- verticalAlign: 'top',
- marginRight: (k < 3 ? '4%' : 0),
- padding: '3px',
- textAlign: 'center',
- color: 'white' }}
- >
-
{getIcon(option.icon, this.props.customIcons)}
-
{option.title}
-
- {option.time > 0
- ? {Math.round(option.time / 60)} min
- : (Not Found)
- }
-
-
- )
- })}
-
- )
- }
-}
diff --git a/lib/components/narrative/narrative-routing-results.js b/lib/components/narrative/narrative-routing-results.js
index f7f407420..204cc245b 100644
--- a/lib/components/narrative/narrative-routing-results.js
+++ b/lib/components/narrative/narrative-routing-results.js
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Loading from './loading'
-import NarrativeProfileOptions from './narrative-profile-options'
import TabbedItineraries from './tabbed-itineraries'
import ErrorMessage from '../form/error-message'
@@ -26,18 +25,14 @@ class NarrativeRoutingResults extends Component {
}
render () {
- const { customIcons, error, itineraryClass, itineraryFooter, pending, routingType, itineraries, mainPanelContent } = this.props
+ const { customIcons, error, itineraryClass, itineraryFooter, pending, itineraries, mainPanelContent } = this.props
if (pending) return
if (error) return
if (mainPanelContent) return null
return (
- routingType === 'ITINERARY'
- ?
- :
+ // TODO: If multiple routing types exist, do the check here.
+
)
}
}
diff --git a/lib/components/narrative/realtime-annotation.js b/lib/components/narrative/realtime-annotation.js
index 834046d50..31ed7cd0e 100644
--- a/lib/components/narrative/realtime-annotation.js
+++ b/lib/components/narrative/realtime-annotation.js
@@ -1,9 +1,8 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button, OverlayTrigger, Popover } from 'react-bootstrap'
-import { formatDuration } from '../../util/time'
-
export default class RealtimeAnnotation extends Component {
static propTypes = {
realtimeEffects: PropTypes.object,
@@ -33,7 +32,7 @@ export default class RealtimeAnnotation extends Component {
?
Your trip results have been adjusted based on real-time
information. Under normal conditions, this trip would take{' '}
- {formatDuration(realtimeEffects.normalDuration)}
+ {coreUtils.time.formatDuration(realtimeEffects.normalDuration)}
using the following routes:{' '}
{filteredRoutes
.map((route, idx) => (
diff --git a/lib/components/narrative/tabbed-itineraries.js b/lib/components/narrative/tabbed-itineraries.js
index f3c4e2d76..94f38eeb8 100644
--- a/lib/components/narrative/tabbed-itineraries.js
+++ b/lib/components/narrative/tabbed-itineraries.js
@@ -1,13 +1,15 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
+import { connect } from 'react-redux'
import { setActiveItinerary, setActiveLeg, setActiveStep, setUseRealtimeResponse } from '../../actions/narrative'
import DefaultItinerary from './default/default-itinerary'
import { getActiveSearch, getRealtimeEffects } from '../../util/state'
-import { calculateFares, calculatePhysicalActivity, getTimeZoneOffset } from '../../util/itinerary'
-import { formatDuration, formatTime, getTimeFormat } from '../../util/time'
+
+const { calculateFares, calculatePhysicalActivity, getTimeZoneOffset } = coreUtils.itinerary
+const { formatDuration, formatTime, getTimeFormat } = coreUtils.time
class TabbedItineraries extends Component {
static propTypes = {
diff --git a/lib/components/viewers/route-viewer.js b/lib/components/viewers/route-viewer.js
index c298d5a50..3cb3e6df5 100644
--- a/lib/components/viewers/route-viewer.js
+++ b/lib/components/viewers/route-viewer.js
@@ -1,3 +1,4 @@
+import coreUtils from '@opentripplanner/core-utils'
import React, { Component, PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Label, Button } from 'react-bootstrap'
@@ -8,7 +9,6 @@ import Icon from '../narrative/icon'
import { setMainPanelContent, setViewedRoute } from '../../actions/ui'
import { findRoutes, findRoute } from '../../actions/api'
-import { routeComparator } from '../../util/itinerary'
function operatorIndexForRoute (transitOperators, route) {
if (!route.agency) return 0
@@ -58,7 +58,7 @@ class RouteViewer extends Component {
viewedRoute
} = this.props
const sortedRoutes = routes
- ? Object.values(routes).sort(routeComparator)
+ ? Object.values(routes).sort(coreUtils.itinerary.routeComparator)
: []
const agencySortedRoutes = transitOperators.length > 0
? sortedRoutes.sort((a, b) => {
diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js
index 424d7278b..ac668f353 100644
--- a/lib/components/viewers/stop-viewer.js
+++ b/lib/components/viewers/stop-viewer.js
@@ -1,20 +1,25 @@
-import React, { Component } from 'react'
+import moment from 'moment'
+import 'moment-timezone'
+import coreUtils from '@opentripplanner/core-utils'
+import FromToLocationPicker from '@opentripplanner/from-to-location-picker'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
-import moment from 'moment'
-import 'moment-timezone'
import { VelocityTransitionGroup } from 'velocity-react'
-import FromToLocationPicker from '@opentripplanner/from-to-location-picker'
-
import Icon from '../narrative/icon'
import { setMainPanelContent, toggleAutoRefresh } from '../../actions/ui'
import { findStop, findStopTimesForStop } from '../../actions/api'
import { forgetStop, rememberStop, setLocation } from '../../actions/map'
-import { routeComparator } from '../../util/itinerary'
import { getShowUserSettings, getStopViewerConfig } from '../../util/state'
-import { formatDuration, formatSecondsAfterMidnight, getTimeFormat, getUserTimezone } from '../../util/time'
+
+const {
+ formatDuration,
+ formatSecondsAfterMidnight,
+ getTimeFormat,
+ getUserTimezone
+} = coreUtils.time
class StopViewer extends Component {
state = {}
@@ -225,7 +230,7 @@ class StopViewer extends Component {
{stopData.stopTimes && stopData.routes && (
{Object.values(stopTimesByPattern)
- .sort((a, b) => routeComparator(a.route, b.route))
+ .sort((a, b) => coreUtils.itinerary.routeComparator(a.route, b.route))
.map(patternTimes => {
// Only add pattern row if route is found.
// FIXME: there is currently a bug with the alernative transit index
diff --git a/lib/components/viewers/trip-viewer.js b/lib/components/viewers/trip-viewer.js
index f1d3fe928..9ce414d29 100644
--- a/lib/components/viewers/trip-viewer.js
+++ b/lib/components/viewers/trip-viewer.js
@@ -1,5 +1,6 @@
-import React, { Component } from 'react'
+import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import { Button, Label } from 'react-bootstrap'
import { connect } from 'react-redux'
@@ -10,8 +11,6 @@ import { setViewedTrip } from '../../actions/ui'
import { findTrip } from '../../actions/api'
import { setLocation } from '../../actions/map'
-import { formatSecondsAfterMidnight, getTimeFormat } from '../../util/time'
-
class TripViewer extends Component {
static propTypes = {
hideBackButton: PropTypes.bool,
@@ -101,7 +100,7 @@ class TripViewer extends Component {
{/* the departure time */}
- {formatSecondsAfterMidnight(tripData.stopTimes[i].scheduledDeparture, timeFormat)}
+ {coreUtils.time.formatSecondsAfterMidnight(tripData.stopTimes[i].scheduledDeparture, timeFormat)}
{/* the vertical strip map */}
@@ -137,7 +136,7 @@ const mapStateToProps = (state, ownProps) => {
const viewedTrip = state.otp.ui.viewedTrip
return {
languageConfig: state.otp.config.language,
- timeFormat: getTimeFormat(state.otp.config),
+ timeFormat: coreUtils.time.getTimeFormat(state.otp.config),
tripData: state.otp.transitIndex.trips[viewedTrip.tripId],
viewedTrip
}
diff --git a/lib/index.js b/lib/index.js
index ad1bb1d41..4ed9f8b55 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -16,7 +16,6 @@ import TileOverlay from './components/map/tile-overlay'
import ItineraryCarousel from './components/narrative/itinerary-carousel'
import LegDiagramPreview from './components/narrative/leg-diagram-preview'
import NarrativeItineraries from './components/narrative/narrative-itineraries'
-import NarrativeProfileOptions from './components/narrative/narrative-profile-options'
import NarrativeItinerary from './components/narrative/narrative-itinerary'
import NarrativeRoutingResults from './components/narrative/narrative-routing-results'
import RealtimeAnnotation from './components/narrative/realtime-annotation'
@@ -71,7 +70,6 @@ export {
LineItinerary,
NarrativeItineraries,
NarrativeItinerary,
- NarrativeProfileOptions,
NarrativeRoutingResults,
RealtimeAnnotation,
SimpleRealtimeAnnotation,
diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js
index 2ea229618..d18fbdb20 100644
--- a/lib/reducers/create-otp-reducer.js
+++ b/lib/reducers/create-otp-reducer.js
@@ -2,18 +2,20 @@ import clone from 'clone'
import update from 'immutability-helper'
import isEqual from 'lodash.isequal'
import objectPath from 'object-path'
+import coreUtils from '@opentripplanner/core-utils'
-import { matchLatLon } from '../util/map'
-import {
+import { MainPanelContent, MobileScreens } from '../actions/ui'
+
+const { isTransit, getTransitModes } = coreUtils.itinerary
+const { matchLatLon } = coreUtils.map
+const { filterProfileOptions } = coreUtils.profile
+const {
ensureSingleAccessMode,
getDefaultQuery,
getTripOptionsFromQuery
-} from '../util/query'
-import { isTransit, getTransitModes } from '../util/itinerary'
-import { filterProfileOptions } from '../util/profile'
-import { getItem, removeItem, storeItem } from '../util/storage'
-import { getUserTimezone } from '../util/time'
-import { MainPanelContent, MobileScreens } from '../actions/ui'
+} = coreUtils.query
+const { getItem, removeItem, storeItem } = coreUtils.storage
+const { getUserTimezone } = coreUtils.time
const MAX_RECENT_STORAGE = 5
diff --git a/lib/util/index.js b/lib/util/index.js
index da1a44a7c..02d4e6327 100644
--- a/lib/util/index.js
+++ b/lib/util/index.js
@@ -1,21 +1,9 @@
import * as itinerary from './itinerary'
-import * as map from './map'
-import * as profile from './profile'
-import * as query from './query'
-import * as reverse from './reverse'
import * as state from './state'
-import * as time from './time'
-import * as ui from './ui'
const OtpUtils = {
itinerary,
- map,
- profile,
- query,
- reverse,
- state,
- time,
- ui
+ state
}
export default OtpUtils
diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js
index 748b88068..6a65f5f13 100644
--- a/lib/util/itinerary.js
+++ b/lib/util/itinerary.js
@@ -1,197 +1,9 @@
-import React from 'react'
import { latLngBounds } from 'leaflet'
-import polyline from '@mapbox/polyline'
-import turfAlong from '@turf/along'
+import coreUtils from '@opentripplanner/core-utils'
+import React from 'react'
import ModeIcon from '../components/icons/mode-icon'
-// All OTP transit modes
-export const transitModes = ['TRAM', 'BUS', 'SUBWAY', 'FERRY', 'RAIL', 'GONDOLA']
-
-/**
- * @param {config} config OTP-RR configuration object
- * @return {Array} List of all transit modes defined in config; otherwise default mode list
- */
-
-export function getTransitModes (config) {
- if (!config || !config.modes || !config.modes.transitModes) return transitModes
- return config.modes.transitModes.map(tm => tm.mode)
-}
-
-export function isTransit (mode) {
- return transitModes.includes(mode) || mode === 'TRANSIT'
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes are transit modes
- */
-export function hasTransit (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (isTransit(mode)) return true
- }
- return false
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes are car-based modes
- */
-export function hasCar (modesStr) {
- if (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (isCar(mode)) return true
- }
- }
- return false
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes are bicycle-based modes
- */
-export function hasBike (modesStr) {
- if (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (isBicycle(mode) || isBicycleRent(mode)) return true
- }
- }
- return false
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes are micromobility-based modes
- */
-export function hasMicromobility (modesStr) {
- if (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (isMicromobility(mode)) return true
- }
- }
- return false
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes is a hailing mode
- */
-export function hasHail (modesStr) {
- if (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (mode.indexOf('_HAIL') > -1) return true
- }
- }
- return false
-}
-
-/**
- * @param {string} modesStr a comma-separated list of OTP modes
- * @return {boolean} whether any of the modes is a rental mode
- */
-export function hasRental (modesStr) {
- if (modesStr) {
- for (const mode of modesStr.split(',')) {
- if (mode.indexOf('_RENT') > -1) return true
- }
- }
- return false
-}
-
-export function isWalk (mode) {
- if (!mode) return false
-
- return mode === 'WALK'
-}
-
-export function isBicycle (mode) {
- if (!mode) return false
-
- return mode === 'BICYCLE'
-}
-
-export function isBicycleRent (mode) {
- if (!mode) return false
-
- return mode === 'BICYCLE_RENT'
-}
-
-export function isCar (mode) {
- if (!mode) return false
- return mode.startsWith('CAR')
-}
-
-export function isMicromobility (mode) {
- if (!mode) return false
- return mode.startsWith('MICROMOBILITY')
-}
-
-export function isAccessMode (mode) {
- return isWalk(mode) ||
- isBicycle(mode) ||
- isBicycleRent(mode) ||
- isCar(mode) ||
- isMicromobility(mode)
-}
-
-export function getMapColor (mode) {
- mode = mode || this.get('mode')
- if (mode === 'WALK') return '#444'
- if (mode === 'BICYCLE') return '#0073e5'
- if (mode === 'SUBWAY') return '#f00'
- if (mode === 'RAIL') return '#b00'
- if (mode === 'BUS') return '#080'
- if (mode === 'TRAM') return '#800'
- if (mode === 'FERRY') return '#008'
- if (mode === 'CAR') return '#444'
- if (mode === 'MICROMOBILITY') return '#f5a729'
- return '#aaa'
-}
-
-// TODO: temporary code; handle via migrated OTP i18n language table
-export function getStepDirection (step) {
- switch (step.relativeDirection) {
- case 'DEPART': return 'Head ' + step.absoluteDirection.toLowerCase()
- case 'LEFT': return 'Left'
- case 'HARD_LEFT': return 'Hard left'
- case 'SLIGHTLY_LEFT': return 'Slight left'
- case 'CONTINUE': return 'Continue'
- case 'SLIGHTLY_RIGHT': return 'Slight right'
- case 'RIGHT': return 'Right'
- case 'HARD_RIGHT': return 'Hard right'
- case 'CIRCLE_CLOCKWISE': return 'Follow circle clockwise'
- case 'CIRCLE_COUNTERCLOCKWISE': return 'Follow circle counterclockwise'
- case 'ELEVATOR': return 'Take elevator'
- case 'UTURN_LEFT': return 'Left U-turn'
- case 'UTURN_RIGHT': return 'Right U-turn'
- }
- return step.relativeDirection
-}
-
-export function getStepInstructions (step) {
- const conjunction = step.relativeDirection === 'ELEVATOR' ? 'to' : 'on'
- return `${getStepDirection(step)} ${conjunction} ${step.streetName}`
-}
-
-export function getStepStreetName (step) {
- if (step.streetName === 'road') return 'Unnamed Road'
- if (step.streetName === 'path') return 'Unnamed Path'
- return step.streetName
-}
-
-export function getLegModeLabel (leg) {
- switch (leg.mode) {
- case 'BICYCLE_RENT': return 'Biketown'
- case 'CAR': return leg.hailedCar ? 'Ride' : 'Drive'
- case 'GONDOLA': return 'Aerial Tram'
- case 'TRAM':
- if (leg.routeLongName.toLowerCase().indexOf('streetcar') !== -1) return 'Streetcar'
- return 'Light Rail'
- case 'MICROMOBILITY': return 'Ride'
- }
- return toSentenceCase(leg.mode)
-}
-
/**
* Returns a react element of the desired icon. If customIcons are defined, then
* the icon will be attempted to be used from that lookup of icons. Otherwise,
@@ -217,386 +29,15 @@ export function getIcon (iconId, customIcons) {
return
}
-export function getItineraryBounds (itinerary) {
- let coords = []
- itinerary.legs.forEach(leg => {
- const legCoords = polyline
- .toGeoJSON(leg.legGeometry.points)
- .coordinates.map(c => [c[1], c[0]])
- coords = [...coords, ...legCoords]
- })
- return latLngBounds(coords)
+export function getLeafletItineraryBounds (itinerary) {
+ return latLngBounds(coreUtils.itinerary.getItineraryBounds(itinerary))
}
/**
* Return a leaflet LatLngBounds object that encloses the given leg's geometry.
*/
-export function getLegBounds (leg) {
- const coords = polyline
- .toGeoJSON(leg.legGeometry.points)
- .coordinates.map(c => [c[1], c[0]])
-
- // in certain cases, there might be zero-length coordinates in the leg
- // geometry. In these cases, build us an array of coordinates using the from
- // and to data of the leg.
- if (coords.length === 0) {
- coords.push([leg.from.lat, leg.from.lon], [leg.to.lat, leg.to.lon])
- }
- return latLngBounds(coords)
-}
-
-/**
- * Gets the desired sort values according to an optional getter function. If the
- * getter function is not defined, the original sort values are returned.
- */
-function getSortValues (getterFn, a, b) {
- let aVal
- let bVal
- if (typeof getterFn === 'function') {
- aVal = getterFn(a)
- bVal = getterFn(b)
- } else {
- aVal = a
- bVal = b
- }
- return { aVal, bVal }
-}
-
-// Lookup for the sort values associated with various OTP modes.
-// Note: JSDoc format not used to avoid bug in documentationjs.
-// https://github.com/documentationjs/documentation/issues/372
-const modeComparatorValue = {
- SUBWAY: 1,
- TRAM: 2,
- RAIL: 3,
- GONDOLA: 4,
- FERRY: 5,
- CABLE_CAR: 6,
- FUNICULAR: 7,
- BUS: 8
-}
-
-// Lookup that maps route types to the OTP mode sort values.
-// Note: JSDoc format not used to avoid bug in documentationjs.
-// https://github.com/documentationjs/documentation/issues/372
-const routeTypeComparatorValue = {
- 0: modeComparatorValue.TRAM, // - Tram, Streetcar, Light rail.
- 1: modeComparatorValue.SUBWAY, // - Subway, Metro.
- 2: modeComparatorValue.RAIL, // - Rail. Used for intercity or long-distance travel.
- 3: modeComparatorValue.BUS, // - Bus.
- 4: modeComparatorValue.FERRY, // - Ferry.
- 5: modeComparatorValue.CABLE_CAR, // - Cable tram.
- 6: modeComparatorValue.GONDOLA, // - Gondola, etc.
- 7: modeComparatorValue.FUNICULAR, // - Funicular.
- // TODO: 11 and 12 are not a part of OTP as of 2019-02-14, but for now just
- // associate them with bus/rail.
- 11: modeComparatorValue.BUS, // - Trolleybus.
- 12: modeComparatorValue.RAIL // - Monorail.
-}
-
-// Gets a comparator value for a given route's type (OTP mode).
-// Note: JSDoc format not used to avoid bug in documentationjs.
-// ttps://github.com/documentationjs/documentation/issues/372
-function getRouteTypeComparatorValue (route) {
- // For some strange reason, the short route response in OTP returns the
- // string-based modes, but the long route response returns the
- // integer route type. This attempts to account for both of those cases.
- if (!route) throw new Error('Route is undefined.', route)
- if (typeof modeComparatorValue[route.mode] !== 'undefined') {
- return modeComparatorValue[route.mode]
- } else if (typeof routeTypeComparatorValue[route.type] !== 'undefined') {
- return routeTypeComparatorValue[route.type]
- } else {
- // Default the comparator value to a large number (placing the route at the
- // end of the list).
- console.warn('no mode/route type found for route', route)
- return 9999
- }
-}
-
-/**
- * Calculates the sort comparator value given two routes based off of route type
- * (OTP mode).
- */
-function routeTypeComparator (a, b) {
- return getRouteTypeComparatorValue(a) - getRouteTypeComparatorValue(b)
-}
-
-/**
- * Determines whether a value is a string that starts with an alphabetic
- * ascii character.
- */
-function startsWithAlphabeticCharacter (val) {
- if (typeof val === 'string' && val.length > 0) {
- const firstCharCode = val.charCodeAt(0)
- return (firstCharCode >= 65 && firstCharCode <= 90) ||
- (firstCharCode >= 97 && firstCharCode <= 122)
- }
- return false
-}
-
-/**
- * Sorts routes based off of whether the shortName begins with an alphabetic
- * character. Routes with shortn that do start with an alphabetic character will
- * be prioritized over those that don't.
- */
-function alphabeticShortNameComparator (a, b) {
- const aStartsWithAlphabeticCharacter = startsWithAlphabeticCharacter(
- a.shortName
- )
- const bStartsWithAlphabeticCharacter = startsWithAlphabeticCharacter(
- b.shortName
- )
-
- if (aStartsWithAlphabeticCharacter && bStartsWithAlphabeticCharacter) {
- // both start with an alphabetic character, return equivalence
- return 0
- }
- // a does start with an alphabetic character, but b does not. Prioritize a
- if (aStartsWithAlphabeticCharacter) return -1
- // b does start with an alphabetic character, but a does not. Prioritize b
- if (bStartsWithAlphabeticCharacter) return 1
- // neither route has a shortName that starts with an alphabetic character.
- // Return equivalence
- return 0
-}
-
-/**
- * Checks whether an appropriate comparison of numeric values can be made for
- * sorting purposes. If both values are not valid numbers according to the
- * isNaN check, then this function returns undefined which indicates that a
- * secondary sorting criteria should be used instead. If one value is valid and
- * the other is not, then the valid value will be given sorting priority. If
- * both values are valid numbers, the difference is obtained as the sort value.
- *
- * An optional argument can be provided which will be used to obtain the
- * comparison value from the comparison function arguments.
- *
- * IMPORTANT: the comparison values must be numeric values or at least be
- * attempted to be converted to numeric values! If one of the arguments is
- * something crazy like an empty string, unexpected behavior will occur because
- * JavaScript.
- *
- * @param {function} [objGetterFn] An optional function to obtain the
- * comparison value from the comparator function arguments
- */
-function makeNumericValueComparator (objGetterFn) {
- return (a, b) => {
- const { aVal, bVal } = getSortValues(objGetterFn, a, b)
- // if both values aren't valid numbers, use the next sort criteria
- if (isNaN(aVal) && isNaN(bVal)) return 0
- // b is a valid number, b gets priority
- if (isNaN(aVal)) return 1
- // a is a valid number, a gets priority
- if (isNaN(bVal)) return -1
- // a and b are valid numbers, return the sort value
- return aVal - bVal
- }
-}
-
-/**
- * Create a comparator function that compares string values. The comparison
- * values feed to the sort comparator function are assumed to be objects that
- * will have either undefined, null or string values at the given key. If one
- * object has undefined, null or an empty string, but the other does have a
- * string with length > 0, then that string will get priority.
- *
- * @param {function} [objGetterFn] An optional function to obtain the
- * comparison value from the comparator function arguments
- */
-function makeStringValueComparator (objGetterFn) {
- return (a, b) => {
- const { aVal, bVal } = getSortValues(objGetterFn, a, b)
- // both a and b are uncomparable strings, return equivalent value
- if (!aVal && !bVal) return 0
- // a is not a comparable string, b gets priority
- if (!aVal) return 1
- // b is not a comparable string, a gets priority
- if (!bVal) return -1
- // a and b are comparable strings, return the sort value
- if (aVal < bVal) return -1
- if (aVal > bVal) return 1
- return 0
- }
-}
-
-/**
- * OpenTripPlanner sets the routeSortOrder to -999 by default. So, if that value
- * is encountered, assume that it actually means that the routeSortOrder is not
- * set in the GTFS.
- *
- * See https://github.com/opentripplanner/OpenTripPlanner/issues/2938
- * Also see https://github.com/opentripplanner/otp-react-redux/issues/122
- */
-function getRouteSortOrderValue (val) {
- return val === -999 ? undefined : val
-}
-
-/**
- * Create a multi-criteria sort comparator function composed of other sort
- * comparator functions. Each comparator function will be ran in the order given
- * until a non-zero comparison value is obtained which is then immediately
- * returned. If all comparison functions return equivalance, then the values
- * are assumed to be equivalent.
- */
-function makeMultiCriteriaSort (...criteria) {
- return (a, b) => {
- for (let i = 0; i < criteria.length; i++) {
- const curCriteriaComparatorValue = criteria[i](a, b)
- // if the comparison objects are not equivalent, return the value obtained
- // in this current criteria comparison
- if (curCriteriaComparatorValue !== 0) {
- return curCriteriaComparatorValue
- }
- }
- return 0
- }
-}
-
-/**
- * Compares routes for the purposes of sorting and displaying in a user
- * interface. Due to GTFS feeds having varying levels of data quality, a multi-
- * criteria sort is needed to account for various differences. The criteria
- * included here are each applied to the routes in the order listed. If a given
- * sort criterion yields equivalence (e.g., two routes have the short name
- * "20"), the comparator falls back onto the next sort criterion (e.g., long
- * name). If desired, the criteria of sorting based off of integer shortName can
- * be disabled. The sort operates on the following values (in order):
- *
- * 1. sortOrder. Routes that do not have a valid sortOrder will be placed
- * beneath those that do.
- * 2. route type (OTP mode). See routeTypeComparator code for prioritization of
- * route types.
- * 3. shortNames that begin with alphabetic characters. shortNames that do not
- * start with alphabetic characters will be place beneath those that do.
- * 4. shortName as integer. shortNames that cannot be parsed as integers will
- * be placed beneath those that are valid.
- * 5. shortName as string. Routes without shortNames will be placed beneath
- * those with shortNames.
- * 6. longName as string.
- */
-export const routeComparator = makeMultiCriteriaSort(
- makeNumericValueComparator(obj => getRouteSortOrderValue(obj.sortOrder)),
- routeTypeComparator,
- alphabeticShortNameComparator,
- makeNumericValueComparator(obj => parseInt(obj.shortName)),
- makeStringValueComparator(obj => obj.shortName),
- makeStringValueComparator(obj => obj.longName)
-)
-
-/* Returns an interpolated lat-lon at a specified distance along a leg */
-
-export function legLocationAtDistance (leg, distance) {
- if (!leg.legGeometry) return null
-
- try {
- const line = polyline.toGeoJSON(leg.legGeometry.points)
- const pt = turfAlong(line, distance, { units: 'meters' })
- if (pt && pt.geometry && pt.geometry.coordinates) {
- return [
- pt.geometry.coordinates[1],
- pt.geometry.coordinates[0]
- ]
- }
- } catch (e) { }
-
- return null
-}
-
-/* Returns an interpolated elevation at a specified distance along a leg */
-
-export function legElevationAtDistance (points, distance) {
- // Iterate through the combined elevation profile
- let traversed = 0
- // If first point distance is not zero, insert starting point at zero with
- // null elevation. Encountering this value should trigger the warning below.
- if (points[0][0] > 0) {
- points.unshift([0, null])
- }
- for (let i = 1; i < points.length; i++) {
- const start = points[i - 1]
- const elevDistanceSpan = points[i][0] - start[0]
- if (distance >= traversed && distance <= traversed + elevDistanceSpan) {
- // Distance falls within this point and the previous one;
- // compute & return iterpolated elevation value
- if (start[1] === null) {
- console.warn('Elevation value does not exist for distance.', distance, traversed)
- return null
- }
- const pct = (distance - traversed) / elevDistanceSpan
- const elevSpan = points[i][1] - start[1]
- return start[1] + elevSpan * pct
- }
- traversed += elevDistanceSpan
- }
- console.warn('Elevation value does not exist for distance.', distance, traversed)
- return null
-}
-
-// Iterate through the steps, building the array of elevation points and
-// keeping track of the minimum and maximum elevations reached
-export function getElevationProfile (steps, unitConversion = 1) {
- let minElev = 100000
- let maxElev = -100000
- let traversed = 0
- let gain = 0
- let loss = 0
- let previous = null
- const points = []
- steps.forEach((step, stepIndex) => {
- if (!step.elevation || step.elevation.length === 0) {
- traversed += step.distance
- return
- }
- for (let i = 0; i < step.elevation.length; i++) {
- const elev = step.elevation[i]
- if (previous) {
- const diff = (elev.second - previous.second) * unitConversion
- if (diff > 0) gain += diff
- else loss += diff
- }
- if (i === 0 && elev.first !== 0) {
- // console.warn(`No elevation data available for step ${stepIndex}-${i} at beginning of segment`, elev)
- }
- const convertedElevation = elev.second * unitConversion
- if (convertedElevation < minElev) minElev = convertedElevation
- if (convertedElevation > maxElev) maxElev = convertedElevation
- points.push([traversed + elev.first, elev.second])
- // Insert "filler" point if the last point in elevation profile does not
- // reach the full distance of the step.
- if (i === step.elevation.length - 1 && elev.first !== step.distance) {
- // points.push([traversed + step.distance, elev.second])
- }
- previous = elev
- }
- traversed += step.distance
- })
- return { maxElev, minElev, points, traversed, gain, loss }
-}
-
-/**
- * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
- *
- * @param {string} text The text to be rendered.
- * @param {string} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
- *
- * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
- */
-export function getTextWidth (text, font = '22px Arial') {
- // re-use canvas object for better performance
- var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement('canvas'))
- var context = canvas.getContext('2d')
- context.font = font
- var metrics = context.measureText(text)
- return metrics.width
-}
-
-export function toSentenceCase (str) {
- if (str == null) {
- return ''
- }
- str = String(str)
- return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase()
+export function getLeafletLegBounds (leg) {
+ return latLngBounds(coreUtils.itinerary.getLegBounds(leg))
}
/**
@@ -633,134 +74,3 @@ export function getLegIcon (leg, customIcons) {
return getIcon(iconStr, customIcons)
}
-
-/**
- * Get the configured company object for the given network string if the company
- * has been defined in the provided companies array config.
- */
-function getCompanyForNetwork (networkString, companies = []) {
- const company = companies.find(co => co.id === networkString)
- if (!company) {
- console.warn(`No company found in config.yml that matches rented vehicle network: ${networkString}`, companies)
- }
- return company
-}
-
-/**
- * Get a string label to display from a list of vehicle rental networks.
- *
- * @param {Array
} networks A list of network ids.
- * @param {Array} [companies=[]] An optional list of the companies config.
- * @return {string} A label for use in presentation on a website.
- */
-export function getCompaniesLabelFromNetworks (networks, companies = []) {
- return networks.map(network => getCompanyForNetwork(network, companies))
- .filter(co => !!co)
- .map(co => co.label)
- .join('/')
-}
-
-/**
- * Returns mode name by checking the vertex type (VertexType class in OTP) for
- * the provided place. NOTE: this is currently only intended for vehicles at
- * the moment (not transit or walking).
- *
- * TODO: I18N
- * @param {string} place place from itinerary leg
- */
-export function getModeForPlace (place) {
- switch (place.vertexType) {
- case 'CARSHARE':
- return 'car'
- case 'VEHICLERENTAL':
- return 'E-scooter'
- // TODO: Should the type change depending on bike vertex type?
- case 'BIKESHARE':
- case 'BIKEPARK':
- return 'bike'
- // If company offers more than one mode, default to `vehicle` string.
- default:
- return 'vehicle'
- }
-}
-
-export function getPlaceName (place, companies) {
- // If address is provided (i.e. for carshare station, use it)
- if (place.address) return place.address.split(',')[0]
- if (place.networks && place.vertexType === 'VEHICLERENTAL') {
- // For vehicle rental pick up, do not use the place name. Rather, use
- // company name + vehicle type (e.g., SPIN E-scooter). Place name is often just
- // a UUID that has no relevance to the actual vehicle. For bikeshare, however,
- // there are often hubs or bikes that have relevant names to the user.
- const company = getCompanyForNetwork(place.networks[0], companies)
- if (company) {
- return `${company.label} ${getModeForPlace(place)}`
- }
- }
- // Default to place name
- return place.name
-}
-
-export function getTNCLocation (leg, type) {
- const location = leg[type]
- return `${location.lat.toFixed(5)},${location.lon.toFixed(5)}`
-}
-
-export function calculatePhysicalActivity (itinerary) {
- let walkDuration = 0
- let bikeDuration = 0
- for (const leg of itinerary.legs) {
- if (leg.mode.startsWith('WALK')) walkDuration += leg.duration
- if (leg.mode.startsWith('BICYCLE')) bikeDuration += leg.duration
- }
- const caloriesBurned =
- walkDuration / 3600 * 280 +
- bikeDuration / 3600 * 290
- return {
- bikeDuration,
- caloriesBurned,
- walkDuration
- }
-}
-
-export function calculateFares (itinerary) {
- let transitFare = 0
- let symbol = '$' // default to USD
- let dollarsToString = dollars => `${symbol}${dollars.toFixed(2)}`
- let centsToString = cents => `${symbol}${(cents / Math.pow(10, 2)).toFixed(2)}`
- if (itinerary.fare && itinerary.fare.fare && itinerary.fare.fare.regular) {
- const reg = itinerary.fare.fare.regular
- symbol = reg.currency.symbol
- transitFare = reg.cents
- centsToString = cents => `${symbol}${(cents / Math.pow(10, reg.currency.defaultFractionDigits)).toFixed(reg.currency.defaultFractionDigits)}`
- dollarsToString = dollars => `${symbol}${dollars.toFixed(2)}`
- }
-
- // Process any TNC fares
- let minTNCFare = 0
- let maxTNCFare = 0
- for (const leg of itinerary.legs) {
- if (leg.mode === 'CAR' && leg.hailedCar && leg.tncData) {
- const { maxCost, minCost } = leg.tncData
- // TODO: Support non-USD
- minTNCFare += minCost
- maxTNCFare += maxCost
- }
- }
- return {
- centsToString,
- dollarsToString,
- maxTNCFare,
- minTNCFare,
- transitFare
- }
-}
-
-export function getTimeZoneOffset (itinerary) {
- if (!itinerary.legs || !itinerary.legs.length) return 0
-
- // Determine if there is a DST offset between now and the itinerary start date
- const dstOffset = new Date(itinerary.startTime).getTimezoneOffset() - new Date().getTimezoneOffset()
-
- return itinerary.legs[0].agencyTimeZoneOffset + (new Date().getTimezoneOffset() + dstOffset) * 60000
-}
diff --git a/lib/util/map.js b/lib/util/map.js
deleted file mode 100644
index 8c5d44db7..000000000
--- a/lib/util/map.js
+++ /dev/null
@@ -1,223 +0,0 @@
-import moment from 'moment'
-
-import { isTransit, toSentenceCase } from './itinerary'
-
-export function latlngToString (latlng) {
- return latlng && `${latlng.lat.toFixed(5)}, ${(latlng.lng || latlng.lon).toFixed(5)}`
-}
-
-export function coordsToString (coords) {
- return coords.length && coords.map(c => (+c).toFixed(5)).join(', ')
-}
-
-export function stringToCoords (str) {
- return (str && str.split(',').map(c => +c)) || []
-}
-
-export function constructLocation (latlng) {
- return {
- name: latlngToString(latlng),
- lat: latlng.lat,
- lon: latlng.lng
- }
-}
-
-export function formatStoredPlaceName (location, withDetails = true) {
- let displayName = location.type === 'home' || location.type === 'work'
- ? toSentenceCase(location.type)
- : location.name
- if (withDetails) {
- let detailText = getDetailText(location)
- if (detailText) displayName += ` (${detailText})`
- }
- return displayName
-}
-
-export function getDetailText (location) {
- let detailText
- if (location.type === 'home' || location.type === 'work') {
- detailText = location.name
- }
- if (location.type === 'stop') {
- detailText = location.id
- } else if (location.type === 'recent' && location.timestamp) {
- detailText = moment(location.timestamp).fromNow()
- }
- return detailText
-}
-
-export function matchLatLon (location1, location2) {
- if (!location1 || !location2) return location1 === location2
- return location1.lat === location2.lat && location1.lon === location2.lon
-}
-
-export function itineraryToTransitive (itin, includeGeometry) {
- // console.log('itineraryToTransitive', itin);
- const tdata = {
- journeys: [],
- streetEdges: [],
- places: [],
- patterns: [],
- routes: [],
- stops: []
- }
- const routes = {}
- const stops = {}
- let streetEdgeId = 0
- let patternId = 0
-
- const journey = {
- journey_id: 'itin',
- journey_name: 'Iterarary-derived Journey',
- segments: []
- }
-
- // add 'from' and 'to' places to the tdata places array
- tdata.places.push({
- place_id: 'from',
- place_lat: itin.legs[0].from.lat,
- place_lon: itin.legs[0].from.lon
- })
- tdata.places.push({
- place_id: 'to',
- place_lat: itin.legs[itin.legs.length - 1].to.lat,
- place_lon: itin.legs[itin.legs.length - 1].to.lon
- })
-
- itin.legs.forEach(leg => {
- if (
- leg.mode === 'WALK' ||
- leg.mode === 'BICYCLE' ||
- leg.mode === 'CAR' ||
- leg.mode === 'MICROMOBILITY'
- ) {
- const fromPlaceId = leg.from.bikeShareId
- ? `bicycle_rent_station_${leg.from.bikeShareId}`
- : `itin_street_${streetEdgeId}_from`
- const toPlaceId = leg.to.bikeShareId
- ? `bicycle_rent_station_${leg.to.bikeShareId}`
- : `itin_street_${streetEdgeId}_to`
-
- const segment = {
- type: leg.mode,
- streetEdges: [streetEdgeId],
- from: { type: 'PLACE', place_id: fromPlaceId },
- to: { type: 'PLACE', place_id: toPlaceId }
- }
- // For TNC segments, draw using an arc
- if (leg.mode === 'CAR' && leg.hailedCar) segment.arc = true
- journey.segments.push(segment)
-
- tdata.streetEdges.push({
- edge_id: streetEdgeId,
- geometry: leg.legGeometry
- })
- tdata.places.push({
- place_id: fromPlaceId,
- // Do not label the from place in addition to the to place. Otherwise,
- // in some cases (bike rental station) the label for a single place will
- // appear twice on the rendered transitive view.
- // See https://github.com/conveyal/trimet-mod-otp/issues/152
- // place_name: leg.from.name,
- place_lat: leg.from.lat,
- place_lon: leg.from.lon
- })
- tdata.places.push({
- place_id: toPlaceId,
- place_name: leg.to.name,
- place_lat: leg.to.lat,
- place_lon: leg.to.lon
- })
- streetEdgeId++
- }
- if (isTransit(leg.mode)) {
- // determine if we have valid inter-stop geometry
- const hasInterStopGeometry =
- leg.interStopGeometry &&
- leg.interStopGeometry.length === leg.intermediateStops.length + 1
-
- // create leg-specific pattern
- const ptnId = 'ptn_' + patternId
- const pattern = {
- pattern_id: ptnId,
- pattern_name: 'Pattern ' + patternId,
- route_id: leg.routeId,
- stops: []
- }
-
- // add 'from' stop to stops dictionary and pattern object
- stops[leg.from.stopId] = {
- stop_id: leg.from.stopId,
- stop_name: leg.from.name,
- stop_lat: leg.from.lat,
- stop_lon: leg.from.lon
- }
- pattern.stops.push({ stop_id: leg.from.stopId })
-
- // add intermediate stops to stops dictionary and pattern object
- for (const [i, stop] of leg.intermediateStops.entries()) {
- stops[stop.stopId] = {
- stop_id: stop.stopId,
- stop_name: stop.name,
- stop_lat: stop.lat,
- stop_lon: stop.lon
- }
- pattern.stops.push({
- stop_id: stop.stopId,
- geometry: hasInterStopGeometry && leg.interStopGeometry[i].points
- })
- }
-
- // add 'to' stop to stops dictionary and pattern object
- stops[leg.to.stopId] = {
- stop_id: leg.to.stopId,
- stop_name: leg.to.name,
- stop_lat: leg.to.lat,
- stop_lon: leg.to.lon
- }
- pattern.stops.push({
- stop_id: leg.to.stopId,
- geometry: hasInterStopGeometry && leg.interStopGeometry[leg.interStopGeometry.length - 1].points
- })
-
- // add route to the route dictionary
- routes[leg.routeId] = {
- agency_id: leg.agencyId,
- route_id: leg.routeId,
- route_short_name: leg.routeShortName || '',
- route_long_name: leg.routeLongName || '',
- route_type: leg.routeType,
- route_color: leg.routeColor
- }
-
- // add the pattern to the tdata patterns array
- tdata.patterns.push(pattern)
-
- // add the pattern refrerence to the journey object
- journey.segments.push({
- type: 'TRANSIT',
- patterns: [{
- pattern_id: ptnId,
- from_stop_index: 0,
- to_stop_index: (leg.intermediateStops.length + 2) - 1
- }]
- })
-
- patternId++
- }
- })
-
- // add the routes and stops to the tdata arrays
- for (const k in routes) tdata.routes.push(routes[k])
- for (const k in stops) tdata.stops.push(stops[k])
-
- // add the journey to the tdata journeys array
- tdata.journeys.push(journey)
-
- // console.log('derived tdata', tdata);
- return tdata
-}
-
-export function isBikeshareStation (place) {
- return place.place_id.lastIndexOf('bicycle_rent_station') !== -1
-}
diff --git a/lib/util/profile.js b/lib/util/profile.js
deleted file mode 100644
index ed79ed806..000000000
--- a/lib/util/profile.js
+++ /dev/null
@@ -1,180 +0,0 @@
-export function filterProfileOptions (response) {
- // Filter out similar options. TODO: handle on server?
- const optStrs = []
- const filteredIndices = []
-
- const filteredProfile = response.otp.profile.filter((option, i) => {
- let optStr = option.access.map(a => a.mode).join('/')
- if (option.transit) {
- optStr += ' to ' + option.transit.map(transit => {
- return transit.routes.map(route => route.id).join('/')
- }).join(',')
- }
- if (optStrs.indexOf(optStr) !== -1) return false
- optStrs.push(optStr)
- filteredIndices.push(i)
- return true
- })
-
- const filteredJourneys = response.otp.journeys.filter((journey, i) => filteredIndices.indexOf(i) !== -1)
-
- response.otp.profile = filteredProfile
- response.otp.journeys = filteredJourneys
- return response
-}
-
-/** profileOptionsToItineraries **/
-
-export function profileOptionsToItineraries (options, query) {
- return options.map(option => optionToItinerary(option, query))
-}
-
-// helper functions for profileOptionsToItineraries:
-
-function optionToItinerary (option, query) {
- const itin = {
- duration: option.time,
- legs: [],
- walkTime: 0,
- waitingTime: 0
- }
-
- // access leg
- if (option.access && option.access.length > 0) {
- if (option.access[0].mode === 'BICYCLE_RENT') {
- let status = 'WALK_ON'
- const walkOnEdges = []
- const bikeEdges = []
- const walkOffEdges = []
- let onStationName
- let walkOnTime = 0
- let offStationName
- let walkOffTime = 0
- option.access[0].streetEdges.forEach(edge => {
- // check if we're returning the bike
- if (edge.bikeRentalOffStation) {
- status = 'WALK_OFF'
- offStationName = edge.bikeRentalOffStation.name
- }
-
- if (status === 'WALK_ON') {
- walkOnEdges.push(edge)
- walkOnTime += edge.distance
- } else if (status === 'BIKE') {
- bikeEdges.push(edge)
- } else if (status === 'WALK_OFF') {
- walkOffEdges.push(edge)
- walkOffTime += edge.distance
- }
-
- // check if we're picking up the bike
- if (edge.bikeRentalOnStation) {
- status = 'BIKE'
- onStationName = edge.bikeRentalOnStation.name
- }
- })
-
- itin.walkTime += (walkOnTime + walkOffTime)
-
- // create the 'on' walk leg
- itin.legs.push({
- mode: 'WALK',
- duration: walkOnTime,
- transitLeg: false,
- from: {
- name: locationString(query && query.from.name, 'Destination')
- },
- to: {
- name: onStationName
- }
- })
-
- // create the bike leg
- itin.legs.push({
- mode: 'BICYCLE_RENT',
- duration: option.time - walkOnTime - walkOffTime,
- transitLeg: false,
- from: {
- name: onStationName
- },
- to: {
- name: offStationName
- }
- })
-
- // create the 'off' walk leg
- itin.legs.push({
- mode: 'WALK',
- duration: walkOffTime,
- transitLeg: false,
- from: {
- name: offStationName
- },
- to: {
- name: locationString(query && query.to.name, 'Destination')
- }
- })
- } else {
- itin.legs.push(accessToLeg(option.access[0], query && query.from.name, option.transit ? null : query && query.to.name))
- if (option.access[0].mode === 'WALK') itin.walkTime += option.access[0].time
- }
- }
-
- // transit legs
- if (option.transit) {
- option.transit.forEach(transit => {
- itin.legs.push({
- transitLeg: true,
- mode: transit.mode,
- from: {
- name: transit.fromName
- },
- to: {
- name: transit.toName
- },
- routes: transit.routes,
- duration: transit.rideStats.avg,
- averageWait: transit.waitStats.avg
- })
- itin.waitingTime += transit.waitStats.avg
- })
- }
-
- // egress leg
- if (option.egress && option.egress.length > 0) {
- // find the origin name, for transit trips
- const origin = option.transit ? option.transit[option.transit.length - 1].toName : null
-
- itin.legs.push(accessToLeg(option.egress[0], origin, query && query.to.name))
- if (option.egress[0].mode === 'WALK') itin.walkTime += option.egress[0].time
- }
-
- // construct summary
- if (option.transit) {
- itin.summary = 'Transit'
- } else {
- if (option.modes.length === 1 && option.modes[0] === 'bicycle') itin.summary = 'Bicycle'
- else if (option.modes.length === 1 && option.modes[0] === 'walk') itin.summary = 'Walk'
- else if (option.modes.indexOf('bicycle_rent') !== -1) itin.summary = 'Bikeshare'
- }
-
- return itin
-}
-
-function accessToLeg (access, origin, destination) {
- return {
- mode: access.mode,
- duration: access.time,
- transitLeg: false,
- from: {
- name: locationString(origin, 'Origin')
- },
- to: {
- name: locationString(destination, 'Destination')
- }
- }
-}
-
-function locationString (str, defaultStr) {
- return str ? str.split(',')[0] : defaultStr
-}
diff --git a/lib/util/query-params.js b/lib/util/query-params.js
deleted file mode 100644
index 94ebf17e2..000000000
--- a/lib/util/query-params.js
+++ /dev/null
@@ -1,546 +0,0 @@
-import {
- isTransit,
- isAccessMode,
- isCar,
- hasTransit,
- hasBike,
- hasMicromobility
-} from './itinerary'
-import { getItem } from './storage'
-import { getCurrentDate, getCurrentTime } from './time'
-
-/**
- * name: the default name of the parameter used for internal reference and API calls
- *
- * routingTypes: array of routing type(s) (ITINERARY, PROFILE, or both) this param applies to
- *
- * applicable: an optional function (accepting the current full query as a
- * parameter) indicating whether this query parameter is applicable to the query.
- * (Applicability is assumed if this function is not provided.)
- *
- * default: the default value for this parameter. The default can be also be a
- * function that gets executed when accessing the default value. When the value
- * is a funciton, it will take an argument of the current config of the otp-rr
- * store. This is needed when a brand new time-dependent value is desired to be
- * calculated. It's also helpful for producing tests that have consistent data
- * output.
- *
- * itineraryRewrite: an optional function for translating the key and/or value
- * for ITINERARY mode only (e.g. 'to' is rewritten as 'toPlace'). Accepts the
- * intial internal value as a function parameter.
- *
- * profileRewrite: an optional function for translating the value for PROFILE mode
- *
- * label: a text label for for onscreen display. May either be a text string or a
- * function (accepting the current full query as a parameter) returning a string
- *
- * selector: the default type of UI selector to use in the form. Can be one of:
- * - DROPDOWN: a standard drop-down menu selector
- *
- * options: an array of text/value pairs used with a dropdown selector
- *
- * TODO: validation system for rewrite functions and/or better user documentation
- * TODO: alphabetize below list
- */
-
-// FIXME: Use for parsing URL values?
-// const stringToLocation = string => {
-// const split = string.split(',')
-// return split.length === 2
-// ? {lat: split[0], lon: split[1]}
-// : {lat: null, lon: null}
-// }
-
-const formatPlace = (location, alternateName) => {
- if (!location) return null
- const name = location.name || `${alternateName || 'Place'} (${location.lat},${location.lon})`
- return `${name}::${location.lat},${location.lon}`
-}
-
-// Load stored default query settings from local storage
-let storedSettings = getItem('defaultQuery', {})
-
-const queryParams = [
- { /* from - the trip origin. stored internally as a location (lat/lon/name) object */
- name: 'from',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: null,
- itineraryRewrite: value => ({ fromPlace: formatPlace(value, 'Origin') }),
- profileRewrite: value => ({ from: { lat: value.lat, lon: value.lon } })
- // FIXME: Use for parsing URL values?
- // fromURL: stringToLocation
- },
-
- { /* to - the trip destination. stored internally as a location (lat/lon/name) object */
- name: 'to',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: null,
- itineraryRewrite: value => ({ toPlace: formatPlace(value, 'Destination') }),
- profileRewrite: value => ({ to: { lat: value.lat, lon: value.lon } })
- // FIXME: Use for parsing URL values?
- // fromURL: stringToLocation
- },
-
- { /* date - the date of travel, in MM-DD-YYYY format */
- name: 'date',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: getCurrentDate
- },
-
- { /* time - the arrival/departure time for an itinerary trip, in HH:mm format */
- name: 'time',
- routingTypes: [ 'ITINERARY' ],
- default: getCurrentTime
- },
-
- { /* departArrive - whether this is a depart-at, arrive-by, or leave-now trip */
- name: 'departArrive',
- routingTypes: [ 'ITINERARY' ],
- default: 'NOW',
- itineraryRewrite: value => ({ arriveBy: (value === 'ARRIVE') })
- },
-
- { /* startTime - the start time for a profile trip, in HH:mm format */
- name: 'startTime',
- routingTypes: [ 'PROFILE' ],
- default: '07:00'
- },
-
- { /* endTime - the end time for a profile trip, in HH:mm format */
- name: 'endTime',
- routingTypes: [ 'PROFILE' ],
- default: '09:00'
- },
-
- { /* mode - the allowed modes for a trip, as a comma-separated list */
- name: 'mode',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: 'WALK,TRANSIT', // TODO: make this dependent on routingType?
- profileRewrite: value => {
- const accessModes = []
- const directModes = []
- const transitModes = []
-
- if (value && value.length > 0) {
- value.split(',').forEach(m => {
- if (isTransit(m)) transitModes.push(m)
- if (isAccessMode(m)) {
- accessModes.push(m)
- // TODO: make configurable whether direct-driving is considered
- if (!isCar(m)) directModes.push(m)
- }
- })
- }
-
- return { accessModes, directModes, transitModes }
- }
- },
-
- { /* showIntermediateStops - whether response should include intermediate stops for transit legs */
- name: 'showIntermediateStops',
- routingTypes: [ 'ITINERARY' ],
- default: true
- },
-
- { /* maxWalkDistance - the maximum distance in meters the user will walk to transit. */
- name: 'maxWalkDistance',
- routingTypes: [ 'ITINERARY' ],
- applicable: query => query.mode && hasTransit(query.mode) && query.mode.indexOf('WALK') !== -1,
- default: 1207, // 3/4 mi.
- selector: 'DROPDOWN',
- label: 'Maximum Walk',
- options: [
- {
- text: '1/10 mile',
- value: 160.9
- }, {
- text: '1/4 mile',
- value: 402.3
- }, {
- text: '1/2 mile',
- value: 804.7
- }, {
- text: '3/4 mile',
- value: 1207
- }, {
- text: '1 mile',
- value: 1609
- }, {
- text: '2 miles',
- value: 3219
- }, {
- text: '5 miles',
- value: 8047
- }
- ]
- },
-
- { /* maxBikeDistance - the maximum distance in meters the user will bike. Not
- * actually an OTP parameter (maxWalkDistance doubles for biking) but we
- * store it separately internally in order to allow different default values,
- * options, etc. Translated to 'maxWalkDistance' via the rewrite function.
- */
- name: 'maxBikeDistance',
- routingTypes: [ 'ITINERARY' ],
- applicable: query => query.mode && hasTransit(query.mode) && query.mode.indexOf('BICYCLE') !== -1,
- default: 4828, // 3 mi.
- selector: 'DROPDOWN',
- label: 'Maximum Bike',
- options: [
- {
- text: '1/4 mile',
- value: 402.3
- }, {
- text: '1/2 mile',
- value: 804.7
- }, {
- text: '3/4 mile',
- value: 1207
- }, {
- text: '1 mile',
- value: 1609
- }, {
- text: '2 miles',
- value: 3219
- }, {
- text: '3 miles',
- value: 4828
- }, {
- text: '5 miles',
- value: 8047
- }, {
- text: '10 miles',
- value: 16093
- }, {
- text: '20 miles',
- value: 32187
- }, {
- text: '30 miles',
- value: 48280
- }
- ],
- itineraryRewrite: value => ({
- maxWalkDistance: value,
- // ensures that the value is repopulated when loaded from URL params
- maxBikeDistance: value
- })
- },
-
- { /* optimize -- how to optimize a trip (non-bike, non-micromobility trips) */
- name: 'optimize',
- applicable: query => hasTransit(query.mode) && !hasBike(query.mode),
- routingTypes: [ 'ITINERARY' ],
- default: 'QUICK',
- selector: 'DROPDOWN',
- label: 'Optimize for',
- options: [
- {
- text: 'Speed',
- value: 'QUICK'
- }, {
- text: 'Fewest Transfers',
- value: 'TRANSFERS'
- }
- ]
- },
-
- { /* optimizeBike -- how to optimize an bike-based trip */
- name: 'optimizeBike',
- applicable: query => hasBike(query.mode),
- routingTypes: [ 'ITINERARY' ],
- default: 'SAFE',
- selector: 'DROPDOWN',
- label: 'Optimize for',
- options: query => {
- const opts = [{
- text: 'Speed',
- value: 'QUICK'
- }, {
- text: 'Bike-Friendly Trip',
- value: 'SAFE'
- }, {
- text: 'Flat Trip',
- value: 'FLAT'
- }]
-
- // Include transit-specific option, if applicable
- if (hasTransit(query.mode)) {
- opts.splice(1, 0, {
- text: 'Fewest Transfers',
- value: 'TRANSFERS'
- })
- }
-
- return opts
- },
- itineraryRewrite: value => ({ optimize: value })
- },
-
- { /* maxWalkTime -- the maximum time the user will spend walking in minutes */
- name: 'maxWalkTime',
- routingTypes: [ 'PROFILE' ],
- default: 15,
- selector: 'DROPDOWN',
- label: 'Max Walk Time',
- applicable: query => query.mode && hasTransit(query.mode) && query.mode.indexOf('WALK') !== -1,
- options: [
- {
- text: '5 minutes',
- value: 5
- }, {
- text: '10 minutes',
- value: 10
- }, {
- text: '15 minutes',
- value: 15
- }, {
- text: '20 minutes',
- value: 20
- }, {
- text: '30 minutes',
- value: 30
- }, {
- text: '45 minutes',
- value: 45
- }, {
- text: '1 hour',
- value: 60
- }
- ]
- },
-
- { /* walkSpeed -- the user's walking speed in m/s */
- name: 'walkSpeed',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: 1.34,
- selector: 'DROPDOWN',
- label: 'Walk Speed',
- applicable: query => query.mode && query.mode.indexOf('WALK') !== -1,
- options: [
- {
- text: '2 MPH',
- value: 0.89
- }, {
- text: '3 MPH',
- value: 1.34
- }, {
- text: '4 MPH',
- value: 1.79
- }
- ]
- },
-
- { /* maxBikeTime -- the maximum time the user will spend biking in minutes */
- name: 'maxBikeTime',
- routingTypes: [ 'PROFILE' ],
- default: 20,
- selector: 'DROPDOWN',
- label: 'Max Bike Time',
- applicable: query => query.mode && hasTransit(query.mode) && query.mode.indexOf('BICYCLE') !== -1,
- options: [
- {
- text: '5 minutes',
- value: 5
- }, {
- text: '10 minutes',
- value: 10
- }, {
- text: '15 minutes',
- value: 15
- }, {
- text: '20 minutes',
- value: 20
- }, {
- text: '30 minutes',
- value: 30
- }, {
- text: '45 minutes',
- value: 45
- }, {
- text: '1 hour',
- value: 60
- }
- ]
- },
-
- { /* bikeSpeed -- the user's bikeSpeed speed in m/s */
- name: 'bikeSpeed',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: 3.58,
- selector: 'DROPDOWN',
- label: 'Bicycle Speed',
- applicable: query => query.mode && query.mode.indexOf('BICYCLE') !== -1,
- options: [
- {
- text: '6 MPH',
- value: 2.68
- }, {
- text: '8 MPH',
- value: 3.58
- }, {
- text: '10 MPH',
- value: 4.47
- }, {
- text: '12 MPH',
- value: 5.36
- }
- ]
- },
-
- { /* maxEScooterDistance - the maximum distance in meters the user will ride
- * an E-scooter. Not actually an OTP parameter (maxWalkDistance doubles for
- * any non-transit mode except for car) but we store it separately
- * internally in order to allow different default values, options, etc.
- * Translated to 'maxWalkDistance' via the rewrite function.
- */
- name: 'maxEScooterDistance',
- routingTypes: [ 'ITINERARY' ],
- applicable: query => query.mode && hasTransit(query.mode) && hasMicromobility(query.mode),
- default: 4828, // 3 mi.
- selector: 'DROPDOWN',
- label: 'Maximum E-scooter Distance',
- options: [
- {
- text: '1/4 mile',
- value: 402.3
- }, {
- text: '1/2 mile',
- value: 804.7
- }, {
- text: '3/4 mile',
- value: 1207
- }, {
- text: '1 mile',
- value: 1609
- }, {
- text: '2 miles',
- value: 3219
- }, {
- text: '3 miles',
- value: 4828
- }, {
- text: '5 miles',
- value: 8047
- }, {
- text: '10 miles',
- value: 16093
- }, {
- text: '20 miles',
- value: 32187
- }, {
- text: '30 miles',
- value: 48280
- }
- ],
- itineraryRewrite: value => ({
- maxWalkDistance: value,
- // ensures that the value is repopulated when loaded from URL params
- maxEScooterDistance: value
- })
- },
-
- { /* bikeSpeed -- the user's bikeSpeed speed in m/s */
- name: 'watts',
- routingTypes: [ 'ITINERARY', 'PROFILE' ],
- default: 250,
- selector: 'DROPDOWN',
- label: 'E-scooter Power',
- // this configuration should only be allowed for personal E-scooters as these
- // settings will be defined by the vehicle type of an E-scooter being rented
- applicable: query => (
- query.mode &&
- query.mode.indexOf('MICROMOBILITY') !== -1 &&
- query.mode.indexOf('MICROMOBILITY_RENT') === -1
- ),
- options: [
- {
- text: 'Kid\'s hoverboard (6mph)',
- value: 125
- }, {
- text: 'Entry-level scooter (11mph)',
- value: 250
- }, {
- text: 'Robust E-scooter (18mph)',
- value: 500
- }, {
- text: 'Powerful E-scooter (24mph)',
- value: 1500
- }
- ],
- // rewrite a few other values to add some baseline assumptions about the
- // vehicle
- itineraryRewrite: value => {
- const watts = value
- // the maximum cruising and downhill speed. Units in m/s
- let maximumMicromobilitySpeed
- let weight
- // see https://en.wikipedia.org/wiki/Human_body_weight#Average_weight_around_the_world
- // estimate is for an average North American human with clothes and stuff
- // units are in kg
- const TYPICAL_RIDER_WEIGHT = 90
- switch (watts) {
- case 125:
- // exemplar: Swagtron Turbo 5 hoverboard (https://swagtron.com/product/recertified-swagtron-turbo-five-hoverboard-classic/)
- maximumMicromobilitySpeed = 2.8 // ~= 6mph
- weight = TYPICAL_RIDER_WEIGHT + 9
- break
- case 250:
- // exemplar: Xiaomi M365 (https://www.gearbest.com/skateboard/pp_596618.html)
- maximumMicromobilitySpeed = 5 // ~= 11.5mph
- weight = TYPICAL_RIDER_WEIGHT + 12.5
- break
- case 500:
- // exemplar: Razor EcoSmart Metro (https://www.amazon.com/Razor-EcoSmart-Metro-Electric-Scooter/dp/B002ZDAEIS?SubscriptionId=AKIAJMXJ2YFJTEDLQMUQ&tag=digitren08-20&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B002ZDAEIS&ascsubtag=15599460143449ocb)
- maximumMicromobilitySpeed = 8 // ~= 18mph
- weight = TYPICAL_RIDER_WEIGHT + 30
- break
- case 1000:
- // exemplar: Boosted Rev (https://boostedboards.com/vehicles/scooters/boosted-rev)
- maximumMicromobilitySpeed = 11 // ~= 24mph
- weight = TYPICAL_RIDER_WEIGHT + 21
- break
- }
- return {maximumMicromobilitySpeed, watts, weight}
- }
- },
-
- { /* ignoreRealtimeUpdates -- if true, do not use realtime updates in routing */
- name: 'ignoreRealtimeUpdates',
- routingTypes: [ 'ITINERARY' ],
- default: false
- },
-
- { /* companies -- tnc companies to query */
- name: 'companies',
- routingTypes: [ 'ITINERARY' ],
- default: null
- },
-
- { /* wheelchair -- whether the user requires a wheelchair-accessible trip */
- name: 'wheelchair',
- routingTypes: [ 'ITINERARY' ],
- default: false,
- selector: 'CHECKBOX',
- label: 'Wheelchair Accessible',
- applicable: (query, config) => {
- if (!query.mode || !config.modes) return false
- const configModes = (config.modes.accessModes || []).concat(config.modes.transitModes || [])
- for (const mode of query.mode.split(',')) {
- const configMode = configModes.find(m => m.mode === mode)
- if (!configMode || !configMode.showWheelchairSetting) continue
- if (configMode.company && (!query.companies || !query.companies.split(',').includes(configMode.company))) continue
- return true
- }
- }
- }
-]
-// Iterate over stored settings and update query param defaults.
-// FIXME: this does not get updated if the user defaults are cleared
-queryParams.forEach(param => {
- if (param.name in storedSettings) {
- param.default = storedSettings[param.name]
- param.userDefaultOverride = true
- }
-})
-
-export default queryParams
diff --git a/lib/util/query.js b/lib/util/query.js
deleted file mode 100644
index 5779108c7..000000000
--- a/lib/util/query.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import qs from 'qs'
-
-import { getTransitModes, hasTransit, isAccessMode, toSentenceCase } from './itinerary'
-import { coordsToString, matchLatLon, stringToCoords } from './map'
-import queryParams from './query-params'
-import { getActiveSearch } from './state'
-import { getCurrentTime, getCurrentDate } from './time'
-
-/* The list of default parameters considered in the settings panel */
-
-export const defaultParams = [
- 'wheelchair',
- 'maxWalkDistance',
- 'maxWalkTime',
- 'walkSpeed',
- 'maxBikeDistance',
- 'maxBikeTime',
- 'bikeSpeed',
- 'optimize',
- 'optimizeBike',
- 'maxEScooterDistance',
- 'watts'
-]
-
-/* A function to retrieve a property value from an entry in the query-params
- * table, checking for either a static value or a function */
-
-export function getQueryParamProperty (paramInfo, property, query) {
- return typeof paramInfo[property] === 'function'
- ? paramInfo[property](query)
- : paramInfo[property]
-}
-
-export function ensureSingleAccessMode (queryModes) {
- // Count the number of access modes
- const accessCount = queryModes.filter(m => isAccessMode(m)).length
-
- // If multiple access modes are specified, keep only the first one
- if (accessCount > 1) {
- const firstAccess = queryModes.find(m => isAccessMode(m))
- queryModes = queryModes.filter(m => !isAccessMode(m) || m === firstAccess)
-
- // If no access modes are specified, add 'WALK' as the default
- } else if (accessCount === 0) {
- queryModes.push('WALK')
- }
-
- return queryModes
-}
-
-export function getUrlParams () {
- return qs.parse(window.location.href.split('?')[1])
-}
-
-export function getOtpUrlParams () {
- return Object.keys(getUrlParams()).filter(key => !key.startsWith('ui_'))
-}
-
-function findLocationType (location, locations = [], types = ['home', 'work', 'suggested']) {
- const match = locations.find(l => matchLatLon(l, location))
- return match && types.indexOf(match.type) !== -1 ? match.type : null
-}
-
-export function summarizeQuery (query, locations = []) {
- const from = findLocationType(query.from, locations) || query.from.name.split(',')[0]
- const to = findLocationType(query.to, locations) || query.to.name.split(',')[0]
- const mode = hasTransit(query.mode)
- ? 'Transit'
- : toSentenceCase(query.mode)
- return `${mode} from ${from} to ${to}`
-}
-
-/**
- * Assemble any UI-state properties to be tracked via URL into a single object
- * TODO: Expand to include additional UI properties
- */
-
-export function getUiUrlParams (otpState) {
- const activeSearch = getActiveSearch(otpState)
- const uiParams = {
- ui_activeItinerary: activeSearch ? activeSearch.activeItinerary : 0,
- ui_activeSearch: otpState.activeSearchId
- }
- return uiParams
-}
-
-export function getTripOptionsFromQuery (query, keepPlace = false) {
- const options = Object.assign({}, query)
- // Delete time/date options and from/to
- delete options.time
- delete options.departArrive
- delete options.date
- if (!keepPlace) {
- delete options.from
- delete options.to
- }
- return options
-}
-
-/**
- * Gets the default query param by executing the default value function with the
- * provided otp config if the default value is a function.
- */
-function getDefaultQueryParamValue (param, config) {
- return typeof param.default === 'function' ? param.default(config) : param.default
-}
-
-/**
- * Determines whether the specified query differs from the default query, i.e.,
- * whether the user has modified any trip options (including mode) from their
- * default values.
- */
-export function isNotDefaultQuery (query, config) {
- const activeModes = query.mode.split(',')
- const defaultModes = getTransitModes(config).concat(['WALK'])
- let queryIsDifferent = false
- const modesEqual = (activeModes.length === defaultModes.length) &&
- activeModes.sort().every((value, index) => { return value === defaultModes.sort()[index] })
-
- if (!modesEqual) {
- queryIsDifferent = true
- } else {
- defaultParams.forEach(param => {
- const paramInfo = queryParams.find(qp => qp.name === param)
- // Check that the parameter applies to the specified routingType
- if (!paramInfo.routingTypes.includes(query.routingType)) return
- // Check that the applicability test (if provided) is satisfied
- if (typeof paramInfo.applicable === 'function' &&
- !paramInfo.applicable(query, config)) return
- if (query[param] !== getDefaultQueryParamValue(paramInfo, config)) {
- queryIsDifferent = true
- }
- })
- }
- return queryIsDifferent
-}
-
-/**
- * Get the default query to OTP based on the given config.
- *
- * @param config the config in the otp-rr store.
- */
-export function getDefaultQuery (config) {
- const defaultQuery = { routingType: 'ITINERARY' }
- queryParams.filter(qp => 'default' in qp).forEach(qp => {
- defaultQuery[qp.name] = getDefaultQueryParamValue(qp, config)
- })
- return defaultQuery
-}
-
-/**
- * Create a otp query based on a the url params.
- *
- * @param {Object} params An object representing the parsed querystring of url
- * params.
- * @param config the config in the otp-rr store.
- */
-export function planParamsToQuery (params, config) {
- const query = {}
- for (var key in params) {
- switch (key) {
- case 'fromPlace':
- query.from = parseLocationString(params.fromPlace)
- break
- case 'toPlace':
- query.to = parseLocationString(params.toPlace)
- break
- case 'arriveBy':
- query.departArrive = params.arriveBy === 'true'
- ? 'ARRIVE'
- : params.arriveBy === 'false'
- ? 'DEPART'
- : 'NOW'
- break
- case 'date':
- query.date = params.date || getCurrentDate(config)
- break
- case 'time':
- query.time = params.time || getCurrentTime(config)
- break
- default:
- if (!isNaN(params[key])) query[key] = parseFloat(params[key])
- else query[key] = params[key]
- }
- }
- return query
-}
-
-/**
- * OTP allows passing a location in the form '123 Main St::lat,lon', so we check
- * for the double colon and parse the coordinates accordingly.
- */
-function parseLocationString (value) {
- const parts = value.split('::')
- const coordinates = parts[1]
- ? stringToCoords(parts[1])
- : stringToCoords(parts[0])
- const name = parts[1]
- ? parts[0]
- : coordsToString(coordinates)
- return coordinates.length === 2 ? {
- name: name || null,
- lat: coordinates[0] || null,
- lon: coordinates[1] || null
- } : null
-}
diff --git a/lib/util/reverse.js b/lib/util/reverse.js
deleted file mode 100644
index 8f8f93409..000000000
--- a/lib/util/reverse.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// TODO: add reverse geocode for map click
-// export async function reversePelias (point) {
-// const location = {lon: point.lng, lat: point.lat}
-// const apiKey = getConfigProperty('MAPZEN_TURN_BY_TURN_KEY')
-// const params = {
-// api_key: apiKey,
-// ...location
-// }
-// const url = `https://search.mapzen.com/v1/reverse?${qs.stringify(params)}`
-// const response = await fetch(url)
-// return await response.json()
-// }
diff --git a/lib/util/state.js b/lib/util/state.js
index 8019564e8..cc22fc24c 100644
--- a/lib/util/state.js
+++ b/lib/util/state.js
@@ -1,5 +1,8 @@
+import coreUtils from '@opentripplanner/core-utils'
import isEqual from 'lodash.isequal'
+import { MainPanelContent } from '../actions/ui'
+
/**
* Get the active search object
* @param {Object} otpState the OTP state object
@@ -135,3 +138,44 @@ export function getShowUserSettings (otpState) {
export function getStopViewerConfig (otpState) {
return otpState.config.stopViewer
}
+
+/**
+ * Assemble any UI-state properties to be tracked via URL into a single object
+ * TODO: Expand to include additional UI properties
+ */
+export function getUiUrlParams (otpState) {
+ const activeSearch = getActiveSearch(otpState)
+ const uiParams = {
+ ui_activeItinerary: activeSearch ? activeSearch.activeItinerary : 0,
+ ui_activeSearch: otpState.activeSearchId
+ }
+ return uiParams
+}
+
+// Set default title to the original document title (on load) set in index.html
+const DEFAULT_TITLE = document.title
+
+export function getTitle (state) {
+ // Override title can optionally be provided in config.yml
+ const { config, ui, user } = state.otp
+ let title = config.title || DEFAULT_TITLE
+ const { mainPanelContent, viewedRoute, viewedStop } = ui
+ switch (mainPanelContent) {
+ case MainPanelContent.ROUTE_VIEWER:
+ title += ' | Route'
+ if (viewedRoute && viewedRoute.routeId) title += ` ${viewedRoute.routeId}`
+ break
+ case MainPanelContent.STOP_VIEWER:
+ title += ' | Stop'
+ if (viewedStop && viewedStop.stopId) title += ` ${viewedStop.stopId}`
+ break
+ default:
+ const activeSearch = getActiveSearch(state.otp)
+ if (activeSearch) {
+ title += ` | ${coreUtils.query.summarizeQuery(activeSearch.query, user.locations)}`
+ }
+ break
+ }
+ // if (printView) title += ' | Print'
+ return title
+}
diff --git a/lib/util/storage.js b/lib/util/storage.js
deleted file mode 100644
index 4d9b7d88c..000000000
--- a/lib/util/storage.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Prefix to use with local storage keys.
-const STORAGE_PREFIX = 'otp'
-
-/**
- * Store a javascript object at the specified key.
- */
-export function storeItem (key, object) {
- window.localStorage.setItem(`${STORAGE_PREFIX}.${key}`, JSON.stringify(object))
-}
-
-/**
- * Retrieve a javascript object at the specified key. If not found, defaults to
- * null or, the optionally provided notFoundValue.
- */
-export function getItem (key, notFoundValue = null) {
- let itemAsString
- try {
- itemAsString = window.localStorage.getItem(`${STORAGE_PREFIX}.${key}`)
- const json = JSON.parse(itemAsString)
- if (json) return json
- else return notFoundValue
- } catch (e) {
- // Catch any errors associated with parsing bad JSON.
- console.warn(e, itemAsString)
- return notFoundValue
- }
-}
-
-/**
- * Remove item at specified key.
- */
-export function removeItem (key) {
- window.localStorage.removeItem(`${STORAGE_PREFIX}.${key}`)
-}
-
-/**
- * Generate a random ID. This might not quite be a UUID, but it serves our
- * purposes for now.
- */
-export function randId () {
- return Math.random().toString(36).substr(2, 9)
-}
diff --git a/lib/util/time.js b/lib/util/time.js
deleted file mode 100644
index be7e5964c..000000000
--- a/lib/util/time.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import moment from 'moment'
-import 'moment-timezone'
-
-// special constants for making sure the following date format is always sent to
-// OTP regardless of whatever the user has configured as the display format
-export const OTP_API_DATE_FORMAT = 'YYYY-MM-DD'
-export const OTP_API_TIME_FORMAT = 'HH:mm'
-
-/**
- * @param {[type]} config the OTP config object found in store
- * @return {string} the config-defined time formatter or HH:mm (24-hr time)
- */
-export function getTimeFormat (config) {
- return (config.dateTime && config.dateTime.timeFormat)
- ? config.dateTime.timeFormat
- : OTP_API_TIME_FORMAT
-}
-
-export function getDateFormat (config) {
- return (config.dateTime && config.dateTime.dateFormat)
- ? config.dateTime.dateFormat
- : OTP_API_DATE_FORMAT
-}
-
-export function getLongDateFormat (config) {
- return (config.dateTime && config.dateTime.longDateFormat)
- ? config.dateTime.longDateFormat
- : 'D MMMM YYYY'
-}
-
-/**
- * Formats an elapsed time duration for display in narrative
- * TODO: internationalization
- * @param {number} seconds duration in seconds
- * @returns {string} formatted text representation
- */
-export function formatDuration (seconds) {
- const dur = moment.duration(seconds, 'seconds')
- let text = ''
- if (dur.hours() > 0) text += dur.hours() + ' hr, '
- text += dur.minutes() + ' min'
- return text
-}
-
-/**
- * Formats a time value for display in narrative
- * TODO: internationalization/timezone
- * @param {number} ms epoch time value in milliseconds
- * @returns {string} formatted text representation
- */
-export function formatTime (ms, options) {
- return moment(ms + (options && options.offset ? options.offset : 0))
- .format(options && options.format ? options.format : OTP_API_TIME_FORMAT)
-}
-
-/**
- * Formats a seconds after midnight value for display in narrative
- * @param {number} seconds time since midnight in seconds
- * @param {string} timeFormat A valid moment.js time format
- * @return {string} formatted text representation
- */
-export function formatSecondsAfterMidnight (seconds, timeFormat) {
- return moment().startOf('day').seconds(seconds).format(timeFormat)
-}
-
-/**
- * Formats current time for use in OTP query
- * The conversion to the user's timezone is needed for testing purposes.
- */
-export function getCurrentTime () {
- return moment().tz(getUserTimezone()).format(OTP_API_TIME_FORMAT)
-}
-
-/**
- * Formats current date for use in OTP query
- * The conversion to the user's timezone is needed for testing purposes.
- */
-export function getCurrentDate (config) {
- return moment().tz(getUserTimezone()).format(OTP_API_DATE_FORMAT)
-}
-
-/**
- * Get the timezone name that is set for the user that is currently looking at
- * this website. Use a bit of hackery to force a specific timezone if in a
- * test environment.
- */
-export function getUserTimezone () {
- return process.env.NODE_ENV === 'test' ? process.env.TZ : moment.tz.guess()
-}
diff --git a/lib/util/ui.js b/lib/util/ui.js
deleted file mode 100644
index bf5be4753..000000000
--- a/lib/util/ui.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import { MainPanelContent } from '../actions/ui'
-import { summarizeQuery } from './query'
-import { getActiveSearch } from './state'
-
-// Set default title to the original document title (on load) set in index.html
-const DEFAULT_TITLE = document.title
-
-export function isMobile () {
- // TODO: consider using 3rd-party library?
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
-}
-
-/**
- * Enables scrolling for a specified selector, while disabling scrolling for all
- * other targets. This is adapted from https://stackoverflow.com/a/41601290/915811
- * and intended to fix issues with iOS elastic scrolling, e.g.,
- * https://github.com/conveyal/trimet-mod-otp/issues/92.
- */
-export function enableScrollForSelector (selector) {
- const _overlay = document.querySelector(selector)
- let _clientY = null // remember Y position on touch start
-
- _overlay.addEventListener('touchstart', function (event) {
- if (event.targetTouches.length === 1) {
- // detect single touch
- _clientY = event.targetTouches[0].clientY
- }
- }, false)
-
- _overlay.addEventListener('touchmove', function (event) {
- if (event.targetTouches.length === 1) {
- // detect single touch
- disableRubberBand(event)
- }
- }, false)
-
- function disableRubberBand (event) {
- const clientY = event.targetTouches[0].clientY - _clientY
-
- if (_overlay.scrollTop === 0 && clientY > 0) {
- // element is at the top of its scroll
- event.preventDefault()
- }
-
- if (isOverlayTotallyScrolled() && clientY < 0) {
- // element is at the top of its scroll
- event.preventDefault()
- }
- }
-
- function isOverlayTotallyScrolled () {
- // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
- return _overlay.scrollHeight - _overlay.scrollTop <= _overlay.clientHeight
- }
-}
-
-export function getTitle (state) {
- // Override title can optionally be provided in config.yml
- const { config, ui, user } = state.otp
- let title = config.title || DEFAULT_TITLE
- const { mainPanelContent, viewedRoute, viewedStop } = ui
- switch (mainPanelContent) {
- case MainPanelContent.ROUTE_VIEWER:
- title += ' | Route'
- if (viewedRoute && viewedRoute.routeId) title += ` ${viewedRoute.routeId}`
- break
- case MainPanelContent.STOP_VIEWER:
- title += ' | Stop'
- if (viewedStop && viewedStop.stopId) title += ` ${viewedStop.stopId}`
- break
- default:
- const activeSearch = getActiveSearch(state.otp)
- if (activeSearch) {
- title += ` | ${summarizeQuery(activeSearch.query, user.locations)}`
- }
- break
- }
- // if (printView) title += ' | Print'
- return title
-}
diff --git a/package.json b/package.json
index 080c59b60..f2ac610dc 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"@opentripplanner/vehicle-rental-overlay": "^0.0.20",
"@turf/along": "^6.0.1",
"bootstrap": "^3.3.7",
+ "bowser": "^1.9.3",
"clone": "^2.1.0",
"connected-react-router": "^6.5.2",
"copy-to-clipboard": "^3.0.8",
diff --git a/yarn.lock b/yarn.lock
index 2db3165eb..e2ad479a9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3517,6 +3517,11 @@ bottleneck@^2.18.1:
resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.4.tgz#63c505687a0ddaf89a6f515225c75e05833bb079"
integrity sha512-2poBdvpAGG+dkMVKZqtDhyuMN6JviD81h89W4bfmt3UO7O60F+qf/84V0alNqL8PM1RByl4SZ1fVMu/ZvxkmcA==
+bowser@^1.9.3:
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
+ integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==
+
bowser@^2.7.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9"