elasticsearch rails - illegal latitude value - ruby-on-rails

When trying to search from latitude dynamically from records in my index I've been getting an illegal latitude value. When looking at my index I can't see any invalid latitude and longitude values, so I'm guessing it's an error in my code.
Exact error;
{"type":"query_parsing_exception","reason":"illegal latitude value [269.99999983236194] for [geo_distance]","index":"addresses","line":1,"col":172}}]},"status":400}
Model code for search;
def self.search(query)
__elasticsearch__.search(
{
query:{
multi_match: {
query: query,
fields: ['full_address']
}
},
filter:{
geo_distance:{
distance: "6miles",
location: "address.location"
}
}
}
)
end
Mapping;
{ "addresses" : {
"mappings" : {
"address" : {
"properties" : {
"county" : {
"type" : "string"
},
"full_address" : {
"type" : "string"
},
"location" : {
"type" : "geo_point"
}
}
}
}
}
}

I experienced the same error today but with longitude. Turns out Elasticsearch version 2.0 and newer only allows longitude values from range -180..180.
For example version 1.7.5 (which I had in my local environment) worked with both ranges -180..180 and 0..360.
Your error states:
illegal latitude value [269.99999983236194]
For latitude, only values from range -90..90 are allowed.
The only fix is to re-compute lat/long values before sending them to Elasticsearch. For example for longitude you could do:
((longitude.to_f + 180) % 360) - 180

It's better to clean longitude with something like this:
$adjustedLongitude = function($vongitude) {
$result = $vongitude - 360 * floor($vongitude / 360);
if (abs($result) > 180) {
$result = 360 - $result;
}
return $result;
}

Related

Firebase queryOrderedByChild() method not giving sorted data

My database structure is some thing like this:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"score": 4,
},
"ghopper": { ... },
"eclarke": { ... }
}
}
I am trying to retrieve top 20 scores in descending order.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToLast(20)
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
print(querySnapShot.value)
})
i am trying to get output like
score": 4
score": 3
score": 2
or
score": 2
score": 3
score": 4
or
2
3
4
Please let me know how to solve this.
When you request the children in a specific order, the resulting snapshot will contain both the data that matches the query and information about the order in which you requested them.
But when you request the .value of the snapshot, the keys+data are converted to a Dictionary<String,AnyObject>. Since a dictionary does not have an extra place to put the information about the order, that information is lost when converting to a dictionary.
The solution is to not convert to a dictionary prematurely and instead loop over the snapshot:
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
for childSnapshot in querySnapShot.children {
print(childSnapshot.value)
}
})
You can also listen to the .ChildAdded event, instead of .Value, in which case the children will arrive in the correct value:
queryRef.observeSingleEventOfType(.ChildAdded, withBlock: { (childSnapshot) in
print(childSnapshot.value)
})
Update
I just added this JSON to my database:
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
And then ran this code:
let queryRef = ref.child("users").queryOrderedByChild("score").queryLimitedToLast(20);
queryRef.observeEventType(.ChildAdded) { (snapshot) in
print(snapshot.key)
}
The output is:
ghopper
alovelace
eclarke
Which is the users in ascending order of score.
Update to add more on getting the scores in descending order
The above code gets the 20 highest scores in ascending order. There is no API call to return themthem in descending score.
But reversing 20 items client side is no performance concern, you just need to write the code for it. See for example this answer.
If you really are stuck on reversing them client side, you can add an inverted score. See this answer for an example of that.
Use method observeEventType instead of observeSingleEventOfType.
Also, make FIRDataEventType to ChildAdded.
Last, If you want Top 20 items, use queryLimitedToFirst instead of queryLimitedToLast.
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
For the dataset above
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
print("key: \(snapshot.key), value: \(snapshot.value)")
})
key: ghopper, value: Optional({
name = Grace Hopper;
score = 2;
})
key: alovelace, value: Optional({
name = Ada Lovelace;
score = 4;
})
key: eclarke, value: Optional({
name = Emily Clarke;
score = 5;
})
Snapshot will returns the contents as native types.
Data types returned:
NSDictionary
NSArray
NSNumber (also includes booleans)
NSString
So, you can get your scores this way.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let scores = snapshot.value as? NSDictionary {
print(scores["score"])
}
})
Optional(2)
Optional(4)
Optional(5)
Moreover, the default of realtime database return everything in ascending order.
If you want descending order, you can make some tricks(4:40) in your database.

Server side filtering of Firebase data

