【需求规格说明】
1、题目
求解球面距离:
1)地球的平均半径为6371千米,已知地球上两个城市A、B的经度和纬度,编程序求出这两个城市之间的球面距离(一般来说,球面上任意两点A和B都可以与球心确定唯一的大圆,而在大圆上连接这两点的较短的一条弧的长度就是球面距离)。要求:定义坐标点类,地球类。
2)读入武汉市某地区的坐标点数据,输入任意两个点的ID,能够快速计算两点之间的球面距离。
3)输出所求点之间的球面距离到文本文件中。
(注:这里的实验数据 用的是武汉市洪山区的POI数据,大概格式如下图所示,数据量 emm 大概14w+,不知道会不会涉及到什么比较严重的问题,所以 保守起见,我就不全都分享出来了哈哈~😜)
2、问题描述
本题要求我们求解两点之间的球面距离。
其中第一问要求我们并分别定义坐标点类和地球类用以计算;第二问要求我们读取文件中的信息,并可以通过输入任意两个点的ID实现计算;第三问要求我们把计算得出的信息输入到文本文件中。
3、问题分析
首先对于第一问,我们要定义一个坐标点类,该类应具备x,y两个数据成员变量,我们还要定义一个地球类,该类则是要实现计算两点之间的球面距离,所以该类中就应有计算球面距离的函数;接下来,我们要读取文件中的信息,这里我们就需要用“,”来区分两个数据,此外,文件中的数据我们读取出来都是都是字符串型,为了计算,我们则要把字符串型转换成浮点型,这些都要在主函数中实现;最后,第三问要求我们把计算出的信息输入到文本文件中,那么我们只需在工程文件夹中新建一个文本文件,然后再把计算出的数据输入其中即可。
【算法设计】
1、设计思想
首先第一问要求我们分别定义坐标点类Point和地球点类Earth用以计算。所以在类Point中我们要定义两个成员变量来表示坐标,为方便后续计算球面距离,我们要在Point类里把数据转化成弧度制;在类Earth中定义了三个成员变量,其中有两个point类的对象,和一个常成员变量r,即地球平均半径——6371千米,在类Earth中我们还需定义一个成员函数calc()用以计算球面距离。
其次第二问要求我们读取文件中的信息。所以在主函数里,我先定义了一个动态数组a,用来暂时存储文件中某一行的数据信息;接着我定义了Point类的对象,把数据存放在Point类型的静态数组里,但是由于静态数组存放在栈里容量有限,所以在定义时要new一下把数组存放在堆里;然后我们利用ifstream 来打开文件hubei_wuhan_hongshan_POIs.csv,打开文件后,我们要利用“,”来区分两个数据,这里我利用了(char*)str.c_str()把字符型数据转化成char*型,此外,我们读取到的数据都是字符型,而我们计算都需要用double型,所以我利用atof()函数把字符型数据转化成double型数据。
最后第三问实现计算并把结果输出到文本文件中,这里我们用了文件输出流ofstream,然后用了一个do while循环来判断你是否多次计算,在循环内,利用earth类的对象调用计算球面距离的函数calc()即可。
最后把new的point类型的数组delete掉,完毕。
2、设计表示
Ps:
1、getradx()和getrady()函数用以把数据转换成弧度制
2、calc()函数用以实现计算球面距离
主函数流程图如下:
【调试报告】
调试问题1
在完成第一问后,即定义完Point类和Earth类后,我在主函数中分别定义了两个类的对象进行了测试,但是测试结果和老师给的参考数据不一样,后来,经过我的调试以及和同学的讨论,我发现在类Point类中中我们有把数据进行弧度制转化,以至于在套入公式计算时导致计算错误。
调试问题2
在定义Point类型的数组时,我原本直接定义了一个静态数组,形如“ Point p[144593]”,但是在花老师的帮助下,我了解到,由于我们要定义的数据大小较大,而静态数组的存放在栈里,容量有限,所以就要在定义时要new一下,把数据存放在堆里,这样就避免了容量有限而导致内存泄漏的bug。
调试问题3
在语句char p = strtok_s((char)str.c_str(), “,”, &next_token);中,我运用了强制转化,不然运行时一直报错。因为函数str.c_str()是只是把字符型转化成常量char型,而我们要把字符型转化成char型,所以加一个强制转换,用(char*)str.c_str()把字符型数据转化成char*型。
调试问题4
针对语句data = ear.calc(poi[A-1], poi[B-1]);起初我编写的语句是data = ear.calc(poi[A], poi[B]);但是经过数据测试我发现,最后一行(144593行)无法与任意行进行计算,经过调试与思考,我找出了问题所在,由于我将数据储存在数组中,而文件中的第一行对应是是数组中的0,所以我要将数组元素元素减一,这样就可是实现根据文件中的信息进行计算。
【附录】
1、源代码
#include "stdafx.h"
#include<iostream>
#include<math.h>
#include<string>
#include<fstream>
#include<vector>
using namespace std;
double PI = 3.1415926;
class Point{
private:
double x;
double y;
public:
Point(){};
~Point(){};
Point(double x, double y) :x(x), y(y){};
double getradx(){ return abs(x / 180 * PI); };
double getrady(){ return abs(y / 180 * PI); };
};
class Earth{
public:
Earth(){};
~Earth(){};
Earth(Point p1, Point p2) :p1(p1), p2(p2){};
double calc(Point p1, Point p2);
private:
Point p1;
Point p2;
const double r = 6371000;
};
double Earth::calc(Point p1, Point p2){
double cosr;
double d;
cosr = cos(p1.getrady())*cos(p2.getrady())*cos(abs(p1.getradx() - p2.getradx())) + sin(p1.getrady())*sin(p2.getrady());
d = r*acos(cosr);
return d;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> a;
Point *poi = new Point[144593];
Earth ear;
string str;
ifstream myfile("hubei_wuhan_hongshan_POIs.csv");
char *next_token = NULL;
for (int i = 0; i < 144593; i++){
a.clear();
getline(myfile, str);
char *p = strtok_s((char*)str.c_str(), ",", &next_token);//(char*)str.c_str()把字符型数据转化成char*型 str.c_str()只有这个是把字符型转化成常量char*型
while (p != NULL)
{
a.push_back(p);
p = strtok_s(NULL, ",", &next_token);
}
poi[i] = Point(atof(a[0].c_str()), atof(a[1].c_str())); //atof()是C++中把字符型数据转化成double型数据的函数
}
myfile.close();
char ch = '0';
int A, B;
double data;
ofstream myfile1("myfile.txt");
do{
cout << "请输入需要计算的两个位置的行号(2-144593)" << endl;
cin >> A >> B;
data = ear.calc(poi[A-1], poi[B-1]);
cout <<"计算结果为:"<< data << endl;
myfile1 <<"第"<<A<<"行与第"<<B<<"行这两点之间的球面距离是:" << data <<"m"<< endl;
cout << "若继续计算,请输入1" << endl;
cin >> ch;
} while (ch == '1');
cout << "计算结束。" << endl;
myfile1.close();
delete[]poi;
return 0;
}
2、测试数据
测试了两组数据,分别为数据的第一行和最后一行(即文件中的第2行和144593行)(看文件是否能成功读到最后一行)以及4行和72这样任意的两行数据。
3、运行结果