diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt
index e3de7d2d..6ea30c30 100644
--- a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt
+++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt
@@ -41,6 +41,7 @@ import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
+import com.rtbishop.look4sat.utility.GeneralUtils
import com.rtbishop.look4sat.utility.PassPredictor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -182,7 +183,7 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
val degLon = width / 360f
val degLat = height / 180f
drawHomeLoc(canvas, degLon, degLat)
- val currentTime = getDateFor(System.currentTimeMillis())
+ val currentTime = GeneralUtils.getDateFor(System.currentTimeMillis())
val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt()
val positions = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3)
setTextViewsToSelectedSatPos(positions[0])
@@ -215,8 +216,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
}
private fun setTextViewsToSelectedSatPos(position: SatPos) {
- var lon = rad2Deg(position.longitude).toFloat()
- val lat = rad2Deg(position.latitude).toFloat()
+ var lon = GeneralUtils.rad2Deg(position.longitude).toFloat()
+ val lat = GeneralUtils.rad2Deg(position.latitude).toFloat()
val rng = position.range
if (lon > 180f) lon -= 360f
@@ -238,8 +239,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
var lastLon = 181f
list.withIndex().forEach { (index, satPos) ->
- lon = rad2Deg(satPos.longitude).toFloat()
- lat = rad2Deg(satPos.latitude).toFloat() * -1
+ lon = GeneralUtils.rad2Deg(satPos.longitude).toFloat()
+ lat = GeneralUtils.rad2Deg(satPos.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
@@ -305,8 +306,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
position: SatPos,
name: String
) {
- var lon = rad2Deg(position.longitude).toFloat()
- val lat = rad2Deg(position.latitude).toFloat() * -1
+ var lon = GeneralUtils.rad2Deg(position.longitude).toFloat()
+ val lat = GeneralUtils.rad2Deg(position.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
@@ -318,13 +319,5 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint)
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint)
}
-
- private fun getDateFor(value: Long): Date {
- return Date(value)
- }
-
- private fun rad2Deg(value: Double): Double {
- return value * 180 / Math.PI
- }
}
}
diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt
index 45918ff1..400b1efb 100644
--- a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt
+++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt
@@ -20,18 +20,18 @@
package com.rtbishop.look4sat.ui.fragments
import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.Path
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
import android.os.Bundle
import android.view.View
-import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
-import com.github.amsacode.predict4java.SatPos
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
@@ -41,225 +41,136 @@ import com.rtbishop.look4sat.databinding.FragmentPolarViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.TransmitterAdapter
+import com.rtbishop.look4sat.ui.views.PolarView
+import com.rtbishop.look4sat.utility.GeneralUtils
+import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
-import kotlin.math.cos
-import kotlin.math.sin
-class PolarViewFragment : Fragment(R.layout.fragment_polar_view) {
+class PolarViewFragment : Fragment(R.layout.fragment_polar_view), SensorEventListener {
@Inject
lateinit var modelFactory: ViewModelFactory
- private val args: PolarViewFragmentArgs by navArgs()
+ private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
+ private lateinit var binding: FragmentPolarViewBinding
private lateinit var satPass: SatPass
private lateinit var polarView: PolarView
- private lateinit var mainActivity: MainActivity
- private var transmitterAdapter: TransmitterAdapter = TransmitterAdapter()
+ private lateinit var sensorManager: SensorManager
+
+ private val args: PolarViewFragmentArgs by navArgs()
+ private val transmitterAdapter = TransmitterAdapter()
+ private var lastAccData = FloatArray(3)
+ private var lastMagData = FloatArray(3)
+ private var refreshJob: Job? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val binding = FragmentPolarViewBinding.bind(view)
mainActivity = activity as MainActivity
+ binding = FragmentPolarViewBinding.bind(view)
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
- val refreshRate = viewModel.getRefreshRate()
-
+ sensorManager = mainActivity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
binding.recPolar.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
}
-
- observePasses(refreshRate, binding)
+ observePasses()
}
- private fun observePasses(
- refreshRate: Long,
- binding: FragmentPolarViewBinding
- ) {
- viewModel.getPassList()
- .observe(viewLifecycleOwner, androidx.lifecycle.Observer { result ->
- when (result) {
- is Result.Success -> {
- satPass = result.data[args.satPassIndex]
- mainActivity.supportActionBar?.title = satPass.tle.name
- polarView = PolarView(mainActivity, refreshRate, binding)
- binding.framePolar.addView(polarView)
- observeTransmitters(binding)
- refreshView()
- }
+ override fun onResume() {
+ super.onResume()
+ sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER).also { accData ->
+ sensorManager.registerListener(this, accData, SensorManager.SENSOR_DELAY_GAME)
+ }
+ sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD).also { magData ->
+ sensorManager.registerListener(this, magData, SensorManager.SENSOR_DELAY_GAME)
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ sensorManager.unregisterListener(this)
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
+
+ override fun onSensorChanged(event: SensorEvent?) {
+ event?.let {
+ if (it.sensor.type == Sensor.TYPE_ACCELEROMETER) {
+ filterSensorData(event.values, lastAccData)
+ } else if (it.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
+ filterSensorData(event.values, lastMagData)
+ }
+
+ val rotationMatrix = FloatArray(9)
+ if (SensorManager.getRotationMatrix(rotationMatrix, null, lastAccData, lastMagData)) {
+ val orientation = FloatArray(3)
+ SensorManager.getOrientation(rotationMatrix, orientation)
+ val degree = orientation[0] * 57.2957795f
+ polarView.rotation = -degree
+ }
+ }
+ }
+
+ private fun filterSensorData(input: FloatArray, output: FloatArray) {
+ val filterStep = 0.05f
+ input.indices.forEach { output[it] = output[it] + filterStep * (input[it] - output[it]) }
+ }
+
+ private fun observePasses() {
+ viewModel.getPassList().observe(viewLifecycleOwner, Observer { result ->
+ when (result) {
+ is Result.Success -> {
+ val refreshRate = viewModel.getRefreshRate()
+ satPass = result.data[args.satPassIndex]
+ mainActivity.supportActionBar?.title = satPass.tle.name
+ polarView = PolarView(mainActivity, satPass)
+ binding.framePolar.addView(polarView)
+ observeTransmitters()
+ refreshText(refreshRate)
}
- })
+ }
+ })
}
- private fun observeTransmitters(binding: FragmentPolarViewBinding) {
- viewModel.getTransmittersForSat(satPass.tle.catnum)
- .observe(viewLifecycleOwner, androidx.lifecycle.Observer {
- if (it.isNotEmpty()) {
- transmitterAdapter.setList(it)
- transmitterAdapter.notifyDataSetChanged()
- binding.recPolar.visibility = View.VISIBLE
- binding.tvPolarNoTrans.visibility = View.INVISIBLE
- } else {
- binding.recPolar.visibility = View.INVISIBLE
- binding.tvPolarNoTrans.visibility = View.VISIBLE
- }
- })
+ private fun observeTransmitters() {
+ viewModel.getTransmittersForSat(satPass.tle.catnum).observe(viewLifecycleOwner, Observer {
+ if (it.isNotEmpty()) {
+ transmitterAdapter.setList(it)
+ transmitterAdapter.notifyDataSetChanged()
+ binding.recPolar.visibility = View.VISIBLE
+ binding.tvPolarNoTrans.visibility = View.INVISIBLE
+ } else {
+ binding.recPolar.visibility = View.INVISIBLE
+ binding.tvPolarNoTrans.visibility = View.VISIBLE
+ }
+ })
}
- private fun refreshView() {
- val rate = viewModel.getRefreshRate()
- lifecycleScope.launch {
+ private fun refreshText(rate: Long) {
+ refreshJob?.cancel()
+ refreshJob = null
+ refreshJob = lifecycleScope.launch {
while (true) {
+ setPassText()
polarView.invalidate()
delay(rate)
}
}
}
- inner class PolarView(
- context: Context,
- private val updateFreq: Long,
- private val binding: FragmentPolarViewBinding
- ) :
- View(context) {
-
- private val radarSize = resources.displayMetrics.widthPixels
- private val scale = resources.displayMetrics.density
- private val startTime = satPass.pass.startTime
- private val endTime = satPass.pass.endTime
- private val radius = radarSize * 0.45f
- private val piDiv2 = Math.PI / 2.0
- private val txtSize = scale * 15
- private val center = 0f
-
- private val radarPaint = Paint().apply {
- isAntiAlias = true
- color = ContextCompat.getColor(mainActivity, R.color.themeLight)
- style = Paint.Style.STROKE
- strokeWidth = scale
- }
- private val txtPaint = Paint().apply {
- isAntiAlias = true
- color = ContextCompat.getColor(mainActivity, R.color.themeAccent)
- textSize = txtSize
- }
- private val trackPaint = Paint().apply {
- isAntiAlias = true
- color = ContextCompat.getColor(mainActivity, R.color.satTrack)
- style = Paint.Style.STROKE
- strokeWidth = scale
- }
- private val satPaint = Paint().apply {
- isAntiAlias = true
- color = ContextCompat.getColor(mainActivity, R.color.themeAccent)
- style = Paint.Style.FILL
- }
- private val path: Path = Path()
-
- private lateinit var satPos: SatPos
- private var satPassX = 0f
- private var satPassY = 0f
-
- override fun onDraw(canvas: Canvas) {
- canvas.translate(radarSize / 2f, radarSize / 2f)
- setPassText()
- drawRadarView(canvas)
- drawRadarText(canvas)
- if (!satPass.tle.isDeepspace) drawPassTrajectory(canvas)
- drawSatellite(canvas)
- }
-
- private fun setPassText() {
- satPos = satPass.predictor.getSatPos(Date())
- binding.tvPolarAz.text =
- String.format(context.getString(R.string.pat_azimuth), rad2Deg(satPos.azimuth))
- binding.tvPolarEl.text =
- String.format(context.getString(R.string.pat_elevation), rad2Deg(satPos.elevation))
- binding.tvPolarRng.text =
- String.format(context.getString(R.string.pat_range), satPos.range)
- binding.tvPolarAlt.text =
- String.format(context.getString(R.string.pat_altitude), satPos.altitude)
- }
-
- private fun drawRadarView(cvs: Canvas) {
- cvs.drawLine(center - radius, center, center + radius, center, radarPaint)
- cvs.drawLine(center, center - radius, center, center + radius, radarPaint)
- cvs.drawCircle(center, center, radius, radarPaint)
- cvs.drawCircle(center, center, (radius / 3) * 2, radarPaint)
- cvs.drawCircle(center, center, radius / 3, radarPaint)
- }
-
- private fun drawRadarText(cvs: Canvas) {
- cvs.drawText(
- context.getString(R.string.polar_north),
- center - txtSize / 3,
- center - radius - scale * 2,
- txtPaint
- )
- cvs.drawText(
- context.getString(R.string.polar_east),
- center + radius + scale * 2,
- center + txtSize / 3,
- txtPaint
- )
- cvs.drawText(
- context.getString(R.string.polar_south),
- center - txtSize / 3,
- center + radius + txtSize,
- txtPaint
- )
- cvs.drawText(
- context.getString(R.string.polar_west),
- center - radius - txtSize,
- center + txtSize / 3,
- txtPaint
- )
- cvs.drawText("90°", center + scale, center - scale * 2, txtPaint)
- cvs.drawText("60°", center + scale, center - (radius / 3) - scale * 2, txtPaint)
- cvs.drawText("30°", center + scale, center - ((radius / 3) * 2) - scale * 2, txtPaint)
- }
-
- private fun drawPassTrajectory(cvs: Canvas) {
- while (startTime.before(endTime)) {
- satPos = satPass.predictor.getSatPos(startTime)
- satPassX = center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
- satPassY = center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
- if (startTime.compareTo(satPass.pass.startTime) == 0) {
- path.moveTo(satPassX, satPassY)
- } else {
- path.lineTo(satPassX, satPassY)
- }
- startTime.time += updateFreq
- }
- cvs.drawPath(path, trackPaint)
- }
-
- private fun drawSatellite(cvs: Canvas) {
- satPos = satPass.predictor.getSatPos(Date())
- if (satPos.elevation > 0) {
- cvs.drawCircle(
- center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble()),
- center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble()),
- txtSize / 3, satPaint
- )
- }
- }
-
- private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
- val radius = r * (piDiv2 - elevation) / piDiv2
- return (radius * cos(piDiv2 - azimuth)).toFloat()
- }
-
- private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
- val radius = r * (piDiv2 - elevation) / piDiv2
- return (radius * sin(piDiv2 - azimuth)).toFloat()
- }
-
- private fun rad2Deg(value: Double): Double {
- return value * 180 / Math.PI
- }
+ private fun setPassText() {
+ val satPos = satPass.predictor.getSatPos(Date())
+ val polarAz = mainActivity.getString(R.string.pat_azimuth)
+ val polarEl = mainActivity.getString(R.string.pat_elevation)
+ val polarRng = mainActivity.getString(R.string.pat_range)
+ val polarAlt = mainActivity.getString(R.string.pat_altitude)
+ binding.tvPolarAz.text = String.format(polarAz, GeneralUtils.rad2Deg(satPos.azimuth))
+ binding.tvPolarEl.text = String.format(polarEl, GeneralUtils.rad2Deg(satPos.elevation))
+ binding.tvPolarRng.text = String.format(polarRng, satPos.range)
+ binding.tvPolarAlt.text = String.format(polarAlt, satPos.altitude)
}
}
diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/views/PolarView.kt b/app/src/main/java/com/rtbishop/look4sat/ui/views/PolarView.kt
new file mode 100644
index 00000000..205a05c0
--- /dev/null
+++ b/app/src/main/java/com/rtbishop/look4sat/ui/views/PolarView.kt
@@ -0,0 +1,106 @@
+package com.rtbishop.look4sat.ui.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.view.View
+import androidx.core.content.ContextCompat
+import com.rtbishop.look4sat.R
+import com.rtbishop.look4sat.data.SatPass
+import com.rtbishop.look4sat.utility.GeneralUtils
+
+class PolarView(context: Context) : View(context) {
+
+ constructor(context: Context, satPass: SatPass) : this(context) {
+ pass = satPass
+ }
+
+ private lateinit var pass: SatPass
+ private val polarWidth = resources.displayMetrics.widthPixels
+ private val polarCenter = polarWidth / 2f
+ private val scale = resources.displayMetrics.density
+ private val radius = polarWidth * 0.49f
+ private val txtSize = scale * 15
+ private val path: Path = Path()
+ private val mMatrix = Matrix()
+ var azimuth = 0f
+
+ private val radarPaint = Paint().apply {
+ isAntiAlias = true
+ color = ContextCompat.getColor(context, R.color.themeLight)
+ style = Paint.Style.STROKE
+ strokeWidth = scale
+ }
+ private val txtPaint = Paint().apply {
+ isAntiAlias = true
+ color = ContextCompat.getColor(context, R.color.themeAccent)
+ textSize = txtSize
+ }
+ private val trackPaint = Paint().apply {
+ isAntiAlias = true
+ color = ContextCompat.getColor(context, R.color.satTrack)
+ style = Paint.Style.STROKE
+ strokeWidth = scale
+ }
+ private val satPaint = Paint().apply {
+ isAntiAlias = true
+ color = ContextCompat.getColor(context, R.color.themeAccent)
+ style = Paint.Style.FILL
+ }
+
+ override fun onDraw(canvas: Canvas) {
+// Log.w("myTag", "$measuredWidth, $measuredHeight")
+ canvas.rotate(-azimuth, polarCenter, polarCenter)
+ canvas.translate(polarCenter, polarCenter)
+ drawRadarView(canvas)
+ drawRadarText(canvas)
+ if (!pass.tle.isDeepspace) {
+ drawPassTrajectory(canvas, pass)
+ }
+ drawSatellite(canvas, pass)
+ }
+
+ private fun drawRadarView(cvs: Canvas) {
+ cvs.drawLine(-radius, 0f, radius, 0f, radarPaint)
+ cvs.drawLine(0f, -radius, 0f, radius, radarPaint)
+ cvs.drawCircle(0f, 0f, radius, radarPaint)
+ cvs.drawCircle(0f, 0f, (radius / 3) * 2, radarPaint)
+ cvs.drawCircle(0f, 0f, radius / 3, radarPaint)
+ }
+
+ private fun drawRadarText(cvs: Canvas) {
+ cvs.drawText("N", scale, -radius + txtSize - scale * 2, txtPaint)
+ cvs.drawText("30°", scale, -((radius / 3) * 2) - scale * 2, txtPaint)
+ cvs.drawText("60°", scale, -(radius / 3) - scale * 2, txtPaint)
+ cvs.drawText("90°", scale, -scale * 2, txtPaint)
+ }
+
+ private fun drawPassTrajectory(cvs: Canvas, satPass: SatPass) {
+ val startTime = satPass.pass.startTime
+ val endTime = satPass.pass.endTime
+ while (startTime.before(endTime)) {
+ val satPos = satPass.predictor.getSatPos(startTime)
+ val x = GeneralUtils.sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
+ val y = GeneralUtils.sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
+ if (startTime.compareTo(satPass.pass.startTime) == 0) {
+ path.moveTo(x, -y)
+ } else {
+ path.lineTo(x, -y)
+ }
+ startTime.time += 15000
+ }
+ cvs.drawPath(path, trackPaint)
+ }
+
+ private fun drawSatellite(cvs: Canvas, satPass: SatPass) {
+ val date = GeneralUtils.getDateFor(System.currentTimeMillis())
+ val satPos = satPass.predictor.getSatPos(date)
+ if (satPos.elevation > 0) {
+ val x = GeneralUtils.sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
+ val y = GeneralUtils.sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
+ cvs.drawCircle(x, -y, txtSize / 3, satPaint)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rtbishop/look4sat/utility/GeneralUtils.kt b/app/src/main/java/com/rtbishop/look4sat/utility/GeneralUtils.kt
new file mode 100644
index 00000000..7da294da
--- /dev/null
+++ b/app/src/main/java/com/rtbishop/look4sat/utility/GeneralUtils.kt
@@ -0,0 +1,28 @@
+package com.rtbishop.look4sat.utility
+
+import java.util.*
+import kotlin.math.cos
+import kotlin.math.sin
+
+object GeneralUtils {
+
+ private const val piDiv2 = Math.PI / 2.0
+
+ fun getDateFor(value: Long): Date {
+ return Date(value)
+ }
+
+ fun rad2Deg(value: Double): Double {
+ return value * 180 / Math.PI
+ }
+
+ fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
+ val radius = r * (piDiv2 - elevation) / piDiv2
+ return (radius * cos(piDiv2 - azimuth)).toFloat()
+ }
+
+ fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
+ val radius = r * (piDiv2 - elevation) / piDiv2
+ return (radius * sin(piDiv2 - azimuth)).toFloat()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 4e573830..3e313898 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -77,11 +77,6 @@
Элевация: %.1f°
%2d° — КПС
- С
- З
- Ю
- В
-
Азимут: %.1f°
Элевация: %.1f°
Широта: %.1f°
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bc8f1b54..f2f0952e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,18 +91,13 @@
%2d° — LOS
dd MMM @ HH:mm:ss
- N
- W
- S
- E
-
%.4f
- Azimuth: %.1f°
- Elevation: %.1f°
- Latitude: %.1f°
- Longitude: %.1f°
- Distance: %.0f km
- Altitude: %.0f km
+ Az: %.1f°
+ El: %.1f°
+ Lat: %.1f°
+ Lon: %.1f°
+ Dst: %.0f km
+ Alt: %.0f km
%02d:%02d:%02d
%s