Skip to content

Commit

Permalink
Merge 53c2fb3 into be95c36
Browse files Browse the repository at this point in the history
  • Loading branch information
Jake Pruitt committed Aug 8, 2015
2 parents be95c36 + 53c2fb3 commit 9b8aa05
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 34 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ var Cardboard = module.exports = function(config) {
/**
* Given a GeoJSON feature to remove, perform all required metadata updates. This operation **will not** create a metadata record if one does not exist. This operation **will not** shrink metadata bounds.
* @param {object} dataset - the name of the dataset
* @param {object} feature - a GeoJSON feature to remove from the dataset
* @param {object|number} feature - a GeoJSON feature to remove from the dataset or the size of the GeoJSON feature provided as a number
* @param {function} callback - a function to handle the response
*/
cardboard.metadata.deleteFeature = function(dataset, feature, callback) {
Expand Down
85 changes: 70 additions & 15 deletions lib/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,64 +162,91 @@ var Metadata = module.exports = function(dyno, dataset) {

/**
* Given a GeoJSON feature, perform all required metadata updates. This operation **will** create a metadata record if one does not exist.
* @param {object} feature - a GeoJSON feature being added to the dataset
* @param {object} feature - a GeoJSON feature being added to the dataset, or the backend representation of a feature
* @param {function} callback - a function to handle the response
*/
metadata.addFeature = function(feature, callback) {
var info = metadata.getFeatureInfo(feature);
var info = isDatabaseRecord(feature) ? feature : metadata.getFeatureInfo(feature);

metadata.defaultInfo(function(err) {
if (err) return callback(err);

queue()
.defer(metadata.adjustProperties, { count: 1, size: info.size })
.defer(metadata.adjustBounds, info.bounds)
.awaitAll(callback);
.defer(metadata.adjustBounds, [info.west, info.south, info.east, info.north])
.awaitAll(function(err) {
if (err) return callback(err);
callback();
});
});
};

/**
* Given before and after states of a GeoJSON feature, perform all required metadata adjustments. This operation **will not** create a metadata record if one does not exist.
* @param {object} from - a GeoJSON feature representing the state of the feature *before* the update
* @param {object} to - a GeoJSON feature representing the state of the feature *after* the update
* @param {object} from - a GeoJSON feature representing the state of the feature *before* the update, or the backend representation of a feature
* @param {object} to - a GeoJSON feature representing the state of the feature *after* the update, or the backend representation of a feature
* @param {function} callback - a function to handle the response
*/
metadata.updateFeature = function(from, to, callback) {
var fromInfo = metadata.getFeatureInfo(from);
var toInfo = metadata.getFeatureInfo(to);
var bounds = toInfo.bounds;
var fromInfo = isDatabaseRecord(from) ? from : metadata.getFeatureInfo(from);
var toInfo = isDatabaseRecord(to) ? to : metadata.getFeatureInfo(to);
var size = toInfo.size - fromInfo.size;

queue()
.defer(metadata.adjustProperties, { size: size })
.defer(metadata.adjustBounds, bounds)
.awaitAll(callback);
.defer(metadata.adjustBounds, [toInfo.west, toInfo.south, toInfo.east, toInfo.north])
.awaitAll(function(err) {
if (err) return callback(err);
callback();
});
};

/**
* Given a GeoJSON feature to remove, perform all required metadata updates. This operation **will not** create a metadata record if one does not exist. This operation **will not** shrink metadata bounds.
* @param {object} feature - a GeoJSON feature to remove from the dataset
* @param {object} feature - a GeoJSON feature to remove from the dataset, or the backend representation of a feature
* @param {function} callback - a function to handle the response
*/
metadata.deleteFeature = function(feature, callback) {
var info = metadata.getFeatureInfo(feature);
var info = isDatabaseRecord(feature) ? feature : metadata.getFeatureInfo(feature);

queue()
.defer(metadata.adjustProperties, { count: -1, size: -info.size })
.awaitAll(callback);
.awaitAll(function(err) {
if (err) return callback(err);
callback();
});
};

return metadata;
};

/**
* Adds additional information to return which can be calculated from the metadata object
*
* @private
* @param {object} info - a metadata record from the database
* @returns {object} same metadata with additional information appended
*/
function prepare(info) {
var range = zoomRange(info.size, [info.west, info.south, info.east, info.west]);
var range = zoomRange(info.size, [info.west, info.south, info.east, info.north]);
var result = _.clone(info);
result.minzoom = range.min;
result.maxzoom = range.max;
return result;
}

/**
* Calculate an ideal zoom range based on data size and geographic extent.
* Makes an implicit assumption that the data is evenly distributed geographically
* - max = zoom level at which a single tile would contain < 1 kilobyte
* - min = zoom level at which a single tile would contain > 500 kilobytes
* - never sets max zoom > 22
*
* @private
* @param {number} bytes - the number of bytes
* @param {Array<number>} extent - the geographic extent in decimal degrees as [west, south, east, north]
* @returns {object} an object with `min` and `max` properties corresponding to an ideal min and max zoom
*/
function zoomRange(bytes, extent) {
var maxSize = 500 * 1024;
var maxzoom = 14;
Expand All @@ -241,3 +268,31 @@ function zoomRange(bytes, extent) {
if (tiles === 1 || z === 0) return { min: 0, max: maxzoom };
}
}

/**
* Simple duck-type detection of whether or not an object is a database record
*
* @private
* @param {object} obj - the object to test
* @returns {boolean} whether or not the object is a backend record
*/
function isDatabaseRecord(obj) {
if (typeof obj !== 'object') return false;

var schema = {
dataset: 'string',
id: 'string',
cell: 'string',
size: 'number',
west: 'number',
south: 'number',
east: 'number',
north: 'number',
s3url: 'string'
};

return Object.keys(schema).reduce(function(isDatabaseRecord, key) {
if (typeof obj[key] !== schema[key]) isDatabaseRecord = false;
return isDatabaseRecord;
}, true);
}

0 comments on commit 9b8aa05

Please sign in to comment.