Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: WPRDC data fetching function #83

Merged
merged 3 commits into from
Jan 27, 2017
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 154 additions & 106 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@
//Array of markers
var markers = new Array();

//WPRDC data
const WPRDC_BASE_URL = 'https://data.wprdc.org/api/action/datastore_search_sql?sql=';

//Create a new Date object for the current date
var currentDate = new Date();

Expand Down Expand Up @@ -93,115 +90,166 @@
//Listener for sidebar toggle
document.getElementById("sidebarToggle").addEventListener("click", toggleSidebar);

//City of Pittsburgh police data
const CITY_POLICE_API = "1797ead8-8262-41cc-9099-cbc8a161924b";
const CITY_POLICE_SQL = `SELECT * from "${CITY_POLICE_API}" WHERE "INCIDENTNEIGHBORHOOD" LIKE '%Oakland'`;
const CITY_POLICE_ICON = L.divIcon({
className: 'map-pin blue',
html: '<i class="fa fa-balance-scale"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
}); // TODO: make a global dictionary for these?
fetch(`${WPRDC_BASE_URL}${CITY_POLICE_SQL}`)
// TODO: ensure 200 response
.then((response) => response.json())
//TODO: should have some generic error handling for data
.catch((err) => console.log(err))
.then((data) => {
const records = data.result.records;
records.forEach((record, i) => {
//Collect time of incident from the record
record.incidentYear = parseInt(record.INCIDENTTIME.substring(0, 4));
record.incidentMonth = parseInt(record.INCIDENTTIME.substring(5, 8));
record.incidentDay = parseInt(record.INCIDENTTIME.substring(8, 10));

record.pin = L.marker([record.Y, record.X], { icon: CITY_POLICE_ICON });
record.pin.addTo(map)
.bindPopup(`${record.OFFENSES}`);

//Push the marker and date to their respective arrays
markers.push(record);
});
});
//WPRDC data
const WPRDC_BASE_URL = 'https://data.wprdc.org/api/action/datastore_search_sql?sql=';

// Marker Icons
const iconTypes = {
CITY_POLICE: L.divIcon({
className: 'map-pin blue',
html: '<i class="fa fa-balance-scale"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
}),
CITY_311_ICON: L.divIcon({
className: 'map-pin yellow',
html: '<i class="fa fa-commenting"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
}),
LIBRARY_ICON: L.divIcon({
className: 'map-pin black',
html: '<i class="fa fa-book"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
})
};

const WPRDC_DATA_SOURCES = {
"Police": {
id: '1797ead8-8262-41cc-9099-cbc8a161924b',
primaryFiltering: 'WHERE "INCIDENTNEIGHBORHOOD" LIKE \'%Oakland\'',
latLong: ['Y', 'X'],
icon: iconTypes.CITY_POLICE,

popup: (record) => record['OFFENSES'],

processRecord: (record) => {
// Collect time of incident from the record
record.incidentYear = parseInt(record.INCIDENTTIME.substring(0,4));
record.incidentMonth = parseInt(record.INCIDENTTIME.substring(5,8));
record.incidentDay = parseInt(record.INCIDENTTIME.substring(8,10));
}
},

// City of Pittsburgh 311 data
// TODO: would be great to prune 311 data to the last 30 days, like the police data
"311": {
id: '40776043-ad00-40f5-9dc8-1fde865ff571',
primaryFiltering: 'WHERE "NEIGHBORHOOD" LIKE \'%Oakland\' ORDER BY "CREATED_ON" DESC',
latLong: ['Y', 'X'],
icon: iconTypes.CITY_311_ICON,

popup: (record) => record['REQUEST_TYPE'],

processRecord: (record) => {
// Collect time of incident from the record
record.incidentYear = parseInt(record.CREATED_ON.substring(0,4));
record.incidentMonth = parseInt(record.CREATED_ON.substring(5,8));
record.incidentDay = parseInt(record.CREATED_ON.substring(8,10));
}
},

// Calls from the library db
"Library": {
id: "2ba0788a-2f35-43aa-a47c-89c75f55cf9d",
primaryFiltering: 'WHERE "Name" LIKE \'%OAKLAND%\'',
latLong: ['Lat', 'Lon'],
icon: iconTypes.LIBRARY_ICON,

title: (record) => record['Name'],
popup: (record) => `
<strong>${record.Name}</strong>
<br> Address: ${record.Address}
<br> Phone: ${record.Phone}
<br> Monday: ${record.MoOpen.substring(0, 5)} - ${record.MoClose.substring(0, 5)}
<br> Tuesday: ${record.TuOpen.substring(0, 5)} - ${record.TuClose.substring(0, 5)}
<br> Wednesday: ${record.WeOpen.substring(0, 5)} - ${record.WeClose.substring(0, 5)}
<br> Thursday: ${record.ThOpen.substring(0, 5)} - ${record.ThClose.substring(0, 5)}
<br> Friday: ${record.FrOpen.substring(0, 5)} - ${record.FrClose.substring(0, 5)}
<br> Saturday: ${record.SaOpen.substring(0, 5)} - ${record.SaClose.substring(0, 5)}
<br> Sunday: ${record.SuOpen.substring(0, 5)} - ${record.SuClose.substring(0, 5)}
`
}
};

