Skip to content
This repository has been archived by the owner on Feb 1, 2023. It is now read-only.

Commit

Permalink
[Issue-23] Adds the ability to geo locate nearby bus stops. (#48)
Browse files Browse the repository at this point in the history
* Adds nearby permission intent

* Fetch nearby stops WIP

* Additional work on bus stops

* Additional Updates

* Adds nearby intent data

* Adds tests

* Adding DialogFlow

* Fixing tests
  • Loading branch information
JamesIves authored Feb 12, 2019
1 parent 8bd5490 commit 19da814
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 9 deletions.
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ jobs:
keys:
- v1-dependencies-{{ checksum "./functions/package.json" }}
- v1-dependencies-

- run:
name: Installing Dependencies
working_directory: ~/project/functions
Expand Down
Binary file modified DC-Metro.zip
Binary file not shown.
83 changes: 76 additions & 7 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import {
Table,
SimpleResponse,
Suggestions,
Permission,
LinkOutSuggestion,
List,
} from 'actions-on-google';
import {lineNamesEnum, serviceCodesEnum, convertCode} from './util/constants';
import {serviceIncidents} from './util/incidents';
import {fetchTrainTimetable, fetchBusTimetable} from './wmata';
import {
fetchTrainTimetable,
fetchBusTimetable,
fetchNearbyStops,
} from './wmata';
import {createNearbyStopList} from './util/bus';

const app = dialogflow({debug: true});

Expand All @@ -20,17 +27,24 @@ app.intent(
'metro_timetable',
async (
conv: any,
{transport, station}: {transport: string, station: string}
{transport, station}: {transport: string, station: string},
option: string
) => {
const transportParam = transport.toLowerCase();
let transportParam = transport.toLowerCase();
let stationParam = station.toLowerCase();

if (conv.contexts.get('bus_nearby_selection')) {
transportParam = 'bus';
stationParam = option;
}

if (
transportParam === 'train' ||
transportParam === 'rail' ||
transportParam === 'metro'
) {
// Handles train times.
const timetable: any = await fetchTrainTimetable(station);
const timetable: any = await fetchTrainTimetable(stationParam);

if (!timetable) {
conv.ask(
Expand Down Expand Up @@ -194,7 +208,7 @@ app.intent(
}
} else if (transportParam === 'bus') {
// Handles bus times.
const timetable: any = await fetchBusTimetable(station);
const timetable: any = await fetchBusTimetable(stationParam);

if (!timetable) {
conv.ask(
Expand Down Expand Up @@ -427,9 +441,9 @@ app.intent(
`To get the next train arrival at a Metro station you can say things such as 'Train times for Farragut North' or 'Rail times for Smithsonian'. What would you like me to do?`
);
} else if (transportParam === 'bus') {
conv.ask(new Suggestions(['Train Commands']));
conv.ask(new Suggestions(['Bus Stops Near Me', 'Train Commands']));
conv.ask(
`To find out when the next bus arrives you can say 'Bus times for 123', replacing the 123 with the stop id found on the Metro bus stop sign. What would you like me to do?`
`To find out when the next bus arrives you can say 'Bus times for 123', replacing the 123 with the stop id found on the Metro bus stop sign. You can also ask me to fetch bus stops near you. What would you like me to do?`
);
} else {
conv.ask(new Suggestions(['Train Commands', 'Bus Commands']));
Expand Down Expand Up @@ -491,4 +505,59 @@ app.intent('feedback_intent', (conv) => {
);
});

/**
* DialogFlow intent to ask for location permissions for nearby bus stops.
*/
app.intent('bus_stop_nearby_permission', (conv) => {
if (conv.surface.capabilities.has('actions.capability.SCREEN_OUTPUT')) {
conv.ask(
new Permission({
context: 'To get nearby bus stops',
permissions: 'DEVICE_PRECISE_LOCATION',
})
);
} else {
conv.ask(
'This action requires a device with a screen, is there anything else I can do for you?'
);
}
});

/**
* DialogFlow intent for asking the user which bus stop to choose.
*/
app.intent('bus_stop_nearby', async (conv: any, input, granted) => {
if (granted) {
const stops = await fetchNearbyStops(
conv.device.location.coordinates.latitude,
conv.device.location.coordinates.longitude
);

if (stops.length) {
conv.ask(
`Here are the bus stops I found nearby, select the one which you'd like to hear about, or say 'Bus stop' followed by the number.`
);

conv.contexts.set('bus_nearby_selection', 1);

const stopCells = await createNearbyStopList(stops);

conv.ask(
new List({
title: 'Nearby Bus Stops',
items: stopCells,
})
);
} else {
conv.ask(
`I couldn't find any bus stops near your current location. Is there anything else I can do for you?`
);
}
} else {
conv.ask(
`Unfortunately I require access to your location to show you nearby bus stops. Is there anything else I can do for you?`
);
}
});

exports.dcMetro = functions.https.onRequest(app);
73 changes: 72 additions & 1 deletion functions/src/tests/bus.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as test from 'tape';
import {getRelevantBusIncidents} from '../util/bus';
import {getRelevantBusIncidents, createNearbyStopList} from '../util/bus';

test('should get incidents that are relevant to the train lines in the station', (t: any) => {
t.plan(3);
Expand Down Expand Up @@ -93,3 +93,74 @@ test('should get incidents that are relevant to the train lines in the station',
'Should get incidents affecting JI and PQ route.'
);
});

test('should generate an object with all of the correct keys for the nearby bus stop intent', (t) => {
const stops = [
{
Lat: 38.878356,
Lon: -76.990378,
Name: 'K ST + POTOMAC AVE',
Routes: ['V7', 'V7c', 'V7cv1', 'V7v1', 'V7v2', 'V8', 'V9'],
StopID: '1000533',
},
{
Lat: 38.879041,
Lon: -76.988528,
Name: 'POTOMAC AVE + 13TH ST',
Routes: ['V7', 'V7c', 'V7cv1', 'V7v1', 'V7v2', 'V8', 'V9'],
StopID: '1000544',
},
{
Lat: 38.879347,
Lon: -76.991248,
Name: 'I ST + 11TH ST',
Routes: ['V7', 'V7c', 'V7cv1', 'V7cv2', 'V8', 'V9'],
StopID: '1000550',
},
];

t.deepEqual(
createNearbyStopList(stops),
{
1000533: {
synonyms: 'Stop 1000533',
title: 'Stop 1000533: K ST + POTOMAC AVE',
description: 'Routes: V7, V7c, V7cv1, V7v1, V7v2, V8, V9',
image: {
url:
'https://raw.githubusercontent.com/JamesIves/dc-metro-google-assistant-action/master/assets/app_icon.png',
accessibilityText: '1000533',
height: undefined,
width: undefined,
},
},
1000544: {
synonyms: 'Stop 1000544',
title: 'Stop 1000544: POTOMAC AVE + 13TH ST',
description: 'Routes: V7, V7c, V7cv1, V7v1, V7v2, V8, V9',
image: {
url:
'https://raw.githubusercontent.com/JamesIves/dc-metro-google-assistant-action/master/assets/app_icon.png',
accessibilityText: '1000544',
height: undefined,
width: undefined,
},
},
1000550: {
synonyms: 'Stop 1000550',
title: 'Stop 1000550: I ST + 11TH ST',
description: 'Routes: V7, V7c, V7cv1, V7cv2, V8, V9',
image: {
url:
'https://raw.githubusercontent.com/JamesIves/dc-metro-google-assistant-action/master/assets/app_icon.png',
accessibilityText: '1000550',
height: undefined,
width: undefined,
},
},
},
'Should generate a object used for the stop list.'
);

t.end();
});
22 changes: 22 additions & 0 deletions functions/src/util/bus.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Image} from 'actions-on-google';

/**
* Filters bus incident data and returns a set of incidents which are relevant to the bus stop.
* @param {array} routes - An array of routes which arrive at this stop. For example ['ABC', 'EFG']
Expand All @@ -22,3 +24,23 @@ export function getRelevantBusIncidents(
[]
);
}

/**
* Creates an object which actions-on-google can consume to generate a list.
* @param {array} stops - An array of nearby bus stops.
* @returns {object} Returns an object containing the nearby stops.
*/
export function createNearbyStopList(stops: Array<object>): any {
return stops.reduce((obj, item: any) => {
obj[item.StopID] = {};
(obj[item.StopID].synonyms = `Stop ${item.StopID}`),
(obj[item.StopID].title = `Stop ${item.StopID}: ${item.Name}`);
obj[item.StopID].description = `Routes: ${item.Routes.join(', ')}`;
obj[item.StopID].image = new Image({
url:
'https://raw.githubusercontent.com/JamesIves/dc-metro-google-assistant-action/master/assets/app_icon.png',
alt: item.StopID,
});
return obj;
}, {});
}
17 changes: 17 additions & 0 deletions functions/src/wmata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ import {serviceTypeEnum} from './util/constants';
export const rootUrl = 'https://api.wmata.com';
export const wmataApiKey = functions.config().metro.apikey;

export const fetchNearbyStops = async (
lat: string,
lon: string
): Promise<[]> => {
try {
const stopResponse = await fetch(
`${rootUrl}/Bus.svc/json/jStops?Lat=${lat}&Lon=${lon}&Radius=250&api_key=${wmataApiKey}`,
{method: 'GET'}
);
const stopObj = await stopResponse.json();

return stopObj.Stops;
} catch (error) {
return [];
}
};

/**
* Fetches all incidents which are currently affecting the Metro.
* @param {string} transport - The mode of transport, either 'train' or 'bus'.
Expand Down

0 comments on commit 19da814

Please sign in to comment.