From b17ceae886249764c3d4f94224de186037989f73 Mon Sep 17 00:00:00 2001 From: "Making GitHub Delicious." Date: Tue, 28 Jul 2015 10:54:32 -0600 Subject: [PATCH 01/44] add waffle.io badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c640e9d..60474bd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Stories in Ready](https://badge.waffle.io/CunningCoders/Relocalc.png?label=ready&title=Ready)](https://waffle.io/CunningCoders/Relocalc) # Relocalc A resource for residents or businesses looking to move to Austin, TX. From 95a38eb6c802b119b0f35bfa3c21f32d7be1e55f Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Tue, 28 Jul 2015 14:16:30 -0500 Subject: [PATCH 02/44] Added Work Address search bar to view/model --- client/app/components/searchBox.js | 5 +++++ client/app/models/Location.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index f5ba9cc..99e14d9 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -34,6 +34,11 @@ exports.view = function (ctrl, options) { {value: options.location.address(), //this could be refactored to to use m.withAttr to set the values... onchange: function(e){ options.location.address(e.currentTarget.value); }} + )], + [m('input.addressInput.workAddress[type="text"][placeholder="Enter your work address (1100 Congress Avenue, Austin, TX 78701)"]', + {value: options.location.workAddress(), + //this could be refactored to to use m.withAttr to set the values... + onchange: function(e){ options.location.workAddress(e.currentTarget.value); }} )], [m('h3', "On a scale of 0-100, how important are these criteria in your search?")], [m('.col-sm-6', diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 40c62e4..b301115 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -95,7 +95,8 @@ var Locations = module.exports = { return { address: m.prop(''), lat: m.prop(''), - lng: m.prop('') + lng: m.prop(''), + workAddress: m.prop('') } } From 9f5e6fcf13f67341dafa56469b480895e5cf22e6 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 10:23:29 -0500 Subject: [PATCH 03/44] Query Zillow API and send data to client to be used --- .gitignore | 5 ++- package.json | 4 ++- server/index.js | 82 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 1aca443..d9ebb83 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ build.properties junit*.properties ### database info -server/lib/restaurants.js \ No newline at end of file +server/lib/restaurants.js + +### Zillow API key +server/lib/credentials.js \ No newline at end of file diff --git a/package.json b/package.json index e09ca45..317d829 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "knex": "^0.8.6", "mithril": "^0.2.0", "pg": "^4.4.0", - "underscore": "^1.8.3" + "request-promise": "^0.4.3", + "underscore": "^1.8.3", + "xml2js": "^0.4.9" }, "devDependencies": { "gulp": "^3.9.0", diff --git a/server/index.js b/server/index.js index 34b27b3..cacbf06 100644 --- a/server/index.js +++ b/server/index.js @@ -2,13 +2,16 @@ var browserify = require('browserify-middleware'); var bodyParser = require('body-parser'); var search = require('./lib/search.js'); var express = require('express'); +var requestPromise = require('request-promise'); // Use for promisified http requests to Zillow +var parser = require('xml2js'); // Parse XML response from Zillow API +var credentials = require('./lib/credentials.js') // git-ignored Zillow API key. var app = express(); //var db = require('./lib/db.js'); var Restaurant = require('./models/restaurant.js'); var httpResponseBody = require('./lib/httpResponseBody.js'); var calculateLivability = require('./lib/calculateLivability.js'); -//provide a browserified f;ile at a path +//provide a browserified file at a path var shared = ['mithril']; app.get('/js/vendor-bundle.js', browserify(shared)); app.get('/js/app-bundle.js', browserify('./client/app/index.js', { external: shared })); @@ -20,7 +23,6 @@ app.use(bodyParser.json()); app.use(express.static('client/public')); app.get('/', function (req, res){ - console.log('looking for restaurant!!'); return Restaurant.getInspections('2801033') .then( function (restaurantList){ res.json(restaurantList); @@ -29,6 +31,18 @@ app.get('/', function (req, res){ //app.get('/crimes', function (req, res){}); +//Init objects for use in Zillow API calls. +var houseData = {}; +var neighborhoodURL = ''; +var addressOptions = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=1803+E+18th+St&citystatezip=Austin+TX', + method: 'GET' +}; +var neighborhoodOptions = { + uri: '', + method: 'GET' +}; + app.post('/', function (req, res){ function milestoMeters(miles){ @@ -39,7 +53,6 @@ app.post('/', function (req, res){ longitude: req.body.lng, meters: milestoMeters(req.body.radius || 1) , }; - console.log('meters: ' + circle.meters); search('Crime', circle) .then(function attachCrimestoHttpResponse(crimes){ httpResponseBody.crimes = crimes; @@ -48,7 +61,6 @@ app.post('/', function (req, res){ .then(function attachRestaurantsToHttpResponse(restaurants){ httpResponseBody.restaurants = restaurants; return httpResponseBody; - //return res.json(httpResponseBody); }) .then(function attachSearchInspectionAvgToHttpResponse(httpResponseBody){ var count = 0; @@ -59,13 +71,61 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - .then(function (httpResponseBody){ - var weights = req.body.weights || {restaurants: 50, crimes: 50}; - calculateLivability(weights, httpResponseBody, req.body.radius); - console.log(Object.keys(httpResponseBody)); - //console.log(weights) - res.json(httpResponseBody); - }); + //Make Zillow API call to get address value and neighborhood demographic info + .then(function (httpResponseBody) { + console.log('Starting Zillow calls'); + requestPromise(addressOptions) + .then(function(res){ + console.log('First Zillow call results:', res) + //Parse the XML response from Zillow into a JS object + parser.parseString(res, function(err, result) { + //Unwrap the response the extract the desired data + houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] + }) + houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood = {} + houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] + houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] + neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + return neighborhoodOptions + }) + .then(function(result){ + //Get the neighborhood information for the requested address + return requestPromise(result) + }) + .then(function(res){ + parser.parseString(res, function(err, result){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 + + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + // Attach Zillow data to response + httpResponseBody.zillowData = houseData + }) + return httpResponseBody + }) + // Send response back to client + .then(function (httpResponseBody){ + var weights = req.body.weights || {restaurants: 50, crimes: 50}; + calculateLivability(weights, httpResponseBody, req.body.radius); + res.json(httpResponseBody); + }); + }) }); var port = process.env.PORT || 4000; From 637feecf3eab655fffbb52beac190fe2350d681f Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 11:39:22 -0500 Subject: [PATCH 04/44] Pull user-entered address for Zillow API call. Server response fails if Zillow has no exact match --- server/index.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/server/index.js b/server/index.js index cacbf06..0794023 100644 --- a/server/index.js +++ b/server/index.js @@ -34,8 +34,11 @@ app.get('/', function (req, res){ //Init objects for use in Zillow API calls. var houseData = {}; var neighborhoodURL = ''; -var addressOptions = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=1803+E+18th+St&citystatezip=Austin+TX', +var addressOptionsTemplate = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + method: 'GET' +};var addressOptions = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', method: 'GET' }; var neighborhoodOptions = { @@ -45,6 +48,10 @@ var neighborhoodOptions = { app.post('/', function (req, res){ + // Get the street address, remove commas, and replace whitespace with '+' for use in Zillow API + var zillowAddress = req.body.address.split(' Austin')[0].replace(/\s/g, '+').replace(/\,/,''); + addressOptions.uri = addressOptionsTemplate.uri.replace(/\*/,zillowAddress); + function milestoMeters(miles){ return miles / 0.00062137; } @@ -73,13 +80,19 @@ app.post('/', function (req, res){ }) //Make Zillow API call to get address value and neighborhood demographic info .then(function (httpResponseBody) { - console.log('Starting Zillow calls'); requestPromise(addressOptions) .then(function(res){ - console.log('First Zillow call results:', res) + if(res.search(/Error/g)>0) { + console.log('No exact match found by Zillow:',res.search(/Error/g)) + } //Parse the XML response from Zillow into a JS object + // NOTE: If the address does not have an exact match in Zillow, everything will break because parseString breaks. parser.parseString(res, function(err, result) { //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + return neighborhoodOptions + } houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] }) houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number @@ -124,6 +137,7 @@ app.post('/', function (req, res){ var weights = req.body.weights || {restaurants: 50, crimes: 50}; calculateLivability(weights, httpResponseBody, req.body.radius); res.json(httpResponseBody); + console.log(httpResponseBody.zillowData) }); }) }); From 49317e1e9ce38b4320fcb7fc591042b07394c377 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 12:03:56 -0500 Subject: [PATCH 05/44] Zillow errors do not prevent response from being sent back to client --- server/index.js | 92 ++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/server/index.js b/server/index.js index 0794023..3f7453c 100644 --- a/server/index.js +++ b/server/index.js @@ -78,58 +78,70 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - //Make Zillow API call to get address value and neighborhood demographic info + // Make Zillow API call to get address value and neighborhood demographic info + // Then send httpResponseBody back to client .then(function (httpResponseBody) { requestPromise(addressOptions) + // Get the basic info for the requested address .then(function(res){ if(res.search(/Error/g)>0) { - console.log('No exact match found by Zillow:',res.search(/Error/g)) - } + console.log('No exact match found by Zillow') + } else { //Parse the XML response from Zillow into a JS object - // NOTE: If the address does not have an exact match in Zillow, everything will break because parseString breaks. - parser.parseString(res, function(err, result) { - //Unwrap the response the extract the desired data - if (err) { - console.log('Parse error') - return neighborhoodOptions - } - houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] - }) - houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood = {} - houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] - houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] - neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. + parser.parseString(res, function(err, result) { + //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + } else { + houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] + houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood = {} + houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] + houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] + neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + } + }) + } return neighborhoodOptions }) + // Get the neighborhood information for the requested address .then(function(result){ - //Get the neighborhood information for the requested address - return requestPromise(result) + if (result.uri===''){ + return 'Zillow Error' + } else { + return requestPromise(result) + } }) + // Extract the neighborhood data and insert into httpResponseBody .then(function(res){ - parser.parseString(res, function(err, result){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + if (res !== 'Zillow Error'){ + parser.parseString(res, function(err, result){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 - // Attach Zillow data to response - httpResponseBody.zillowData = houseData - }) + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + // Attach Zillow data to response + httpResponseBody.zillowData = houseData + }) + } else { + httpResponseBody.zillowData = {} + } return httpResponseBody }) // Send response back to client From 11491bda3f75d376b0eaaea358a75b167b92dc1d Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 12:21:29 -0500 Subject: [PATCH 06/44] Zillow results that do not have neighborhood data do not prevent server from sending response to the client --- server/index.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/server/index.js b/server/index.js index 3f7453c..81641a8 100644 --- a/server/index.js +++ b/server/index.js @@ -117,25 +117,27 @@ app.post('/', function (req, res){ .then(function(res){ if (res !== 'Zillow Error'){ parser.parseString(res, function(err, result){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + } // Attach Zillow data to response httpResponseBody.zillowData = houseData }) From 8de96f6d6b574e4292e234c44d38a8e66bb46d71 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 15:36:30 -0500 Subject: [PATCH 07/44] Store zillowData in Mithril m.prop in Location.js --- client/app/models/Location.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/app/models/Location.js b/client/app/models/Location.js index b301115..7300353 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -38,6 +38,7 @@ var Locations = module.exports = { lng: m.prop(''), crimeWeight: m.prop(''), restWeight: m.prop(''), + costWeight: m.prop(''), address: m.prop(''), postToFetchRestaurantData: function(address, cb) { @@ -65,6 +66,7 @@ var Locations = module.exports = { var data = modelData(res); if (data !== null) { Locations.search(data); + console.log('Locations.search()', Locations.search()) } Locations.saveSearch(Locations.address(), res.livibility); return cb(data); @@ -131,7 +133,8 @@ var modelData = function(data) { crimeAvg: data.searchCrimesPerSqMi, livability: data.livibility, cityRestAvg: data.meanRestInspecAvg, - cityCrimeAvg: data.meanCrimesPerSqMi + cityCrimeAvg: data.meanCrimesPerSqMi, + zillow: data.zillowData }; if(isNaN(response.restAvg)) { From a0ad074f0d65d3f5c68444c2b2c040aba58009f3 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 16:33:43 -0500 Subject: [PATCH 08/44] Zillow Income data for neighborhood/city displayed on graph --- client/app/components/graphContainer.js | 49 +++++++++++++++++++++++-- client/app/models/Location.js | 12 +++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index 326a156..b058cc0 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -49,7 +49,6 @@ exports.controller = function(options) { } }); }); - }; ctrl.initRestSafety = function (element, isInit, context) { @@ -92,7 +91,6 @@ exports.controller = function(options) { } }); }); - }; ctrl.initRestNumber = function (element, isInit, context) { @@ -135,7 +133,49 @@ exports.controller = function(options) { } }); }); - + }; + + ctrl.initCostCompare = function (element, isInit, context) { + + //Initialize number of restaurants chart + $(function () { + $('.costCompare').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Median Neighborhood Income' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Yearly Income (average)' + } + }, + series: [{ + name: 'Your Search', + data: [Location.zillowIncomeNeighborhood()] + }, { + name: 'City of Austin (average)', + // data: [Location.search().restaurants] + data: [Location.zillowIncomeCity()] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; }; @@ -144,7 +184,8 @@ exports.view = function(ctrl, options) { return m('div', [m('.col-sm-4 .crimeGraph', {config: ctrl.initCrime}), m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), - m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}) + m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}), + m('.col-sm-4 .costCompare', {config: ctrl.initCostCompare}) ]); }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 7300353..6ce16c0 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -40,12 +40,13 @@ var Locations = module.exports = { restWeight: m.prop(''), costWeight: m.prop(''), address: m.prop(''), + zillowIncomeNeighborhood: m.prop(0), + zillowIncomeCity: m.prop(0), postToFetchRestaurantData: function(address, cb) { Locations.address(address); var cb = cb; this.postToFetchGeoCode(address, function (res) { - console.log("google", res); Locations.lat(res.results[0].geometry.location.lat); Locations.lng(res.results[0].geometry.location.lng); var locationData = { @@ -58,15 +59,15 @@ var Locations = module.exports = { "restaurants": Locations.restWeight() || 50 } }; - console.log(locationData); return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) .then(function(res) { - console.log(res); - console.log(Object.keys(res)); var data = modelData(res); if (data !== null) { Locations.search(data); - console.log('Locations.search()', Locations.search()) + Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); + Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); + console.log('neighborhood', Locations.zillowIncomeNeighborhood()) + console.log('city', Locations.zillowIncomeCity()) } Locations.saveSearch(Locations.address(), res.livibility); return cb(data); @@ -136,6 +137,7 @@ var modelData = function(data) { cityCrimeAvg: data.meanCrimesPerSqMi, zillow: data.zillowData }; + console.log('response:', response) if(isNaN(response.restAvg)) { toastr["error"]("No available data. Please check that the address"); From a5ce9538a8585c064ce264d6930327ffa644201e Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 16:44:19 -0500 Subject: [PATCH 09/44] Update Income Comparison chart styling --- client/public/sass/graphContainer.scss | 6 +++++- client/public/style.css | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/public/sass/graphContainer.scss b/client/public/sass/graphContainer.scss index 95e8915..527e799 100644 --- a/client/public/sass/graphContainer.scss +++ b/client/public/sass/graphContainer.scss @@ -1,10 +1,14 @@ -.crimeGraph .restaurantSafety .restaurantNumber { +.crimeGraph .restaurantSafety .restaurantNumber .costCompare { height: 100%; width: 100%; margin: 0 auto; padding: 100px; } +.costCompare { + margin-top: 30px; +} + .gaugeContainer { width: 50%; height: 100%; diff --git a/client/public/style.css b/client/public/style.css index 7f92b97..0cce900 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -68,12 +68,15 @@ h3, h4 { /* END general styling */ -.crimeGraph .restaurantSafety .restaurantNumber { +.crimeGraph .restaurantSafety .restaurantNumber .costCompare { height: 100%; width: 100%; margin: 0 auto; padding: 100px; } +.costCompare { + margin-top: 30px; } + .gaugeContainer { width: 50%; height: 100%; } From 5481b3c4d735de8a186aee534fd592172d002744 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 11:39:22 -0500 Subject: [PATCH 10/44] Pull user-entered address for Zillow API call. Server response fails if Zillow has no exact match --- server/index.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/server/index.js b/server/index.js index cacbf06..0794023 100644 --- a/server/index.js +++ b/server/index.js @@ -34,8 +34,11 @@ app.get('/', function (req, res){ //Init objects for use in Zillow API calls. var houseData = {}; var neighborhoodURL = ''; -var addressOptions = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=1803+E+18th+St&citystatezip=Austin+TX', +var addressOptionsTemplate = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + method: 'GET' +};var addressOptions = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', method: 'GET' }; var neighborhoodOptions = { @@ -45,6 +48,10 @@ var neighborhoodOptions = { app.post('/', function (req, res){ + // Get the street address, remove commas, and replace whitespace with '+' for use in Zillow API + var zillowAddress = req.body.address.split(' Austin')[0].replace(/\s/g, '+').replace(/\,/,''); + addressOptions.uri = addressOptionsTemplate.uri.replace(/\*/,zillowAddress); + function milestoMeters(miles){ return miles / 0.00062137; } @@ -73,13 +80,19 @@ app.post('/', function (req, res){ }) //Make Zillow API call to get address value and neighborhood demographic info .then(function (httpResponseBody) { - console.log('Starting Zillow calls'); requestPromise(addressOptions) .then(function(res){ - console.log('First Zillow call results:', res) + if(res.search(/Error/g)>0) { + console.log('No exact match found by Zillow:',res.search(/Error/g)) + } //Parse the XML response from Zillow into a JS object + // NOTE: If the address does not have an exact match in Zillow, everything will break because parseString breaks. parser.parseString(res, function(err, result) { //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + return neighborhoodOptions + } houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] }) houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number @@ -124,6 +137,7 @@ app.post('/', function (req, res){ var weights = req.body.weights || {restaurants: 50, crimes: 50}; calculateLivability(weights, httpResponseBody, req.body.radius); res.json(httpResponseBody); + console.log(httpResponseBody.zillowData) }); }) }); From 1b1dfd075fc8844fc0305cba4efd5596c77562fb Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 12:03:56 -0500 Subject: [PATCH 11/44] Zillow errors do not prevent response from being sent back to client --- server/index.js | 92 ++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/server/index.js b/server/index.js index 0794023..3f7453c 100644 --- a/server/index.js +++ b/server/index.js @@ -78,58 +78,70 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - //Make Zillow API call to get address value and neighborhood demographic info + // Make Zillow API call to get address value and neighborhood demographic info + // Then send httpResponseBody back to client .then(function (httpResponseBody) { requestPromise(addressOptions) + // Get the basic info for the requested address .then(function(res){ if(res.search(/Error/g)>0) { - console.log('No exact match found by Zillow:',res.search(/Error/g)) - } + console.log('No exact match found by Zillow') + } else { //Parse the XML response from Zillow into a JS object - // NOTE: If the address does not have an exact match in Zillow, everything will break because parseString breaks. - parser.parseString(res, function(err, result) { - //Unwrap the response the extract the desired data - if (err) { - console.log('Parse error') - return neighborhoodOptions - } - houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] - }) - houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood = {} - houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] - houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] - neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. + parser.parseString(res, function(err, result) { + //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + } else { + houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] + houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood = {} + houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] + houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] + neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + } + }) + } return neighborhoodOptions }) + // Get the neighborhood information for the requested address .then(function(result){ - //Get the neighborhood information for the requested address - return requestPromise(result) + if (result.uri===''){ + return 'Zillow Error' + } else { + return requestPromise(result) + } }) + // Extract the neighborhood data and insert into httpResponseBody .then(function(res){ - parser.parseString(res, function(err, result){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + if (res !== 'Zillow Error'){ + parser.parseString(res, function(err, result){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 - // Attach Zillow data to response - httpResponseBody.zillowData = houseData - }) + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + // Attach Zillow data to response + httpResponseBody.zillowData = houseData + }) + } else { + httpResponseBody.zillowData = {} + } return httpResponseBody }) // Send response back to client From d0d91ea839481e41a6f81ec8b48bfbf292e0aeea Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 12:21:29 -0500 Subject: [PATCH 12/44] Zillow results that do not have neighborhood data do not prevent server from sending response to the client --- server/index.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/server/index.js b/server/index.js index 3f7453c..81641a8 100644 --- a/server/index.js +++ b/server/index.js @@ -117,25 +117,27 @@ app.post('/', function (req, res){ .then(function(res){ if (res !== 'Zillow Error'){ parser.parseString(res, function(err, result){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + } // Attach Zillow data to response httpResponseBody.zillowData = houseData }) From 9838bdaee27334f718f3cd91daca606ae169fb93 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 15:36:30 -0500 Subject: [PATCH 13/44] Store zillowData in Mithril m.prop in Location.js --- client/app/models/Location.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/app/models/Location.js b/client/app/models/Location.js index b301115..7300353 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -38,6 +38,7 @@ var Locations = module.exports = { lng: m.prop(''), crimeWeight: m.prop(''), restWeight: m.prop(''), + costWeight: m.prop(''), address: m.prop(''), postToFetchRestaurantData: function(address, cb) { @@ -65,6 +66,7 @@ var Locations = module.exports = { var data = modelData(res); if (data !== null) { Locations.search(data); + console.log('Locations.search()', Locations.search()) } Locations.saveSearch(Locations.address(), res.livibility); return cb(data); @@ -131,7 +133,8 @@ var modelData = function(data) { crimeAvg: data.searchCrimesPerSqMi, livability: data.livibility, cityRestAvg: data.meanRestInspecAvg, - cityCrimeAvg: data.meanCrimesPerSqMi + cityCrimeAvg: data.meanCrimesPerSqMi, + zillow: data.zillowData }; if(isNaN(response.restAvg)) { From 8cd34f5fd4f75049d9e2df13f5ce23b00ef14459 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 16:33:43 -0500 Subject: [PATCH 14/44] Zillow Income data for neighborhood/city displayed on graph --- client/app/components/graphContainer.js | 49 +++++++++++++++++++++++-- client/app/models/Location.js | 12 +++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index 326a156..b058cc0 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -49,7 +49,6 @@ exports.controller = function(options) { } }); }); - }; ctrl.initRestSafety = function (element, isInit, context) { @@ -92,7 +91,6 @@ exports.controller = function(options) { } }); }); - }; ctrl.initRestNumber = function (element, isInit, context) { @@ -135,7 +133,49 @@ exports.controller = function(options) { } }); }); - + }; + + ctrl.initCostCompare = function (element, isInit, context) { + + //Initialize number of restaurants chart + $(function () { + $('.costCompare').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Median Neighborhood Income' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Yearly Income (average)' + } + }, + series: [{ + name: 'Your Search', + data: [Location.zillowIncomeNeighborhood()] + }, { + name: 'City of Austin (average)', + // data: [Location.search().restaurants] + data: [Location.zillowIncomeCity()] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; }; @@ -144,7 +184,8 @@ exports.view = function(ctrl, options) { return m('div', [m('.col-sm-4 .crimeGraph', {config: ctrl.initCrime}), m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), - m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}) + m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}), + m('.col-sm-4 .costCompare', {config: ctrl.initCostCompare}) ]); }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 7300353..6ce16c0 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -40,12 +40,13 @@ var Locations = module.exports = { restWeight: m.prop(''), costWeight: m.prop(''), address: m.prop(''), + zillowIncomeNeighborhood: m.prop(0), + zillowIncomeCity: m.prop(0), postToFetchRestaurantData: function(address, cb) { Locations.address(address); var cb = cb; this.postToFetchGeoCode(address, function (res) { - console.log("google", res); Locations.lat(res.results[0].geometry.location.lat); Locations.lng(res.results[0].geometry.location.lng); var locationData = { @@ -58,15 +59,15 @@ var Locations = module.exports = { "restaurants": Locations.restWeight() || 50 } }; - console.log(locationData); return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) .then(function(res) { - console.log(res); - console.log(Object.keys(res)); var data = modelData(res); if (data !== null) { Locations.search(data); - console.log('Locations.search()', Locations.search()) + Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); + Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); + console.log('neighborhood', Locations.zillowIncomeNeighborhood()) + console.log('city', Locations.zillowIncomeCity()) } Locations.saveSearch(Locations.address(), res.livibility); return cb(data); @@ -136,6 +137,7 @@ var modelData = function(data) { cityCrimeAvg: data.meanCrimesPerSqMi, zillow: data.zillowData }; + console.log('response:', response) if(isNaN(response.restAvg)) { toastr["error"]("No available data. Please check that the address"); From 30b4e02cea6849d774680854c4cffd242ed36d0b Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 16:44:19 -0500 Subject: [PATCH 15/44] Update Income Comparison chart styling --- client/public/sass/graphContainer.scss | 6 +++++- client/public/style.css | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/public/sass/graphContainer.scss b/client/public/sass/graphContainer.scss index 95e8915..527e799 100644 --- a/client/public/sass/graphContainer.scss +++ b/client/public/sass/graphContainer.scss @@ -1,10 +1,14 @@ -.crimeGraph .restaurantSafety .restaurantNumber { +.crimeGraph .restaurantSafety .restaurantNumber .costCompare { height: 100%; width: 100%; margin: 0 auto; padding: 100px; } +.costCompare { + margin-top: 30px; +} + .gaugeContainer { width: 50%; height: 100%; diff --git a/client/public/style.css b/client/public/style.css index 7f92b97..0cce900 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -68,12 +68,15 @@ h3, h4 { /* END general styling */ -.crimeGraph .restaurantSafety .restaurantNumber { +.crimeGraph .restaurantSafety .restaurantNumber .costCompare { height: 100%; width: 100%; margin: 0 auto; padding: 100px; } +.costCompare { + margin-top: 30px; } + .gaugeContainer { width: 50%; height: 100%; } From 53e018953b81ac93002995f70a55676caace427f Mon Sep 17 00:00:00 2001 From: jmcgui05 Date: Wed, 29 Jul 2015 19:44:28 -0500 Subject: [PATCH 16/44] map configured to match color scheme and custom marker --- client/app/components/mapContainer.js | 26 ++++++++++++++++++++++---- client/public/img/house2.png | Bin 0 -> 839 bytes 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 client/public/img/house2.png diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 458ba34..977902c 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -52,17 +52,35 @@ function mapSetup(options, element, isInitialized) { var mapCenter = new google.maps.LatLng(lat, lng); var mapOptions = { - center: mapCenter, - zoom: adjustZoom() + center: new google.maps.LatLng(30.2500, -97.7500), + zoom: adjustZoom(), + mapTypeId: google.maps.MapTypeId.ROADMAP }; var map = new google.maps.Map(document.querySelector('.mapContainer'), mapOptions); - var iconImg = '../img/icon.png'; + map.set('styles', [ + {"featureType": "all", + "elementType": "all", + "stylers": [ + { + "saturation": -100 + }, + { + "gamma": 0.5 + }] + } + ]); + + //var iconImg = '../img/icon.png'; + + var myLatLng = new google.maps.LatLng(30.2500, -97.7500); var marker = new google.maps.Marker({ - position: mapCenter, + //position: mapCenter, + position: myLatLng, map: map, + icon: '/public/img//house2.png', // icon: iconImg, title: options.location.address() || '' }); diff --git a/client/public/img/house2.png b/client/public/img/house2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4e1e6459ab13cc16c5675cac049efb582a8bbf GIT binary patch literal 839 zcmV-N1GxN&P);M`B_5AU@c_^-MjzeD zKp8j${)n+6SLqYt5syE--C^Mqc6?O4A*MP|wWdDc@rSo(Qd3;MbdkZqfsP6n3J2`& z?UTvowvUgBR}kA^Ej%C46>-2GQ`VU(t6h3m#OpwqnzG6}Rqbv}Jul(Y6z$sb%a!v3 zkm6^BXbZqDdKL)R0Iyfx)cw6(2*&`)=~+H*ex|p#7fsVp6cx3n$8%El9S^n-gg4g62Km0V{^-#Ux-E-8BGAt*B1of zaq3Ca?_B}dKUNhLkJrO|dJ%w}NcqTUf@mbde0mX&*Mq94P2)HMXe1sEg^0xl%#}-9 z%Do~M8z3ACxq@i;pTE4cKyrGP<)sAxQZr9^o}L3ZB`)plyZ3B-ST)~&kx854GLGFG zp8`d!sFAod`CL()g?4=FF$|q&b7>NH@36YIZmx7>G*NeM7`mfV&=kO$wrpnJoUg8} zw+w83zij}g)@oZi4j|KPpSNTG&3!;W2W?#fR4q=SaL_eHOKek94%%rBceQU^(*w=60$z*eNpNYogH&6sh8nq0RskKXproq~p zsQ{U5?g!8kLW&o~4WPRCNvofGNX->^MyvHBjX_g6#_;pU!R^#ThMSCtsYy0-I z9)v~wnpmaWXDdMc_Pm)1Fdwx#2?~f`RtSsuRUuvyUY0_QtR+ALh@i{_U|o3$7vhcq z>Ok}hh#f%gd5KzaITm*XV1oz<_lidqYN Date: Wed, 29 Jul 2015 20:08:12 -0500 Subject: [PATCH 17/44] Factor affordability into livability score. Higher-than-average income yields lower livability. --- client/app/components/searchBox.js | 8 ++++++++ client/app/models/Location.js | 5 ++--- server/index.js | 4 ++-- server/lib/calculateLivability.js | 15 ++++++++++++--- server/lib/search.js | 4 ++-- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 99e14d9..fa4a4ca 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -57,6 +57,14 @@ exports.view = function (ctrl, options) { )] )] )], + [m('.col-sm-6', + [m('h4', 'Affordability: ' + Location.costWeight())], + [m('.slider', + [m('input[type="range"]' + ,{min: 0, max: 100, step: 1, value: Location.costWeight(), onchange: m.withAttr('value', Location.costWeight)} + )] + )] + )], [m('input.addressInput.addressInput-submit[type="submit"][value="Try your luck"]')] //input form ), //form-group ] //form diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 6ce16c0..cb98709 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -56,7 +56,8 @@ var Locations = module.exports = { "radius": 1, "weights": { "crimes": Locations.crimeWeight() || 50, - "restaurants": Locations.restWeight() || 50 + "restaurants": Locations.restWeight() || 50, + "affordability": Locations.costWeight() || 50, } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) @@ -66,8 +67,6 @@ var Locations = module.exports = { Locations.search(data); Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); - console.log('neighborhood', Locations.zillowIncomeNeighborhood()) - console.log('city', Locations.zillowIncomeCity()) } Locations.saveSearch(Locations.address(), res.livibility); return cb(data); diff --git a/server/index.js b/server/index.js index 81641a8..3809454 100644 --- a/server/index.js +++ b/server/index.js @@ -148,10 +148,10 @@ app.post('/', function (req, res){ }) // Send response back to client .then(function (httpResponseBody){ - var weights = req.body.weights || {restaurants: 50, crimes: 50}; + var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; + console.log(httpResponseBody) calculateLivability(weights, httpResponseBody, req.body.radius); res.json(httpResponseBody); - console.log(httpResponseBody.zillowData) }); }) }); diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index e62299c..0bca55f 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -21,15 +21,22 @@ var linEqHandler = function(data, tag){ case 'restaurants': return { pnt1: {x: data.meanRestInspecAvg, y: 80}, - pnt2: {x: data.meanRestInspecAvg + 5, y: 100}, + pnt2: {x: data.meanRestInspecAvg + 5, y: 100}, // For every change of 5 in a health inspection score, the restaurant livability portion changes by 20 input: data.searchInspecAvg }; case 'crimes': return { pnt1: {x: data.meanCrimesPerSqMi, y: data.meanCrimesPerSqMi}, - pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, + pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, // For every change of 30 in crimes/sq mi, the crimes livability portion changes by 12 input: data.searchCrimesPerSqMi - };; + }; + case 'affordability': + return { + pnt1: {x: data.zillowData.neighborhood.medianIncomeCity/1000, y: 75}, + pnt2: {x: (data.zillowData.neighborhood.medianIncomeCity*0.5)/1000, y: 100}, // For every change of 10% in income vs city average, the affordability livability portion changes by 2.5 + input: data.zillowData.neighborhood.medianIncomeNeighborhood/1000 + }; + ; } } @@ -50,6 +57,8 @@ var linEq = function(handler){ var m = slope(handler.pnt1, handler.pnt2) var x = handler.input var b = yIntercept(handler.pnt1, handler.pnt2) + console.log('m:', m,'x:',x,'b:',b) + console.log('y:',m*x+b) return m * x + b; } diff --git a/server/lib/search.js b/server/lib/search.js index ff7311f..05ebf1d 100644 --- a/server/lib/search.js +++ b/server/lib/search.js @@ -7,7 +7,7 @@ var Restaurant = require('../models/restaurant.js'); var Promise = require('bluebird'); var geolib = require('geolib') -//ffunction allows search to be called on multiple tables +//function allows search to be called on multiple tables //returns an object containing appropratie functions for given model name var handler = function (modelName){ switch(modelName){ @@ -55,7 +55,7 @@ var reduceRestaurants = function (restaurants, circle){ return geolib.isPointInCircle(restCoords, circle, circle.meters); }) - //take that list from promsie, then map that list to a new list (change is described in nex comment) + //take that list from promise, then map that list to a new list (change is described in nex comment) .then(function (stored){ return Promise.map(stored, function findRestaurantInspections(rest){ From 91f8bf249aee6d557f4363485fc2dfb699fa3079 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Tue, 28 Jul 2015 20:16:02 -0500 Subject: [PATCH 18/44] Google Direction API Hooked Up --- .gitignore | 1 + client/app/components/searchBox.js | 7 +++ client/app/models/Location.js | 11 ++++ package.json | 1 + server/index.js | 93 +++--------------------------- 5 files changed, 29 insertions(+), 84 deletions(-) diff --git a/.gitignore b/.gitignore index d9ebb83..eb38160 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ junit*.properties ### database info server/lib/restaurants.js +APIKeys.js ### Zillow API key server/lib/credentials.js \ No newline at end of file diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 99e14d9..11a3110 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -8,9 +8,13 @@ exports.controller = function (options) { * this function in Location.js model; makes a post request to GoogleAPI for coordinates for the address, which are * then used for a post request to our database for the data for radius around location */ + ctrl.fetchData = function() { + ctrl.fetchGeoCode() + } ctrl.fetchGeoCode = function() { var address = options.location.address(); + var workAddress = options.location.workAddress(); return Location.postToFetchRestaurantData(address, function cb(res) { //set values on vm options.location.lng(res.lng); @@ -19,6 +23,9 @@ exports.controller = function (options) { if (res !== null) { m.redraw(); toastr["success"]("Data successfully loaded for " + address); + Location.postToGoogleDistanceAPI(address, workAddress, function(res){ + console.log(res) + }) } }); }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 6ce16c0..2e19c0d 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -75,6 +75,17 @@ var Locations = module.exports = { }); }, + postToGoogleDistanceAPI: function(address, workAddress, callback) { + return m.request({method: "POST", url: '/distance', data: { + address:addressFormatter(address), + workAddress:addressFormatter(workAddress) + }}) + .then(function(res){ + console.log(res); + callback(res); + }); + }, + saveSearch: function(address, livabilityScore){ var user = Auth.isAuthenticated(); if(user) { diff --git a/package.json b/package.json index 317d829..ae11cba 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "knex": "^0.8.6", "mithril": "^0.2.0", "pg": "^4.4.0", + "request": "^2.60.0", "request-promise": "^0.4.3", "underscore": "^1.8.3", "xml2js": "^0.4.9" diff --git a/server/index.js b/server/index.js index 81641a8..7cb4734 100644 --- a/server/index.js +++ b/server/index.js @@ -2,16 +2,13 @@ var browserify = require('browserify-middleware'); var bodyParser = require('body-parser'); var search = require('./lib/search.js'); var express = require('express'); -var requestPromise = require('request-promise'); // Use for promisified http requests to Zillow -var parser = require('xml2js'); // Parse XML response from Zillow API -var credentials = require('./lib/credentials.js') // git-ignored Zillow API key. var app = express(); //var db = require('./lib/db.js'); var Restaurant = require('./models/restaurant.js'); var httpResponseBody = require('./lib/httpResponseBody.js'); var calculateLivability = require('./lib/calculateLivability.js'); -//provide a browserified file at a path + var shared = ['mithril']; app.get('/js/vendor-bundle.js', browserify(shared)); app.get('/js/app-bundle.js', browserify('./client/app/index.js', { external: shared })); @@ -31,6 +28,14 @@ app.get('/', function (req, res){ //app.get('/crimes', function (req, res){}); +app.post('/distance', function(req, res){ + request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + + req.body.address+'&destinations='+req.body.workAddress+'&key='+ + APIKeys.GoogleDistance, function(error, response, body) { + if (error) throw error; + res.send(body.rows[0].elements[0]) + }) +}) //Init objects for use in Zillow API calls. var houseData = {}; var neighborhoodURL = ''; @@ -48,10 +53,6 @@ var neighborhoodOptions = { app.post('/', function (req, res){ - // Get the street address, remove commas, and replace whitespace with '+' for use in Zillow API - var zillowAddress = req.body.address.split(' Austin')[0].replace(/\s/g, '+').replace(/\,/,''); - addressOptions.uri = addressOptionsTemplate.uri.replace(/\*/,zillowAddress); - function milestoMeters(miles){ return miles / 0.00062137; } @@ -78,82 +79,6 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - // Make Zillow API call to get address value and neighborhood demographic info - // Then send httpResponseBody back to client - .then(function (httpResponseBody) { - requestPromise(addressOptions) - // Get the basic info for the requested address - .then(function(res){ - if(res.search(/Error/g)>0) { - console.log('No exact match found by Zillow') - } else { - //Parse the XML response from Zillow into a JS object - // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. - parser.parseString(res, function(err, result) { - //Unwrap the response the extract the desired data - if (err) { - console.log('Parse error') - } else { - houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] - houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood = {} - houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] - houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] - neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); - } - }) - } - return neighborhoodOptions - }) - // Get the neighborhood information for the requested address - .then(function(result){ - if (result.uri===''){ - return 'Zillow Error' - } else { - return requestPromise(result) - } - }) - // Extract the neighborhood data and insert into httpResponseBody - .then(function(res){ - if (res !== 'Zillow Error'){ - parser.parseString(res, function(err, result){ - if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 - - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 - } - // Attach Zillow data to response - httpResponseBody.zillowData = houseData - }) - } else { - httpResponseBody.zillowData = {} - } - return httpResponseBody - }) - // Send response back to client - .then(function (httpResponseBody){ - var weights = req.body.weights || {restaurants: 50, crimes: 50}; - calculateLivability(weights, httpResponseBody, req.body.radius); - res.json(httpResponseBody); - console.log(httpResponseBody.zillowData) - }); - }) }); var port = process.env.PORT || 4000; From adb4b1280e4dfd4d2311adeda50f661d21edac0e Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 10:46:20 -0500 Subject: [PATCH 19/44] Client gets Distance data --- client/app/components/mapContainer.js | 4 ++-- client/app/components/searchBox.js | 2 +- client/app/components/signinBox.js | 2 +- client/app/models/Location.js | 1 - server/index.js | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 458ba34..293b925 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -47,8 +47,8 @@ function mapSetup(options, element, isInitialized) { var lat = options.location.lat() || 30.25; var lng = options.location.lng() || -97.75; - console.log(lat, lng); - console.log(isInitialized); + // console.log(lat, lng); + // console.log(isInitialized); var mapCenter = new google.maps.LatLng(lat, lng); var mapOptions = { diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 11a3110..cfa5de8 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -24,7 +24,7 @@ exports.controller = function (options) { m.redraw(); toastr["success"]("Data successfully loaded for " + address); Location.postToGoogleDistanceAPI(address, workAddress, function(res){ - console.log(res) + console.log(res.rows[0].elements[0]) }) } }); diff --git a/client/app/components/signinBox.js b/client/app/components/signinBox.js index f5ceea9..8955d75 100644 --- a/client/app/components/signinBox.js +++ b/client/app/components/signinBox.js @@ -19,7 +19,7 @@ header.controller = function (options) { toastr["error"](error); //ctrl.error(error); } else { - console.log("failed"); + console.log("Log in failed"); } }); }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 2e19c0d..41f976a 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -81,7 +81,6 @@ var Locations = module.exports = { workAddress:addressFormatter(workAddress) }}) .then(function(res){ - console.log(res); callback(res); }); }, diff --git a/server/index.js b/server/index.js index 7cb4734..4b924b1 100644 --- a/server/index.js +++ b/server/index.js @@ -33,7 +33,7 @@ app.post('/distance', function(req, res){ req.body.address+'&destinations='+req.body.workAddress+'&key='+ APIKeys.GoogleDistance, function(error, response, body) { if (error) throw error; - res.send(body.rows[0].elements[0]) + res.send(body) }) }) //Init objects for use in Zillow API calls. From 43c35de69828d3294effa4eea7fb55350e48cbc4 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 15:56:56 -0500 Subject: [PATCH 20/44] Calculating Distance Livability --- client/app/components/graphContainer.js | 6 ++++- client/app/components/searchBox.js | 13 +++++++---- client/app/models/Location.js | 31 ++++++++++++++----------- server/index.js | 18 +++++++++++++- server/lib/calculateLivability.js | 5 ++++ 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index b058cc0..fa3cdb1 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -177,15 +177,19 @@ exports.controller = function(options) { }); }); }; - }; exports.view = function(ctrl, options) { return m('div', [m('.col-sm-4 .crimeGraph', {config: ctrl.initCrime}), m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), +<<<<<<< HEAD m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}), m('.col-sm-4 .costCompare', {config: ctrl.initCostCompare}) +======= + m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), + m('.col-sm-4 .commuteTime', {config: ctrl.initCommuteTime}) +>>>>>>> Calculating Distance Livability ]); }; diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index cfa5de8..e9552bc 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -15,7 +15,7 @@ exports.controller = function (options) { ctrl.fetchGeoCode = function() { var address = options.location.address(); var workAddress = options.location.workAddress(); - return Location.postToFetchRestaurantData(address, function cb(res) { + return Location.postToFetchRestaurantData(address, workAddress, function cb(res) { //set values on vm options.location.lng(res.lng); options.location.lat(res.lat); @@ -23,9 +23,6 @@ exports.controller = function (options) { if (res !== null) { m.redraw(); toastr["success"]("Data successfully loaded for " + address); - Location.postToGoogleDistanceAPI(address, workAddress, function(res){ - console.log(res.rows[0].elements[0]) - }) } }); }; @@ -64,6 +61,14 @@ exports.view = function (ctrl, options) { )] )] )], + [m('.col-sm-6', + [m('h4', 'Commute Time: ' + Location.commuteWeight())], + [m('.slider', + [m('input[type="range"]' + ,{min: 0, max: 100, step: 1, value: Location.commuteWeight(), onchange: m.withAttr('value', Location.commuteWeight)} + )] + )] + )], [m('input.addressInput.addressInput-submit[type="submit"][value="Try your luck"]')] //input form ), //form-group ] //form diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 41f976a..fe51ece 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -38,14 +38,16 @@ var Locations = module.exports = { lng: m.prop(''), crimeWeight: m.prop(''), restWeight: m.prop(''), + commuteWeight: m.prop(''), costWeight: m.prop(''), address: m.prop(''), + commuteTime: m.prop(''), zillowIncomeNeighborhood: m.prop(0), zillowIncomeCity: m.prop(0), - postToFetchRestaurantData: function(address, cb) { + postToFetchRestaurantData: function(address, workAddress, callback) { Locations.address(address); - var cb = cb; + var callback = callback; this.postToFetchGeoCode(address, function (res) { Locations.lat(res.results[0].geometry.location.lat); Locations.lng(res.results[0].geometry.location.lng); @@ -56,7 +58,8 @@ var Locations = module.exports = { "radius": 1, "weights": { "crimes": Locations.crimeWeight() || 50, - "restaurants": Locations.restWeight() || 50 + "restaurants": Locations.restWeight() || 50, + "commute" : Locations.commuteWeight() || 50 } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) @@ -70,20 +73,20 @@ var Locations = module.exports = { console.log('city', Locations.zillowIncomeCity()) } Locations.saveSearch(Locations.address(), res.livibility); - return cb(data); + return callback(data); }) }); }, - postToGoogleDistanceAPI: function(address, workAddress, callback) { - return m.request({method: "POST", url: '/distance', data: { - address:addressFormatter(address), - workAddress:addressFormatter(workAddress) - }}) - .then(function(res){ - callback(res); - }); - }, + // postToGoogleDistanceAPI: function(address, workAddress, callback) { + // return m.request({method: "POST", url: '/distance', data: { + // address:addressFormatter(address), + // workAddress:addressFormatter(workAddress) + // }}) + // .then(function(res){ + // Locations.commuteTime(Math.round(res.rows[0].elements[0].duration.value/60)) + // }); + // }, saveSearch: function(address, livabilityScore){ var user = Auth.isAuthenticated(); @@ -109,7 +112,7 @@ var Locations = module.exports = { address: m.prop(''), lat: m.prop(''), lng: m.prop(''), - workAddress: m.prop('') + workAddress: m.prop(''), } } diff --git a/server/index.js b/server/index.js index 4b924b1..01f1f76 100644 --- a/server/index.js +++ b/server/index.js @@ -30,7 +30,7 @@ app.get('/', function (req, res){ app.post('/distance', function(req, res){ request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + - req.body.address+'&destinations='+req.body.workAddress+'&key='+ + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ APIKeys.GoogleDistance, function(error, response, body) { if (error) throw error; res.send(body) @@ -79,6 +79,22 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) + // .then(function (httpResponseBody) { + // request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + + // req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ + // APIKeys.GoogleDistance, function(error, response, body) { + // if (error) throw error; + // httpResponseBody.distance = body; + // return httpResponseBody + // }) + // }) + .then(function (httpResponseBody){ + var weights = req.body.weights || {restaurants: 50, crimes: 50}; + calculateLivability(weights, httpResponseBody, req.body.radius); + console.log(Object.keys(httpResponseBody)); + //console.log(weights) + res.json(httpResponseBody); + }); }); var port = process.env.PORT || 4000; diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index e62299c..b74806e 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -7,6 +7,7 @@ var logDebug = function(string){ //takes an object that has user slider weights, and returns an object with same information. //The purpose is to make sure the weights add up to 100, but maintains ratios to one another. var scaleWeights = function(weights){ + console.log(weights) var sum = _.reduce(weights, function (tot, weight){ return tot += +weight; }, 0) @@ -30,6 +31,10 @@ var linEqHandler = function(data, tag){ pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, input: data.searchCrimesPerSqMi };; + case 'commute': + return { + + } } } From f5d4ac7d559e16fd86d4bfcd9ed7d324dcc38451 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 16:52:17 -0500 Subject: [PATCH 21/44] Rebase --- client/app/components/searchBox.js | 2 +- client/app/models/Location.js | 2 +- exampleResponse.js | 3 ++- server/index.js | 18 +++++++++--------- server/lib/calculateLivability.js | 10 ++++++---- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index e9552bc..ffba02d 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -23,7 +23,7 @@ exports.controller = function (options) { if (res !== null) { m.redraw(); toastr["success"]("Data successfully loaded for " + address); - } + }; }); }; }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index fe51ece..685f129 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -59,7 +59,7 @@ var Locations = module.exports = { "weights": { "crimes": Locations.crimeWeight() || 50, "restaurants": Locations.restWeight() || 50, - "commute" : Locations.commuteWeight() || 50 + // "commute" : Locations.commuteWeight() || 50 } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) diff --git a/exampleResponse.js b/exampleResponse.js index 0304530..e9100aa 100644 --- a/exampleResponse.js +++ b/exampleResponse.js @@ -3,7 +3,8 @@ Post({ "address": "3308 Webberville/Bedford, Austin, TX 78702", "lat":"30.269527", "lng":"-97.70707990099999", - "radius": 1 + "radius": 1, + "weights": {"restaurants": 46, "crimes": 34} }) //single object that will be sent to client diff --git a/server/index.js b/server/index.js index 01f1f76..5b74037 100644 --- a/server/index.js +++ b/server/index.js @@ -79,15 +79,15 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - // .then(function (httpResponseBody) { - // request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + - // req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ - // APIKeys.GoogleDistance, function(error, response, body) { - // if (error) throw error; - // httpResponseBody.distance = body; - // return httpResponseBody - // }) - // }) + .then(function (httpResponseBody) { + request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ + APIKeys.GoogleDistance, function(error, response, body) { + if (error) throw error; + httpResponseBody.distance = body; + return httpResponseBody + }) + }) .then(function (httpResponseBody){ var weights = req.body.weights || {restaurants: 50, crimes: 50}; calculateLivability(weights, httpResponseBody, req.body.radius); diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index b74806e..92ab2a2 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -31,10 +31,12 @@ var linEqHandler = function(data, tag){ pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, input: data.searchCrimesPerSqMi };; - case 'commute': - return { - - } + // case 'commute': + // return { + // pnt1: {x: ,y:80} + // pnt2: {x:,y:100} + // input: data. + // } } } From 57dd1f1cd20dc2fff2cd5f7c24d76a0e3c0ad6c3 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 17:05:28 -0500 Subject: [PATCH 22/44] Merged with Zillow --- client/app/components/graphContainer.js | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index fa3cdb1..501d28b 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -135,6 +135,49 @@ exports.controller = function(options) { }); }; + ctrl.initCommuteTime = function (element, isInit, context) { + + //Initialize restaurant safety chart + $(function () { + $('.commuteTime').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Commute Time' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Average Commute Time (2015)' + } + }, + series: [{ + name: 'Your Search', + data: [Location.commuteTime()] + }, { + name: 'City of Austin (average)', + data: [24.6] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); + }; + + ctrl.initCostCompare = function (element, isInit, context) { //Initialize number of restaurants chart From 98e75438db3d965ed6657ff74409750a44f4ca2a Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 21:13:47 -0500 Subject: [PATCH 23/44] Display commute time using previous post function --- client/app/components/graphContainer.js | 49 ------------------------- client/app/models/Location.js | 21 ++--------- client/public/sass/graphContainer.scss | 9 +---- client/public/style.css | 8 +--- server/index.js | 25 ++----------- 5 files changed, 10 insertions(+), 102 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index 501d28b..fd5e69f 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -176,63 +176,14 @@ exports.controller = function(options) { }); }); }; - - - ctrl.initCostCompare = function (element, isInit, context) { - - //Initialize number of restaurants chart - $(function () { - $('.costCompare').highcharts({ - colors: ['#7A878B', '#434348'], - chart: { - type: 'column', - spacing: 50 - }, - title: { - text: 'Median Neighborhood Income' - }, - xAxis: { - categories: [''] - }, - yAxis: { - title: { - text: 'Yearly Income (average)' - } - }, - series: [{ - name: 'Your Search', - data: [Location.zillowIncomeNeighborhood()] - }, { - name: 'City of Austin (average)', - // data: [Location.search().restaurants] - data: [Location.zillowIncomeCity()] - }], - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormat: '' + - '', - footerFormat: '
{series.name}: {point.y}
', - }, - credits: { - enabled: false - } - }); - }); - }; }; exports.view = function(ctrl, options) { return m('div', [m('.col-sm-4 .crimeGraph', {config: ctrl.initCrime}), m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), -<<<<<<< HEAD m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}), - m('.col-sm-4 .costCompare', {config: ctrl.initCostCompare}) -======= - m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), m('.col-sm-4 .commuteTime', {config: ctrl.initCommuteTime}) ->>>>>>> Calculating Distance Livability ]); }; diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 685f129..42e87a9 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -42,8 +42,6 @@ var Locations = module.exports = { costWeight: m.prop(''), address: m.prop(''), commuteTime: m.prop(''), - zillowIncomeNeighborhood: m.prop(0), - zillowIncomeCity: m.prop(0), postToFetchRestaurantData: function(address, workAddress, callback) { Locations.address(address); @@ -53,6 +51,7 @@ var Locations = module.exports = { Locations.lng(res.results[0].geometry.location.lng); var locationData = { "address": address, + "workAddress" : workAddress, "lat": res.results[0].geometry.location.lat, "lng": res.results[0].geometry.location.lng, "radius": 1, @@ -67,10 +66,6 @@ var Locations = module.exports = { var data = modelData(res); if (data !== null) { Locations.search(data); - Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); - Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); - console.log('neighborhood', Locations.zillowIncomeNeighborhood()) - console.log('city', Locations.zillowIncomeCity()) } Locations.saveSearch(Locations.address(), res.livibility); return callback(data); @@ -78,16 +73,6 @@ var Locations = module.exports = { }); }, - // postToGoogleDistanceAPI: function(address, workAddress, callback) { - // return m.request({method: "POST", url: '/distance', data: { - // address:addressFormatter(address), - // workAddress:addressFormatter(workAddress) - // }}) - // .then(function(res){ - // Locations.commuteTime(Math.round(res.rows[0].elements[0].duration.value/60)) - // }); - // }, - saveSearch: function(address, livabilityScore){ var user = Auth.isAuthenticated(); if(user) { @@ -128,6 +113,9 @@ var modelData = function(data) { //Separate data into variables var inspectCount = 0; + Locations.commuteTime(Math.round(JSON.parse(data.distance).rows[0].elements[0].duration.value/60)) + console.log(Locations.commuteTime()) + var sum = data.restaurants.reduce(function(tot, rest){ if(rest.avg) { tot += rest.avg; @@ -148,7 +136,6 @@ var modelData = function(data) { livability: data.livibility, cityRestAvg: data.meanRestInspecAvg, cityCrimeAvg: data.meanCrimesPerSqMi, - zillow: data.zillowData }; console.log('response:', response) diff --git a/client/public/sass/graphContainer.scss b/client/public/sass/graphContainer.scss index 527e799..9a15218 100644 --- a/client/public/sass/graphContainer.scss +++ b/client/public/sass/graphContainer.scss @@ -1,11 +1,4 @@ -.crimeGraph .restaurantSafety .restaurantNumber .costCompare { - height: 100%; - width: 100%; - margin: 0 auto; - padding: 100px; -} - -.costCompare { +.costCompare, .commuteTime { margin-top: 30px; } diff --git a/client/public/style.css b/client/public/style.css index 0cce900..ff74105 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -68,13 +68,7 @@ h3, h4 { /* END general styling */ -.crimeGraph .restaurantSafety .restaurantNumber .costCompare { - height: 100%; - width: 100%; - margin: 0 auto; - padding: 100px; } - -.costCompare { +.costCompare, .commuteTime { margin-top: 30px; } .gaugeContainer { diff --git a/server/index.js b/server/index.js index 5b74037..e3cdfa3 100644 --- a/server/index.js +++ b/server/index.js @@ -7,7 +7,8 @@ var app = express(); var Restaurant = require('./models/restaurant.js'); var httpResponseBody = require('./lib/httpResponseBody.js'); var calculateLivability = require('./lib/calculateLivability.js'); - +var request = require('request-promise') +var APIKeys = require('./apikeys.js') var shared = ['mithril']; app.get('/js/vendor-bundle.js', browserify(shared)); @@ -36,21 +37,6 @@ app.post('/distance', function(req, res){ res.send(body) }) }) -//Init objects for use in Zillow API calls. -var houseData = {}; -var neighborhoodURL = ''; -var addressOptionsTemplate = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', - method: 'GET' -};var addressOptions = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', - method: 'GET' -}; -var neighborhoodOptions = { - uri: '', - method: 'GET' -}; - app.post('/', function (req, res){ function milestoMeters(miles){ @@ -85,17 +71,14 @@ app.post('/', function (req, res){ APIKeys.GoogleDistance, function(error, response, body) { if (error) throw error; httpResponseBody.distance = body; - return httpResponseBody - }) + }).then(function(){return httpResponseBody }) .then(function (httpResponseBody){ var weights = req.body.weights || {restaurants: 50, crimes: 50}; calculateLivability(weights, httpResponseBody, req.body.radius); - console.log(Object.keys(httpResponseBody)); - //console.log(weights) res.json(httpResponseBody); }); -}); +})}); var port = process.env.PORT || 4000; app.listen(port); From ff393c31a187a408d874fdcf2e1d1d1d43eb3c70 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Wed, 29 Jul 2015 21:28:53 -0500 Subject: [PATCH 24/44] Added commute time to livability score --- client/app/models/Location.js | 3 +-- server/lib/calculateLivability.js | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 42e87a9..9792c35 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -39,7 +39,6 @@ var Locations = module.exports = { crimeWeight: m.prop(''), restWeight: m.prop(''), commuteWeight: m.prop(''), - costWeight: m.prop(''), address: m.prop(''), commuteTime: m.prop(''), @@ -58,7 +57,7 @@ var Locations = module.exports = { "weights": { "crimes": Locations.crimeWeight() || 50, "restaurants": Locations.restWeight() || 50, - // "commute" : Locations.commuteWeight() || 50 + "commute" : Locations.commuteWeight() || 50 } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index 92ab2a2..4343cdd 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -31,12 +31,13 @@ var linEqHandler = function(data, tag){ pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, input: data.searchCrimesPerSqMi };; - // case 'commute': - // return { - // pnt1: {x: ,y:80} - // pnt2: {x:,y:100} - // input: data. - // } + case 'commute': + var commute = JSON.parse(data.distance).rows[0].elements[0].duration.value/60 + return { + pnt1: {x: 24.6, y: 50}, + pnt2: {x: 12.3, y:100}, + input: commute + } } } @@ -69,10 +70,10 @@ var calculateScore = function(handler, weight){ score > 100 ? weight * 100 : score * weight } -module.exports = function attachStatsToHttpResponeBody(weights, httpResponseBody, radius){ - httpResponseBody.searchCrimesPerSqMi = (httpResponseBody.crimes.length / (Math.PI * radius * radius)); - httpResponseBody.livibility = _.reduce(scaleWeights(weights), function findPartialLivibility(score, val, key){ +module.exports = + function attachStatsToHttpResponeBody(weights, httpResponseBody, radius){ + httpResponseBody.searchCrimesPerSqMi = (httpResponseBody.crimes.length / (Math.PI * radius * radius)); + httpResponseBody.livibility = _.reduce(scaleWeights(weights), function findPartialLivibility(score, val, key){ return score += calculateScore(linEqHandler(httpResponseBody, key), val/100) - }, 0); } \ No newline at end of file From 64e3b83475eaa1524c387e7de50550aed749a17f Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 11:52:46 -0500 Subject: [PATCH 25/44] Page Still Functions without Work Address and Weight is set to 0 --- client/app/models/Location.js | 8 +++++--- server/index.js | 2 +- server/lib/calculateLivability.js | 17 +++++++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 9792c35..aea6d8d 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -57,7 +57,7 @@ var Locations = module.exports = { "weights": { "crimes": Locations.crimeWeight() || 50, "restaurants": Locations.restWeight() || 50, - "commute" : Locations.commuteWeight() || 50 + "commute" : workAddress !== '' ? Locations.commuteWeight() || 50 : 0 } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) @@ -112,8 +112,10 @@ var modelData = function(data) { //Separate data into variables var inspectCount = 0; - Locations.commuteTime(Math.round(JSON.parse(data.distance).rows[0].elements[0].duration.value/60)) - console.log(Locations.commuteTime()) + var commuteData = JSON.parse(data.distance) + if (commuteData.status === 'OK'){ + Locations.commuteTime(Math.round(JSON.parse(data.distance).rows[0].elements[0].duration.value/60)) + } var sum = data.restaurants.reduce(function(tot, rest){ if(rest.avg) { diff --git a/server/index.js b/server/index.js index e3cdfa3..7e10ef6 100644 --- a/server/index.js +++ b/server/index.js @@ -65,7 +65,7 @@ app.post('/', function (req, res){ httpResponseBody.searchInspecAvg = sum / count; return httpResponseBody; }) - .then(function (httpResponseBody) { + .then(function attachCommuteTime(httpResponseBody) { request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ APIKeys.GoogleDistance, function(error, response, body) { diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index 4343cdd..22526de 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -32,11 +32,20 @@ var linEqHandler = function(data, tag){ input: data.searchCrimesPerSqMi };; case 'commute': - var commute = JSON.parse(data.distance).rows[0].elements[0].duration.value/60 + //Average and lowest average commute based on zipatlas.com + var commute = JSON.parse(data.distance) + if (commute.status === "INVALID_REQUEST") { + return { + pnt1: {x: 24.6, y: 80}, + pnt2: {x: 16.1, y:100}, + input: 24.6 + } + } + var input = commute.rows[0].elements[0].duration.value/60 return { - pnt1: {x: 24.6, y: 50}, - pnt2: {x: 12.3, y:100}, - input: commute + pnt1: {x: 24.6, y: 80}, + pnt2: {x: 16.1, y:100}, + input: input } } } From 0cf3993e46677e0aacdfefb3c8bb0436a92f003e Mon Sep 17 00:00:00 2001 From: WesTyler Date: Thu, 30 Jul 2015 12:27:04 -0500 Subject: [PATCH 26/44] Remove extra console.logs --- server/index.js | 1 - server/lib/calculateLivability.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/server/index.js b/server/index.js index 3809454..a3e5ca3 100644 --- a/server/index.js +++ b/server/index.js @@ -149,7 +149,6 @@ app.post('/', function (req, res){ // Send response back to client .then(function (httpResponseBody){ var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; - console.log(httpResponseBody) calculateLivability(weights, httpResponseBody, req.body.radius); res.json(httpResponseBody); }); diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index 0bca55f..7e32ab2 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -57,8 +57,6 @@ var linEq = function(handler){ var m = slope(handler.pnt1, handler.pnt2) var x = handler.input var b = yIntercept(handler.pnt1, handler.pnt2) - console.log('m:', m,'x:',x,'b:',b) - console.log('y:',m*x+b) return m * x + b; } From f3161e1ed6abe7b9047ca6cd26cfcbe118c743a3 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Wed, 29 Jul 2015 20:08:12 -0500 Subject: [PATCH 27/44] Factor affordability into livability score. Higher-than-average income yields lower livability. --- client/app/components/searchBox.js | 10 +++- client/app/models/Location.js | 5 +- server/index.js | 84 +++++++++++++++++++++++++++--- server/lib/calculateLivability.js | 13 +++-- server/lib/search.js | 4 +- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index ffba02d..56a5560 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -60,7 +60,7 @@ exports.view = function (ctrl, options) { ,{min: 0, max: 100, step: 1, value: Location.restWeight(), onchange: m.withAttr('value', Location.restWeight)} )] )] - )], + )], [m('.col-sm-6', [m('h4', 'Commute Time: ' + Location.commuteWeight())], [m('.slider', @@ -69,6 +69,14 @@ exports.view = function (ctrl, options) { )] )] )], + [m('.col-sm-6', + [m('h4', 'Affordability: ' + Location.costWeight())], + [m('.slider', + [m('input[type="range"]' + ,{min: 0, max: 100, step: 1, value: Location.costWeight(), onchange: m.withAttr('value', Location.costWeight)} + )] + )] + )], [m('input.addressInput.addressInput-submit[type="submit"][value="Try your luck"]')] //input form ), //form-group ] //form diff --git a/client/app/models/Location.js b/client/app/models/Location.js index aea6d8d..6a49358 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -57,7 +57,8 @@ var Locations = module.exports = { "weights": { "crimes": Locations.crimeWeight() || 50, "restaurants": Locations.restWeight() || 50, - "commute" : workAddress !== '' ? Locations.commuteWeight() || 50 : 0 + "commute" : workAddress !== '' ? Locations.commuteWeight() || 50 : 0, + "affordability": Locations.costWeight() || 50, } }; return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) @@ -65,6 +66,8 @@ var Locations = module.exports = { var data = modelData(res); if (data !== null) { Locations.search(data); + Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); + Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); } Locations.saveSearch(Locations.address(), res.livibility); return callback(data); diff --git a/server/index.js b/server/index.js index 7e10ef6..65c0196 100644 --- a/server/index.js +++ b/server/index.js @@ -71,13 +71,85 @@ app.post('/', function (req, res){ APIKeys.GoogleDistance, function(error, response, body) { if (error) throw error; httpResponseBody.distance = body; - }).then(function(){return httpResponseBody + }) + .then(function(){return httpResponseBody}) + }) + // Make Zillow API call to get address value and neighborhood demographic info + // Then send httpResponseBody back to client + .then(function (httpResponseBody) { + requestPromise(addressOptions) + // Get the basic info for the requested address + .then(function(res){ + if(res.search(/Error/g)>0) { + console.log('No exact match found by Zillow') + } else { + //Parse the XML response from Zillow into a JS object + // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. + parser.parseString(res, function(err, result) { + //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + } else { + houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] + houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood = {} + houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] + houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] + neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + } + }) + } + return neighborhoodOptions + }) + // Get the neighborhood information for the requested address + .then(function(result){ + if (result.uri===''){ + return 'Zillow Error' + } else { + return requestPromise(result) + } + }) + // Extract the neighborhood data and insert into httpResponseBody + .then(function(res){ + if (res !== 'Zillow Error'){ + parser.parseString(res, function(err, result){ + if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 + + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + } + // Attach Zillow data to response + httpResponseBody.zillowData = houseData + }) + } else { + httpResponseBody.zillowData = {} + } + return httpResponseBody + }) + // Send response back to client + .then(function (httpResponseBody){ + var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; + console.log(httpResponseBody) + calculateLivability(weights, httpResponseBody, req.body.radius); + res.json(httpResponseBody); + }); }) - .then(function (httpResponseBody){ - var weights = req.body.weights || {restaurants: 50, crimes: 50}; - calculateLivability(weights, httpResponseBody, req.body.radius); - res.json(httpResponseBody); - }); })}); var port = process.env.PORT || 4000; diff --git a/server/lib/calculateLivability.js b/server/lib/calculateLivability.js index 22526de..b8308a5 100644 --- a/server/lib/calculateLivability.js +++ b/server/lib/calculateLivability.js @@ -7,7 +7,6 @@ var logDebug = function(string){ //takes an object that has user slider weights, and returns an object with same information. //The purpose is to make sure the weights add up to 100, but maintains ratios to one another. var scaleWeights = function(weights){ - console.log(weights) var sum = _.reduce(weights, function (tot, weight){ return tot += +weight; }, 0) @@ -22,15 +21,15 @@ var linEqHandler = function(data, tag){ case 'restaurants': return { pnt1: {x: data.meanRestInspecAvg, y: 80}, - pnt2: {x: data.meanRestInspecAvg + 5, y: 100}, + pnt2: {x: data.meanRestInspecAvg + 5, y: 100}, // For every change of 5 in a health inspection score, the restaurant livability portion changes by 20 input: data.searchInspecAvg }; case 'crimes': return { pnt1: {x: data.meanCrimesPerSqMi, y: data.meanCrimesPerSqMi}, - pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, + pnt2: {x: data.meanCrimesPerSqMi - 30, y: data.meanCrimesPerSqMi + 12}, // For every change of 30 in crimes/sq mi, the crimes livability portion changes by 12 input: data.searchCrimesPerSqMi - };; + }; case 'commute': //Average and lowest average commute based on zipatlas.com var commute = JSON.parse(data.distance) @@ -47,6 +46,12 @@ var linEqHandler = function(data, tag){ pnt2: {x: 16.1, y:100}, input: input } + case 'affordability': + return { + pnt1: {x: data.zillowData.neighborhood.medianIncomeCity/1000, y: 75}, + pnt2: {x: (data.zillowData.neighborhood.medianIncomeCity*0.5)/1000, y: 100}, // For every change of 10% in income vs city average, the affordability livability portion changes by 2.5 + input: data.zillowData.neighborhood.medianIncomeNeighborhood/1000 + }; } } diff --git a/server/lib/search.js b/server/lib/search.js index ff7311f..05ebf1d 100644 --- a/server/lib/search.js +++ b/server/lib/search.js @@ -7,7 +7,7 @@ var Restaurant = require('../models/restaurant.js'); var Promise = require('bluebird'); var geolib = require('geolib') -//ffunction allows search to be called on multiple tables +//function allows search to be called on multiple tables //returns an object containing appropratie functions for given model name var handler = function (modelName){ switch(modelName){ @@ -55,7 +55,7 @@ var reduceRestaurants = function (restaurants, circle){ return geolib.isPointInCircle(restCoords, circle, circle.meters); }) - //take that list from promsie, then map that list to a new list (change is described in nex comment) + //take that list from promise, then map that list to a new list (change is described in nex comment) .then(function (stored){ return Promise.map(stored, function findRestaurantInspections(rest){ From 57dd3e1a3cff752419a524e0d96dffa76078936a Mon Sep 17 00:00:00 2001 From: WesTyler Date: Thu, 30 Jul 2015 12:27:04 -0500 Subject: [PATCH 28/44] Remove extra console.logs --- server/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/index.js b/server/index.js index 65c0196..fbeafc1 100644 --- a/server/index.js +++ b/server/index.js @@ -145,7 +145,6 @@ app.post('/', function (req, res){ // Send response back to client .then(function (httpResponseBody){ var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; - console.log(httpResponseBody) calculateLivability(weights, httpResponseBody, req.body.radius); res.json(httpResponseBody); }); From dd8833a8a81118ba4469fb0d16a147d401472361 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Thu, 30 Jul 2015 12:47:34 -0500 Subject: [PATCH 29/44] Merge with master in progress --- client/app/components/graphContainer.js | 316 ++++++++++++------------ client/app/models/Location.js | 2 +- server/index.js | 2 +- 3 files changed, 161 insertions(+), 159 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index fd5e69f..63c5a67 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -10,172 +10,174 @@ exports.controller = function(options) { ctrl.initCrime = function (element, isInit, context) { - //Initialize crime chart - $(function () { - $('.crimeGraph').highcharts({ - colors: ['#7A878B', '#434348'], - chart: { - type: 'column', - spacing: 50, - }, - title: { - text: 'Crime Rate (Adjusted)' - }, - xAxis: { - categories: [''] - }, - yAxis: { - title: { - text: 'Crimes Reported: Jan 2015 - Present' - } - }, - series: [{ - name: 'Your Search', - data: [Math.floor(Location.search().crimeAvg)] - }, { - name: 'City of Austin (average)', - data: [Math.floor(Location.search().cityCrimeAvg)] - }], - //Info that appears when you hover over a column - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormat: '' + - '', - footerFormat: '
{series.name}: {point.y}
', - }, - credits: { - enabled: false - } - }); - }); + //Initialize crime chart + $(function () { + $('.crimeGraph').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50, + }, + title: { + text: 'Crime Rate (Adjusted)' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Crimes Reported: Jan 2015 - Present' + } + }, + series: [{ + name: 'Your Search', + data: [Math.floor(Location.search().crimeAvg)] + }, { + name: 'City of Austin (average)', + data: [Math.floor(Location.search().cityCrimeAvg)] + }], + //Info that appears when you hover over a column + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; ctrl.initRestSafety = function (element, isInit, context) { - - //Initialize restaurant safety chart - $(function () { - $('.restaurantSafety').highcharts({ - colors: ['#7A878B', '#434348'], - chart: { - type: 'column', - spacing: 50 - }, - title: { - text: 'Restaurant Score (Adjusted)' - }, - xAxis: { - categories: [''] - }, - yAxis: { - title: { - text: 'Average Health Inspection Rating (2015)' - } - }, - series: [{ - name: 'Your Search', - data: [Math.floor(Location.search().restAvg)] - }, { - name: 'City of Austin (average)', - data: [Math.floor(Location.search().cityRestAvg)] - }], - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormat: '' + - '', - footerFormat: '
{series.name}: {point.y}
', - }, - credits: { - enabled: false - } - }); - }); + + //Initialize restaurant safety chart + $(function () { + $('.restaurantSafety').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Restaurant Score (Adjusted)' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Average Health Inspection Rating (2015)' + } + }, + series: [{ + name: 'Your Search', + data: [Math.floor(Location.search().restAvg)] + }, { + name: 'City of Austin (average)', + data: [Math.floor(Location.search().cityRestAvg)] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; ctrl.initRestNumber = function (element, isInit, context) { - - //Initialize number of restaurants chart - $(function () { - $('.restaurantNumber').highcharts({ - colors: ['#7A878B', '#434348'], - chart: { - type: 'column', - spacing: 50 - }, - title: { - text: 'Number of Restaurants' - }, - xAxis: { - categories: [''] - }, - yAxis: { - title: { - text: 'Restaurants per square mile' - } - }, - series: [{ - name: 'Your Search', - data: [Location.search().restaurants] - }, { - name: 'City of Austin (average)', - data: Location.search().restaurants ? [51] : [0] - }], - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormat: '' + - '', - footerFormat: '
{series.name}: {point.y}
', - }, - credits: { - enabled: false - } - }); - }); + + //Initialize number of restaurants chart + $(function () { + $('.restaurantNumber').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Number of Restaurants' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Restaurants per square mile' + } + }, + series: [{ + name: 'Your Search', + data: [Location.search().restaurants] + }, { + name: 'City of Austin (average)', + data: Location.search().restaurants ? [51] : [0] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; - ctrl.initCommuteTime = function (element, isInit, context) { - - //Initialize restaurant safety chart - $(function () { - $('.commuteTime').highcharts({ - colors: ['#7A878B', '#434348'], - chart: { - type: 'column', - spacing: 50 - }, - title: { - text: 'Commute Time' - }, - xAxis: { - categories: [''] - }, - yAxis: { - title: { - text: 'Average Commute Time (2015)' - } - }, - series: [{ - name: 'Your Search', - data: [Location.commuteTime()] - }, { - name: 'City of Austin (average)', - data: [24.6] - }], - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormat: '' + - '', - footerFormat: '
{series.name}: {point.y}
', - }, - credits: { - enabled: false - } - }); - }); + ctrl.initCommuteTime = function (element, isInit, context) { + + //Initialize restaurant safety chart + $(function () { + $('.commuteTime').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Commute Time' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Average Commute Time (2015)' + } + }, + series: [{ + name: 'Your Search', + data: [Location.commuteTime()] + }, { + name: 'City of Austin (average)', + data: [24.6] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); }; + + }; exports.view = function(ctrl, options) { diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 6a49358..586fcc9 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -41,6 +41,7 @@ var Locations = module.exports = { commuteWeight: m.prop(''), address: m.prop(''), commuteTime: m.prop(''), + costWeight: m.prop(''), postToFetchRestaurantData: function(address, workAddress, callback) { Locations.address(address); @@ -141,7 +142,6 @@ var modelData = function(data) { cityRestAvg: data.meanRestInspecAvg, cityCrimeAvg: data.meanCrimesPerSqMi, }; - console.log('response:', response) if(isNaN(response.restAvg)) { toastr["error"]("No available data. Please check that the address"); diff --git a/server/index.js b/server/index.js index fbeafc1..a4d16d6 100644 --- a/server/index.js +++ b/server/index.js @@ -149,7 +149,7 @@ app.post('/', function (req, res){ res.json(httpResponseBody); }); }) -})}); +}); var port = process.env.PORT || 4000; app.listen(port); From 654f7eebb826a64b6366eebca1c0e2b0ab87cb08 Mon Sep 17 00:00:00 2001 From: WesTyler Date: Thu, 30 Jul 2015 12:57:20 -0500 Subject: [PATCH 30/44] Add costCompare graph back in after merge with master --- client/app/components/graphContainer.js | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index 63c5a67..0e9710a 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -177,6 +177,49 @@ exports.controller = function(options) { }); }; + ctrl.initCostCompare = function (element, isInit, context) { + + //Initialize number of restaurants chart + $(function () { + $('.costCompare').highcharts({ + colors: ['#7A878B', '#434348'], + chart: { + type: 'column', + spacing: 50 + }, + title: { + text: 'Median Neighborhood Income' + }, + xAxis: { + categories: [''] + }, + yAxis: { + title: { + text: 'Yearly Income (average)' + } + }, + series: [{ + name: 'Your Search', + data: [Location.zillowIncomeNeighborhood()] + }, { + name: 'City of Austin (average)', + // data: [Location.search().restaurants] + data: [Location.zillowIncomeCity()] + }], + tooltip: { + useHTML: true, + headerFormat: '{point.key}', + pointFormat: '' + + '', + footerFormat: '
{series.name}: {point.y}
', + }, + credits: { + enabled: false + } + }); + }); + }; + }; @@ -185,7 +228,8 @@ exports.view = function(ctrl, options) { [m('.col-sm-4 .crimeGraph', {config: ctrl.initCrime}), m('.col-sm-4 .restaurantSafety', {config: ctrl.initRestSafety}), m('.col-sm-4 .restaurantNumber', {config: ctrl.initRestNumber}), - m('.col-sm-4 .commuteTime', {config: ctrl.initCommuteTime}) + m('.col-sm-4 .commuteTime', {config: ctrl.initCommuteTime}), + m('.col-sm-4 .costCompare', {config: ctrl.initCostCompare}), ]); }; From c991eb855d26443d34475a3597307e4d014ac4fe Mon Sep 17 00:00:00 2001 From: WesTyler Date: Thu, 30 Jul 2015 13:09:21 -0500 Subject: [PATCH 31/44] Incorporate affordability and commute time functionality --- client/app/models/Location.js | 3 + server/index.js | 168 +++++++++++++++++++--------------- 2 files changed, 99 insertions(+), 72 deletions(-) diff --git a/client/app/models/Location.js b/client/app/models/Location.js index 586fcc9..bdc89a7 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -42,6 +42,8 @@ var Locations = module.exports = { address: m.prop(''), commuteTime: m.prop(''), costWeight: m.prop(''), + zillowIncomeNeighborhood: m.prop(0), + zillowIncomeCity: m.prop(0), postToFetchRestaurantData: function(address, workAddress, callback) { Locations.address(address); @@ -141,6 +143,7 @@ var modelData = function(data) { livability: data.livibility, cityRestAvg: data.meanRestInspecAvg, cityCrimeAvg: data.meanCrimesPerSqMi, + zillow: data.zillowData }; if(isNaN(response.restAvg)) { diff --git a/server/index.js b/server/index.js index a4d16d6..a5a677b 100644 --- a/server/index.js +++ b/server/index.js @@ -2,6 +2,9 @@ var browserify = require('browserify-middleware'); var bodyParser = require('body-parser'); var search = require('./lib/search.js'); var express = require('express'); +var requestPromise = require('request-promise'); // Use for promisified http requests to Zillow +var parser = require('xml2js'); // Parse XML response from Zillow API +var credentials = require('./lib/credentials.js') // git-ignored Zillow API key. var app = express(); //var db = require('./lib/db.js'); var Restaurant = require('./models/restaurant.js'); @@ -38,7 +41,27 @@ app.post('/distance', function(req, res){ }) }) +//Init objects for use in Zillow API calls. +var houseData = {}; +var neighborhoodURL = ''; +var addressOptionsTemplate = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + method: 'GET' +};var addressOptions = { + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + method: 'GET' +}; +var neighborhoodOptions = { + uri: '', + method: 'GET' +}; + + app.post('/', function (req, res){ + // Get the street address, remove commas, and replace whitespace with '+' for use in Zillow API + var zillowAddress = req.body.address.split(' Austin')[0].replace(/\s/g, '+').replace(/\,/,''); + addressOptions.uri = addressOptionsTemplate.uri.replace(/\*/,zillowAddress); + function milestoMeters(miles){ return miles / 0.00062137; } @@ -73,81 +96,82 @@ app.post('/', function (req, res){ httpResponseBody.distance = body; }) .then(function(){return httpResponseBody}) - }) - // Make Zillow API call to get address value and neighborhood demographic info - // Then send httpResponseBody back to client - .then(function (httpResponseBody) { - requestPromise(addressOptions) - // Get the basic info for the requested address - .then(function(res){ - if(res.search(/Error/g)>0) { - console.log('No exact match found by Zillow') - } else { - //Parse the XML response from Zillow into a JS object - // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. - parser.parseString(res, function(err, result) { - //Unwrap the response the extract the desired data - if (err) { - console.log('Parse error') - } else { - houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] - houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood = {} - houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] - houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] - neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); - } - }) - } - return neighborhoodOptions - }) - // Get the neighborhood information for the requested address - .then(function(result){ - if (result.uri===''){ - return 'Zillow Error' - } else { - return requestPromise(result) - } - }) - // Extract the neighborhood data and insert into httpResponseBody - .then(function(res){ - if (res !== 'Zillow Error'){ - parser.parseString(res, function(err, result){ - if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ - //Property Taxes for neighborhood and Austin average - houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - - //Median House Size for neighborhood and Austin average - houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 - - //Median Household Income for neighborhood and Austin average - houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) - houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + // Make Zillow API call to get address value and neighborhood demographic info + // Then send httpResponseBody back to client + .then(function (httpResponseBody) { + console.log('begin zillow calls:', httpResponseBody) + requestPromise(addressOptions) + // Get the basic info for the requested address + .then(function(res){ + if(res.search(/Error/g)>0) { + console.log('No exact match found by Zillow') + } else { + //Parse the XML response from Zillow into a JS object + // NOTE: If the address does not have an exact match in Zillow, the server will bypass the Zillow Data. + parser.parseString(res, function(err, result) { + //Unwrap the response the extract the desired data + if (err) { + console.log('Parse error') + } else { + houseData.summary = result['SearchResults:searchresults']['response'][0]['results'][0]['result'] + houseData.value = houseData.summary[0]['zestimate'][0]['amount'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood = {} + houseData.neighborhood.rid = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['id'] + houseData.neighborhood.name = houseData.summary[0]['localRealEstate'][0]['region'][0]['$']['name'] + neighborhoodOptions.uri = 'http://www.zillow.com/webservice/GetDemographics.htm?zws-id=X1-ZWz1a5itpkflzf_540hi&rid='+houseData.neighborhood.rid+'&state=TX&city=Austin&neighborhood='+houseData.neighborhood.name.split(' ').join('+'); + } + }) + } + return neighborhoodOptions + }) + // Get the neighborhood information for the requested address + .then(function(result){ + if (result.uri===''){ + return 'Zillow Error' + } else { + return requestPromise(result) + } + }) + // Extract the neighborhood data and insert into httpResponseBody + .then(function(res){ + if (res !== 'Zillow Error'){ + parser.parseString(res, function(err, result){ + if (result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood']){ + //Property Taxes for neighborhood and Austin average + houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - //Median Age for neighborhood and Austin average - houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 + //Median House Size for neighborhood and Austin average + houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + + //Median Household Income for neighborhood and Austin average + houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) + houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) + + //Median Age for neighborhood and Austin average + houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number + houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 - //% of households with kids for neighborhood and Austin average - houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 - } - // Attach Zillow data to response - httpResponseBody.zillowData = houseData - }) - } else { - httpResponseBody.zillowData = {} - } - return httpResponseBody + //% of households with kids for neighborhood and Austin average + houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + } + // Attach Zillow data to response + httpResponseBody.zillowData = houseData + }) + } else { + httpResponseBody.zillowData = {} + } + return httpResponseBody + }) + // Send response back to client + .then(function (httpResponseBody){ + var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; + calculateLivability(weights, httpResponseBody, req.body.radius); + res.json(httpResponseBody); + }); }) - // Send response back to client - .then(function (httpResponseBody){ - var weights = req.body.weights || {restaurants: 50, crimes: 50, affordability: 50}; - calculateLivability(weights, httpResponseBody, req.body.radius); - res.json(httpResponseBody); - }); }) }); From 395ca75a94872a52e0cdb424909427a420550655 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 13:58:45 -0500 Subject: [PATCH 32/44] Combined APIKeys and Credentials --- server/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/server/index.js b/server/index.js index a5a677b..9053317 100644 --- a/server/index.js +++ b/server/index.js @@ -11,7 +11,6 @@ var Restaurant = require('./models/restaurant.js'); var httpResponseBody = require('./lib/httpResponseBody.js'); var calculateLivability = require('./lib/calculateLivability.js'); var request = require('request-promise') -var APIKeys = require('./apikeys.js') var shared = ['mithril']; app.get('/js/vendor-bundle.js', browserify(shared)); @@ -35,7 +34,7 @@ app.get('/', function (req, res){ app.post('/distance', function(req, res){ request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ - APIKeys.GoogleDistance, function(error, response, body) { + credentials.GoogleDistance, function(error, response, body) { if (error) throw error; res.send(body) }) @@ -45,10 +44,10 @@ app.post('/distance', function(req, res){ var houseData = {}; var neighborhoodURL = ''; var addressOptionsTemplate = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials.zillow+'&address=*&citystatezip=Austin+TX', method: 'GET' };var addressOptions = { - uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials+'&address=*&citystatezip=Austin+TX', + uri: 'http://www.zillow.com/webservice/GetDeepSearchResults.htm?zws-id='+credentials.zillow+'&address=*&citystatezip=Austin+TX', method: 'GET' }; var neighborhoodOptions = { @@ -91,7 +90,7 @@ app.post('/', function (req, res){ .then(function attachCommuteTime(httpResponseBody) { request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ - APIKeys.GoogleDistance, function(error, response, body) { + credentials.GoogleDistance, function(error, response, body) { if (error) throw error; httpResponseBody.distance = body; }) From 22df8726108f82a60bb9bae4e99a759485226f52 Mon Sep 17 00:00:00 2001 From: MKS-students Date: Thu, 30 Jul 2015 14:22:26 -0500 Subject: [PATCH 33/44] map customisation complete --- client/app/components/mapContainer.js | 123 +++++++++++++++++--------- client/public/sass/mapContainer.scss | 5 +- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 977902c..660e5ee 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -35,55 +35,92 @@ exports.view = function(ctrl, options) { function mapSetup(options, element, isInitialized) { //we zoom in when a user does a search - var adjustZoom = function () { - if (options.location.address()) { - return 16; - } - else { - return 8; + var adjustZoom = function () { + if (options.location.address()) { + return 11; + } + else { + return 8; + } + }; + //notice that the locations object has m.prop setters/getters which are from a virtual model + var lat = options.location.lat() || 30.25; + var lng = options.location.lng() || -97.75; + + console.log(lat, lng); + console.log(isInitialized); + + var mapCenter = new google.maps.LatLng(lat, lng); + var mapOptions = { + center: new google.maps.LatLng(30.2500, -97.7500), + zoom: adjustZoom(), + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + + //Map styling + var map = new google.maps.Map(document.querySelector('.mapContainer'), mapOptions); + + map.set('styles', [ + { + "stylers": [ + { + "hue": "#ff1a00" + }, + { + "invert_lightness": true + }, + { + "saturation": -100 + }, + { + "lightness": 40 + }, + { + "gamma": 0.5 } - }; - //notice that the locations object has m.prop setters/getters which are from a virtual model - var lat = options.location.lat() || 30.25; - var lng = options.location.lng() || -97.75; - - console.log(lat, lng); - console.log(isInitialized); - - var mapCenter = new google.maps.LatLng(lat, lng); - var mapOptions = { - center: new google.maps.LatLng(30.2500, -97.7500), - zoom: adjustZoom(), - mapTypeId: google.maps.MapTypeId.ROADMAP - }; - - var map = new google.maps.Map(document.querySelector('.mapContainer'), mapOptions); - - map.set('styles', [ - {"featureType": "all", - "elementType": "all", - "stylers": [ + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ { - "saturation": -100 - }, + "color": "#435359" + } + ] + }, + { + "featureType": "landscape", + "stylers": [ { - "gamma": 0.5 + "color": "#2A373C" }] - } - ]); + } + ]); + + //var iconImg = '../img/icon.png'; + + var myLatLng = new google.maps.LatLng(30.2500, -97.7500); + + var marker = new google.maps.Marker({ + //position: mapCenter, + position: myLatLng, + map: map, + icon: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png', + animation: google.maps.Animation.DROP, + title: options.location.address() || '' + }); - //var iconImg = '../img/icon.png'; + marker.setMap(map); + google.maps.event.addListener(marker, 'click', toggleBounce); - var myLatLng = new google.maps.LatLng(30.2500, -97.7500); + function toggleBounce() { - var marker = new google.maps.Marker({ - //position: mapCenter, - position: myLatLng, - map: map, - icon: '/public/img//house2.png', - // icon: iconImg, - title: options.location.address() || '' - }); + if (marker.getAnimation() != null) { + marker.setAnimation(null); + } else { + marker.setAnimation(google.maps.Animation.BOUNCE); + } + } - marker.setMap(map); } diff --git a/client/public/sass/mapContainer.scss b/client/public/sass/mapContainer.scss index a0861b5..97a3123 100644 --- a/client/public/sass/mapContainer.scss +++ b/client/public/sass/mapContainer.scss @@ -3,6 +3,9 @@ width: 50%; padding: 100px; margin: 0 auto; + border-style: solid; + border-width: 2px; + border-color: white; } .logout-btn { @@ -19,4 +22,4 @@ .navbar-login { margin-right: 6px; -} \ No newline at end of file +} From bca25bb33e549dd11bd29dbd2b2e5968731cd055 Mon Sep 17 00:00:00 2001 From: jmcgui05 Date: Wed, 29 Jul 2015 19:44:28 -0500 Subject: [PATCH 34/44] map configured to match color scheme and custom marker --- client/app/components/mapContainer.js | 26 ++++++++++++++++++++++---- client/public/img/house2.png | Bin 0 -> 839 bytes 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 client/public/img/house2.png diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 293b925..ab576cf 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -52,17 +52,35 @@ function mapSetup(options, element, isInitialized) { var mapCenter = new google.maps.LatLng(lat, lng); var mapOptions = { - center: mapCenter, - zoom: adjustZoom() + center: new google.maps.LatLng(30.2500, -97.7500), + zoom: adjustZoom(), + mapTypeId: google.maps.MapTypeId.ROADMAP }; var map = new google.maps.Map(document.querySelector('.mapContainer'), mapOptions); - var iconImg = '../img/icon.png'; + map.set('styles', [ + {"featureType": "all", + "elementType": "all", + "stylers": [ + { + "saturation": -100 + }, + { + "gamma": 0.5 + }] + } + ]); + + //var iconImg = '../img/icon.png'; + + var myLatLng = new google.maps.LatLng(30.2500, -97.7500); var marker = new google.maps.Marker({ - position: mapCenter, + //position: mapCenter, + position: myLatLng, map: map, + icon: '/public/img//house2.png', // icon: iconImg, title: options.location.address() || '' }); diff --git a/client/public/img/house2.png b/client/public/img/house2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4e1e6459ab13cc16c5675cac049efb582a8bbf GIT binary patch literal 839 zcmV-N1GxN&P);M`B_5AU@c_^-MjzeD zKp8j${)n+6SLqYt5syE--C^Mqc6?O4A*MP|wWdDc@rSo(Qd3;MbdkZqfsP6n3J2`& z?UTvowvUgBR}kA^Ej%C46>-2GQ`VU(t6h3m#OpwqnzG6}Rqbv}Jul(Y6z$sb%a!v3 zkm6^BXbZqDdKL)R0Iyfx)cw6(2*&`)=~+H*ex|p#7fsVp6cx3n$8%El9S^n-gg4g62Km0V{^-#Ux-E-8BGAt*B1of zaq3Ca?_B}dKUNhLkJrO|dJ%w}NcqTUf@mbde0mX&*Mq94P2)HMXe1sEg^0xl%#}-9 z%Do~M8z3ACxq@i;pTE4cKyrGP<)sAxQZr9^o}L3ZB`)plyZ3B-ST)~&kx854GLGFG zp8`d!sFAod`CL()g?4=FF$|q&b7>NH@36YIZmx7>G*NeM7`mfV&=kO$wrpnJoUg8} zw+w83zij}g)@oZi4j|KPpSNTG&3!;W2W?#fR4q=SaL_eHOKek94%%rBceQU^(*w=60$z*eNpNYogH&6sh8nq0RskKXproq~p zsQ{U5?g!8kLW&o~4WPRCNvofGNX->^MyvHBjX_g6#_;pU!R^#ThMSCtsYy0-I z9)v~wnpmaWXDdMc_Pm)1Fdwx#2?~f`RtSsuRUuvyUY0_QtR+ALh@i{_U|o3$7vhcq z>Ok}hh#f%gd5KzaITm*XV1oz<_lidqYN Date: Thu, 30 Jul 2015 14:53:09 -0500 Subject: [PATCH 35/44] Remove console.log --- server/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/index.js b/server/index.js index 9053317..ed3cc00 100644 --- a/server/index.js +++ b/server/index.js @@ -98,7 +98,6 @@ app.post('/', function (req, res){ // Make Zillow API call to get address value and neighborhood demographic info // Then send httpResponseBody back to client .then(function (httpResponseBody) { - console.log('begin zillow calls:', httpResponseBody) requestPromise(addressOptions) // Get the basic info for the requested address .then(function(res){ From 78f8112fea9615722cb2132ece6480530e5b7f6e Mon Sep 17 00:00:00 2001 From: MKS-students Date: Thu, 30 Jul 2015 19:52:14 -0500 Subject: [PATCH 36/44] Basic alerts for blank home/works address fields --- client/app/components/mapContainer.js | 15 +++++++-------- client/app/components/searchBox.js | 8 +++++++- client/public/index.html | 4 ++-- client/public/style.css | 5 ++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index b6e5112..58da096 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -98,20 +98,19 @@ function mapSetup(options, element, isInitialized) { } ]); - var mapCenter = new google.maps.LatLng(lat, lng); - var mapOptions = { - center: new google.maps.LatLng(30.2500, -97.7500), - zoom: adjustZoom(), - mapTypeId: google.maps.MapTypeId.ROADMAP - }; + var mapCenter = new google.maps.LatLng(lat, lng); + var mapOptions = { + center: new google.maps.LatLng(30.2500, -97.7500), + zoom: adjustZoom(), + mapTypeId: google.maps.MapTypeId.ROADMAP + }; var myLatLng = new google.maps.LatLng(lat, lng); var marker = new google.maps.Marker({ //position: mapCenter, position: myLatLng, map: map, - icon: '/public/img//house2.png', - // icon: iconImg, + icon: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png', title: options.location.address() || '' }); diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 56a5560..e95dee9 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -1,6 +1,7 @@ var m = require('mithril'); var Location = require('../models/Location'); var Relocalc = require('../index'); +var swal = require('sweetAlert'); exports.controller = function (options) { ctrl = this; @@ -15,6 +16,11 @@ exports.controller = function (options) { ctrl.fetchGeoCode = function() { var address = options.location.address(); var workAddress = options.location.workAddress(); + if(!address || !workAddress){ + console.log("null val"); + swal("Hey you!", "Don't forget to enter both address fields!", "error"); + } else {console.log("address: ", address);} + return Location.postToFetchRestaurantData(address, workAddress, function cb(res) { //set values on vm options.location.lng(res.lng); @@ -60,7 +66,7 @@ exports.view = function (ctrl, options) { ,{min: 0, max: 100, step: 1, value: Location.restWeight(), onchange: m.withAttr('value', Location.restWeight)} )] )] - )], + )], [m('.col-sm-6', [m('h4', 'Commute Time: ' + Location.commuteWeight())], [m('.slider', diff --git a/client/public/index.html b/client/public/index.html index 5c822e6..3850fa6 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -7,6 +7,7 @@ + @@ -23,6 +24,5 @@ - - \ No newline at end of file + diff --git a/client/public/style.css b/client/public/style.css index ff74105..1e99838 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -79,7 +79,10 @@ h3, h4 { height: 400px; width: 50%; padding: 100px; - margin: 0 auto; } + margin: 0 auto; + border-style: solid; + border-width: 2px; + border-color: white; } .logout-btn { margin-top: 8px; From 929e84411019182c7c2724cc26209cd319606868 Mon Sep 17 00:00:00 2001 From: MKS-students Date: Thu, 30 Jul 2015 19:56:05 -0500 Subject: [PATCH 37/44] removed a console log --- client/app/components/searchBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index e95dee9..1d90032 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -17,7 +17,7 @@ exports.controller = function (options) { var address = options.location.address(); var workAddress = options.location.workAddress(); if(!address || !workAddress){ - console.log("null val"); + //console.log("null val"); swal("Hey you!", "Don't forget to enter both address fields!", "error"); } else {console.log("address: ", address);} From d34428e435ae8d216c7f9ee732b71cb83c95e41b Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 15:28:28 -0500 Subject: [PATCH 38/44] Work Marker on Google Maps --- client/app/components/mapContainer.js | 9 +++- client/app/components/searchBox.js | 2 + client/app/index.js | 2 +- client/app/models/Location.js | 72 ++++++++++++++++----------- 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 58da096..9f93d76 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -97,7 +97,8 @@ function mapSetup(options, element, isInitialized) { }] } ]); - + var workLat = options.location.workLat(); + var workLng = options.location.workLng(); var mapCenter = new google.maps.LatLng(lat, lng); var mapOptions = { center: new google.maps.LatLng(30.2500, -97.7500), @@ -121,6 +122,12 @@ function mapSetup(options, element, isInitialized) { function toggleBounce() { + var workMarker = new google.maps.Marker({ + position: new google.maps.LatLng(workLat, workLng), + map: map, + // icon: iconImg, + title: options.location.workAddress() || '' + }); if (marker.getAnimation() != null) { marker.setAnimation(null); } else { diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 1d90032..743d7cf 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -25,6 +25,8 @@ exports.controller = function (options) { //set values on vm options.location.lng(res.lng); options.location.lat(res.lat); + options.location.workLng(res.workLng) + options.location.workLat(res.workLat) //IMPORTANT: force a re-render so the graphs display with the new values! if (res !== null) { m.redraw(); diff --git a/client/app/index.js b/client/app/index.js index 3d6528e..c1d2351 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -44,4 +44,4 @@ m.route(document.getElementById('app'), "/", { "/signup": Signup, "/about": About, "/searches/:userID": SearchList -}); +}); \ No newline at end of file diff --git a/client/app/models/Location.js b/client/app/models/Location.js index bdc89a7..aed77f1 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -36,10 +36,13 @@ var Locations = module.exports = { lat: m.prop(''), lng: m.prop(''), + workLat: m.prop(''), + workLng: m.prop(''), crimeWeight: m.prop(''), restWeight: m.prop(''), commuteWeight: m.prop(''), address: m.prop(''), + workAddress: m.prop(''), commuteTime: m.prop(''), costWeight: m.prop(''), zillowIncomeNeighborhood: m.prop(0), @@ -48,34 +51,43 @@ var Locations = module.exports = { postToFetchRestaurantData: function(address, workAddress, callback) { Locations.address(address); var callback = callback; - this.postToFetchGeoCode(address, function (res) { - Locations.lat(res.results[0].geometry.location.lat); - Locations.lng(res.results[0].geometry.location.lng); - var locationData = { - "address": address, - "workAddress" : workAddress, - "lat": res.results[0].geometry.location.lat, - "lng": res.results[0].geometry.location.lng, - "radius": 1, - "weights": { - "crimes": Locations.crimeWeight() || 50, - "restaurants": Locations.restWeight() || 50, - "commute" : workAddress !== '' ? Locations.commuteWeight() || 50 : 0, - "affordability": Locations.costWeight() || 50, - } - }; - return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) - .then(function(res) { - var data = modelData(res); - if (data !== null) { - Locations.search(data); - Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); - Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); + Locations.postToFetchGeoCode(workAddress, function (res) { + if (res.status === 'OK'){ + Locations.workLat(res.results[0].geometry.location.lat); + Locations.workLng(res.results[0].geometry.location.lng); + } + console.log('work coord' + Locations.workLat(), Locations.workLng()) + Locations.postToFetchGeoCode(address, function (res) { + Locations.lat(res.results[0].geometry.location.lat); + Locations.lng(res.results[0].geometry.location.lng); + var locationData = { + "address": address, + "workAddress" : workAddress, + "lat": res.results[0].geometry.location.lat, + "lng": res.results[0].geometry.location.lng, + "workLat": Locations.workLat(), + "workLng": Locations.workLng(), + "radius": 1, + "weights": { + "crimes": Locations.crimeWeight() || 50, + "restaurants": Locations.restWeight() || 50, + "commute" : workAddress !== '' ? Locations.commuteWeight() || 50 : 0, + "affordability": Locations.costWeight() || 50, } - Locations.saveSearch(Locations.address(), res.livibility); - return callback(data); - }) - }); + }; + return m.request({method: "POST", url: "", 'Content-Type': 'application/json', data: locationData}) + .then(function(res) { + var data = modelData(res); + if (data !== null) { + Locations.search(data); + Locations.zillowIncomeNeighborhood(data.zillow.neighborhood.medianIncomeNeighborhood); + Locations.zillowIncomeCity(data.zillow.neighborhood.medianIncomeCity); + } + Locations.saveSearch(Locations.address(), res.livibility); + return callback(data); + }) + }); + }) }, saveSearch: function(address, livabilityScore){ @@ -103,9 +115,10 @@ var Locations = module.exports = { lat: m.prop(''), lng: m.prop(''), workAddress: m.prop(''), + workLat: m.prop(''), + workLng: m.prop('') } } - }; /** @@ -132,12 +145,13 @@ var modelData = function(data) { }, 0); var avg = sum / inspectCount; - var response = { crimes: data.crimes.length, restaurants: data.restaurants.length, lat: Locations.lat(), lng: Locations.lng(), + workLat: Locations.workLat(), + workLng: Locations.workLng(), restAvg: data.searchInspecAvg, crimeAvg: data.searchCrimesPerSqMi, livability: data.livibility, From f99b43a1488a9ec3fa636a3fdf0c9e4157784b89 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 15:53:41 -0500 Subject: [PATCH 39/44] Merged with Google Map Updates --- client/app/components/mapContainer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 9f93d76..0ba58c2 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -108,13 +108,18 @@ function mapSetup(options, element, isInitialized) { var myLatLng = new google.maps.LatLng(lat, lng); var marker = new google.maps.Marker({ - //position: mapCenter, position: myLatLng, map: map, - icon: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png', + // icon: '/public/img//house2.png', title: options.location.address() || '' }); + var workMarker = new google.maps.Marker({ + position: new google.maps.LatLng(workLat, workLng), + map: map, + // icon: iconImg, + title: options.location.workAddress() || '' + }); marker.setMap(map); google.maps.event.addListener(marker, 'click', toggleBounce); From afb5a7e3e11a4dab6454a51374c691c903368ed0 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 16:26:34 -0500 Subject: [PATCH 40/44] Map API --- client/app/components/mapContainer.js | 5 +++-- client/public/img/apartment-3.png | Bin 0 -> 835 bytes client/public/img/office-building.png | Bin 0 -> 686 bytes 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 client/public/img/apartment-3.png create mode 100644 client/public/img/office-building.png diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 0ba58c2..0456a96 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -1,5 +1,6 @@ var m = require('mithril'); var Location = require('../models/location'); +var path = require('path') //var loaderView = require('./loader').loader; /** @@ -110,14 +111,14 @@ function mapSetup(options, element, isInitialized) { var marker = new google.maps.Marker({ position: myLatLng, map: map, - // icon: '/public/img//house2.png', + // icon: 'client/public/img//house2.png', title: options.location.address() || '' }); var workMarker = new google.maps.Marker({ position: new google.maps.LatLng(workLat, workLng), map: map, - // icon: iconImg, + // icon: path.join(__dirname+'/../../public/img/office-building.png'), title: options.location.workAddress() || '' }); diff --git a/client/public/img/apartment-3.png b/client/public/img/apartment-3.png new file mode 100644 index 0000000000000000000000000000000000000000..a4470733fc14aee1a1f5c7603d681aceec0a8c72 GIT binary patch literal 835 zcmV-J1HAl+P)N_cZQ?i}*I>6`22a!D_J;N|^wx$ozFp64$2L>$KfV0Cph21J3l z>|=y$7GMJ=U>A!;3n1b+juMXphHP;O5DiJ(mH;LoWHXD!q7_+LSyAE%U|RMDTvj83do>nnbEc-S+apP#=BL|0Q~xo@@$AxNcCR10fV3u~lO zDTEM11Gp@zNPJBQL9JF}>F#x2fA|ByotqKL2Q|{^^rge)3m}BRG)m=*2U=~LTeAl3cAN4+ZKQk%1OV{q)kEL?+4Y@~#UbFU z*0k)dkUkE>!g|+_7a$&sQZ1}q7>8j)|KWJK-~E!(ZBT*+2AEyn>7G2O2ZH3v$T_gN zwa@0(zUTP)+uwoj6BxjK>h0Ylws(&_$AxdzzyKqEbm&0+#z8Ew2i)vSEAHOz2^8nO=jt4BuQj1;980Y1Z0B{_KiHQl4$z(_X z9TP&(ZnwKrscFYF@n$xXUn*6+3zJMH)8kG*tJmvPDizN?S(r4@>H=F%PUQs)^p<8( zfKny53k-x%;@L?4v0PI5 z;)}YAT>#$Yxt~ce1DKL6QF3y>bO=rSz7#)`O(>y$HV~i>#FX*`pr8DVA;bd%ctA`` zh&@2X{ESX=1r{F)zy*cX8F3r~&~CTOKnbYGSf*H801*%Z zk?Xn(03(iLC0+qc=@ki3Do8w%03jfxhpy``j8?0q#4X@NMw8pgfB*vEr;MR`N>hs0 zyx8d#EWBn-quY=_BVeJY-tuCnXWKTGWnr3Tp~6B4f*`=_JH4jSeT1Bb5;uis0(*;3 zr_;5#X{KpnS=Q=MYL-;BE8qLcec$Ir;~u{6ZylrEd!?xgZELj;27}BHub$uM-N&!L z{ij~9Ck-ztl2rTgp_3V6bL`p0)z0xnt@FL@-vUtS%o+;^xa^$giiHEbdHFC`EF9o& z3ht)hznp@Dc0GEk^L#IKJNQ++T(R;%6Gwp?aFNX@9Mi3d%i+wl6yjfrjBRIAlYVw1^)e!ri1O*SThf>0R{ zEZZey)1bFzB>=DQd;(G-lz7wVK9i?ZeQma80*JCSr>i}@7dzLxGNFk-WxckxH>WA> zxr%!G_Bjbc6Mv$ocmp80J>O*#%mj|5TVCvZ(IGVP$5MPIT_~Y#mI|;1;#g$@(4Txp z4)M$Y2@od|;u=tMK4V#2nZ Date: Thu, 30 Jul 2015 16:45:01 -0500 Subject: [PATCH 41/44] Added Map Icons --- client/app/components/mapContainer.js | 4 ++-- server/index.js | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 0456a96..9cf6325 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -111,14 +111,14 @@ function mapSetup(options, element, isInitialized) { var marker = new google.maps.Marker({ position: myLatLng, map: map, - // icon: 'client/public/img//house2.png', + icon: '/client/img/house', title: options.location.address() || '' }); var workMarker = new google.maps.Marker({ position: new google.maps.LatLng(workLat, workLng), map: map, - // icon: path.join(__dirname+'/../../public/img/office-building.png'), + icon: '/client/img/office-building', title: options.location.workAddress() || '' }); diff --git a/server/index.js b/server/index.js index ed3cc00..faf7c13 100644 --- a/server/index.js +++ b/server/index.js @@ -11,6 +11,7 @@ var Restaurant = require('./models/restaurant.js'); var httpResponseBody = require('./lib/httpResponseBody.js'); var calculateLivability = require('./lib/calculateLivability.js'); var request = require('request-promise') +var path = require('path') var shared = ['mithril']; app.get('/js/vendor-bundle.js', browserify(shared)); @@ -31,6 +32,15 @@ app.get('/', function (req, res){ //app.get('/crimes', function (req, res){}); +app.get('/client/img/office-building', function(req, res) { + res.sendFile(path.join(__dirname+'/../client/public/img/office-building.png')) +}) + +app.get('/client/img/house', function(req, res) { + res.sendFile(path.join(__dirname+'/../client/public/img/apartment-3.png')) +}) + + app.post('/distance', function(req, res){ request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ From 07a17b407d1c53775030b7d28f18f3949e853011 Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Thu, 30 Jul 2015 20:16:07 -0500 Subject: [PATCH 42/44] Map displays route --- client/app/components/graphContainer.js | 67 +++++++++++++++++--- client/app/components/mapContainer.js | 83 ++++++++++++++++--------- server/index.js | 2 + 3 files changed, 114 insertions(+), 38 deletions(-) diff --git a/client/app/components/graphContainer.js b/client/app/components/graphContainer.js index 0e9710a..c2b7462 100644 --- a/client/app/components/graphContainer.js +++ b/client/app/components/graphContainer.js @@ -12,8 +12,19 @@ exports.controller = function(options) { //Initialize crime chart $(function () { + var userData = Math.floor(Location.search().crimeAvg) + var averageData = Math.floor(Location.search().cityCrimeAvg) + var colors; + if (userData > averageData*1.2) { + colors = ['#DF5353', '#434348'] + } else if (userData < averageData*0.8) { + colors = ['#55BF3B', '#434348'] + } else { + colors = ['#DDDF0D', '#434348'] + } + $('.crimeGraph').highcharts({ - colors: ['#7A878B', '#434348'], + colors: colors, chart: { type: 'column', spacing: 50, @@ -54,9 +65,20 @@ exports.controller = function(options) { ctrl.initRestSafety = function (element, isInit, context) { //Initialize restaurant safety chart - $(function () { + $(function () { + var userData = Math.floor(Location.search().restAvg) + var averageData = Math.floor(Location.search().cityRestAvg) + var colors; + if (userData > averageData*1.2) { + colors = ['#DF5353', '#434348'] + } else if (userData < averageData*0.8) { + colors = ['#55BF3B', '#434348'] + } else { + colors = ['#DDDF0D', '#434348'] + } + $('.restaurantSafety').highcharts({ - colors: ['#7A878B', '#434348'], + colors: colors, chart: { type: 'column', spacing: 50 @@ -97,8 +119,19 @@ exports.controller = function(options) { //Initialize number of restaurants chart $(function () { + var userData = Math.floor(Location.search().restaurants) + var averageData = Math.floor(Location.search().restaurants ? [51] : [0]) + var colors; + if (userData < averageData*0.8) { + colors = ['#DF5353', '#434348'] + } else if (userData > averageData*1.2) { + colors = ['#55BF3B', '#434348'] + } else { + colors = ['#DDDF0D', '#434348'] + } + $('.restaurantNumber').highcharts({ - colors: ['#7A878B', '#434348'], + colors: colors, chart: { type: 'column', spacing: 50 @@ -139,8 +172,18 @@ exports.controller = function(options) { //Initialize restaurant safety chart $(function () { + var userData = Math.floor(Location.commuteTime()) + var averageData = Math.floor(24.6) + var colors; + if (userData > averageData*1.2) { + colors = ['#DF5353', '#434348'] + } else if (userData < averageData*0.8) { + colors = ['#55BF3B', '#434348'] + } else { + colors = ['#DDDF0D', '#434348'] + } $('.commuteTime').highcharts({ - colors: ['#7A878B', '#434348'], + colors: colors, chart: { type: 'column', spacing: 50 @@ -180,9 +223,19 @@ exports.controller = function(options) { ctrl.initCostCompare = function (element, isInit, context) { //Initialize number of restaurants chart - $(function () { + $(function () { + var userData = Location.zillowIncomeNeighborhood() + var averageData = Location.zillowIncomeCity() + var colors; + if (userData > averageData*1.2) { + colors = ['#DF5353', '#434348'] + } else if (userData < averageData*0.8) { + colors = ['#55BF3B', '#434348'] + } else { + colors = ['#DDDF0D', '#434348'] + } $('.costCompare').highcharts({ - colors: ['#7A878B', '#434348'], + colors: colors, chart: { type: 'column', spacing: 50 diff --git a/client/app/components/mapContainer.js b/client/app/components/mapContainer.js index 9cf6325..8b4e12a 100644 --- a/client/app/components/mapContainer.js +++ b/client/app/components/mapContainer.js @@ -58,9 +58,59 @@ function mapSetup(options, element, isInitialized) { mapTypeId: google.maps.MapTypeId.ROADMAP }; - //Map styling + var workLat = options.location.workLat() || 30.25;; + var workLng = options.location.workLng() || -97.75; + var mapCenter = new google.maps.LatLng(lat, lng); + var mapOptions = { + center: new google.maps.LatLng(30.2500, -97.7500), + zoom: adjustZoom(), + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + + var myLatLng = new google.maps.LatLng(lat, lng); + var workLatLng = new google.maps.LatLng(workLat, workLng) + + var marker = new google.maps.Marker({ + position: myLatLng, + map: map, + icon: '/client/img/house', + title: options.location.address() || '' + }); + + var workMarker = new google.maps.Marker({ + position: workLatLng, + map: map, + icon: '/client/img/office-building', + title: options.location.workAddress() || '' + }); + var map = new google.maps.Map(document.querySelector('.mapContainer'), mapOptions); + //Adds markers to map + marker.setMap(map); + workMarker.setMap(map); + + var directionsDisplay = new google.maps.DirectionsRenderer({ + suppressMarkers: true, + draggable: true + }); + var directionsService = new google.maps.DirectionsService() + var request = { + origin: myLatLng, + destination: workLatLng, + travelMode: google.maps.TravelMode.DRIVING, + avoidTolls: true + }; + directionsService.route(request, function(res, status){ + if (status == google.maps.DirectionsStatus.OK) { + directionsDisplay.setMap(map); + directionsDisplay.setDirections(res); + } + }); + + + //Map styling + map.set('styles', [ { "stylers": [ @@ -100,45 +150,16 @@ function mapSetup(options, element, isInitialized) { ]); var workLat = options.location.workLat(); var workLng = options.location.workLng(); - var mapCenter = new google.maps.LatLng(lat, lng); - var mapOptions = { - center: new google.maps.LatLng(30.2500, -97.7500), - zoom: adjustZoom(), - mapTypeId: google.maps.MapTypeId.ROADMAP - }; - var myLatLng = new google.maps.LatLng(lat, lng); - var marker = new google.maps.Marker({ - position: myLatLng, - map: map, - icon: '/client/img/house', - title: options.location.address() || '' - }); - - var workMarker = new google.maps.Marker({ - position: new google.maps.LatLng(workLat, workLng), - map: map, - icon: '/client/img/office-building', - title: options.location.workAddress() || '' - }); - - marker.setMap(map); google.maps.event.addListener(marker, 'click', toggleBounce); function toggleBounce() { - var workMarker = new google.maps.Marker({ - position: new google.maps.LatLng(workLat, workLng), - map: map, - // icon: iconImg, - title: options.location.workAddress() || '' - }); if (marker.getAnimation() != null) { marker.setAnimation(null); } else { marker.setAnimation(google.maps.Animation.BOUNCE); } } - -} +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index faf7c13..ef88e9f 100644 --- a/server/index.js +++ b/server/index.js @@ -50,6 +50,8 @@ app.post('/distance', function(req, res){ }) }) +app.post + //Init objects for use in Zillow API calls. var houseData = {}; var neighborhoodURL = ''; From 2afa2935f69c45117259429739f9447d6b76482e Mon Sep 17 00:00:00 2001 From: Colin Wiley Date: Fri, 31 Jul 2015 11:22:14 -0500 Subject: [PATCH 43/44] Merged CommuteMap with Master --- client/app/components/searchBox.js | 2 +- npm-debug.log | 113 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 npm-debug.log diff --git a/client/app/components/searchBox.js b/client/app/components/searchBox.js index 743d7cf..455dae8 100644 --- a/client/app/components/searchBox.js +++ b/client/app/components/searchBox.js @@ -1,7 +1,7 @@ var m = require('mithril'); var Location = require('../models/Location'); var Relocalc = require('../index'); -var swal = require('sweetAlert'); +// var swal = require('sweetAlert'); exports.controller = function (options) { ctrl = this; diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..3ad5554 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,113 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'node', +1 verbose cli '/Users/colinwiley/.node/bin/npm', +1 verbose cli 'install', +1 verbose cli '--save', +1 verbose cli 'sweetAlert' ] +2 info using npm@2.11.3 +3 info using node@v0.12.5 +4 verbose install initial load of /Users/colinwiley/Documents/Relocalc/package.json +5 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/bluebird/package.json +6 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/body-parser/package.json +7 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/browserify/package.json +8 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/browserify-middleware/package.json +9 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/express/package.json +10 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/geolib/package.json +11 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp/package.json +12 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-concat/package.json +13 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-jshint/package.json +14 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-nodemon/package.json +15 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-sass/package.json +16 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-uglify/package.json +17 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-util/package.json +18 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/gulp-watch/package.json +19 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/jshint-stylish/package.json +20 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/knex/package.json +21 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/mithril/package.json +22 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/pg/package.json +23 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/request/package.json +24 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/request-promise/package.json +25 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/underscore/package.json +26 verbose installManyTop reading scoped package data from /Users/colinwiley/Documents/Relocalc/node_modules/xml2js/package.json +27 info package.json geolib@2.0.17 No license field. +28 info package.json mithril@0.2.0 No license field. +29 verbose readDependencies loading dependencies from /Users/colinwiley/Documents/Relocalc/package.json +30 silly cache add args [ 'sweetAlert', null ] +31 verbose cache add spec sweetAlert +32 silly cache add parsed spec { raw: 'sweetAlert', +32 silly cache add scope: null, +32 silly cache add name: 'sweetAlert', +32 silly cache add rawSpec: '', +32 silly cache add spec: '*', +32 silly cache add type: 'range' } +33 silly addNamed sweetAlert@* +34 verbose addNamed "*" is a valid semver range for sweetAlert +35 silly addNameRange { name: 'sweetAlert', range: '*', hasData: false } +36 silly mapToRegistry name sweetAlert +37 silly mapToRegistry using default registry +38 silly mapToRegistry registry https://registry.npmjs.org/ +39 silly mapToRegistry uri https://registry.npmjs.org/sweetAlert +40 verbose addNameRange registry:https://registry.npmjs.org/sweetAlert not in flight; fetching +41 verbose request uri https://registry.npmjs.org/sweetAlert +42 verbose request no auth needed +43 info attempt registry request try #1 at 11:19:39 AM +44 verbose request id 2f0c7cfc773e4791 +45 http request GET https://registry.npmjs.org/sweetAlert +46 http 404 https://registry.npmjs.org/sweetAlert +47 verbose headers { server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', +47 verbose headers 'content-type': 'application/json', +47 verbose headers 'cache-control': 'max-age=0', +47 verbose headers 'content-length': '51', +47 verbose headers 'accept-ranges': 'bytes', +47 verbose headers date: 'Fri, 31 Jul 2015 16:19:39 GMT', +47 verbose headers via: '1.1 varnish', +47 verbose headers age: '0', +47 verbose headers connection: 'keep-alive', +47 verbose headers 'x-served-by': 'cache-ord1725-ORD', +47 verbose headers 'x-cache': 'MISS', +47 verbose headers 'x-cache-hits': '0', +47 verbose headers 'x-timer': 'S1438359579.835302,VS0,VE149' } +48 silly get cb [ 404, +48 silly get { server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', +48 silly get 'content-type': 'application/json', +48 silly get 'cache-control': 'max-age=0', +48 silly get 'content-length': '51', +48 silly get 'accept-ranges': 'bytes', +48 silly get date: 'Fri, 31 Jul 2015 16:19:39 GMT', +48 silly get via: '1.1 varnish', +48 silly get age: '0', +48 silly get connection: 'keep-alive', +48 silly get 'x-served-by': 'cache-ord1725-ORD', +48 silly get 'x-cache': 'MISS', +48 silly get 'x-cache-hits': '0', +48 silly get 'x-timer': 'S1438359579.835302,VS0,VE149' } ] +49 verbose stack Error: 404 Not Found: sweetAlert +49 verbose stack at CachingRegistryClient. (/Users/colinwiley/.node/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:245:14) +49 verbose stack at Request._callback (/Users/colinwiley/.node/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:170:14) +49 verbose stack at Request.self.callback (/Users/colinwiley/.node/lib/node_modules/npm/node_modules/request/request.js:354:22) +49 verbose stack at Request.emit (events.js:110:17) +49 verbose stack at Request. (/Users/colinwiley/.node/lib/node_modules/npm/node_modules/request/request.js:1207:14) +49 verbose stack at Request.emit (events.js:129:20) +49 verbose stack at IncomingMessage. (/Users/colinwiley/.node/lib/node_modules/npm/node_modules/request/request.js:1153:12) +49 verbose stack at IncomingMessage.emit (events.js:129:20) +49 verbose stack at _stream_readable.js:908:16 +49 verbose stack at process._tickCallback (node.js:355:11) +50 verbose statusCode 404 +51 verbose pkgid sweetAlert +52 verbose cwd /Users/colinwiley/Documents/Relocalc +53 error Darwin 14.4.0 +54 error argv "node" "/Users/colinwiley/.node/bin/npm" "install" "--save" "sweetAlert" +55 error node v0.12.5 +56 error npm v2.11.3 +57 error code E404 +58 error 404 Not Found: sweetAlert +58 error 404 +58 error 404 'sweetAlert' is not in the npm registry. +58 error 404 Your package name is not valid, because +58 error 404 +58 error 404 1. name can no longer contain capital letters +58 error 404 It was specified as a dependency of 'relocalc' +58 error 404 +58 error 404 Note that you can also install from a +58 error 404 tarball, folder, http url, or git url. +59 verbose exit [ 1, true ] From 25839b6895209bfce55baff22059a14ed83e3e1f Mon Sep 17 00:00:00 2001 From: Zachary Lee Date: Thu, 30 Jul 2015 20:35:12 -0500 Subject: [PATCH 44/44] FbOAuth fully up and running! Views Rendering correctly. --- client/app/components/signinBox.js | 13 ++++--------- client/app/components/signup.js | 1 - client/app/models/Auth.js | 21 +++++++++++---------- client/app/models/Location.js | 6 +++--- client/app/models/Searches.js | 4 ++-- client/public/sass/mapContainer.scss | 2 +- client/public/style.css | 2 +- firebase.json | 4 +--- package.json | 1 + server/index.js | 21 ++++++++++----------- 10 files changed, 34 insertions(+), 41 deletions(-) diff --git a/client/app/components/signinBox.js b/client/app/components/signinBox.js index 8955d75..90be1ff 100644 --- a/client/app/components/signinBox.js +++ b/client/app/components/signinBox.js @@ -9,7 +9,7 @@ header.controller = function (options) { ctrl.loginUser = function(e) { e.preventDefault(); - Auth.signIn( e.target.email.value, e.target.password.value , function( user, error, uid ) { + Auth.signIn(function( user, error, uid ) { if (user) { toastr["success"]("You are logged in!"); //console.log("User: " + uid); @@ -61,13 +61,8 @@ function renderLoggedOutView(ctrl){ ]) ]), //end div wrapping ul m('form.navbar-form.navbar-right', {onsubmit: ctrl.loginUser}, - [m('form-group', - [m('input.form-control.navbar-login[name="email"][autofocus][id="inputEmail"][placeholder="Email"][required][type="email"]') - ]), - m('form-group', - [m('input.form-control.navbar-login[name="password"][autocomplete="off"][id="inputPassword"][placeholder="Password"][required][type="password"]') - ]), - m('input.btn.btn-default.navbar-login.login-btn[type="submit"][value="Log in"]') + [ + m('input.btn.btn-primary.navbar-login.login-btn[type="submit"][value="Log in with Facebook"]') ]) //end of form ]) //end container }; @@ -88,4 +83,4 @@ var renderLoggedInView = function(ctrl){ m('button.btn.btn-default.logout-btn', {onclick: ctrl.logout}, "Log out")]), ]) ]) //end container -}; \ No newline at end of file +}; diff --git a/client/app/components/signup.js b/client/app/components/signup.js index 8e53c8a..870b30e 100644 --- a/client/app/components/signup.js +++ b/client/app/components/signup.js @@ -100,4 +100,3 @@ function errorView(ctrl) { ]) : null ] } - diff --git a/client/app/models/Auth.js b/client/app/models/Auth.js index 98265de..39bb0d6 100644 --- a/client/app/models/Auth.js +++ b/client/app/models/Auth.js @@ -1,6 +1,6 @@ var m = require('mithril'); var host = "localhost:4000"; -var fb = 'https://craply.firebaseio.com/'; +var fb = 'https://livability.firebaseio.com/'; var ref = new Firebase(fb); /** @@ -18,8 +18,10 @@ window.checkUser = function() { // onAuth is a Firebase method that triggers if there is a change in the status of the authentication status + + ref.onAuth(function(authData) { - if(authData) { + if (authData) { console.log("Authentication state update (+)" + authData); return LoggedIn.userID = authData.uid; } @@ -42,10 +44,10 @@ var Auth = module.exports = { * @returns {userData} */ - createUserAndLogin: function(email, password, cb){ + createUserAndLogin: function(email, password, cb) { return ref.createUser({ - email : email, - password : password + email: email, + password: password }, function(error, userData) { if (error) { //Toastr is a nice flash messaging plugin @@ -72,12 +74,9 @@ var Auth = module.exports = { * @returns {userID} */ - signIn: function(email, password, cb){ + signIn: function(cb) { var loggedIn = null; - return ref.authWithPassword({ - 'email' : email, - 'password' : password - }, function(error, authData) { + return ref.authWithOAuthPopup("facebook", function(error, authData) { if (error) { console.log("Login Failed!", error); cb(null, error, null); @@ -86,6 +85,8 @@ var Auth = module.exports = { LoggedIn.userID = authData.uid; cb(LoggedIn.userID, error, authData.uid); } + }, { + remember: "sessionOnly" }); }, diff --git a/client/app/models/Location.js b/client/app/models/Location.js index aed77f1..84faf95 100644 --- a/client/app/models/Location.js +++ b/client/app/models/Location.js @@ -1,6 +1,6 @@ var m = require('mithril'); var Auth = require('./Auth'); -var fbUrl = 'https://craply.firebaseio.com/'; +var fbUrl = 'https://livability.firebaseio.com/'; var maps = 'https://maps.googleapis.com/maps/api/geocode/'; /** @@ -129,7 +129,7 @@ var Locations = module.exports = { var modelData = function(data) { //Separate data into variables - var inspectCount = 0; + var inspectCount = 0; var commuteData = JSON.parse(data.distance) if (commuteData.status === 'OK'){ @@ -166,4 +166,4 @@ var modelData = function(data) { } else { return response; } -}; \ No newline at end of file +}; diff --git a/client/app/models/Searches.js b/client/app/models/Searches.js index 6ce2bfb..8180ef7 100644 --- a/client/app/models/Searches.js +++ b/client/app/models/Searches.js @@ -1,7 +1,7 @@ var m = require('mithril'); var Auth = require('./Auth'); var _ = require('underscore'); //not really needed as it is used only once -var fbUrl = 'https://craply.firebaseio.com/'; +var fbUrl = 'https://livability.firebaseio.com/'; /** * You shoudld check the Fire base docs to understand the special methods to access data @@ -23,4 +23,4 @@ var Searches = module.exports = { m.redraw(); }) } -}; \ No newline at end of file +}; diff --git a/client/public/sass/mapContainer.scss b/client/public/sass/mapContainer.scss index 97a3123..fc77864 100644 --- a/client/public/sass/mapContainer.scss +++ b/client/public/sass/mapContainer.scss @@ -16,7 +16,7 @@ } .login-btn { - background-color: #435359; + background-color: #3b5998; color: white; } diff --git a/client/public/style.css b/client/public/style.css index 1e99838..632703f 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -91,7 +91,7 @@ h3, h4 { color: white; } .login-btn { - background-color: #435359; + background-color: #3b5998; color: white; } .navbar-login { diff --git a/firebase.json b/firebase.json index ae75bc7..a9cdc7d 100644 --- a/firebase.json +++ b/firebase.json @@ -2,11 +2,9 @@ "rules": { "users": { "$uid": { - // grants write access to the owner of this user account whose uid must exactly match the key ($uid) ".write": "auth !== null && auth.uid === $uid", - // grants read access to any user who is logged in with an email and password ".read": "auth !== null && auth.provider === 'password'" } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index ae11cba..50705b6 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "browserify": "^10.2.6", "browserify-middleware": "^6.0.0", "express": "^4.13.1", + "firebase": "^2.2.9", "geolib": "^2.0.17", "knex": "^0.8.6", "mithril": "^0.2.0", diff --git a/server/index.js b/server/index.js index ef88e9f..824de85 100644 --- a/server/index.js +++ b/server/index.js @@ -42,8 +42,8 @@ app.get('/client/img/house', function(req, res) { app.post('/distance', function(req, res){ - request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + - req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ + request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ credentials.GoogleDistance, function(error, response, body) { if (error) throw error; res.send(body) @@ -100,8 +100,8 @@ app.post('/', function (req, res){ return httpResponseBody; }) .then(function attachCommuteTime(httpResponseBody) { - request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + - req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ + request('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + + req.body.address+'&destinations='+req.body.workAddress+'&arrival_time=1438610400&key='+ credentials.GoogleDistance, function(error, response, body) { if (error) throw error; httpResponseBody.distance = body; @@ -132,7 +132,7 @@ app.post('/', function (req, res){ } }) } - return neighborhoodOptions + return neighborhoodOptions }) // Get the neighborhood information for the requested address .then(function(result){ @@ -150,22 +150,22 @@ app.post('/', function (req, res){ //Property Taxes for neighborhood and Austin average houseData.neighborhood.propTaxNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['neighborhood'][0]['value'][0]['_']-0 // Using '-0' to implicitly convert the string value to a number houseData.neighborhood.propTaxCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][0]['tables'][0]['table'][0]['data'][0]['attribute'][13]['values'][0]['city'][0]['value'][0]['_']-0 - + //Median House Size for neighborhood and Austin average houseData.neighborhood.houseSizeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number - houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 - + houseData.neighborhood.houseSizeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][1]['tables'][0]['table'][0]['data'][0]['attribute'][2]['values'][0]['city'][0]['value'][0]-0 + //Median Household Income for neighborhood and Austin average houseData.neighborhood.medianIncomeNeighborhood = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['neighborhood'][0]['value'][0]['_']) houseData.neighborhood.medianIncomeCity = Math.floor(result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][0]['values'][0]['city'][0]['value'][0]['_']) - + //Median Age for neighborhood and Austin average houseData.neighborhood.medianAgeNeighborhood = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['neighborhood'][0]['value'][0]-0 // Using '-0' to implicitly convert the string value to a number houseData.neighborhood.medianAgeCity = result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][3]['values'][0]['city'][0]['value'][0]-0 //% of households with kids for neighborhood and Austin average houseData.neighborhood.percentWithKidsNeighborhood = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['neighborhood'][0]['value'][0]['_']*100).toFixed(3)-0 //Convert decimal to percentage with 3 decimal places - houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 + houseData.neighborhood.percentWithKidsCity = (result['Demographics:demographics']['response'][0]['pages'][0]['page'][2]['tables'][0]['table'][0]['data'][0]['attribute'][4]['values'][0]['city'][0]['value'][0]['_']*100).toFixed(3)-0 } // Attach Zillow data to response httpResponseBody.zillowData = houseData @@ -188,4 +188,3 @@ app.post('/', function (req, res){ var port = process.env.PORT || 4000; app.listen(port); console.log("Listening on port", port); -