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

 
  • Loction 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,005