와챠의 우당탕탕 개발 기록장

[안드로이드] 사용자 위치 가져와서 날씨 정보 설정하기 본문

코딩 일기장/Android(Kotlin)

[안드로이드] 사용자 위치 가져와서 날씨 정보 설정하기

minWachya 2021. 7. 30. 21:56
반응형

https://min-wachya.tistory.com/163

 

[안드로이드] 최신 기상청 단기예보 API 활용하기(초단기예보, Json)

올해 1학기 때 과제로 기상청 동네 예보 API를 사용한 적이 있었는데... 7월 초에 이런 메일이 왔다. 동네 예보 API가 종료된다고... 물론 단기 예보 단위가 상세화 되는 건 좋다~ 기존에 있던 코드

min-wachya.tistory.com

이전 게시글에 이어서 이번엔 사용자의 현재 위치를 가져오는 것을 추가해봤다.

여기에 생략한 내용들 다 위에 있음

 

위치 가져올 때 권한 설정하는 것도 필요해서 스플래시도 넣었다.

스플래시 화면에서 권한 설정하게 하고 권한 얻었을 때만 메인 액티비티로 넘어가는 식이다.

api 불러올 때 위치 인자만 바뀐 거라 크게 눈에띄게 달라진 점은 없다.

 


공식문서

 

 

build.gradle 설정

내 위치 찾기할 때 권한 필요해서build.gradle

권한 설정 하기 위한 라이브러리 추가
implementation "io.github.ParkSangGwon:tedpermission:2.3.0"


AndroidManifest 설정
위치 가져오기 위한 라이브러리 추가
implementation 'com.google.android.gms:play-services-location:18.0.0'

 

대략적인 위치, 상세 위치 얻기 위한 권한 추가
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

실시간 위치 정보 엑세스 권한 추가
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />


Splash.kt : 스플래시 화면 및 권한 묻기

아래 포스트 참고

https://min-wachya.tistory.com/144

 

[안드로이드] 스플래시 1초간 보여주기

1, 스플래시 액티비티(Splash.kt) 생성 2, AndroidMAnifest.xml에 등록 스플래시 액티비티 제일 먼저 방문하기위해 android:name=".Splash" android:theme="@style/SplashTheme"> 로 수정 및 추가 추가 3, res > v..

min-wachya.tistory.com

package com.example.myweathertest2
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import java.util.concurrent.TimeUnit
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.TedPermission
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
// 스플래시
class Splash : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 권한 설정하기
setPermission()
}
// tedpermission 권한 설정
private fun setPermission() {
// 권한 묻는 팝업 만들기
val permissionListener = object : PermissionListener {
// 설정해놓은 권한을 허용됐을 때
override fun onPermissionGranted() {
// 1초간 스플래시 보여주기
val backgroundExecutable : ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
val mainExecutor : Executor = ContextCompat.getMainExecutor(this@Splash)
backgroundExecutable.schedule({
mainExecutor.execute {
// MainActivity 넘어가기
var intent = Intent(this@Splash, MainActivity::class.java)
startActivity(intent)
finish()
}
}, 1, TimeUnit.SECONDS)
Toast.makeText(this@Splash, "권한 허용", Toast.LENGTH_SHORT).show()
}
// 설정해놓은 권한을 거부됐을 때
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
// 권한 없어서 요청
AlertDialog.Builder(this@Splash)
.setMessage("권한 거절로 인해 일부 기능이 제한됩니다.")
.setPositiveButton("권한 설정하러 가기") { dialog, which ->
try {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.parse("package:com.example.myweathertest2"))
startActivity(intent)
} catch (e : ActivityNotFoundException) {
e.printStackTrace()
val intent = Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)
startActivity(intent)
}
}
.show()
Toast.makeText(this@Splash, "권한 거부", Toast.LENGTH_SHORT).show()
}
}
// 권한 설정
TedPermission.with(this@Splash)
.setPermissionListener(permissionListener)
.setRationaleMessage("정확한 날씨 정보를 위해 권한을 허용해주세요.")
.setDeniedMessage("권한을 거부하셨습니다. [앱 설정]->[권한] 항목에서 허용해주세요.")
.setPermissions(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION) // 필수 권한만 문기
.check()
}
}
view raw Splash.kt hosted with ❤ by GitHub

