Use Redis GeoHash with Spring boot
One very handy Data Structure when it comes to Redis is the GeoHash Data structure. Essentially it is a sorted set that generates a score based on the longitude and latitude.
We will spin up a Redis database using Compose
services: redis: image: redis ports: - 6379:6379
Can be run like this
docker compose up
You can find more on Compose on the Developers Essential Guide to Docker Compose.
Let’s add our dependencies
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> <modelVersion>4.0.0</modelVersion> <artifactId>location-service</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.7.5</version> </dependency> </dependencies> </project>
We shall start with our Configuration. For convenience on injecting we shall create a GeoOperations<String,String> bean.
package org.location; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.GeoOperations; import org.springframework.data.redis.core.RedisTemplate; @Configuration public class RedisConfiguration { @Bean public GeoOperations<String,String> geoOperations(RedisTemplate<String,String> template) { return template.opsForGeo(); } }
Our model would be this one
package org.location; import lombok.Data; @Data public class Location { private String name; private Double lat; private Double lng; }
This simple service will persist venue locations and also fetch venues nearby of a location.
package org.location; import java.util.List; import java.util.stream.Collectors; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.redis.connection.RedisGeoCommands; import org.springframework.data.redis.core.GeoOperations; import org.springframework.data.redis.domain.geo.GeoLocation; import org.springframework.stereotype.Service; @Service public class GeoService { public static final String VENUS_VISITED = "venues_visited"; private final GeoOperations<String, String> geoOperations; public GeoService(GeoOperations<String, String> geoOperations) { this.geoOperations = geoOperations; } public void add(Location location) { Point point = new Point(location.getLng(), location.getLat()); geoOperations.add(VENUS_VISITED, point, location.getName()); } public List<String> nearByVenues(Double lng, Double lat, Double kmDistance) { Circle circle = new Circle(new Point(lng, lat), new Distance(kmDistance, Metrics.KILOMETERS)); GeoResults<RedisGeoCommands.GeoLocation<String>> res = geoOperations.radius(VENUS_VISITED, circle); return res.getContent().stream() .map(GeoResult::getContent) .map(GeoLocation::getName) .collect(Collectors.toList()); } }
We shall also add a controller
package org.location; import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class LocationController { private final GeoService geoService; public LocationController(GeoService geoService) { this.geoService = geoService; } @PostMapping("/location") public ResponseEntity<String> addLocation(@RequestBody Location location) { geoService.add(location); return ResponseEntity.ok("Success"); } @GetMapping("/location/nearby") public ResponseEntity<List<String>> locations(Double lng, Double lat, Double km) { List<String> locations = geoService.nearByVenues(lng, lat, km); return ResponseEntity.ok(locations); } }
Then let’s add an element.
curl --location --request POST 'localhost:8080/location' \ --header 'Content-Type: application/json' \ --data-raw '{ "lng": 51.5187516, "lat":-0.0814374, "name": "liverpool-street" }'
Let’s retrieve the element of the api
curl --location --request GET 'localhost:8080/location/nearby?lng=51.4595573&lat=0.24949&km=100' > [ "liverpool-street" ]
And also let’s check redis
ZRANGE venues_visited 0 -1 WITHSCORES 1) "liverpool-street" 2) "2770072452773375"
We did it, pretty convenient for our day to day distance use cases.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Use Redis GeoHash with Spring boot Opinions expressed by Java Code Geeks contributors are their own. |