kopia lustrzana https://github.com/Hamlib/Hamlib
451 wiersze
13 KiB
C
451 wiersze
13 KiB
C
/**
|
|
* \file src/locator.c
|
|
* \ingroup hamlib
|
|
* \brief locator and bearing conversion interface
|
|
* \author Stephane Fillod
|
|
* \date 2000-2002
|
|
*
|
|
* Hamlib Interface - locator and bearing conversion calls
|
|
*/
|
|
|
|
/*
|
|
* Hamlib Interface - locator and bearing conversion calls
|
|
* Copyright (c) 2001-2002 by Stephane Fillod
|
|
* Copyright (c) 2003 by Nate Bargmann
|
|
* Copyright (c) 2003 by Dave Hines
|
|
*
|
|
* $Id: locator.c,v 1.9 2003-08-21 20:22:06 n0nb Exp $
|
|
*
|
|
* Code to determine bearing and range was taken from the Great Circle,
|
|
* by S. R. Sampson, N5OWK.
|
|
* Ref: "Air Navigation", Air Force Manual 51-40, 1 February 1987
|
|
* Ref: "ARRL Satellite Experimenters Handbook", August 1990
|
|
*
|
|
* Code to calculate distance and azimuth between two Maidenhead locators,
|
|
* taken from wwl, by IK0ZSN Mirko Caserta.
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
/*! \page hamlib Hamlib general purpose API
|
|
*
|
|
* Here are grouped some often used functions, like locator conversion
|
|
* routines.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
|
|
#include <hamlib/rotator.h>
|
|
|
|
|
|
#ifndef DOC_HIDDEN
|
|
|
|
#define RADIAN (180.0 / M_PI)
|
|
|
|
/* arc length for 1 degree, 60 Nautical Miles */
|
|
#define ARC_IN_KM 111.2
|
|
|
|
/* The following is contributed by Dave Hines
|
|
*
|
|
* begin dph
|
|
*/
|
|
/*
|
|
* These are the constants used when converting between Maidenhead grid
|
|
* locators and longitude/latitude values. MAX_LOCATOR_PAIRS is the maximum
|
|
* number of locator character pairs to convert. This number MUST NOT exceed
|
|
* the number of pairs of values in range[] & weight[].
|
|
* Setting MAX_LOCATOR_PAIRS to 3 will convert the currently defined 6
|
|
* character locators. A value of 4 will convert the extended 8 character
|
|
* locators described in section 3L of "The IARU region 1 VHF managers
|
|
* handbook". Values of 5 and 6 will extent the format even more, to the
|
|
* longest definition I have seen for locators. Beware that there seems to be
|
|
* no universally accepted standard for 10 & 12 character locators.
|
|
* Note that the loc_char_weight values are in minutes of arc, to avoid
|
|
* constants which can't be represented precisely in either binary or decimal.
|
|
*
|
|
* MAX_LOCATOR_PAIRS now sets the limit locator2longlat() will convert and
|
|
* sets the maximum length longlat2locator() will generate. Each function
|
|
* properly handles any value from 1 to 6 so MAX_LOCATOR_PAIRS should be
|
|
* left at 6. MIN_LOCATOR_PAIRS sets a floor on the shortest locator that
|
|
* should be handled. -N0NB
|
|
*
|
|
*/
|
|
const static double loc_char_weight[] = { 600.0, 60.0, 2.5, 0.25, 0.01, 0.001 };
|
|
const static int loc_char_range[] = { 18, 10, 24, 10, 25, 10 };
|
|
#define MAX_LOCATOR_PAIRS 6
|
|
#define MIN_LOCATOR_PAIRS 1
|
|
|
|
/* end dph */
|
|
|
|
#endif /* !DOC_HIDDEN */
|
|
|
|
/**
|
|
* \brief Convert DMS angle to decimal representation
|
|
* \param degrees Degrees
|
|
* \param minutes Minutes
|
|
* \param seconds Seconds
|
|
*
|
|
* Convert degree/minute/second angle to a decimal representation.
|
|
* Degrees >360, minutes > 60, and seconds > 60 are allowed, but
|
|
* resulting angle won't be normalized.
|
|
*
|
|
* \return the decimal representation.
|
|
*
|
|
* \sa dec2dms()
|
|
*/
|
|
double dms2dec(int degrees, int minutes, double seconds) {
|
|
if (degrees >= 0)
|
|
return (double)degrees + (double)minutes/60. + seconds/3600.;
|
|
else
|
|
return (double)degrees - (double)minutes/60. - seconds/3600.;
|
|
}
|
|
|
|
/**
|
|
* \brief Convert decimal angle into DMS representation
|
|
* \param dec Decimal angle
|
|
* \param degrees The location where to store the degrees
|
|
* \param minutes The location where to store the minutes
|
|
* \param seconds The location where to store the seconds
|
|
*
|
|
* Convert decimal angle into its degree/minute/second representation.
|
|
*
|
|
* When passed a value < -180 or > 180, the sign will be reversed
|
|
* and the value constrained to => -180 and <= 180 before conversion.
|
|
*
|
|
* Upon return dec2dms guarantees -180<=degrees<180,
|
|
* 0<=minutes<60, and 0<=seconds<60.
|
|
*
|
|
* \sa dms2dec()
|
|
*/
|
|
void dec2dms(double dec, int *degrees, int *minutes, double *seconds) {
|
|
int deg, min, is_neg = 0;
|
|
double st;
|
|
|
|
if (!degrees || !minutes || !seconds)
|
|
return;
|
|
|
|
/* reverse the sign if dec has a magnitude greater
|
|
* than 180 and factor out multiples of 360.
|
|
* e.g. when passed 270 st will be set to -90
|
|
* and when passed -270 st will be set to 90. If
|
|
* passed 361 st will be set to -1, etc. If passed
|
|
* a value > -180 || < 180, value will be unchanged.
|
|
*/
|
|
if (dec >= 0.0)
|
|
st = fmod(dec + 180, 360) - 180;
|
|
else
|
|
st = fmod(dec - 180, 360) + 180;
|
|
|
|
/* if after all of that st is negative, we want deg
|
|
* to be negative as well. Treat -180 as a special
|
|
* case, not returning its sign so longitudes will
|
|
* be returned from -179.999 to 180.0.
|
|
*/
|
|
if (st < 0.0 && st != -180.)
|
|
is_neg = 1;
|
|
|
|
/* work on st as a positive value to remove a
|
|
* bug introduced by the effect of floor() when
|
|
* passed a negative value. e.g. when passed
|
|
* -96.8333 floor() returns -95! Also avoids
|
|
* a rounding error introduced on negative values.
|
|
*/
|
|
st = fabs(st);
|
|
|
|
deg = (int)floor(st);
|
|
st = 60. * (st-(double)deg);
|
|
min = (int)floor(st);
|
|
st = 60. * (st-(double)min);
|
|
|
|
/* set *degrees to original sign passed to dec */
|
|
(is_neg == 1) ? (*degrees = deg * -1) : (*degrees = deg);
|
|
|
|
*minutes = min;
|
|
*seconds = st;
|
|
}
|
|
|
|
/**
|
|
* \brief Convert Maidenhead grid locator to longitude/latitude
|
|
* \param longitude The location where to store longitude, decimal
|
|
* \param latitude The location where to store latitude, decimal
|
|
* \param locator The locator--2 through 12 char nul terminated string
|
|
*
|
|
* Convert Maidenhead grid locator to longitude/latitude (decimal).
|
|
* The locator should be in 2 through 12 chars long format. locator2longlat
|
|
* is case insensitive, however it checks for locator validity.
|
|
*
|
|
* Decimal long/lat is computed to center of grid square, i.e. given
|
|
* EM19 will return coordinates equivalent to the southwest corner
|
|
* of EM19mm. Given a six character locator, computed values will
|
|
* in the center of the given subsquare, i.e. 2' 30" from west boundary
|
|
* and 1' 15" from south boundary.
|
|
*
|
|
* \return RIG_OK to indicate conversion went ok, -RIG_EINVAL if locator
|
|
* exceeds RR99xx or exceeds length limit. Currently 1 to 6 lon/lat pairs.
|
|
*
|
|
* \sa longlat2locator()
|
|
*/
|
|
|
|
/* begin dph */
|
|
|
|
int locator2longlat(double *longitude, double *latitude, const char *locator) {
|
|
int x_or_y, paircount;
|
|
int locvalue, pair;
|
|
double xy[2], minutes;
|
|
|
|
paircount = strlen(locator) / 2;
|
|
|
|
if (paircount > MAX_LOCATOR_PAIRS) /* Max. locator length to allow */
|
|
paircount = MAX_LOCATOR_PAIRS;
|
|
else if (paircount < MIN_LOCATOR_PAIRS)
|
|
return -RIG_EINVAL;
|
|
|
|
for (x_or_y = 0; x_or_y < 2; ++x_or_y) { /* For x(=long) and y(=lat) */
|
|
minutes = 0.0;
|
|
|
|
for (pair = 0; pair < paircount; ++pair) {
|
|
locvalue = locator[pair*2 + x_or_y];
|
|
|
|
locvalue -= (loc_char_range[pair] == 10) ? '0' : /* Value of digit */
|
|
(isupper(locvalue)) ? 'A' : 'a'; /* or letter. */
|
|
|
|
if (((unsigned) locvalue) >= loc_char_range[pair]) /* Check range */
|
|
return -RIG_EINVAL; /* Non-letter/digit or out of range */
|
|
|
|
minutes += locvalue * loc_char_weight[pair];
|
|
}
|
|
minutes += loc_char_weight[paircount - 1] / 2.0; /* Center coordinate */
|
|
|
|
xy[x_or_y] = minutes / 60.0 - 90.0;
|
|
}
|
|
|
|
/* Don't seg. fault if longitude or latitude pointers are null */
|
|
if (longitude != NULL) *longitude = xy[0] * 2;
|
|
if (latitude != NULL) *latitude = xy[1];
|
|
|
|
return RIG_OK;
|
|
}
|
|
/* end dph */
|
|
|
|
/**
|
|
* \brief Convert longitude/latitude to Maidenhead grid locator
|
|
* \param longitude The longitude, decimal
|
|
* \param latitude The latitude, decimal
|
|
* \param locator The location where to store the locator
|
|
* \param pair_count The desired precision expressed as lon/lat pairs in the locator
|
|
*
|
|
* Convert longitude/latitude (decimal) to Maidenhead grid locator.
|
|
* \a locator must point to an array at least pair_count * 2 char plus '\0'.
|
|
*
|
|
* \return RIG_OK if locator was successfully computed. -RIG_EINVAL if
|
|
* pair_count exceeds length limit. Currently 1 to 6 lon/lat pairs.
|
|
*
|
|
* \sa locator2longlat()
|
|
*/
|
|
|
|
/* begin dph */
|
|
|
|
int longlat2locator(double longitude, double latitude,
|
|
char *locator, int pair_count) {
|
|
int x_or_y, pair, locvalue;
|
|
double tmp;
|
|
|
|
if (pair_count < MIN_LOCATOR_PAIRS || pair_count > MAX_LOCATOR_PAIRS)
|
|
return -RIG_EINVAL;
|
|
|
|
for (x_or_y = 0; x_or_y < 2; ++x_or_y) {
|
|
tmp = ((x_or_y == 0) ? longitude / 2. : latitude);
|
|
|
|
/* The 1e-6 here guards against floating point rounding errors */
|
|
tmp = fmod(tmp + 270., 180.) * 60. + 1e-6;
|
|
for (pair = 0; pair < pair_count; ++pair) {
|
|
locvalue = (int) (tmp / loc_char_weight[pair]);
|
|
|
|
/* assert(locvalue < loc_char_range[pair]); */
|
|
tmp -= loc_char_weight[pair] * locvalue;
|
|
locvalue += (loc_char_range[pair] == 10) ? '0':'A';
|
|
locator[pair * 2 + x_or_y] = locvalue;
|
|
}
|
|
}
|
|
locator[pair_count * 2] = '\0';
|
|
|
|
return RIG_OK;
|
|
}
|
|
|
|
/* end dph */
|
|
|
|
/**
|
|
* \brief Calculate the distance and bearing between two points.
|
|
* \param lon1 The local longitude, decimal degrees
|
|
* \param lat1 The local latitude, decimal degrees
|
|
* \param lon2 The remote longitude, decimal degrees
|
|
* \param lat2 The remote latitude, decimal degrees
|
|
* \param distance The location where to store the distance
|
|
* \param azimuth The location where to store the bearing
|
|
*
|
|
* Calculate the QRB between \a lat1,\a lat1 and \a lon2,\a lat2.
|
|
*
|
|
* This version also takes into consideration the two points
|
|
* being close enough to be in the near-field, and the antipodal points,
|
|
* which are easily calculated.
|
|
*
|
|
* \return the distance in kilometers and azimuth in decimal degrees
|
|
* for the short path.
|
|
*
|
|
* \sa distance_long_path(), azimuth_long_path()
|
|
*/
|
|
int qrb(double lon1, double lat1, double lon2, double lat2,
|
|
double *distance, double *azimuth) {
|
|
double delta_long, tmp, arc, cosaz, az;
|
|
|
|
if (!distance || !azimuth)
|
|
return -1;
|
|
|
|
if ((lat1 > 90.0 || lat1 < -90.0) || (lat2 > 90.0 || lat2 < -90.0))
|
|
return -1;
|
|
|
|
if ((lon1 > 180.0 || lon1 < -180.0) || (lon2 > 180.0 || lon2 < -180.0))
|
|
return -1;
|
|
|
|
/* Prevent ACOS() Domain Error */
|
|
|
|
if (lat1 == 90.0)
|
|
lat1 = 89.99;
|
|
else if (lat1 == -90.0)
|
|
lat1 = -89.99;
|
|
|
|
if (lat2 == 90.0)
|
|
lat2 = 89.99;
|
|
else if (lat2 == -90.0)
|
|
lat2 = -89.99;
|
|
|
|
/*
|
|
* Convert variables to Radians
|
|
*/
|
|
lat1 /= RADIAN;
|
|
lon1 /= RADIAN;
|
|
lat2 /= RADIAN;
|
|
lon2 /= RADIAN;
|
|
|
|
delta_long = lon2 - lon1;
|
|
|
|
tmp = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(delta_long);
|
|
|
|
if (tmp > .999999) {
|
|
/* Station points coincide, use an Omni! */
|
|
*distance = 0.0;
|
|
*azimuth = 0.0;
|
|
return 0;
|
|
}
|
|
|
|
if (tmp < -.999999) {
|
|
/*
|
|
* points are antipodal, it's straight down.
|
|
* Station is equal distance in all Azimuths.
|
|
* So take 180 Degrees of arc times 60 nm,
|
|
* and you get 10800 nm, or whatever units...
|
|
*/
|
|
|
|
*distance = 180.0 * ARC_IN_KM;
|
|
*azimuth = 0.0;
|
|
return 0;
|
|
}
|
|
|
|
arc = acos(tmp);
|
|
|
|
/*
|
|
* One degree of arc is 60 Nautical miles
|
|
* at the surface of the earth, 111.2 km, or 69.1 sm
|
|
* This method is easier than the one in the handbook
|
|
*/
|
|
|
|
/* Short Path */
|
|
|
|
*distance = ARC_IN_KM * RADIAN * arc;
|
|
|
|
/*
|
|
* Long Path
|
|
*
|
|
* distlp = (ARC_IN_KM * 360.0) - distsp;
|
|
*/
|
|
|
|
cosaz = (sin(lat2) - (sin(lat1) * cos(arc))) /
|
|
(sin(arc) * cos(lat1));
|
|
|
|
if (cosaz > .999999)
|
|
az = 0.0;
|
|
else if (cosaz < -.999999)
|
|
az = 180.0;
|
|
else
|
|
az = acos(cosaz) * RADIAN;
|
|
|
|
/*
|
|
* Handbook had the test ">= 0.0" which looks backwards??
|
|
* must've been frontwards since the numbers seem to make sense
|
|
* now. ;-) -N0NB
|
|
*/
|
|
|
|
// if (sin(delta_long) < 0.0) {
|
|
if (sin(delta_long) >= 0.0) {
|
|
*azimuth = az;
|
|
} else {
|
|
*azimuth = 360.0 - az;
|
|
}
|
|
|
|
if (*azimuth == 360.0)
|
|
*azimuth = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculate the long path distance between two points.
|
|
* \param distance The distance
|
|
*
|
|
* Calculate the long path (resp. short path) of a given distance.
|
|
*
|
|
* \return the distance in kilometers for the opposite path.
|
|
*
|
|
* \sa qrb()
|
|
*/
|
|
double distance_long_path(double distance) {
|
|
return (ARC_IN_KM * 360.0) - distance;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculate the long path bearing between two points.
|
|
* \param azimuth The bearing
|
|
*
|
|
* Calculate the long path (resp. short path) of a given bearing.
|
|
*
|
|
* \return the azimuth in decimal degrees for the opposite path.
|
|
*
|
|
* \sa qrb()
|
|
*/
|
|
double azimuth_long_path(double azimuth) {
|
|
return 360.0 - azimuth;
|
|
}
|