Geo search with elasic
2
Saurabh Bhatia, email: s@wub.space
Full stack engineer
https://flatmates.com.au
friends of elastic
- searchkick gem - https://github.com/ankane/searchkick
- Geocoder gem - https://github.com/alexreisner/geocoder for distance calculations, running geocoding on models
- Maxmind for ip lookup
- Australia Post - for keeping the postcodes
- Geonames
1
Structure the data
- Understanding the
geo-political structure of a country - what do you want to search
1
4 different cases of data
- A property always belongs to a location
- People can decide to search for rooms in multiple suburbs (locations)
- A suburb belongs to a city - maybe, maybe not
- Where are the cities by the way ?
1
Property
- property has one location.
- has a street address , suburb typically geocoded using google places autocomplete.
1
Property
Text
1
def search_data
as_json only: [:street_number, :street_name, :latitude, :longitude]
attributes.merge(
suburb: location.suburb,
city: city,
state: location.state,
postcode: location.postcode)
coordinates: [latitude, longitude])
end
{
"_index": "properties_development_20160111174504595",
"_type": "property",
"_id": "",
"_version": 1,
"_score": 1,
"_source": {
"id": ,
"street_number": "33",
"street_name": "Second Avenue",
"latitude": -33.906534,
"longitude": 151.096148,
"suburb": "Campsie",
"city": "Sydney",
"State": "NSW",
"coordinates": [ 151.096148 , -33.906534 ],
"postcode": "2194"
}
}
Property
Text
1
search_conditions[:state] = @get_states
search_conditions[:suburb] = @get_localities
search_conditions[:postcode] = @get_postcodes
if search_params["location"]
city = fetch_city(search_params)
if city
search_conditions[:location] = { geo_polygon: { points: city.polygon } }
end
end
Person
- A person can have many locations
- A person can look for a house in Darlinghurst,
Randwick , Bondi or Rose bay - Locations are array of arrays
1
Person
def search_data
....
attributes.merge(
...
suburb: locations.map(&:suburb),
postcode: locations.map(&:postcode),
state: locations.map(&:state),
location: locations.map { |l| [l.latitude, l.longitude] }
)
end
{
"_index": "people_development_20160111171735900",
"_type": "person",
"_id": "",
"_version": 1,
"_score": 1,
"_source": {
"suburb": [ "Aspley" , "Chermside", "Gordon Park" , "Lutwyche" , "Banyo" , "Northgate" ,
"Clayfield" , "Eagle Farm" , "Nundah" ],
"postcode": [ "4034" , "4032", "4031", "4030", "4014" , "4013" , "4011", "4009", "4012"],
"state": [ "QLD", "QLD", "QLD", "QLD", "QLD", "QLD", "QLD", "QLD", "QLD" ],
"location": [ [ 153.0177628, -27.3643732],
[ 153.0301776 , -27.3854227],
[ 153.0254209 , -27.4190244 ],
[ 153.0339817 , -27.4225439 ],
[ 153.0772783 , -27.3749829 ] ,
[ 153.0695527 , -27.392405 ] ,
[ 153.0569754 , -27.4175155 ] ,
[ 153.0883543 , -27.4361559 ] ,
[ 153.0619368 , -27.4013961 ]]
}
}
Person
search_conditions[:state] = @get_states
search_conditions[:suburb] = @get_localities
search_conditions[:postcode] = @get_postcodes
if search_params["location"]
city = fetch_city(search_params)
if city
search_conditions[:location] = { geo_polygon: { points: city.polygon } }
end
end
locations
-
Locti on types -suburbs ,areas , cities
3
location
def search_data
as_json only: [:state, :city, :suburb, :country, :latitude, :longitude, :polygon,
:location_type]
attributes.merge(
location: [latitude, longitude]
)
end
{
"_index": "locations_development_20160111172021108",
"_type": "location",
"_id": "",
"_version": 2,
"_score": null,
"_source": {
"id": ,
"state": "QLD",
"city": "Brisbane",
"suburb": "Brisbane",
"postcode": "4000",
"country": "AU",
"latitude": -27.4709331,
"longitude": 153.0235024,
"polygon": [ ],
"location_type": "suburb",
"location": [ 153.0235024, -27.4709331 ]
},
"sort": [ "suburb" ]
}
city
polygon = [[151.274529, -33.566859],
[151.342163, -33.579731],
[151.346283, -33.624626],
[151.318817, -33.697208],
........................
[151.157799, -33.515923],
[151.274529, -33.566859]]
city
Location id:,
State: "NSW",
city: "Sydney",
suburb: nil,
postcode: nil,
country: "AU",
latitude: -33.8674869,
longitude: 151.2069902,
polygon: [..],
location_type: "city"
{
"_index": "locations_development_20160111172021108",
"_type": "location",
"_id": "",
"_version": 2,
"_score": null,
"_source": {
"id": "",
"State": "NSW",
"city": "Sydney",
"suburb": null,
"postcode": null,
"country": "AU",
"latitude": -33.8674869,
"longitude": 151.2069902,
"polygon": [ ... ],
"location_type": "city",
"location": [ 151.2069902, -33.8674869]
}
}
searching a polygon
Location.search("*", where: { state: state, city: city, suburb: suburb,
postcode: postcode, country: country,
location_type: "suburb",
location: { geo_polygon: { points: city.polygon }}})
{
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_polygon" : {
"location" : {
"points" : [
[ … ],
]
}
}
}
}
}
searching a bounding box
Model.search(“*", where: { location: { top_left: top_left.split(","),
bottom_right: bottom_right.split(",") } },
load: false)
{
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"location" : {
"top_left" : {
"lat" : 33.79733070958013,
"lon" : 150.59759231181647
},
"bottom_right" : {
"lat" : -33.90282834951249,
"lon" : 151.05146499248053
}
}
}
}
}
}
Google maps Integration
# call the api
%script{ src: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&key=API-KEY" }
#function
addMarkersToMap = (map, points) ->
bounds = map.getBounds()
topLeftBounds = bounds.getNorthEast()
bottomRightBounds = bounds.getSouthWest()
topLeft = [topLeftBounds.lat(), bottomRightBounds.lng()].join()
bottomRight = [bottomRightBounds.lat(), topLeftBounds.lng()].join()
boundingBoxPoints = '/map_markers.json?search[top_left]=' + topLeft
+ '&search[bottom_right]=' + bottomRight
$.getJSON boundingBoxPoints, (data) ->
$.each data, (key, val) ->
# get points from json
allSearchResultPoints = val
# markers
resultMarker = '<%= image_url("icons/listing-marker.png") %>'
for point in allSearchResultPoints
do (point) ->
coordinates = new google.maps.LatLng(point.latitude, point.longitude)
locationMarker = new (google.maps.Marker)(
position: coordinates
map: map
title: "search result marker"
icon: resultMarker
class: "popup-listing"
Yandex: 1000)
Google maps
Thank you!
geo-elastic
By saurabhbhatia
geo-elastic
Our journey of creating geo search with elastic
- 2,012