土木測量プログラムの開発 (角度の変換と入出力)

2017/03/25


角度の取り扱いと度分秒への変換

関数電卓では角度を DEG、RAD、GRAD で指定できますが、アドインプログラムではラジアンが基本になります。 関数電卓にかかわらずエクセル等でも角度の取り扱いは基本的にラジアンです。

内部ではラジアンの値を保存しますが、実作業では度分秒(DMS)で角度を扱うので、入出力は度分秒(DMS)に変換します。

初めにラジアンの数値を度分秒(DMS)の数値に変換する関数と、度分秒(DMS)の数値をラジアンに変換する関数を作成します。 三角関数を使用するので <math.h> をインクルードします。

もっと簡潔な計算方法があると思うんだけど、手間だけど解りやすい計算式を使用しています。

#include <math.h>
// ラジアンの値を度分秒に変換
double Rad2Dms(double a) {
	double d, m, s;
	double pi = atan(1) * 4;  // 円周率
	double f = a * 180 / pi;  // ラジアンの値を度に変換

// 角度を 0 から 360以内の数値に変換
// 解りやすいのでループで処理
	while(f < 0) f += 360;
	while(f > 360) f -= 360;

	d = floor(f);
	f = (f - d) * 60;
	m = floor(f);
	s = (f - m) * 60;
	f = d + (m / 100) + (s / 10000);

	return f;
}

// 度分秒の値をラジアンに変換
double Dms2Rad(double a) {
	double d, m, s;
	double pi = atan(1) * 4;  // 円周率
	double f = fabs(a);

	while(f < 0) f += 360;

	d = floor(f);
	f = (f - d) * 100;
	m = floor(f);
	s = (f - m) * 100;
	f = d + (f / 60) + (s / 3600);

	if(a < 0) f *= -1;
	while(f > 360) f -= 360;

	f = a * pi / 180;

	return f;
}

角度の表示用文字列

計算結果の角度を表示する為に、ラジアンの値を度分秒の文字列にします。

文字列を戻り値にしたい場合はポインタを使用するのですが、あまり詳しいことはわからないので突っ込まないでください。

なお角度を度分秒の形で表すのは 〇〇゚〇〇'〇〇.〇〇" といった形をとるのですが、ここでは自分の好みで 〇〇-〇〇-〇〇.〇〇 と表示するようにします。

Dmstext(double a, char *buf) {
	int d, m;
	double f, s;
	char txt[22];

	f = Rad2Dms(f);
	d = floor(f);
	f = (f - d) * 100;
	m = floor(f);
	s = (f - m) * 100;

	sprintf(txt, "%d-%02d-%05.2f", d, m, s);  // 文字列に変換
	sprintf(buf, "%12s", txt);

	return;
}


角度入力用の関数

角度の入力は、以前作成した数値入力関数を修正し角度の入力に対応させます。

なお、数値入力関数では GetOneKey 関数を使用するので、追加しておいてください。