const WPRDC_QUERY_PREFIX = 'SELECT * FROM "';
const WPRDC_QUERY_SUFFIX = '" ';

// Fetch data from West Pennsylvania Regional Data Center using the SQL API
function fetchWPRDCData(dataSourceName, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, we can set default JS params in ES6, like:

function fetchWPRDCData(dataSourceName, options={}) {

So we don't have to have the conditional after. We're being selective in what es6 features we use, so this is fine as is. Just FYI.

if (!options) {
options = {};
}

console.group(`${dataSourceName} API`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prune out all logging


const dataSource = WPRDC_DATA_SOURCES[dataSourceName];
let query = WPRDC_QUERY_PREFIX + dataSource.id + WPRDC_QUERY_SUFFIX + dataSource.primaryFiltering;

if (options.limit) {
console.log(`Limit set to: ${options.limit}`);
query += ' LIMIT ' + options.limit;
}

console.log(`Final query: ${query}`);
console.groupEnd();

return fetch(WPRDC_BASE_URL + query)
// TODO: ensure 200 response
.then((response) => response.json())
// TODO: should have some generic error handling for data
.catch((err) => console.log(err))
.then((data) => {
const records = data.result.records;

records.forEach((record, i) => {
// TODO: Check browser compatability for `instanceof Function`
if (dataSource.processRecord instanceof Function) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do without the instanceof check, and just check for the existence of the processRecord field. I can't think of a scenario when it wouldn't be a function

dataSource.processRecord(record, i);
}

const latLong = dataSource.latLong.map((fieldName) => record[fieldName]);
const latLongNoNulls = latLong.some((field) => !!field);
if (latLongNoNulls) {
let title = null;
if (dataSource.title instanceof Function) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's assume that all datasets have a title and that it is a function.In fact, could you add this for the police and 311? It can be the field that you're using now for the display.

title = dataSource.title(record);
}

record.pin = L.marker(latLong, {
title: title,
icon: dataSource.icon
});

if (dataSource.popup instanceof Function) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, with #79 we can assume that all datasets will have a function to generate the popup that can , so we don't have to have this check, I think.

record.pin.bindPopup(dataSource.popup(record));
} else {
record.pin.bindPopup(record[dataSource.popup])
}

record.pin.addTo(map)
markers.push(record);
} else {
console.log('Found a null location!', record);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll fix this with #61

}
})
});
}

