Created
May 28, 2026 19:34
-
-
Save gwaldron/77794a6be14b1c6db7e7ac653fdc0f09 to your computer and use it in GitHub Desktop.
osgearth_viewer hacked to parse and animate SIMDIS ASI files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* osgEarth | |
| * Copyright 2025 Pelican Mapping | |
| * MIT License | |
| */ | |
| #include <osgViewer/Viewer> | |
| #include <osgEarth/EarthManipulator> | |
| #include <osgEarth/ExampleResources> | |
| #include <osgEarth/LabelNode> | |
| #include <osgEarth/MapNode> | |
| #include <osg/Group> | |
| #include <osg/NodeCallback> | |
| #include <osg/NodeVisitor> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <map> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #define LC "[asi_viewer] " | |
| using namespace osgEarth; | |
| using namespace osgEarth::Util; | |
| namespace | |
| { | |
| constexpr double SAMPLE_INTERVAL = 0.1; | |
| struct Waypoint | |
| { | |
| double lat = 0.0; | |
| double lon = 0.0; | |
| double alt = 0.0; | |
| }; | |
| struct Platform | |
| { | |
| std::string id; | |
| std::vector<Waypoint> waypoints; | |
| osg::ref_ptr<LabelNode> label; | |
| }; | |
| using PlatformMap = std::map<std::string, Platform>; | |
| int usage(const char* name) | |
| { | |
| std::cout | |
| << "View an earth file and animate SIMDIS ASI PlatformData labels:\n" | |
| << " " << name << " file.earth [--asi file.asi]\n\n" | |
| << "Default ASI file: D:/data/simdis/BMD_Demo.asi\n" | |
| << MapNodeHelper().usage() << std::endl; | |
| return 0; | |
| } | |
| bool parseASI(const std::string& filename, PlatformMap& platforms) | |
| { | |
| std::ifstream in(filename.c_str()); | |
| if (!in.is_open()) | |
| { | |
| OE_WARN << LC << "Unable to open ASI file: " << filename << std::endl; | |
| return false; | |
| } | |
| std::string line; | |
| while (std::getline(in, line)) | |
| { | |
| std::istringstream buf(line); | |
| std::string tag; | |
| std::string platformId; | |
| std::string frame; | |
| Waypoint point; | |
| if (buf >> tag && tag == "PlatformData" && | |
| buf >> platformId >> frame >> point.lat >> point.lon >> point.alt) | |
| { | |
| Platform& platform = platforms[platformId]; | |
| platform.id = platformId; | |
| platform.waypoints.push_back(point); | |
| } | |
| } | |
| return !platforms.empty(); | |
| } | |
| Waypoint sample(const Platform& platform, double time_s) | |
| { | |
| if (platform.waypoints.size() == 1) | |
| { | |
| return platform.waypoints.front(); | |
| } | |
| const double sample = time_s / SAMPLE_INTERVAL; | |
| const unsigned i0 = osg::minimum( | |
| static_cast<unsigned>(sample), | |
| static_cast<unsigned>(platform.waypoints.size() - 1)); | |
| const unsigned i1 = osg::minimum( | |
| i0 + 1u, | |
| static_cast<unsigned>(platform.waypoints.size() - 1)); | |
| const double f = osg::clampBetween(sample - static_cast<double>(i0), 0.0, 1.0); | |
| const Waypoint& a = platform.waypoints[i0]; | |
| const Waypoint& b = platform.waypoints[i1]; | |
| Waypoint result; | |
| result.lat = a.lat + (b.lat - a.lat) * f; | |
| result.lon = a.lon + (b.lon - a.lon) * f; | |
| result.alt = a.alt + (b.alt - a.alt) * f; | |
| return result; | |
| } | |
| struct ASIPlayback : public osg::NodeCallback | |
| { | |
| ASIPlayback(PlatformMap& platforms, const SpatialReference* geoSRS) : | |
| _platforms(platforms), | |
| _geoSRS(geoSRS) | |
| { | |
| for (const auto& entry : _platforms) | |
| { | |
| const double duration = entry.second.waypoints.empty() ? | |
| 0.0 : | |
| static_cast<double>(entry.second.waypoints.size() - 1) * SAMPLE_INTERVAL; | |
| _duration = osg::maximum(_duration, duration); | |
| } | |
| } | |
| void operator()(osg::Node* node, osg::NodeVisitor* nv) override | |
| { | |
| double now = 0.0; | |
| if (nv && nv->getFrameStamp()) | |
| { | |
| now = nv->getFrameStamp()->getSimulationTime(); | |
| } | |
| if (!_initialized) | |
| { | |
| _startTime = now; | |
| _initialized = true; | |
| } | |
| double elapsed = now - _startTime; | |
| if (_duration > 0.0) | |
| { | |
| elapsed = std::fmod(elapsed, _duration); | |
| } | |
| for (auto& entry : _platforms) | |
| { | |
| Platform& platform = entry.second; | |
| if (platform.label.valid() && !platform.waypoints.empty()) | |
| { | |
| const Waypoint point = sample(platform, elapsed); | |
| platform.label->setPosition( | |
| GeoPoint(_geoSRS.get(), point.lon, point.lat, point.alt, ALTMODE_ABSOLUTE)); | |
| } | |
| } | |
| traverse(node, nv); | |
| } | |
| PlatformMap& _platforms; | |
| osg::observer_ptr<const SpatialReference> _geoSRS; | |
| double _startTime = 0.0; | |
| double _duration = 0.0; | |
| bool _initialized = false; | |
| }; | |
| } | |
| int | |
| main(int argc, char** argv) | |
| { | |
| osg::ArgumentParser arguments(&argc, argv); | |
| if (arguments.read("--help")) | |
| return usage(argv[0]); | |
| std::string asiFile = "D:/data/simdis/BMD_Demo.asi"; | |
| arguments.read("--asi", asiFile); | |
| osgEarth::initialize(arguments); | |
| osgViewer::Viewer viewer(arguments); | |
| viewer.setCameraManipulator(new EarthManipulator(arguments)); | |
| viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f); | |
| auto node = MapNodeHelper().load(arguments, &viewer); | |
| if (!node.valid()) | |
| return usage(argv[0]); | |
| MapNode* mapNode = MapNode::get(node); | |
| if (!mapNode) | |
| { | |
| OE_WARN << LC << "Please provide an earth file." << std::endl; | |
| return usage(argv[0]); | |
| } | |
| PlatformMap platforms; | |
| if (!parseASI(asiFile, platforms)) | |
| { | |
| OE_WARN << LC << "No PlatformData records found in: " << asiFile << std::endl; | |
| return 1; | |
| } | |
| const SpatialReference* geoSRS = mapNode->getMapSRS()->getGeographicSRS(); | |
| Style labelStyle; | |
| TextSymbol* text = labelStyle.getOrCreate<TextSymbol>(); | |
| text->alignment() = TextSymbol::ALIGN_CENTER_BOTTOM; | |
| text->pixelOffset() = osg::Vec2s(0, -10); | |
| text->size() = 16.0f; | |
| text->fill().mutable_value().color() = Color::Yellow; | |
| text->halo().mutable_value().color() = Color::Black; | |
| text->declutter() = true; | |
| osg::Group* labelGroup = new osg::Group(); | |
| labelGroup->setName("ASI Platform Labels"); | |
| mapNode->addChild(labelGroup); | |
| for (auto& entry : platforms) | |
| { | |
| Platform& platform = entry.second; | |
| const Waypoint first = platform.waypoints.front(); | |
| platform.label = new LabelNode( | |
| GeoPoint(geoSRS, first.lon, first.lat, first.alt, ALTMODE_ABSOLUTE), | |
| platform.id, | |
| labelStyle); | |
| platform.label->setDynamic(true); | |
| platform.label->setName("Platform " + platform.id); | |
| labelGroup->addChild(platform.label.get()); | |
| } | |
| labelGroup->setUpdateCallback(new ASIPlayback(platforms, geoSRS)); | |
| OE_INFO << LC << "Loaded " << platforms.size() << " platforms from " << asiFile << std::endl; | |
| viewer.setSceneData(node); | |
| return viewer.run(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment