Article directory
-
- 1 Basic usage of GEO data structure
- 2 Import store data to GEO
- 3 Realize the function of nearby merchants
1 Basic usage of GEO data structure
GEO is the abbreviation of Geolocation, which stands for geographic coordinates. Redis added support for GEO in version 3.2, which allows storing geographic coordinate information and helping us retrieve data based on latitude and longitude. Common commands are:
- GEOADD: Add a geospatial information, including: longitude (longitude), latitude (latitude), value (member)
- GEODIST: Calculate the distance between the specified two points and return
- GEOHASH: Convert the coordinates of the specified member into a hash string and return
- GEOPOS: returns the coordinates of the specified member
- GEORADIUS: Specify the center and radius of the circle, find all the members contained in the circle, sort them according to the distance from the center of the circle, and return them. 6. Obsolete in the future
- GEOSEARCH: Search for members within the specified range, and return them sorted by distance from the specified point. Ranges can be circular or rectangular. 6.2. New features
- GEOSEARCHSTORE: The function is the same as GEOSEARCH, but the result can be stored in a specified key. 6.2. New features
2 Import store data to GEO
Specific scene description:
When we click on the food, a series of merchants will appear. The merchants can be sorted in a variety of ways. At this time, we are concerned about the distance. This place needs to use our GEO to pass the address collected by the current app to the background ( We have hardcoded it here), take the current coordinates as the center of the circle, bind the same store type type, and paging information at the same time, pass these conditions into the background, query the corresponding data in the background, and then return.
What we have to do is: import the data in the database table into redis, GEO in redis, GEO in redis is just a menber and a latitude and longitude, we pass the x and y axes to the latitude and longitude position made by redis , but we can’t put all the data into menber. After all, as redis is a memory-level database, if there is a huge amount of data, redis is still powerless, so we can store his id here.
But there is still a problem at this time, that is, the type is not stored in redis, so we cannot filter the data according to the type, so we can group according to the merchant type, and the merchants of the same type are the same group, and the typeId is used as the key to store into the same GEO set
the code
HmDianPingApplicationTests
@Test void loadShopData() {<!-- --> // 1. Query store information List<Shop> list = shopService. list(); // 2. Group the stores according to typeId, and put the same typeId into a collection Map<Long, List<Shop>> map = list. stream(). collect(Collectors. groupingBy(Shop::getTypeId)); // 3. Write to Redis in batches for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {<!-- --> // 3.1. Get type id Long typeId = entry. getKey(); String key = SHOP_GEO_KEY + typeId; // 3.2. Get the collection of stores of the same type List<Shop> value = entry. getValue(); List<RedisGeoCommands. GeoLocation<String>> locations = new ArrayList<>(value. size()); // 3.3. Write redis GEOADD key longitude latitude member for (Shop shop : value) {<!-- --> // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString()); locations.add(new RedisGeoCommands.GeoLocation<>( shop. getId(). toString(), new Point(shop. getX(), shop. getY()) )); } stringRedisTemplate.opsForGeo().add(key, locations); } }
3 Realize the function of nearby merchants
The 2.3.9 version of SpringDataRedis does not support the GEOSEARCH command provided by Redis 6.2, so we need to upgrade its version and modify our POM
Step 1: import pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <artifactId>spring-data-redis</artifactId> <groupId>org.springframework.data</groupId> </exclusion> <exclusion> <artifactId>lettuce-core</artifactId> <groupId>io.lettuce</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.6.2</version> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.1.6.RELEASE</version> </dependency>
Step two:
ShopController
@GetMapping("/of/type") public Result queryShopByType( @RequestParam("typeId") Integer typeId, @RequestParam(value = "current", defaultValue = "1") Integer current, @RequestParam(value = "x", required = false) Double x, @RequestParam(value = "y", required = false) Double y ) {<!-- --> return shopService. queryShopByType(typeId, current, x, y); }
ShopServiceImpl
@Override public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {<!-- --> // 1. Determine whether to query based on coordinates if (x == null || y == null) {<!-- --> // No need for coordinate query, query by database Page<Shop> page = query() .eq("type_id", typeId) .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE)); // return data return Result.ok(page.getRecords()); } // 2. Calculate paging parameters int from = (current - 1) * SystemConstants. DEFAULT_PAGE_SIZE; int end = current * SystemConstants. DEFAULT_PAGE_SIZE; // 3. Query redis, sort by distance, and paginate. Result: shopId, distance String key = SHOP_GEO_KEY + typeId; GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE .search( key, GeoReference. fromCoordinate(x, y), new Distance(5000), RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end) ); // 4. Parse the id if (results == null) {<!-- --> return Result.ok(Collections.emptyList()); } List<GeoResult<RedisGeoCommands. GeoLocation<String>>> list = results. getContent(); if (list. size() <= from) {<!-- --> // There is no next page, end return Result.ok(Collections.emptyList()); } // 4.1. Intercept the part from ~ end List<Long> ids = new ArrayList<>(list. size()); Map<String, Distance> distanceMap = new HashMap<>(list. size()); list.stream().skip(from).forEach(result -> {<!-- --> // 4.2. Get the store id String shopIdStr = result. getContent(). getName(); ids.add(Long.valueOf(shopIdStr)); // 4.3. Get the distance Distance distance = result. getDistance(); distanceMap.put(shopIdStr, distance); }); // 5. Query Shop by id String idStr = StrUtil. join(",", ids); List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list(); for (Shop shop : shops) {<!-- --> shop.setDistance(distanceMap.get(shop.getId().toString()).getValue()); } // 6. return return Result.ok(shops); }