double InputVal(int y, double d, char type) {  // 引数 type を追加
	double ret;
	char txt[22];
	char fmt[8];
	char pos, c;
	int x;
	double min ,max;
	char flg_minus = 1;
	char flg_dot = 0;
	char len, flen;

// 初期値をタイプ別に設定
	switch (type) {
		case 1:  // 角度入力用(1)
			x = 9;
			min = -9999; // 0~360 でもいいが、マイナスで指定したいときもある為。
			max = 9999;
			flg_dot = 0;  // ドット使用可。
			len = 13;
			flen = 7;
			d = Rad2Dms(d);  // ラジアンをDMSに変換
			break
		default:
			x = 8;
			min = -9999.9999;
			max = 99999.9999;
			flg_dot = 0;
			len = 14;
			flen = 5;
			break;
	}
	FuncKey = 0;
	ret = d;
	if (min < 0) flg_minus = 0;

START:
	sprintf(fmt, "%s%df_", "%-.", flen);
	sprintf(txt, fmt, ret);
	pos = strlen(txt) - 2;
	txt[pos] = '_';
	txt[pos + 1] = 0;

	sprintf(fmt, "%s%ds", "%-", len);
	sprintf(txt, fmt, txt);
	locate(x, y);
	Print((unsigned char*)txt);

	c = GetOneKey();
	switch (c) {
		case 'E':
			goto END;
			break;
		case 'D':
			pos--;
			txt[pos] = '_';
			txt[pos + 1] = 0;
			break;
		case 'a': // F1キー
		case 'b': // F2キー
		case 'd': // F4キー
		case 'f': // F6キー
			FuncKey = c - 0x60;
			return ret;
			break;
		case 'U':
		case 'W':
			goto START;
			break;
		case '-': // マイナスキーの処理を追加
			if (flg_minus) goto START;
			txt[0] = c;
			txt[1] = '_';
			txt[2] = 0;
			pos = 1;
			break;
		case '.': // ドット入力の処理を追加
			if (flg_dot) goto START;
			txt[0] = c;
			txt[1] = '_';
			txt[2] = 0;
			pos = 1;
			break;
		default:
			txt[0] = c;
			txt[1] = '_';
			txt[2] = 0;
			pos = 1;
			break;
	}
	sprintf(txt, fmt, txt); // 書式文字列を変数化
	locate(x, y); // 横位置を変数で指定
	Print((unsigned char*)txt);

	while(1) {
		c = GetOneKey();
		switch (c) {
			case 'E':
				txt[pos] = 0;
				ret = atof(txt);
				goto END;
				break;
			case 'C':
				goto START;
				break;
			case 'D':
				if (pos == 0 ) {
					goto START;
				} else {
					pos--;
					txt[pos] = '_';
					txt[pos + 1] = 0;
				}
				break;
			case 'a':
			case 'b':
			case 'd':
			case 'f':
				FuncKey = c - 0x60;
				return ret;
				break;
			case '.':
				if (flg_dot) break;

				if (strstr(txt, ".") == 0) {
					txt[pos] = c;
					pos++;
					txt[pos] = '_';
					txt[pos + 1] = 0;
				}
				break;
			case 'U':
			case 'W':
			case '-':
				break;
			default:
				txt[pos] = c;
				pos++;
				txt[pos] = '_';
				txt[pos + 1] = 0;
				break;
		}

		if (pos > (len - 1)) pos = len - 1;

		sprintf(txt, fmt, txt);
		locate(x, y);
		Print((unsigned char*)txt);
	}

END:
	if (ret < min || ret > max) goto START;

// タイプ別出力
	switch (type) {
		case 1:
			ret = Rad2Dms(ret); // ラジアンに変換
			Dmstext(ret, &txt); // 度分秒の文字に変換
			break;
		default:
			if (ret < 0) {
				sprintf(fmt, "%s%d%s%df", "%", len, ".", flen);
				sprintf(txt, fmt, ret);
			} else {
				sprintf(fmt, "%s%d%s%df", "%", len + 1, ".", flen);
				sprintf(txt, fmt, ret);
			}
			break;
	}

	locate(x, y);
	Print((unsigned char*)txt);

	return ret;
}

動作検証用サンプル。

//****************************************************************************
int AddIn_main(int isAppli, unsigned short OptionNum) {
	unsigned int key;
	char c;
	double d = 12.345678;

	Bdisp_AllClr_DDVRAM();

	locate(3,2);
	Print((unsigned char*)"Input Angle.");

	while(1) {
		d = InputVal(3, d, 1);
		c = GetOneKey();
	}
	return 1;
}

おかしな表示

例えば、角度を -10度 30分と入力すると 349度 30分と表示されますが、 -370度 30分と入力すると 349度 29分 60秒と表示されます。 これは、コンピュータ内部の誤差なので数値を丸めて表示するようにします。

あくまでも表示上の四捨五入なので保存されている変数の値は誤差を保持したままです。

度分秒の表示に使用している、Dmstext 関数を変更します。

// 度分秒文字列 **************************
void Dmstext(double a, char *buf, char type) {
    int d, m;
    double f, s;
    char txt[22];

    switch (type) {
        case 1:
            f = a;
            while(f < 0) f += 360;
            while(f >= 360) f -= 360;
            break;
        default:
            f = Rad2Dms(a);
            break;
    }
    d = floor(f);
    f = (f - d) * 100;
    m = floor(f);
    s = (f - m) * 100;
    if (floor(s * 100 + 0.5) == 6000) {  // 四捨五入
        s = 0;
        m++;
        if (m == 60) {  // 四捨五入の結果 60分になった時の処理
            m = 0;
            d++;
            if (d == 360) d = 0;
        } 
    }

    sprintf(txt, "%d-%02d-%05.2f", d, m, s);
    sprintf(buf, "%12s", txt);

    return;
}