Add in memory geo filtering.

This commit is contained in:
Ritchie Martori 2013-06-25 20:31:00 -07:00
parent d730aab674
commit 6cb53e5001
5 changed files with 107 additions and 30 deletions

View File

@ -5,6 +5,8 @@ exports.initialize = function initializeSchema(schema, callback) {
schema.adapter.connect(callback);
};
exports.Memory = Memory;
function Memory(m) {
if (m) {
this.isTransaction = true;
@ -84,25 +86,9 @@ Memory.prototype.destroy = function destroy(model, id, callback) {
Memory.prototype.fromDb = function(model, data) {
if (!data) return null;
data = JSON.parse(data);
var ctor = this._models[model].model;
var props = this._models[model].properties;
Object.keys(data).forEach(function (key) {
var val = data[key];
if (typeof val === 'undefined' || val === null) {
return;
}
if (props[key]) {
switch(props[key].type.name) {
case 'Date':
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
break;
case 'Boolean':
val = new Boolean(val);
break;
}
}
data[key] = val;
});
return data;
return ctor(data);
};
Memory.prototype.all = function all(model, filter, callback) {
@ -112,7 +98,6 @@ Memory.prototype.all = function all(model, filter, callback) {
}.bind(this));
var isGeo = false;
if (filter) {
// do we need some sorting?
if (filter.order) {
@ -137,9 +122,9 @@ Memory.prototype.all = function all(model, filter, callback) {
// geo sorting
if(nearFilter) {
nodes = geo.filter(nodes, nearFilter);
nodes = geo.filter(nodes, nearFilter);
}
// do we need some filtration?
if (filter.where) {
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
@ -194,6 +179,9 @@ function applyFilter(filter) {
if (typeof example === 'undefined') return undefined;
if (typeof value === 'undefined') return undefined;
if (typeof example === 'object') {
// ignore geo near filter
if(example.near) return true;
if (example.inq) {
if (!value) return false;
for (var i = 0; i < example.inq.length; i += 1) {
@ -271,11 +259,14 @@ Memory.prototype.exec = function(callback) {
setTimeout(callback, 50);
};
Memory.prototype.buildNearFilter = function (filter) {
// noop
}
function merge(base, update) {
if (!base) return update;
Object.keys(update).forEach(function (key) {
base[key] = update[key];
});
return base;
}
}

View File

@ -14,6 +14,8 @@ var List = require('./list.js');
require('./relations.js');
var Inclusion = require('./include.js');
var Relation = require('./relations.js');
var geo = require('./geo');
var Memory = require('./adapters/memory').Memory;
/**
* DAO class - base class for all persist objects
@ -314,11 +316,55 @@ DataAccessObject.find = function find(params, cb) {
params = null;
}
var constr = this;
var near = params && geo.nearFilter(params.where);
var supportsGeo = !!this.schema.adapter.buildNearFilter;
if(near) {
if(supportsGeo) {
// convert it
this.schema.adapter.buildNearFilter(filter, near);
} else if(params.where) {
// do in memory query
// using all documents
this.schema.adapter.all(this.modelName, {}, function (err, data) {
var memory = new Memory();
var modelName = constr.modelName;
if(err) {
cb(err);
} else if(Array.isArray(data)) {
memory.define({
properties: constr.schema.definitions[constr.modelName].properties,
settings: constr.schema.definitions[constr.modelName].settings,
model: constr
});
data.forEach(function (obj) {
memory.create(modelName, obj, function () {
// noop
});
});
memory.all(modelName, params, cb);
} else {
cb(null, []);
}
}.bind(this));
// already handled
return;
}
}
this.schema.adapter.all(this.modelName, params, function (err, data) {
if (data && data.forEach) {
data.forEach(function (d, i) {
var obj = new constr;
obj._initProperties(d, false);
var obj = new constr(d);
if (params && params.include && params.collect) {
data[i] = obj.__cachedRelations[params.collect];
} else {
@ -328,6 +374,10 @@ DataAccessObject.find = function find(params, cb) {
if (data && data.countBeforeLimit) {
data.countBeforeLimit = data.countBeforeLimit;
}
if(!supportsGeo && near) {
data = geo.filter(data, near);
}
cb(err, data);
}
else

View File

@ -46,6 +46,11 @@ exports.filter = function (arr, filter) {
// filter out objects without locations
if(!loc) return;
if(!(loc instanceof GeoPoint)) {
loc = GeoPoint(loc);
}
if(typeof loc.lat !== 'number') return;
if(typeof loc.lng !== 'number') return;
@ -87,6 +92,20 @@ function GeoPoint(data) {
return new GeoPoint(data);
}
if(typeof data === 'string') {
data = data.split(/,\s*/);
assert(data.length === 2, 'must provide a string "lng,lat" creating a GeoPoint with a string');
}
if(Array.isArray(data)) {
data = {
lng: Number(data[0]),
lat: Number(data[1])
};
} else {
data.lng = Number(data.lng);
data.lat = Number(data.lat);
}
assert(typeof data === 'object', 'must provide a lat and lng object when creating a GeoPoint');
assert(typeof data.lat === 'number', 'lat must be a number when creating a GeoPoint');
assert(typeof data.lng === 'number', 'lng must be a number when creating a GeoPoint');
@ -104,6 +123,13 @@ function GeoPoint(data) {
*/
GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
if(!(a instanceof GeoPoint)) {
a = GeoPoint(a);
}
if(!(b instanceof GeoPoint)) {
b = GeoPoint(b);
}
var x1 = a.lat;
var y1 = a.lng;
@ -121,6 +147,18 @@ GeoPoint.prototype.distanceTo = function (point, options) {
return GeoPoint.distanceBetween(this, point, options);
}
/**
* Simple serialization.
*/
GeoPoint.prototype.toString = function () {
return this.lng + ',' + this.lat;
}
/**
* Si
*/
// ratio of a circle's circumference to its diameter
var PI = 3.1415926535897932384626433832795;
@ -154,4 +192,5 @@ function geoDistance(x1, y1, x2, y2, options) {
var type = (options && options.type) || 'miles';
return 2 * Math.asin( c ) * EARTH_RADIUS[type];
}
}

View File

@ -217,9 +217,6 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
if (value === null || value === undefined) {
this.__data[attr] = value;
} else {
if(attr === 'geo') {
debugger;
}
this.__data[attr] = DataType(value);
}
}

View File

@ -91,7 +91,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
ctor.forEachProperty(function (attr) {
var type = properties[attr].type;
if (BASE_TYPES.indexOf(type.name) === -1) {
if (typeof self.__data[attr] !== 'object' && self.__data[attr]) {
try {