I have a Firebase db structure as follows:
{
"listings" : {
"-KOt8OUGkUphoMyqEXJ2" : {
"created" : 1470911323208,
"ends" : 1470911323209,
"make" : "LONDON TAXIS INT",
"model" : "TX4 BRONZE",
"status" : "For Sale",
},
"-KOt97_P8sJW7woED4aH" : {
"created" : 1470911515115,
"ends" : 1471775515000,
"make" : "NISSAN",
"model" : "QASHQAI N-TEC",
"status" : "For Sale",
},
"-KOt_BYYUEaXu_LNvnUv" : {
"created" : 1470918609414,
"ends" : 1471782609000,
"make" : "MAZDA",
"model" : "3 TS",
"status" : "For Sale",
}
}
}
I use GeoFire to get keys for listings in a given radius. I then use observeSingleEventOfType to return the listing for the key. This all works fine.
However, I would like to only return a listing if the "ends" timestamp > current time. I have tried the following approach:
geoQueryHandle = geoQuery.observeEventType(.KeyEntered, withBlock: {(key, location) in
let listingRef = self.ref.child("listings").child(key)
let now = Int(NSDate().timeIntervalSince1970 * 1000)
let query = listingRef.queryStartingAtValue(now, childKey: "ends")
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
//...populate my tableview
}
}
})
Can anyone advise me on why my query isn't working?
Thanks
Firebase queries apply ordering/filtering to the properties of the child nodes of the location where you execute them.
queryLocation
child1
filterProperty: filterValue
child2
filterProperty: filterValue
Since your listingRef already points to a specific listing, the order/filter you add will apply to child nodes one level deeper.
The simplest way to get your use-case working seems to simply filter the node client-side:
let listingRef = self.ref.child("listings").child(key)
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
let now = Int(NSDate().timeIntervalSince1970 * 1000)
// TODO: exit if snapshot.child("ends").getValue() < now
//...populate my tableview
}
}
A more complex, but more efficient, way to accomplish this would be to remove the expired events from the Geofire location.

Filter array of array with keyword in Swift

