1137 lines
48 KiB
C++
1137 lines
48 KiB
C++
/* -*-c++-*- */
|
|
/* osgEarth - Geospatial SDK for OpenSceneGraph
|
|
* Copyright 2018 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
#pragma once
|
|
|
|
#include <osgEarthImGui/ImGuiPanel>
|
|
#include <osgEarth/MapNode>
|
|
#include <osgEarth/EarthManipulator>
|
|
#include <osgEarth/ViewFitter>
|
|
#include <osgEarth/ThreeDTilesLayer>
|
|
#include <osgEarth/ImageLayer>
|
|
#include <osgEarth/MapboxGLImageLayer>
|
|
#include <osgEarth/FeatureModelLayer>
|
|
#include <osgEarth/ModelLayer>
|
|
#include <osgEarth/TerrainConstraintLayer>
|
|
#include <osgEarth/NodeUtils>
|
|
#include <osgEarth/TerrainEngineNode>
|
|
#include <osgEarth/ExampleResources>
|
|
#include <osgEarth/Sky>
|
|
#include <osg/Camera>
|
|
|
|
#ifdef OSGEARTH_HAVE_CESIUM_NODEKIT
|
|
#include <osgEarthCesium/CesiumLayer>
|
|
#endif
|
|
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#if defined(__has_include)
|
|
#if __has_include(<third_party/portable-file-dialogs/portable-file-dialogs.h>)
|
|
#include <third_party/portable-file-dialogs/portable-file-dialogs.h>
|
|
#define HAS_PFD
|
|
#endif
|
|
#endif
|
|
|
|
#include <osgEarth/BuildConfig>
|
|
#include <osgEarth/Registry>
|
|
#include <osgEarth/XYZ>
|
|
#include <osgEarth/TMS>
|
|
#include <osgEarth/GDAL>
|
|
#include <osgEarth/WMS>
|
|
#include <osgEarth/DebugImageLayer>
|
|
|
|
#ifdef OSGEARTH_HAVE_MBTILES
|
|
#include <osgEarth/MBTiles>
|
|
#endif
|
|
|
|
#ifdef OSGEARTH_HAVE_PROCEDURAL_NODEKIT
|
|
#include <osgEarthProcedural/LifeMapLayer>
|
|
#include <osgEarthProcedural/TextureSplattingLayer>
|
|
#include <osgEarthProcedural/VegetationLayer>
|
|
#include <osgEarthProcedural/BiomeLayer>
|
|
#endif
|
|
|
|
namespace osgEarth
|
|
{
|
|
using namespace osgEarth::Contrib;
|
|
using namespace osgEarth::Util;
|
|
|
|
namespace detail
|
|
{
|
|
struct AddWMSDialog
|
|
{
|
|
AddWMSDialog()
|
|
{
|
|
strcpy(url, "http://readymap.org/readymap/tiles");
|
|
memset(name, 0, sizeof(name));
|
|
}
|
|
|
|
void draw(osgEarth::MapNode* mapNode)
|
|
{
|
|
if (!visible) return;
|
|
|
|
ImGui::Begin("Add WMS", &visible);
|
|
ImGui::InputText("URL", url, IM_ARRAYSIZE(url));
|
|
const std::string wmsVersion = "1.1.1";
|
|
|
|
if (ImGui::Button("Fetch layers from server"))
|
|
{
|
|
std::string wmsString = std::string(url);
|
|
char sep = wmsString.find_first_of('?') == std::string::npos ? '?' : '&';
|
|
|
|
std::string capUrl = wmsString +
|
|
sep +
|
|
std::string("SERVICE=WMS") +
|
|
std::string("&VERSION=") + wmsVersion +
|
|
std::string("&REQUEST=GetCapabilities");
|
|
|
|
capabilities = WMS::CapabilitiesReader::read(capUrl, nullptr);
|
|
}
|
|
|
|
static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
|
ImVec2 outer_size = ImVec2(0.0f, 300.0f);
|
|
if (ImGui::BeginTable("wms_layers", 3, flags, outer_size))
|
|
{
|
|
// The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
|
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
|
|
ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_NoHide);
|
|
ImGui::TableSetupColumn("Abstract", ImGuiTableColumnFlags_NoHide);
|
|
ImGui::TableHeadersRow();
|
|
|
|
if (capabilities.valid())
|
|
{
|
|
for (auto& layer : capabilities->getLayers())
|
|
{
|
|
displayWMSLayer(layer);
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::InputText("Name", name, IM_ARRAYSIZE(name));
|
|
|
|
if (ImGui::Button("OK"))
|
|
{
|
|
if (selectedWMSLayer)
|
|
{
|
|
WMSImageLayer* wms = new WMSImageLayer;
|
|
if (strlen(name) > 0)
|
|
wms->setName(name);
|
|
else
|
|
wms->setName(selectedWMSLayer->getTitle());
|
|
wms->setURL(url);
|
|
wms->setLayers(selectedWMSLayer->getName());
|
|
mapNode->getMap()->addLayer(wms);
|
|
}
|
|
visible = false;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
visible = false;
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void displayWMSLayer(WMS::Layer* layer)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
const bool is_folder = (layer->getLayers().size() > 0);
|
|
if (is_folder)
|
|
{
|
|
bool open = ImGui::TreeNodeEx(layer->getName().c_str(), ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextDisabled(layer->getTitle().c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text(layer->getAbstract().c_str());
|
|
if (open)
|
|
{
|
|
for (auto& l : layer->getLayers())
|
|
{
|
|
displayWMSLayer(l);
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen;
|
|
if (layer == selectedWMSLayer)
|
|
{
|
|
node_flags |= ImGuiTreeNodeFlags_Selected;
|
|
}
|
|
ImGui::TreeNodeEx(layer->getName().c_str(), node_flags);
|
|
if (ImGui::IsItemClicked())
|
|
{
|
|
selectedWMSLayer = layer;
|
|
strcpy(name, selectedWMSLayer->getTitle().c_str());
|
|
}
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text(layer->getTitle().c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text(layer->getAbstract().c_str());
|
|
}
|
|
}
|
|
|
|
bool visible = false;
|
|
char url[128];
|
|
char name[1024];
|
|
osg::ref_ptr<WMS::Capabilities> capabilities;
|
|
osg::ref_ptr< WMS::Layer > selectedWMSLayer;
|
|
};
|
|
|
|
|
|
struct AddTMSDialog
|
|
{
|
|
void draw(MapNode* mapNode)
|
|
{
|
|
if (!visible) return;
|
|
|
|
ImGui::Begin("Add TMS", &visible);
|
|
ImGui::InputText("Name", name, IM_ARRAYSIZE(name));
|
|
ImGui::InputText("URL", url, IM_ARRAYSIZE(url));
|
|
ImGui::Checkbox("Treat as Elevation", &isElevation);
|
|
if (ImGui::Button("OK"))
|
|
{
|
|
if (isElevation)
|
|
{
|
|
TMSElevationLayer* tms = new TMSElevationLayer;
|
|
tms->setName(name);
|
|
tms->setURL(url);
|
|
mapNode->getMap()->addLayer(tms);
|
|
}
|
|
else
|
|
{
|
|
TMSImageLayer* tms = new TMSImageLayer;
|
|
tms->setName(name);
|
|
tms->setURL(url);
|
|
mapNode->getMap()->addLayer(tms);
|
|
}
|
|
visible = false;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
visible = false;
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
bool visible = false;
|
|
char url[128] = "http://readymap.org/readymap/tiles/1.0.0/7/";
|
|
char name[64] = "New Layer";
|
|
bool isElevation = false;
|
|
};
|
|
|
|
struct AddXYZDialog
|
|
{
|
|
void draw(MapNode* mapNode)
|
|
{
|
|
if (!visible) return;
|
|
|
|
ImGui::Begin("Add XYZ", &visible);
|
|
ImGui::InputText("Name", name, IM_ARRAYSIZE(name));
|
|
ImGui::InputText("URL", url, IM_ARRAYSIZE(url));
|
|
|
|
static int profile = 1;
|
|
ImGui::Text("Profile");
|
|
if (ImGui::RadioButton("Global Geodetic", profile == 0)) { profile = 0; } ImGui::SameLine();
|
|
if (ImGui::RadioButton("Spherical Mercator", profile == 1)) { profile = 1; }
|
|
|
|
ImGui::Checkbox("Treat as Elevation", &isElevation);
|
|
if (ImGui::Button("OK"))
|
|
{
|
|
if (isElevation)
|
|
{
|
|
XYZElevationLayer* xyz = new XYZElevationLayer;
|
|
xyz->setName(name);
|
|
xyz->setURL(url);
|
|
if (profile == 0) xyz->setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
|
|
else if (profile == 1) xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
|
|
mapNode->getMap()->addLayer(xyz);
|
|
}
|
|
else
|
|
{
|
|
XYZImageLayer* xyz = new XYZImageLayer;
|
|
xyz->setName(name);
|
|
xyz->setURL(url);
|
|
if (profile == 0) xyz->setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
|
|
else if (profile == 1) xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
|
|
xyz->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
|
|
mapNode->getMap()->addLayer(xyz);
|
|
}
|
|
visible = false;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
visible = false;
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
bool visible = false;
|
|
char url[128] = "http://[abc].tile.openstreetmap.org/{z}/{x}/{y}.png";
|
|
char name[64] = "New Layer";
|
|
bool isElevation = false;
|
|
};
|
|
}
|
|
|
|
class LayersGUI : public ImGuiPanel
|
|
{
|
|
private:
|
|
osg::observer_ptr<MapNode> _mapNode;
|
|
bool _showDisabled = false;
|
|
bool _sortByCat = false;
|
|
LayerVector _layers;
|
|
std::unordered_map<Layer*, bool> _layerExpanded;
|
|
int _mapRevision = -1;
|
|
bool _first = true;
|
|
std::unordered_map<Layer*, float> _maxMaxRanges;
|
|
std::unordered_map<Layer*, float> _maxMinRanges;
|
|
std::unordered_map<Layer*, float> _maxAttenRanges;
|
|
bool _addXYZ = false;
|
|
ImageLayer* _mouseOverImageLayer = nullptr;
|
|
|
|
static const int NUM_CATS = 8;
|
|
|
|
enum LayerCat {
|
|
IMAGE = 0,
|
|
ELEVATION,
|
|
FEATURE,
|
|
MODEL,
|
|
PROCEDURAL,
|
|
CONSTRAINT,
|
|
DATA,
|
|
OTHER
|
|
};
|
|
const std::string _layerCatName[NUM_CATS] = {
|
|
"Image",
|
|
"Elevation",
|
|
"Feature",
|
|
"Model",
|
|
"Procedural",
|
|
"Constraint",
|
|
"Data",
|
|
"Other"
|
|
};
|
|
|
|
std::vector<osg::ref_ptr<Layer>> _layersByCat[NUM_CATS];
|
|
|
|
detail::AddWMSDialog _addWMSDialog;
|
|
detail::AddTMSDialog _addTMSDialog;
|
|
detail::AddXYZDialog _addXYZDialog;
|
|
|
|
using ValueUnderMouse = struct {
|
|
osg::Vec4 pixel;
|
|
GLenum pixelFormat;
|
|
GLenum dataType;
|
|
};
|
|
Future<Result<ValueUnderMouse>> _imageLayerValueUnderMouse;
|
|
|
|
std::function<bool(const Layer*)>
|
|
_showPred,
|
|
_showVisibleLayers,
|
|
_showImageLayers,
|
|
_showTerrainSurfaceLayers,
|
|
_showFeatureModelLayers,
|
|
_showConstraintLayers;
|
|
|
|
public:
|
|
|
|
LayersGUI() : ImGuiPanel("Map")
|
|
{
|
|
_showVisibleLayers = [this](const Layer* layer)
|
|
{
|
|
return
|
|
dynamic_cast<const VisibleLayer*>(layer) &&
|
|
layer->getUserProperty("show_in_ui", true);
|
|
};
|
|
|
|
_showImageLayers = [this](const Layer* layer)
|
|
{
|
|
return
|
|
dynamic_cast<const ImageLayer*>(layer) &&
|
|
layer->getUserProperty("show_in_ui", true);
|
|
};
|
|
|
|
_showTerrainSurfaceLayers = [this](const Layer* layer)
|
|
{
|
|
return
|
|
layer->getRenderType() == layer->RENDERTYPE_TERRAIN_SURFACE &&
|
|
layer->getUserProperty("show_in_ui", true);
|
|
};
|
|
|
|
_showFeatureModelLayers = [this](const Layer* layer)
|
|
{
|
|
return
|
|
dynamic_cast<const FeatureModelLayer*>(layer) &&
|
|
layer->getUserProperty("show_in_ui", true);
|
|
};
|
|
|
|
_showConstraintLayers = [this](const Layer* layer)
|
|
{
|
|
return
|
|
dynamic_cast<const TerrainConstraintLayer*>(layer) &&
|
|
layer->getUserProperty("show_in_ui", true);
|
|
};
|
|
|
|
_showPred = _showVisibleLayers;
|
|
}
|
|
|
|
//! Sets a predicate that decides whether to include a layer in the GUI
|
|
void setShowPredicate(std::function<bool(const Layer*)> func)
|
|
{
|
|
_showPred = func;
|
|
}
|
|
|
|
void load(const Config& conf) override
|
|
{
|
|
conf.get("ShowDisabled", _showDisabled);
|
|
conf.get("SortByCategory", _sortByCat);
|
|
}
|
|
|
|
void save(Config& conf) override
|
|
{
|
|
conf.set("ShowDisabled", _showDisabled);
|
|
conf.set("SortByCategory", _sortByCat);
|
|
}
|
|
|
|
void draw(osg::RenderInfo& ri) override
|
|
{
|
|
if (!isVisible())
|
|
return;
|
|
|
|
bool mapNodeWasValid = _mapNode.valid();
|
|
|
|
if (!findNodeOrHide(_mapNode, ri))
|
|
return;
|
|
|
|
if (_first)
|
|
{
|
|
EventRouter::get(view(ri))
|
|
.onMove([&](osg::View* v, float x, float y) { onMove(v, x, y); });
|
|
_first = false;
|
|
}
|
|
|
|
refreshMap(ri, mapNodeWasValid);
|
|
|
|
if (ImGui::Begin(name(), visible(), ImGuiWindowFlags_MenuBar))
|
|
{
|
|
drawAddLayerMenu(ri);
|
|
|
|
// Map name:
|
|
if (_mapNode->getMap()->getName().empty() == false)
|
|
{
|
|
ImGui::TextColored(ImVec4(1, 1, 0, 1), _mapNode->getMap()->getName().c_str());
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::Text("(%d)", _layers.size());
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Checkbox("Sort", &_sortByCat))
|
|
dirtySettings();
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Checkbox("Closed", &_showDisabled))
|
|
dirtySettings();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (_sortByCat)
|
|
{
|
|
for (int cat = 0; cat < NUM_CATS; ++cat)
|
|
{
|
|
auto& layers = _layersByCat[cat];
|
|
if (layers.empty())
|
|
continue;
|
|
|
|
if (ImGui::TreeNode(std::string(_layerCatName[cat] + " Layers").c_str()))
|
|
{
|
|
drawLayers(layers, ri);
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
drawLayers(_layers, ri);
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
void refreshMap(osg::RenderInfo& ri, bool mapNodeWasValid)
|
|
{
|
|
const Map* map = _mapNode->getMap();
|
|
|
|
Revision rev = map->getDataModelRevision();
|
|
if (rev != _mapRevision || !mapNodeWasValid)
|
|
{
|
|
_layers.clear();
|
|
_layerExpanded.clear();
|
|
|
|
if (_showPred)
|
|
_mapRevision = map->getLayers(_layers, _showPred);
|
|
else
|
|
_mapRevision = map->getLayers(_layers);
|
|
|
|
for (auto& layer : _layers)
|
|
_layerExpanded[layer.get()] = false;
|
|
|
|
_maxMaxRanges.clear();
|
|
_maxMinRanges.clear();
|
|
_maxAttenRanges.clear();
|
|
for (auto layer : _layers)
|
|
{
|
|
VisibleLayer* v = dynamic_cast<VisibleLayer*>(layer.get());
|
|
if (v)
|
|
{
|
|
_maxMaxRanges[layer.get()] = v->getMaxVisibleRange() * 2.0f;
|
|
_maxMinRanges[layer.get()] = v->getMinVisibleRange() * 2.0f;
|
|
_maxAttenRanges[layer.get()] = v->getAttenuationRange() * 2.0f;
|
|
}
|
|
}
|
|
|
|
for (int cat = 0; cat < NUM_CATS; ++cat)
|
|
_layersByCat[cat].clear();
|
|
|
|
for (auto& layer : _layers)
|
|
{
|
|
#ifdef OSGEARTH_HAVE_PROCEDURAL_NODEKIT
|
|
if (dynamic_cast<osgEarth::Procedural::LifeMapLayer*>(layer.get()) ||
|
|
dynamic_cast<osgEarth::Procedural::VegetationLayer*>(layer.get()) ||
|
|
dynamic_cast<osgEarth::Procedural::TextureSplattingLayer*>(layer.get()) ||
|
|
dynamic_cast<osgEarth::Procedural::BiomeLayer*>(layer.get()))
|
|
_layersByCat[PROCEDURAL].push_back(layer);
|
|
else
|
|
#endif
|
|
if (dynamic_cast<ImageLayer*>(layer.get()))
|
|
_layersByCat[IMAGE].push_back(layer);
|
|
else if (dynamic_cast<ElevationLayer*>(layer.get()))
|
|
_layersByCat[ELEVATION].push_back(layer);
|
|
else if (dynamic_cast<FeatureModelLayer*>(layer.get()))
|
|
_layersByCat[FEATURE].push_back(layer);
|
|
else if (dynamic_cast<ModelLayer*>(layer.get()))
|
|
_layersByCat[MODEL].push_back(layer);
|
|
else if (dynamic_cast<TerrainConstraintLayer*>(layer.get()))
|
|
_layersByCat[CONSTRAINT].push_back(layer);
|
|
else if (dynamic_cast<FeatureSource*>(layer.get()))
|
|
_layersByCat[DATA].push_back(layer);
|
|
else
|
|
_layersByCat[OTHER].push_back(layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawLayers(std::vector<osg::ref_ptr<Layer>>& layers, osg::RenderInfo& ri)
|
|
{
|
|
auto camera = view(ri)->getCamera();
|
|
auto map = _mapNode->getMap();
|
|
|
|
for (int i = layers.size() - 1; i >= 0; --i)
|
|
{
|
|
osgEarth::Layer* layer = layers[i];
|
|
|
|
if (!_showDisabled && !layer->isOpen() && !layer->getOpenAutomatically())
|
|
continue;
|
|
|
|
ImGui::PushID(layer);
|
|
bool stylePushed = false;
|
|
|
|
bool error =
|
|
layer->getStatus().isError() &&
|
|
layer->getStatus().message() != "Layer closed" &&
|
|
layer->getStatus().message() != "Layer disabled";
|
|
|
|
if (error)
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImColor(255, 72, 72))), stylePushed = true;
|
|
else if (!layer->isOpen())
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImColor(127, 127, 127))), stylePushed = true;
|
|
|
|
auto visibleLayer = dynamic_cast<osgEarth::VisibleLayer*>(layer);
|
|
|
|
if (visibleLayer)
|
|
{
|
|
if (layer->isOpen())
|
|
{
|
|
bool visible = visibleLayer->getVisible();
|
|
if (ImGui::Checkbox("", &visible))
|
|
{
|
|
visibleLayer->setVisible(visible);
|
|
}
|
|
}
|
|
else // closed? the checkbox will try to open it.
|
|
{
|
|
bool dummy = false;
|
|
if (ImGui::Checkbox("", &dummy))
|
|
{
|
|
Registry::instance()->clearBlacklist();
|
|
layer->open();
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushID("selectable");
|
|
bool layerNameClicked = false;
|
|
if (layer->isOpen())
|
|
{
|
|
ImGui::Selectable(layer->getName().c_str(), &layerNameClicked);
|
|
}
|
|
else
|
|
{
|
|
std::string text = layer->getName() + " (closed)";
|
|
ImGui::Selectable(text.c_str(), &layerNameClicked);
|
|
}
|
|
|
|
if (layerNameClicked)
|
|
{
|
|
_layerExpanded[layer] = !_layerExpanded[layer];
|
|
}
|
|
ImGui::PopID(); // "selectable"
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text(layer->getName().c_str());
|
|
}
|
|
|
|
if (_layerExpanded[layer])
|
|
{
|
|
ImGui::Indent();
|
|
|
|
ImGui::TextColored(ImVec4(.8, .8, .8, 1), "%s", layer->className());
|
|
|
|
if (layer->isOpen())
|
|
{
|
|
auto visibleLayer = dynamic_cast<osgEarth::VisibleLayer*>(layer);
|
|
auto tileLayer = dynamic_cast<osgEarth::TileLayer*>(layer);
|
|
auto imageLayer = dynamic_cast<osgEarth::ImageLayer*>(layer);
|
|
auto elevationLayer = dynamic_cast<osgEarth::ElevationLayer*>(layer);
|
|
|
|
if (tileLayer)
|
|
{
|
|
const Profile* profile = tileLayer->getProfile();
|
|
if (profile)
|
|
{
|
|
std::string srsname = profile->getSRS()->getName();
|
|
if (srsname == "unknown")
|
|
srsname = profile->getSRS()->getHorizInitString();
|
|
ImGui::TextColored(ImVec4(.8, .8, .8, 1), "%s", srsname.c_str());
|
|
|
|
if (elevationLayer)
|
|
{
|
|
ImGui::SameLine();
|
|
auto vdatum = profile->getSRS()->getVertInitString();
|
|
if (vdatum.empty()) vdatum = "geodetic";
|
|
ImGui::TextColored(ImVec4(1, .8, .8, 1), " (vdatum = %s)", vdatum.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
const GeoExtent& extent = layer->getExtent();
|
|
if (extent.isValid())
|
|
{
|
|
const std::string fmt[] = { "%5.1f", "%4.1f * %4.1f", "%1.f", "%.1f * %.1f" };
|
|
int i = _mapNode->getMapSRS()->isGeographic() ? 0 : 2;
|
|
ImGuiEx::TextCentered(fmt[i].c_str(), extent.north());
|
|
ImGuiEx::TextCentered(fmt[i + 1].c_str(), extent.west(), extent.east());
|
|
ImGuiEx::TextCentered(fmt[i].c_str(), extent.south());
|
|
}
|
|
|
|
ImGuiLTable::Begin("Layer");
|
|
|
|
if (visibleLayer && !elevationLayer)
|
|
{
|
|
float opacity = visibleLayer->getOpacity();
|
|
if (ImGuiLTable::SliderFloat("Opacity", &opacity, 0.0f, 1.0f))
|
|
visibleLayer->setOpacity(opacity);
|
|
|
|
if (visibleLayer->options().maxVisibleRange().isSet())
|
|
{
|
|
float value = visibleLayer->getMaxVisibleRange();
|
|
if (value < FLT_MAX) {
|
|
if (ImGuiLTable::SliderFloat("Max range", &value, 0.0f, _maxMaxRanges[layer]))
|
|
visibleLayer->setMaxVisibleRange(value);
|
|
}
|
|
}
|
|
|
|
if (visibleLayer->options().minVisibleRange().isSet())
|
|
{
|
|
float value = visibleLayer->getMinVisibleRange();
|
|
if (ImGuiLTable::SliderFloat("Min range", &value, 0.0f, _maxMinRanges[layer]))
|
|
visibleLayer->setMinVisibleRange(value);
|
|
}
|
|
|
|
if (visibleLayer->options().attenuationRange().isSet())
|
|
{
|
|
float value = visibleLayer->getAttenuationRange();
|
|
if (ImGuiLTable::SliderFloat("Attenuation range", &value, 0.0f, _maxAttenRanges[layer]))
|
|
visibleLayer->setAttenuationRange(value);
|
|
}
|
|
|
|
bool debugView = visibleLayer->getEnableDebugView();
|
|
if (ImGuiLTable::Checkbox("Highlight", &debugView)) {
|
|
visibleLayer->setEnableDebugView(debugView);
|
|
}
|
|
}
|
|
|
|
#ifdef OSGEARTH_HAVE_CESIUM_NODEKIT
|
|
auto cesiumNativeLayer = dynamic_cast<osgEarth::Cesium::CesiumNative3DTilesLayer*>(layer);
|
|
if (cesiumNativeLayer)
|
|
{
|
|
float sse = cesiumNativeLayer->getMaximumScreenSpaceError();
|
|
ImGui::PushID("sse");
|
|
ImGuiLTable::SliderFloat("SSE", &sse, 0.0f, 50.0f);
|
|
cesiumNativeLayer->setMaximumScreenSpaceError(sse);
|
|
ImGui::PopID();
|
|
}
|
|
#endif
|
|
|
|
auto threedTiles = dynamic_cast<osgEarth::Contrib::ThreeDTilesLayer*>(layer);
|
|
if (threedTiles)
|
|
{
|
|
float sse = threedTiles->getMaximumScreenSpaceError();
|
|
ImGui::PushID("sse");
|
|
ImGuiLTable::SliderFloat("SSE", &sse, 0.0f, 50.0f);
|
|
threedTiles->setMaximumScreenSpaceError(sse);
|
|
ImGui::PopID();
|
|
|
|
ImGui::PushID("debugVolumes");
|
|
bool showBoundingVolumes = threedTiles->getTilesetNode()->getShowBoundingVolumes();
|
|
ImGuiLTable::Checkbox("Show debug volumes", &showBoundingVolumes);
|
|
threedTiles->getTilesetNode()->setShowBoundingVolumes(showBoundingVolumes);
|
|
ImGui::PopID();
|
|
|
|
ImGui::PushID("debugColors");
|
|
bool colorPerTile = threedTiles->getTilesetNode()->getColorPerTile();
|
|
ImGuiLTable::Checkbox("Show color per tile", &colorPerTile);
|
|
threedTiles->getTilesetNode()->setColorPerTile(colorPerTile);
|
|
ImGui::PopID();
|
|
}
|
|
|
|
auto mapboxGLLayer = dynamic_cast<MapBoxGLImageLayer*>(layer);
|
|
if (mapboxGLLayer)
|
|
{
|
|
bool disableText = mapboxGLLayer->getDisableText();
|
|
if (ImGuiLTable::Checkbox("Disable text", &disableText))
|
|
{
|
|
mapboxGLLayer->setDisableText(disableText);
|
|
}
|
|
|
|
float pixelScale = mapboxGLLayer->getPixelScale();
|
|
if (ImGuiLTable::InputFloat("Pixel Scale", &pixelScale))
|
|
{
|
|
mapboxGLLayer->setPixelScale(pixelScale);
|
|
}
|
|
}
|
|
|
|
if (tileLayer)
|
|
{
|
|
if (tileLayer->options().minLevel().isSet())
|
|
{
|
|
ImGuiLTable::Text("Min level", "%d", tileLayer->getMinLevel());
|
|
}
|
|
if (tileLayer->options().maxLevel().isSet())
|
|
{
|
|
ImGuiLTable::Text("Max level", "%d", tileLayer->getMaxLevel());
|
|
}
|
|
if (tileLayer->options().maxDataLevel().isSet())
|
|
{
|
|
ImGuiLTable::Text("Max data level", "%d", tileLayer->getMaxDataLevel());
|
|
}
|
|
|
|
if (tileLayer->options().upsample().isSet())
|
|
{
|
|
bool upsampling = tileLayer->options().upsample().value();
|
|
ImGuiLTable::Text("Upsampling", "%s", (upsampling ? "ON" : "off"));
|
|
}
|
|
}
|
|
|
|
auto report = layer->reportStats();
|
|
for (auto& kv : report)
|
|
{
|
|
ImGuiLTable::Text(kv.first.c_str(), "%s", kv.second.c_str()); // (kv.first + ": " + kv.second).c_str());
|
|
}
|
|
|
|
const DateTimeExtent& dtextent = layer->getDateTimeExtent();
|
|
if (dtextent.valid())
|
|
{
|
|
//ImGui::Text("Time Series:");
|
|
ImGuiLTable::Text("Start time", "%s", dtextent.getStart().asISO8601().c_str());
|
|
ImGuiLTable::Text("End time", "%s", dtextent.getEnd().asISO8601().c_str());
|
|
}
|
|
|
|
ImGuiLTable::End();
|
|
|
|
if (imageLayer)
|
|
{
|
|
bool queryOn = (_mouseOverImageLayer == imageLayer);
|
|
if (ImGui::Checkbox("Show value under mouse", &queryOn))
|
|
{
|
|
_mouseOverImageLayer = queryOn ? imageLayer : nullptr;
|
|
}
|
|
|
|
if (_mouseOverImageLayer == imageLayer)
|
|
{
|
|
if (_imageLayerValueUnderMouse.available())
|
|
{
|
|
if (_imageLayerValueUnderMouse->isOK())
|
|
{
|
|
auto& value = _imageLayerValueUnderMouse->value();
|
|
if (value.dataType == GL_UNSIGNED_BYTE)
|
|
{
|
|
auto& p = value.pixel;
|
|
if (value.pixelFormat == GL_RED)
|
|
{
|
|
ImGui::Text(" f32 (%.2f)", p.r());
|
|
ImGui::Text(" int (%d)", (int)(255.f * p.r()));
|
|
ImGui::Text(" hex (%.2X)", (int)(255.f * p.r()));
|
|
}
|
|
if (value.pixelFormat == GL_RG)
|
|
{
|
|
ImGui::Text(" f32 (%.2f %.2f)", p.r(), p.g());
|
|
ImGui::Text(" int (%d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()));
|
|
ImGui::Text(" hex (%.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()));
|
|
}
|
|
else if (value.pixelFormat == GL_RGB)
|
|
{
|
|
ImGui::Text(" f32 (%.2f %.2f %.2f)", p.r(), p.g(), p.b());
|
|
ImGui::Text(" int (%d %d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()));
|
|
ImGui::Text(" hex (%.2X %.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()));
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text(" f32 (%.2f %.2f %.2f %.2f)", p.r(), p.g(), p.b(), p.a());
|
|
ImGui::Text(" int (%d %d %d %d)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()), (int)(255.0f * p.a()));
|
|
ImGui::Text(" hex (%.2X %.2X %.2X %.2X)", (int)(255.f * p.r()), (int)(255.f * p.g()), (int)(255.0f * p.b()), (int)(255.0f * p.a()));
|
|
}
|
|
}
|
|
else if (value.dataType == GL_FLOAT)
|
|
{
|
|
int v = (int)value.pixel.r();
|
|
ImGui::Text(" f32 (%0.4f)", value.pixel.r());
|
|
ImGui::Text(" int (%d)", v);
|
|
ImGui::Text(" hex (%#.8X)", v);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text("%s", _imageLayerValueUnderMouse->message().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui::Text(" (searching)");
|
|
ImGui::Text("");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (visibleLayer)
|
|
{
|
|
if ((extent.isValid() && !extent.isWholeEarth()) ||
|
|
(layer->getNode() && layer->getNode()->getBound().valid()) ||
|
|
(dtextent.valid()))
|
|
{
|
|
if (ImGui::Button("Zoom"))
|
|
{
|
|
if (extent.isValid())
|
|
{
|
|
std::vector<GeoPoint> points;
|
|
points.push_back(GeoPoint(extent.getSRS(), extent.west(), extent.south()));
|
|
points.push_back(GeoPoint(extent.getSRS(), extent.east(), extent.north()));
|
|
|
|
ViewFitter fitter(_mapNode->getMap()->getSRS(), camera);
|
|
Viewpoint vp;
|
|
if (fitter.createViewpoint(points, vp))
|
|
{
|
|
auto manip = dynamic_cast<EarthManipulator*>(view(ri)->getCameraManipulator());
|
|
if (manip) manip->setViewpoint(vp, 2.0);
|
|
}
|
|
}
|
|
else if (layer->getNode())
|
|
{
|
|
ViewFitter fitter(map->getSRS(), camera);
|
|
Viewpoint vp;
|
|
if (fitter.createViewpoint(layer->getNode(), vp))
|
|
{
|
|
auto manip = dynamic_cast<EarthManipulator*>(view(ri)->getCameraManipulator());
|
|
if (manip) manip->setViewpoint(vp, 2.0);
|
|
}
|
|
}
|
|
|
|
if (dtextent.valid())
|
|
{
|
|
auto sky = osgEarth::findRelativeNodeOfType<SkyNode>(_mapNode.get());
|
|
if (sky)
|
|
{
|
|
sky->setDateTime(dtextent.getStart());
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
}
|
|
|
|
if (ImGui::Button("Refresh"))
|
|
{
|
|
layer->dirty();
|
|
auto cp = layer->getCachePolicy();
|
|
cp.minTime() = DateTime().asTimeStamp();
|
|
layer->setCachePolicy(cp);
|
|
std::vector<const Layer*> layers = { layer };
|
|
_mapNode->getTerrainEngine()->invalidateRegion(layers, GeoExtent());
|
|
}
|
|
|
|
if (layer->isOpen())
|
|
{
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Close"))
|
|
{
|
|
layer->close();
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("JSON"))
|
|
{
|
|
auto conf = layer->getConfig();
|
|
std::cout << conf.toJSON(true) << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (layer->getStatus().isError() && layer->getStatus().message() != "Layer closed")
|
|
{
|
|
ImGui::TextWrapped(layer->getStatus().message().c_str());
|
|
}
|
|
|
|
ImGui::Unindent();
|
|
}
|
|
|
|
ImGui::PopID();
|
|
|
|
if (stylePushed)
|
|
ImGui::PopStyleColor(1);
|
|
|
|
ImGui::Separator();
|
|
}
|
|
}
|
|
|
|
void drawAddLayerMenu(osg::RenderInfo& ri)
|
|
{
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
if (ImGui::BeginMenu("Add Layer"))
|
|
{
|
|
#ifdef HAS_PFD
|
|
if (ImGui::MenuItem("Local File"))
|
|
{
|
|
auto f = pfd::open_file("Choose files to read", pfd::path::home(),
|
|
{ "All Files", "*" },
|
|
pfd::opt::multiselect);
|
|
|
|
if (f.result().size() > 0)
|
|
{
|
|
auto m = pfd::message("Imagery",
|
|
"Are these files imagery? Select No for elevation.",
|
|
pfd::choice::yes_no,
|
|
pfd::icon::question);
|
|
bool imagery = m.result() == pfd::button::yes;
|
|
|
|
_mapNode->getMap()->beginUpdate();
|
|
|
|
if (imagery)
|
|
{
|
|
for (auto const& name : f.result())
|
|
{
|
|
std::string ext = osgDB::getLowerCaseFileExtension(name);
|
|
if (ext == "mbtiles")
|
|
{
|
|
#ifdef OSGEARTH_HAVE_MBTILES
|
|
MBTilesImageLayer* mbtilesImage = new MBTilesImageLayer;
|
|
mbtilesImage->setName(osgDB::getSimpleFileName(name));
|
|
mbtilesImage->setURL(name);
|
|
_mapNode->getMap()->addLayer(mbtilesImage);
|
|
#else
|
|
OE_WARN << "MBTiles driver not available" << std::endl;
|
|
#endif
|
|
}
|
|
else
|
|
|
|
{
|
|
GDALImageLayer* gdalImage = new GDALImageLayer;
|
|
gdalImage->setName(osgDB::getSimpleFileName(name));
|
|
gdalImage->setURL(name);
|
|
_mapNode->getMap()->addLayer(gdalImage);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto const& name : f.result())
|
|
{
|
|
std::string ext = osgDB::getLowerCaseFileExtension(name);
|
|
if (ext == "mbtiles")
|
|
{
|
|
#ifdef OSGEARTH_HAVE_MBTILES
|
|
MBTilesElevationLayer* mbtilesElevation = new MBTilesElevationLayer;
|
|
mbtilesElevation->setName(osgDB::getSimpleFileName(name));
|
|
mbtilesElevation->setURL(name);
|
|
_mapNode->getMap()->addLayer(mbtilesElevation);
|
|
#else
|
|
OE_WARN << "MBTiles driver not available" << std::endl;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
GDALElevationLayer* gdalElevation = new GDALElevationLayer;
|
|
gdalElevation->setName(osgDB::getSimpleFileName(name));
|
|
gdalElevation->setURL(name);
|
|
_mapNode->getMap()->addLayer(gdalElevation);
|
|
}
|
|
}
|
|
}
|
|
_mapNode->getMap()->endUpdate();
|
|
}
|
|
}
|
|
#endif
|
|
if (ImGui::MenuItem("TMS")) _addTMSDialog.visible = true;
|
|
if (ImGui::MenuItem("XYZ")) _addXYZDialog.visible = true;
|
|
if (ImGui::MenuItem("WMS")) _addWMSDialog.visible = true;
|
|
|
|
drawUsefulLayers();
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
// Draw the add dialogs
|
|
_addXYZDialog.draw(_mapNode.get());
|
|
_addTMSDialog.draw(_mapNode.get());
|
|
_addWMSDialog.draw(_mapNode.get());
|
|
}
|
|
|
|
void drawUsefulLayers()
|
|
{
|
|
ImGui::Separator();
|
|
|
|
//if (ImGui::BeginMenu("Useful Layers"))
|
|
{
|
|
// ReadyMap Imagery
|
|
if (_mapNode->getMap()->getLayerByName("ReadyMap Imagery") == nullptr)
|
|
{
|
|
if (ImGui::MenuItem("ReadyMap Imagery"))
|
|
{
|
|
TMSImageLayer* readymap = new TMSImageLayer();
|
|
readymap->setName("ReadyMap Imagery");
|
|
readymap->setURL("https://readymap.org/readymap/tiles/1.0.0/7/");
|
|
_mapNode->getMap()->addLayer(readymap);
|
|
}
|
|
}
|
|
|
|
// ReadyMap Elevation
|
|
if (_mapNode->getMap()->getLayerByName("ReadyMap Elevation") == nullptr)
|
|
{
|
|
if (ImGui::MenuItem("ReadyMap Elevation"))
|
|
{
|
|
TMSElevationLayer* readymap = new TMSElevationLayer();
|
|
readymap->setName("ReadyMap Elevation");
|
|
readymap->setURL("https://readymap.org/readymap/tiles/1.0.0/116/");
|
|
_mapNode->getMap()->addLayer(readymap);
|
|
}
|
|
}
|
|
|
|
// OpenStreetMap
|
|
if (_mapNode->getMap()->getLayerByName("OpenStreetMap") == nullptr)
|
|
{
|
|
if (ImGui::MenuItem("OpenStreetMap"))
|
|
{
|
|
XYZImageLayer* osm = new XYZImageLayer();
|
|
osm->setName("OpenStreetMap");
|
|
osm->setURL("https://[abc].tile.openstreetmap.org/{z}/{x}/{y}.png");
|
|
osm->setProfile(osgEarth::Registry::instance()->getSphericalMercatorProfile());
|
|
osm->setAttribution("©OpenStreetMap contributors");
|
|
_mapNode->getMap()->addLayer(osm);
|
|
}
|
|
}
|
|
|
|
if (_mapNode->getMap()->getLayer<DebugImageLayer>() == nullptr)
|
|
{
|
|
if (ImGui::MenuItem("Debug"))
|
|
{
|
|
DebugImageLayer* debugImage = new DebugImageLayer;
|
|
debugImage->setName("Debug");
|
|
_mapNode->getMap()->addLayer(debugImage);
|
|
}
|
|
}
|
|
|
|
//ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void onMove(osg::View* view, float x, float y)
|
|
{
|
|
if (_mouseOverImageLayer)
|
|
{
|
|
_imageLayerValueUnderMouse.reset();
|
|
|
|
TerrainTile* tile = _mapNode->getTerrain()->getTerrainTileUnderMouse(view, x, y);
|
|
if (tile)
|
|
{
|
|
GeoPoint p = _mapNode->getGeoPointUnderMouse(view, x, y);
|
|
TileKey key = _mouseOverImageLayer->getProfile()->createTileKey(p, tile->getKey().getLOD());
|
|
key = _mouseOverImageLayer->getBestAvailableTileKey(key);
|
|
|
|
if (key.valid())
|
|
{
|
|
auto task = [this, key, p](Cancelable& c)
|
|
{
|
|
ValueUnderMouse value;
|
|
osg::ref_ptr<ProgressCallback> prog = new ProgressCallback(&c);
|
|
GeoImage g = _mouseOverImageLayer->createImage(key, prog.get());
|
|
if (g.valid())
|
|
{
|
|
g.getReader().setBilinear(false);
|
|
if (g.read(value.pixel, p))
|
|
{
|
|
value.pixelFormat = g.getImage()->getPixelFormat();
|
|
value.dataType = g.getImage()->getDataType();
|
|
return Result<ValueUnderMouse>(value);
|
|
}
|
|
}
|
|
return Result<ValueUnderMouse>(Status::Error("No value"));
|
|
};
|
|
|
|
_imageLayerValueUnderMouse = jobs::dispatch(task);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|