首先声明一下,本文C语言程序部分不明白可以略过,而且知乎图片清晰度不高,代码部分看不清,主要理解思想就可以了:
毕业设计遇到一个难题,需要求解日本导航卫星在亚太地区可视性范围,需求解一天内24小时可视时间百分比为100%的区域,另外日本导航卫星是高轨道卫星,还要分析高截止角下卫星的可视性,比如你在高楼林立的城市中心,那些仰角低的卫星就看不到了,这个时候你就要知道这颗卫星在你看到的仰角之上的可视性是否满足要求。
我很快想到了一个方法,就是采用中心点射线法,选取卫星运行轨迹的的中点,从360°方向上选取距离这个点最远并且满足要求的点,解算完成后将360°方向上的这些最远点连起来就是范围了。下面给出原理介绍和主要代码:
![b6be9570789c261caea68e7c59a196b8.png](https://i-blog.csdnimg.cn/blog_migrate/2d0b6cc34844c5e721b49ed9e1b9df24.jpeg)
![2b2ced6320386be640809310720e028b.png](https://i-blog.csdnimg.cn/blog_migrate/72e52417980ef489e0042fac5275ac15.jpeg)
![eb940a0dc3186fc552bd9d3b0e91c34b.png](https://i-blog.csdnimg.cn/blog_migrate/770b76f685bba762fb6e4a4ec75249af.jpeg)
看起来是不是没有问题?逻辑也确实没错,但是有个重要的缺陷,如果范围并不是很规则像下面画的那样:
![ce5a4b1206256ae104ddf8f09958a627.png](https://i-blog.csdnimg.cn/blog_migrate/a04040dee6f458fbccac75e4257decc5.png)
或者像下面这样选取的中心点Z在范围外面
![5d3ce6e65a2e8d84f944dc0a3016ba66.png](https://i-blog.csdnimg.cn/blog_migrate/0abb791280952125e4c1db812642be9f.png)
那么采用用这种中心点辐射的方法可定是错的!
所以必须要开发另外一种更可靠的办法,来解决问题。
众所周知,图像在电脑上是个个像素点的方式存储的,我们看到的图片都是由上百万个像素点所组成,所以我有个大胆的想法,是不是可以把整个地球表面划分成一个个像素点,对所有像素点进行计算,满足要求的点不就是要求的范围了吗?
![4a9abe60721966e12beaaedaa111b539.png](https://i-blog.csdnimg.cn/blog_migrate/345583d1377a425520068038cab8f018.png)
想法没有错,但是有个严重的问题!我们想要的是范围,是要用一个线圈起来的范围,这些个点是神马意思?太难理解它的意思。我们要的效果是这样的:
![d12ff4fe23640ded177e59d3d3e6bde1.png](https://i-blog.csdnimg.cn/blog_migrate/2fec42113d899e6afc6e1245ddb0f7ac.png)
这样就表达明了了!圈里面就是满足要求得范围,圈外面的就是不满足要求的点。
但是有个问题。这样一个线咱们怎么画出来?智慧的生物——人类能一眼看出图中的点并画出线。但是对于电脑来说,这些只是一堆存在内存里的数据,只有大小概念,根本没有范围的概念。
所以!我们需要做一个程序,要对图像的轮廓进行识别,并且把轮廓给画出来。
首先,对于一张图像来说,那些边界的像素就是图像的像素点。怎么把这些像素点从那么多的内部点里给分开呢?下面我给张图:
![062f05301442a034e622031637eb62fe.png](https://i-blog.csdnimg.cn/blog_migrate/4a90422645c7ff570d75d7b82c969522.jpeg)
黑点代表我们求得的范围,是一个个点的坐标,这些点聚在一起就成了我们的范围图。那么他的边界点是什么?
仔细观察就会发现一个要点,就是边界点有个特点就是它们四周有空格,这在围棋里面就是代表“气孔”,如果一团棋子没有“气孔”它就会被堵死,这团棋子就是死棋!所以,那些能与外面通气的点就是外点。我们把这些外面的点给用白子给表现出来:
![4633e0cde2e4bda83c0d2c98ef538421.png](https://i-blog.csdnimg.cn/blog_migrate/c8d5efc8c4e9c298468f7bfbcf5cc37a.jpeg)
这些白色点就是我们要求得点了!记住其特征就是与外面相通的点。
实现这一步的方法就是创建一个二维链表,链表基本结构单元如下:
![aac1e0e273d3ad0368823c092f81164c.png](https://i-blog.csdnimg.cn/blog_migrate/f2ff1fecb267a5f54758a4efa5dd50ac.png)
dB代表这个像素点所在纬度,dL代表这个像素点所在经度,N、S、W、E东西南北指针代表这个像素点周围的点的地址。其他的参数,后面会讲到。我们生成一个网状链表,让每一个点实现可以找到与东西南北四个点的位置,如果某个方向没有点,就指向NULL,最后四周没有NULL的点就是内点,存在NULL的点就是外点;
但是,我们要做的远远不止这些,这些只是一个个点,我们要把它们连成线。同样遇到了问题,作为人类能很快按照次序将它们连在一起,但是电脑比较憨,它分不清每个点的连接次序,需要你来对点进行排序才能连接。
我们很容易想到,先选一个外点,对它四周八个邻近点的格子进行扫描,找出下一个外点,然后将它们相连。如下图从1号点开始,它周围有8个格子,这8个格子有两个点满足要求,因为是起始点,所以随便选一个点作为2号点进行连线。
![90b16ca12442e552138b21ab4d9901fd.png](https://i-blog.csdnimg.cn/blog_migrate/2dd83012549ba9b1882faa0b94c1334e.jpeg)
这样到了2号点,2号点周围8个格子里同样满足两个点满足要求分别是点1点3,但是点1我们连过了,所以可以将它舍去掉,下一步很明确连点3。但是到了点3就又出现一个严重的问题,周围存在点4和点5,同样都是外点,我们是连点4还是连点5呢?
很明显嘛点4在外面点肯定先连点4,但是这个在“外面点”概念太模糊,电脑理解不了。但是,有个概念可以很清楚明了,就是点5它周围4个方向上存在黑点,而点4周围不存在黑点,说明点4不与内点相接,咱们就以这个标准舍弃点4选择点5。程序继续正常运行。
![c874960891ccb7add38599452c482b7e.png](https://i-blog.csdnimg.cn/blog_migrate/bbce1cdb0b1a1a7226c979dd6877d417.jpeg)
以同样的理由排除了9和10,这个时候到了12点问题又来了!
13号点和14号点同样满足即是外点周围又存在内点的条件,到底选13还是选14呢?
这个问题先不讨论,反正13点和14点都差不多,我们先选一个点先连着把,先选13。最后出来的图是这样的。
![0fc58e4924dcc2f31c9e6297f6581f8f.png](https://i-blog.csdnimg.cn/blog_migrate/c15acb1277b3bf39ca6c32f2028c10c0.jpeg)
范围画好了。等等好像少了什么?有些点没连!,那些没连的点在利用另外一条线连上呀,我们再去选取一个点做开头,把剩下的点连起来就是的。
![c9d5bb2c7b674cf4e468f81d89d87bb6.png](https://i-blog.csdnimg.cn/blog_migrate/7671480a262da43bdf1bf24edcb769a4.jpeg)
全图是这样的,但是有个问题,由于之前定义过,已经连接的线不能当做连接点,第二条线它不闭合,我一开始也没想到这个问题。知道我写好了程序,测试程序的时候发现了这样一个图。
![5d39833fe0de5c2c8225220a71d8f788.png](https://i-blog.csdnimg.cn/blog_migrate/43f768ca8d12ac998ac558dcc03b026b.jpeg)
我来解释下这个图,本来红线和蓝线是相连的线,他们下方的区域满足要求,而浅绿色线上方也是范围满足要求的点,但是他们之间公用一个点A,如下图所示。
![8d9d4e65d4bf6dfaf81f8015bc58699d.png](https://i-blog.csdnimg.cn/blog_migrate/3e7d2aabba033991c71ebeedcfd310a1.jpeg)
图中虚线部分本应该要被连出来,由于浅绿色线先征用了A点,然后把它当做已连过线的点去掉,当第二个区域的边界线运行到B点时没有A点可以与C点接上,B的四周没有符合要求的外点。程序自动认为连线已完毕,所以断开了
所以问题来了就要解决,比如下面一张图你该怎么连线?
![4346a06b723e07149c3d522ce476b7d9.png](https://i-blog.csdnimg.cn/blog_migrate/ca27aa96edbd4d6229076fafe6eae7c9.jpeg)
在我们眼中可以这样连线:
![b8595f175c8efe77685158a03d6a903a.png](https://i-blog.csdnimg.cn/blog_migrate/66375f2544b7ebf6353c0b4846fe27d9.jpeg)
但是同样程序不会这么连,因为他他只能从一个点到下一个点去分析不会有“大局观念”。根据高数里面的区域的定义,所以我们在来定一个新的规则:下一个要找的点必须与当前这个点所在的区域相同。也就是这样的:
![331bd8d725503b9da0f7efa062e381b0.png](https://i-blog.csdnimg.cn/blog_migrate/b7196d5d95335552723d42e1330f68e0.jpeg)
实际上这是3个区域,根据我们刚才的定义,从1号点开始有4和2可以选且都满足同时在C区域。因为它是初始点,所以随便选一个就是的,假设选4,到了4点,有7、5、3,三个点是外点,但是只有3号点符合要求,因此,选择3号点,到了3号点,有2和8符合要求,同理选2。以此类推。连出了上面的图,可以发现4号点连了3次,3和7连了两次,这证明一个点与与多少个边界相连就要被连多少次。这样,找出每一个外点所有的连通的区域个数,就是它要被连的次数,我们把这个次数存到一开始将的像素结构体flg变量中,flg总共有8位0000 0000,右边第一位是表示该像素是内点还是外点,如果是外点则为1,每有一个连通区域,flg向左移一位并且+1,比如4号点的像素flg是这样的0000 1111,每次被连一次线后向右一位,连完3次后就成了0000 0001,代表这个点虽然是外点,但是可连次数用完了。(但是说到这里,回到刚刚连接的3号点,有2和8符合要求,但是4点还有两次连接机会没用完,万一程序选择4点了怎么办,这个好说,判断一下所找的点是否与上一个点相同就可以了,如果相同则不选这个点连线。)
但是走到了这一步,还有个重大问题没有解决!就是怎么区分不同的区域。这个是最难的点。
相信大家玩过扫雷那游戏吧,如果点上了一个安全点,那么周围的安全点都会扫描出来。这是怎么实现的呢?我们找一个开始的内点作为起始点,扫描它四个方向上有没有内点,假设有3个,,如果有内点再将这些内点,的四周内点扫描一遍,假设有这4个点里每个点周围都存在3个内点,现在要搜查的点有3*3=9个一次类推。这是一个链式反应!
![f4f1138abd404c8294c1d896c88ea15a.png](https://i-blog.csdnimg.cn/blog_migrate/a6a058b99532902c17c1de07dee7030b.jpeg)
将所有扫描到的点,都定义一个区域,也就是一开始结构体的变量region默认为0。第一个区域所有点的region都为1,每新增一个区域使region加1,第二个区域所有内点的region都为2以此类推。
有人问到,链式反应程序咋写啊?这个一开始我也没想到,一开始想到的是想让系统生成多线程,每次寻找到符合要求的点多生成一个线程来实现。但是,后来才发现解决办法很简单,就是利用递归函数就可以了。如下图:
![6705fa72f2f99f4b119181f5a9241286.png](https://i-blog.csdnimg.cn/blog_migrate/14652bdf00b640f97076b64f0071e50b.png)
chain_react自己调用自己,就可以实现了!
最后就是南北极问题。如果我们采用经纬度坐标系做标尺,规定每0.1度为一个像素点,那么南北极肯定是一个点与3600个都相连的。所以南北极问题,要另外作讨论,具体问题,还得通过程序来分析,这里就先不做讲解。
![6be01b8cadab8e0bb6cfac44cdf9dc82.png](https://i-blog.csdnimg.cn/blog_migrate/693039eaccbd7f7e85299a8d6fac65bd.png)
整个程序流程大概是这样的,但是主要的是RangeCreate()函数也就是生成范围函数,将产生的范围string链表地址,送给Link_Range()函数,也就是连线函数。连线完成后在桌面生成谷歌地图可以识别的KML文件,最后两个函数是释放所有数据。
range_create()函数里功能是这样的(由于篇幅关系我只讲核心部分):
![6e6d51b443b6ae4c888f506163b80dec.png](https://i-blog.csdnimg.cn/blog_migrate/bdb73e6f4ec95c49de3b884072404527.png)
dpi是图像解析度,dpi越大像素点越多,图像精度越高。下图是dpi选择100和选择10 的区别(但是最好选择10以下,因为10对应的经纬度精度为0.1°整个地球就有3600*1800个点了,内存占用太高)。
![a8327baf6ebdc97dd6038cb41b3bc8f5.png](https://i-blog.csdnimg.cn/blog_migrate/506d07b5c7b0d458e891c4d3f712fd02.jpeg)
前面两个if函数是检验南北极点是否符合要求,如果符合要求就进行下面操作,赋予其flg为1(令它有外点的性质),且四周方向为NULL。
![f882989588aff90363f2d51a3f70ab57.png](https://i-blog.csdnimg.cn/blog_migrate/c6c6857094bfbf2de115ea2ed3c0a61f.png)
其中check_ok()函数可以自己定义,就是一个判断所输入的两个坐标值对应点是否满足要求。我写了下面这个函数,用来调试和检验我写的程序是否符合我的要求。
![199998c83a515377ae09a7e9839fb8ba.png](https://i-blog.csdnimg.cn/blog_migrate/71c375177c01c1ce27c1e673e7776dc5.png)
有兴趣的可以用横坐标代表i纵坐标代表j(为了程序计算方便我们将纬度值范围90到-90度转换到0-180度,经度东经0-180和西经0-180转换为0-360度,在最后用一个函数将i和j转换回来就行了),在坐标轴上脑补这个函数所代表的范围。在文末我会给出答案。
第一个for循环完成的工作是1.扫描所有非极点的区域,将满足要求的点存储下来。2.并且在扫描的同时将所有点之间的关系联系起来,比如A点如果在B点的东边,那么A点的东方向指针指向B,B点的东方向指针指向B。
如果想研究清楚其中的逻辑实现可以看下面代码。其中search_p()函数是寻找左右相邻的点的函数,如果该点旁边经线没有点则返回NULL存在点就返回那个点的地址。.next元素是当一条经线上两点C和D不相邻时,C.next指向D。如果C和D相邻那么C.S(C的南边)指向D,D.N(D的北边)指向C。
![918d5ed5f4517a4ab4c648f0053605c4.png](https://i-blog.csdnimg.cn/blog_migrate/8b85d19453df35452e3f9c51261fc53b.jpeg)
第二个循环干的事情是把所有内点和外点区分开,如果是外点就给它的flg标上1。
![0d085bd7ccf6ebc2ab68d7e9f3fbf4e0.png](https://i-blog.csdnimg.cn/blog_migrate/fc050be6d09d6705698926107a6f424f.png)
第三个循环干的事情是将所有的内点划分区域。其实现方法是如果程序扫描的时候发现了一个内点没有划分区域,让区域总数加1,给与这个内点相连通的点标记统一区域标示。比如当前已经找到2个区域了,又发现一个点A没有划分区域,那么把这个点A分到区域3中。其中chain_react()函数是把这新发现的点丢进链式反应中,保证与这个点连通的所有点都被同时划分区域,以免下次扫描再扫到与点A相同区域的内点,其函数在在前面讲到过。
![bb8c5c305dd4f5abadcab3bfc0ff1824.png](https://i-blog.csdnimg.cn/blog_migrate/bd3516b1dc9352bccb2fa22a9bd1cead.png)
第四个循环就是把所有的外点都赋予其连线使能,如果一个外点A与3个区域相连就代表它要被连3次,对它的flg左移三位并加上二进制数0111也就是7;
![0a9b538028d1a203b211c8b6d21750a1.png](https://i-blog.csdnimg.cn/blog_migrate/ef4a402f99e781eb2fb7e88e59383798.jpeg)
为什么我要这样写程序?请看这个图:
![04f0200336f2adc3ab500d1a5f3f7abe.png](https://i-blog.csdnimg.cn/blog_migrate/60cd2036c3160fad2d4691d99aad2a90.jpeg)
设这个点为M将这个点周围4个内点所在区域存到数字ABCD中,如果不存在就代表0。将这4个点两两比较,需要比较6次,记录相等次数,罗列了所有可能就是上图。(因为这个点是外点,最少有个方向是0)。而我算的周围所在区域数是加了区域0,但是区域0代表“空”区域,所以我们要减去1就是我们要对应的区域数。比如我们相互比较发现相等次数为1说明它周围存在3-1=2个不同区域。
到了这一步,range_create函数就完了,其返回值string是我们求的范围头地址。就剩一个连线Link_Range()程序了。先分析一下之前这个图。
![4346a06b723e07149c3d522ce476b7d9.png](https://i-blog.csdnimg.cn/blog_migrate/ca27aa96edbd4d6229076fafe6eae7c9.jpeg)
Link_Range()就是要做的工作是所给的检验check_ok函数如果是这样:
![642faf386bc00964f7e1c29955892575.png](https://i-blog.csdnimg.cn/blog_migrate/e91cc76ad33e143383a7681638ad0a79.png)
连线完成的效果图是:
![1abbb889ecceaa293c287a2c0cda7097.png](https://i-blog.csdnimg.cn/blog_migrate/e9757e6b174bb443ef801317fa10478e.jpeg)
而且还要考虑极区问题:当check_ok是这样的时,经度范围是0-50和90到270.
![eb340cc142dfbb2a27179d983cee4408.png](https://i-blog.csdnimg.cn/blog_migrate/ac135c21fb96462b9fcc7027e927900b.png)
极点连线效果是这样的
![d27075fe42bc340696c393518e3bf6c2.png](https://i-blog.csdnimg.cn/blog_migrate/03eaa44f90df3d33f254101d7421edc6.jpeg)
A和B是我后来加上去的。代表满足要求的2个区域,它们的公共点是北极点。
当我们给的check_ok函数是这样的时候
![e783a0555f9801a963b1316f095412d1.png](https://i-blog.csdnimg.cn/blog_migrate/b0b9d782a76604af70916f42157827e5.png)
这个跟上面的的有个区别就是上个函数i是>=0而这个函数是i>0将南北极点给去掉了,因为我们在range_creat()函数里检验南北极点是否符合要求的时候是用的0经度线作为输入值,当0度经线不满足要求的时候,那么南北极点同时不满足要求。南极的效果图如下:
![3a908c5c7a58e86478603edac3f450ac.png](https://i-blog.csdnimg.cn/blog_migrate/3d21aea52938218c408f30898c7919d5.jpeg)
因为南极点被排除了,范围线绕过了南极点。
Link_Range 主要代码如下图:
![4caa70fc19b4c0d5dacba3be3acf85e5.png](https://i-blog.csdnimg.cn/blog_migrate/674a5b89eaed0c1e1c8a1fb6482c254d.png)
它的核心部分就是那个for循环里,代码为(不理解可以跳过):
![e59d85762729eeb35c8529c3e6693349.png](https://i-blog.csdnimg.cn/blog_migrate/2b9d480bccc1a3018e9e23f252c806ae.jpeg)
要注意的几点是:
1. search_out()是寻找满足连线条件的第一个外点(也就是该点flg>1)。在每一次找到连线的开始点的时候,要验证它是否只与一个区域相连,如果这个点与多个区域相连,舍弃该点,寻找下一个满足要求的外点。并且明白这个线围的是哪个区域。
2.其中search_NeLi()是寻找下一个相邻点的函数,入口三个变量是:这个点的地址B和上个点的地址A和现在所连线的区域号X。为什么要把上一个点也考虑进来呢?因为怕找到的下一个相邻点C和上一个点A相同,如果相同舍弃该点寻找另外一个点。(前面已经讨论过了)为了不打断现在所讲,其中search_NeLi()函数实现在后面讲到。
3.需要判断该点与下一个点的连线是否越过极区,如果寻找的下一点C与当前点B的经度差别不是-1,0,1说明该点越过了极区,在连线时要这样连B——极点——C。
4.需要对线段进行首尾相接。如果首位两点经纬度差值都在1以内说明满足要求,可以连线相接。(由于先前定下的种种规则,一般程序不出错,首尾两点经纬度差值不可能大于1),如果首尾两都在极区另做考虑(太繁琐这里就不一一细讲了)。
下面直接将search_NeLi()函数内容贴出来:
![101b2e6073b5331f45092406817bed29.png](https://i-blog.csdnimg.cn/blog_migrate/285db4561fdaf72546f09995e3f81af1.jpeg)
其中前面4个if是判断该点A东西南北四个方向上是否存在,如果某个方向存在比如北点B,首先判断点B符合下一点要求不(范围与当前线包裹的范围相同,并且不和上一个连线点相同,并且有连线次数),其中check_SameRange()函数是判断该点B四周有没有与当前所包裹范围相同的内点,如果有返回1,否则返回0。如果B不满足要求依次判断B点西边点C和东边点D是否满足要求。最后如果北边这个点A左右两个点C和D也不满足要求那么接着扫描A点东边点、南边点、西边点其过程与B点类似。最后如果都不满足要求,那么还有一种可能是该点在极区附近。分别从这两个点东西边进行while()循环查找,如果找到一个满足要求的外点E那么返回该点E,说明这条线越过了极区。
讲到这里,程序的所有逻辑差不多都讲完了。还记得之前的那个我为了测试程序写的check_ok函数吗,在经过这个程序运行之后是神马样子?答案如下:
![18a376a368a58f036a471fad8fa6d31b.png](https://i-blog.csdnimg.cn/blog_migrate/02b80fd8839e3426426eecf0f23c39ce.jpeg)
最后我用这种方法把我毕业设计的卫星仰角检验函数放到check_ok中。它运算后的结果是这样的:
![e5f31a003b2c1c5fafd5db3ed8bb1e7c.png](https://i-blog.csdnimg.cn/blog_migrate/c23f43cc54ec92ba0ca92a4491aa5471.jpeg)
绿线代表24小时100%可以见到卫星的区域(卫星仰角要求高于0°)
蓝线代表24小时50%可以见到卫星的区域(卫星仰角要求高于45°)
白线是卫星运行轨迹图。
和一开始那个图比较:
![d341db4690a0e457a0dd4310b426bf06.png](https://i-blog.csdnimg.cn/blog_migrate/6b06dd5a9f8eb35a18f07721385ae024.jpeg)
就会发现比较规则的绿线两者没多大区别,但是不规则的蓝线两者差别有点大,但是图1明显更贴切实际(因为卫星轨道的对称性)。
如果解析一张图片的轮廓,只要在check_ok()函数里输入对应的函数就行了,比如想要找出图片中绿色区域的轮廓。只要判断其RGB值是否符合要求就行了。绿色RGB值是(0,255,0)。
但是我上面这个函数有个缺陷,坐标系不均匀,如果解析度是10也就是每个点间隔0.1°的话南北极点附近两像素点经度方向间距几乎为0,而赤道两像素点的距离是11km。这种不均匀明显对于赤道上的点不公平。那么怎么在球面上建立坐标系,使得所有的像素点间距都均匀了。可以参考下面的文章:
产生在球面上均匀分布的点_人工智能_diansai3900的博客-CSDN博客blog.csdn.net程序肯定能实现,但是我还没研究出来。。。我C语言不好,计算机二级都没过,为了毕业设计赶鸭子上架学的一点零碎的知识。可以发现,数学和编程逻辑上是相同的,只要一个问题找到它的数学规律,肯定能用程序实现吧。