目录

  • 1 获取卫星信息
  • 2 自定义视图
  • 3 绘制底图
    • 3.1 圆的画法
    • 3.2 直线的画法
    • 3.3 绘制刻度
    • 3.4 绘制结果
  • 4 绘制卫星
    • 4.1 计算卫星坐标
    • 4.2 获取卫星旗帜
    • 4.3 绘制卫星
  • 5 动态绘制
  • 6 自定义视图大小的控制

效果图:

首先介绍一下相关的概念:

方位角:是从某点的指北(地理北极)方向线起,依顺时针方向到目标方向线之间的水平夹角。

高度角:从一点至观测目标的方向线与水平面间的夹角。

卫星天顶图:即是根据每一颗卫星的方位角和高度角将其画在以观测位置为中心的天顶图上。

天顶图其底图为由外向内的三个圆和四条直线。三个圆由外向内依次代表高度角0°、30°、60°,中心点代表90°;四条直线分别表示正北-正南、东北-西南、正东-正西、东南-西北的方位角方向。

下面逐一介绍绘制的步骤

1 获取卫星信息

要想画出卫星图,必须先有卫星的方位角和高度角等基本信息。在Android中,可以通过向LocationManager类注册GnssStatus监听器获取卫星PRN、方位角、高度角、星座类型、卫星载噪比等与卫星本身有关的信息。

关于如何注册GNSS监听器,Android GNSS伪距计算 中有注册测量监听器的详细步骤,卫星状态监听器的注册方式完全类似。

不同的是在回调方法中的行为,在这里,我们需要使用数组将卫星信息保存起来,如下:

private float[] mElevations, mAzimuths, mSnrs;private int[] mPrns, mConstellationTypes;public void setGnssStatus(GnssStatus status) {if (mPrns == null) {final int MAX_LENGTH = 255;mPrns = new int[MAX_LENGTH];mElevations = new float[MAX_LENGTH];mAzimuths = new float[MAX_LENGTH];mConstellationTypes = new int[MAX_LENGTH];mSnrs = new float[MAX_LENGTH];}int length = status.getSatelliteCount();mSvCount = 0;while (mSvCount < length) {mPrns[mSvCount] = status.getSvid(mSvCount);mElevations[mSvCount] = status.getElevationDegrees(mSvCount);mAzimuths[mSvCount] = status.getAzimuthDegrees(mSvCount);mConstellationTypes[mSvCount] = status.getConstellationType(mSvCount);mSnrs[mSvCount] = status.getCn0DbHz(mSvCount);mSvCount++;}invalidate();
}

通过GnssStatus的参数对象status的 getSatelliteCount() 方法获取卫星数量,然后使用 getSvid()、getElevationDegrees()、getAzimuthDegrees()、getConstellationType()、getCn0DbHz() 方法分别获取卫星的 PRN、高度角、方位角、星座、载噪比

然后调用View对象的invalidate()方法重绘星空图,即更新。

2 自定义视图

由于Android中并没有一种系统控件可以让我们方便的展示卫星图,因此我们需要自定义一个天空图视图

public class GnssSkyView extends View { }

我们在碎片Fragment中加载这个视图,通过在碎片上注册监听器获得不断更新的信息,并在碎片的回调方法中调用 GnssSkyView 对象的 setGnssStatus 方法将信息传给它,从而使其获得数据来源,进而进行多样化地展示

3 绘制底图

底图包括三个圆、四条直线和外圈圆上的方位角刻度。

onDraw() 方法会在视图绘制的过程中系统自动调用,我们需要在这个方法中自定义绘制内容。

protected void onDraw(Canvas canvas) {super.onDraw(canvas);int minScreenDimen = Math.min(mWidth, mHeight);float radius = minScreenDimen / 2.0f;drawCircle(canvas, radius);drawLine(canvas, minScreenDimen, radius);drawDegree(canvas, radius);
}

因为我们要画的是一个圆,所以一定有一个外切正方形,上面的代码中,minScreenDimen 为视图长和宽的最小值,这个值就作为正方形的边长,也是外圈圆的直径

然后我们分别调用三个函数drawCircle()、drawLine()、drawDegree(),这三个函数是我们自己定义的,分别画圆、直线、刻度。

3.1 圆的画法

onDraw() 方法会给我们一个参数canvas,这个参数是视图画布Canvas的对象,通过调用canvas的 drawCircle() 方法,就能实现圆的绘制。

