๐
2025-05-12
๐งญ ๊ฐ์ ๋ฐ ํ์ต ๋ชฉ์
๐ ๋ฌด์์ ๊ตฌํํ๋?
- ์น ๋ธ๋ผ์ฐ์ ์์ Daum ๊ธฐ๋ฐ ์ธํฐ๋ํฐ๋ธ ์ง๋๋ฅผ ๋์ฐ๊ณ ,
- ์ฌ์ฉ์๊ฐ ํด๋ฆญํ ์์น์ ์ค์๊ฐ ๋ ์จ ์ ๋ณด๋ฅผ OpenWeatherMap API๋ก ๋ฐ์,
- ์ง๋ ์์ ๋ง์ปค์ ํ์
ํํ๋ก ์๊ฐํํ๋ ํ๋ก์ ํธ๋ฅผ ๊ตฌํํจ.
๐ง ์ฌ์ฉํ ๊ธฐ์ ์คํ
| ๋ถ๋ฅ | ์ฌ์ฉ ๊ธฐ์ |
|---|
| ์ง๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | Leaflet.js |
| ์ขํ๊ณ ๋ณํ | proj4.js + proj4leaflet.js |
| ํ๊ตญํ ํ์ผ ์์ค | Leaflet.KoreanTmsProviders.js |
| ๋ฐฑ์๋ ํ๋ ์์ํฌ | Spring Boot (Java) |
| HTTP ํต์ | Axios (ํ๋ก ํธ), RestTemplate (๋ฐฑ์๋) |
| ์ธ๋ถ API | OpenWeatherMap (๋ ์จ ์ ๋ณด) |
๐ ์ ์ฒด ํ๋ฆ ์์ฝ
1. ์ฌ์ฉ์๊ฐ ์ง๋ ํด๋ฆญ
2. ํด๋ฆญํ ์๋/๊ฒฝ๋๋ฅผ ์๋ฒ๋ก ์ ์ก
3. ์๋ฒ๋ OpenWeatherMap API์ ๋ ์จ ์ ๋ณด ์์ฒญ
4. ๋ฐ์ JSON ์๋ต์ ํ์ํ ๋ฐ์ดํฐ๋ง ์ถ์ถํด ํด๋ผ์ด์ธํธ๋ก ๋ฐํ
5. ํ๋ก ํธ๋ ํ์
+ ๋ง์ปค๋ก ๋ ์จ ์๊ฐํ
๐บ๏ธ index.html ๊ตฌ์ฑ
1๏ธโฃ Head โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก๋ฉ
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="/js/KoreanTmsProviders/lib/proj4.js"></script>
<script src="/js/KoreanTmsProviders/lib/proj4leaflet.js"></script>
<script src="/js/KoreanTmsProviders/src/Leaflet.KoreanTmsProviders.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.9.0/axios.min.js"></script>
2๏ธโฃ Body โ ์ง๋ ๋ฐ ๋ก๋ฉ ๋ฉ์์ง
<div id="loading" style="display:none;position:absolute;top:20px;left:20px;background:#fff;padding:5px;z-index:9999;">
๐ค๏ธ ๋ ์จ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค...
</div>
<div id="map" style="height:100vh;width:100%;"></div>
3๏ธโฃ Script โ ์ง๋ ์์ฑ ๋ฐ ๋ ์จ ํ์
์ฒ๋ฆฌ
let currentMarker = null;
const map = new L.Map('map', {
center: new L.LatLng(35.829890, 128.532719),
zoom: 8,
crs: L.Proj.CRS.Daum
});
L.tileLayer.koreaProvider('DaumMap.Street').addTo(map);
const centerMarker = L.marker([35.829890, 128.532719]).addTo(map);
centerMarker.bindTooltip("๋๊ตฌ").openTooltip();
centerMarker.bindPopup("<div>์ค์ฌ ์ง์ </div>");
map.on('click', function(e) {
const lat = e.latlng.lat;
const lon = e.latlng.lng;
if (currentMarker) map.removeLayer(currentMarker);
currentMarker = L.marker([lat, lon]).addTo(map);
document.getElementById("loading").style.display = "block";
axios.get(`/open/weather/get/${lat}/${lon}`)
.then(resp => {
const data = resp.data;
const temp = Math.round(data.temp - 273.15);
const feels = Math.round(data.feelsLike - 273.15);
const content = `
<div style="text-align:center;">
<img src="https://openweathermap.org/img/wn/${data.icon}@2x.png" /><br/>
<strong>${data.name}</strong><br/>
${data.description} / ${temp}ยฐC<br/>
์ฒด๊ฐ์จ๋: ${feels}ยฐC<br/>
์ต๋: ${data.humidity}% | ๋ฐ๋: ${data.windSpeed} m/s
</div>`;
currentMarker.bindPopup(content).openPopup();
})
.catch(err => {
alert("๋ ์จ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค.");
console.error(err);
})
.finally(() => {
document.getElementById("loading").style.display = "none";
});
});
โ๏ธ OpenWeatherController.java
@RestController
@RequestMapping("/open/weather")
public class OpenWeatherController {
private final String serviceKey = "YOUR_API_KEY_HERE";
@GetMapping("/get/{lat}/{lon}")
public ResponseEntity<WeatherDto> get(@PathVariable String lat, @PathVariable String lon) throws UnsupportedEncodingException {
URI uri = UriComponentsBuilder.fromHttpUrl("https://api.openweathermap.org/data/2.5/weather")
.queryParam("appid", URLEncoder.encode(serviceKey, "UTF-8"))
.queryParam("lat", lat)
.queryParam("lon", lon)
.build(true).toUri();
RestTemplate rt = new RestTemplate();
ResponseEntity<Map> response = rt.exchange(uri, HttpMethod.GET, null, Map.class);
Map body = response.getBody();
Map main = (Map) body.get("main");
List<Map> weatherList = (List<Map>) body.get("weather");
Map wind = (Map) body.get("wind");
WeatherDto dto = WeatherDto.builder()
.name((String) body.get("name"))
.description((String) weatherList.get(0).get("description"))
.icon((String) weatherList.get(0).get("icon"))
.temp(((Number) main.get("temp")).doubleValue())
.feelsLike(((Number) main.get("feels_like")).doubleValue())
.humidity(((Number) main.get("humidity")).intValue())
.windSpeed(((Number) wind.get("speed")).doubleValue())
.build();
return ResponseEntity.ok(dto);
}
}
โ
WeatherDto.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WeatherDto {
private String name;
private String description;
private String icon;
private double temp;
private double feelsLike;
private int humidity;
private double windSpeed;
}
๐ฅ ํต์ฌ ์ ๋ฆฌ
| ํญ๋ชฉ | ์ค๋ช
|
|---|
| ์ขํ๊ณ | EPSG:5181 (Daum์ฉ) |
| ์ง๋ API | Leaflet + KoreanTmsProviders |
| ๋ ์จ API | OpenWeatherMap |
| ์๊ฐํ | ๋ง์ปค + ํดํ + ํ์
|
| ๊ธฐ๋ฅ ๊ฐ์ | ์จ๋ K โ โ, ์์ด์ฝ ํ์, ๋ง์ปค ์ค๋ณต ์ ๊ฑฐ, ๋ก๋ฉ ๋ฉ์์ง ํ์ |
| ๊ตฌ์กฐ | Axios โ Spring REST โ OpenWeather โ DTO โ ํ์
ํ์ |
๐ค ๋๋ ์
- ์ขํ๊ณ ์ ์๋ถํฐ API ํธ์ถ, ์๋ต ํ์ฑ๊น์ง ํ๋ก ํธ์ ๋ฐฑ์๋๊ฐ ํ๋ ฅํ๋ ์ ์ฒด ํ๋ฆ์ ์ฒดํํ ์ ์์๋ค.
RestTemplate๋ก ์ธ๋ถ API๋ฅผ ์ฐ๋ํ๊ณ , ํ์ํ JSON ํ๋๋ง ์ถ์ถํด DTO๋ก ์ ๋ฆฌํ๋ ๊ฒฝํ์ด ์ ์ตํ๋ค.
- Leaflet์์ ์ปค์คํ
์ขํ๊ณ๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์ํ ์ ๊ณผ ์ค์ ํ์ผ ์์ค๋ฅผ ์ฐ๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ธ ์ ์์๋ค.
๐ก ๋ค์์ ๋์ ํด๋ณผ ๊ฒ๋ค
- ์ฌ์ฉ์์ ํ์ฌ ์์น ์๋ ๋ง์ปค ํ์
- ํด๋ฆญ ์์น์ ๊ณผ๊ฑฐ ๋๋ ์ฃผ๊ฐ ๋ ์จ ์ ๋ณด๋ ํจ๊ป ๋ณด์ฌ์ฃผ๊ธฐ
- ๋ ์จ์ ๋ฐ๋ผ ๋ง์ปค ์์์ ๋์ ์ผ๋ก ๋ณ๊ฒฝ
- ๋ ์จ ์ข
๋ฅ์ ๋ฐ๋ผ ์์ด์ฝ/๋ฐฐ๊ฒฝ์ ๋ฐ๊พธ๋ UI ๊ฐ์