File indexing completed on 2024-04-28 03:50:24

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Guillaume Martres <smarter@ubuntu.com>
0004 //
0005 
0006 #include "SatellitesModel.h"
0007 
0008 #include "MarbleDebug.h"
0009 #include "MarbleDirs.h"
0010 #include "SatellitesMSCItem.h"
0011 #include "SatellitesTLEItem.h"
0012 
0013 #include "MarbleClock.h"
0014 #include "MarbleColors.h"
0015 #include "GeoDataPlacemark.h"
0016 #include "GeoDataStyle.h"
0017 #include "GeoDataIconStyle.h"
0018 #include "GeoDataLabelStyle.h"
0019 #include "GeoDataLineStyle.h"
0020 
0021 #include <planetarySats.h>
0022 #include <sgp4io.h>
0023 
0024 #include <clocale>
0025 
0026 namespace Marble {
0027 
0028 SatellitesModel::SatellitesModel( GeoDataTreeModel *treeModel,
0029                                   const MarbleClock *clock )
0030     : TrackerPluginModel( treeModel ),
0031       m_clock( clock ),
0032       m_currentColorIndex( 0 )
0033 {
0034     setupColors();
0035     connect(m_clock, SIGNAL(timeChanged()), this, SLOT(update()));
0036 }
0037 
0038 void SatellitesModel::setupColors()
0039 {
0040     m_colorList.push_back( Oxygen::brickRed4 );
0041     m_colorList.push_back( Oxygen::raspberryPink4 );
0042     m_colorList.push_back( Oxygen::burgundyPurple4 );
0043     m_colorList.push_back( Oxygen::grapeViolet4 );
0044     m_colorList.push_back( Oxygen::skyBlue4 );
0045     m_colorList.push_back( Oxygen::seaBlue4 );
0046     m_colorList.push_back( Oxygen::emeraldGreen4 );
0047     m_colorList.push_back( Oxygen::forestGreen4 );
0048     m_colorList.push_back( Oxygen::sunYellow4 );
0049     m_colorList.push_back( Oxygen::hotOrange4 );
0050     m_colorList.push_back( Oxygen::aluminumGray4 );
0051     m_colorList.push_back( Oxygen::woodBrown4 );
0052 }
0053 
0054 QColor SatellitesModel::nextColor()
0055 {
0056     if (m_colorList.isEmpty()) {
0057         return Oxygen::brickRed4;
0058     }
0059     if (m_currentColorIndex < m_colorList.size()) {
0060         m_currentColorIndex++;
0061         return m_colorList[m_currentColorIndex-1];
0062     } else {
0063         m_currentColorIndex = 1;
0064         return m_colorList[0];
0065     }
0066     return Oxygen::brickRed4;
0067 }
0068 
0069 void SatellitesModel::loadSettings( const QHash<QString, QVariant> &settings )
0070 {
0071     QStringList idList = settings[QStringLiteral("idList")].toStringList();
0072     m_enabledIds = idList;
0073 
0074     updateVisibility();
0075 }
0076 
0077 void SatellitesModel::setPlanet( const QString &planetId )
0078 {
0079     if( m_lcPlanet != planetId ) {
0080 
0081         mDebug() << "Planet changed from" << m_lcPlanet << "to" << planetId;
0082         m_lcPlanet = planetId;
0083 
0084         updateVisibility();
0085     }
0086 }
0087 
0088 void SatellitesModel::updateVisibility()
0089 {
0090     beginUpdateItems();
0091 
0092     for( TrackerPluginItem *obj: items() ) {
0093         SatellitesMSCItem *oItem = dynamic_cast<SatellitesMSCItem*>(obj);
0094         if( oItem != nullptr ) {
0095             bool enabled = ( ( oItem->relatedBody().toLower() == m_lcPlanet ) &&
0096                              ( m_enabledIds.contains( oItem->id() ) ) );
0097             oItem->setEnabled( enabled );
0098 
0099             if( enabled ) {
0100                 oItem->update();
0101             }
0102         }
0103 
0104         SatellitesTLEItem *eItem = dynamic_cast<SatellitesTLEItem*>(obj);
0105         if( eItem != nullptr ) {
0106             // TLE satellites are always earth satellites
0107             bool enabled = (m_lcPlanet == QLatin1String("earth"));
0108             eItem->setEnabled( enabled );
0109 
0110             if( enabled ) {
0111                 eItem->update();
0112             }
0113         }
0114     }
0115 
0116     endUpdateItems();
0117 }
0118 
0119 void SatellitesModel::parseFile( const QString &id,
0120                                  const QByteArray &data )
0121 {
0122     // catalog files are comma separated while tle files
0123     // are not allowed to contain commas, so we use this
0124     // to distinguish between those two
0125     if( data.contains( ',' ) ) {
0126         parseCatalog( id, data );
0127     } else {
0128         parseTLE( id, data );
0129     }
0130 
0131     emit fileParsed( id );
0132 }
0133 
0134 void SatellitesModel::parseCatalog( const QString &id,
0135                                     const QByteArray &data )
0136 {
0137     // For details see:
0138     // https://techbase.kde.org/Projects/Marble/SatelliteCatalogFormat
0139 
0140     mDebug() << "Reading satellite catalog from:" << id;
0141 
0142     QTextStream ts(data);
0143     int index = 1;
0144 
0145     beginUpdateItems();
0146 
0147     QString line = ts.readLine();
0148     for( ; !line.isNull(); line = ts.readLine() ) {
0149 
0150         if (line.trimmed().startsWith(QLatin1Char('#'))) {
0151             continue;
0152         }
0153 
0154         QStringList elms = line.split(", ");
0155 
0156         // check for '<' instead of '==' in order to allow fields to be added
0157         // to catalogs later without breaking the code
0158         if( elms.size() < 14 ) {
0159             mDebug() << "Skipping line:" << elms << "(" << line << ")";
0160             continue;
0161         }
0162 
0163         QString name( elms[0] );
0164         QString category( elms[1] );
0165         QString body( elms[2] );
0166         QByteArray body8Bit = body.toLocal8Bit();
0167         char *cbody = const_cast<char*>( body8Bit.constData() );
0168 
0169         mDebug() << "Loading" << category << name;
0170 
0171         PlanetarySats *planSat = new PlanetarySats();
0172         planSat->setPlanet( cbody );
0173 
0174         planSat->setStateVector(
0175             elms[7].toFloat() - 2400000.5,
0176             elms[8].toFloat(),  elms[9].toFloat(),  elms[10].toFloat(),
0177             elms[11].toFloat(), elms[12].toFloat(), elms[13].toFloat() );
0178 
0179         planSat->stateToKepler();
0180 
0181         QDateTime missionStart, missionEnd;
0182         if( elms[3].toUInt() > 0 ) {
0183             missionStart = QDateTime::fromTime_t( elms[3].toUInt() );
0184         }
0185         if( elms[4].toUInt() > 0 ) {
0186             missionEnd = QDateTime::fromTime_t( elms[4].toUInt() );
0187         }
0188 
0189         SatellitesMSCItem *item = new SatellitesMSCItem( name, category, body, id,
0190                                       missionStart, missionEnd,
0191                                       index++, planSat, m_clock );
0192         GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() ));
0193         style->lineStyle().setPenStyle( Qt::SolidLine );
0194         style->lineStyle().setColor( nextColor() );
0195         style->labelStyle().setGlow( true );
0196 
0197         // use special icon for moons
0198         if (category == QLatin1String("Moons")) {
0199             style->iconStyle().setIconPath(QStringLiteral(":/icons/moon.png"));
0200         } else {
0201             style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png")));
0202         }
0203 
0204         item->placemark()->setStyle( style );
0205 
0206         item->placemark()->setVisible( ( body.toLower() == m_lcPlanet ) );
0207         addItem( item );
0208     }
0209 
0210     endUpdateItems();
0211 }
0212 
0213 void SatellitesModel::parseTLE( const QString &id,
0214                                 const QByteArray &data )
0215 {
0216     mDebug() << "Reading satellite TLE data from:" << id;
0217 
0218     QList<QByteArray> tleLines = data.split( '\n' );
0219     // File format: One line of description, two lines of TLE, last line is empty
0220     if ( tleLines.size() % 3 != 1 ) {
0221         mDebug() << "Malformated satellite data file";
0222     }
0223 
0224     beginUpdateItems();
0225 
0226     //FIXME: terrible hack because twoline2rv uses sscanf
0227     setlocale( LC_NUMERIC, "C" );
0228 
0229     double startmfe, stopmfe, deltamin;
0230     elsetrec satrec;
0231     int i = 0;
0232     while ( i < tleLines.size() - 1 ) {
0233         QString satelliteName = QString( tleLines.at( i++ ) ).trimmed();
0234         char line1[130];
0235         char line2[130];
0236         if( tleLines.at( i ).size() >= 79  ||
0237             tleLines.at( i+1 ).size() >= 79 ) {
0238             mDebug() << "Invalid TLE data!";
0239             return;
0240         }
0241         qstrcpy( line1, tleLines.at( i++ ).constData() );
0242         qstrcpy( line2, tleLines.at( i++ ).constData() );
0243         twoline2rv( line1, line2, 'c', 'd', 'i', wgs84,
0244                     startmfe, stopmfe, deltamin, satrec );
0245         if ( satrec.error != 0 ) {
0246             mDebug() << "Error: " << satrec.error;
0247             return;
0248         }
0249 
0250         SatellitesTLEItem *item = new SatellitesTLEItem( satelliteName, satrec, m_clock );
0251         GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() ));
0252         style->lineStyle().setPenStyle( Qt::SolidLine );
0253         style->lineStyle().setColor( nextColor() );
0254         style->labelStyle().setGlow( true );
0255         style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png")));
0256         item->placemark()->setStyle( style );
0257         addItem( item );
0258     }
0259 
0260     //Reset to environment
0261     setlocale( LC_NUMERIC, "" );
0262 
0263     endUpdateItems();
0264 }
0265 
0266 } // namespace Marble
0267 
0268 #include "moc_SatellitesModel.cpp"