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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
self.client.execute_command('FT.CREATE', 'idx', 'ON', 'JSON', 'PREFIX', '1', 'key:', | |
'SCHEMA', '$.name', 'AS', 'name', 'TEXT', '$.geom', 'AS', 'geom', 'GEOSHAPE', 'FLAT') |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }) |
Redis Polygon Search
Redis Polygon search (contains or within) code below. Again, this is the raw CLI command.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*** 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 |
Source
Copyright ©1993-2024 Joey E Whelan, All rights reserved.