Skip to content

Instantly share code, notes, and snippets.

@gwaldron
Created May 28, 2026 19:34
Show Gist options
  • Select an option

  • Save gwaldron/77794a6be14b1c6db7e7ac653fdc0f09 to your computer and use it in GitHub Desktop.

Select an option

Save gwaldron/77794a6be14b1c6db7e7ac653fdc0f09 to your computer and use it in GitHub Desktop.
osgearth_viewer hacked to parse and animate SIMDIS ASI files
/* 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