Initial support for TopoJSON (#79)
Does not include any attributes in the format
This commit is contained in:
parent
e311c388b6
commit
39669578b6
@ -79,7 +79,7 @@ function sanitize_filename(filename) {
|
||||
// request handlers
|
||||
function handleQuery(req, res) {
|
||||
|
||||
var supportedFormats = ['json', 'geojson', 'csv', 'svg', 'shp', 'kml'];
|
||||
var supportedFormats = ['json', 'geojson', 'topojson', 'csv', 'svg', 'shp', 'kml'];
|
||||
var svg_width = 1024.0;
|
||||
var svg_height = 768.0;
|
||||
|
||||
@ -187,7 +187,7 @@ function handleQuery(req, res) {
|
||||
}
|
||||
|
||||
// TODO: refactor formats to external object
|
||||
if (format === 'geojson'){
|
||||
if (format === 'geojson' || format === 'topojson' ){
|
||||
sql = ['SELECT *, ST_AsGeoJSON(the_geom,',dp,') as the_geom FROM (', sql, ') as foo'].join("");
|
||||
} else if (format === 'shp') {
|
||||
return null;
|
||||
@ -255,7 +255,9 @@ function handleQuery(req, res) {
|
||||
|
||||
// TODO: refactor formats to external object
|
||||
if (format === 'geojson'){
|
||||
toGeoJSON(result, res, this);
|
||||
toGeoJSON(result, gn, this);
|
||||
} else if (format === 'topojson'){
|
||||
toTopoJSON(result, gn, this);
|
||||
} else if (format === 'svg'){
|
||||
toSVG(result.rows, gn, this);
|
||||
} else if (format === 'csv'){
|
||||
@ -299,7 +301,7 @@ function handleCacheStatus(req, res){
|
||||
}
|
||||
|
||||
// helper functions
|
||||
function toGeoJSON(data, res, callback){
|
||||
function toGeoJSON(data, gn, callback){
|
||||
try{
|
||||
var out = {
|
||||
type: "FeatureCollection",
|
||||
@ -312,9 +314,9 @@ function toGeoJSON(data, res, callback){
|
||||
properties: { },
|
||||
geometry: { }
|
||||
};
|
||||
geojson.geometry = JSON.parse(ele["the_geom"]);
|
||||
delete ele["the_geom"];
|
||||
delete ele["the_geom_webmercator"];
|
||||
geojson.geometry = JSON.parse(ele[gn]);
|
||||
delete ele[gn];
|
||||
delete ele["the_geom_webmercator"]; // TODO: use skipfields
|
||||
geojson.properties = ele;
|
||||
out.features.push(geojson);
|
||||
});
|
||||
@ -326,6 +328,19 @@ function toGeoJSON(data, res, callback){
|
||||
}
|
||||
}
|
||||
|
||||
function toTopoJSON(data, gn, callback){
|
||||
toGeoJSON(data, gn, function(err, geojson) {
|
||||
if ( err ) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
var TopoJSON = require('topojson');
|
||||
// TODO: provide some identifiers here
|
||||
var topology = TopoJSON.topology(geojson.features);
|
||||
callback(err, topology);
|
||||
});
|
||||
}
|
||||
|
||||
function toSVG(rows, gn, callback){
|
||||
|
||||
var radius = 5; // in pixels (based on svg_width and svg_height)
|
||||
@ -734,6 +749,9 @@ function getContentDisposition(format, filename, inline) {
|
||||
if (format === 'geojson'){
|
||||
ext = 'geojson';
|
||||
}
|
||||
else if (format === 'topojson'){
|
||||
ext = 'topojson';
|
||||
}
|
||||
else if (format === 'csv'){
|
||||
ext = 'csv';
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ Supported query string parameters:
|
||||
|
||||
'format': Specifies which format to use for the response.
|
||||
Supported formats: JSON (the default), GeoJSON,
|
||||
CSV, SVG, SHP
|
||||
TopoJSON, CSV, SVG, SHP
|
||||
|
||||
'filename': Sets the filename to use for the query result
|
||||
file attachment
|
||||
@ -22,7 +22,7 @@ Supported query string parameters:
|
||||
in output. Only useful with "SELECT *" queries.
|
||||
|
||||
'dp': Number of digits after the decimal point.
|
||||
Only affects format=GeoJSON.
|
||||
Only affects format GeoJSON, TopoJSON, SVG.
|
||||
By default this is 6.
|
||||
|
||||
'api_key': Needed to authenticate in order to modify the database.
|
||||
@ -88,7 +88,7 @@ The GeoJSON response is follows:
|
||||
}
|
||||
```
|
||||
|
||||
TODO: csv, kml responses
|
||||
TODO: csv, kml, svg, topojson responses
|
||||
|
||||
Response errors
|
||||
---------------
|
||||
|
20
npm-shrinkwrap.json
generated
20
npm-shrinkwrap.json
generated
@ -186,12 +186,7 @@
|
||||
},
|
||||
"pg": {
|
||||
"version": "0.8.7-cdb1",
|
||||
"from": "git://github.com/CartoDB/node-postgres.git#cdb_production",
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "1.0.9"
|
||||
}
|
||||
}
|
||||
"from": "git://github.com/CartoDB/node-postgres.git#cdb_production"
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "1.0.12"
|
||||
@ -205,6 +200,19 @@
|
||||
"step": {
|
||||
"version": "0.0.5"
|
||||
},
|
||||
"topojson": {
|
||||
"version": "0.0.8",
|
||||
"dependencies": {
|
||||
"optimist": {
|
||||
"version": "0.3.5",
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth-client": {
|
||||
"version": "0.2.0",
|
||||
"dependencies": {
|
||||
|
@ -18,6 +18,7 @@
|
||||
"redis": "0.7.1",
|
||||
"hiredis": "*",
|
||||
"step": "0.0.x",
|
||||
"topojson": "~0.0.8",
|
||||
"oauth-client": "0.2.0",
|
||||
"node-uuid":"1.3.3",
|
||||
"strftime":"~0.4.7",
|
||||
@ -29,7 +30,7 @@
|
||||
"libxmljs": "~0.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "test/run_tests.sh ${RUNTESTFLAGS} test/unit/redis_pool.test.js test/unit/metadata.test.js test/unit/oauth.test.js test/unit/psql.test.js test/acceptance/app.test.js test/acceptance/app.auth.test.js"
|
||||
"test": "test/run_tests.sh ${RUNTESTFLAGS} test/unit/redis_pool.test.js test/unit/metadata.test.js test/unit/oauth.test.js test/unit/psql.test.js test/acceptance/app.test.js test/acceptance/app.auth.test.js test/acceptance/export/topojson.js"
|
||||
},
|
||||
"engines": { "node": ">= 0.4.1 < 0.9" }
|
||||
}
|
||||
|
113
test/acceptance/export/topojson.js
Normal file
113
test/acceptance/export/topojson.js
Normal file
@ -0,0 +1,113 @@
|
||||
require('../../helper');
|
||||
require('../../support/assert');
|
||||
|
||||
|
||||
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||
, assert = require('assert')
|
||||
, querystring = require('querystring')
|
||||
, _ = require('underscore')
|
||||
, zipfile = require('zipfile')
|
||||
, fs = require('fs')
|
||||
, libxmljs = require('libxmljs')
|
||||
;
|
||||
|
||||
// allow lots of emitters to be set to silence warning
|
||||
app.setMaxListeners(0);
|
||||
|
||||
|
||||
suite('export.topojson', function() {
|
||||
|
||||
// TOPOJSON tests
|
||||
|
||||
test('GET two polygons sharing an edge as topojson', function(done){
|
||||
assert.response(app, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT 1 as cartodb_id, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " +
|
||||
" UNION ALL " +
|
||||
"SELECT 2 as cartodb_id, 'POLYGON((0 -5,0 5,-5 0,0 -5))'::geometry as the_geom ",
|
||||
format: 'topojson'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.header('Content-Disposition');
|
||||
assert.equal(true, /^attachment/.test(cd), 'TOPOJSON is not disposed as attachment: ' + cd);
|
||||
assert.equal(true, /filename=cartodb-query.topojson/gi.test(cd));
|
||||
var topojson = JSON.parse(res.body);
|
||||
assert.equal(topojson.type, 'Topology');
|
||||
|
||||
// Check transform
|
||||
assert.ok(topojson.hasOwnProperty('transform'));
|
||||
var trans = topojson.transform;
|
||||
assert.equal(_.keys(trans).length, 2); // only scale and translate
|
||||
assert.equal(trans.scale.length, 2); // scalex, scaley
|
||||
assert.equal(Math.round(trans.scale[0]*1e6), 1000);
|
||||
assert.equal(Math.round(trans.scale[1]*1e6), 1000);
|
||||
assert.equal(trans.translate.length, 2); // translatex, translatey
|
||||
assert.equal(trans.translate[0], -5);
|
||||
assert.equal(trans.translate[1], -5);
|
||||
|
||||
// Check objects
|
||||
assert.ok(topojson.hasOwnProperty('objects'));
|
||||
assert.equal(_.keys(topojson.objects).length, 2);
|
||||
var obj = topojson.objects[0];
|
||||
//console.dir(obj);
|
||||
// Expected: { type: 'Polygon', arcs: [ [ 0, 1 ] ] }
|
||||
assert.equal(_.keys(obj).length, 2); // only type and arcs, no props
|
||||
assert.equal(obj.type, 'Polygon');
|
||||
assert.equal(obj.arcs.length, 1); /* only shell, no holes */
|
||||
var shell = obj.arcs[0];
|
||||
assert.equal(shell.length, 2); /* one shared arc, one non-shared */
|
||||
assert.equal(shell[0], 0); /* shared arc */
|
||||
assert.equal(shell[1], 1); /* non-shared arc */
|
||||
obj = topojson.objects[1];
|
||||
//console.dir(obj);
|
||||
// Expected: { type: 'Polygon', arcs: [ [ 0, 2 ] ] }
|
||||
assert.equal(_.keys(obj).length, 2); // only type and arcs, no props
|
||||
assert.equal(obj.type, 'Polygon');
|
||||
assert.equal(obj.arcs.length, 1); /* only shell, no holes */
|
||||
shell = obj.arcs[0];
|
||||
assert.equal(shell.length, 2); /* one shared arc, one non-shared */
|
||||
assert.equal(shell[0], 0); /* shared arc */
|
||||
assert.equal(shell[1], 2); /* non-shared arc */
|
||||
|
||||
// Check arcs
|
||||
assert.ok(topojson.hasOwnProperty('arcs'));
|
||||
assert.equal(topojson.arcs.length, 3); // one shared, two non-shared
|
||||
var arc = topojson.arcs[0]; // shared arc
|
||||
assert.equal(arc.length, 2); // shared arc has two vertices
|
||||
var p = arc[0];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 0);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||
p = arc[1];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||
arc = topojson.arcs[1]; // non shared arc
|
||||
assert.equal(arc.length, 3); // non shared arcs have three vertices
|
||||
p = arc[0];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 10);
|
||||
p = arc[1];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), -5);
|
||||
p = arc[2];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), -10);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 0);
|
||||
arc = topojson.arcs[2]; // non shared arc
|
||||
assert.equal(arc.length, 3); // non shared arcs have three vertices
|
||||
p = arc[0];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 10);
|
||||
p = arc[1];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), 0);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), -10);
|
||||
p = arc[2];
|
||||
assert.equal(Math.round(p[0]*trans.scale[0]), -5);
|
||||
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user