//TODO: would be great to prune 311 data to the last 30 days, like the police data
//City of Pittsburgh 311 data
const CITY_311_API = "40776043-ad00-40f5-9dc8-1fde865ff571";
const CITY_311_SQL = `SELECT * FROM "${CITY_311_API}" WHERE "NEIGHBORHOOD" LIKE '%Oakland' ORDER BY "CREATED_ON" DESC LIMIT 25`;
const CITY_311_ICON = L.divIcon({
className: 'map-pin yellow',
html: '<i class="fa fa-commenting"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
Promise.all([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this looks so clean

fetchWPRDCData('Police', { limit: 250 }),
fetchWPRDCData('311', { limit: 250 }),
fetchWPRDCData('Library')
]).then(() => {
console.log('All data loaded');
}).catch((err) => {
console.log('error fetching data', err);
});
fetch(`${WPRDC_BASE_URL}${CITY_311_SQL}`)
// TODO: ensure 200 response
.then((response) => response.json())
//TODO: should have some generic error handling for data
.catch((err) => console.log(err))
.then((data) => {
const records = data.result.records;
records.forEach((record, i) => {

//Collect time of incident from the record
record.incidentYear = parseInt(record.CREATED_ON.substring(0, 4));
record.incidentMonth = parseInt(record.CREATED_ON.substring(5, 8));
record.incidentDay = parseInt(record.CREATED_ON.substring(8, 10));

record.pin = L.marker([record.Y, record.X], {
icon: CITY_311_ICON,
title: record.REQUEST_TYPE || 'default title',
zIndexOffset: 100
});
record.pin.bindPopup(`<pre>${JSON.stringify(record, null, 2)}</pre>`);
record.pin.addTo(map);

//Push the marker and date to their respective arrays
markers.push(record);
});
});

//Helper function that returns difference between two dates in days
function getDateDifference(dateA, dateB) {
return Math.floor(Math.abs(dateA.getTime() - dateB.getTime()) / 86400000);
}
//Calls from the library db
const LibraryAPI = "2ba0788a-2f35-43aa-a47c-89c75f55cf9d";
const Library_SQL = `SELECT * FROM "${LibraryAPI}" WHERE "Name" LIKE '%OAKLAND%'`;
const Library_ICON = L.divIcon({
className: 'map-pin black',
html: '<i class="fa fa-book"></i>',
iconSize: [32, 32],
iconAnchor: [16, 32]
});
fetch(`${WPRDC_BASE_URL}${Library_SQL}`)
.then((response) => response.json())
.catch((err) => console.log(err))
.then((data) => {
const libRecords = data.result.records;
libRecords.forEach((record, i) => {
//Library Icon from their twitter
record.pin = L.marker([record.Lat, record.Lon], {
icon: Library_ICON,
title: record.Name,
zIndexOffset: 100
});
// Probably a better way to format the library popup but its cleaner for now
record.pin.bindPopup(`
<strong>${record.Name}</strong>
<br> Address: ${record.Address}
<br> Phone: ${record.Phone}
<br> Monday: ${record.MoOpen.substring(0, 5)} - ${record.MoClose.substring(0, 5)}
<br> Tuesday: ${record.TuOpen.substring(0, 5)} - ${record.TuClose.substring(0, 5)}
<br> Wednesday: ${record.WeOpen.substring(0, 5)} - ${record.WeClose.substring(0, 5)}
<br> Thursday: ${record.ThOpen.substring(0, 5)} - ${record.ThClose.substring(0, 5)}
<br> Friday: ${record.FrOpen.substring(0, 5)} - ${record.FrClose.substring(0, 5)}
<br> Saturday: ${record.SaOpen.substring(0, 5)} - ${record.SaClose.substring(0, 5)}
<br> Sunday: ${record.SuOpen.substring(0, 5)} - ${record.SuClose.substring(0, 5)}
`);
record.pin.addTo(map);
markers.push(record);
});
});

})(typeof window !== "undefined" ? window : {});