diff --git a/CHANGELOG.md b/CHANGELOG.md index 337ee2a..2115d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Detect field types if they are not passed in with `geojson.metadata` - Translate ISO Date Strings to Unix Timestamps when `options.toEsri` is true +- Add Object IDs if there is no ID field set with `options.toEsri` is true ## [1.9.0] - 05-24-2017 ### Added diff --git a/src/index.js b/src/index.js index e49343b..d68411c 100644 --- a/src/index.js +++ b/src/index.js @@ -70,25 +70,7 @@ Winnow.prepareSql = function (statement) { } } -function finishQuery (features, options) { - if (options.groupBy) { - return features - } else if (options.aggregates) { - return features[0] - } else if (options.collection) { - const collection = options.collection - collection.features = features - return collection - } else { - return features - } -} - -function aggregateQuery (features, query, options) { - const params = Query.params(features, options) - const filtered = sql(query, params) - return finishQuery(filtered, options) -} +// TODO move these functions to a new file function limitQuery (features, query, options) { const filtered = [] @@ -102,23 +84,53 @@ function limitQuery (features, query, options) { function standardQuery (features, query, options) { const filtered = features.reduce((filteredFeatures, feature, i) => { - // TODO used passed in fields if available - const result = processQuery(feature, query, options) + const result = processQuery(feature, query, options, i) if (result) filteredFeatures.push(result) return filteredFeatures }, []) return finishQuery(filtered, options) } -function processQuery (feature, query, options) { +function aggregateQuery (features, query, options) { + const params = Query.params(features, options) + const filtered = sql(query, params) + return finishQuery(filtered, options) +} + +function processQuery (feature, query, options, i) { const params = Query.params([feature], options) const result = sql(query, params)[0] - if (options.dateFields.length && options.toEsri) { + + if (result && options.toEsri) return esriFy(result, options, i) + else return result +} + +function esriFy (result, options, i) { + if (options.dateFields.length) { options.dateFields.forEach(field => { result.attributes[field] = new Date(result.attributes[field]).getTime() }) } + + const metadata = (options.collection && options.collection.metadata) || {} + if (!metadata.idField) { + result.attributes.OBJECTID = i + } return result } +function finishQuery (features, options) { + if (options.groupBy) { + return features + } else if (options.aggregates) { + return features[0] + } else if (options.collection) { + const collection = options.collection + collection.features = features + return collection + } else { + return features + } +} + module.exports = Winnow diff --git a/test/filter.js b/test/filter.js index ac73072..4d277e9 100644 --- a/test/filter.js +++ b/test/filter.js @@ -2,21 +2,21 @@ const test = require('tape') const winnow = require('../src') -test('With a where option', (t) => { +test('With a where option', t => { const options = { where: "Genus like '%Quercus%'" } run('trees', options, 12105, t) }) -test('With a field that has been uppercased', (t) => { +test('With a field that has been uppercased', t => { const options = { where: "UPPER(Genus) like '%Quercus%'" } run('trees', options, 12105, t) }) -test('With the toEsri option', (t) => { +test('With the toEsri option', t => { const options = { toEsri: true, where: "Genus like '%Quercus%'" @@ -24,21 +24,21 @@ test('With the toEsri option', (t) => { run('trees', options, 12105, t) }) -test('With the toEsri option and a null geometry', (t) => { +test('With the toEsri option and a null geometry', t => { const options = { toEsri: true } run('nogeom', options, 100, t) }) -test('With a field with a space', (t) => { +test('With a field with a space', t => { const options = { where: '"total precip" > 1' } run('snow', options, 4, t) }) -test('With esri json', (t) => { +test('With esri json', t => { const options = { where: "Genus like '%Quercus%'", esri: true @@ -46,63 +46,63 @@ test('With esri json', (t) => { run('esri', options, 267, t) }) -test('With multiple like clauses', (t) => { +test('With multiple like clauses', t => { const options = { where: "Genus like '%Quercus%' AND Common_Name like '%Live Oak%' AND Street_Type like '%AVE%'" } run('trees', options, 3330, t) }) -test('With an in parameter', (t) => { +test('With an in parameter', t => { const options = { where: "Genus IN ('QUERCUS', 'EUGENIA')" } run('trees', options, 13134, t) }) -test('With an > parameter', (t) => { +test('With an > parameter', t => { const options = { where: 'Trunk_Diameter>10' } run('trees', options, 35961, t) }) -test('With an >= parameter', (t) => { +test('With an >= parameter', t => { const options = { where: 'Trunk_Diameter>=10' } run('trees', options, 37777, t) }) -test('With an in parameter and a numeric test', (t) => { +test('With an in parameter and a numeric test', t => { const options = { where: "Genus IN ('QUERCUS', 'EUGENIA') AND Trunk_Diameter=10" } run('trees', options, 409, t) }) -test('With an AND and an OR', (t) => { +test('With an AND and an OR', t => { const options = { where: "(Genus like '%Quercus%' AND Common_Name like '%Live Oak%') OR Street_Type like '%AVE%'" } run('trees', options, 31533, t) }) -test('With an equality parameter', (t) => { +test('With an equality parameter', t => { const options = { where: "Common_Name = 'LIVE OAK'" } run('trees', options, 6498, t) }) -test('With date inputs', (t) => { +test('With date inputs', t => { const options = { where: "Date1 >= '2012-03-14T04:00:00.000Z' AND Date1 <= '2012-03-18T03:59:59.000Z'" } run('dates', options, 3, t) }) -test('With an esri style envelope', (t) => { +test('With an esri style envelope', t => { const options = { geometry: { xmin: -13155799.066536672, @@ -117,7 +117,7 @@ test('With an esri style envelope', (t) => { run('trees', options, 29744, t) }) -test('With an esri style envelope and wkt string for web mercator', (t) => { +test('With an esri style envelope and wkt string for web mercator', t => { const options = { geometry: { xmin: -13155799.066536672, @@ -125,14 +125,15 @@ test('With an esri style envelope and wkt string for web mercator', (t) => { xmax: -13143569.142011061, ymax: 4050673.16627152, spatialReference: { - wkt: 'PROJCS["WGS_1984_Web_Mercator_Auxiliary_Sphere",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Mercator_Auxiliary_Sphere"],PARAMETER["False_Easting",0.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-97.03124999997486],PARAMETER["Standard_Parallel_1",0.0],PARAMETER["Auxiliary_Sphere_Type",0.0],UNIT["Meter",1.0]]' + wkt: + 'PROJCS["WGS_1984_Web_Mercator_Auxiliary_Sphere",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Mercator_Auxiliary_Sphere"],PARAMETER["False_Easting",0.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-97.03124999997486],PARAMETER["Standard_Parallel_1",0.0],PARAMETER["Auxiliary_Sphere_Type",0.0],UNIT["Meter",1.0]]' } } } run('trees', options, 29744, t) }) -test('With an empty multipolygon', (t) => { +test('With an empty multipolygon', t => { const options = { geometry: { xmin: -8968940.494006854, @@ -147,7 +148,7 @@ test('With an empty multipolygon', (t) => { run('emptyMultiPolygon', options, 1, t) }) -test('Without a spatialReference property on an Esri-style Envelope', (t) => { +test('Without a spatialReference property on an Esri-style Envelope', t => { const options = { geometry: { xmin: -118.18055376275225, @@ -159,31 +160,16 @@ test('Without a spatialReference property on an Esri-style Envelope', (t) => { run('trees', options, 29744, t) }) -test('With a an Esri-style Polygon', (t) => { +test('With a an Esri-style Polygon', t => { const options = { geometry: { rings: [ [ - [ - -12993071.816030473, - 3913575.8482084945 - ], - [ - -12836528.782102507, - 3913575.8482084945 - ], - [ - -12836528.782102507, - 4070118.8821364585 - ], - [ - -12993071.816030473, - 4070118.8821364585 - ], - [ - -12993071.816030473, - 3913575.8482084945 - ] + [-12993071.816030473, 3913575.8482084945], + [-12836528.782102507, 3913575.8482084945], + [-12836528.782102507, 4070118.8821364585], + [-12993071.816030473, 4070118.8821364585], + [-12993071.816030473, 3913575.8482084945] ] ] }, @@ -192,21 +178,21 @@ test('With a an Esri-style Polygon', (t) => { run('restaurants', options, 249, t) }) -test('With an array-style geometry', (t) => { +test('With an array-style geometry', t => { const options = { geometry: [-118.18055376275225, 34.14141744789609, -118.07069048150241, 34.162726215637875] } run('trees', options, 29744, t) }) -test('With a string-style geometry', (t) => { +test('With a string-style geometry', t => { const options = { geometry: '-118.18055376275225, 34.14141744789609, -118.07069048150241, 34.162726215637875' } run('trees', options, 29744, t) }) -test('With a ST_Contains geometry predicate', (t) => { +test('With a ST_Contains geometry predicate', t => { const options = { geometry: { type: 'Polygon', @@ -217,7 +203,7 @@ test('With a ST_Contains geometry predicate', (t) => { run('trees', options, 9878, t) }) -test('With a ST_Within geometry predicate', (t) => { +test('With a ST_Within geometry predicate', t => { const options = { geometry: { type: 'Polygon', @@ -228,7 +214,7 @@ test('With a ST_Within geometry predicate', (t) => { run('states', options, 1, t) }) -test('With a ST_Intersects geometry predicate', (t) => { +test('With a ST_Intersects geometry predicate', t => { const options = { geometry: { type: 'LineString', @@ -239,7 +225,7 @@ test('With a ST_Intersects geometry predicate', (t) => { run('states', options, 9, t) }) -test('With a where and a geometry option', (t) => { +test('With a where and a geometry option', t => { const options = { where: "Genus like '%Quercus%'", geometry: { @@ -256,13 +242,13 @@ test('With an envelope, an inSR and an outSR', t => { returnGeometry: true, spatialRel: 'esriSpatialRelIntersects', geometry: { - 'xmin': -8570731.10757695, - 'ymin': 4710966.9272944275, - 'xmax': -8565839.137766706, - 'ymax': 4715858.897104671, - 'spatialReference': { - 'wkid': 102100, - 'latestWkid': 3857 + xmin: -8570731.10757695, + ymin: 4710966.9272944275, + xmax: -8565839.137766706, + ymax: 4715858.897104671, + spatialReference: { + wkid: 102100, + latestWkid: 3857 } }, geometryType: 'esriGeometryEnvelope', @@ -278,48 +264,18 @@ test('With a multi-ring geometry and an inSR', t => { geometry: { rings: [ [ - [ - 19930537.269606635, - -1018885.7633881811 - ], - [ - 19930537.269606635, - 13148258.807095852 - ], - [ - 20037508.342788905, - 13148258.807095852 - ], - [ - 20037508.342788905, - -1018885.7633881811 - ], - [ - 19930537.269606635, - -1018885.7633881811 - ] + [19930537.269606635, -1018885.7633881811], + [19930537.269606635, 13148258.807095852], + [20037508.342788905, 13148258.807095852], + [20037508.342788905, -1018885.7633881811], + [19930537.269606635, -1018885.7633881811] ], [ - [ - -20037508.342788905, - -1018885.7633881811 - ], - [ - -20037508.342788905, - 13148258.807095852 - ], - [ - -4568447.54013514, - 13148258.807095852 - ], - [ - -4568447.54013514, - -1018885.7633881811 - ], - [ - -20037508.342788905, - -1018885.7633881811 - ] + [-20037508.342788905, -1018885.7633881811], + [-20037508.342788905, 13148258.807095852], + [-4568447.54013514, 13148258.807095852], + [-4568447.54013514, -1018885.7633881811], + [-20037508.342788905, -1018885.7633881811] ] ] }, diff --git a/test/to-esri.js b/test/to-esri.js index 84240f9..1f89020 100644 --- a/test/to-esri.js +++ b/test/to-esri.js @@ -1,6 +1,7 @@ const test = require('tape') const geojson = require('./fixtures/to-esri-fixture.json') const Winnow = require('../src') +const _ = require('lodash') test('detecting fields', t => { const options = {} @@ -13,11 +14,36 @@ test('detecting fields', t => { t.end() }) +test('adding an object id', t => { + const options = { + toEsri: true + } + const fixture = _.cloneDeep(geojson) + const result = Winnow.query(fixture, options) + t.equal(result.features[0].attributes.OBJECTID, 0) + t.end() +}) + +test('do not overwrite the object id', t => { + const options = { + toEsri: true + } + + const fixture = _.cloneDeep(geojson) + fixture.features[0].properties.OBJECTID = 1 + fixture.metadata = { idField: 'OBJECTID' } + + const result = Winnow.query(fixture, options) + t.equal(result.features[0].attributes.OBJECTID, 1) + t.end() +}) + test('converting date fields', t => { const options = { toEsri: true } - const result = Winnow.query(geojson, options) + const fixture = _.cloneDeep(geojson) + const result = Winnow.query(fixture, options) t.equal(result.features[0].attributes.date, 1331769600000) t.equal(Object.keys(result.metadata.fields).length, 4) t.end()