mirror of
https://github.com/SatDump/SatDump
synced 2026-08-13 17:47:30 -04:00
369 lines
14 KiB
C++
369 lines
14 KiB
C++
#include "projection.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
#include "global.h"
|
|
#include "logger.h"
|
|
#include "main_ui.h"
|
|
#include "imgui/imgui_image.h"
|
|
|
|
#include "common/image/image.h"
|
|
|
|
#include "imgui/file_selection.h"
|
|
#include "common/map/map_drawer.h"
|
|
#include "resources.h"
|
|
#include "common/geodetic/projection/stereo.h"
|
|
#include "common/geodetic/projection/tpers.h"
|
|
#include "common/map/maidenhead.h"
|
|
#include "common/geodetic/projection/proj_file.h"
|
|
#include "common/geodetic/projection/satellite_reprojector.h"
|
|
#include <filesystem>
|
|
#include "global.h"
|
|
#include "common/geodetic/projection/geo_projection.h"
|
|
#include "modules/goes/gvar/image/crop.h"
|
|
#include "settings.h"
|
|
#include "findgeoref.h"
|
|
|
|
namespace projection
|
|
{
|
|
extern char overlay_image[1000];
|
|
extern char overlay_georef[1000];
|
|
}
|
|
|
|
namespace projection_overlay
|
|
{
|
|
image::Image<uint8_t> overlayed_image;
|
|
unsigned int textureID = 0;
|
|
uint32_t *textureBuffer;
|
|
|
|
std::mutex imageMutex;
|
|
std::shared_ptr<geodetic::projection::proj_file::GeodeticReferenceFile> geofile;
|
|
|
|
bool invert_image = false;
|
|
|
|
bool draw_borders = true;
|
|
bool draw_cities = true;
|
|
bool draw_custom_labels = true;
|
|
float cities_size_ratio = 0.3;
|
|
|
|
// Custom
|
|
char new_custom_label_str[1000];
|
|
float new_custom_label_lat = 0;
|
|
float new_custom_label_lon = 0;
|
|
bool show_custom_labels_window = false;
|
|
std::vector<map::CustomLabel> customLabels;
|
|
|
|
bool isFirstUiRun = false;
|
|
bool rendering = false;
|
|
int isRenderDone = false;
|
|
|
|
void loadImage()
|
|
{
|
|
imageMutex.lock();
|
|
overlayed_image.load_png(projection::overlay_image);
|
|
|
|
if (geofile->file_type == geodetic::projection::proj_file::GEO_TYPE)
|
|
{
|
|
geodetic::projection::proj_file::GEO_GeodeticReferenceFile gsofile = *((geodetic::projection::proj_file::GEO_GeodeticReferenceFile *)geofile.get());
|
|
|
|
// If this is GOES-N Data, we need to crop it first!
|
|
if (gsofile.norad == 29155 || gsofile.norad == 35491 || gsofile.norad == 36441)
|
|
{
|
|
logger->info("Cropping GOES-N data...");
|
|
|
|
// One of those should work
|
|
overlayed_image = goes::gvar::cropIR(overlayed_image); // IR Case
|
|
overlayed_image = goes::gvar::cropVIS(overlayed_image); // VIS Case
|
|
}
|
|
|
|
overlayed_image.resize(gsofile.image_width, gsofile.image_height); // Safety
|
|
}
|
|
|
|
overlayed_image.to_rgb(); // If WB, make RGB
|
|
|
|
imageMutex.unlock();
|
|
}
|
|
|
|
void initOverlay()
|
|
{
|
|
geofile = geodetic::projection::proj_file::readReferenceFile(std::string(projection::overlay_georef));
|
|
|
|
loadImage();
|
|
|
|
// Init texture
|
|
textureID = makeImageTexture();
|
|
textureBuffer = new uint32_t[overlayed_image.width() * overlayed_image.height()];
|
|
uchar_to_rgba(overlayed_image.data(), textureBuffer, overlayed_image.width() * overlayed_image.height(), 3);
|
|
updateImageTexture(textureID, textureBuffer, overlayed_image.width(), overlayed_image.height());
|
|
|
|
std::fill(new_custom_label_str, &new_custom_label_str[1000], 0);
|
|
|
|
isFirstUiRun = true;
|
|
rendering = false;
|
|
|
|
// Load custom labels
|
|
customLabels.clear();
|
|
if (settings.count("custom_map_labels") > 0)
|
|
{
|
|
std::vector<std::tuple<std::string, double, double>> labelTuple = settings["custom_map_labels"].get<std::vector<std::tuple<std::string, double, double>>>();
|
|
for (std::tuple<std::string, double, double> &label : labelTuple)
|
|
customLabels.push_back({std::get<0>(label), std::get<1>(label), std::get<2>(label)});
|
|
}
|
|
}
|
|
|
|
void destroyOverlay()
|
|
{
|
|
deleteImageTexture(textureID);
|
|
delete[] textureBuffer;
|
|
overlayed_image = image::Image<uint8_t>(1, 1, 3);
|
|
satdumpUiStatus = MAIN_MENU;
|
|
}
|
|
|
|
void doRender()
|
|
{
|
|
loadImage(); // Reset
|
|
|
|
if (invert_image)
|
|
overlayed_image.mirror(true, true);
|
|
|
|
std::function<std::pair<int, int>(float, float, int, int)> projectionFunc;
|
|
|
|
std::shared_ptr<geodetic::projection::LEOScanProjector> leoprojector;
|
|
std::shared_ptr<geodetic::projection::GEOProjector> geoprojector;
|
|
if (geofile->file_type == geodetic::projection::proj_file::LEO_TYPE)
|
|
{
|
|
geodetic::projection::proj_file::LEO_GeodeticReferenceFile leofile = *((geodetic::projection::proj_file::LEO_GeodeticReferenceFile *)geofile.get());
|
|
std::shared_ptr<geodetic::projection::LEOScanProjectorSettings> settings = leoProjectionRefFile(leofile);
|
|
leoprojector = std::make_shared<geodetic::projection::LEOScanProjector>(settings);
|
|
leoprojector->setup_forward(98, 2, 16, 10);
|
|
projectionFunc = [&leoprojector](float lat, float lon, int map_height, int map_width) -> std::pair<int, int>
|
|
{
|
|
int x;
|
|
int y;
|
|
leoprojector->forward({lat, lon, 0}, x, y);
|
|
|
|
if (x < 0 || x > map_width)
|
|
return {-1, -1};
|
|
if (y < 0 || y > map_height)
|
|
return {-1, -1};
|
|
|
|
if (invert_image)
|
|
{
|
|
x = (map_width - 1) - x;
|
|
y = (map_height - 1) - y;
|
|
}
|
|
|
|
return {x, y};
|
|
};
|
|
}
|
|
else if (geofile->file_type == geodetic::projection::proj_file::GEO_TYPE)
|
|
{
|
|
geodetic::projection::proj_file::GEO_GeodeticReferenceFile gsofile = *((geodetic::projection::proj_file::GEO_GeodeticReferenceFile *)geofile.get());
|
|
geoprojector = std::make_shared<geodetic::projection::GEOProjector>(geodetic::projection::proj_file::geoProjectionRefFile(gsofile));
|
|
|
|
projectionFunc = [&geoprojector](float lat, float lon, int map_height, int map_width) -> std::pair<int, int>
|
|
{
|
|
int x;
|
|
int y;
|
|
geoprojector->forward(lon, lat, x, y);
|
|
|
|
if (x < 0 || x > map_width)
|
|
return {-1, -1};
|
|
if (y < 0 || y > map_height)
|
|
return {-1, -1};
|
|
|
|
if (invert_image)
|
|
{
|
|
x = (map_width - 1) - x;
|
|
y = (map_height - 1) - y;
|
|
}
|
|
|
|
return {x, y};
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Should we draw borders?
|
|
if (draw_borders)
|
|
{
|
|
unsigned char color[3] = {0, 255, 0};
|
|
map::drawProjectedMapShapefile({resources::getResourcePath("maps/ne_10m_admin_0_countries.shp")}, overlayed_image, color, projectionFunc, 200);
|
|
}
|
|
|
|
// Should we draw cities?
|
|
if (draw_cities)
|
|
{
|
|
unsigned char color[3] = {255, 0, 0};
|
|
map::drawProjectedCapitalsGeoJson({resources::getResourcePath("maps/ne_10m_populated_places_simple.json")}, overlayed_image, color, projectionFunc, cities_size_ratio);
|
|
}
|
|
|
|
// Should we draw custom labels?
|
|
if (draw_custom_labels)
|
|
{
|
|
unsigned char color[3] = {0, 0, 255};
|
|
map::drawProjectedLabels(customLabels, overlayed_image, color, projectionFunc, cities_size_ratio);
|
|
}
|
|
|
|
// Finally, update image
|
|
//uchar_to_rgba(projected_image.data(), textureBuffer, output_width * output_height, 3);
|
|
//updateImageTexture(textureID, textureBuffer, output_width, output_height);
|
|
};
|
|
|
|
void renderOverlay(int wwidth, int wheight)
|
|
{
|
|
if (rendering || isRenderDone)
|
|
{
|
|
// When rendering, update more often
|
|
imageMutex.lock();
|
|
uchar_to_rgba(overlayed_image.data(), textureBuffer, overlayed_image.width() * overlayed_image.height(), 3);
|
|
updateImageTexture(textureID, textureBuffer, overlayed_image.width(), overlayed_image.height());
|
|
imageMutex.unlock();
|
|
|
|
if (isRenderDone > 0)
|
|
{
|
|
isRenderDone--;
|
|
}
|
|
}
|
|
|
|
ImGui::SetNextWindowPos({0, 0});
|
|
ImGui::SetNextWindowSize({(float)wwidth, (float)wheight});
|
|
ImGui::Begin("Overlay Image", NULL, NOWINDOW_FLAGS | ImGuiWindowFlags_NoTitleBar);
|
|
{
|
|
float ratioX = ImGui::GetWindowWidth() / overlayed_image.width();
|
|
float ratioY = (ImGui::GetWindowHeight() - 16) / overlayed_image.height();
|
|
float ratio = ratioX > ratioY ? ratioY : ratioX;
|
|
ImGui::SetCursorPos({(ImGui::GetWindowWidth() / 2) - (overlayed_image.width() * ratio) / 2, (ImGui::GetWindowHeight() / 2) - (overlayed_image.height() * ratio) / 2});
|
|
ImGui::Image((void *)(intptr_t)textureID, {overlayed_image.width() * ratio, overlayed_image.height() * ratio});
|
|
}
|
|
ImGui::End();
|
|
|
|
if (isFirstUiRun)
|
|
ImGui::SetNextWindowSize({400 * ui_scale, 270 * ui_scale});
|
|
ImGui::Begin("Overlay");
|
|
{
|
|
|
|
ImGui::Checkbox("Invert Image", &invert_image);
|
|
ImGui::Spacing();
|
|
ImGui::Checkbox("Draw Borders", &draw_borders);
|
|
ImGui::Checkbox("Draw Cities", &draw_cities);
|
|
ImGui::Checkbox("Draw Labels", &draw_custom_labels);
|
|
ImGui::InputFloat("Cities Scale", &cities_size_ratio);
|
|
if (ImGui::Button("Edit Custom Labels"))
|
|
show_custom_labels_window = true;
|
|
|
|
if (!rendering)
|
|
{
|
|
if (ImGui::Button("Overlay"))
|
|
{
|
|
if (!rendering)
|
|
processThreadPool.push([&](int)
|
|
{
|
|
doRender();
|
|
rendering = false;
|
|
isRenderDone = 10;
|
|
});
|
|
rendering = true;
|
|
isRenderDone = false;
|
|
}
|
|
}
|
|
else
|
|
ImGui::Button("Processing...");
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Save"))
|
|
{
|
|
#ifdef __APPLE__
|
|
overlayed_image.save_png("overlayed.png");
|
|
#else
|
|
logger->debug("Opening file dialog");
|
|
std::string output_file = selectOutputFileDialog("Save projection to", "overlayed.png", {"*.png"});
|
|
|
|
logger->info("Saving to " + output_file);
|
|
|
|
if (output_file.size() > 0)
|
|
{
|
|
overlayed_image.save_png(output_file.c_str());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Exit"))
|
|
{
|
|
destroyOverlay();
|
|
}
|
|
|
|
ImGui::Text("On the first time, processing will take longer as some \n"
|
|
"equations have to be solved for applying the overlay. \n");
|
|
}
|
|
ImGui::End();
|
|
|
|
if (show_custom_labels_window)
|
|
{
|
|
ImGui::Begin(std::string("Edit Custom Labels").c_str(), &show_custom_labels_window);
|
|
{
|
|
if (ImGui::BeginTable("CustomlabelTable", 4, ImGuiTableFlags_Borders))
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::Text("Label");
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Text("Lat");
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::Text("Lon");
|
|
ImGui::TableSetColumnIndex(3);
|
|
for (map::CustomLabel &label : customLabels)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::Text("%s", label.label.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Text("%f", label.lat);
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::Text("%f", label.lon);
|
|
ImGui::TableSetColumnIndex(3);
|
|
if (ImGui::Button(std::string("Delete##" + label.label).c_str()))
|
|
{
|
|
logger->warn("Deleting " + label.label);
|
|
customLabels.erase(std::find_if(customLabels.begin(), customLabels.end(), [&label](map::CustomLabel &l)
|
|
{ return l.label == label.label; }));
|
|
break;
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
|
|
ImGui::InputText("Label Name", new_custom_label_str, 1000);
|
|
ImGui::InputFloat("Label Latitude", &new_custom_label_lat, 0.1, 10);
|
|
ImGui::InputFloat("Label Longitude", &new_custom_label_lon, 0.1, 10);
|
|
if (ImGui::Button("Add##addcustomlabel"))
|
|
{
|
|
customLabels.push_back({std::string(new_custom_label_str), new_custom_label_lat, new_custom_label_lon});
|
|
std::fill(new_custom_label_str, &new_custom_label_str[1000], 0);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Save"))
|
|
{
|
|
std::vector<std::tuple<std::string, double, double>> labelTuple;
|
|
for (map::CustomLabel &label : customLabels)
|
|
{
|
|
labelTuple.push_back({label.label, label.lat, label.lon});
|
|
}
|
|
settings["custom_map_labels"] = labelTuple;
|
|
saveSettings();
|
|
}
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
if (isFirstUiRun)
|
|
ImGui::SetNextWindowSize({300 * ui_scale, 400 * ui_scale});
|
|
|
|
if (isFirstUiRun)
|
|
isFirstUiRun = false;
|
|
}
|
|
};
|