/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2019 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ /** * Experiment with using a J2000/ECI reference frame as the root of the scene, * with the MapNode under an ECI-to-ECEF transform. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LC "[eci] " using namespace osgEarth; using namespace osgEarth::Util; using namespace osgEarth::Symbology; using namespace osgEarth::Annotation; namespace ui = osgEarth::Util::Controls; int usage(const char* name, const char* msg) { OE_NOTICE << "\nUsage: " << name << " [file.earth]\n" << " --tle : Load a NORAD TLE file\n" << " --maxpoints : Limit the track size to points\n" << " --ecef : View the track in ECEF space instead of ECI\n" << " --tessellate : Add interpolated points to the track data\n" << "\nDownload NORAD TLE files from https://www.celestrak.com/NORAD/archives\n\n" << msg << std::endl; return 0; } // Reference time for the J2000 ECI coordinate frame static DateTime J2000Epoch(2000, 1, 1, 12.00); // Transform that takes us from a J2000 ECI reference frame // to an ECEF reference frame (i.e. MapNode) class J2000ToECEFTransform : public osg::MatrixTransform { public: void setDateTime(const DateTime& dt) { osg::Matrix matrix = createMatrix(dt); setMatrix(matrix); } static osg::Matrix createMatrix(const DateTime& dt) { // Earth's rotation rate: International Astronomical Union (IAU) GRS 67 const double IAU_EARTH_ANGULAR_VELOCITY = 7292115.1467e-11; // (rad/sec) double secondsElapsed = (double)(dt.asTimeStamp() - J2000Epoch.asTimeStamp()); const double rotation = IAU_EARTH_ANGULAR_VELOCITY * secondsElapsed; osg::Matrix matrix; matrix.makeRotate(rotation, 0, 0, 1); return matrix; } }; // Code to read TLE track data files from https://celestrak.com/NORAD struct ECILocation { DateTime timestamp; // point time Angle incl; // inclination Angle raan; // right ascencion of ascending node Distance alt; // altitude osg::Vec3d eci; // ECI coordinate osg::Vec3d ecef; // ECEF coordinate void computeECIAndECEF() { eci = osg::Quat(raan.as(Units::RADIANS), osg::Vec3d(0, 0, 1)) * osg::Quat(incl.as(Units::RADIANS), osg::Vec3d(1, 0, 0)) * osg::Vec3d(alt.as(Units::METERS), 0, 0); osg::Matrix eci2ecef = J2000ToECEFTransform::createMatrix(timestamp); ecef = eci * eci2ecef; } }; struct ECITrack : public std::vector { // interpolate points for a smoother track void tessellate() { ECITrack newTrack; for(unsigned k=0; k(line1.substr(18, 2), 99); int year = year2digit > 50? 1900+year2digit : 2000+year2digit; double dayOfYear = osgEarth::as(line1.substr(20, 12), 0); loc.timestamp = DateTime(year, dayOfYear); // read ra/decl loc.incl.set(osgEarth::as(line2.substr(8,8),0), Units::DEGREES); loc.raan.set(osgEarth::as(line2.substr(17,8),0), Units::DEGREES); loc.alt.set(6371 + 715, Units::KILOMETERS); loc.computeECIAndECEF(); } OE_INFO << "Read " << track.size() << " track points" << std::endl; return true; } }; // If the "global" coordinate system is ECI, you can put this transform // under the MapNode (in ECEF space) to "revert" to that global ECI frame. // Useful if you want to put ECI-space data under the MapNode. class ECIReferenceFrame : public osg::Group { public: ECIReferenceFrame() { Lighting::set(getOrCreateStateSet(), osg::StateAttribute::OFF); } void traverse(osg::NodeVisitor& nv) { osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv); if (cv) { const osg::Camera* cam = cv->getRenderStage()->getCamera(); cv->pushModelViewMatrix(new osg::RefMatrix(cam->getViewMatrix()), osg::Transform::ABSOLUTE_RF); osg::Group::traverse(nv); cv->popModelViewMatrix(); } else osg::Group::traverse(nv); } }; // Loads up an ECITrack for display as a series of points. class ECITrackDrawable : public LineDrawable //public PointDrawable { public: ECITrackDrawable() : LineDrawable(GL_LINE_STRIP) { Lighting::set(getOrCreateStateSet(), 0); //setPointSmooth(true); //setPointSize(4.0f); } void setDateTime(const DateTime& dt) { osg::FloatArray* times = dynamic_cast(getVertexAttribArray(6)); unsigned i; for (i = 0; i < getNumVerts(); ++i) { if (dt.asTimeStamp() < getVertexAttrib(times, i)) break; } setCount(i); } void load(const ECITrack& track, bool drawECEF) { osg::FloatArray* times = new osg::FloatArray(); times->setBinding(osg::Array::BIND_PER_VERTEX); setVertexAttribArray(6, times); osg::Vec4f HSLA; Color color; for(unsigned i=0; iallocate(6); d->setVertex(0, osg::Vec3(0,0,0)); d->setColor(0, osg::Vec4(1,0,0,1)); d->setVertex(1, osg::Vec3(R,0,0)); d->setColor(1, osg::Vec4(1,0,0,1)); d->setVertex(2, osg::Vec3(0,0,0)); d->setColor(2, osg::Vec4(0,1,0,1)); d->setVertex(3, osg::Vec3(0,R,0)); d->setColor(3, osg::Vec4(0,1,0,1)); d->setVertex(4, osg::Vec3(0,0,0)); d->setColor(4, osg::Vec4(0,0,1,1)); d->setVertex(5, osg::Vec3(0,0,R)); d->setColor(5, osg::Vec4(0,0,1,1)); d->setLineWidth(10); return d; } // Application-wide data and control structure struct App { DateTime start, end; HSliderControl* time; LabelControl* timeLabel; SkyNode* sky; J2000ToECEFTransform* ecef; osg::Group* eci; ECITrackDrawable* trackDrawable; ECITrack track; App() { trackDrawable = 0L; start = J2000Epoch; end = start + 24.0; } void setTime() { DateTime newTime(time->getValue()); if (sky) sky->setDateTime(newTime); if (ecef) ecef->setDateTime(newTime); if (trackDrawable) trackDrawable->setDateTime(newTime); timeLabel->setText(newTime.asRFC1123()); } }; OE_UI_HANDLER(setTime); int main(int argc, char** argv) { osg::ArgumentParser arguments(&argc,argv); if ( arguments.read("--help") ) return usage(argv[0], ""); App app; // Read in an optiona TLE track data file std::string tlefile; if (arguments.read("--tle", tlefile)) { TLEReader().read(tlefile, app.track); if (!app.track.empty()) { int maxPoints; if (arguments.read("--maxpoints", maxPoints) && app.track.size() > maxPoints) app.track.resize(maxPoints); if (arguments.read("--tessellate")) app.track.tessellate(); app.start = app.track.front().timestamp; app.end = app.track.back().timestamp; } } osgViewer::Viewer viewer(arguments); viewer.setCameraManipulator( new EarthManipulator(arguments) ); ui::VBox* container = new ui::VBox(); container->setChildSpacing(3); container->addControl(new ui::LabelControl("ECI COORDINATE SYSTEM EXAMPLE", Color::Yellow)); // UI control to modify the time of day. ui::HBox* h = container->addControl(new ui::HBox()); h->addControl(new ui::LabelControl("Time:")); app.time = h->addControl(new HSliderControl( app.start.asTimeStamp(), app.end.asTimeStamp(), app.start.asTimeStamp(), new setTime(app))); app.time->setWidth(500); app.timeLabel = container->addControl(new LabelControl()); // Load an earth file osg::Node* earth = MapNodeHelper().load(arguments, &viewer, container); if (earth) { // New scene graph root osg::Group* root = new osg::Group(); // First create a Sky which we will place in the (default) ECI frame. SkyOptions skyOptions; skyOptions.coordinateSystem() = SkyOptions::COORDSYS_ECI; app.sky = SkyNode::create(MapNode::get(earth)); app.sky->attach(&viewer); app.sky->getSunLight()->setAmbient(osg::Vec4(0.5,0.5,0.5,1.0)); root->addChild(app.sky); // A special transform takes us from the ECI into an ECEF frame // based on the current date and time. // The earth (MapNode) lives here since it is ECEF. app.ecef = new J2000ToECEFTransform(); app.sky->addChild(app.ecef); app.ecef->addChild(earth); // This group holds data in the ECI frame. app.eci = new ECIReferenceFrame(); app.eci->addChild(createECIAxes()); MapNode::get(earth)->addChild(app.eci); // Track data if (!app.track.empty()) { app.trackDrawable = new ECITrackDrawable(); bool drawECEF = arguments.read("--ecef"); if (drawECEF) { app.trackDrawable->load(app.track, true); MapNode::get(earth)->addChild(app.trackDrawable); } else { app.trackDrawable->load(app.track, false); app.eci->addChild(app.trackDrawable); } } viewer.realize(); app.time->setWidth(viewer.getCamera()->getViewport()->width()-40); app.setTime(); viewer.setSceneData(root); viewer.run(); } else { return usage(argv[0], "Bad earth file"); } return 0; }