From 12fb1c346477b43b60e7d51c567e75829adcb8a8 Mon Sep 17 00:00:00 2001 From: jacobawenger Date: Fri, 7 Nov 2014 15:33:58 -0800 Subject: [PATCH] Reverted changes to example's geofire.min.js file --- examples/fish2/js/vendor/geofire.min.js | 1180 +---------------------- 1 file changed, 1 insertion(+), 1179 deletions(-) diff --git a/examples/fish2/js/vendor/geofire.min.js b/examples/fish2/js/vendor/geofire.min.js index 49714f78..1ce1f27a 100644 --- a/examples/fish2/js/vendor/geofire.min.js +++ b/examples/fish2/js/vendor/geofire.min.js @@ -9,1182 +9,4 @@ * https://github.com/firebase/geofire-js/ * License: MIT */ - -// Include RSVP if this is being run in node -if (typeof module !== "undefined" && typeof process !== "undefined") { - var RSVP = require("rsvp"); -} - -var GeoFire = (function() { - "use strict"; -/** - * Creates a GeoCallbackRegistration instance. - * - * @constructor - * @this {GeoCallbackRegistration} - * @callback cancelCallback Callback to run when this callback registration is cancelled. - */ -var GeoCallbackRegistration = function(cancelCallback) { - /********************/ - /* PUBLIC METHODS */ - /********************/ - /** - * Cancels this callback registration so that it no longer fires its callback. This - * has no effect on any other callback registrations you may have created. - */ - this.cancel = function() { - if (typeof _cancelCallback !== "undefined") { - _cancelCallback(); - _cancelCallback = undefined; - } - }; - - /*****************/ - /* CONSTRUCTOR */ - /*****************/ - if (typeof cancelCallback !== "function") { - throw new Error("callback must be a function"); - } - - var _cancelCallback = cancelCallback; -}; -/** - * Creates a GeoFire instance. - * - * @constructor - * @this {GeoFire} - * @param {Firebase} firebaseRef A Firebase reference where the GeoFire data will be stored. - */ -var GeoFire = function(firebaseRef) { - /********************/ - /* PUBLIC METHODS */ - /********************/ - /** - * Returns the Firebase instance used to create this GeoFire instance. - * - * @return {Firebase} The Firebase instance used to create this GeoFire instance. - */ - this.ref = function() { - return _firebaseRef; - }; - - /** - * Adds the provided key - location pair to Firebase. Returns an empty promise which is fulfilled when the write is complete. - * - * If the provided key already exists in this GeoFire, it will be overwritten with the new location value. - * - * @param {string} key The key representing the location to add. - * @param {Array.} location The [latitude, longitude] pair to add. - * @return {Promise.<>} A promise that is fulfilled when the write is complete. - */ - this.set = function(key, location) { - validateKey(key); - if (location !== null) { - // Setting location to null is valid since it will remove the key - validateLocation(location); - } - return new RSVP.Promise(function(resolve, reject) { - function onComplete(error) { - if (error) { - reject("Error: Firebase synchronization failed: " + error); - } - else { - resolve(); - } - } - if (location === null) { - _firebaseRef.child(key).remove(onComplete); - } else { - var geohash = encodeGeohash(location); - _firebaseRef.child(key).setWithPriority(encodeGeoFireObject(location, geohash), geohash, onComplete); - } - }); - }; - - /** - * Returns a promise fulfilled with the location corresponding to the provided key. - * - * If the provided key does not exist, the returned promise is fulfilled with null. - * - * @param {string} key The key of the location to retrieve. - * @return {Promise.>} A promise that is fulfilled with the location of the given key. - */ - this.get = function(key) { - validateKey(key); - return new RSVP.Promise(function(resolve, reject) { - _firebaseRef.child(key).once("value", function(dataSnapshot) { - if (dataSnapshot.val() === null) { - resolve(null); - } else { - resolve(decodeGeoFireObject(dataSnapshot.val())); - } - }, function (error) { - reject("Error: Firebase synchronization failed: " + error); - }); - }); - }; - - /** - * Removes the provided key from this GeoFire. Returns an empty promise fulfilled when the key has been removed. - * - * If the provided key is not in this GeoFire, the promise will still successfully resolve. - * - * @param {string} key The key of the location to remove. - * @return {Promise.} A promise that is fulfilled after the inputted key is removed. - */ - this.remove = function(key) { - return this.set(key, null); - }; - - /** - * Returns a new GeoQuery instance with the provided queryCriteria. - * - * @param {Object} queryCriteria The criteria which specifies the GeoQuery's center and radius. - * @return {GeoQuery} A new GeoQuery object. - */ - this.query = function(queryCriteria) { - return new GeoQuery(_firebaseRef, queryCriteria); - }; - - /*****************/ - /* CONSTRUCTOR */ - /*****************/ - if (Object.prototype.toString.call(firebaseRef) !== "[object Object]") { - throw new Error("firebaseRef must be an instance of Firebase"); - } - - var _firebaseRef = firebaseRef; -}; - -/** - * Static method which calculates the distance, in kilometers, between two locations, - * via the Haversine formula. Note that this is approximate due to the fact that the - * Earth's radius varies between 6356.752 km and 6378.137 km. - * - * @param {Array.} location1 The [latitude, longitude] pair of the first location. - * @param {Array.} location2 The [latitude, longitude] pair of the second location. - * @return {number} The distance, in kilometers, between the inputted locations. - */ -GeoFire.distance = function(location1, location2) { - validateLocation(location1); - validateLocation(location2); - - var radius = 6371; // Earth's radius in kilometers - var latDelta = degreesToRadians(location2[0] - location1[0]); - var lonDelta = degreesToRadians(location2[1] - location1[1]); - - var a = (Math.sin(latDelta / 2) * Math.sin(latDelta / 2)) + - (Math.cos(degreesToRadians(location1[0])) * Math.cos(degreesToRadians(location2[0])) * - Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2)); - - var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return radius * c; -}; - -// Default geohash length -var g_GEOHASH_PRECISION = 10; - -// Characters used in location geohashes -var g_BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"; - -// The meridional circumference of the earth in meters -var g_EARTH_MERI_CIRCUMFERENCE = 40007860; - -// Length of a degree latitude at the equator -var g_METERS_PER_DEGREE_LATITUDE = 110574; - -// Number of bits per geohash character -var g_BITS_PER_CHAR = 5; - -// Maximum length of a geohash in bits -var g_MAXIMUM_BITS_PRECISION = 22*g_BITS_PER_CHAR; - -// Equatorial radius of the earth in meters -var g_EARTH_EQ_RADIUS = 6378137.0; - -// The following value assumes a polar radius of -// var g_EARTH_POL_RADIUS = 6356752.3; -// The formulate to calculate g_E2 is -// g_E2 == (g_EARTH_EQ_RADIUS^2-g_EARTH_POL_RADIUS^2)/(g_EARTH_EQ_RADIUS^2) -// The exact value is used here to avoid rounding errors -var g_E2 = 0.00669447819799; - -// Cutoff for rounding errors on double calculations -var g_EPSILON = 1e-12; - -Math.log2 = Math.log2 || function(x) { - return Math.log(x)/Math.log(2); -}; - -/** - * Validates the inputted key and throws an error if it is invalid. - * - * @param {string} key The key to be verified. - */ -var validateKey = function(key) { - var error; - - if (typeof key !== "string") { - error = "key must be a string"; - } - else if (key.length === 0) { - error = "key cannot be the empty string"; - } - else if (1 + g_GEOHASH_PRECISION + key.length > 755) { - // Firebase can only stored child paths up to 768 characters - // The child path for this key is at the least: "i/key" - error = "key is too long to be stored in Firebase"; - } - else if (/[\[\].#$\/\u0000-\u001F\u007F]/.test(key)) { - // Firebase does not allow node keys to contain the following characters - error = "key cannot contain any of the following characters: . # $ ] [ /"; - } - - if (typeof error !== "undefined") { - throw new Error("Invalid GeoFire key '" + key + "': " + error); - } -}; - -/** - * Validates the inputted location and throws an error if it is invalid. - * - * @param {Array.} location The [latitude, longitude] pair to be verified. - */ -var validateLocation = function(location) { - var error; - - if (!Array.isArray(location)) { - error = "location must be an array"; - } - else if (location.length !== 2) { - error = "expected array of length 2, got length " + location.length; - } - else { - var latitude = location[0]; - var longitude = location[1]; - - if (typeof latitude !== "number" || isNaN(latitude)) { - error = "latitude must be a number"; - } - else if (latitude < -90 || latitude > 90) { - error = "latitude must be within the range [-90, 90]"; - } - else if (typeof longitude !== "number" || isNaN(longitude)) { - error = "longitude must be a number"; - } - else if (longitude < -180 || longitude > 180) { - error = "longitude must be within the range [-180, 180]"; - } - } - - if (typeof error !== "undefined") { - throw new Error("Invalid GeoFire location '" + location + "': " + error); - } -}; - -/** - * Validates the inputted geohash and throws an error if it is invalid. - * - * @param {string} geohash The geohash to be validated. - */ -var validateGeohash = function(geohash) { - var error; - - if (typeof geohash !== "string") { - error = "geohash must be a string"; - } - else if (geohash.length === 0) { - error = "geohash cannot be the empty string"; - } - else { - for (var i = 0, length = geohash.length; i < length; ++i) { - if (g_BASE32.indexOf(geohash[i]) === -1) { - error = "geohash cannot contain \"" + geohash[i] + "\""; - } - } - } - - if (typeof error !== "undefined") { - throw new Error("Invalid GeoFire geohash '" + geohash + "': " + error); - } -}; - -/** - * Validates the inputted query criteria and throws an error if it is invalid. - * - * @param {Object} newQueryCriteria The criteria which specifies the query's center and/or radius. - */ -var validateCriteria = function(newQueryCriteria, requireCenterAndRadius) { - if (typeof newQueryCriteria !== "object") { - throw new Error("query criteria must be an object"); - } - else if (typeof newQueryCriteria.center === "undefined" && typeof newQueryCriteria.radius === "undefined") { - throw new Error("radius and/or center must be specified"); - } - else if (requireCenterAndRadius && (typeof newQueryCriteria.center === "undefined" || typeof newQueryCriteria.radius === "undefined")) { - throw new Error("query criteria for a new query must contain both a center and a radius"); - } - - // Throw an error if there are any extraneous attributes - var keys = Object.keys(newQueryCriteria); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var key = keys[i]; - if (key !== "center" && key !== "radius") { - throw new Error("Unexpected attribute '" + key + "'' found in query criteria"); - } - } - - // Validate the "center" attribute - if (typeof newQueryCriteria.center !== "undefined") { - validateLocation(newQueryCriteria.center); - } - - // Validate the "radius" attribute - if (typeof newQueryCriteria.radius !== "undefined") { - if (typeof newQueryCriteria.radius !== "number" || isNaN(newQueryCriteria.radius)) { - throw new Error("radius must be a number"); - } - else if (newQueryCriteria.radius < 0) { - throw new Error("radius must be greater than or equal to 0"); - } - } -}; - -/** - * Converts degrees to radians. - * - * @param {number} degrees The number of degrees to be converted to radians. - * @return {number} The number of radians equal to the inputted number of degrees. - */ -var degreesToRadians = function(degrees) { - if (typeof degrees !== "number" || isNaN(degrees)) { - throw new Error("Error: degrees must be a number"); - } - - return (degrees * Math.PI / 180); -}; - -/** - * Generates a geohash of the specified precision/string length from the [latitude, longitude] - * pair, specified as an array. - * - * @param {Array.} location The [latitude, longitude] pair to encode into a geohash. - * @param {number=} precision The length of the geohash to create. If no precision is - * specified, the global default is used. - * @return {string} The geohash of the inputted location. - */ -var encodeGeohash = function(location, precision) { - validateLocation(location); - if (typeof precision !== "undefined") { - if (typeof precision !== "number" || isNaN(precision)) { - throw new Error("precision must be a number"); - } - else if (precision <= 0) { - throw new Error("precision must be greater than 0"); - } - else if (precision > 22) { - throw new Error("precision cannot be greater than 22"); - } - else if (Math.round(precision) !== precision) { - throw new Error("precision must be an integer"); - } - } - - // Use the global precision default if no precision is specified - precision = precision || g_GEOHASH_PRECISION; - - var latitudeRange = { - min: -90, - max: 90 - }; - var longitudeRange = { - min: -180, - max: 180 - }; - var hash = ""; - var hashVal = 0; - var bits = 0; - var even = 1; - - while (hash.length < precision) { - var val = even ? location[1] : location[0]; - var range = even ? longitudeRange : latitudeRange; - var mid = (range.min + range.max) / 2; - - /* jshint -W016 */ - if (val > mid) { - hashVal = (hashVal << 1) + 1; - range.min = mid; - } - else { - hashVal = (hashVal << 1) + 0; - range.max = mid; - } - /* jshint +W016 */ - - even = !even; - if (bits < 4) { - bits++; - } - else { - bits = 0; - hash += g_BASE32[hashVal]; - hashVal = 0; - } - } - - return hash; -}; - -/** - * Calculates the number of degrees a given distance is at a given latitude. - * - * @param {number} distance The distance to convert. - * @param {number} latitude The latitude at which to calculate. - * @return {number} The number of degrees the distance corresponds to. - */ -var metersToLongitudeDegrees = function(distance, latitude) { - var radians = degreesToRadians(latitude); - var num = Math.cos(radians)*g_EARTH_EQ_RADIUS*Math.PI/180; - var denom = 1/Math.sqrt(1-g_E2*Math.sin(radians)*Math.sin(radians)); - var deltaDeg = num*denom; - if (deltaDeg < g_EPSILON) { - return distance > 0 ? 360 : 0; - } - else { - return Math.min(360, distance/deltaDeg); - } -}; - -/** - * Calculates the bits necessary to reach a given resolution, in meters, for the longitude at a - * given latitude. - * - * @param {number} resolution The desired resolution. - * @param {number} latitude The latitude used in the conversion. - * @return {number} The bits necessary to reach a given resolution, in meters. - */ -var longitudeBitsForResolution = function(resolution, latitude) { - var degs = metersToLongitudeDegrees(resolution, latitude); - return (Math.abs(degs) > 0.000001) ? Math.max(1, Math.log2(360/degs)) : 1; -}; - -/** - * Calculates the bits necessary to reach a given resolution, in meters, for the latitude. - * - * @param {number} resolution The bits necessary to reach a given resolution, in meters. - */ -var latitudeBitsForResolution = function(resolution) { - return Math.min(Math.log2(g_EARTH_MERI_CIRCUMFERENCE/2/resolution), g_MAXIMUM_BITS_PRECISION); -}; - -/** - * Wraps the longitude to [-180,180]. - * - * @param {number} longitude The longitude to wrap. - * @return {number} longitude The resulting longitude. - */ -var wrapLongitude = function(longitude) { - if (longitude <= 180 && longitude >= -180) { - return longitude; - } - var adjusted = longitude + 180; - if (adjusted > 0) { - return (adjusted % 360) - 180; - } - else { - return 180 - (-adjusted % 360); - } -}; - -/** - * Calculates the maximum number of bits of a geohash to get a bounding box that is larger than a - * given size at the given coordinate. - * - * @param {Array.} coordinate The coordinate as a [latitude, longitude] pair. - * @param {number} size The size of the bounding box. - * @return {number} The number of bits necessary for the geohash. - */ -var boundingBoxBits = function(coordinate, size) { - var latDeltaDegrees = size/g_METERS_PER_DEGREE_LATITUDE; - var latitudeNorth = Math.min(90, coordinate[0] + latDeltaDegrees); - var latitudeSouth = Math.max(-90, coordinate[0] - latDeltaDegrees); - var bitsLat = Math.floor(latitudeBitsForResolution(size))*2; - var bitsLongNorth = Math.floor(longitudeBitsForResolution(size, latitudeNorth))*2-1; - var bitsLongSouth = Math.floor(longitudeBitsForResolution(size, latitudeSouth))*2-1; - return Math.min(bitsLat, bitsLongNorth, bitsLongSouth, g_MAXIMUM_BITS_PRECISION); -}; - -/** - * Calculates eight points on the bounding box and the center of a given circle. At least one - * geohash of these nine coordinates, truncated to a precision of at most radius, are guaranteed - * to be prefixes of any geohash that lies within the circle. - * - * @param {Array.} center The center given as [latitude, longitude]. - * @param {number} radius The radius of the circle. - * @return {Array.>} The eight bounding box points. - */ -var boundingBoxCoordinates = function(center, radius) { - var latDegrees = radius/g_METERS_PER_DEGREE_LATITUDE; - var latitudeNorth = Math.min(90, center[0] + latDegrees); - var latitudeSouth = Math.max(-90, center[0] - latDegrees); - var longDegsNorth = metersToLongitudeDegrees(radius, latitudeNorth); - var longDegsSouth = metersToLongitudeDegrees(radius, latitudeSouth); - var longDegs = Math.max(longDegsNorth, longDegsSouth); - return [ - [center[0], center[1]], - [center[0], wrapLongitude(center[1] - longDegs)], - [center[0], wrapLongitude(center[1] + longDegs)], - [latitudeNorth, center[1]], - [latitudeNorth, wrapLongitude(center[1] - longDegs)], - [latitudeNorth, wrapLongitude(center[1] + longDegs)], - [latitudeSouth, center[1]], - [latitudeSouth, wrapLongitude(center[1] - longDegs)], - [latitudeSouth, wrapLongitude(center[1] + longDegs)] - ]; -}; - -/** - * Calculates the bounding box query for a geohash with x bits precision. - * - * @param {string} geohash The geohash whose bounding box query to generate. - * @param {number} bits The number of bits of precision. - * @return {Array.} A [start, end] pair of geohashes. - */ -var geohashQuery = function(geohash, bits) { - validateGeohash(geohash); - var precision = Math.ceil(bits/g_BITS_PER_CHAR); - if (geohash.length < precision) { - return [geohash, geohash+"~"]; - } - geohash = geohash.substring(0, precision); - var base = geohash.substring(0, geohash.length - 1); - var lastValue = g_BASE32.indexOf(geohash.charAt(geohash.length - 1)); - var significantBits = bits - (base.length*g_BITS_PER_CHAR); - var unusedBits = (g_BITS_PER_CHAR - significantBits); - /*jshint bitwise: false*/ - // delete unused bits - var startValue = (lastValue >> unusedBits) << unusedBits; - var endValue = startValue + (1 << unusedBits); - /*jshint bitwise: true*/ - if (endValue > 31) { - return [base+g_BASE32[startValue], base+"~"]; - } - else { - return [base+g_BASE32[startValue], base+g_BASE32[endValue]]; - } -}; - -/** - * Calculates a set of queries to fully contain a given circle. A query is a [start, end] pair - * where any geohash is guaranteed to be lexiographically larger then start and smaller than end. - * - * @param {Array.} center The center given as [latitude, longitude] pair. - * @param {number} radius The radius of the circle. - * @return {Array.>} An array of geohashes containing a [start, end] pair. - */ -var geohashQueries = function(center, radius) { - validateLocation(center); - var queryBits = Math.max(1, boundingBoxBits(center, radius)); - var geohashPrecision = Math.ceil(queryBits/g_BITS_PER_CHAR); - var coordinates = boundingBoxCoordinates(center, radius); - var queries = coordinates.map(function(coordinate) { - return geohashQuery(encodeGeohash(coordinate, geohashPrecision), queryBits); - }); - // remove duplicates - return queries.filter(function(query, index) { - return !queries.some(function(other, otherIndex) { - return index > otherIndex && query[0] === other[0] && query[1] === other[1]; - }); - }); -}; - -/** - * Encodes a location and geohash as a GeoFire object. - * - * @param {Array.} location The location as [latitude, longitude] pair. - * @param {string} geohash The geohash of the location. - * @return {Object} The location encoded as GeoFire object. - */ -function encodeGeoFireObject(location, geohash) { - validateLocation(location); - validateGeohash(geohash); - return { - "g": geohash, - "l": location - }; -} - -/** - * Decodes the location given as GeoFire object. Returns null if decoding fails. - * - * @param {Object} geoFireObj The location encoded as GeoFire object. - * @return {?Array.} location The location as [latitude, longitude] pair or null if - * decoding fails. - */ -function decodeGeoFireObject(geoFireObj) { - if (geoFireObj !== null && geoFireObj.hasOwnProperty("l") && Array.isArray(geoFireObj.l) && geoFireObj.l.length === 2) { - return geoFireObj.l; - } else { - throw new Error("Unexpected GeoFire location object encountered: " + JSON.stringify(geoFireObj)); - } -} - -/** - * Creates a GeoQuery instance. - * - * @constructor - * @this {GeoQuery} - * @param {Firebase} firebaseRef A Firebase reference. - * @param {Object} queryCriteria The criteria which specifies the query's center and radius. - */ -var GeoQuery = function (firebaseRef, queryCriteria) { - /*********************/ - /* PRIVATE METHODS */ - /*********************/ - /** - * Fires each callback for the provided eventType, passing it provided key's data. - * - * @param {string} eventType The event type whose callbacks to fire. One of "key_entered", "key_exited", or "key_moved". - * @param {string} key The key of the location for which to fire the callbacks. - * @param {?Array.} location The location as [latitude, longitude] pair - * @param {?double} distanceFromCenter The distance from the center or null. - */ - function _fireCallbacksForKey(eventType, key, location, distanceFromCenter) { - _callbacks[eventType].forEach(function(callback) { - if (typeof location === "undefined" || location === null) { - callback(key, null, null); - } - else { - callback(key, location, distanceFromCenter); - } - }); - } - - /** - * Fires each callback for the "ready" event. - */ - function _fireReadyEventCallbacks() { - _callbacks.ready.forEach(function(callback) { - callback(); - }); - } - - /** - * Decodes a query string to a query - * - * @param {string} str The encoded query. - * @return {Array.} The decoded query as a [start, end] pair. - */ - function _stringToQuery(string) { - var decoded = string.split(":"); - if (decoded.length !== 2) { - throw new Error("Invalid internal state! Not a valid geohash query: " + string); - } - return decoded; - } - - /** - * Encodes a query as a string for easier indexing and equality. - * - * @param {Array.} query The query to encode. - * @param {string} The encoded query as string. - */ - function _queryToString(query) { - if (query.length !== 2) { - throw new Error("Not a valid geohash query: " + query); - } - return query[0]+":"+query[1]; - } - - /** - * Turns off all callbacks for the provide geohash query. - * - * @param {Array.} query The geohash query. - * @param {Object} queryState An object storing the current state of the query. - */ - function _cancelGeohashQuery(query, queryState) { - var queryRef = _firebaseRef.orderByChild("g").startAt(query[0]).endAt(query[1]); - queryRef.off("child_added", queryState.childAddedCallback); - queryRef.off("child_removed", queryState.childRemovedCallback); - queryRef.off("child_changed", queryState.childChangedCallback); - queryRef.off("value", queryState.valueCallback); - } - - /** - * Removes unnecessary Firebase queries which are currently being queried. - */ - function _cleanUpCurrentGeohashesQueried() { - var keys = Object.keys(_currentGeohashesQueried); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var geohashQueryStr = keys[i]; - var queryState = _currentGeohashesQueried[geohashQueryStr]; - if (queryState.active === false) { - var query = _stringToQuery(geohashQueryStr); - // Delete the geohash since it should no longer be queried - _cancelGeohashQuery(query, queryState); - delete _currentGeohashesQueried[geohashQueryStr]; - } - } - - // Delete each location which should no longer be queried - keys = Object.keys(_locationsTracked); - numKeys = keys.length; - for (i = 0; i < numKeys; ++i) { - var key = keys[i]; - if (!_geohashInSomeQuery(_locationsTracked[key].geohash)) { - if (_locationsTracked[key].isInQuery) { - throw new Error("Internal State error, trying to remove location that is still in query"); - } - delete _locationsTracked[key]; - } - } - - // Specify that this is done cleaning up the current geohashes queried - _geohashCleanupScheduled = false; - - // Cancel any outstanding scheduled cleanup - if (_cleanUpCurrentGeohashesQueriedTimeout !== null) { - clearTimeout(_cleanUpCurrentGeohashesQueriedTimeout); - _cleanUpCurrentGeohashesQueriedTimeout = null; - } - } - - /** - * Callback for any updates to locations. Will update the information about a key and fire any necessary - * events every time the key's location changes. - * - * When a key is removed from GeoFire or the query, this function will be called with null and performs - * any necessary cleanup. - * - * @param {string} key The key of the geofire location. - * @param {?Array.} location The location as [latitude, longitude] pair. - */ - function _updateLocation(key, location) { - validateLocation(location); - // Get the key and location - var distanceFromCenter, isInQuery; - var wasInQuery = (_locationsTracked.hasOwnProperty(key)) ? _locationsTracked[key].isInQuery : false; - var oldLocation = (_locationsTracked.hasOwnProperty(key)) ? _locationsTracked[key].location : null; - - // Determine if the location is within this query - distanceFromCenter = GeoFire.distance(location, _center); - isInQuery = (distanceFromCenter <= _radius); - - // Add this location to the locations queried dictionary even if it is not within this query - _locationsTracked[key] = { - location: location, - distanceFromCenter: distanceFromCenter, - isInQuery: isInQuery, - geohash: encodeGeohash(location, g_GEOHASH_PRECISION) - }; - - // Fire the "key_entered" event if the provided key has entered this query - if (isInQuery && !wasInQuery) { - _fireCallbacksForKey("key_entered", key, location, distanceFromCenter); - } else if (isInQuery && oldLocation !== null && (location[0] !== oldLocation[0] || location[1] !== oldLocation[1])) { - _fireCallbacksForKey("key_moved", key, location, distanceFromCenter); - } else if (!isInQuery && wasInQuery) { - _fireCallbacksForKey("key_exited", key, location, distanceFromCenter); - } - } - - /** - * Checks if this geohash is currently part of any of the geohash queries. - * - * @param {string} geohash The geohash. - * @param {boolean} Returns true if the geohash is part of any of the current geohash queries. - */ - function _geohashInSomeQuery(geohash) { - var keys = Object.keys(_currentGeohashesQueried); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var queryStr = keys[i]; - if (_currentGeohashesQueried.hasOwnProperty(queryStr)) { - var query = _stringToQuery(queryStr); - if (geohash >= query[0] && geohash <= query[1]) { - return true; - } - } - } - return false; - } - - /** - * Removes the location from the local state and fires any events if necessary. - * - * @param {string} key The key to be removed. - * @param {?Array.} currentLocation The current location as [latitude, longitude] pair - * or null if removed. - */ - function _removeLocation(key, currentLocation) { - var locationDict = _locationsTracked[key]; - delete _locationsTracked[key]; - if (typeof locationDict !== "undefined" && locationDict.isInQuery) { - var distanceFromCenter = (currentLocation) ? GeoFire.distance(currentLocation, _center) : null; - _fireCallbacksForKey("key_exited", key, currentLocation, distanceFromCenter); - } - } - - /** - * Callback for child added events. - * - * @param {Firebase DataSnapshot} locationDataSnapshot A snapshot of the data stored for this location. - */ - function _childAddedCallback(locationDataSnapshot) { - _updateLocation(locationDataSnapshot.key(), decodeGeoFireObject(locationDataSnapshot.val())); - } - - /** - * Callback for child changed events - * - * @param {Firebase DataSnapshot} locationDataSnapshot A snapshot of the data stored for this location. - */ - function _childChangedCallback(locationDataSnapshot) { - _updateLocation(locationDataSnapshot.key(), decodeGeoFireObject(locationDataSnapshot.val())); - } - - /** - * Callback for child removed events - * - * @param {Firebase DataSnapshot} locationDataSnapshot A snapshot of the data stored for this location. - */ - function _childRemovedCallback(locationDataSnapshot) { - var key = locationDataSnapshot.key(); - if (_locationsTracked.hasOwnProperty(key)) { - _firebaseRef.child(key).once("value", function(snapshot) { - var location = snapshot.val() === null ? null : decodeGeoFireObject(snapshot.val()); - var geohash = (location !== null) ? encodeGeohash(location) : null; - // Only notify observers if key is not part of any other geohash query or this actually might not be - // a key exited event, but a key moved or entered event. These events will be triggered by updates - // to a different query - if (!_geohashInSomeQuery(geohash)) { - _removeLocation(key, location); - } - }); - } - } - - /** - * Called once all geohash queries have received all child added events and fires the ready - * event if necessary. - */ - function _geohashQueryReadyCallback(queryStr) { - var index = _outstandingGeohashReadyEvents.indexOf(queryStr); - if (index > -1) { - _outstandingGeohashReadyEvents.splice(index, 1); - } - _valueEventFired = (_outstandingGeohashReadyEvents.length === 0); - - // If all queries have been processed, fire the ready event - if (_valueEventFired) { - _fireReadyEventCallbacks(); - } - } - - /** - * Attaches listeners to Firebase which track when new geohashes are added within this query's - * bounding box. - */ - function _listenForNewGeohashes() { - // Get the list of geohashes to query - var geohashesToQuery = geohashQueries(_center, _radius*1000).map(_queryToString); - - // Filter out duplicate geohashes - geohashesToQuery = geohashesToQuery.filter(function(geohash, i){ - return geohashesToQuery.indexOf(geohash) === i; - }); - - // For all of the geohashes that we are already currently querying, check if they are still - // supposed to be queried. If so, don't re-query them. Otherwise, mark them to be un-queried - // next time we clean up the current geohashes queried dictionary. - var keys = Object.keys(_currentGeohashesQueried); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var geohashQueryStr = keys[i]; - var index = geohashesToQuery.indexOf(geohashQueryStr); - if (index === -1) { - _currentGeohashesQueried[geohashQueryStr].active = false; - } - else { - _currentGeohashesQueried[geohashQueryStr].active = true; - geohashesToQuery.splice(index, 1); - } - } - - // If we are not already cleaning up the current geohashes queried and we have more than 25 of them, - // kick off a timeout to clean them up so we don't create an infinite number of unneeded queries. - if (_geohashCleanupScheduled === false && Object.keys(_currentGeohashesQueried).length > 25) { - _geohashCleanupScheduled = true; - _cleanUpCurrentGeohashesQueriedTimeout = setTimeout(_cleanUpCurrentGeohashesQueried, 10); - } - - // Keep track of which geohashes have been processed so we know when to fire the "ready" event - _outstandingGeohashReadyEvents = geohashesToQuery.slice(); - - // Loop through each geohash to query for and listen for new geohashes which have the same prefix. - // For every match, attach a value callback which will fire the appropriate events. - // Once every geohash to query is processed, fire the "ready" event. - geohashesToQuery.forEach(function(toQueryStr) { - // decode the geohash query string - var query = _stringToQuery(toQueryStr); - - // Create the Firebase query - var firebaseQuery = _firebaseRef.orderByChild("g").startAt(query[0]).endAt(query[1]); - - // For every new matching geohash, determine if we should fire the "key_entered" event - var childAddedCallback = firebaseQuery.on("child_added", _childAddedCallback); - var childRemovedCallback = firebaseQuery.on("child_removed", _childRemovedCallback); - var childChangedCallback = firebaseQuery.on("child_changed", _childChangedCallback); - - // Once the current geohash to query is processed, see if it is the last one to be processed - // and, if so, mark the value event as fired. - // Note that Firebase fires the "value" event after every "child_added" event fires. - var valueCallback = firebaseQuery.on("value", function() { - firebaseQuery.off("value", valueCallback); - _geohashQueryReadyCallback(toQueryStr); - }); - - // Add the geohash query to the current geohashes queried dictionary and save its state - _currentGeohashesQueried[toQueryStr] = { - active: true, - childAddedCallback: childAddedCallback, - childRemovedCallback: childRemovedCallback, - childChangedCallback: childChangedCallback, - valueCallback: valueCallback - }; - }); - // Based upon the algorithm to calculate geohashes, it's possible that no "new" - // geohashes were queried even if the client updates the radius of the query. - // This results in no "READY" event being fired after the .updateQuery() call. - // Check to see if this is the case, and trigger the "READY" event. - if(geohashesToQuery.length === 0) { - _geohashQueryReadyCallback(); - } - } - - /********************/ - /* PUBLIC METHODS */ - /********************/ - /** - * Returns the location signifying the center of this query. - * - * @return {Array.} The [latitude, longitude] pair signifying the center of this query. - */ - this.center = function() { - return _center; - }; - - /** - * Returns the radius of this query, in kilometers. - * - * @return {number} The radius of this query, in kilometers. - */ - this.radius = function() { - return _radius; - }; - - /** - * Updates the criteria for this query. - * - * @param {Object} newQueryCriteria The criteria which specifies the query's center and radius. - */ - this.updateCriteria = function(newQueryCriteria) { - // Validate and save the new query criteria - validateCriteria(newQueryCriteria); - _center = newQueryCriteria.center || _center; - _radius = newQueryCriteria.radius || _radius; - - // Loop through all of the locations in the query, update their distance from the center of the - // query, and fire any appropriate events - var keys = Object.keys(_locationsTracked); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var key = keys[i]; - - // Get the cached information for this location - var locationDict = _locationsTracked[key]; - - // Save if the location was already in the query - var wasAlreadyInQuery = locationDict.isInQuery; - - // Update the location's distance to the new query center - locationDict.distanceFromCenter = GeoFire.distance(locationDict.location, _center); - - // Determine if the location is now in this query - locationDict.isInQuery = (locationDict.distanceFromCenter <= _radius); - - // If the location just left the query, fire the "key_exited" callbacks - if (wasAlreadyInQuery && !locationDict.isInQuery) { - _fireCallbacksForKey("key_exited", key, locationDict.location, locationDict.distanceFromCenter); - } - - // If the location just entered the query, fire the "key_entered" callbacks - else if (!wasAlreadyInQuery && locationDict.isInQuery) { - _fireCallbacksForKey("key_entered", key, locationDict.location, locationDict.distanceFromCenter); - } - } - - // Reset the variables which control when the "ready" event fires - _valueEventFired = false; - - // Listen for new geohashes being added to GeoFire and fire the appropriate events - _listenForNewGeohashes(); - }; - - /** - * Attaches a callback to this query which will be run when the provided eventType fires. Valid eventType - * values are "ready", "key_entered", "key_exited", and "key_moved". The ready event callback is passed no - * parameters. All other callbacks will be passed three parameters: (1) the location's key, (2) the location's - * [latitude, longitude] pair, and (3) the distance, in kilometers, from the location to this query's center - * - * "ready" is used to signify that this query has loaded its initial state and is up-to-date with its corresponding - * GeoFire instance. "ready" fires when this query has loaded all of the initial data from GeoFire and fired all - * other events for that data. It also fires every time updateQuery() is called, after all other events have - * fired for the updated query. - * - * "key_entered" fires when a key enters this query. This can happen when a key moves from a location outside of - * this query to one inside of it or when a key is written to GeoFire for the first time and it falls within - * this query. - * - * "key_exited" fires when a key moves from a location inside of this query to one outside of it. If the key was - * entirely removed from GeoFire, both the location and distance passed to the callback will be null. - * - * "key_moved" fires when a key which is already in this query moves to another location inside of it. - * - * Returns a GeoCallbackRegistration which can be used to cancel the callback. You can add as many callbacks - * as you would like for the same eventType by repeatedly calling on(). Each one will get called when its - * corresponding eventType fires. Each callback must be cancelled individually. - * - * @param {string} eventType The event type for which to attach the callback. One of "ready", "key_entered", - * "key_exited", or "key_moved". - * @callback callback Callback function to be called when an event of type eventType fires. - * @return {GeoCallbackRegistration} A callback registration which can be used to cancel the provided callback. - */ - this.on = function(eventType, callback) { - // Validate the inputs - if (["ready", "key_entered", "key_exited", "key_moved"].indexOf(eventType) === -1) { - throw new Error("event type must be \"ready\", \"key_entered\", \"key_exited\", or \"key_moved\""); - } - if (typeof callback !== "function") { - throw new Error("callback must be a function"); - } - - // Add the callback to this query's callbacks list - _callbacks[eventType].push(callback); - - // If this is a "key_entered" callback, fire it for every location already within this query - if (eventType === "key_entered") { - var keys = Object.keys(_locationsTracked); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var key = keys[i]; - var locationDict = _locationsTracked[key]; - if (locationDict.isInQuery) { - callback(key, locationDict.location, locationDict.distanceFromCenter); - } - } - } - - // If this is a "ready" callback, fire it if this query is already ready - if (eventType === "ready") { - if (_valueEventFired) { - callback(); - } - } - - // Return an event registration which can be used to cancel the callback - return new GeoCallbackRegistration(function() { - _callbacks[eventType].splice(_callbacks[eventType].indexOf(callback), 1); - }); - }; - - /** - * Terminates this query so that it no longer sends location updates. All callbacks attached to this - * query via on() will be cancelled. This query can no longer be used in the future. - */ - this.cancel = function () { - // Cancel all callbacks in this query's callback list - _callbacks = { - ready: [], - key_entered: [], - key_exited: [], - key_moved: [] - }; - - // Turn off all Firebase listeners for the current geohashes being queried - var keys = Object.keys(_currentGeohashesQueried); - var numKeys = keys.length; - for (var i = 0; i < numKeys; ++i) { - var geohashQueryStr = keys[i]; - var query = _stringToQuery(geohashQueryStr); - _cancelGeohashQuery(query, _currentGeohashesQueried[geohashQueryStr]); - delete _currentGeohashesQueried[geohashQueryStr]; - } - - // Delete any stored locations - _locationsTracked = {}; - - // Turn off the current geohashes queried clean up interval - clearInterval(_cleanUpCurrentGeohashesQueriedInterval); - }; - - - /*****************/ - /* CONSTRUCTOR */ - /*****************/ - // Firebase reference of the GeoFire which created this query - if (Object.prototype.toString.call(firebaseRef) !== "[object Object]") { - throw new Error("firebaseRef must be an instance of Firebase"); - } - var _firebaseRef = firebaseRef; - - // Event callbacks - var _callbacks = { - ready: [], - key_entered: [], - key_exited: [], - key_moved: [] - }; - - // Variables used to keep track of when to fire the "ready" event - var _valueEventFired = false; - var _outstandingGeohashReadyEvents; - - // A dictionary of locations that a currently active in the queries - // Note that not all of these are currently within this query - var _locationsTracked = {}; - - // A dictionary of geohash queries which currently have an active callbacks - var _currentGeohashesQueried = {}; - - // Every ten seconds, clean up the geohashes we are currently querying for. We keep these around - // for a little while since it's likely that they will need to be re-queried shortly after they - // move outside of the query's bounding box. - var _geohashCleanupScheduled = false; - var _cleanUpCurrentGeohashesQueriedTimeout = null; - var _cleanUpCurrentGeohashesQueriedInterval = setInterval(function() { - if (_geohashCleanupScheduled === false) { - _cleanUpCurrentGeohashesQueried(); - } - }, 10000); - - // Validate and save the query criteria - validateCriteria(queryCriteria, /* requireCenterAndRadius */ true); - var _center = queryCriteria.center; - var _radius = queryCriteria.radius; - - // Listen for new geohashes being added around this query and fire the appropriate events - _listenForNewGeohashes(); -}; - - return GeoFire; -})(); - -// Export GeoFire if this is being run in node -if (typeof module !== "undefined" && typeof process !== "undefined") { - module.exports = GeoFire; -} +if("undefined"!=typeof module&&"undefined"!=typeof process)var RSVP=require("rsvp");var GeoFire=function(){"use strict";function e(e,n){return y(e),v(n),{g:n,l:e}}function n(e){if(null!==e&&e.hasOwnProperty("l")&&Array.isArray(e.l)&&2===e.l.length)return e.l;throw new Error("Unexpected GeoFire location object encountered: "+JSON.stringify(e))}var t=function(e){if(this.cancel=function(){"undefined"!=typeof n&&(n(),n=void 0)},"function"!=typeof e)throw new Error("callback must be a function");var n=e},r=function(t){if(this.ref=function(){return r},this.set=function(n,t){return h(n),null!==t&&y(t),new RSVP.Promise(function(i,o){function a(e){e?o("Error: Firebase synchronization failed: "+e):i()}if(null===t)r.child(n).remove(a);else{var u=g(t);r.child(n).setWithPriority(e(t,u),u,a)}})},this.get=function(e){return h(e),new RSVP.Promise(function(t,i){r.child(e).once("value",function(e){t(null===e.val()?null:n(e.val()))},function(e){i("Error: Firebase synchronization failed: "+e)})})},this.remove=function(e){return this.set(e,null)},this.query=function(e){return new j(r,e)},"[object Object]"!==Object.prototype.toString.call(t))throw new Error("firebaseRef must be an instance of Firebase");var r=t};r.distance=function(e,n){y(e),y(n);var t=6371,r=b(n[0]-e[0]),i=b(n[1]-e[1]),o=Math.sin(r/2)*Math.sin(r/2)+Math.cos(b(e[0]))*Math.cos(b(n[0]))*Math.sin(i/2)*Math.sin(i/2),a=2*Math.atan2(Math.sqrt(o),Math.sqrt(1-o));return t*a};var i=10,o="0123456789bcdefghjkmnpqrstuvwxyz",a=40007860,u=110574,c=5,f=22*c,l=6378137,s=.00669447819799,d=1e-12;Math.log2=Math.log2||function(e){return Math.log(e)/Math.log(2)};var h=function(e){var n;if("string"!=typeof e?n="key must be a string":0===e.length?n="key cannot be the empty string":1+i+e.length>755?n="key is too long to be stored in Firebase":/[\[\].#$\/\u0000-\u001F\u007F]/.test(e)&&(n="key cannot contain any of the following characters: . # $ ] [ /"),"undefined"!=typeof n)throw new Error("Invalid GeoFire key '"+e+"': "+n)},y=function(e){var n;if(Array.isArray(e))if(2!==e.length)n="expected array of length 2, got length "+e.length;else{var t=e[0],r=e[1];"number"!=typeof t||isNaN(t)?n="latitude must be a number":-90>t||t>90?n="latitude must be within the range [-90, 90]":"number"!=typeof r||isNaN(r)?n="longitude must be a number":(-180>r||r>180)&&(n="longitude must be within the range [-180, 180]")}else n="location must be an array";if("undefined"!=typeof n)throw new Error("Invalid GeoFire location '"+e+"': "+n)},v=function(e){var n;if("string"!=typeof e)n="geohash must be a string";else if(0===e.length)n="geohash cannot be the empty string";else for(var t=0,r=e.length;r>t;++t)-1===o.indexOf(e[t])&&(n='geohash cannot contain "'+e[t]+'"');if("undefined"!=typeof n)throw new Error("Invalid GeoFire geohash '"+e+"': "+n)},m=function(e,n){if("object"!=typeof e)throw new Error("query criteria must be an object");if("undefined"==typeof e.center&&"undefined"==typeof e.radius)throw new Error("radius and/or center must be specified");if(n&&("undefined"==typeof e.center||"undefined"==typeof e.radius))throw new Error("query criteria for a new query must contain both a center and a radius");for(var t=Object.keys(e),r=t.length,i=0;r>i;++i){var o=t[i];if("center"!==o&&"radius"!==o)throw new Error("Unexpected attribute '"+o+"'' found in query criteria")}if("undefined"!=typeof e.center&&y(e.center),"undefined"!=typeof e.radius){if("number"!=typeof e.radius||isNaN(e.radius))throw new Error("radius must be a number");if(e.radius<0)throw new Error("radius must be greater than or equal to 0")}},b=function(e){if("number"!=typeof e||isNaN(e))throw new Error("Error: degrees must be a number");return e*Math.PI/180},g=function(e,n){if(y(e),"undefined"!=typeof n){if("number"!=typeof n||isNaN(n))throw new Error("precision must be a number");if(0>=n)throw new Error("precision must be greater than 0");if(n>22)throw new Error("precision cannot be greater than 22");if(Math.round(n)!==n)throw new Error("precision must be an integer")}n=n||i;for(var t={min:-90,max:90},r={min:-180,max:180},a="",u=0,c=0,f=1;a.lengthd?(u=(u<<1)+1,s.min=d):(u=(u<<1)+0,s.max=d),f=!f,4>c?c++:(c=0,a+=o[u],u=0)}return a},p=function(e,n){var t=b(n),r=Math.cos(t)*l*Math.PI/180,i=1/Math.sqrt(1-s*Math.sin(t)*Math.sin(t)),o=r*i;return d>o?e>0?360:0:Math.min(360,e/o)},w=function(e,n){var t=p(e,n);return Math.abs(t)>1e-6?Math.max(1,Math.log2(360/t)):1},k=function(e){return Math.min(Math.log2(a/2/e),f)},M=function(e){if(180>=e&&e>=-180)return e;var n=e+180;return n>0?n%360-180:180- -n%360},E=function(e,n){var t=n/u,r=Math.min(90,e[0]+t),i=Math.max(-90,e[0]-t),o=2*Math.floor(k(n)),a=2*Math.floor(w(n,r))-1,c=2*Math.floor(w(n,i))-1;return Math.min(o,a,c,f)},x=function(e,n){var t=n/u,r=Math.min(90,e[0]+t),i=Math.max(-90,e[0]-t),o=p(n,r),a=p(n,i),c=Math.max(o,a);return[[e[0],e[1]],[e[0],M(e[1]-c)],[e[0],M(e[1]+c)],[r,e[1]],[r,M(e[1]-c)],[r,M(e[1]+c)],[i,e[1]],[i,M(e[1]-c)],[i,M(e[1]+c)]]},O=function(e,n){v(e);var t=Math.ceil(n/c);if(e.length>u<31?[r+o[f],r+"~"]:[r+o[f],r+o[l]]},_=function(e,n){y(e);var t=Math.max(1,E(e,n)),r=Math.ceil(t/c),i=x(e,n),o=i.map(function(e){return O(g(e,r),t)});return o.filter(function(e,n){return!o.some(function(t,r){return n>r&&e[0]===t[0]&&e[1]===t[1]})})},j=function(e,o){function a(e,n,t,r){O[e].forEach(function(e){"undefined"==typeof t||null===t?e(n,null,null):e(n,t,r)})}function u(){O.ready.forEach(function(e){e()})}function c(e){var n=e.split(":");if(2!==n.length)throw new Error("Invalid internal state! Not a valid geohash query: "+e);return n}function f(e){if(2!==e.length)throw new Error("Not a valid geohash query: "+e);return e[0]+":"+e[1]}function l(e,n){var t=x.orderByChild("g").startAt(e[0]).endAt(e[1]);t.off("child_added",n.childAddedCallback),t.off("child_removed",n.childRemovedCallback),t.off("child_changed",n.childChangedCallback),t.off("value",n.valueCallback)}function s(){for(var e=Object.keys(F),n=e.length,t=0;n>t;++t){var r=e[t],i=F[r];if(i.active===!1){var o=c(r);l(o,i),delete F[r]}}for(e=Object.keys(C),n=e.length,t=0;n>t;++t){var a=e[t];if(!h(C[a].geohash)){if(C[a].isInQuery)throw new Error("Internal State error, trying to remove location that is still in query");delete C[a]}}I=!1,null!==q&&(clearTimeout(q),q=null)}function d(e,n){y(n);var t,o,u=C.hasOwnProperty(e)?C[e].isInQuery:!1,c=C.hasOwnProperty(e)?C[e].location:null;t=r.distance(n,P),o=A>=t,C[e]={location:n,distanceFromCenter:t,isInQuery:o,geohash:g(n,i)},o&&!u?a("key_entered",e,n,t):!o||null===c||n[0]===c[0]&&n[1]===c[1]?!o&&u&&a("key_exited",e,n,t):a("key_moved",e,n,t)}function h(e){for(var n=Object.keys(F),t=n.length,r=0;t>r;++r){var i=n[r];if(F.hasOwnProperty(i)){var o=c(i);if(e>=o[0]&&e<=o[1])return!0}}return!1}function v(e,n){var t=C[e];if(delete C[e],"undefined"!=typeof t&&t.isInQuery){var i=n?r.distance(n,P):null;a("key_exited",e,n,i)}}function b(e){d(e.key(),n(e.val()))}function p(e){d(e.key(),n(e.val()))}function w(e){var t=e.key();C.hasOwnProperty(t)&&x.child(t).once("value",function(e){var r=null===e.val()?null:n(e.val()),i=null!==r?g(r):null;h(i)||v(t,r)})}function k(e){var n=E.indexOf(e);n>-1&&E.splice(n,1),j=0===E.length,j&&u()}function M(){var e=_(P,1e3*A).map(f);e=e.filter(function(n,t){return e.indexOf(n)===t});for(var n=Object.keys(F),t=n.length,r=0;t>r;++r){var i=n[r],o=e.indexOf(i);-1===o?F[i].active=!1:(F[i].active=!0,e.splice(o,1))}I===!1&&Object.keys(F).length>25&&(I=!0,q=setTimeout(s,10)),E=e.slice(),e.forEach(function(e){var n=c(e),t=x.orderByChild("g").startAt(n[0]).endAt(n[1]),r=t.on("child_added",b),i=t.on("child_removed",w),o=t.on("child_changed",p),a=t.on("value",function(){t.off("value",a),k(e)});F[e]={active:!0,childAddedCallback:r,childRemovedCallback:i,childChangedCallback:o,valueCallback:a}}),0===e.length&&k()}if(this.center=function(){return P},this.radius=function(){return A},this.updateCriteria=function(e){m(e),P=e.center||P,A=e.radius||A;for(var n=Object.keys(C),t=n.length,i=0;t>i;++i){var o=n[i],u=C[o],c=u.isInQuery;u.distanceFromCenter=r.distance(u.location,P),u.isInQuery=u.distanceFromCenter<=A,c&&!u.isInQuery?a("key_exited",o,u.location,u.distanceFromCenter):!c&&u.isInQuery&&a("key_entered",o,u.location,u.distanceFromCenter)}j=!1,M()},this.on=function(e,n){if(-1===["ready","key_entered","key_exited","key_moved"].indexOf(e))throw new Error('event type must be "ready", "key_entered", "key_exited", or "key_moved"');if("function"!=typeof n)throw new Error("callback must be a function");if(O[e].push(n),"key_entered"===e)for(var r=Object.keys(C),i=r.length,o=0;i>o;++o){var a=r[o],u=C[a];u.isInQuery&&n(a,u.location,u.distanceFromCenter)}return"ready"===e&&j&&n(),new t(function(){O[e].splice(O[e].indexOf(n),1)})},this.cancel=function(){O={ready:[],key_entered:[],key_exited:[],key_moved:[]};for(var e=Object.keys(F),n=e.length,t=0;n>t;++t){var r=e[t],i=c(r);l(i,F[r]),delete F[r]}C={},clearInterval(N)},"[object Object]"!==Object.prototype.toString.call(e))throw new Error("firebaseRef must be an instance of Firebase");var E,x=e,O={ready:[],key_entered:[],key_exited:[],key_moved:[]},j=!1,C={},F={},I=!1,q=null,N=setInterval(function(){I===!1&&s()},1e4);m(o,!0);var P=o.center,A=o.radius;M()};return r}();"undefined"!=typeof module&&"undefined"!=typeof process&&(module.exports=GeoFire); \ No newline at end of file