Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ dependencies {

// S2
implementation 'com.github.google:s2-geometry-library-java:2.0.0'
implementation 'org.hibernate.orm:hibernate-spatial'
implementation 'org.locationtech.jts:jts-core:1.19.0'

// QueryDSL
implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.knu.ddip.ddipevent.application.dto.*;
import com.knu.ddip.ddipevent.domain.DdipEvent;
import com.knu.ddip.ddipevent.exception.DdipNotFoundException;
import com.knu.ddip.ddipevent.util.DistanceConverter;
import com.knu.ddip.user.business.dto.UserEntityDto;
import com.knu.ddip.user.business.service.UserRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -19,6 +20,7 @@ public class DdipService {

private final DdipEventRepository ddipEventRepository;
private final UserRepository userRepository;
private final DistanceConverter distanceConverter;

@Transactional
public DdipEventDetailDto createDdipEvent(CreateDdipRequestDto dto, UUID requesterId) {
Expand All @@ -35,7 +37,7 @@ public List<DdipEventSummaryDto> getDdipEventFeed(FeedRequestDto dto) {
dto.sw_lat(), dto.sw_lon(), dto.ne_lat(), dto.ne_lon(), dto.sort(), dto.user_lat(), dto.user_lon());

return events.stream()
.map(this::convertToSummaryDto)
.map(event -> convertToSummaryDto(event, dto.user_lat(), dto.user_lon()))
.toList();
}

Expand All @@ -45,8 +47,8 @@ public DdipEventDetailDto getDdipEventDetail(UUID ddipId) {
return convertToDetailDto(event);
}

private DdipEventSummaryDto convertToSummaryDto(DdipEvent event) {
// TODO: distance(요청자와 사용자 사이의 거리) 계산 로직 추가
private DdipEventSummaryDto convertToSummaryDto(DdipEvent event, Double userLat, Double userLon) {
double dist = distanceConverter.haversineMeters(event.getLatitude(), event.getLongitude(), userLat, userLon);
return new DdipEventSummaryDto(
event.getId().toString(),
event.getTitle(),
Expand All @@ -58,7 +60,7 @@ private DdipEventSummaryDto convertToSummaryDto(DdipEvent event) {
event.getCreatedAt().toString(),
event.getApplicants().size(),
event.getContent(),
0.0, // 임시값으로 0.0
dist,
event.getDifficulty()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import com.knu.ddip.ddipevent.infrastructure.entity.PhotoEntity;
import com.knu.ddip.location.application.util.S2Converter;
import lombok.RequiredArgsConstructor;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.springframework.stereotype.Component;

import java.util.List;
Expand All @@ -18,11 +21,15 @@ public class DdipMapper {

private final S2Converter s2Converter;

public static final int SRID = 4326;
private GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), SRID);

public DdipEventEntity toEntity(DdipEvent domain) {
DdipEventEntity entity = buildDdipEventEntity(domain);
entity.setPhotos(mapPhotos(domain.getPhotos(), entity));
entity.setInteractions(mapInteractions(domain.getInteractions(), entity));
entity.setCellId(s2Converter.toCellIdString(domain.getLatitude(),domain.getLongitude()));
entity.setLocalPoint(geometryFactory.createPoint(new Coordinate(domain.getLongitude(), domain.getLatitude())));
return entity;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.UuidGenerator;
import org.hibernate.type.SqlTypes;
import org.locationtech.jts.geom.Point;

import java.time.Instant;
import java.util.List;
Expand Down Expand Up @@ -44,6 +45,10 @@ public class DdipEventEntity {
@Column(nullable = false)
private Double longitude;

@Column(name = "local_point", columnDefinition = "POINT SRID 4326", nullable = false)
@Setter
private Point localPoint;

@Column(nullable = false)
@Setter
private String cellId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.UUID;

public interface DdipEventJpaRepository extends JpaRepository<DdipEventEntity, UUID> {
List<DdipEventEntity> findAllByCellIdIn(List<String> cellIds);
@Query(value = """
SELECT * FROM ddip_event
WHERE ST_CONTAINS(ST_Buffer(ST_SRID(POINT(:lng, :lat), 4326), :dist), local_point)
ORDER BY ST_Distance_Sphere(ST_SRID(POINT(:lng, :lat), 4326), local_point)
""", nativeQuery = true)
List<DdipEventEntity> findAllByDistance(@Param("lng") Double lng, @Param("lat") Double lat, @Param("dist") Double dist);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

import com.knu.ddip.ddipevent.application.service.DdipEventRepository;
import com.knu.ddip.ddipevent.domain.DdipEvent;
import com.knu.ddip.ddipevent.domain.DdipStatus;
import com.knu.ddip.ddipevent.infrastructure.DdipMapper;
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import com.knu.ddip.location.application.service.LocationService;
import com.knu.ddip.ddipevent.util.DistanceConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand All @@ -23,8 +21,7 @@ public class DdipEventRepositoryImpl implements DdipEventRepository {

private final DdipEventJpaRepository ddipEventJpaRepository;
private final DdipMapper ddipMapper;

private final LocationService locationService;
private final DistanceConverter distanceConverter;

@Transactional
@Override
Expand All @@ -41,21 +38,33 @@ public Optional<DdipEvent> findById(UUID id) {

@Override
public List<DdipEvent> findWithinBounds(double swLat, double swLon, double neLat, double neLon, String sort, Double userLat, Double userLon) {
List<String> cellIds = locationService.getNeighborCellIdsToRetrieveNearDdipRequest(swLat, swLon, neLat, neLon);
double dist = boundingBoxRadiusMeters(swLat, swLon, neLat, neLon, userLat, userLon);

return ddipEventJpaRepository.findAllByDistance(userLon, userLat, dist).stream()
.map(ddipMapper::toDomain)
.toList();
}

List<DdipEventEntity> ddipEventEntities = ddipEventJpaRepository.findAllByCellIdIn(cellIds);
private double boundingBoxRadiusMeters(double swLat, double swLon, double neLat, double neLon, Double userLat, Double userLon) {
double[][] locations = new double[][]{
{swLat, swLon},
{neLat, neLon},

Comparator<DdipEventEntity> comparator = (o1, o2) -> {
double dist1 = Math.pow(userLat - o1.getLatitude(), 2) + Math.pow(userLon - o1.getLongitude(), 2);
double dist2 = Math.pow(userLat - o2.getLatitude(), 2) + Math.pow(userLon - o2.getLongitude(), 2);
return dist1 - dist2 >= 0 ? 1 : -1;
{neLat, swLon},
{swLat, neLon},

{neLat, userLon},
{swLat, userLon},

{userLat, neLon},
{userLat, swLon},
};

// 유저와 이벤트 거리 비교해서 거리 가까운 순 정렬
return ddipEventEntities.stream()
.filter(event -> event.getStatus().equals(DdipStatus.OPEN))
.sorted(comparator)
.map(ddipMapper::toDomain)
.toList();
double maxDist = -1;
for (double[] location : locations) {
double dist = distanceConverter.haversineMeters(userLat, userLon, location[0], location[1]);
maxDist = Math.max(maxDist, dist);
}
return maxDist;
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/knu/ddip/ddipevent/util/DistanceConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.knu.ddip.ddipevent.util;

import org.springframework.stereotype.Component;

@Component
public class DistanceConverter {

private final double EARTH_RADIUS_M = 6_371_000.0;

public double haversineMeters(double lat1, double lon1, double lat2, double lon2) {
double phi1 = Math.toRadians(lat1);
double phi2 = Math.toRadians(lat2);
double dPhi = Math.toRadians(lat2 - lat1);
double dLambda = Math.toRadians(lon2 - lon1);

double a = Math.sin(dPhi / 2) * Math.sin(dPhi / 2)
+ Math.cos(phi1) * Math.cos(phi2)
* Math.sin(dLambda / 2) * Math.sin(dLambda / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_M * c;
}
}
8 changes: 4 additions & 4 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ spring.data.redis.port=${REDIS_PORT}
spring.data.redis.password=${REDIS_PASSWORD}

# JPA
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
#spring.jpa.properties.hibernate.default_batch_fetch_size=1000 # TODO: ????. ?? ????
spring.jpa.properties.hibernate.default_batch_fetch_size=1000
1 change: 1 addition & 0 deletions src/main/resources/sql/spatial_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE SPATIAL INDEX idx_ddip_event_local_point ON ddip_event (local_point);
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.knu.ddip.ddipevent.domain.DdipEvent;
import com.knu.ddip.ddipevent.domain.DdipStatus;
import com.knu.ddip.ddipevent.exception.DdipNotFoundException;
import com.knu.ddip.ddipevent.util.DistanceConverter;
import com.knu.ddip.user.business.dto.UserEntityDto;
import com.knu.ddip.user.business.service.UserRepository;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -40,6 +41,9 @@ class DdipServiceTest {
@Mock
private UserRepository userRepository;

@Mock
private DistanceConverter distanceConverter;

@DisplayName("띱 이벤트 생성 성공")
@Test
void givenCreateDdipRequest_whenCreateDdipEvent_thenDdipEventDetailDtoIsReturned() {
Expand Down Expand Up @@ -86,6 +90,8 @@ void givenFeedRequest_whenGetDdipEventFeed_thenListOfDdipEventSummaryDtoIsReturn
.requesterId(UUID.randomUUID())
.createdAt(Instant.now())
.applicants(new ArrayList<>())
.latitude(0.0)
.longitude(0.0)
.build();
List<DdipEvent> events = List.of(ddipEvent);

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@
import com.knu.ddip.config.RedisTestContainerConfig;
import com.knu.ddip.config.TestEnvironmentConfig;
import com.knu.ddip.ddipevent.domain.DdipEvent;
import com.knu.ddip.ddipevent.domain.DdipStatus;
import com.knu.ddip.ddipevent.fixture.DdipEventFixture;
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import com.knu.ddip.location.application.util.S2Converter;
import com.knu.ddip.user.infrastructure.repository.UserRepositoryImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -20,6 +16,7 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -40,39 +37,22 @@ class DdipEventRepositoryImplIntegrationTest {
@Test
void findWithinBoundsTest() {
// given
List<DdipEventEntity> ddipEvents = List.of(
DdipEventFixture.createDdipEvent(35.8880523, 128.6058911, DdipStatus.OPEN, "대운동장", s2Converter.toCellIdString(35.8880523, 128.6058911)), // 대운동장
DdipEventFixture.createDdipEvent(35.8868876, 128.6082622, DdipStatus.OPEN, "공대9호관", s2Converter.toCellIdString(35.8868876, 128.6082622)), // 공대9호관
DdipEventFixture.createDdipEvent(35.8880089, 128.6114594, DdipStatus.OPEN, "융복합관", s2Converter.toCellIdString(35.8880089, 128.6114594)) // 융복합관
List<DdipEvent> ddipEvents = List.of(
DdipEvent.create("대운동장", "대운동장", 1, 35.8880523, 128.6058911, 1, UUID.randomUUID()),
DdipEvent.create("공대9호관", "공대9호관", 1, 35.8868876, 128.6082622, 1, UUID.randomUUID()),
DdipEvent.create("융복합관", "융복합관", 1, 35.8880089, 128.6114594, 1, UUID.randomUUID())
);
ddipEventJpaRepository.saveAll(ddipEvents);
for (DdipEvent ddipEvent : ddipEvents) {
ddipEventRepositoryImpl.save(ddipEvent);
}

// when
List<DdipEvent> sortDdipEvents = ddipEventRepositoryImpl.findWithinBounds(35.8853838, 128.6058911, 35.8955185, 128.6140665, "sort", 35.8886499, 128.6121487);// 일청담
List<DdipEvent> sortDdipEvents = ddipEventRepositoryImpl.findWithinBounds(35.8853838, 128.6058911, 35.8955185, 128.6140665, "sort", 35.8886499, 128.6121487); // 일청담

// then
assertThat(sortDdipEvents).hasSize(3)
.extracting(DdipEvent::getContent)
.containsExactly("융복합관", "공대9호관", "대운동장"); // 거리 가까운 순 정렬
}

@Test
void findWithinBoundsWithOnlyOpenEventsTest() {
// given
List<DdipEventEntity> ddipEvents = List.of(
DdipEventFixture.createDdipEvent(35.8880523, 128.6058911, DdipStatus.OPEN, "대운동장", s2Converter.toCellIdString(35.8880523, 128.6058911)), // 대운동장
DdipEventFixture.createDdipEvent(35.8868876, 128.6082622, DdipStatus.COMPLETED, "공대9호관", s2Converter.toCellIdString(35.8868876, 128.6082622)), // 공대9호관
DdipEventFixture.createDdipEvent(35.8880089, 128.6114594, DdipStatus.COMPLETED, "융복합관", s2Converter.toCellIdString(35.8880089, 128.6114594)) // 융복합관
);
ddipEventJpaRepository.saveAll(ddipEvents);

// when
List<DdipEvent> sortDdipEvents = ddipEventRepositoryImpl.findWithinBounds(35.8853838, 128.6058911, 35.8955185, 128.6140665, "sort", 35.8886499, 128.6121487); // 일청담

// then
assertThat(sortDdipEvents).hasSize(1)
.extracting(DdipEvent::getContent)
.containsExactly("대운동장");
}

}
Loading