let searchResultItem1 = SearchResult()
searchResultItem1.type = "contact"
searchResultItem1.typeTitle = "CONTACTS"
searchResultItem1.results = ["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
let searchResultItem2 = SearchResult()
searchResultItem2.type = "address"
searchResultItem2.typeTitle = "ADDRESS"
searchResultItem2.results = ["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
searchResults.append(searchResultItem1)
searchResults.append(searchResultItem2)
When i search for "al" i am expecting to return one SearchResult with its results "[Alan]" and another SearchResult with its result "["669 Windsor Drive Randallstown"]"
I tried the below filter , but returns empty array.
let results = searchResults.filter({$0.results.contains("al")})
As both other answers point out, you're searching your array of results for an item with a value of "al". What you're wanting to is actually return an array of results, narrowed down to only those that match:
struct SearchResult {
let type:String
let typeTitle:String
let results:[String]
}
let searchResultItem1 = SearchResult(
type: "contact",
typeTitle: "CONTACTS",
results: ["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
)
let searchResultItem2 = SearchResult(
type:"address",
typeTitle: "ADDRESS",
results:["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
)
var searchResults = [ searchResultItem1, searchResultItem2 ]
Now then, again, for convenience, define a case insensitive contains function for String:
extension String {
func containsIgnoreCase(substring:String) -> Bool {
return rangeOfString(
substring,
options: .CaseInsensitiveSearch,
range: startIndex..<endIndex,
locale: nil)?.startIndex != nil
}
}
Note that String already has a contains function, it's just case sensitive, but if that's sufficient, you don't even need to define your own.
Now, you can use map to get rid of results that don't contain your search string:
searchResults = searchResults.map({
return SearchResult(
type: $0.type,
typeTitle: $0.typeTitle,
results: $0.results.filter({
$0.containsIgnoreCase("al")
})
)
})
And, presumably, you also want to eliminate any SearchResult with no actual results, so use filter:
searchResults = searchResults.filter { $0.results.count > 0 }
Of course, the whole thing can be strung into one expression:
searchResults = searchResults.map({
return SearchResult(
type: $0.type,
typeTitle: $0.typeTitle,
results: $0.results.filter({
$0.contains("al")
})
)
}).filter { $0.results.count > 0 }
And, you can possibly further reduce some of the iteration by using flatMap, which is like map, but eliminates any nil values:
searchResults = searchResults.flatMap {
let results = $0.results.filter { $0.containsIgnoreCase("al") }
if results.count > 0 {
return SearchResult(type: $0.type, typeTitle: $0.typeTitle, results: results)
} else {
return nil
}
}
Within your filter, $0.results is, in each example:
["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
The Array.contains(_) function searches for an object exactly matching it's argument, not a substring of the argument
To get each entire array that contains a string element with the substring "al", use:
let results = searchResults.filter({$0.results.filter({$0.rangeOfString("al") != nil}).count > 0})
Alternatively, to get just the strings with the substring "al", use:
let results = searchResults.map{ $0.results }.reduce([], combine: {$0 + $1}).filter { $0.rangeOfString("al") != nil }
Note that this won't match "Alan" because rangeOfString is case-sensitive.
Your filter checks if the results array contains an entry of "al", not if any of the strings in results contains "al". You need to filter the results array and construct a new SearchResult with the filtered results array.
I would probably flat map a function that filtered the results array of the input SearchResult and return an optional depending on whether or not any matches were found.

Get City, State, Country from Latitude and Longitude in Google Sheets

In Google Sheets, I have a column with latitude and longitude coordinates. The list goes from A2:A1000. I also have columns for City, State, and Country in B1, C1, and D1, respectively. Is there a formula or script I can run that reads the coordinates and provides the city, state, and country in their respective column? I do not know how to use JavaScript, XML, JSON, serialized PHP, etc. so if your suggestion includes one of those, please provide some instructions. Thanks in advance.
Well, I have a psuedo-solution. Go into Google Spreadsheets > Tools > Script Editor and paste the following code into a blank project:
function reverse_geocode(lat,lng) {
Utilities.sleep(1500);
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
Logger.log('%s: %s, %s', result.formatted_address, result.geometry.location.lat,
result.geometry.location.lng);
return result.formatted_address;
}
}
Then in the spreadsheet, use this formula:
=reverse_geocode(latitude_goes_here,longitude_goes_here)
For example, if I have the latitude in A2 and longitude in B2:
=reverse_geocode(A2,B2)
This will provide the full address. I'm now trying to figure out how to parse the country from the address.
Based on #gabriel-rotman solution.
Go into Google Spreadsheets > Tools > Script Editor and paste the following code into a blank project:
/**
* Return the closest, human-readable address type based on the the latitude and longitude values specified.
*
* #param {"locality"} addressType Address type. Examples of address types include
* a street address, a country, or a political entity.
* For more info check: https://developers.google.com/maps/documentation/geocoding/intro#Types
* #param {"52.379219"} lat Latitude
* #param {"4.900174"} lng Longitude
* #customfunction
*/
function reverseGeocode(addressType, lat, lng) {
Utilities.sleep(1500);
if (typeof addressType != 'string') {
throw new Error("addressType should be a string.");
}
if (typeof lat != 'number') {
throw new Error("lat should be a number");
}
if (typeof lng != 'number') {
throw new Error("lng should be a number");
}
var response = Maps.newGeocoder().reverseGeocode(lat, lng),
key = '';
response.results.some(function (result) {
result.address_components.some(function (address_component) {
return address_component.types.some(function (type) {
if (type == addressType) {
key = address_component.long_name;
return true;
}
});
});
});
return key;
}
Then in the spreadsheet, use this formula:
=reverseGeocode(address_type; latitude_goes_here; longitude_goes_here)
For example, if I have the latitude in A2 and longitude in B2 and that I want to get the city then I can use:
=reverseGeocode("locality"; A2; B2)
If you want the country you can use:
=reverseGeocode("country"; A2; B2)
Bonus function to extract part of an address:
/**
* Return the closest, human-readable address type based on the the address passed.
*
* #param {"locality"} addressType Address type. Examples of address types include
* a street address, a country, or a political entity.
* For more info check: https://developers.google.com/maps/documentation/geocoding/intro#Types
* #param {"Amsterdam"} address The street address that you want to geocode,
* in the format used by the national postal service
* of the country concerned. Additional address elements
* such as business names and unit, suite or floor
* numbers should be avoided.
* #customfunction
*/
function geocode(addressType, address) {
if (typeof addressType != 'string') {
throw new Error("addressType should be a string.");
}
if (typeof address != 'string') {
throw new Error("address should be a string.");
}
var response = Maps.newGeocoder().geocode(address),
key = "";
response.results.some(function (result) {
return result.address_components.some(function (address_component) {
return address_component.types.some(function (type) {
if (type === addressType) {
key = address_component.long_name;
}
});
});
});
return key;
}

Modeling JSON result in Core Data Editor

I'm learning Core Data and I need to create an Entity called Country. I'm getting this response from my API and I'm not sure how to create and store the JSON response in my country entity.
{
"total_count":10,
"results": [
{
"name": "Spain",
"population" : 45.000,
"location" : {
"latitude" : 29.567423,
"longitude" : -5.675326
}
},
{
"name": "France",
"population" : 25.000,
"location" : {
"latitude" : 29.567423,
"longitude" : -5.675326
}
},
{
"name": "Germany",
"population" : 15.000,
"location" : {
"latitude" : 29.567423,
"longitude" : -5.675326
}
}
]
}
The parent fields (name, population) are not problem, but how can I set the child fields (location key) in the entity graph? With an NSDictionary maybe?
There are many possibilities.
You can create two separate attributes like (latitude, longitude) having type (double, double) and at fetch time create location with both values or you can create CGPoint with both of values and then create string from CGPoint and store them
CGPoint location = CGPointMake(latitude, longitude);
NSString *stringLocation = NSStringFromCGPoint(point);
store that string and fetch it and then again convert into CGpoint and use it
CGPoint myPoint = CGPointFromString(stringLocation);

Resources