1. https 적용
https 적용기
기존의 문제점프로젝트를 진행하면서 사용자의 위치가 필요했다.사용자의 현재위치를 가져오는 API는 Geolocation API을 사용하려 했다. 이는 사용자에게 권한 확인을 받은 후 사용할 수 있다.navigat
sesam-dev.tistory.com
2. 프론트에서 위치 정보(위도, 경도)를 받아왔다.
클릭하면 위도, 경도를 받아올 수 있게 했다.
const Location = () => {
const userInfo = useSelector(state => state.loginCheck.loginInfo);
const sendLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
const lat = position.coords.latitude;
const lon = position.coords.latitude;
// 위치 정보를 콘솔에 출력
console.log("Latitude:", lat);
console.log("Longitude:", lon);
// 위치 정보를 서버에 전송
fetch(`http://localhost:8000/api/v1/user/${userInfo.userId}/location`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
latitude: lat,
longitude: lon,
}),
})
.then(response => response.text())
.then(data => {
console.log("Location sent to server:", data);
})
.catch(error => {
console.error("Error:", error);
});
});
} else {
console.log("Geolocation is not supported by this browser.");
}
};
return (
<div>
<h1>Geolocation Example</h1>
<button onClick={sendLocation}>Send Location to Server</button>
</div>
);
};
3. 역지오코딩(Reverse Geocoding) API가 필요
위치 정보(위도, 경도)를 주소로 변환:
프론트에서 받아온 위도와 경도를 사용해 주소로 변환을 해야 한다.
위도와 경도를 OO시, OO동과 같은 주소로 변환하려면 역지오코딩(Reverse Geocoding) API가 필요하다. Google Maps API 또는 Kakao, Naver 등의 국내 API를 사용할 수 있다.
GCP를 사용하기 때문에 Google Maps API가 관리하기 편할 것 같아 Google Maps API를 사용하기로 했다.
applicaiton.yml
# Google API 키 설정
google:
api:
key: YOUR_GOOGLE_MAPS_API_KEY_HERE # Google Maps API에서 준 key값
보안성을 위해 key은 설정파일에서 하드코딩하지 않고 외부에서 관리하는 것이 좋다.
Google Maps API에서 준 key값을 넣으면 된다.(아래에 나온다.)
테스트용이라면 하드코딩을 해도 된다.
💡 환경 변수 사용하는 방법
applicaiton.yml
google:
api:
key: ${GOOGLE_API_KEY}
여기서 ${GOOGLE_API_KEY}는 시스템 환경 변수에서 GOOGLE_API_KEY 값을 가져옵니다. 환경 변수는 서버의 운영 체제에서 설정하거나, Docker와 같은 컨테이너 환경에서 설정할 수 있습니다.
환경 변수 설정 방법:
- 운영 체제에서 설정:
- Windows: set GOOGLE_API_KEY=your_api_key_here
- Linux/Mac: export GOOGLE_API_KEY=your_api_key_here
- Docker에서 설정:
- Dockerfile에 ENV GOOGLE_API_KEY=your_api_key_here 추가
- 또는 docker run 명령에서 -e GOOGLE_API_KEY=your_api_key_here 플래그 사용
build.gradle
implementation 'org.json:json:20210307'
프론트에서 위도와 경도는 잘 가져왔지만,
ERROR
역지오코딩을 할 때 에러가 났다.
REQUEST_DENIED 오류는 Google Maps Geocoding API 요청이 거부되었음을 나타낸다. 이 오류의 주요 원인과 해결 방법은 다음과 같다:
GCP에서 API 중에 Geocoding API를 활성화 시켜주어야 한다고 한다.
💡 GoogleGeoCoding 상태값
OK OK ZERO_RESULTS indicates that the geocode was successful but returned no results.
This may occur if the geocoder was passed a non-existent address.OVER_DAILY_LIMIT The API key is missing or invalid.
Billing has not been enabled on your account.
A self-imposed usage cap has been exceeded.
The provided method of payment is no longer valid (for example, a credit card has expired).OVER_QUERY_LIMIT indicates that you are over your quota. REQUEST_DENIED indicates that your request was denied INVALID_REQUEST generally indicates that the query (address, components or latlng) is missing.
400을 리턴UNKNOWN_ERROR indicates that the request could not be processed due to a server error. The request may succeed if you try again.
💡 구글 지도 API key 발급 방법 (Google Map API)
1. API키 발급 받기 (사용자 인증 정보 만들기)
제한이 넘어가면 돈이 추가로 든다.. 매우 주의해야할 것같다.
https://developers.google.com/maps/get-started?hl=ko
이때 나온 key를 프로젝트에 적용하면 된다.
💡 TODO 키 제한
2. 지도 삽입
GCP 콘솔 메인 > 지도 관리(Google Maps Platform) > API 사용해 보기 > 주소 입력 합니다.
주소에 맞게 생성된 코드에 앱키를 넣어줍니다.
4. 변환된 주소를 DB에 저장
사용자 테이블에 위치 정보를 추가하고, 변환된 주소를 저장한다.
// UserService
public void updateUserLocation(Long userId, double latitude, double longitude) {
// 위도, 경도를 주소로 변환
String address = geocodingService.getAddressFromCoordinates(latitude, longitude); // 아래에 있음
System.out.println(address);
// 사용자 정보 업데이트
UserEntity user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found"));
user.setBaseLocation(address);
// DB에 저장
userRepository.save(user);
}
// GeocodingService
@Service
public class GeocodingService {
@Autowired
private Environment env;
public String getAddressFromCoordinates(double latitude, double longitude) {
String googleApiKey = env.getProperty("google.api.key");
String url = String.format(
"https://maps.googleapis.com/maps/api/geocode/json?latlng=%s,%s&key=%s&language=ko",
latitude, longitude, googleApiKey
);
,,,
생략
,,,
System.out.println("동 정보를 찾을 수 없습니다");
return "지역 미지정"; // 예외 상황에서 동 정보를 찾을 수 없는 경우
}
}
참고) 위도와 경도를 주소로 변환한 후, 원하는 주소로 보이게 하기 위해서는 별도의 로직이 필요하다.(GPT와 함께..)
시와 동이 들어간 것을 확인할 수 있다!
더보기
@Service
public class GeocodingService {
@Autowired
private Environment env;
public String getAddressFromCoordinates(double latitude, double longitude) {
String googleApiKey = env.getProperty("google.api.key");
String url = String.format(
"https://maps.googleapis.com/maps/api/geocode/json?latlng=%s,%s&key=%s&language=ko",
latitude, longitude, googleApiKey
);
RestTemplate restTemplate = new RestTemplate();
try {
String response = restTemplate.getForObject(url, String.class);
if (response != null) {
JSONObject jsonObject = new JSONObject(response);
String status = jsonObject.optString("status", "ZERO_RESULTS");
if ("OK".equals(status)) {
JSONArray resultsArray = jsonObject.optJSONArray("results");
if (resultsArray != null && resultsArray.length() > 0) {
JSONObject firstResult = resultsArray.getJSONObject(0);
JSONArray addressComponents = firstResult.getJSONArray("address_components");
String dong = null;
String gu = null;
String city = null;
System.out.println(addressComponents.toString());
// 각 address_component의 types 분석
for (int i = 0; i < addressComponents.length(); i++) {
JSONObject component = addressComponents.getJSONObject(i);
JSONArray types = component.getJSONArray("types");
// 구(political, sublocality_level_1) 정보 추출
if (types.toString().contains("sublocality_level_1")) {
gu = component.getString("long_name");
}
// 동(sublocality_level_2) 정보 추출
if (types.toString().contains("sublocality_level_2")) {
dong = component.getString("long_name");
}
// 동(sublocality_level_1) 정보 추출
if (types.toString().contains("sublocality_level_1")) {
// sublocality_level_1이 동 정보인 경우
if (dong == null) { // 동 정보가 없는 경우에만 저장
dong = component.getString("long_name");
}
}
// 시(locality) 정보 추출
if (types.toString().contains("locality")) {
city = component.getString("long_name");
System.out.println("시(locality) 정보: " + city);
}
}
// 시, 구, 동 정보를 출력
if (city != null) {
System.out.println("시 정보: " + city); // 예: 수원시
}
if (gu != null) {
System.out.println("구 정보: " + gu); // 예: 팔달구
}
if (dong != null) {
System.out.println("동 정보: " + dong); // 예: 인계동
}
// 동 정보가 있으면 시와 동 정보를 반환, 동이 없으면 시 정보만 반환
if (city != null) {
if (dong != null) {
return city + " " + dong;
} else {
return city; // 동 정보가 없으면 시 정보만 반환
}
} else {
return "지역 미지정"; // 시 정보도 없을 때
}
}
} else {
System.out.println("Geocoding API returned status: " + status);
}
}
} catch (Exception e) {
System.out.println("Error occurred while calling Geocoding API: " + e.getMessage());
e.printStackTrace();
}
return "지역 미지정"; // 예외 상황에서 동 정보를 찾을 수 없는 경우
}
}
회고
위도와 경도를 기반으로 주소를 처리하였으나, 주소마다 일관되지 않은 결과가 발생했다.
위치 데이터의 정확도가 낮아, 일부 주소는 잘못된 좌표로 변환되거나 아예 동 정보가 반환되지 않는 경우가 생기거나 해외 지역(예: 튀르키예)으로 잘못 표시되는 경우가 있었다.
동이 없는 주소의 경우 "시"만 저장될 수 있게 처리하여 데이터 손실을 방지했다.
"시"만 저장되는 경우는 해당 시에 대한 전체 도서를 조회할 수 있게 해두었다.
(기존 기능은 내 위치를 기반으로 도서 조회하여 보여주어야 한다.)
당장은 현재 위치 정보의 정확도가 높지 않아 위도와 경도를 고정하는 방안을 고려 중이다. 다만, 이 경우에도 유연성이 떨어질 수 있으므로 정확도를 보완할 수 있는 다른 방법을 모색할 계획이다.
다른 지도(kakao map 등...)을 적용해 보는 것도 고려해봐야겠다.
사용자 위치 조회 기능의 신뢰성을 높이기 위해 테스트를 더 진행하고, 상황에 맞는 개선 방향을 설정할 예정이다.
참고링크
'Project > Collabo Project' 카테고리의 다른 글
[Villion] https 적용기 (3) | 2024.09.16 |
---|---|
[Villion] [GCP] 외부에서 구매한 도메인 등록하기 (0) | 2024.09.13 |
[Villion] Gateway와 User-Service 간 네트워크 문제 해결 및 Docker 설정 방법 (0) | 2024.09.05 |
[Villion] eureka 배포 (0) | 2024.08.29 |
[Villion] Docker+GCP로 프론트 배포 정리 (1) | 2024.08.28 |