Saturday, May 27, 2023

Redis Polygon Search

Summary

This post will demonstrate the usage of a new search feature within Redis - geospatial search with polygons.  This search feature is part of the 7.2.0-M01 Redis Stack release.  This initial release supports the WITHIN and CONTAINS query types for polygons, only.  Additional geospatial search types will be forthcoming in future releases.  

Architecture


Code Snippets

Point Generation

I use the Shapely module to generate the geometries for this demo.  The code snippet below will generate a random point, optionally within a bounding box.

def _get_point(self, box: Polygon = None) -> Point:
""" Private function to generate a random point, potentially within a bounding box
Parameters
----------
box - Optional bounding box
Returns
-------
Shapely Point object
"""
point: Point
if box:
minx, miny, maxx, maxy = box.bounds
while True:
point = Point(random.uniform(minx, maxx), random.uniform(miny, maxy))
if box.contains(point):
break
else:
point = Point(random.uniform(MIN_X, MAX_X), random.uniform(MIN_Y, MAX_Y))
return point

Polygon Generation

Random polygons can be generated using the random point function above.  By passing a polygon as an input parameter, the generated polygon can be placed inside that input polygon.

def _get_polygon(self, box: Polygon = None) -> Polygon:
""" Private function to generate a random polygon, potentially within a bounding box
Parameters
----------
box - Optional bounding box
Returns
-------
Shapely Polygon object
"""
points: List[Point] = []
for _ in range(random.randint(3,10)):
points.append(self._get_point(box))
ob: MultiPoint = MultiPoint(points)
return Polygon(ob.convex_hull)

Redis Polygon Search Index

The command below creates an index on the polygons with the new keyword 'GEOMETRY' for their associated WKT-formatted points.  Note this code is sending a raw CLI command to Redis.  The redis-py lib does not support the new geospatial command sets at the time of this writing.

self.client.execute_command('FT.CREATE', 'idx', 'ON', 'JSON', 'PREFIX', '1', 'key:',
'SCHEMA', '$.name', 'AS', 'name', 'TEXT', '$.geom', 'AS', 'geom', 'GEOSHAPE', 'FLAT')
view raw poly-index.py hosted with ❤ by GitHub

Redis Polygon Load as JSON

The code below inserts 4 polygons into Redis as JSON objects.  Those objects are indexed within Redis by the code above.
  
self.client.json().set('key:1', '$', { "name": "Red Polygon", "geom": poly_red.wkt })
self.client.json().set('key:2', '$', { "name": "Green Polygon", "geom": poly_green.wkt })
self.client.json().set('key:3', '$', { "name": "Blue Polygon", "geom": poly_blue.wkt })
self.client.json().set('key:4', '$', { "name": "Cyan Polygon", "geom": poly_cyan.wkt })
self.client.json().set('key:5', '$', { "name": "Purple Point", "geom": point_purple.wkt })
self.client.json().set('key:6', '$', { "name": "Brown Point", "geom": point_brown.wkt })
self.client.json().set('key:7', '$', { "name": "Orange Point", "geom": point_orange.wkt })
self.client.json().set('key:8', '$', { "name": "Olive Point", "geom": point_olive.wkt })
view raw poly-json.py hosted with ❤ by GitHub

Redis Polygon Search

Redis Polygon search (contains or within) code below. Again, this is the raw CLI command.
def _poly_search(self, qt: QUERY, color: COLOR, shape: Polygon, filter: SHAPE) -> None:
""" Private function for POLYGON search in Redis.
Parameters
----------
qt - Redis Geometry search type (contains or within)
color - color attribute of polygon
shape - Shapely point or polygon object
filter - query filter on shape types (polygon or point) to be returned
Returns
-------
None
"""
results: list = self.client.execute_command('FT.SEARCH', 'idx', f'(-@name:{color.value} @name:{filter.value} @geom:[{qt.value} $qshape])', 'PARAMS', '2', 'qshape', shape.wkt, 'RETURN', '1', 'name', 'DIALECT', '3')
if (results[0] > 0):
for res in results:
if isinstance(res, list):
print(res[1].decode('utf-8').strip('[]"'))
else:
print('None')

Results

Plot






Results


*** Polygons within the Red Polygon ***
Green Polygon
Blue Polygon
Cyan Polygon
*** Polygons within the Green Polygon ***
Blue Polygon
Cyan Polygon
*** Polygons within the Blue Polygon ***
Cyan Polygon
*** Polygons within the Cyan Polygon ***
None
*** Points within the Red Polygon ***
Purple Point
Brown Point
Orange Point
Olive Point
*** Points within the Green Polygon ***
Purple Point
Brown Point
Orange Point
Olive Point
*** Points within the Blue Polygon ***
Purple Point
Brown Point
*** Points within the Cyan Polygon ***
Purple Point
Brown Point
*** Polygons containing the Red Polygon ***
None
*** Polygons containing the Green Polygon ***
Red Polygon
*** Polygons containing the Blue Polygon ***
Red Polygon
Green Polygon
*** Polygons containing the Cyan Polygon ***
Red Polygon
Green Polygon
Blue Polygon
*** Polygons containing the Purple Point ***
Red Polygon
Green Polygon
Blue Polygon
Cyan Polygon
*** Polygons containing the Brown Point ***
Red Polygon
Green Polygon
Blue Polygon
Cyan Polygon
*** Polygons containing the Orange Point ***
Red Polygon
Green Polygon
*** Polygons containing the Olive Point ***
Red Polygon
Green Polygon
view raw results.txt hosted with ❤ by GitHub

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.