Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

지도 기능 #455

Merged
merged 35 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8f4c46a
Merge pull request #449 from woowacourse-teams/refactor/430-n+1
donghae-kim Sep 14, 2023
5016eef
Merge pull request #447 from woowacourse-teams/refactor/442-imporve-i…
green-kong Sep 14, 2023
cb97af4
feat: cafe coordinate 컬럼추가
green-kong Sep 14, 2023
534098e
chore: 빌드파일 & application.properties
green-kong Sep 14, 2023
64bb3c8
chore: coordinate table 분리
green-kong Sep 14, 2023
cd4ea53
feat: CafeCoordinate 구현
green-kong Sep 14, 2023
61472c8
feat: PointGenerator구현
green-kong Sep 14, 2023
80eae2e
feat: CafeAdminService#존재하는 카페에 좌표를 추가하는 기능 구현
green-kong Sep 14, 2023
ab2e217
feat: CafeAdminController#카페에 좌표를 추가하는 기능 구현
green-kong Sep 14, 2023
d72d17b
chore: coordinate column에 SRID 값 추가
green-kong Sep 15, 2023
219f1fa
fix: 위도 경도 파라미터 순서 변경
green-kong Sep 15, 2023
5312ecd
feat: 입력받은 위도 경도로 부터 반경안에 포함된 모든 카페를 반환하는 기능 구현
green-kong Sep 15, 2023
48ba832
feat: 위도 경도 델타값을 통해 가장 작은 반경의 크기를 구하는 기능 구현
green-kong Sep 15, 2023
8a4773f
feat: 위도 경도를 통해 스트링형식의 포인트를 생성하는 기능 구현
green-kong Sep 15, 2023
6c85817
feat: 기준 좌표와, 델타 좌표를 받아 반경안에 포함된 카페정보를 반환하는 기능 구현
green-kong Sep 15, 2023
80c755a
feat: 지도에 표시 될 수 있는 카페들을 반환하는 api 구현
green-kong Sep 15, 2023
90f3c49
chore: index 설정
green-kong Sep 16, 2023
6673f30
chore: properties 필요없는 부분 삭제 & hibernate-spatial gradle내 위치 변경
green-kong Sep 20, 2023
e8da93b
chore: cafe_coordinates -> cafe_coordinate 테이블 명 변경
green-kong Sep 20, 2023
0ffc535
refactor: `@ElementCollection` fetch 옵션 제거(default 사용)
green-kong Sep 20, 2023
aae9b15
chore: SRID 설명 주석 추가
green-kong Sep 20, 2023
fab67ef
fix: CafeLocationResponse 에 name 중복으로 들어간 부분 수정
green-kong Sep 20, 2023
7713312
chore: coordinate 인덱스 생성 쿼리 수정
green-kong Sep 20, 2023
cb5074f
fix: native쿼리 table명 수정
green-kong Sep 20, 2023
29d4ea1
refactor: `@QueryParam` dto로 변경
green-kong Sep 20, 2023
67520c2
Merge branch 'dev' into feat/451-map
green-kong Sep 20, 2023
e6774f5
feat: GeometryGenerator#generateStringPolygon
green-kong Sep 21, 2023
a44ac82
feat: STRING_POLYGON_FORMAT 수정
green-kong Sep 21, 2023
93b795a
feat: area 내부에 있는 카페를 반환하는 기능 구현
green-kong Sep 21, 2023
56188a5
refactor: 좌표기반 카페검색 로직 변경
green-kong Sep 21, 2023
eb52d23
refactor: 사용하지 않는 값 & 메서드 제거
green-kong Sep 21, 2023
d1d422e
Merge branch 'feat/451-map' of https://github.com/woowacourse-teams/2…
green-kong Sep 21, 2023
7c42a01
refactor: 사용하지 않는 클래스 제거
green-kong Sep 22, 2023
c379a6c
chore: spatial 디펜던시 변경
green-kong Sep 25, 2023
4d014f2
refactor: CafeCoordinate name option 삭제 & 기본 생성자 접근제한 public -> prote…
green-kong Sep 25, 2023
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 server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation "org.testcontainers:junit-jupiter:1.19.0"
testImplementation 'org.testcontainers:mysql'
implementation group: 'org.hibernate', name: 'hibernate-spatial', version: '6.2.5.Final'
Copy link
Collaborator

Choose a reason for hiding this comment

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

    implementation 'org.hibernate:hibernate-spatial:6.2.5.Final'

다른 것들과 마찬가지로 이 문법을 사용하면 좋을 것 같아요!


//JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import com.project.yozmcafe.controller.dto.cafe.CafeCoordinateRequest;
import com.project.yozmcafe.controller.dto.cafe.CafeRequest;
import com.project.yozmcafe.controller.dto.cafe.CafeResponse;
import com.project.yozmcafe.controller.dto.cafe.CafeUpdateRequest;
Expand Down Expand Up @@ -108,4 +110,11 @@ public ResponseEntity<String> saveMenuBoards(@PathVariable("cafeId") final Long

return ResponseEntity.created(URI.create("/admin/cafes/" + cafeId)).build();
}

@PostMapping("/{cafeId}/coordinate")
public ResponseEntity<String> saveCoordinate(@PathVariable("cafeId") final Long cafeId,
@RequestBody final CafeCoordinateRequest cafeCoordinateRequest) {
cafeAdminService.saveCafeCoordinate(cafeId, cafeCoordinateRequest);
return ResponseEntity.created(URI.create("/admin/cafes/" + cafeId)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.project.yozmcafe.controller;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.project.yozmcafe.controller.dto.cafe.CafeLocationRequest;
import com.project.yozmcafe.controller.dto.cafe.CafeLocationResponse;
import com.project.yozmcafe.service.LocationService;

@RestController
@RequestMapping("/cafes/location")
public class LocationController {

private final LocationService locationService;

public LocationController(final LocationService locationService) {
this.locationService = locationService;
}

@GetMapping
public ResponseEntity<List<CafeLocationResponse>> findCafesFromLocation(
final CafeLocationRequest cafeLocationRequest) {
final List<CafeLocationResponse> cafesFromLocations = locationService.findCafesFromLocations(
cafeLocationRequest);
return ResponseEntity.ok(cafesFromLocations);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.project.yozmcafe.controller.dto.cafe;

import org.locationtech.jts.geom.Point;

import com.project.yozmcafe.domain.cafe.Cafe;
import com.project.yozmcafe.domain.cafe.GeometryGenerator;
import com.project.yozmcafe.domain.cafe.coordinate.CafeCoordinate;

public record CafeCoordinateRequest(double latitude, double longitude) {

public CafeCoordinate toCafeCoordinateWithCafe(final Cafe cafe) {
final Point point = GeometryGenerator.generatePointWithCoordinate(latitude, longitude);
return new CafeCoordinate(point, cafe);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.project.yozmcafe.controller.dto.cafe;

public record CafeLocationRequest(double latitude, double longitude, double latitudeDelta, double longitudeDelta) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

파라미터 dto 굿~~

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.project.yozmcafe.controller.dto.cafe;

import com.project.yozmcafe.domain.cafe.coordinate.dto.CafePinDto;

public record CafeLocationResponse(Long id, String name, String address, double latitude, double longitude) {

public static CafeLocationResponse from(final CafePinDto cafePinDto) {
return new CafeLocationResponse(
cafePinDto.getId(),
cafePinDto.getName(),
cafePinDto.getAddress(),
cafePinDto.getLatitude(),
cafePinDto.getLongitude()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.project.yozmcafe.domain.cafe;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;

import com.project.yozmcafe.controller.dto.cafe.CafeLocationRequest;

public class GeometryGenerator {

private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
/**
* SRID(Spatial Reference Identifier)는 고유한 공간 좌표 식별자입니다.
* <p>
* 그 중, 4326은 WGS84 경위도 좌표계를 의미합니다. GPS 기술에서도 사용되는 좌표계로 범용적으로 가장 널리 사용되는 좌표계입니다.
*/
private static final int SRID = 4326;
private static final String STRING_POLYGON_FORMAT = "POLYGON((%s))";
private static final String STRING_GEOMETRY_DELIMITER = " ";
private static final String POINT_DELIMITER = ", ";

private GeometryGenerator() {
}

public static Point generatePointWithCoordinate(final double latitude, final double longitude) {
final Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(longitude, latitude));
point.setSRID(SRID);
return point;
}

public static String generateStringPolygon(final CafeLocationRequest cafeLocationRequest) {
final double latitude = cafeLocationRequest.latitude();
final double longitude = cafeLocationRequest.longitude();
final double latitudeDelta = cafeLocationRequest.latitudeDelta();
final double longitudeDelta = cafeLocationRequest.longitudeDelta();

final String minLatitude = String.valueOf(latitude - latitudeDelta);
final String maxLatitude = String.valueOf(latitude + latitudeDelta);
final String minLongitude = String.valueOf(longitude - longitudeDelta);
final String maxLongitude = String.valueOf(longitude + longitudeDelta);

final String firstVertex = String.join(STRING_GEOMETRY_DELIMITER, minLatitude, maxLongitude);
final String secondVertex = String.join(STRING_GEOMETRY_DELIMITER, maxLatitude, maxLongitude);
final String thirdVertex = String.join(STRING_GEOMETRY_DELIMITER, maxLatitude, minLongitude);
final String fourthVertex = String.join(STRING_GEOMETRY_DELIMITER, minLatitude, minLongitude);
Copy link
Collaborator

Choose a reason for hiding this comment

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

원 대신 새로 생긴 사각형 좌표들이군요! 좋아용


final String vertexes = String.join(POINT_DELIMITER, firstVertex, secondVertex, thirdVertex, fourthVertex,
firstVertex);
return String.format(STRING_POLYGON_FORMAT, vertexes);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.project.yozmcafe.domain.cafe;

import java.util.List;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;

import java.util.List;

@Embeddable
public class Images {

private static final int REPRESENTATIVE_INDEX = 0;

@ElementCollection(fetch = FetchType.LAZY)
@ElementCollection
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

@CollectionTable(name = "image")
private List<String> urls;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.project.yozmcafe.domain.cafe.coordinate;

import static jakarta.persistence.GenerationType.IDENTITY;

import org.locationtech.jts.geom.Point;

import com.project.yozmcafe.domain.cafe.Cafe;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;

@Entity(name = "cafe_coordinate")
Copy link
Collaborator

Choose a reason for hiding this comment

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

name 옵션 없애도 될 것 같습니다!

public class CafeCoordinate {
Copy link
Collaborator

Choose a reason for hiding this comment

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

하나의 엔티티로 만든 이유가 궁금합니다!
카페의 위치 같은 경우는 비즈니스적으로 분리된다고 생각하셔서 만드신 걸까요??

제 생각으로는 카페의 전화번호나 주소 등의 추가 정보와 Coordinate는 동일한 변경 주기를 가지고 있는 것 같아요.

Copy link
Collaborator

Choose a reason for hiding this comment

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

저도 연어 의견에 동의합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아 이 부분.. 저도 많이 고민했었는데요.

연어의 말대로 비즈니스적으로 분리가 된다고 생각했습니다.
현재의 비즈니스에서는 단순히 지도검색용으로 사용되는 데이터기에 카페의 메인데이터와는 분리를 하고싶었습니다.

다른 부분은 모르겠지만, 카페의 주소와는 동일한 변경 주기를 갖고있다는 말에는 백프로 동의합니다.
주소가 바뀌면 좌표도 당연히 바뀌게 되니까요.
하지만 가게의 이전이라는게 그렇게 많이 일어나는 일이 아니다보니,(폐업이면 몰라도..)
이 부분 만큼은 비즈니스적으로 데이터를 분리하는 게 낫지 않을까 라는 생각을 했습니다.

만약 이 부분이 다른 비즈니스로직에도 사용되어야 하는 경우에는
Location table로 주소와 좌표를 분리하는게 낫지 않을까.. 라는 생각을 했었습니다


@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

private Point coordinate;

@OneToOne(fetch = FetchType.LAZY)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@OneToOne 관계에서의 지연 로딩 옵션이 안 먹힐 때가 있는 걸로 아는데 이때는 되는 건가요??

Copy link
Collaborator

Choose a reason for hiding this comment

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

연어 의견에 덧붙이자면 @OnetoOne은 양방향일때만 안먹는 거로 아는데 단방향으로 LAZY 처리 해야될 것 같네요

Copy link
Collaborator Author

@green-kong green-kong Sep 20, 2023

Choose a reason for hiding this comment

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

저는 @OneToOne처음 써봐서, 이번에 지도기능 구현하면서 구글링 하다가 연어가 얘기한 내용을 접했는데요.
조금 더 덧붙이자면, 양방향 관계일때, 연관관계의 주인이 아닌 경우 LAZY가 안먹는 다고 하네요!

Cafe <-> CafeCoordinates(필드에 Cafe 갖고있음)
이 경우에 Cafe쪽에서는 LAZY 를 걸어둬도 안먹는 경우가 있다하네요.
nullable을 통해 해결할 수 있다 정도의 내용만 기억나지 더 자세한 해결방법에 대해선 기억이 나질 않네요ㅠ

현재 코드는 단방향 매핑관계라 LAZY 잘 먹습니당 ㅎㅎ

private Cafe cafe;

public CafeCoordinate() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

접근제어자 protected로 바꿔도 좋을 것 같아용

}

public CafeCoordinate(final Long id, final Point coordinate, final Cafe cafe) {
this.id = id;
this.coordinate = coordinate;
this.cafe = cafe;
}

public CafeCoordinate(final Point coordinate, final Cafe cafe) {
this(null, coordinate, cafe);
}

public double getLatitude() {
return coordinate.getY();
}

public double getLongitude() {
return coordinate.getX();
}

public Long getId() {
return id;
}

public Point getCoordinate() {
return coordinate;
}

public Cafe getCafe() {
return cafe;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.project.yozmcafe.domain.cafe.coordinate;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.project.yozmcafe.domain.cafe.coordinate.dto.CafePinDto;

public interface CafeCoordinateRepository extends JpaRepository<CafeCoordinate, Long> {

@Query(nativeQuery = true,
value = """
SELECT c.id, c.name, c.address, ST_X(co.coordinate) AS latitude, ST_Y(co.coordinate) AS longitude
FROM cafe_coordinate co
JOIN cafe AS c
ON co.cafe_id = c.id
WHERE ST_CONTAINS(ST_GeomFromText(:area, 4326), co.coordinate);
""")
List<CafePinDto> findCafePinsFromCoordinate(@Param("area") final String area);
Copy link
Collaborator

Choose a reason for hiding this comment

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

string으로 범위를 주면 그거에 맞춰서 조회 하나보네요 ㄷㄷ 신기방기

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

정확하게는 Polygon wktST_GeomFromText() 의 인자로 넘기면 텍스트를 Polygon데이터로 변환해 줍니다.
그리고 변환된 polygon을 ST_cotains의 첫번째 인자로 넘겨줍니다~

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.project.yozmcafe.domain.cafe.coordinate.dto;

public interface CafePinDto {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Projection으로 매핑을 해주는 것이군요!

Copy link
Collaborator

Choose a reason for hiding this comment

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

신기하네요 ...

Long getId();

String getName();

String getAddress();

double getLatitude();

double getLongitude();
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
package com.project.yozmcafe.service;

import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_CAFE;

import java.util.List;
import java.util.stream.Stream;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.project.yozmcafe.controller.dto.cafe.CafeCoordinateRequest;
import com.project.yozmcafe.controller.dto.cafe.CafeRequest;
import com.project.yozmcafe.controller.dto.cafe.CafeResponse;
import com.project.yozmcafe.controller.dto.cafe.CafeUpdateRequest;
import com.project.yozmcafe.domain.cafe.Cafe;
import com.project.yozmcafe.domain.cafe.CafeRepository;
import com.project.yozmcafe.domain.cafe.Images;
import com.project.yozmcafe.domain.cafe.coordinate.CafeCoordinate;
import com.project.yozmcafe.domain.cafe.coordinate.CafeCoordinateRepository;
import com.project.yozmcafe.exception.BadRequestException;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Stream;

import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_CAFE;

@Service
@Transactional(readOnly = true)
public class CafeAdminService {

private final CafeRepository cafeRepository;
private final CafeCoordinateRepository cafeCoordinateRepository;
@PersistenceContext
private final EntityManager entityManager;

public CafeAdminService(final CafeRepository cafeRepository, final EntityManager entityManager) {
public CafeAdminService(final CafeRepository cafeRepository,
final CafeCoordinateRepository cafeCoordinateRepository,
final EntityManager entityManager) {
this.cafeRepository = cafeRepository;
this.cafeCoordinateRepository = cafeCoordinateRepository;
this.entityManager = entityManager;
}

Expand All @@ -40,13 +49,13 @@ public Long save(final CafeRequest cafeRequest, List<String> imageNames) {

@Transactional
public void update(final long cafeId, final CafeUpdateRequest request, List<String> images) {
final Cafe cafe = getOrThrow(cafeId);
final Cafe cafe = getCafeOrThrow(cafeId);
final Cafe requestedCafe = request.toCafeWithId(cafeId, images);

cafe.update(requestedCafe);
}

private Cafe getOrThrow(final long cafeId) {
private Cafe getCafeOrThrow(final long cafeId) {
return cafeRepository.findById(cafeId)
.orElseThrow(() -> new BadRequestException(NOT_EXISTED_CAFE));
}
Expand All @@ -58,7 +67,7 @@ public List<CafeResponse> findAll() {
}

public CafeResponse findById(final long cafeId) {
final Cafe cafe = getOrThrow(cafeId);
final Cafe cafe = getCafeOrThrow(cafeId);
return CafeResponse.fromUnLoggedInUser(cafe);
}

Expand All @@ -75,7 +84,15 @@ public void delete(final long cafeId) {
}

public List<String> findImagesByCafeId(final Long cafeId) {
final Images images = getOrThrow(cafeId).getImages();
final Images images = getCafeOrThrow(cafeId).getImages();
return images.getUrls();
}

@Transactional
public Long saveCafeCoordinate(final Long cafeId, final CafeCoordinateRequest cafeCoordinateRequest) {
final Cafe cafe = getCafeOrThrow(cafeId);
final CafeCoordinate cafeCoordinate = cafeCoordinateRequest.toCafeCoordinateWithCafe(cafe);
cafeCoordinateRepository.save(cafeCoordinate);
return cafeCoordinate.getId();
}
}
Loading