Skip to content

Commit

Permalink
Merge pull request #46 from Team-PLAT/feat/#45/reverseGeocoding
Browse files Browse the repository at this point in the history
[FEAT]� 역지오코딩 API
  • Loading branch information
kyxxgsoo authored Sep 4, 2024
2 parents 36cedf5 + b105e2c commit 4df5d57
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 21 deletions.
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static class TrackLike {
public static class TrackUpload {
private String isrc;
private String imageUrl;
private String context;
private String content;
private double latitude;
private double longitude;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static class TrackDetail {
private String locationString;
private String address;
private String imageUrl;
private String context;
private String content;
private int likeCount;
private Boolean isLiked;
private MemberInfo member;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.cabin.plat.domain.track.mapper;

import com.cabin.plat.domain.member.dto.MemberResponse;
import com.cabin.plat.domain.member.entity.Member;
import com.cabin.plat.domain.track.dto.TrackRequest;
import com.cabin.plat.domain.track.dto.TrackResponse;
Expand Down Expand Up @@ -57,7 +56,7 @@ public TrackResponse.TrackDetail toTrackDetail(
.locationString(locationString)
.address(address)
.imageUrl(imageUrl)
.context(content)
.content(content)
.likeCount(likeCount)
.isLiked(isLiked)
.member(memberInfo)
Expand Down Expand Up @@ -111,7 +110,7 @@ public Track toTrack(Member member, Location location, TrackRequest.TrackUpload
.member(member)
.location(location)
.isrc(trackUpload.getIsrc())
.content(trackUpload.getContext())
.content(trackUpload.getContent())
.imageUrl(trackUpload.getImageUrl())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import com.cabin.plat.domain.track.repository.*;
import com.cabin.plat.global.exception.RestApiException;
import com.cabin.plat.global.exception.errorCode.TrackErrorCode;
import com.cabin.plat.global.util.geocoding.AddressInfo;
import com.cabin.plat.global.util.geocoding.ApiKeyProperties;
import com.cabin.plat.global.util.geocoding.ReverseGeoCoding;
import java.util.List;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -23,6 +27,7 @@ public class TrackServiceImpl implements TrackService {
private final TrackLikeRepository trackLikeRepository;
private final TrackReportRepository trackReportRepository;
private final TrackMapper trackMapper;
private final ReverseGeoCoding reverseGeoCoding;

@Override
public TrackResponse.TrackMapList getTracksByLocation(
Expand Down Expand Up @@ -87,14 +92,17 @@ public TrackResponse.TrackId likeTrack(Member member, Long trackId, Boolean isLi
@Transactional
@Override
public TrackResponse.TrackId addTrack(Member member, TrackRequest.TrackUpload trackUpload) {
double latitude = trackUpload.getLatitude();
double longitude = trackUpload.getLongitude();
AddressInfo addressInfo = reverseGeoCoding.getAddressInfo(latitude, longitude);

Location location = locationRepository.save(trackMapper.toLocation(
"장소 이름 (미구현)" ,// TODO: 장소 이름
"주소 (미구현)", // TODO: 위도 경도로 주소 받아오기
trackUpload.getLatitude(),
trackUpload.getLongitude()));
addressInfo.buildingName(),
addressInfo.toAddress(),
latitude,
longitude));

Track track = trackMapper.toTrack(member, location, trackUpload);

Track savedTrack = trackRepository.save(track);

return trackMapper.toTrackId(savedTrack.getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.cabin.plat.global.util.geocoding;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;

public record AddressInfo(String area1, String area2, String area3, String buildingName) {
public String toAddress() {
List<String> parts = Arrays.asList(area1, area2, area3);
return String.join(" ", parts.stream()
.filter(part -> !part.isEmpty())
.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.cabin.plat.global.util.geocoding;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "api-key")
public class ApiKeyProperties {
private String clientId;
private String clientKey;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.cabin.plat.global.util.geocoding;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpMethod;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

@Component
@RequiredArgsConstructor
public class ReverseGeoCoding {
private final String API_URL = "https://naveropenapi.apigw.ntruss.com/map-reversegeocode/v2/gc";
private final String ORDERS_TYPE = "legalcode,roadaddr";
private final String EMPTY_STRING = "";
private final String ERROR_STRING = "알 수 없음";

private final ApiKeyProperties apiKeyProperties;

public AddressInfo getAddressInfo(double latitude, double longitude) {
String url = buildUrl(latitude, longitude);
HttpHeaders headers = createHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);

try {
String responseBody = sendRequest(url, entity);
return parseResponse(responseBody);
} catch (HttpStatusCodeException | ResourceAccessException e) {
System.out.println("HTTP 통신 오류 발생: " + e.getMessage());
e.printStackTrace();
} catch (ParserConfigurationException | SAXException | IOException e) {
System.out.println("XML 파싱 오류 발생: " + e.getMessage());
e.printStackTrace();
} catch(Exception e) {
System.out.println("알 수 없는 오류 발생: " + e.getMessage());
e.printStackTrace();
}

return new AddressInfo(ERROR_STRING, EMPTY_STRING, EMPTY_STRING, EMPTY_STRING);
}

private String buildUrl(double latitude, double longitude) {
return API_URL + "?coords=" + longitude + "," + latitude + "&orders=" + ORDERS_TYPE;
}

private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("X-NCP-APIGW-API-KEY-ID", apiKeyProperties.getClientId());
headers.set("X-NCP-APIGW-API-KEY", apiKeyProperties.getClientKey());
return headers;
}

private String sendRequest(String url, HttpEntity<String> entity) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
return response.getBody();
}

private AddressInfo parseResponse(String responseBody) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

InputStream inputStream = new ByteArrayInputStream(responseBody.getBytes());
Document document = documentBuilder.parse(inputStream);
document.getDocumentElement().normalize();

// 주소 없음 (바다)
String statusCode = getTagValue("code", (Element) document.getElementsByTagName("status").item(0));
if (statusCode.equals("3")) {
return new AddressInfo("바다", EMPTY_STRING, EMPTY_STRING, EMPTY_STRING);
}

// 주소
String area1Name = getTagValue("name", (Element) document.getElementsByTagName("area1").item(0));
String area2Name = getTagValue("name", (Element) document.getElementsByTagName("area2").item(0));
String area3Name = getTagValue("name", (Element) document.getElementsByTagName("area3").item(0));

// 건물명
String buildingName = getTagValue("value", (Element) document.getElementsByTagName("addition0").item(0));

return new AddressInfo(area1Name, area2Name, area3Name, buildingName);
}

private String getTagValue(String tag, Element element) {
if (element == null) {
return EMPTY_STRING;
}

NodeList nodeList = element.getElementsByTagName(tag).item(0).getChildNodes();
Node node = nodeList.item(0);
if (node == null) {
return EMPTY_STRING;
}

return node.getNodeValue();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.cabin.plat.domain.track.service;

import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;

import com.cabin.plat.domain.member.entity.*;
import com.cabin.plat.domain.member.repository.MemberRepository;
import com.cabin.plat.domain.track.dto.TrackRequest;
import com.cabin.plat.domain.track.dto.TrackResponse;
import com.cabin.plat.domain.track.dto.TrackResponse.TrackDetail;
import com.cabin.plat.domain.track.dto.TrackResponse.TrackMap;
import com.cabin.plat.domain.track.entity.Location;
import com.cabin.plat.domain.track.entity.Track;
import com.cabin.plat.domain.track.entity.TrackReport;
Expand Down Expand Up @@ -173,7 +171,7 @@ private List<Track> createTestTracks(List<Member> members, List<Location> locati
.locationString("Dormitory 16 (DICE)")
.address("경상북도 포항시 남구 지곡동 287")
.imageUrl("https://testimage1.com")
.context("기숙사에서 한곡")
.content("기숙사에서 한곡")
.likeCount(0)
.isLiked(false)
.member(memberInfo)
Expand All @@ -184,7 +182,16 @@ private List<Track> createTestTracks(List<Member> members, List<Location> locati

// then
assertThat(trackDetail).isNotNull();
assertThat(trackDetail).usingRecursiveComparison().isEqualTo(expectedTrackDetail);
assertThat(trackDetail.getIsrc()).isEqualTo("isrc1");
assertThat(trackDetail.getLatitude()).isEqualTo(36.017062);
assertThat(trackDetail.getLongitude()).isEqualTo(129.321993);
assertThat(trackDetail.getLocationString()).isEqualTo("Dormitory 16 (DICE)");
assertThat(trackDetail.getAddress()).isEqualTo("경상북도 포항시 남구 지곡동 287");
assertThat(trackDetail.getImageUrl()).isEqualTo("https://testimage1.com");
assertThat(trackDetail.getContent()).isEqualTo("기숙사에서 한곡");
assertThat(trackDetail.getLikeCount()).isEqualTo(0);
assertThat(trackDetail.getIsLiked()).isEqualTo(false);
assertThat(trackDetail.getMember()).usingRecursiveComparison().isEqualTo(memberInfo);
}

@Nested
Expand Down Expand Up @@ -250,16 +257,17 @@ void setUp() {
}
}

// MARK: 해당 테스트는 외부 API (역지오코딩) 에 종속적입니다.
@Test
void 트랙_게시() {
// given
Member member = members.get(0);
TrackRequest.TrackUpload trackUpload = TrackRequest.TrackUpload.builder()
.isrc("isrc9")
.imageUrl("https://testimage9.com")
.context("테스트9")
.latitude(12.131)
.longitude(123.123)
.content("테스트9")
.latitude(36.014188)
.longitude(129.325802)
.build();

// when
Expand All @@ -269,9 +277,11 @@ void setUp() {
TrackResponse.TrackDetail trackDetail = trackService.getTrackById(member, trackId);
assertThat(trackDetail.getIsrc()).isEqualTo("isrc9");
assertThat(trackDetail.getImageUrl()).isEqualTo("https://testimage9.com");
assertThat(trackDetail.getContext()).isEqualTo("테스트9");
assertThat(trackDetail.getLatitude()).isEqualTo(12.131);
assertThat(trackDetail.getLongitude()).isEqualTo(123.123);
assertThat(trackDetail.getContent()).isEqualTo("테스트9");
assertThat(trackDetail.getLatitude()).isEqualTo(36.014188);
assertThat(trackDetail.getLongitude()).isEqualTo(129.325802);
assertThat(trackDetail.getAddress()).isEqualTo("경상북도 포항시 남구 지곡동");
assertThat(trackDetail.getLocationString()).isEqualTo("포항공대제1융합관");
assertThat(trackDetail.getLikeCount()).isZero();
assertThat(trackDetail.getIsLiked()).isFalse();
assertThat(trackDetail.getMember().getMemberId()).isEqualTo(member.getId());
Expand Down
79 changes: 79 additions & 0 deletions src/test/java/com/cabin/plat/global/util/ReverseGeoCodingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.cabin.plat.global.util;

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

import com.cabin.plat.global.util.geocoding.AddressInfo;
import com.cabin.plat.global.util.geocoding.ReverseGeoCoding;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ReverseGeoCodingTest {
@Autowired
private ReverseGeoCoding reverseGeoCoding;

@Test
void 주소O_도로명주소O_건물명O() {
// given
double latitude = 36.014188;
double longitude = 129.325802;

// when
AddressInfo addressInfo = reverseGeoCoding.getAddressInfo(latitude, longitude);

// then
assertThat(addressInfo.area1()).isEqualTo("경상북도");
assertThat(addressInfo.area2()).isEqualTo("포항시 남구");
assertThat(addressInfo.area3()).isEqualTo("지곡동");
assertThat(addressInfo.buildingName()).isEqualTo("포항공대제1융합관");
}

@Test
void 주소O_도로명주소O_건물명X() {
// given
double latitude = 36.030597;
double longitude = 129.399123;

// when
AddressInfo addressInfo = reverseGeoCoding.getAddressInfo(latitude, longitude);

// then
assertThat(addressInfo.area1()).isEqualTo("경상북도");
assertThat(addressInfo.area2()).isEqualTo("포항시 남구");
assertThat(addressInfo.area3()).isEqualTo("송정동");
assertThat(addressInfo.buildingName()).isEmpty();
}

@Test
void 주소O_도로명주소X_건물명X() {
// given
double latitude = 36.018981;
double longitude = 129.335739;

// when
AddressInfo addressInfo = reverseGeoCoding.getAddressInfo(latitude, longitude);

// then
assertThat(addressInfo.area1()).isEqualTo("경상북도");
assertThat(addressInfo.area2()).isEqualTo("포항시 남구");
assertThat(addressInfo.area3()).isEqualTo("대잠동");
assertThat(addressInfo.buildingName()).isEmpty();
}

@Test
void 주소X_도로명주소X_건물명X() {
// given
double latitude = 36.051039;
double longitude = 129.396599;

// when
AddressInfo addressInfo = reverseGeoCoding.getAddressInfo(latitude, longitude);

// then
assertThat(addressInfo.area1()).isEqualTo("바다");
assertThat(addressInfo.area2()).isEmpty();
assertThat(addressInfo.area3()).isEmpty();
assertThat(addressInfo.buildingName()).isEmpty();
}
}

0 comments on commit 4df5d57

Please sign in to comment.