Permalink
Browse files

Add parcels API

Implement the parcels API according to the spec described by the tests.
Requires a Postgresql + PostGIS parcel server and the appropriate
environment variables to connect to that server.
  • Loading branch information...
1 parent 5a73751 commit c2f9cd2da1076bd04cc5049c14fb7e9c4cdb910d @prashtx prashtx committed Aug 7, 2012
Showing with 175 additions and 0 deletions.
  1. +8 −0 herokuconfig.sh.sample
  2. +1 −0 package.json
  3. +148 −0 parcels.js
  4. +6 −0 settings-test.js
  5. +6 −0 settings.js
  6. +6 −0 web.js
View
8 herokuconfig.sh.sample
@@ -9,6 +9,10 @@ if [ "prod" = "$1" ]; then
heroku config:add MONGO_DB="dbName"
heroku config:add S3_KEY="S3KEYblah"
heroku config:add S3_SECRET="S3SECRETblah"
+ heroku config:add PSQL_HOST='my.postgresql-host.com'
+ heroku config:add PSQL_NAME='parceldb'
+ heroku config:add PSQL_USER='dbuser'
+ heroku config:add PSQL_PASS='dbpass'
elif [ "dev" = "$1" ]; then
heroku config:add NODE_ENV="development"
heroku config:add MONGO_HOST="localhost"
@@ -18,6 +22,10 @@ elif [ "dev" = "$1" ]; then
heroku config:add MONGO_DB="dbNameDev"
heroku config:add S3_KEY="S3KEYblah"
heroku config:add S3_SECRET="S3SECRETblah"
+ heroku config:add PSQL_HOST='my.postgresql-host.com'
+ heroku config:add PSQL_NAME='parceldb'
+ heroku config:add PSQL_USER='dbuser'
+ heroku config:add PSQL_PASS='dbpass'
else
echo "Usage: ./herokuconfig.sh prod/dev"
fi
View
1 package.json
@@ -9,6 +9,7 @@
, "request" : "2.9.x"
, "node-uuid" : "1.3.3"
, "knox" : "https://github.com/prashtx/knox/tarball/master"
+ , "pg" : "0.6.x"
}
, "devDependencies": {
"mocha" : "https://github.com/visionmedia/mocha/tarball/master"
View
148 parcels.js
@@ -0,0 +1,148 @@
+/*jslint node: true */
+'use strict';
+
+/*
+ * ==================================================
+ * Parcels
+ * ==================================================
+ *
+ * Serves parcel data from the geo database.
+ */
+
+var pg = require('pg');
+
+function bboxToPolygon(bbox) {
+ var polygon = 'POLYGON((' +
+ bbox[0] + ' ' + bbox[1] + ', ' +
+ bbox[0] + ' ' + bbox[3] + ', ' +
+ bbox[2] + ' ' + bbox[3] + ', ' +
+ bbox[2] + ' ' + bbox[1] + ', ' +
+ bbox[0] + ' ' + bbox[1] + '))';
+ return polygon;
+}
+
+
+/*
+ * app: express server
+ */
+function setup(app, settings) {
+ var connectionString =
+ 'tcp://' + settings.psqlUser + ':' + settings.psqlPass +
+ '@' + settings.psqlHost + '/' + settings.psqlName;
+ var client = new pg.Client(connectionString);
+ client.connect();
+
+ app.on('close', function () {
+ client.end();
+ });
+
+ // Get parcels
+ // Filter to include only parcels inside a bounding box or only parcels that
+ // intersect a point.
+ // We do not allow filtering by both, and we require one of the filters.
+ // GET http://localhost:3000/parcels?bbox=-{SW_LON},{SW_LAT},{NE_LON},{NE_LAT}
+ // GET http://localhost:3000/parcels?bbox=-83.0805,42.336,-83.08,42.34
+ // GET http://localhost:3000/parcels?lon={LONGITUDE}&lat={LATITUDE}
+ // GET http://localhost:3000/parcels?lon=-83.08076&lat=42.338
+ app.get('/parcels', function(req, response) {
+ var bbox = req.query.bbox;
+ var lon = req.query.lon;
+ var lat = req.query.lat;
+ var query;
+ var coordString;
+ var coords;
+ var output;
+ var error;
+ var i;
+
+ // Require a filter
+ if (bbox === undefined &&
+ (lon === undefined || lat === undefined)) {
+ response.send(413);
+ return;
+ }
+
+ if (bbox !== undefined) {
+ // Bounding box query
+
+ // Don't allow both filters at once
+ if (lon !== undefined || lat !== undefined) {
+ response.send(400);
+ return;
+ }
+
+ // Split bounding box into coordinates
+ coordString = bbox.split(',');
+
+ if (coordString.length !== 4) {
+ response.send(400);
+ return;
+ }
+
+ // Convert coordinates to numbers
+ coords = [];
+ for (i = 0; i < 4; i += 1) {
+ coords[i] = parseFloat(coordString[i]);
+
+ // Make sure the conversion worked
+ if (isNaN(coords[i])) {
+ response.send(400);
+ return;
+ }
+ }
+
+ query = client.query({
+ text: 'SELECT parcelnumb, propaddres, proaddress, ST_AsGeoJSON(wkb_geometry) AS polygon, ST_AsGeoJSON(ST_Centroid(wkb_geometry)) AS centroid FROM qgis WHERE ST_Intersects(wkb_geometry, ST_SetSRID($1, 4326))',
+ values: [bboxToPolygon(coords)],
+ name: 'parcelBBoxQuery'
+ });
+ } else {
+ // Point query
+
+ // Convert coordinates to numbers
+ lat = parseFloat(lat);
+ lon = parseFloat(lon);
+
+ if (isNaN(lat) || isNaN(lon)) {
+ response.send(400);
+ return;
+ }
+
+ query = client.query({
+ text: 'SELECT parcelnumb, propaddres, proaddress, ST_AsGeoJSON(wkb_geometry) AS polygon, ST_AsGeoJSON(ST_Centroid(wkb_geometry)) AS centroid FROM qgis WHERE ST_Contains(wkb_geometry, ST_SetSRID($1, 4326))',
+ values: ['POINT(' + lon + ' ' + lat + ')'],
+ name: 'parcelPointQuery'
+ });
+ }
+
+ output = [];
+ query
+ .on('row', function (row, result) {
+ try {
+ output.push({
+ parcelId: row.parcelnumb.trim(),
+ address: row.proaddress.trim(),
+ polygon: JSON.parse(row.polygon),
+ centroid: JSON.parse(row.centroid)
+ });
+ } catch (e) {
+ error = e;
+ }
+ })
+ .on('error', function (e) {
+ console.log(e.message);
+ error = e;
+ })
+ .on('end', function (result) {
+ if (error) {
+ response.send(500);
+ } else {
+ response.send(output);
+ }
+ });
+ });
+}
+
+module.exports = {
+ setup: setup
+}
View
6 settings-test.js
@@ -18,5 +18,11 @@ settings.s3_secret = process.env.S3_SECRET || 'FAKE_SECRET';
settings.s3_bucket = 'cfadetroit_survey';
settings.s3_dir = 'uploaded_test_files';
+// Postgresql parcel server
+settings.psqlHost = process.env.PSQL_HOST;
+settings.psqlName = process.env.PSQL_NAME;
+settings.psqlUser = process.env.PSQL_USER;
+settings.psqlPass = process.env.PSQL_PASS;
+
// Web server
settings.port = 3030;
View
6 settings.js
@@ -17,5 +17,11 @@ settings.s3_secret = process.env.S3_SECRET;
settings.s3_bucket = process.env.S3_BUCKET;
settings.s3_dir = process.env.S3_UPLOAD_DIR;
+// Postgresql parcel server
+settings.psqlHost = process.env.PSQL_HOST;
+settings.psqlName = process.env.PSQL_NAME;
+settings.psqlUser = process.env.PSQL_USER;
+settings.psqlPass = process.env.PSQL_PASS;
+
// Web server
settings.port = process.env.PORT || 3000;
View
6 web.js
@@ -14,6 +14,7 @@ var responses = require('./responses');
var collectors = require('./collectors');
var surveys = require('./surveys');
var scans = require('./scans');
+var parcels = require('./parcels');
var RESPONSES = 'responseCollection';
var FORMS = 'formCollection';
@@ -88,6 +89,7 @@ function setupRoutes(db, settings) {
collectors.setup(app, db, idgen, COLLECTORS);
surveys.setup(app, db, idgen, SURVEYS);
scans.setup(app, db, idgen, SCANIMAGES, settings);
+ parcels.setup(app, settings);
}
// Ensure certain database structure.
@@ -166,6 +168,9 @@ function run(settings, cb) {
console.log('Mongo port: ' + settings.mongo_port);
console.log('Mongo db: ' + settings.mongo_db);
console.log('Mongo user: ' + settings.mongo_user);
+ console.log('Postgresql host: ' + settings.psqlHost);
+ console.log('Postgresql db: ' + settings.psqlName);
+ console.log('Postgresql user: ' + settings.psqlUser);
// Set up database
if (!db) {
db = new mongo.Db(settings.mongo_db, new mongo.Server(settings.mongo_host,
@@ -197,6 +202,7 @@ function run(settings, cb) {
function stop() {
app.close();
db.close();
+ console.log('Stopped server');
}
module.exports = {

0 comments on commit c2f9cd2

Please sign in to comment.