diff options
| author | Claudomator Agent <agent@claudomator> | 2026-03-14 00:50:39 +0000 |
|---|---|---|
| committer | Claudomator Agent <agent@claudomator> | 2026-03-14 00:50:39 +0000 |
| commit | 3f18f770e9d33c5e5d0657c6160fa8f30b21831f (patch) | |
| tree | a1df48d383c8627b4111fd55a7066b6afcb84ae1 /android-app/app/src/main/temp/CompassRoseView.kt | |
| parent | 9fb49aebfc01b5df68e67e97ee088318a3621c26 (diff) | |
Implement barometric pressure trend monitoring and visualization
Diffstat (limited to 'android-app/app/src/main/temp/CompassRoseView.kt')
| -rwxr-xr-x | android-app/app/src/main/temp/CompassRoseView.kt | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/android-app/app/src/main/temp/CompassRoseView.kt b/android-app/app/src/main/temp/CompassRoseView.kt new file mode 100755 index 0000000..8e755a3 --- /dev/null +++ b/android-app/app/src/main/temp/CompassRoseView.kt @@ -0,0 +1,217 @@ +package org.terst.nav.temp // Temporarily placing in 'temp' due to permissions, actual package should be 'org.terst.nav' + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import kotlin.math.cos +import kotlin.math.min +import kotlin.math.sin + +class CompassRoseView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private var heading: Float = 0f // Current heading in degrees + set(value) { + field = value % 360 // Ensure heading is within 0-359 + invalidate() + } + private var cog: Float = 0f // Course Over Ground in degrees + set(value) { + field = value % 360 + invalidate() + } + private var isTrueHeading: Boolean = true // True for True heading, false for Magnetic + + private val rosePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.DKGRAY + style = Paint.Style.STROKE + strokeWidth = 2f + } + + private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + textSize = 30f + textAlign = Paint.Align.CENTER + } + + private val cardinalTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + textSize = 40f + textAlign = Paint.Align.CENTER + isFakeBoldText = true + } + + private val majorTickPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + strokeWidth = 3f + } + + private val minorTickPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.GRAY + strokeWidth = 1f + } + + private val headingNeedlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.RED + style = Paint.Style.FILL + } + + private val cogArrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.BLUE + style = Paint.Style.FILL + strokeWidth = 5f + } + + private var viewCenterX: Float = 0f + private var viewCenterY: Float = 0f + private var radius: Float = 0f + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + viewCenterX = w / 2f + viewCenterY = h / 2f + radius = min(w, h) / 2f - 40f // Leave some padding + textPaint.textSize = radius / 6f + cardinalTextPaint.textSize = radius / 4.5f + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + // Draw outer circle + canvas.drawCircle(viewCenterX, viewCenterY, radius, rosePaint) + + // Draw cardinal and intercardinal points + drawCardinalPoints(canvas) + + // Draw tick marks and degree labels + drawDegreeMarks(canvas) + + // Draw heading needle + drawHeadingNeedle(canvas, heading, headingNeedlePaint, radius * 0.8f) + + // Draw COG arrow + drawCogArrow(canvas, cog, cogArrowPaint, radius * 0.6f) + + // Draw current heading text in the center + drawHeadingText(canvas) + } + + private fun drawCardinalPoints(canvas: Canvas) { + val cardinalPoints = listOf("N", "E", "S", "W") + val angles = listOf(0f, 90f, 180f, 270f) + val textBound = Rect() + + for (i in cardinalPoints.indices) { + val angleRad = Math.toRadians((angles[i] - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock + val x = viewCenterX + (radius * 0.9f) * cos(angleRad) + val y = viewCenterY + (radius * 0.9f) * sin(angleRad) + + val text = cardinalPoints[i] + cardinalTextPaint.getTextBounds(text, 0, text.length, textBound) + val textHeight = textBound.height() + + canvas.drawText(text, x, y + textHeight / 2, cardinalTextPaint) + } + } + + private fun drawDegreeMarks(canvas: Canvas) { + for (i in 0 until 360 step 5) { + val isMajor = (i % 30 == 0) // Major ticks every 30 degrees + val tickLength = if (isMajor) 30f else 15f + val currentTickPaint = if (isMajor) majorTickPaint else minorTickPaint + val startRadius = radius - tickLength + + val angleRad = Math.toRadians((i - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock + + val startX = viewCenterX + startRadius * cos(angleRad) + val startY = viewCenterY + startRadius * sin(angleRad) + val endX = viewCenterX + radius * cos(angleRad) + val endY = viewCenterY + radius * sin(angleRad) + + canvas.drawLine(startX, startY, endX, endY, currentTickPaint) + + if (isMajor && i != 0) { // Draw degree labels for major ticks (except North) + val textRadius = radius - tickLength - textPaint.textSize / 2 - 10f + val textX = viewCenterX + textRadius * cos(angleRad) + val textY = viewCenterY + textRadius * sin(angleRad) + textPaint.textSize / 2 + + canvas.drawText(i.toString(), textX, textY, textPaint) + } + } + } + + private fun drawHeadingNeedle(canvas: Canvas, angle: Float, paint: Paint, length: Float) { + val angleRad = Math.toRadians((angle - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock + val endX = viewCenterX + length * cos(angleRad) + val endY = viewCenterY + length * sin(angleRad) + + // Draw a simple triangle for the needle + val needleWidth = 20f + val path = android.graphics.Path() + path.moveTo(endX, endY) + path.lineTo(viewCenterX + needleWidth * cos(angleRad - Math.toRadians(90.0).toFloat()), + viewCenterY + needleWidth * sin(angleRad - Math.toRadians(90.0).toFloat())) + path.lineTo(viewCenterX + needleWidth * cos(angleRad + Math.toRadians(90.0).toFloat()), + viewCenterY + needleWidth * sin(angleRad + Math.toRadians(90.0).toFloat())) + path.close() + canvas.drawPath(path, paint) + } + + private fun drawCogArrow(canvas: Canvas, angle: Float, paint: Paint, length: Float) { + val angleRad = Math.toRadians((angle - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock + val endX = viewCenterX + length * cos(angleRad) + val endY = viewCenterY + length * sin(angleRad) + + val startX = viewCenterX + (length * 0.5f) * cos(angleRad) + val startY = viewCenterY + (length * 0.5f) * sin(angleRad) + + canvas.drawLine(startX, startY, endX, endY, paint) + + // Draw arrow head + val arrowHeadLength = 25f + val arrowHeadWidth = 15f + val arrowPath = android.graphics.Path() + arrowPath.moveTo(endX, endY) + arrowPath.lineTo(endX - arrowHeadLength * cos(angleRad - Math.toRadians(30.0).toFloat()), + endY - arrowHeadLength * sin(angleRad - Math.toRadians(30.0).toFloat())) + arrowPath.moveTo(endX, endY) + arrowPath.lineTo(endX - arrowHeadLength * cos(angleRad + Math.toRadians(30.0).toFloat()), + endY - arrowHeadLength * sin(angleRad + Math.toRadians(30.0).toFloat())) + canvas.drawPath(arrowPath, paint) + } + + private fun drawHeadingText(canvas: Canvas) { + val headingText = "${heading.toInt()}°" + if (isTrueHeading) "T" else "M" + textPaint.color = Color.WHITE + textPaint.textSize = radius / 3.5f // Larger text for main heading + canvas.drawText(headingText, viewCenterX, viewCenterY + textPaint.textSize / 3, textPaint) + } + + /** + * Sets the current heading to display. + * @param newHeading The new heading value in degrees (0-359). + * @param isTrue Whether the heading is True (magnetic variation applied) or Magnetic. + */ + fun setHeading(newHeading: Float, isTrue: Boolean) { + this.heading = newHeading + this.isTrueHeading = isTrue + invalidate() + } + + /** + * Sets the Course Over Ground (COG) to display. + * @param newCog The new COG value in degrees (0-359). + */ + fun setCog(newCog: Float) { + this.cog = newCog + invalidate() + } +} |