该方法的函数原型如下:

public void drawCircle(float cx, float cy, float radius, Paint paint) {super.drawCircle(cx, cy, radius, paint);
}

可见,我们需要给出圆心的 x,y 坐标和圆的半径 r,还有画笔 paint

这里需要注意的是,画布的坐标系是以左上角为坐标原点,水平向右为X轴正向,竖直向下为Y轴正向

我们在前面已经获取了外圆的半径,而外圈圆代表的是高度角0°,因此还需要计算出30°、60°的高度角代表的圆的半径。

然后这里给了一个 Y_TRANSLATION ,这是预定义的Y轴偏移量,方向向下为正,目的是使最上方的卫星能够完整的显示出来

private void drawCircle(Canvas c, float radius) {c.drawCircle(radius, radius + Y_TRANSLATION, elevationToRadius(radius, 60.0f), mPaintCircleAndLine);c.drawCircle(radius, radius + Y_TRANSLATION, elevationToRadius(radius, 30.0f), mPaintCircleAndLine);c.drawCircle(radius, radius + Y_TRANSLATION, elevationToRadius(radius, 0.0f), mPaintCircleAndLine);
}private float elevationToRadius(float s, float elev) {return s * (1.0f - (elev / 90.0f));
}

3.2 直线的画法

直线的画法与圆类似,画直线的系统函数如下,其属于canvas画布对象:

public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {super.drawLine(startX, startY, stopX, stopY, paint);
}

可知我们需要给出线段的起点坐标和终点坐标,还有画笔对象 paint

先计算出四条直线的起止点坐标,然后调用drawLine()方法

下面的函数中,s即minScreenDimen

private void drawLine(Canvas c, int s, float radius) {c.drawLine(radius, Y_TRANSLATION, radius, s + Y_TRANSLATION, mPaintCircleAndLine);c.drawLine(0, radius + Y_TRANSLATION, s, radius + Y_TRANSLATION, mPaintCircleAndLine);final float cos45 = (float) Math.cos(Math.PI / 4);float d1 = radius * (1 - cos45);float d2 = radius * (1 + cos45);c.drawLine(d1, d1 + Y_TRANSLATION, d2, d2 + Y_TRANSLATION, mPaintCircleAndLine);c.drawLine(d2, d1 + Y_TRANSLATION, d1, d2 + Y_TRANSLATION, mPaintCircleAndLine);
}

3.3 绘制刻度

绘制刻度的实质就是绘制直线,计算出每一个刻度线段的起止点坐标,然后调用绘制直线的函数

这里,在正北、正东、正南、正西方向上还要给出N、E、S、W的文本,可以调用画布对象canvas的drawText()系统函数绘制

drawText()系统函数原型如下:

public void drawText(String text, float x, float y, Paint paint) {super.drawText(text, x, y, paint);
}

只需给出文本字符串、文本位置坐标、画笔对象

这里,使用rorate()系统函数进行旋转绘制,详细原理可以自行百度

private void drawDegree(Canvas c, float radius) {for (int i = 0; i < 360; i += 15) {if (i == 45 || i == 135 || i == 225 || i == 315) {c.drawText(String.valueOf(i), radius, 40 + Y_TRANSLATION, mPaintDegree);} else if (i == 0) {c.drawText("N", radius, 40 + Y_TRANSLATION, mPaintDegree);} else if (i == 90) {c.drawText("E", radius, 40 + Y_TRANSLATION, mPaintDegree);} else if (i == 180) {c.drawText("S", radius, 40 + Y_TRANSLATION, mPaintDegree);} else if (i == 270) {c.drawText("W", radius, 40 + Y_TRANSLATION, mPaintDegree);} else {c.drawLine(radius, Y_TRANSLATION, radius, 20 + Y_TRANSLATION, mPaintDegree);}c.rotate(15, radius, radius + Y_TRANSLATION);}
}

3.4 绘制结果

4 绘制卫星

4.1 计算卫星坐标

如上图,根据高度角可以计算出卫星的半径,即到圆心的距离 r,再根据方位角 α 和外圈圆的半径 R 即可确定卫星在画布坐标系中的坐标

4.2 获取卫星旗帜

这个比较简单,只需要预先准备好四大系统和日本的代表旗帜,然后根据星座类型来获取Bitmap对象

这里,准备了六张PNG图片,放在./res/drawable目录下

