diff --git a/README.md b/README.md index 758cb52..9e478ec 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ If you have any trouble using the app, please send an email to Nate Wessel (nate ## Outputs The app can return results in either CSV or JSON format. The fields in either case are the same. There will be one column for each of the input parameters: -* corridor * time range * date range * days of week @@ -32,10 +31,14 @@ The app can return results in either CSV or JSON format. The fields in either ca The other fields may require some explanation: -| Field | Description | +| Field | Description | |----|----| +| `routeStreets` | The name(s) of the streets along the corridor. I.e. the path taken. | +| `startCrossStreets` | The names of any cross-street(s) at the start of the corridor. If the corridor starts mid-block then coordinates of that point will be returned instead. | +| `endCrossStreets` | The names of any cross-street(s) at the end of the corridor. If the corridor ends mid-block then coordinates of that point will be returned instead. | | `mean_travel_time_minutes` | The mean travel time in minutes is given as a floating point number rounded to two decimal places. Where insufficient data was available to complete the request, the value will be null, and in cases where the request was impossible a value of -999 will be assigned. (See `hoursInRange` below). | -| `URI` | The URI is the API endpoint that corresponds to this exect request. It may be of little use to some end users but may help us to reproduce the request and verify data quality. It can also serve as a unique ID. | +| `mean_travel_time_seconds` | Same as above, but measured in seconds. | +| `URI` | The URI is the API endpoint that corresponds to this exect request. It may be of little use to some end users but can help us to reproduce the request and verify data quality. It can also serve as a unique ID for the record. | | `hoursInRange` | The total number of hours that are theoretically within the scope of this request. This does not imply that data is/was available at all times. It's possible to construct requests with zero hours in range such as e.g `2023-01-01` to `2023-01-02`, Mondays only (There's only one Sunday in that range). Impossible combinations are included in the output for clarity and completeness but are not actually executed against the API and should return an error. | | `estimatedVehicleCount` | A very rough estimate of the number of actual vehicles traversing the corridor within the given temporal bounds. Includes partial trips through the corridor and may be subject to additional caveats which we are still exploring. | diff --git a/frontend/src/Sidebar/index.jsx b/frontend/src/Sidebar/index.jsx index 0674382..642a6d4 100644 --- a/frontend/src/Sidebar/index.jsx +++ b/frontend/src/Sidebar/index.jsx @@ -4,7 +4,6 @@ import { DataContext } from '../Layout' import FactorContainer from './FactorContainer' import BigButton from './BigButton' import FactorList from './FactorList' -import { TravelTimeQuery } from '../travelTimeQuery.js' import { restoreStateFromFile } from './restoreStateFromFile.js' import './sidebar.css' @@ -67,7 +66,7 @@ function Results(){ r.resultsRecord('csv')).join('\n'))}`} + href={`data:text/plain;charset=utf-8,${encodeURIComponent([...results[0].resultsRecord('').keys()].join(',') + '\n' + results.map(r=>r.resultsRecord('csv')).join('\n'))}`} > Download results as CSV diff --git a/frontend/src/corridor.js b/frontend/src/corridor.js index a610e3e..8643b9e 100644 --- a/frontend/src/corridor.js +++ b/frontend/src/corridor.js @@ -42,21 +42,40 @@ export class Corridor extends Factor { get viaStreets(){ return new Set( this.links.map( link => link.name ) ) } + get viaStreetsString(){ + return [...this.viaStreets].join(' & ') + } + get startCrossStreets(){ + try { return difference(this.#intersections[0].streetNames,this.viaStreets) } + catch (e) { return new Set() } + } + get startCrossStreetsString(){ + if(this.startCrossStreets.size > 0){ + return [...this.startCrossStreets].join(' & ') + }else if(this.#intersections.length > 0){ + return this.#intersections[0].displayCoords + } + return '' + } + get endCrossStreets(){ + try { return difference(this.#intersections[1].streetNames,this.viaStreets) } + catch (e) { return new Set() } + } + get endCrossStreetsString(){ + if(this.endCrossStreets.size > 0){ + return [...this.endCrossStreets].join(' & ') + }else if(this.#intersections.length > 1){ + return this.#intersections[1].displayCoords + } + return '' + } get name(){ if(this.#intersections.length == 1){ - return `Incomplete corridor starting from ${this.intersections[0].description}` + return `Incomplete corridor starting from ${this.startCrossStreetsString}` }else if(this.#intersections.length == 2 && this.viaStreets.size > 0){ - // routing should be done - let start = difference(this.intersections[0].streetNames,this.viaStreets) - let end = difference(this.intersections[1].streetNames,this.viaStreets) - // no cross-street of a different name - if(start.size == 0){ start.add(this.intersections[0].displayCoords) } - if(end.size == 0){ end.add(this.intersections[1].displayCoords) } - return `${[...this.viaStreets].join(' & ')} from ${[...start].join(' & ')} to ${[...end].join(' & ')}` + return `${this.viaStreetsString} from ${this.startCrossStreetsString} to ${this.endCrossStreetsString}` }else if(this.#intersections.length == 2){ // but no via streets (yet?) - let start = [...this.intersections[0].streetNames].join(' & ') - let end = [...this.intersections[1].streetNames].join(' & ') - return `from ${start} to ${end}` + return `from ${this.startCrossStreetsString} to ${this.endCrossStreetsString}` } return 'New Corridor' } @@ -68,14 +87,16 @@ export class Corridor extends Factor { function CorridorElement({corridor}){ return (
-
{corridor.name}
+
+ {corridor.name} +
{corridor.isActive && <>
{corridor.intersections.length == 0 && - <>Click on the map to identify the starting point + 'Click on the map to identify the starting point' } {corridor.intersections.length == 1 && - <>Click on the map to identify the end point + 'Click on the map to identify the end point' }
} diff --git a/frontend/src/travelTimeQuery.js b/frontend/src/travelTimeQuery.js index 4794e4d..0197439 100644 --- a/frontend/src/travelTimeQuery.js +++ b/frontend/src/travelTimeQuery.js @@ -53,33 +53,30 @@ export class TravelTimeQuery { return hoursPerDay * numDays } resultsRecord(type='json'){ - const record = { - URI: this.URI, - corridor: this.corridor.name, - timeRange: this.timeRange.name, - dateRange: this.dateRange.name, - daysOfWeek: this.days.name, - holidaysIncluded: this.#holidayOption.holidaysIncluded, - hoursInRange: this.hoursInRange, - estimatedVehicleCount: this.#estimatedSample, - mean_travel_time_minutes: this.#travelTime - } + // map used instead of object to preserve insertion order + const record = new Map() + record.set('URI',this.URI) + record.set('routeStreets',this.corridor.viaStreetsString) + record.set('startCrossStreets',this.corridor.startCrossStreetsString) + record.set('endCrossStreets',this.corridor.endCrossStreetsString) + record.set('timeRange',this.timeRange.name) + record.set('dateRange',this.dateRange.name) + record.set('daysOfWeek', this.days.name) + record.set('holidaysIncluded', this.#holidayOption.holidaysIncluded) + record.set('hoursInRange', this.hoursInRange) + record.set('estimatedVehicleCount', this.#estimatedSample) + record.set('mean_travel_time_minutes', this.#travelTime) + record.set('mean_travel_time_seconds', 60* this.#travelTime) + if(type=='json'){ - return record + return Object.fromEntries(record) // can't JSONify maps }else if(type=='csv'){ - return Object.values(record) - .map( value => { - - if(typeof value == 'string'){ - return `"${value}"` - } - return value - } ) + // add double quotes to strings and concatenate + return [...record.values()] + .map( value => typeof value == 'string' ? `"${value}"` : value ) .join(',') } - return 'invalid type requested' - } - static csvHeader(){ - return 'URI,corridor,timeRange,dateRange,daysOfWeek,holidaysIncluded,hoursPossible,estimatedSample,mean_travel_time_minutes' + // the keys of a map record are used to create the CSV header + return record } } \ No newline at end of file