Skip to content

Widhian Bramantya

coding is an art form

Menu
  • About Me
Menu
elasticsearch

Finding Nearby Merchants in a Ride-Hailing App Using Elasticsearch Polygon Search

Posted on October 6, 2025October 5, 2025 by admin

Modern ride-hailing and food delivery apps rely heavily on location-based search. When a user opens the app, it must quickly find nearby drivers or restaurants, usually within a few kilometers.
The faster the search, the better the user experience.

Elasticsearch is not only for text search, it also supports geo-spatial queries that can find locations inside a specific area. In this article, we will learn how to design radius-based location search efficiently using polygon approximation.

The Problem: Searching by Radius Is Expensive

A common idea is to search by a circle radius, for example,

“Find all merchants within 5 km from my current location.”

In Elasticsearch, this can be done using a geo_distance query:

"geo_distance": {
  "distance": "5km",
  "location": { "lat": -6.2, "lon": 106.8 }
}

But there is a problem. This query must calculate the Haversine distance (real-world spherical distance) for every document inside the bounding area. When you have thousands of merchants, this becomes slow and CPU-heavy.

The Idea: Use Polygon Instead of Circle

To make it faster, we can approximate the circle with a polygon, a closed shape made of straight lines.

Example:
A 5-km radius circle can be represented as a polygon with 32 points (sides).
It is not a perfect circle, but very close.

     * * * * *
   *           *
  *             *
   *           *
     * * * * *

By using polygon instead of a true circle:

  • Elasticsearch can use geo-shape indexing (based on BKD trees).
  • The query becomes a simple spatial check (“is inside polygon?”).
  • No need to calculate distance for each document.
  • Much faster with minimal accuracy loss.

Understanding Error Margin

Because a polygon cannot perfectly match a circle,
there will always be a small error margin — the difference between
the real circular area and the polygon boundary.

See also  Maintaining Super Large Datasets in Elasticsearch
Polygon sidesApproximation error
6 (hexagon)~15% radius error
16~5%
32~2%
64~1%
  • False positive: some points outside the real circle may still be inside the polygon.
  • False negative: a few edge points may be missed.

In most delivery systems, a small error (2-5%) is acceptable,
because users only care that the list feels fast and mostly correct.

How It Works (Step by Step)

Let’s look at how a ride-hailing backend can do this.

Step 1 User Sends Location

The client app sends:

{
  "lat": -6.2000,
  "lon": 106.8167,
  "radius_km": 5
}

Step 2 Backend Generates Polygon Points

The backend converts the circle into polygon coordinates using simple math:

package main

import (
	"fmt"
	"math"
)

// GenerateCirclePolygon generates a polygon that approximates a circle
// centered at (lat, lon) with the given radius in kilometers.
// The polygon has the specified number of sides (default 32 for smooth shape).
func GenerateCirclePolygon(lat, lon, radiusKm float64, sides int) [][]float64 {
	const EarthRadius = 6371.0 // in km

	coords := make([][]float64, 0, sides+1)

	for i := 0; i < sides; i++ {
		angle := 2 * math.Pi * float64(i) / float64(sides)
		dx := radiusKm * math.Cos(angle) / EarthRadius
		dy := radiusKm * math.Sin(angle) / (EarthRadius * math.Cos(lat*math.Pi/180))

		lat_i := lat + (dy * 180 / math.Pi)
		lon_i := lon + (dx * 180 / math.Pi)

		coords = append(coords, []float64{lon_i, lat_i})
	}

	// Close the polygon by repeating the first point
	coords = append(coords, coords[0])

	return coords
}

func main() {
	lat := -6.2000
	lon := 106.8167
	radiusKm := 5.0
	sides := 32

	polygon := GenerateCirclePolygon(lat, lon, radiusKm, sides)

	fmt.Println("Generated polygon coordinates:")
	for _, point := range polygon {
		fmt.Printf("[%.6f, %.6f]\n", point[0], point[1])
	}
}

Now the backend has a polygon shape with ~32 points around the user’s location.

Step 3 Store Merchant Locations as Geo Points

Each merchant or driver location is stored as a geo_point:

PUT merchants
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "location": { "type": "geo_point" }
    }
  }
}

Example documents:

{ "name": "Mindworks Café", "location": { "lat": -6.21, "lon": 106.81 } }
{ "name": "Beauty Spa", "location": { "lat": -6.25, "lon": 106.85 } }

Step 4 Search Merchants Inside the Polygon

Now we can send a geo-shape query using the polygon from Step 2.

GET merchants/_search
{
  "query": {
    "geo_shape": {
      "location": {
        "shape": {
          "type": "polygon",
          "coordinates": [[
            [106.80, -6.15],
            [106.83, -6.18],
            [106.86, -6.21],
            [106.83, -6.24],
            [106.80, -6.27],
            [106.77, -6.24],
            [106.74, -6.21],
            [106.77, -6.18],
            [106.80, -6.15]
          ]]
        },
        "relation": "within"
      }
    }
  }
}

Returns all merchants whose coordinates fall inside that polygon, without doing any distance math per document.

See also  Basic Concept of ElasticSearch (Part 2): Architectural Perspective

Related posts:

Log Management at Scale: Integrating Elasticsearch with Beats, Logstash, and Kibana

Maintaining Super Large Datasets in Elasticsearch

Elasticsearch Best Practices for Beginners

Pages: 1 2
Category: ElasticSearch

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Linkedin

Widhian Bramantya

Recent Posts

  • Smart Automation in PostgreSQL: Managing Time-Based Data with pg_partman and pg_cron
  • Understanding PostgreSQL WAL, Slot, Publication, LSN, and Replication Lag
  • PostgreSQL Write-Ahead Log (WAL): Durability, Performance Tuning, and Recovery Explained
  • PostgreSQL Replication Deep Dive: From High Availability to Multi-Master Clusters
  • Finding Nearby Merchants in a Ride-Hailing App Using Elasticsearch Polygon Search
  • Advanced Text Search in Elasticsearch: N-Gram, Reverse, Fuzzy, and Search-as-you-type
  • Understanding and Customizing Analyzers in Elasticsearch
  • Log Management at Scale: Integrating Elasticsearch with Beats, Logstash, and Kibana
  • Index Lifecycle Management (ILM) in Elasticsearch: Automatic Data Control Made Simple
  • Blue-Green Deployment in Elasticsearch: Safe Reindexing and Zero-Downtime Upgrades
  • Maintaining Super Large Datasets in Elasticsearch
  • Elasticsearch Best Practices for Beginners
  • Implementing the Outbox Pattern with Debezium
  • Production-Grade Debezium Connector with Kafka (Postgres Outbox Example – E-Commerce Orders)
  • Connecting Debezium with Kafka for Real-Time Streaming
  • Debezium Architecture – How It Works and Core Components
  • What is Debezium? – An Introduction to Change Data Capture
  • Offset Management and Consumer Groups in Kafka
  • Partitions, Replication, and Fault Tolerance in Kafka
  • Delivery Semantics in Kafka: At Most Once, At Least Once, Exactly Once

Recent Comments

No comments to show.

Archives

  • October 2025
  • September 2025
  • August 2025
  • November 2021
  • October 2021
  • August 2021
  • July 2021
  • June 2021
  • March 2021
  • January 2021

Categories

  • Debezium
  • Devops
  • ElasticSearch
  • Golang
  • Kafka
  • Lua
  • NATS
  • PostgreSQL
  • Programming
  • RabbitMQ
  • Redis
  • VPC
© 2026 Widhian Bramantya | Powered by Minimalist Blog WordPress Theme