然后,使用BitmapFactory.decodeResource() 方法创建一个位图对象。这个方法的第一个参数为Android系统的资源对象resources,可以通过视图View对象的getResources() 方法得到;第二个参数为图片数据的资源ID

值得一提的是,一般我们准备的图片尺寸都各不相同,并且远大于在视图上展示的大小,而为了让所有星座的卫星使用同样小尺寸的图片,我们需要对位图Bitmap进行放缩

private Bitmap getSatelliteBitmap(int constellationType) {Bitmap baseMap, newMap;int width, height;switch (constellationType) {case GnssStatus.CONSTELLATION_BEIDOU:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_china);break;case GnssStatus.CONSTELLATION_GPS:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_america);break;case GnssStatus.CONSTELLATION_GALILEO:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_europe);break;case GnssStatus.CONSTELLATION_GLONASS:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_russia);break;case GnssStatus.CONSTELLATION_QZSS:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_japan);break;default:baseMap = BitmapFactory.decodeResource(getResources(), R.drawable.flag_of_other);}width = baseMap.getWidth();height = baseMap.getHeight();newMap = UiUtils.scaling(baseMap, (SAT_RADIUS * 2.0f) / width, (SAT_RADIUS * 2.0f) / height);return newMap;
}public class UiUtils {public static Bitmap scaling(Bitmap bitmap, float widthScale, float heightScale) {Matrix matrix = new Matrix();matrix.postScale(widthScale, heightScale); //长和宽放大缩小的比例return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);}
}

4.3 绘制卫星

绘制卫星的代码如下,其中我们还要生成卫星星座+卫星PRN的文本字符串:

private void drawSatellite(Canvas c, int s, float elev, float azim, float snr, int prn, int constellationType) {double radius, angle;float x, y;Bitmap satMap;satMap = getSatelliteBitmap(constellationType);String satText;satText = getSatelliteText(prn, constellationType);radius = elevationToRadius(s / 2.0f, elev);angle = (float) Math.toRadians(azim);x = (float) ((s / 2.0f) + (radius * Math.sin(angle)));y = (float) ((s / 2.0f) - (radius * Math.cos(angle)));c.drawBitmap(satMap, x - SAT_RADIUS, y - SAT_RADIUS + Y_TRANSLATION, null);c.drawText(satText, x - SAT_RADIUS, y + SAT_RADIUS * 2 + Y_TRANSLATION, mPrnIdPaint);
}private String getSatelliteText(int prn, int constellationType) {StringBuilder builder = new StringBuilder();switch (constellationType) {case GnssStatus.CONSTELLATION_BEIDOU:builder.append("C");break;case GnssStatus.CONSTELLATION_GPS:builder.append("G");break;case GnssStatus.CONSTELLATION_GALILEO:builder.append("E");break;case GnssStatus.CONSTELLATION_GLONASS:builder.append("R");break;case GnssStatus.CONSTELLATION_QZSS:builder.append("Q");break;default:builder.append("S");}builder.append(prn);return builder.toString();
}

5 动态绘制

我们在注册卫星状态信息的监听器后,会根据注册时指定的更新参数获得一定时间间隔的卫星信息,每次卫星的状态信息得到更新后,我们调用天空图类对象(自定义视图)的 invalidate() 方法(View及其子类的对象都有这个方法)重绘视图即可更新图上卫星的位置。

6 自定义视图大小的控制

View的工作流程,主要是measure、layout和draw三步,measure用来测量View的宽高,layout用来确定View(在ViewGroup中)的位置,draw则用来绘制View。

决定View的大小只需要两个值:宽详细测量值(widthMeasureSpec)和高详细测量值(heightMeasureSpec)。也可以把详细测量值理解为视图View想要的大小说明(想要的未必就是最终大小)。

我们先获取屏幕的宽高,然后取较小值,因为一般是宽较小,所以将高度设为宽度+20dp,然后使用View的 setMeasuredDimension() 方法设置视图的大小。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int windowWidth = mDisplayMetrics.widthPixels;int windowHeight = mDisplayMetrics.heightPixels;int minL = Math.min(windowWidth, windowHeight);setMeasuredDimension(minL, minL + 20);
}
private void init(Context context) {mDisplayMetrics = mContext.getResources().getDisplayMetrics();getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic boolean onPreDraw() {mHeight = getHeight();mWidth = getWidth();return true;}});invalidate();
}