MainActivity.kt : 현재 사용자의 위치를 위경도로 가져와서 격자 좌표로 변환한 후 날씨 정보 불러오기

package com.example.myweathertest2
import android.graphics.Point
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.myweathertest2.Adapter.WeatherAdapter
import com.example.myweathertest2.Model.ModelWeather
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import retrofit2.Call
import retrofit2.Response
import java.text.SimpleDateFormat
import java.util.*
// 메인 액티비티
class MainActivity : AppCompatActivity() {
lateinit var weatherRecyclerView : RecyclerView
lateinit var tvDate : TextView
lateinit var base_date : String // 발표 일자
lateinit var base_time : String // 발표 시각
private var curPoint : Point? = null // 현재 위치의 격자 좌표를 저장할 포인트
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvDate = findViewById(R.id.tvDate) // 오늘 날짜 텍스트뷰
weatherRecyclerView = findViewById(R.id.weatherRecyclerView) // 날씨 리사이클러 뷰
val btnRefresh = findViewById<Button>(R.id.btnRefresh) // 새로고침 버튼
// 리사이클러 뷰 매니저 설정
weatherRecyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
// 내 위치 위경도 가져와서 날씨 정보 설정하기
requestLocation()
// <새로고침> 버튼 누를 때 위치 정보 & 날씨 정보 다시 가져오기
btnRefresh.setOnClickListener {
requestLocation()
}
}
// 날씨 가져와서 설정하기
private fun setWeather(nx : Int, ny : Int) {
// 준비 단계 : base_date(발표 일자), base_time(발표 시각)
// 현재 날짜, 시간 정보 가져오기
val cal = Calendar.getInstance()
base_date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(cal.time) // 현재 날짜
val timeH = SimpleDateFormat("HH", Locale.getDefault()).format(cal.time) // 현재 시각
val timeM = SimpleDateFormat("HH", Locale.getDefault()).format(cal.time) // 현재 분
// API 가져오기 적당하게 변환
base_time = Common().getBaseTime(timeH, timeM)
// 현재 시각이 00시이고 45분 이하여서 baseTime이 2330이면 어제 정보 받아오기
if (timeH == "00" && base_time == "2330") {
cal.add(Calendar.DATE, -1).toString()
base_date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(cal.time)
}
// 날씨 정보 가져오기
// (한 페이지 결과 수 = 60, 페이지 번호 = 1, 응답 자료 형식-"JSON", 발표 날싸, 발표 시각, 예보지점 좌표)
val call = ApiObject.retrofitService.GetWeather(60, 1, "JSON", base_date, base_time, nx, ny)
// 비동기적으로 실행하기
call.enqueue(object : retrofit2.Callback<WEATHER> {
// 응답 성공 시
override fun onResponse(call: Call<WEATHER>, response: Response<WEATHER>) {
if (response.isSuccessful) {
// 날씨 정보 가져오기
val it: List<ITEM> = response.body()!!.response.body.items.item
// 현재 시각부터 1시간 뒤의 날씨 6개를 담을 배열
val weatherArr = arrayOf(ModelWeather(), ModelWeather(), ModelWeather(), ModelWeather(), ModelWeather(), ModelWeather())
// 배열 채우기
var index = 0
val totalCount = response.body()!!.response.body.totalCount - 1
for (i in 0..totalCount) {
index %= 6
when(it[i].category) {
"PTY" -> weatherArr[index].rainType = it[i].fcstValue // 강수 형태
"REH" -> weatherArr[index].humidity = it[i].fcstValue // 습도
"SKY" -> weatherArr[index].sky = it[i].fcstValue // 하늘 상태
"T1H" -> weatherArr[index].temp = it[i].fcstValue // 기온
else -> continue
}
index++
}
// 각 날짜 배열 시간 설정
for (i in 0..5) weatherArr[i].fcstTime = it[i].fcstTime
// 리사이클러 뷰에 데이터 연결
weatherRecyclerView.adapter = WeatherAdapter(weatherArr)
// 토스트 띄우기
Toast.makeText(applicationContext, it[0].fcstDate + ", " + it[0].fcstTime + "의 날씨 정보입니다.", Toast.LENGTH_SHORT).show()
}
}
// 응답 실패 시
override fun onFailure(call: Call<WEATHER>, t: Throwable) {
val tvError = findViewById<TextView>(R.id.tvError)
tvError.text = "api fail : " + t.message.toString() + "\n 다시 시도해주세요."
tvError.visibility = View.VISIBLE
Log.d("api fail", t.message.toString())
}
})
}
// 내 현재 위치의 위경도를 격자 좌표로 변환하여 해당 위치의 날씨정보 설정하기
private fun requestLocation() {
val locationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity)
try {
// 나의 현재 위치 요청
val locationRequest = LocationRequest.create()
locationRequest.run {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 60 * 1000 // 요청 간격(1초)
}
val locationCallback = object : LocationCallback() {
// 요청 결과
override fun onLocationResult(p0: LocationResult?) {
p0?.let {
for (location in it.locations) {
// 현재 위치의 위경도를 격자 좌표로 변환
curPoint = Common().dfs_xy_conv(location.latitude, location.longitude)
// 오늘 날짜 텍스트뷰 설정
tvDate.text = SimpleDateFormat("MM월 dd일", Locale.getDefault()).format(Calendar.getInstance().time) + "날씨"
// nx, ny지점의 날씨 가져와서 설정하기
setWeather(curPoint!!.x, curPoint!!.y)
}
}
}
}
// 내 위치 실시간으로 감지
locationClient?.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
} catch (e : SecurityException) {
e.printStackTrace()
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Common.kt : baseTime 생성하는 함수, 위경도를 격자 좌표로 변환하는 함수가 공통 함수여서 여기 넣어둠

package com.example.myweathertest2
import android.graphics.Point
class Common {
// baseTime 설정하기
fun getBaseTime(h : String, m : String) : String {
var result = ""
// 45분 전이면
if (m.toInt() < 45) {
// 0시면 2330
if (h == "00") result = "2330"
// 아니면 1시간 전 날씨 정보 부르기
else {
var resultH = h.toInt() - 1
// 1자리면 0 붙여서 2자리로 만들기
if (resultH < 10) result = "0" + resultH + "30"
// 2자리면 그대로
else result = resultH.toString() + "30"
}
}
// 45분 이후면 바로 정보 받아오기
else result = h + "30"
return result
}
// 위경도를 기상청에서 사용하는 격자 좌표로 변환
fun dfs_xy_conv(v1: Double, v2: Double) : Point {
val RE = 6371.00877 // 지구 반경(km)
val GRID = 5.0 // 격자 간격(km)
val SLAT1 = 30.0 // 투영 위도1(degree)
val SLAT2 = 60.0 // 투영 위도2(degree)
val OLON = 126.0 // 기준점 경도(degree)
val OLAT = 38.0 // 기준점 위도(degree)
val XO = 43 // 기준점 X좌표(GRID)
val YO = 136 // 기준점 Y좌표(GRID)
val DEGRAD = Math.PI / 180.0
val re = RE / GRID
val slat1 = SLAT1 * DEGRAD
val slat2 = SLAT2 * DEGRAD
val olon = OLON * DEGRAD
val olat = OLAT * DEGRAD
var sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5)
sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn)
var sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5)
sf = Math.pow(sf, sn) * Math.cos(slat1) / sn
var ro = Math.tan(Math.PI * 0.25 + olat * 0.5)
ro = re * sf / Math.pow(ro, sn)
var ra = Math.tan(Math.PI * 0.25 + (v1) * DEGRAD * 0.5)
ra = re * sf / Math.pow(ra, sn)
var theta = v2 * DEGRAD - olon
if (theta > Math.PI) theta -= 2.0 * Math.PI
if (theta < -Math.PI) theta += 2.0 * Math.PI
theta *= sn
val x = (ra * Math.sin(theta) + XO + 0.5).toInt()
val y = (ro - ra * Math.cos(theta) + YO + 0.5).toInt()
return Point(x, y)
}
}
view raw Common.kt hosted with ❤ by GitHub

다음에 해볼 것

  • 사용자 현재 위치 가져와서 위경도 값을 직접 좌표 값으로 변환
  • 위젯 만들기
  • 가로로 슬라이드하는 리사이클러뷰(?) 만들어보기

 

 

반응형
Comments