DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for ElasticSearch Geo Shape
Taufek Johar
Taufek Johar

Posted on • Originally published at blog.taufek.dev on

ElasticSearch Geo Shape

In previous post, we looked at quick start guide on how to use ElasticSearch with Ruby.

In this post, we will look at how to search based on geo location. Lets say we have food delivery app and we would like to find out the delivery surcharge amount for certain area.

Create Index with Geo Shape Mapping

client = Elasticsearch::Client.new

mapping = {
  surcharge: {
    properties: {
      delivery_area: {
        type: 'geo_shape',
      }
    }
  }
}

client.create(index: :surcharges, type: :surcharge, body: {})

client.indices.put_mapping(index: :surcharges, type: :surcharge, body: mapping)

client.indices.get(index: :surcharges)

# Output
{
  "surcharges"=> {
    "aliases"=>{},
    "mappings"=>{
      "surcharge"=>{
        "properties"=>{
          "delivery_area"=>{"type"=>"geo_shape"}
        }
      }
    },
    "settings"=> {...}
  }
}

Above code, will create an index named surcharges and a document named surcharge. It has property of delivery_area with geo_shape data type.

Index Surcharge Document with Polygon Coordinates

Below shows polygon area that I’ve created with Google Maps. klcc_polygon

Below is the surcharge document which saying within that polygon area, the surcharge amount is $10.

surcharge = {
  id: 1,
  delivery_area: {
    type: 'polygon',
    coordinates: [[
      # longitude, latitude
      [101.710997, 3.157035],
      [101.710997, 3.152954],
      [101.717316, 3.152954],
      [101.717316, 3.157035],
      [101.710997, 3.157035],
    ]]
  },
  amount: 10.0
}

client.index(
  id: surcharge[:id],
  index: :surcharges,
  type: :surcharge,
  body: surcharge
)

# Output
{
  "_index"=>"surcharges",
  "_type"=>"surcharge",
  "_id"=>"1",
  "_version"=>1,
  "result"=>"created",
  "_shards"=>{"total"=>2, "successful"=>1, "failed"=>0},
  "_seq_no"=>1,
  "_primary_term"=>1
}

Search with Geo Point

Once I have my surcharge data indexed, I can start querying based on a coordinate. If my drop off point in somewhere inside the polygon, I’m expecting the query will return the surcharge document. inside_polygon

query = {
  query: {
    bool: {
      filter: {
        geo_shape: {
          'delivery_area': {
            shape: {
              type: :point,
                coordinates: [
                  101.714997, #longitude
                  3.155647 #latitude
                ]
            }
          }
        }
      }
    }
  }
}

client.search(index: 'surcharges', body: query)

# Output
{
  "took"=>9,
  "timed_out"=>false,
  "_shards"=>{
    "total"=>5,
    "successful"=>5,
    "skipped"=>0,
    "failed"=>0
  },
 "hits"=>{
   "total"=>1,
   "max_score"=>1.0,
   "hits"=>[
     {
       "_index"=>"surcharges",
       "_type"=>"surcharge",
       "_id"=>"1",
       "_score"=>1.0,
       "_source"=>{
          "id"=>1,
          "delivery_area"=>{
            "type"=>"polygon",
             "coordinates"=>[[[101.710997, 3.157035], [101.710997, 3.152954], [101.717316, 3.152954], [101.717316, 3.157035], [101.710997, 3.157035]]]
          },
         "amount"=>10.0
        }
      }
    ]
  }
}

Then if my drop off point in somewhere outside the polygon, I’m expecting the query will not return any surcharge document. outside_polygon

query = {
  query: {
    bool: {
      filter: {
        geo_shape: {
          'delivery_area': {
            shape: {
              type: :point,
              coordinates: [
                101.7077343, #longitude
                3.157032 #latitude
              ]
            }
          }
        }
      }
    }
  }
}

client.search(index: 'surcharges', body: query)

# Output
{
  "took"=>0,
  "timed_out"=>false,
  "_shards"=>{
    "total"=>5,
    "successful"=>5,
    "skipped"=>0,
    "failed"=>0
  },
  "hits"=>{
    "total"=>0,
    "max_score"=>nil,
    "hits"=>[]
  }
}

Conclusions

Geo shape is useful when you have data associated with coordinates of land area. Once you indexed those as a polygon in ES index, you could use Geo Shape query to fetch documents that intersect with a particular geo point. There are many other useful features too such as you could set a polygon with a hole in the middle or you could search based on radius instead of point. For more info you can head to ES official documents (geo-shape-datatype and geo-shape-query).

Top comments (0)

Timeless DEV post...

How to write a kickass README

Arguably the single most important piece of documentation for any open source project is the README. A good README not only informs people what the project does and who it is for but also how they use and contribute to it.

If you write a README without sufficient explanation of what your project does or how people can use it then it pretty much defeats the purpose of being open source as other developers are less likely to engage with or contribute towards it.