Created
August 10, 2017 19:41
-
-
Save psaia/314e15b954276d4b7633bb8ea18b92e3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const zoomScale = [ // [ radius, min points ] | |
| [0, 10], | |
| [0.9, 10], // 1 | |
| [0.7, 10], // 2 | |
| [0.6, 10], // 3 | |
| [0.5, 10], // 4 | |
| [0.4, 9], // 5 | |
| [0.3, 8], // 6 | |
| [0.13, 3], // 7 | |
| [0.09, 3], // 8 | |
| [0.07, 2], // 9 | |
| [0.05, 2], // 10 | |
| [0.0239, 2], // 11 | |
| [0.019, 2], // 12 | |
| [0.002, 2], // 13 | |
| [0.0019, 2], // 14 | |
| [0.0008, 5], // 15 | |
| [0.0007, 5], // 16 | |
| [0.00006, 5], // 17 | |
| [0.00000001, 5], // 18 | |
| [0.000000001, 5], // 19 | |
| [0.0000000001, 5], // 20 | |
| [0.00000000001, 5] // 21 | |
| ]; | |
| /** | |
| * This method is used for the map queries only. | |
| */ | |
| schema.statics.spatialFind = function(organization, zoom, bllng, bllat, urlng, urlat, cats, photos, surveys, callback) { | |
| console.time('clustering'); | |
| // Minimum number of points in a cluster. | |
| const POINT_NUM = zoomScale[zoom][1]; | |
| // Margin radius. | |
| const RADIUS = zoomScale[zoom][0]; | |
| const aggregates = []; | |
| const match = { | |
| $match: { | |
| organization: mongoose.Types.ObjectId(organization), | |
| location: { | |
| $exists: true, | |
| $not: { | |
| $size: 0 | |
| } | |
| } | |
| } | |
| }; | |
| if (_.isArray(cats)) { | |
| match.$match.categories = { | |
| $in: cats.map(mongoose.Types.ObjectId) | |
| }; | |
| }; | |
| if (surveys && photos) { | |
| match.$match.$or = [ | |
| { | |
| survey: { | |
| $elemMatch: { | |
| pub: true | |
| } | |
| } | |
| }, | |
| { | |
| photoCount: { | |
| $gt: 0 | |
| } | |
| } | |
| ]; | |
| } else { | |
| if (surveys) { | |
| match.$match.survey = { | |
| $elemMatch: { | |
| pub: true | |
| } | |
| }; | |
| } else if (photos) { | |
| match.$match.photoCount = { | |
| $gte: 1 | |
| }; | |
| } | |
| } | |
| match.$match.location = { | |
| $geoWithin: { | |
| $box: [ | |
| [bllng, bllat], | |
| [urlng, urlat] | |
| ] | |
| } | |
| }; | |
| aggregates.push( | |
| match, | |
| { | |
| $unwind: '$location' | |
| }, | |
| { | |
| $group: { | |
| _id: '$_id', | |
| photoCount: { | |
| $sum: '$photoCount' | |
| }, | |
| city: { | |
| $first: '$city' | |
| }, | |
| state: { | |
| $first: '$state' | |
| }, | |
| zip: { | |
| $first: '$zip' | |
| }, | |
| street: { | |
| $first: '$street' | |
| }, | |
| surveyCompleted: { | |
| $first: '$surveyCompleted' | |
| }, | |
| location: { | |
| $push: '$location' | |
| } | |
| } | |
| }, | |
| { | |
| $project: { | |
| a: { | |
| city: '$city', | |
| state: '$state', | |
| zip: '$zip', | |
| street: '$street', | |
| }, | |
| s: '$surveyCompleted', | |
| p: '$photoCount', // Number of photos. | |
| l: '$location' // Coordinates. | |
| } | |
| } | |
| ); | |
| Model.aggregate(aggregates, function(err, results) { | |
| if (err) { | |
| return callback(err); | |
| } | |
| const coords = results.map(pt => [pt.l[1], pt.l[0]]); // [lat, lng] | |
| const completeClusters = []; | |
| const singles = []; | |
| const populateSingles = () => { | |
| for (let i = 0, len = results.length; i < len; i++) { | |
| singles.push(results[i]); | |
| } | |
| }; | |
| // To cluster or not to cluster is the question. | |
| if (zoom < 13) { | |
| const dbscan = new clustering.DBSCAN(); | |
| const indexClusters = dbscan.run(coords, RADIUS, POINT_NUM); | |
| console.log('result count', results.length); | |
| console.log('noise count', dbscan.noise.length); | |
| console.log('cluster count', indexClusters.length); | |
| for (let i = 0, len = indexClusters.length; i < len; i++) { | |
| const clusterIndexRow = indexClusters[i]; | |
| const clusterPointRow = clusterIndexRow.map(i => { | |
| return results[i]; | |
| }); | |
| const center = centroid(clusterPointRow.map(p => p.l)); | |
| completeClusters.push({ | |
| pts: clusterPointRow, | |
| c: center | |
| }); | |
| } | |
| if (!completeClusters.length) { | |
| populateSingles(); | |
| } | |
| } else { | |
| populateSingles(); | |
| } | |
| async.parallel([ | |
| function(callback) { | |
| Model.count( | |
| { | |
| organization: mongoose.Types.ObjectId(organization), | |
| location: { | |
| $exists: true, | |
| $not: { | |
| $size: 0 | |
| } | |
| }, | |
| }, | |
| callback | |
| ); | |
| }, | |
| function(callback) { | |
| Model.count( | |
| { | |
| organization: mongoose.Types.ObjectId(organization), | |
| location: { | |
| $exists: true, | |
| $not: { | |
| $size: 0 | |
| } | |
| }, | |
| survey: { | |
| $elemMatch: { | |
| pub: true | |
| } | |
| } | |
| }, | |
| callback | |
| ); | |
| }, | |
| function(callback) { | |
| Model.count( | |
| { | |
| organization: mongoose.Types.ObjectId(organization), | |
| location: { | |
| $exists: true, | |
| $not: { | |
| $size: 0 | |
| } | |
| }, | |
| photoCount: { | |
| $gte: 1 | |
| } | |
| }, | |
| callback | |
| ); | |
| } | |
| ], | |
| function(err, results) { | |
| console.timeEnd('clustering'); | |
| if (err) { | |
| return callback(err); | |
| } | |
| callback(null, { | |
| total: results[0], | |
| hasReviews: results[1], | |
| hasPhotos: results[2], | |
| clusters: completeClusters, | |
| singles: singles | |
| }); | |
| }); | |
| }); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment