@opentripplanner/core-utils
Advanced tools
Comparing version 0.0.19 to 0.0.20
@@ -16,2 +16,4 @@ "use strict"; | ||
var queryParams = _interopRequireWildcard(require("./query-params")); | ||
var storage = _interopRequireWildcard(require("./storage")); | ||
@@ -34,2 +36,3 @@ | ||
query, | ||
queryParams, | ||
storage, | ||
@@ -36,0 +39,0 @@ time, |
@@ -28,3 +28,2 @@ "use strict"; | ||
exports.getLegBounds = getLegBounds; | ||
exports.routeComparator = routeComparator; | ||
exports.legLocationAtDistance = legLocationAtDistance; | ||
@@ -41,3 +40,3 @@ exports.legElevationAtDistance = legElevationAtDistance; | ||
exports.getTimeZoneOffset = getTimeZoneOffset; | ||
exports.transitModes = void 0; | ||
exports.routeComparator = exports.transitModes = void 0; | ||
@@ -281,22 +280,2 @@ var _polyline = _interopRequireDefault(require("@mapbox/polyline")); | ||
} | ||
function routeComparator(a, b) { | ||
let aComp; | ||
let bComp; | ||
if (a.sortOrder !== null && b.sortOrder !== null) { | ||
aComp = a.sortOrder; | ||
bComp = b.sortOrder; | ||
} else if (!Number.isNaN(parseInt(a.shortName, 10)) && !Number.isNaN(parseInt(b.shortName, 10))) { | ||
aComp = parseInt(a.shortName, 10); | ||
bComp = parseInt(b.shortName, 10); | ||
} else { | ||
aComp = a.shortName || a.longName; | ||
bComp = b.shortName || b.longName; | ||
} | ||
if (aComp < bComp) return -1; | ||
if (aComp > bComp) return 1; | ||
return 0; | ||
} | ||
/* Returns an interpolated lat-lon at a specified distance along a leg */ | ||
@@ -311,3 +290,5 @@ | ||
const pt = (0, _along.default)(line, distance, "meters"); | ||
const pt = (0, _along.default)(line, distance, { | ||
units: "meters" | ||
}); | ||
@@ -568,2 +549,275 @@ if (pt && pt.geometry && pt.geometry.coordinates) { | ||
return itinerary.legs[0].agencyTimeZoneOffset + (new Date().getTimezoneOffset() + dstOffset) * 60000; | ||
} | ||
} | ||
/** | ||
* The functions below are for enhanced route sorting functions for | ||
* the route viewer on OTP-react-redux. | ||
* They address route ordering issues discussed in | ||
* https://github.com/opentripplanner/otp-react-redux/pull/123 and | ||
* https://github.com/opentripplanner/otp-react-redux/pull/124. | ||
*/ | ||
/** | ||
* 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]; | ||
} | ||
if (typeof routeTypeComparatorValue[route.type] !== "undefined") { | ||
return routeTypeComparatorValue[route.type]; | ||
} // 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) { | ||
/* Note: Using the global version of isNaN (the Number version behaves differently. */ | ||
/* eslint-disable no-restricted-globals */ | ||
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. | ||
*/ | ||
const routeComparator = makeMultiCriteriaSort(makeNumericValueComparator(obj => getRouteSortOrderValue(obj.sortOrder)), routeTypeComparator, alphabeticShortNameComparator, makeNumericValueComparator(obj => parseInt(obj.shortName, 10)), makeStringValueComparator(obj => obj.shortName), makeStringValueComparator(obj => obj.longName)); | ||
exports.routeComparator = routeComparator; |
@@ -303,7 +303,7 @@ "use strict"; | ||
const fareType = _propTypes.default.shape({ | ||
details: _propTypes.default.objectOf(_propTypes.default.shape({ | ||
fareId: feedScopedIdType.isRequired, | ||
details: _propTypes.default.objectOf(_propTypes.default.arrayOf(_propTypes.default.shape({ | ||
fareId: _propTypes.default.oneOfType([_propTypes.default.string, feedScopedIdType]).isRequired, | ||
price: moneyType.isRequired, | ||
routes: _propTypes.default.arrayOf(feedScopedIdType).isRequired | ||
}).isRequired), | ||
routes: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, feedScopedIdType])).isRequired | ||
})).isRequired), | ||
fare: _propTypes.default.objectOf(moneyType) | ||
@@ -546,4 +546,4 @@ }); | ||
const queryType = _propTypes.default.shape({ | ||
from: _propTypes.default.string, | ||
to: _propTypes.default.string, | ||
from: locationType, | ||
to: locationType, | ||
date: _propTypes.default.string, | ||
@@ -550,0 +550,0 @@ time: _propTypes.default.string, |
{ | ||
"name": "@opentripplanner/core-utils", | ||
"version": "0.0.19", | ||
"version": "0.0.20", | ||
"description": "Core functionality that is shared among numerous UI components", | ||
@@ -20,3 +20,3 @@ "main": "lib/index.js", | ||
}, | ||
"gitHead": "b31b6cc7bce62577a9353610a7a09a317dc1f800" | ||
"gitHead": "d3c1a989083b925d9a08eb053c750a57013524ac" | ||
} |
@@ -5,2 +5,3 @@ import * as itinerary from "./itinerary"; | ||
import * as query from "./query"; | ||
import * as queryParams from "./query-params"; | ||
import * as storage from "./storage"; | ||
@@ -16,2 +17,3 @@ import * as time from "./time"; | ||
query, | ||
queryParams, | ||
storage, | ||
@@ -18,0 +20,0 @@ time, |
@@ -231,23 +231,2 @@ import polyline from "@mapbox/polyline"; | ||
export function routeComparator(a, b) { | ||
let aComp; | ||
let bComp; | ||
if (a.sortOrder !== null && b.sortOrder !== null) { | ||
aComp = a.sortOrder; | ||
bComp = b.sortOrder; | ||
} else if ( | ||
!Number.isNaN(parseInt(a.shortName, 10)) && | ||
!Number.isNaN(parseInt(b.shortName, 10)) | ||
) { | ||
aComp = parseInt(a.shortName, 10); | ||
bComp = parseInt(b.shortName, 10); | ||
} else { | ||
aComp = a.shortName || a.longName; | ||
bComp = b.shortName || b.longName; | ||
} | ||
if (aComp < bComp) return -1; | ||
if (aComp > bComp) return 1; | ||
return 0; | ||
} | ||
/* Returns an interpolated lat-lon at a specified distance along a leg */ | ||
@@ -260,3 +239,3 @@ | ||
const line = polyline.toGeoJSON(leg.legGeometry.points); | ||
const pt = turfAlong(line, distance, "meters"); | ||
const pt = turfAlong(line, distance, { units: "meters" }); | ||
if (pt && pt.geometry && pt.geometry.coordinates) { | ||
@@ -511,1 +490,252 @@ return [pt.geometry.coordinates[1], pt.geometry.coordinates[0]]; | ||
} | ||
/** | ||
* The functions below are for enhanced route sorting functions for | ||
* the route viewer on OTP-react-redux. | ||
* They address route ordering issues discussed in | ||
* https://github.com/opentripplanner/otp-react-redux/pull/123 and | ||
* https://github.com/opentripplanner/otp-react-redux/pull/124. | ||
*/ | ||
/** | ||
* 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]; | ||
} | ||
if (typeof routeTypeComparatorValue[route.type] !== "undefined") { | ||
return routeTypeComparatorValue[route.type]; | ||
} | ||
// 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) { | ||
/* Note: Using the global version of isNaN (the Number version behaves differently. */ | ||
/* eslint-disable no-restricted-globals */ | ||
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, 10)), | ||
makeStringValueComparator(obj => obj.shortName), | ||
makeStringValueComparator(obj => obj.longName) | ||
); |
@@ -269,7 +269,12 @@ import PropTypes from "prop-types"; | ||
details: PropTypes.objectOf( | ||
PropTypes.shape({ | ||
fareId: feedScopedIdType.isRequired, | ||
price: moneyType.isRequired, | ||
routes: PropTypes.arrayOf(feedScopedIdType).isRequired | ||
}).isRequired | ||
PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
fareId: PropTypes.oneOfType([PropTypes.string, feedScopedIdType]) | ||
.isRequired, | ||
price: moneyType.isRequired, | ||
routes: PropTypes.arrayOf( | ||
PropTypes.oneOfType([PropTypes.string, feedScopedIdType]) | ||
).isRequired | ||
}) | ||
).isRequired | ||
), | ||
@@ -502,4 +507,4 @@ fare: PropTypes.objectOf(moneyType) | ||
export const queryType = PropTypes.shape({ | ||
from: PropTypes.string, | ||
to: PropTypes.string, | ||
from: locationType, | ||
to: locationType, | ||
date: PropTypes.string, | ||
@@ -506,0 +511,0 @@ time: PropTypes.string, |
204249
30
5671