Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-paths update and Geofire #76

Open
cmellinas opened this issue Oct 5, 2015 · 8 comments
Open

Multi-paths update and Geofire #76

cmellinas opened this issue Oct 5, 2015 · 8 comments

Comments

@cmellinas
Copy link

Hi,

As mentioned here, I think it should be great to easily multi update data paths and a geofire path at the same time. As the set of Geofire is pretty complex, it's hard to directly copy/paste the code to make it works.

Here the use case : (I use the same as the link above but add a path to know where a post is located). So the structure :

{
  posts: {
    post1: {
      from : user1,
      message: "Hi",
    },
    post2: {
      from : user2,
      message: "Hey",
    }
  },
  posts-location: {
    post1: {
      g : 'xxxxxxxx',
      l: {
         0 : latitude,
         1 : longitude
      }
    },
    post2: {
       g : 'xxxxxxxx',
       l: {
         0 : latitude,
         1 : longitude
      }
    }
  },
  users: {
    user1: {
      posts: {
        post1: true
      }
    },
    user2: {
      posts: {
        post2: true
      }
    }
  }
}

Now I can add a post at the same time at users and posts (with multi-path update). But I need to set the position after. In my use case, a post without a position is not valid data, it's for that the best should be to do the 3 updates at the same times (if one fails, all fail).

Deleting Geofire path is easy (using update(null)), we can multi-update for delete, but for set/update, I loop for a way to do that easily,

Cheers,
Clément

@jwngr
Copy link

jwngr commented Oct 6, 2015

I think this is a great suggestion and I think we can find a way to work this into the API. It will be a little while before I have time to implement this, but I'm happy to code review PRs from the community. Otherwise, once I have some time to take a crack at this, I'll go ahead and see how we can support this kind of feature. Thanks for opening this issue!

@cmellinas
Copy link
Author

Ah great Jacob
Hope you will found a solution

Many thanks,
Clément

@kz
Copy link

kz commented Jul 15, 2016

+1. Atomic updates are really important to ensure the integrity of the database.

@vikas-anvaya
Copy link

Do we have a solution for the problem now.

@ghost
Copy link

ghost commented Aug 23, 2016

+1

maybe instead return _firebaseRef.update(newData); use return newData; ... then we can use ref().update function with other data

@jcheroske
Copy link

Playing fast and loose here, so disregard if this is totally whacked, but couldn't we just have a method that returns the geofire data that would be updated? Then we could just use the existing atomic update api.

@mversteeg3
Copy link

Any movement on this? I like @jcheroske's suggestion as long as there isn't any behind the scenes wizardry in the "set" method that would confound this solution

@deflexable
Copy link

checking the set function inside module at ../node_modules/geofire/dist/geofire/index.cjs.js" there wasn't really sophisticated method or external library that help update geofire hash to firebase. It's just plan method and validation.

as @ghost suggested earlier, instead of returning a promise for updating the data, you can simple return newData, then do the uploading yourself.

you can simply copy and paste below function, then you can do something like

`var map = {};
map['eventIndex/' + v] = getGeoFireHashes(eventKey, [location.lat, location.lon]);

firebase.database().ref('/').update(map);`

`function getGeoFireHashes(keyOrLocations, location) {
var locations;
if (typeof keyOrLocations === 'string' && keyOrLocations.length !== 0) {
// If this is a set for a single location, convert it into a object
locations = {};
locations[keyOrLocations] = location;
} else if (typeof keyOrLocations === 'object') {
if (typeof location !== 'undefined') {
throw new Error('The location argument should not be used if you pass an object to set().');
}
locations = keyOrLocations;
} else {
throw new Error('keyOrLocations must be a string or a mapping of key - location pairs.');
}
var newData = {};
Object.keys(locations).forEach(function (key) {
validateKey(key);
var location = locations[key];
if (location === null) {
// Setting location to null is valid since it will remove the key
newData[key] = null;
} else {
validateLocation(location);
var geohash = geohashForLocation(location);
newData[key] = encodeGeoFireObject(location, geohash);
}
});
return newData;
}

/**

  • Encodes a location and geohash as a GeoFire object.
  • @param location The location as [latitude, longitude] pair.
  • @param geohash The geohash of the location.
  • @returns The location encoded as GeoFire object.
    */
    function encodeGeoFireObject(location, geohash) {
    validateLocation(location);
    validateGeohash(geohash);
    return { '.priority': geohash, 'g': geohash, 'l': location };
    }

/**

  • Generates a geohash of the specified precision/string length from the [latitude, longitude]
  • pair, specified as an array.
  • @param location The [latitude, longitude] pair to encode into a geohash.
  • @param precision The length of the geohash to create. If no precision is specified, the
  • global default is used.
  • @returns The geohash of the inputted location.
    */
    function geohashForLocation(location, precision) {
    if (precision === void 0) { precision = GEOHASH_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');
    }
    }
    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;
    if (val > mid) {
    hashVal = (hashVal << 1) + 1;
    range.min = mid;
    } else {
    hashVal = (hashVal << 1) + 0;
    range.max = mid;
    }
    even = !even;
    if (bits < 4) {
    bits++;
    } else {
    bits = 0;
    hash += BASE32[hashVal];
    hashVal = 0;
    }
    }
    return hash;
    }

/**

  • Validates the inputted geohash and throws an error if it is invalid.
  • @param geohash The geohash to be validated.
    */
    function validateGeohash(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, geohash_1 = geohash; _i < geohash_1.length; _i++) {
    var letter = geohash_1[_i];
    if (BASE32.indexOf(letter) === -1) {
    error = 'geohash cannot contain '' + letter + ''';
    }
    }
    }
    if (typeof error !== 'undefined') {
    throw new Error('Invalid GeoFire geohash '' + geohash + '': ' + error);
    }
    }

function validateKey(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 + 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);
}
}

function validateLocation(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);
}
}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants