Geospatial queries

Query based on geographic data

The geospatial feature in Redis Open Source allows you to query for data associated with geographic locations. You can either query for locations within a specific radius or based on geometric shapes, such as polygons. A polygon shape could, for instance, represent a lake or the layout of a building.

The examples in this article use the following schema:

Field name Field type
store_location GEO
pickup_zone GEOSHAPE
Note:
Redis version 7.2.0 or higher is required to use the GEOSHAPE field type.

Radius

You can construct a radius query by passing the center coordinates (longitude, latitude), the radius, and the distance unit to the FT.SEARCH command.

FT.SEARCH index "@geo_field:[lon lat radius unit]"

Allowed units are m, km, mi, and ft.

The following query finds all bicycle stores within a radius of 20 miles around London:

Foundational: Query geographic locations within a radius using center coordinates and distance when you need to find nearby points of interest
FT.SEARCH idx:bicycle "@store_location:[-0.1778 51.5524 20 mi]"
params_dict = {"lon": -0.1778, "lat": 51.5524, "radius": 20, "units": "mi"}
q = Query("@store_location:[$lon $lat $radius $units]").dialect(2)
res = index.search(q, query_params=params_dict)
print(res)
# >>> Result{1 total, docs: [Document {'id': 'bicycle:5', ...
const res1= await client.ft.search('idx:bicycle', '@store_location:[-0.1778 51.5524 20 mi]');
console.log(res1.total); // >>> 1
console.log(res1); // >>> {total: 1, documents: [ { id: 'bicycle:5', value: [Object: null prototype] } ]}
        SearchResult res1 = jedis.ftSearch("idx:bicycle",
            "@store_location:[$lon $lat $radius $units]",
            FTSearchParams.searchParams()
                    .addParam("lon", -0.1778)
                    .addParam("lat", 51.5524)
                    .addParam("radius", 20)
                    .addParam("units", "mi")
                    .dialect(2)
        );
        System.out.println(res1.getTotalResults()); // >>> 1

        List<Document> docs1 = res1.getDocuments();

        for (Document document : docs1) {
            System.out.println(document.getId());
        }
        // >>> bicycle:5
	res1, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle", "@store_location:[$lon $lat $radius $units]",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"lon":    -0.1778,
				"lat":    51.5524,
				"radius": 20,
				"units":  "mi",
			},
			DialectVersion: 2,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1.Total) // >>> 1

	for _, doc := range res1.Docs {
		fmt.Println(doc.ID)
	}
	// >>> bicycle:5

Shape

The only supported shapes are points and polygons. You can query for polygons or points that either contain or are within a given geometric shape.

FT.SEARCH index "@geo_shape_field:[{WITHIN|CONTAINS|INTERSECTS|DISJOINT} $shape] PARAMS 2 shape "shape_as_wkt" DIALECT 3

Here is a more detailed explanation of this query:

  1. Field name: you need to replace geo_shape_field with the GEOSHAPE field's name on which you want to query.
  2. Spatial operator: spatial operators define the relationship between the shapes in the database and the shape you are searching for. You can either use WITHIN, CONTAINS, INTERSECTS, or DISJOINT. WITHIN finds any shape in the database that is inside the given shape. CONTAINS queries for any shape that surrounds the given shape. INTERSECTS finds any shape that has coordinates in common with the provided shape. DISJOINT finds any shapes that have nothing in common with the provided shape. INTERSECTS and DISJOINT were introduced in v2.10.
  3. Parameter: the query refers to a parameter named shape. You can use any parameter name here. You need to use the PARAMS clause to set the parameter value. The value follows the well-known text representation of a geometry. Supported types are POINT(x y) and POLYGON((x1 y1, x2 y2, ...)).
  4. Dialect: Shape-based queries are not supported in DIALECT 1.

The following example query verifies if a bicycle is within a pickup zone:

Spatial operators: Query geometric shapes using CONTAINS to test which shapes enclose another shape when you need to find bounding regions
FT.SEARCH idx:bicycle "@pickup_zone:[CONTAINS $bike]" PARAMS 2 bike "POINT(-0.1278 51.5074)" DIALECT 2
params_dict = {"bike": "POINT(-0.1278 51.5074)"}
q = Query("@pickup_zone:[CONTAINS $bike]").dialect(3)
res = index.search(q, query_params=params_dict)
print(res.total) # >>> 1
const params_dict_geo2 = { bike: 'POINT(-0.1278 51.5074)' };
const q_geo2 = '@pickup_zone:[CONTAINS $bike]';
const res2 = await client.ft.search('idx:bicycle', q_geo2, { PARAMS: params_dict_geo2, DIALECT: 3 });
console.log(res2.total); // >>> 1
console.log(res2); // >>> {total: 1, documents: [ { id: 'bicycle:5', value: [Object: null prototype] } ]}
        SearchResult res2 = jedis.ftSearch("idx:bicycle",
            "@pickup_zone:[CONTAINS $bike]",
            FTSearchParams.searchParams()
                    .addParam("bike", "POINT(-0.1278 51.5074)")
                    .dialect(3)
        );
        System.out.println(res2.getTotalResults());   // >>> 1

        List<Document> docs2 = res2.getDocuments();

        for (Document document : docs2) {
            System.out.println(document.getId());
        }
        // >>> bicycle:5
	res2, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle",
		"@pickup_zone:[CONTAINS $bike]",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"bike": "POINT(-0.1278 51.5074)",
			},
			DialectVersion: 3,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2.Total) // >>> 1

	for _, doc := range res2.Docs {
		fmt.Println(doc.ID)
	}
	// >>> bicycle:5

If you want to find all pickup zones that are approximately within Europe, then you can use the following query:

Spatial operators: Query geometric shapes using the WITHIN operator to find shapes contained in a region when you need to identify items within geographic boundaries
FT.SEARCH idx:bicycle "@pickup_zone:[WITHIN $europe]" PARAMS 2 europe "POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))" DIALECT 2
params_dict = {"europe": "POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))"}
q = Query("@pickup_zone:[WITHIN $europe]").dialect(3)
res = index.search(q, query_params=params_dict)
print(res.total) # >>> 5
const params_dict_geo3 = { europe: 'POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))' };
const q_geo3 = '@pickup_zone:[WITHIN $europe]';
const res3 = await client.ft.search('idx:bicycle', q_geo3, { PARAMS: params_dict_geo3, DIALECT: 3 });
console.log(res3.total); // >>> 5
console.log(res3); // >>>
// {
//   total: 5,
//   documents: [
//     { id: 'bicycle:5', value: [Object: null prototype] },
//     { id: 'bicycle:6', value: [Object: null prototype] },
//     { id: 'bicycle:7', value: [Object: null prototype] },
//     { id: 'bicycle:8', value: [Object: null prototype] },
//     { id: 'bicycle:9', value: [Object: null prototype] }
//   ]
// }
        SearchResult res3 = jedis.ftSearch("idx:bicycle",
            "@pickup_zone:[WITHIN $europe]",
            FTSearchParams.searchParams()
                    .addParam("europe", "POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))")
                    .dialect(3)
        );
        System.out.println(res3.getTotalResults()); // >>> 5

        List<Document> docs3 = res3.getDocuments();

        for (Document document : docs3) {
            System.out.println(document.getId());
        }
        // >>> bicycle:5
        // >>> bicycle:6
        // >>> bicycle:7
        // >>> bicycle:8
        // >>> bicycle:9
	res3, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle",
		"@pickup_zone:[WITHIN $europe]",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"europe": "POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))",
			},
			DialectVersion: 3,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3.Total) // >>> 5

	sort.Slice(res3.Docs, func(i, j int) bool {
		return res3.Docs[i].ID < res3.Docs[j].ID
	})

	for _, doc := range res3.Docs {
		fmt.Println(doc.ID)
	}
	// >>> bicycle:5
	// >>> bicycle:6
	// >>> bicycle:7
	// >>> bicycle:8
	// >>> bicycle:9
RATE THIS PAGE
Back to top ↑