1 /*
2  * Copyright (C) 2008 Fabien Chereau
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
17  */
18 
19 #include "StelLocation.hpp"
20 #include "StelLocationMgr.hpp"
21 #include "StelLocaleMgr.hpp"
22 #include "StelUtils.hpp"
23 #include "StelModuleMgr.hpp"
24 #include "StelApp.hpp"
25 #include "Planet.hpp"
26 #include "SolarSystem.hpp"
27 #include <QTimeZone>
28 #include <QStringList>
29 
30 const int StelLocation::DEFAULT_BORTLE_SCALE_INDEX = 2;
31 
32 int StelLocation::metaTypeId = initMetaType();
initMetaType()33 int StelLocation::initMetaType()
34 {
35 	int id = qRegisterMetaType<StelLocation>();
36 	qRegisterMetaTypeStreamOperators<StelLocation>();
37 	return id;
38 }
39 
40 // Output the location as a string ready to be stored in the user_location file
serializeToLine() const41 QString StelLocation::serializeToLine() const
42 {
43 	QString sanitizedTZ=StelLocationMgr::sanitizeTimezoneStringForLocationDB(ianaTimeZone);
44 	return QString("%1\t%2\t%3\t%4\t%5\t%6\t%7\t%8\t%9\t%10\t%11\t%12")
45 			.arg(name)
46 			.arg(state)
47 			.arg(region)
48 			.arg(role)
49 			.arg(population/1000)
50 			.arg(latitude<0 ? QString("%1S").arg(-latitude, 0, 'f', 6) : QString("%1N").arg(latitude, 0, 'f', 6))
51 			.arg(longitude<0 ? QString("%1W").arg(-longitude, 0, 'f', 6) : QString("%1E").arg(longitude, 0, 'f', 6))
52 			.arg(altitude)
53 			.arg(bortleScaleIndex)
54 			.arg(sanitizedTZ)
55 			.arg(planetName)
56 			.arg(landscapeKey);
57 }
58 
getID() const59 QString StelLocation::getID() const
60 {
61 	if (name.isEmpty())
62 		return QString("%1, %2").arg(latitude).arg(longitude);
63 
64 	if (!region.isEmpty())
65 		return QString("%1, %2").arg(name).arg(region);
66 	else
67 		return name;
68 }
69 
70 // GZ TODO: These operators may require sanitizing for timezone names!
operator <<(QDataStream & out,const StelLocation & loc)71 QDataStream& operator<<(QDataStream& out, const StelLocation& loc)
72 {
73 	out << loc.name << loc.state << loc.region << loc.role << loc.population << loc.latitude << loc.longitude << loc.altitude << loc.bortleScaleIndex << loc.ianaTimeZone << loc.planetName << loc.landscapeKey << loc.isUserLocation;
74 	return out;
75 }
76 
operator >>(QDataStream & in,StelLocation & loc)77 QDataStream& operator>>(QDataStream& in, StelLocation& loc)
78 {
79 	in >> loc.name >> loc.state >> loc.region >> loc.role >> loc.population >> loc.latitude >> loc.longitude >> loc.altitude >> loc.bortleScaleIndex >> loc.ianaTimeZone >> loc.planetName >> loc.landscapeKey >> loc.isUserLocation;
80 	return in;
81 }
82 
getRegionFromCode(const QString & code)83 QString StelLocation::getRegionFromCode(const QString &code)
84 {
85 	QString region;
86 	if (code.toInt()>0) // OK, this is code of geographical region
87 		region = StelLocationMgr::pickRegionFromCode(code.toInt());
88 	else
89 	{
90 		if (code.size() == 2) // The string equals to 2 chars - this is the ISO 3166-1 alpha 2 code for country
91 			region = StelLocationMgr::pickRegionFromCountryCode(code);
92 		else
93 			region = StelLocationMgr::pickRegionFromCountry(code); // This is the English name of country
94 	}
95 	if (region.isEmpty())
96 		region = code; // OK, this is just region
97 
98 	return region;
99 }
100 
101 // Parse a location from a line serialization
createFromLine(const QString & rawline)102 StelLocation StelLocation::createFromLine(const QString& rawline)
103 {
104 	StelLocation loc;
105 	const QStringList& splitline = rawline.split("\t");
106 	loc.name    = splitline.at(0).trimmed();
107 	loc.state   = splitline.at(1).trimmed();
108 	loc.region = getRegionFromCode(splitline.at(2).trimmed());
109 	loc.role    = splitline.at(3).at(0).toUpper();
110 	if (loc.role.isNull())
111 		loc.role = QChar(0x0058); // char 'X'
112 	loc.population = static_cast<int> (splitline.at(4).toFloat()*1000);
113 
114 	const QString& latstring = splitline.at(5).trimmed();
115 	loc.latitude = latstring.left(latstring.size() - 1).toFloat();
116 	if (latstring.endsWith('S'))
117 		loc.latitude=-loc.latitude;
118 
119 	const QString& lngstring = splitline.at(6).trimmed();
120 	loc.longitude = lngstring.left(lngstring.size() - 1).toFloat();
121 	if (lngstring.endsWith('W'))
122 		loc.longitude=-loc.longitude;
123 
124 	loc.altitude = static_cast<int>(splitline.at(7).toFloat());
125 
126 	if (splitline.size()>8)
127 	{
128 		bool ok;
129 		loc.bortleScaleIndex = splitline.at(8).toInt(&ok);
130 		if (ok==false)
131 			loc.bortleScaleIndex = DEFAULT_BORTLE_SCALE_INDEX;
132 	}
133 	else
134 		loc.bortleScaleIndex = DEFAULT_BORTLE_SCALE_INDEX;
135 
136 	if (splitline.size()>9)
137 	{
138 		// Parse time zone
139 		loc.ianaTimeZone = splitline.at(9).trimmed();
140 		// GZ Check whether the timezone ID is available in the current Qt/IANA list?
141 		if ( ! QTimeZone::isTimeZoneIdAvailable(loc.ianaTimeZone.toUtf8()))
142 		{
143 			// Try to find a currently used IANA string from our known replacements.
144 			QString fitName=StelLocationMgr::sanitizeTimezoneStringFromLocationDB(loc.ianaTimeZone);
145 			qDebug() << "StelLocation::createFromLine(): TimeZone name for " << loc.name << " not found. Translating" << loc.ianaTimeZone << " to " << fitName;
146 			loc.ianaTimeZone=fitName;
147 		}
148 	}
149 
150 	// Parse planet name
151 	if (splitline.size()>10)
152 		loc.planetName = splitline.at(10).trimmed();
153 	else
154 		loc.planetName = "Earth"; // Earth by default
155 
156 	// Parse optional associated landscape key
157 	if (splitline.size()>11)
158 		loc.landscapeKey = splitline.at(11).trimmed();
159 
160 	return loc;
161 }
162 
163 // Compute great-circle distance between two locations
distanceDegrees(const float long1,const float lat1,const float long2,const float lat2)164 float StelLocation::distanceDegrees(const float long1, const float lat1, const float long2, const float lat2)
165 {
166 	static const float DEGREES=M_PIf/180.0f;
167 	return std::acos( std::sin(lat1*DEGREES)*std::sin(lat2*DEGREES) +
168 			  std::cos(lat1*DEGREES)*std::cos(lat2*DEGREES) *
169 			  std::cos((long1-long2)*DEGREES) ) / DEGREES;
170 }
distanceKm(Planet * planet,const double long1,const double lat1,const double long2,const double lat2)171 double StelLocation::distanceKm(Planet *planet, const double long1, const double lat1, const double long2, const double lat2)
172 {
173 	static const double DEGREES=M_PI/180.0;
174 	const double f = 1.0 - planet->getOneMinusOblateness(); // flattening
175 	const double a = planet->getEquatorialRadius()*AU;
176 
177 	const double F = (lat1+lat2)*0.5*DEGREES;
178 	const double G = (lat1-lat2)*0.5*DEGREES;
179 	const double L = (long1-long2)*0.5*DEGREES;
180 
181 	const double sinG=sin(G), cosG=cos(G), sinF=sin(F), cosF=cos(F), sinL=sin(L), cosL=cos(L);
182 	const double S  = sinG*sinG*cosL*cosL+cosF*cosF*sinL*sinL;
183 	const double C  = cosG*cosG*cosL*cosL+sinF*sinF*sinL*sinL;
184 	const double om = atan(sqrt(S/C));
185 	const double R  = sqrt(S*C)/om;
186 	const double D  = 2.*om*a;
187 	const double H1 = (3.0*R-1.0)/(2.0*C);
188 	const double H2 = (3.0*R+1.0)/(2.0*S);
189 	return D*(1.0+f*(H1*sinF*sinF*cosG*cosG-H2*cosF*cosF*sinG*sinG));
190 }
distanceKm(const double otherLong,const double otherLat) const191 double StelLocation::distanceKm(const double otherLong, const double otherLat) const
192 {
193 	PlanetP planet=GETSTELMODULE(SolarSystem)->searchByEnglishName(planetName);
194 	return distanceKm(planet.data(), static_cast<double>(longitude), static_cast<double>(latitude), otherLong, otherLat);
195 }
196 
getAzimuthForLocation(double longObs,double latObs,double longTarget,double latTarget)197 double StelLocation::getAzimuthForLocation(double longObs, double latObs, double longTarget, double latTarget)
198 {
199 	longObs    *= M_PI_180;
200 	latObs     *= M_PI_180;
201 	longTarget *= M_PI_180;
202 	latTarget  *= M_PI_180;
203 
204 	double az = atan2(sin(longTarget-longObs), cos(latObs)*tan(latTarget)-sin(latObs)*cos(longTarget-longObs));
205 	if (StelApp::getInstance().getFlagSouthAzimuthUsage())
206 		az += M_PI;
207 	return StelUtils::fmodpos(M_180_PI * az, 360.0);
208 }
209 
getAzimuthForLocation(double longTarget,double latTarget) const210 double StelLocation::getAzimuthForLocation(double longTarget, double latTarget) const
211 {
212 	return getAzimuthForLocation(static_cast<double>(longitude), static_cast<double>(latitude), longTarget, latTarget);
213 }
214