-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #78 from HongDam-org/feat/redis-geolocation
[FEAT] redis geolocation을 사용한 좌표 거리 계산
- Loading branch information
Showing
3 changed files
with
50 additions
and
66 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
backend/src/main/java/com/twtw/backend/domain/location/dto/collection/MemberDistances.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.twtw.backend.domain.location.dto.collection; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
import org.springframework.data.geo.Point; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
public class MemberDistances { | ||
|
||
private static final double DEFAULT_VALUE = 0.0; | ||
|
||
private final List<Point> points; | ||
|
||
public double averageLongitude() { | ||
return points.stream().mapToDouble(Point::getX).average().orElse(DEFAULT_VALUE); | ||
} | ||
|
||
public double averageLatitude() { | ||
return points.stream().mapToDouble(Point::getY).average().orElse(DEFAULT_VALUE); | ||
} | ||
} |
92 changes: 26 additions & 66 deletions
92
backend/src/main/java/com/twtw/backend/domain/location/service/GeoService.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,70 @@ | ||
package com.twtw.backend.domain.location.service; | ||
|
||
import com.twtw.backend.domain.location.dto.collection.MemberDistances; | ||
import com.twtw.backend.domain.location.dto.request.LocationRequest; | ||
import com.twtw.backend.domain.location.dto.response.AverageCoordinate; | ||
import com.twtw.backend.domain.member.entity.Member; | ||
import com.twtw.backend.domain.plan.entity.Plan; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
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.GeoLocation; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class GeoService { | ||
|
||
private static final int CURRENT_LOCATION_INDEX = 0; | ||
private final RedisTemplate<String, String> redisTemplate; | ||
|
||
public AverageCoordinate saveLocation( | ||
final Plan plan, final Member member, final LocationRequest locationRequest) { | ||
final String planId = plan.getId().toString(); | ||
final String memberId = member.getId().toString(); | ||
|
||
redisTemplate | ||
.opsForGeo() | ||
.add( | ||
planId, | ||
new Point(locationRequest.getLongitude(), locationRequest.getLatitude()), | ||
member.getId().toString()); | ||
memberId); | ||
|
||
return calculate(planId, locationRequest); | ||
return calculateAverage(collectMemberDistances(planId), planId, memberId); | ||
} | ||
|
||
private AverageCoordinate calculate( | ||
final String planId, final LocationRequest locationRequest) { | ||
final Double userLongitude = locationRequest.getLongitude(); | ||
final Double userLatitude = locationRequest.getLatitude(); | ||
|
||
GeoResults<GeoLocation<String>> geoResults = | ||
redisTemplate | ||
.opsForGeo() | ||
.radius( | ||
planId, | ||
new Circle( | ||
new Point(userLongitude, userLatitude), | ||
new Distance(0, Metrics.KILOMETERS))); | ||
|
||
if (geoResults == null) { | ||
return new AverageCoordinate(); | ||
} | ||
|
||
final List<GeoResult<GeoLocation<String>>> content = geoResults.getContent(); | ||
|
||
if (content.isEmpty()) { | ||
return new AverageCoordinate(); | ||
} | ||
|
||
return calculateAverage(content, userLatitude, userLongitude); | ||
private MemberDistances collectMemberDistances(final String planId) { | ||
return redisTemplate.opsForSet().members(planId).stream() | ||
.map( | ||
member -> | ||
redisTemplate | ||
.opsForGeo() | ||
.position(planId, member) | ||
.get(CURRENT_LOCATION_INDEX)) | ||
.collect(Collectors.collectingAndThen(Collectors.toList(), MemberDistances::new)); | ||
} | ||
|
||
private AverageCoordinate calculateAverage( | ||
final List<GeoResult<GeoLocation<String>>> content, | ||
final Double userLatitude, | ||
final Double userLongitude) { | ||
|
||
double totalLatitude = 0.0; | ||
double totalLongitude = 0.0; | ||
final MemberDistances memberDistances, final String planId, final String memberId) { | ||
|
||
for (GeoResult<GeoLocation<String>> geoResult : content) { | ||
Point point = geoResult.getContent().getPoint(); | ||
totalLatitude += point.getY(); | ||
totalLongitude += point.getX(); | ||
} | ||
final double averageLongitude = memberDistances.averageLongitude(); | ||
final double averageLatitude = memberDistances.averageLatitude(); | ||
|
||
final int size = content.size(); | ||
double avgLatitude = totalLatitude / size; | ||
double avgLongitude = totalLongitude / size; | ||
redisTemplate.opsForGeo().add(planId, new Point(averageLongitude, averageLatitude), planId); | ||
|
||
final Double distance = distance(userLatitude, userLongitude, avgLatitude, avgLongitude); | ||
final Double distance = distance(planId, memberId); | ||
|
||
return new AverageCoordinate(avgLongitude, avgLatitude, distance); | ||
return new AverageCoordinate(averageLongitude, averageLatitude, distance); | ||
} | ||
|
||
private Double distance( | ||
final Double lat1, final Double lon1, final Double lat2, final Double lon2) { | ||
int radius = 6371; | ||
|
||
double dLat = Math.toRadians(lat2 - lat1); | ||
double dLon = Math.toRadians(lon2 - lon1); | ||
|
||
double a = | ||
Math.sin(dLat / 2) * Math.sin(dLat / 2) | ||
+ Math.cos(Math.toRadians(lat1)) | ||
* Math.cos(Math.toRadians(lat2)) | ||
* Math.sin(dLon / 2) | ||
* Math.sin(dLon / 2); | ||
|
||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
|
||
return radius * c; | ||
private double distance(final String planId, final String memberId) { | ||
return redisTemplate | ||
.opsForGeo() | ||
.distance(planId, memberId, planId, Metrics.KILOMETERS) | ||
.getValue(); | ||
} | ||
} |
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