const mongoose = require('mongoose'); const GoogleMaps = require('@google/maps').createClient({ key: 'AIzaSyCvpBGztvxtRUNigOW9f0GXVRWlukJZsps' }); var logger = require('../modules/logger'); const Schema = mongoose.Schema; const GeocacheSchema = new Schema({ key: { type: String, required: true, unique: true }, formatted: { type: String, required: true }, loc: { type: { type: String, default: 'Point' }, coordinates: [{ type: Number, default: [0, 0] }] }, georesult: { type: Schema.Types.Mixed } }); const Conversion = { kilometersToMeters: (distance) => { return parseFloat(distance * 1000); }, kilometersToMiles: (distance) => { return parseFloat(distance / 1.60934); }, kilometersToNauticalMiles: (distance) => { return parseFloat(distance * 0.539957); }, metersToKilometers: (distance) => { return parseFloat(distance / 1000); }, metersToMiles: (distance) => { return parseFloat(distance / 1609.34); }, milesToKilometers: (distance) => { return parseFloat(distance * 1.60934); }, milesToMeters: (distance) => { return parseFloat(distance * 1609.34); }, milesToNauticalMiles: (distance) => { return parseFloat(distance * 0.868976); }, nauticalMilesToMeters: (distance) => { return parseFloat(distance / 0.868976); }, nauticalMilesToMiles: (distance) => { return parseFloat(distance / 0.868976); }, nauticalMilesToKilometers: (distance) => { return parseFloat(distance * 1.852000674128); } }; GeocacheSchema.index({ name: 1, loc: '2dsphere' }); const GeocacheModel = mongoose.model('geocache', GeocacheSchema); /*function distanceBetween (geoJSON1, geoJSON2, unit = 'mi') { var radlat1 = Math.PI * geoJSON1.coordinates[1]/180; var radlat2 = Math.PI * geoJSON2.coordinates[1]/180; var theta = geoJSON1.coordinates[0] - geoJSON2.coordinates[0]; var radtheta = Math.PI * theta/180; var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); dist = Math.acos(dist); dist = dist * 180/Math.PI; dist = dist * 60 * 1.1515; // miles between if (unit == "km") { dist = Conversion.metersToKilometers(Conversion.milesToMeters(dist)); } if (unit == "m") { dist = dist * 1.609344; } if (unit == "n") { dist = dist * 0.8684; } return dist; }*/ function queryGeodataApi (query, callback) { GoogleMaps.geocode({ address: query }, function(err, response) { if (err) { console.error('[GeocacheModel<>] Address Geocoding Error', { address: query, response: response }); callback(null, err, null); } if (response.json && Array.isArray(response.json.results)) { var data = { key: sanitizeNameForKey(query), formatted: response.json.results[0].formatted_address, georesult: response.json.results[0], loc: { type: 'Point', coordinates: [ response.json.results[0].geometry.location.lng, response.json.results[0].geometry.location.lat ] } }; callback(null, null, data); } }); } function sanitizeNameForKey (name) { var key = name.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g,''); key = key.replace(/\s{2,}/g,' '); key = key.trim(); key = key.toLowerCase(); return key; } module.exports = { create: (e, geodata) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { var geocacheInstance = new GeocacheModel(geodata); geocacheInstance.save((err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { cb('create', null, result); }) .catch((err) => { cb('create', err, null); }); }, conversion: Conversion, find: (e, searchText) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.findOne({ key: sanitizeNameForKey(searchText) }, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { cb('find', null, result); }) .catch((err) => { cb('find', err, null); }); }, findLike: (e, searchText) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.find({ key: new RegExp('.*' + sanitizeNameForKey(searchText) + '.*', "i") }, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { cb('findLike', null, result); }) .catch((err) => { cb('findLike', err, null); }); }, findNear: (e, lng, lat, distance) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; var point = { type: 'Point', coordinates: [ lng, lat ] }; var opts = { spherical: true, maxDistance: Conversion.milesToMeters(distance) }; const promise = new Promise((resolve, reject) => { GeocacheModel.geoNear(point, opts, (err, results) => { if (err) { reject(err); } if (results) { resolve(results); } }); }); promise.then((result) => { cb('findNear', null, result); }) .catch((err) => { cb('findNear', err, null); }); }, getGeo: (e, id) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.findById(id, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { cb('getGeos', null, result); }) .catch((err) => { cb('getGeos', err, null); }); }, getGeos: (e) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.find({}, (err, results) => { if (err) { reject(err); } if (results) { resolve(results); } }); }); promise.then((result) => { cb('getGeos', null, result); }) .catch((err) => { cb('getGeos', err, null); }); }, getGeoJSON: (e, searchText) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { queryGeodataApi(searchText, (f, err, geodata) => { if (err) { reject(err); } if (geodata) { resolve(geodata.loc); } }); }); promise.then((result) => { cb('getGeoJSON', null, result); }) .catch((err) => { cb('getGeoJSON', err, null); }); }, getGeoJSONFromCache: (e, searchText) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.findOne({ key: sanitizeNameForKey(searchText) }, (err, result) => { if (err || !result) { queryGeodataApi(searchText, (f, err, geodata) => { if (err) { reject(err); } if (geodata) { let geocacheInstance = new GeocacheModel(geodata); geocacheInstance.save((err, result) => { if (err) { logger.error('[Geocache::getGeoJSON] There was an error creating the GeoJSON entry.', { err: err }); } logger.debug() }); resolve(geodata.loc); } }); } else if (result) { resolve(result.loc); } }); }); promise.then((result) => { cb('getGeoJSONFromCache', null, result); }) .catch((err) => { cb('getGeoJSONFromCache', err, null); }); }, populateFormatted: (e) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.find({}, (err, results) => { if (err) { reject(err); } if (results) { for (let i = 0; i < results.length; i++) { if (!results[i].formatted) { results[i].formatted = results[i].georesult.formatted_address; GeocacheModel.findByIdAndUpdate(results[i]._id, { $set: results[i] }, (err, result) => { if (err) logger.error('There was an error populating the geocache formatted address.'); if (result) logger.log('The geocache entry was updated'); }); } } resolve({ status: 'OK', message: 'The geocache entries have been updated.'}); } }); }); promise.then((result) => { cb('populateFormattedAddresses', null, result); }) .catch((err) => { cb('populateFormattedAddresses', err, null); }); }, populateKeys: (e) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.find({}, (err, results) => { if (err) { reject(err); } if (results) { for (let i = 0; i < results.length; i++) { if (!results[i].key) { results[i].key = sanitizeNameForKey(results[i].name); GeocacheModel.findByIdAndUpdate(results[i]._id, { $set: results[i] }, (err, result) => { if (err) logger.error('There was an error populating the geocache entry key.'); if (result) logger.log('The geocache entry was updated'); }); } } resolve({ status: 'OK', message: 'The geocache entries have been updated.'}); } }); }); promise.then((result) => { cb('populateKeys', null, result); }) .catch((err) => { cb('populateKeys', err, null); }); }, update: (e, id, geodata) => { var cb = typeof e === 'object' && e.emit ? e.emit.bind(e) : e; const promise = new Promise((resolve, reject) => { GeocacheModel.findByIdAndUpdate(id, { $set: geodata }, (err, result) => { if (err) { reject(err); } if (result) { resolve(result); } }); }); promise.then((result) => { cb('update', null, result); }) .catch((err) => { cb('update', err, null); }); } };