Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/main/java/com/knu/ddip/ddipevent/domain/DdipEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class DdipEvent {
private final UUID requesterId;
private final Double latitude;
private final Double longitude;
private final String cellId;
private final Instant createdAt;
private String title;
private String content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import com.knu.ddip.ddipevent.infrastructure.entity.InteractionEntity;
import com.knu.ddip.ddipevent.infrastructure.entity.PhotoEntity;
import com.knu.ddip.location.application.util.S2Converter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class DdipMapper {

private final S2Converter s2Converter;

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()));
return entity;
}

Expand Down Expand Up @@ -81,6 +87,7 @@ public DdipEvent toDomain(DdipEventEntity entity) {
.reward(entity.getReward())
.latitude(entity.getLatitude())
.longitude(entity.getLongitude())
.cellId(entity.getCellId())
.createdAt(entity.getCreatedAt())
.status(entity.getStatus())
.selectedResponderId(entity.getSelectedResponderId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public class DdipEventEntity {
@Column(nullable = false)
private Double longitude;

@Column(nullable = false)
@Setter
private String cellId;

Comment on lines +47 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생각해보니 notion에는 ddipEvent 엔티티 필드에 cellId가 정의되어 있지 않았네요
정규화가 되어있던건지,, 관련해서 한번 어떤식으로 현재 프론트 fakeRepository의 비즈니스 로직이 구현되어있는지 이야기해보면 좋을 것 같습니다
entity의 필드가 변경되면 노션의 필드도 변경하는게 연동에 좋을 것 같아 그렇습니다 !

@Column(nullable = false)
private Instant createdAt;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import org.springframework.data.jpa.repository.JpaRepository;

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

public interface DdipEventJpaRepository extends JpaRepository<DdipEventEntity, UUID> {
List<DdipEventEntity> findAllByCellIdIn(List<String> cellIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,31 @@

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 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;


@Transactional(readOnly = true)
@Repository
@RequiredArgsConstructor
public class DdipEventRepositoryImpl implements DdipEventRepository {

private final DdipEventJpaRepository ddipEventJpaRepository;
private final DdipMapper ddipMapper;
// private final JPAQueryFactory queryFactory;
// private final RedisTemplate<String, String> redisTemplate;
// private static final int S2_LEVEL = 15;

private final LocationService locationService;

@Transactional
@Override
public DdipEvent save(DdipEvent ddipEvent) {
DdipEventEntity entity = ddipMapper.toEntity(ddipEvent);
Expand All @@ -36,59 +41,21 @@ 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) {
// TODO: 실제 S2를 이용한 로직 구현 필요. 현재는 전체 다 반환
return ddipEventJpaRepository.findAll()
.stream().map(ddipMapper::toDomain).toList();
List<String> cellIds = locationService.getNeighborCellIdsToRetrieveNearDdipRequest(swLat, swLon, neLat, neLon);

List<DdipEventEntity> ddipEventEntities = ddipEventJpaRepository.findAllByCellIdIn(cellIds);

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;
};

// 유저와 이벤트 거리 비교해서 거리 가까운 순 정렬
return ddipEventEntities.stream()
.filter(event -> event.getStatus().equals(DdipStatus.OPEN))
.sorted(comparator)
.map(ddipMapper::toDomain)
.toList();
}
// S2LatLng sw = S2LatLng.fromDegrees(swLat, swLon);
// S2LatLng ne = S2LatLng.fromDegrees(neLat, neLon);
// S2RegionCoverer coverer = new S2RegionCoverer();
// coverer.setMinLevel(S2_LEVEL);
// coverer.setMaxLevel(S2_LEVEL);
// List<S2CellId> cellIds = coverer.getCovering(new com.google.common.geometry.S2LatLngRect(sw, ne)).cellIds();
//
// List<String> keys = cellIds.stream()
// .map(cellId -> "s2:" + cellId.toToken())
// .toList();
//
// List<String> eventIds = keys.stream()
// .flatMap(key -> redisTemplate.opsForSet().members(key).stream())
// .distinct()
// .toList();
//
// if (eventIds.isEmpty()) {
// return List.of();
// }
//
// List<DdipEvent> events = queryFactory
// .selectFrom(ddipEventEntity)
// .where(ddipEventEntity.id.in(eventIds.stream().map(UUID::fromString).collect(Collectors.toList()))
// .and(ddipEventEntity.latitude.between(swLat, neLat))
// .and(ddipEventEntity.longitude.between(swLon, neLon)))
// .fetch()
// .stream()
// .map(ddipMapper::toDomain)
// .collect(Collectors.toList());
//
// if ("distance".equals(sort) && userLat != null && userLon != null) {
// events.sort((e1, e2) -> {
// double dist1 = distance(userLat, userLon, e1.getLatitude(), e1.getLongitude());
// double dist2 = distance(userLat, userLon, e2.getLatitude(), e2.getLongitude());
// return Double.compare(dist1, dist2);
// });
// } else {
// events.sort((e1, e2) -> e2.getCreatedAt().compareTo(e1.getCreatedAt()));
// }
//
// return events;
// }
//
// private double distance(double lat1, double lon1, double lat2, double lon2) {
// double theta = lon1 - lon2;
// double dist = Math.sin(Math.toRadians(lat1)) * Math.sin(Math.toRadians(lat2)) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.cos(Math.toRadians(theta));
// dist = Math.acos(dist);
// dist = Math.toDegrees(dist);
// dist = dist * 60 * 1.1515 * 1609.344;
// return dist;
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.geometry.S2CellId;
import com.google.common.geometry.*;
import com.knu.ddip.location.application.dto.UpdateMyLocationRequest;
import com.knu.ddip.location.application.util.S2Constants;
import com.knu.ddip.location.application.util.S2Converter;
import com.knu.ddip.location.application.util.UuidBase64Utils;
import lombok.RequiredArgsConstructor;
Expand All @@ -21,6 +22,8 @@
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static com.knu.ddip.location.application.util.S2Constants.*;

@Slf4j
@Service
@Transactional(readOnly = true)
Expand All @@ -30,9 +33,10 @@ public class LocationService {
private final LocationReader locationReader;
private final LocationWriter locationWriter;

private final S2Converter s2Converter;

private final ObjectMapper objectMapper;

public static final int LEVEL = 17;
public static final String KNU_GEOJSON_FEATURE_FILENAME = "geojson/cells.geojson";

// KNU GeoJSON 파일을 읽어서 각 Feature를 DB에 저장
Expand Down Expand Up @@ -62,7 +66,7 @@ public void loadAndSaveGeoJsonFeatures() {
}

public void saveUserLocationAtomic(UUID userId, UpdateMyLocationRequest request) {
S2CellId cellIdObj = S2Converter.toCellId(request.lat(), request.lng(), LEVEL);
S2CellId cellIdObj = s2Converter.toCellId(request.lat(), request.lng());
String cellId = cellIdObj.toToken();

// 경북대 내부에 위치하는지 확인
Expand All @@ -75,7 +79,7 @@ public void saveUserLocationAtomic(UUID userId, UpdateMyLocationRequest request)

// 요청 전송 시 이웃 userIds 조회
public List<UUID> getNeighborRecipientUserIds(UUID myUserId, double lat, double lng) {
S2CellId cellIdObj = S2Converter.toCellId(lat, lng, LEVEL);
S2CellId cellIdObj = s2Converter.toCellId(lat, lng);
String cellId = cellIdObj.toToken();

// 경북대 내부에 위치하는지 확인
Expand All @@ -102,9 +106,9 @@ public List<UUID> getNeighborRecipientUserIds(UUID myUserId, double lat, double
return userIds;
}

// 초기 화면에서 인접한 요청 가져오기 (현재는 인접한 cellId 가져오는 것만 구현)
public List<String> getNeighborCellIds(double lat, double lng) {
S2CellId cellIdObj = S2Converter.toCellId(lat, lng, LEVEL);
// 요청 보낼 대상 인접 셀 조회
public List<String> getNeighborCellIdsToSendDdipRequest(double lat, double lng) {
S2CellId cellIdObj = s2Converter.toCellId(lat, lng);
String cellId = cellIdObj.toToken();

// 경북대 내부에 위치하는지 확인
Expand All @@ -124,6 +128,27 @@ public List<String> getNeighborCellIds(double lat, double lng) {
return targetCellIds;
}

// 근처 띱 요청 조회용 셀 조회
public List<String> getNeighborCellIdsToRetrieveNearDdipRequest(double minLat, double minLng, double maxLat, double maxLng) {
S2LatLngRect rect = S2LatLngRect.fromPointPair(
S2LatLng.fromDegrees(minLat, minLng),
S2LatLng.fromDegrees(maxLat, maxLng)
);

S2RegionCoverer coverer = S2RegionCoverer.builder()
.setMinLevel(LEVEL)
.setMaxLevel(LEVEL)
.build();

S2CellUnion union = new S2CellUnion();
coverer.getCovering(rect, union);

List<String> cellIds = normalizeCellLevel(union, LEVEL);

List<String> filteredCellIds = locationReader.findAllLocationsByCellIdIn(cellIds);
return filteredCellIds;
}

private String getGeoJsonContent() {
ClassPathResource resource = new ClassPathResource(KNU_GEOJSON_FEATURE_FILENAME);
try (InputStream is = resource.getInputStream()) {
Expand All @@ -132,4 +157,22 @@ private String getGeoJsonContent() {
throw new RuntimeException(e);
}
}

private List<String> normalizeCellLevel(S2CellUnion union, int level) {
List<String> out = new ArrayList<>();
for (S2CellId cid : union.cellIds()) {
if (cid.level() == level) {
out.add(cid.toToken());
} else if (cid.level() < level) {
S2CellId begin = cid.childBegin(level);
S2CellId end = cid.childEnd(level);
for (S2CellId it = begin; !it.equals(end); it = it.next()) {
out.add(it.toToken());
}
} else {
out.add(cid.parent(level).toToken());
}
}
return out;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.knu.ddip.location.application.util;

public abstract class S2Constants {
public static int LEVEL = 17;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@

import com.google.common.geometry.S2CellId;
import com.google.common.geometry.S2LatLng;
import org.springframework.stereotype.Component;

public abstract class S2Converter {
import static com.knu.ddip.location.application.util.S2Constants.LEVEL;

public static S2CellId toCellId(double lat, double lng, int level) {
@Component
public class S2Converter {
public S2CellId toCellId(double lat, double lng) {
S2LatLng latLng = S2LatLng.fromDegrees(lat, lng);
return S2CellId.fromLatLng(latLng).parent(level);
return S2CellId.fromLatLng(latLng).parent(LEVEL);
}

public String toCellIdString(double lat, double lng) {
S2CellId cellId = toCellId(lat, lng);
return cellId.toToken();
}
}
6 changes: 3 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ 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=create
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Expand Down
31 changes: 31 additions & 0 deletions src/test/java/com/knu/ddip/ddipevent/fixture/DdipEventFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.knu.ddip.ddipevent.fixture;

import com.knu.ddip.ddipevent.domain.DdipStatus;
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;

import java.time.Instant;
import java.util.UUID;

public abstract class DdipEventFixture {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixture 만들어두는거 좋네요 👍


public static DdipEventEntity createDdipEvent() {
DdipEventEntity event = createDdipEvent(0.0, 0.0, DdipStatus.OPEN, "content", "cellId");
return event;
}

public static DdipEventEntity createDdipEvent(Double lat, Double lon, DdipStatus status, String content, String cellId) {
DdipEventEntity event = DdipEventEntity.builder()
.content(content)
.createdAt(Instant.now())
.difficulty(1)
.latitude(lat)
.longitude(lon)
.requesterId(UUID.randomUUID())
.reward(1)
.status(status)
.title("title")
.cellId(cellId)
.build();
return event;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.knu.ddip.ddipevent.infrastructure.entity.DdipEventEntity;
import com.knu.ddip.ddipevent.infrastructure.entity.InteractionEntity;
import com.knu.ddip.ddipevent.infrastructure.entity.PhotoEntity;
import com.knu.ddip.location.application.util.S2Converter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -21,7 +22,8 @@ class DdipMapperTest {

@BeforeEach
void setUp() {
ddipMapper = new DdipMapper();
S2Converter s2Converter = new S2Converter();
ddipMapper = new DdipMapper(s2Converter);
}

@DisplayName("도메인을 엔티티로 변환 - 모든 필드 포함")
Expand All @@ -34,6 +36,8 @@ void givenDdipEventDomainWithLists_whenToEntity_thenDdipEventEntityIsReturned()
.id(UUID.randomUUID())
.photos(List.of(photo))
.interactions(List.of(interaction))
.latitude(0.0)
.longitude(0.0)
.build();

// when
Expand All @@ -55,6 +59,8 @@ void givenDdipEventDomainWithNullLists_whenToEntity_thenDdipEventEntityIsReturne
.id(UUID.randomUUID())
.photos(null)
.interactions(null)
.latitude(0.0)
.longitude(0.0)
.build();

// when
Expand Down
Loading
Loading