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