1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2011 Guillaume Martres <smarter@ubuntu.com>
4 //
5
6 #include "SatellitesModel.h"
7
8 #include "MarbleDebug.h"
9 #include "MarbleDirs.h"
10 #include "SatellitesMSCItem.h"
11 #include "SatellitesTLEItem.h"
12
13 #include "MarbleClock.h"
14 #include "MarbleColors.h"
15 #include "GeoDataPlacemark.h"
16 #include "GeoDataStyle.h"
17 #include "GeoDataIconStyle.h"
18 #include "GeoDataLabelStyle.h"
19 #include "GeoDataLineStyle.h"
20
21 #include <planetarySats.h>
22 #include <sgp4io.h>
23
24 #include <clocale>
25
26 namespace Marble {
27
SatellitesModel(GeoDataTreeModel * treeModel,const MarbleClock * clock)28 SatellitesModel::SatellitesModel( GeoDataTreeModel *treeModel,
29 const MarbleClock *clock )
30 : TrackerPluginModel( treeModel ),
31 m_clock( clock ),
32 m_currentColorIndex( 0 )
33 {
34 setupColors();
35 connect(m_clock, SIGNAL(timeChanged()), this, SLOT(update()));
36 }
37
setupColors()38 void SatellitesModel::setupColors()
39 {
40 m_colorList.push_back( Oxygen::brickRed4 );
41 m_colorList.push_back( Oxygen::raspberryPink4 );
42 m_colorList.push_back( Oxygen::burgundyPurple4 );
43 m_colorList.push_back( Oxygen::grapeViolet4 );
44 m_colorList.push_back( Oxygen::skyBlue4 );
45 m_colorList.push_back( Oxygen::seaBlue4 );
46 m_colorList.push_back( Oxygen::emeraldGreen4 );
47 m_colorList.push_back( Oxygen::forestGreen4 );
48 m_colorList.push_back( Oxygen::sunYellow4 );
49 m_colorList.push_back( Oxygen::hotOrange4 );
50 m_colorList.push_back( Oxygen::aluminumGray4 );
51 m_colorList.push_back( Oxygen::woodBrown4 );
52 }
53
nextColor()54 QColor SatellitesModel::nextColor()
55 {
56 if (m_colorList.isEmpty()) {
57 return Oxygen::brickRed4;
58 }
59 if (m_currentColorIndex < m_colorList.size()) {
60 m_currentColorIndex++;
61 return m_colorList[m_currentColorIndex-1];
62 } else {
63 m_currentColorIndex = 1;
64 return m_colorList[0];
65 }
66 return Oxygen::brickRed4;
67 }
68
loadSettings(const QHash<QString,QVariant> & settings)69 void SatellitesModel::loadSettings( const QHash<QString, QVariant> &settings )
70 {
71 QStringList idList = settings[QStringLiteral("idList")].toStringList();
72 m_enabledIds = idList;
73
74 updateVisibility();
75 }
76
setPlanet(const QString & planetId)77 void SatellitesModel::setPlanet( const QString &planetId )
78 {
79 if( m_lcPlanet != planetId ) {
80
81 mDebug() << "Planet changed from" << m_lcPlanet << "to" << planetId;
82 m_lcPlanet = planetId;
83
84 updateVisibility();
85 }
86 }
87
updateVisibility()88 void SatellitesModel::updateVisibility()
89 {
90 beginUpdateItems();
91
92 for( TrackerPluginItem *obj: items() ) {
93 SatellitesMSCItem *oItem = dynamic_cast<SatellitesMSCItem*>(obj);
94 if( oItem != nullptr ) {
95 bool enabled = ( ( oItem->relatedBody().toLower() == m_lcPlanet ) &&
96 ( m_enabledIds.contains( oItem->id() ) ) );
97 oItem->setEnabled( enabled );
98
99 if( enabled ) {
100 oItem->update();
101 }
102 }
103
104 SatellitesTLEItem *eItem = dynamic_cast<SatellitesTLEItem*>(obj);
105 if( eItem != nullptr ) {
106 // TLE satellites are always earth satellites
107 bool enabled = (m_lcPlanet == QLatin1String("earth"));
108 eItem->setEnabled( enabled );
109
110 if( enabled ) {
111 eItem->update();
112 }
113 }
114 }
115
116 endUpdateItems();
117 }
118
parseFile(const QString & id,const QByteArray & data)119 void SatellitesModel::parseFile( const QString &id,
120 const QByteArray &data )
121 {
122 // catalog files are comma separated while tle files
123 // are not allowed to contain commas, so we use this
124 // to distinguish between those two
125 if( data.contains( ',' ) ) {
126 parseCatalog( id, data );
127 } else {
128 parseTLE( id, data );
129 }
130
131 emit fileParsed( id );
132 }
133
parseCatalog(const QString & id,const QByteArray & data)134 void SatellitesModel::parseCatalog( const QString &id,
135 const QByteArray &data )
136 {
137 // For details see:
138 // https://techbase.kde.org/Projects/Marble/SatelliteCatalogFormat
139
140 mDebug() << "Reading satellite catalog from:" << id;
141
142 QTextStream ts(data);
143 int index = 1;
144
145 beginUpdateItems();
146
147 QString line = ts.readLine();
148 for( ; !line.isNull(); line = ts.readLine() ) {
149
150 if (line.trimmed().startsWith(QLatin1Char('#'))) {
151 continue;
152 }
153
154 QStringList elms = line.split(", ");
155
156 // check for '<' instead of '==' in order to allow fields to be added
157 // to catalogs later without breaking the code
158 if( elms.size() < 14 ) {
159 mDebug() << "Skipping line:" << elms << "(" << line << ")";
160 continue;
161 }
162
163 QString name( elms[0] );
164 QString category( elms[1] );
165 QString body( elms[2] );
166 QByteArray body8Bit = body.toLocal8Bit();
167 char *cbody = const_cast<char*>( body8Bit.constData() );
168
169 mDebug() << "Loading" << category << name;
170
171 PlanetarySats *planSat = new PlanetarySats();
172 planSat->setPlanet( cbody );
173
174 planSat->setStateVector(
175 elms[7].toFloat() - 2400000.5,
176 elms[8].toFloat(), elms[9].toFloat(), elms[10].toFloat(),
177 elms[11].toFloat(), elms[12].toFloat(), elms[13].toFloat() );
178
179 planSat->stateToKepler();
180
181 QDateTime missionStart, missionEnd;
182 if( elms[3].toUInt() > 0 ) {
183 missionStart = QDateTime::fromTime_t( elms[3].toUInt() );
184 }
185 if( elms[4].toUInt() > 0 ) {
186 missionEnd = QDateTime::fromTime_t( elms[4].toUInt() );
187 }
188
189 SatellitesMSCItem *item = new SatellitesMSCItem( name, category, body, id,
190 missionStart, missionEnd,
191 index++, planSat, m_clock );
192 GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() ));
193 style->lineStyle().setPenStyle( Qt::SolidLine );
194 style->lineStyle().setColor( nextColor() );
195 style->labelStyle().setGlow( true );
196
197 // use special icon for moons
198 if (category == QLatin1String("Moons")) {
199 style->iconStyle().setIconPath(QStringLiteral(":/icons/moon.png"));
200 } else {
201 style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png")));
202 }
203
204 item->placemark()->setStyle( style );
205
206 item->placemark()->setVisible( ( body.toLower() == m_lcPlanet ) );
207 addItem( item );
208 }
209
210 endUpdateItems();
211 }
212
parseTLE(const QString & id,const QByteArray & data)213 void SatellitesModel::parseTLE( const QString &id,
214 const QByteArray &data )
215 {
216 mDebug() << "Reading satellite TLE data from:" << id;
217
218 QList<QByteArray> tleLines = data.split( '\n' );
219 // File format: One line of description, two lines of TLE, last line is empty
220 if ( tleLines.size() % 3 != 1 ) {
221 mDebug() << "Malformated satellite data file";
222 }
223
224 beginUpdateItems();
225
226 //FIXME: terrible hack because twoline2rv uses sscanf
227 setlocale( LC_NUMERIC, "C" );
228
229 double startmfe, stopmfe, deltamin;
230 elsetrec satrec;
231 int i = 0;
232 while ( i < tleLines.size() - 1 ) {
233 QString satelliteName = QString( tleLines.at( i++ ) ).trimmed();
234 char line1[130];
235 char line2[130];
236 if( tleLines.at( i ).size() >= 79 ||
237 tleLines.at( i+1 ).size() >= 79 ) {
238 mDebug() << "Invalid TLE data!";
239 return;
240 }
241 qstrcpy( line1, tleLines.at( i++ ).constData() );
242 qstrcpy( line2, tleLines.at( i++ ).constData() );
243 twoline2rv( line1, line2, 'c', 'd', 'i', wgs84,
244 startmfe, stopmfe, deltamin, satrec );
245 if ( satrec.error != 0 ) {
246 mDebug() << "Error: " << satrec.error;
247 return;
248 }
249
250 SatellitesTLEItem *item = new SatellitesTLEItem( satelliteName, satrec, m_clock );
251 GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() ));
252 style->lineStyle().setPenStyle( Qt::SolidLine );
253 style->lineStyle().setColor( nextColor() );
254 style->labelStyle().setGlow( true );
255 style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png")));
256 item->placemark()->setStyle( style );
257 addItem( item );
258 }
259
260 //Reset to environment
261 setlocale( LC_NUMERIC, "" );
262
263 endUpdateItems();
264 }
265
266 } // namespace Marble
267
268 #include "moc_SatellitesModel.cpp"
269