Android GNSS 可视卫星星空图/卫星天顶图 原理及画法介绍相关推荐

  1. Android GNSS 模块分析(四)HAL 层

    紧接着上一篇(Android GNSS 模块分析(三)JNI 层),继续来分析下 Android GNSS HAL 层的功能,本篇准备先介绍下 HIDL 层的封装.至于后面的 HAL 层的功能,由于使 ...

  2. 根据卫星的方位角和仰角画卫星星空图(QT实现)

    作者:haomingHu email:hhm_master@163.com 需求:在0183格式中,根据GNGSV中的卫星仰角和方位角去绘制卫星星空图 如图: 目标:实现一个卫星的映射 名词解释: 方 ...

  3. ECharts+高德卫星地图-飞线图效果

    ECharts+高德地图实现卫星地图-飞线图 加载资源: https://webapi.amap.com/maps?v=1.4.15&key=申请Key(高德开放平台申请key) echart ...

  4. Android GNSS 模块分析(五)NMEA 协议

    紧接着上一篇<Android GNSS 模块分析(四)HAL 层>,本篇简述下导航硬件设备与卫星导航系统之间的通信协议. NMEA 协议 简介: NMEA(National Marine ...

  5. Android Gnss信息获取 绘制卫星图

    Android GNSS基本信息获取 Android 中GNSS为GPS定位中包含的原始定位信息,主要包含三个接口(api >= 24),提供不同信息: // GNSS处理后信息,包含一些常用信 ...

  6. sentinel卫星_IKONOS卫星 遥感影像解译数据 波段

    IKONOS卫星遥感影像解译数据的波段 IKONOS卫星影像 IKONOS卫星简介 广西善图科技 IKONOS为美国DigitalGlobe公司的高分辨率遥感卫星,于1999年09月24日发射,其影像 ...

  7. Android实现截屏和截长图功能的几种方法

    一般情况下各种型号的手机都会有自带的截屏功能,也会有诸如"开关机键+音量键"的截屏快捷键,只要手机是亮屏状态,都会将手机屏幕的可视区域(包含状态栏)全部截取下来. 如果开发中想要调 ...

  8. 网络时间同步(卫星时钟同步系统)技术原理介绍

    网络时间同步(卫星时钟同步系统)技术原理介绍 网络时间同步(卫星时钟同步系统)技术原理介绍 1.前言 由计算机网络系统组成的分布式系统,若想协调一致进行:IT行业的"整点开拍".& ...

  9. 【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )

    文章目录 一.GestureDetector 创建与设置 二.GestureDetector 触摸事件传递 三.触摸滑动操作 四.惯性滑动操作 五.长图滑动组件代码示例 六.运行效果 七.源码及资源下 ...

最新文章

  1. mysql 判断表或字段存不存在
  2. SAP MES接收生产订单及工艺路线
  3. git 创建tag , 查看tag , 删除tag
  4. OKR和KPI的区别是啥?
  5. CUDA零拷贝内存(zerocopy memory)
  6. linux strace cpu,如何定位死循环或高CPU使用率(linux)
  7. tensor.detach() 和 tensor.data 的区别
  8. C++ 什么是句柄?为什么会有句柄?HANDLE
  9. C#操作Excel数据库方法
  10. Java类和对象(未完待续,持续更新)
  11. Intel处理器将被苹果M1处理器降维式打击
  12. 2021-2025年中国冷链跟踪和监测系统行业市场供需与战略研究报告
  13. 通过Matplotlib画sin(x)
  14. HC110110026 网络地址转换
  15. 如何使用Dart的Stream(一)
  16. 西瓜视频下载软件有吗
  17. 沃丰科技:AI赋能泛CRM,为新企服扬风鼓帆
  18. 软件工程 3:模块化设计
  19. JS 日历插件 实现农历、节气 可自定义加班和休假
  20. 苹果充电时一充一停怎么办_苹果充电线一会儿能充一会儿不能充怎么回事

热门文章

  1. Digilent FPGA开发板的Boards file的添加——以Eclypse-Z7为例
  2. 在网站页脚添加QQ邮箱的“邮我”功能
  3. 机器学习 第三节 第八课
  4. 【杭州seo】百度快照关键词不同颜色的代表含义
  5. java 实例化list_java中List的用法和实例详解
  6. Linux C网络编程基础
  7. 中电科45家研究所:北上第一梯队,南京、成都、合肥和重庆第二梯队
  8. Netty保姆级教程(一)IO 演变
  9. CLB Overview
  10. 各种软件如何双开,三开,N开,包括微信,qq等。