-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[FEAT]� 역지오코딩 API
- Loading branch information
Showing
10 changed files
with
258 additions
and
21 deletions.
There are no files selected for viewing
Submodule config
updated
from 516ecb to 954d98
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
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
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
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
15 changes: 15 additions & 0 deletions
15
src/main/java/com/cabin/plat/global/util/geocoding/AddressInfo.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,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()); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/cabin/plat/global/util/geocoding/ApiKeyProperties.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,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; | ||
} |
110 changes: 110 additions & 0 deletions
110
src/main/java/com/cabin/plat/global/util/geocoding/ReverseGeoCoding.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,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(); | ||
} | ||
} |
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
79 changes: 79 additions & 0 deletions
79
src/test/java/com/cabin/plat/global/util/ReverseGeoCodingTest.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,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(); | ||
} | ||
} |