mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-30 20:10:29 +01:00
feat(editor): heightmap terrain from the terrain model of GUGiK, fitted to the tracks of the scenery
- editorNmt: squares of 500 m of the NMT at 1 m from the WCS service of geoportal.gov.pl, as GeoTIFF, fetched in the background, kept in editor/cache/nmt and sampled in the coordinates of the scenery, placed as its orthophoto is - the terrain panel's Terrain model section: Fit to the tracks moves the model up or down by the median of the differences to the formation of the tracks (Biała Podlaska, built at 0: -145.73 m, 0.28 m around it); Along the tracks and Around the camera make the chunks as the model comes in; each path gets its formation and slopes 1:1.5 as a modifier of the terrain, laid over each other into one corridor, bridges and tunnels keep the model; the orthophoto is turned on and laid onto the new ground - the http client of the orthophoto in editorHttp, shared; fill_from_nearest in editormodeshared.h - after the generator of Hirek (MaszynaTerrainGenerator): the WCS requests, the cache of squares, the corridor by envelopes - self-tests nmtfit, nmtmake, nmtwait
This commit is contained in:
224
editor/editorHttp.cpp
Normal file
224
editor/editorHttp.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorHttp.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <winhttp.h>
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace editor_http
|
||||
{
|
||||
|
||||
struct client::platform
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
HINTERNET session{nullptr};
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
CURL *curl{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
client::client() :
|
||||
m_platform(std::make_unique<platform>())
|
||||
{
|
||||
wchar_t const *agent = L"MaSzyna-editor";
|
||||
#ifdef WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
|
||||
m_platform->session = ::WinHttpOpen(agent, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
#endif
|
||||
if (m_platform->session == nullptr) // automatic proxy needs windows 8.1+
|
||||
m_platform->session = ::WinHttpOpen(agent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (m_platform->session != nullptr)
|
||||
::WinHttpSetTimeouts(m_platform->session, 10000, 10000, 15000, 20000);
|
||||
}
|
||||
|
||||
client::~client()
|
||||
{
|
||||
if (m_platform->session != nullptr)
|
||||
::WinHttpCloseHandle(m_platform->session);
|
||||
}
|
||||
|
||||
bool client::get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop)
|
||||
{
|
||||
Out.clear();
|
||||
if (m_platform->session == nullptr)
|
||||
{
|
||||
Error = "WinHttpOpen failed (" + std::to_string(::GetLastError()) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring const url(Url.begin(), Url.end()); // the requests are plain ascii
|
||||
URL_COMPONENTS parts{};
|
||||
parts.dwStructSize = sizeof(parts);
|
||||
parts.dwSchemeLength = static_cast<DWORD>(-1);
|
||||
parts.dwHostNameLength = static_cast<DWORD>(-1);
|
||||
parts.dwUrlPathLength = static_cast<DWORD>(-1);
|
||||
parts.dwExtraInfoLength = static_cast<DWORD>(-1);
|
||||
if (!::WinHttpCrackUrl(url.c_str(), 0, 0, &parts))
|
||||
{
|
||||
Error = "invalid url";
|
||||
return false;
|
||||
}
|
||||
std::wstring const host(parts.lpszHostName, parts.dwHostNameLength);
|
||||
std::wstring const path = std::wstring(parts.lpszUrlPath, parts.dwUrlPathLength) + std::wstring(parts.lpszExtraInfo, parts.dwExtraInfoLength);
|
||||
|
||||
HINTERNET connection = ::WinHttpConnect(m_platform->session, host.c_str(), parts.nPort, 0);
|
||||
HINTERNET request = connection ? ::WinHttpOpenRequest(connection, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
parts.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0)
|
||||
: nullptr;
|
||||
auto const close = [&]() {
|
||||
if (request != nullptr)
|
||||
::WinHttpCloseHandle(request);
|
||||
if (connection != nullptr)
|
||||
::WinHttpCloseHandle(connection);
|
||||
};
|
||||
|
||||
if (request == nullptr || !::WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !::WinHttpReceiveResponse(request, nullptr))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD status{0};
|
||||
DWORD statussize{sizeof(status)};
|
||||
::WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &statussize, WINHTTP_NO_HEADER_INDEX);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (Stop())
|
||||
{
|
||||
Error = "cancelled";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
DWORD available{0};
|
||||
if (!::WinHttpQueryDataAvailable(request, &available))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
if (available == 0)
|
||||
break;
|
||||
std::size_t const offset = Out.size();
|
||||
Out.resize(offset + available);
|
||||
DWORD received{0};
|
||||
if (!::WinHttpReadData(request, Out.data() + offset, available, &received))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
Out.resize(offset + received);
|
||||
}
|
||||
close();
|
||||
|
||||
if (status != 200)
|
||||
{
|
||||
Error = "HTTP " + std::to_string(status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
|
||||
client::client() :
|
||||
m_platform(std::make_unique<platform>())
|
||||
{
|
||||
static std::once_flag initialized;
|
||||
std::call_once(initialized, []() { ::curl_global_init(CURL_GLOBAL_DEFAULT); });
|
||||
m_platform->curl = ::curl_easy_init();
|
||||
}
|
||||
|
||||
client::~client()
|
||||
{
|
||||
if (m_platform->curl != nullptr)
|
||||
::curl_easy_cleanup(m_platform->curl);
|
||||
}
|
||||
|
||||
bool client::get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop)
|
||||
{
|
||||
Out.clear();
|
||||
if (m_platform->curl == nullptr)
|
||||
{
|
||||
Error = "curl_easy_init failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const write = +[](char *Data, std::size_t Size, std::size_t Count, void *User) -> std::size_t {
|
||||
auto *out = static_cast<std::vector<std::uint8_t> *>(User);
|
||||
out->insert(out->end(), Data, Data + Size * Count);
|
||||
return Size * Count;
|
||||
};
|
||||
auto const progress = +[](void *User, curl_off_t, curl_off_t, curl_off_t, curl_off_t) -> int { return (*static_cast<stop_check const *>(User))() ? 1 : 0; };
|
||||
|
||||
::curl_easy_reset(m_platform->curl);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_URL, Url.c_str());
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_USERAGENT, "MaSzyna-editor");
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_NOSIGNAL, 1L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_LOW_SPEED_TIME, 20L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_WRITEFUNCTION, write);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_WRITEDATA, &Out);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_NOPROGRESS, 0L);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_XFERINFOFUNCTION, progress);
|
||||
::curl_easy_setopt(m_platform->curl, CURLOPT_XFERINFODATA, const_cast<stop_check *>(&Stop));
|
||||
|
||||
CURLcode const code = ::curl_easy_perform(m_platform->curl);
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
Error = ::curl_easy_strerror(code);
|
||||
return false;
|
||||
}
|
||||
long status{0};
|
||||
::curl_easy_getinfo(m_platform->curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
if (status != 200)
|
||||
{
|
||||
Error = "HTTP " + std::to_string(status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
client::client() = default;
|
||||
client::~client() = default;
|
||||
|
||||
bool client::get(std::string const &, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &)
|
||||
{
|
||||
Out.clear();
|
||||
Error = "no http client in this build";
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool available()
|
||||
{
|
||||
#if defined(_WIN32) || defined(EU07_WITH_CURL)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace editor_http
|
||||
44
editor/editorHttp.hpp
Normal file
44
editor/editorHttp.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// a minimal blocking https GET for the services the editor fetches its aids from (orthophoto, terrain model)
|
||||
namespace editor_http
|
||||
{
|
||||
|
||||
// true to give the transfer up
|
||||
using stop_check = std::function<bool()>;
|
||||
|
||||
// one instance per worker thread
|
||||
class client
|
||||
{
|
||||
public:
|
||||
client();
|
||||
~client();
|
||||
client(client const &) = delete;
|
||||
client &operator=(client const &) = delete;
|
||||
|
||||
bool get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop);
|
||||
|
||||
private:
|
||||
struct platform;
|
||||
std::unique_ptr<platform> m_platform;
|
||||
};
|
||||
|
||||
// whether this build can fetch anything
|
||||
bool available();
|
||||
|
||||
} // namespace editor_http
|
||||
373
editor/editorNmt.cpp
Normal file
373
editor/editorNmt.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorNmt.hpp"
|
||||
#include "editor/editorHttp.hpp"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// the model at 1 m of the WCS service of geoportal.gov.pl; the name of the model is read from its capabilities
|
||||
char const *const service{"https://mapy.geoportal.gov.pl/wss/service/PZGIK/NMT/GRID1/WCS/DigitalTerrainModelFormatTIFF"};
|
||||
char const *const fallback_coverage{"DTM_PL-KRON86-NH_TIFF"};
|
||||
char const *const cache_folder{"editor/cache/nmt/grid1/"};
|
||||
int constexpr worker_count{2}; // the service is quick, but isn't to be hammered
|
||||
int constexpr border{1}; // pixels of the neighbouring squares fetched around a square, so the heights interpolate across its edges
|
||||
float constexpr no_data{-1000.f}; // the model gives far lower values outside of the country
|
||||
|
||||
// the name of the model among the names the capabilities of the service list: the first which says it's a terrain model
|
||||
std::string coverage_of(std::string const &Capabilities)
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
for (std::size_t at{0}; (at = Capabilities.find("name>", at)) != std::string::npos;)
|
||||
{
|
||||
at += 5;
|
||||
auto const end{Capabilities.find('<', at)};
|
||||
if (end == std::string::npos)
|
||||
break;
|
||||
names.emplace_back(Capabilities.substr(at, end - at));
|
||||
}
|
||||
for (auto const &name : names)
|
||||
{
|
||||
std::string lower{name};
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char const Letter) { return static_cast<char>(std::tolower(Letter)); });
|
||||
if (lower.find("dtm") != std::string::npos || lower.find("nmt") != std::string::npos || lower.find("terrain") != std::string::npos)
|
||||
return name;
|
||||
}
|
||||
return fallback_coverage;
|
||||
}
|
||||
|
||||
// a GeoTIFF as the service gives it: one band of 32-bit floats in strips, not compressed, placed on the grid by its tie point
|
||||
// and pixel scale. false for anything else
|
||||
template <typename Square>
|
||||
bool read_geotiff(std::vector<std::uint8_t> const &Data, Square &Out)
|
||||
{
|
||||
if (Data.size() < 8)
|
||||
return false;
|
||||
bool const little{Data[0] == 'I' && Data[1] == 'I'};
|
||||
if (false == little && false == (Data[0] == 'M' && Data[1] == 'M'))
|
||||
return false;
|
||||
auto const u16 = [&](std::size_t const At) -> std::uint32_t {
|
||||
if (At + 2 > Data.size())
|
||||
return 0;
|
||||
return little ? Data[At] | (Data[At + 1] << 8) : (Data[At] << 8) | Data[At + 1];
|
||||
};
|
||||
auto const u32 = [&](std::size_t const At) -> std::uint32_t {
|
||||
if (At + 4 > Data.size())
|
||||
return 0;
|
||||
return little ? Data[At] | (Data[At + 1] << 8) | (Data[At + 2] << 16) | (static_cast<std::uint32_t>(Data[At + 3]) << 24) :
|
||||
(static_cast<std::uint32_t>(Data[At]) << 24) | (Data[At + 1] << 16) | (Data[At + 2] << 8) | Data[At + 3];
|
||||
};
|
||||
auto const f64 = [&](std::size_t const At) {
|
||||
std::uint64_t bits{0};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
bits |= static_cast<std::uint64_t>(At + i < Data.size() ? Data[At + (little ? i : 7 - i)] : 0) << (8 * i);
|
||||
double value;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
return value;
|
||||
};
|
||||
if (u16(2) != 42)
|
||||
return false;
|
||||
struct entry
|
||||
{
|
||||
std::uint32_t type{0};
|
||||
std::uint32_t count{0};
|
||||
std::size_t at{0}; // of the values: in the entry when they fit in four bytes
|
||||
};
|
||||
std::map<std::uint32_t, entry> entries;
|
||||
auto const ifd{static_cast<std::size_t>(u32(4))};
|
||||
auto const count{u16(ifd)};
|
||||
for (std::uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto const at{ifd + 2 + i * 12};
|
||||
entry value{u16(at + 2), u32(at + 4), at + 8};
|
||||
auto const size{(value.type == 3 ? 2u : value.type == 12 ? 8u : 4u) * value.count};
|
||||
if (size > 4)
|
||||
value.at = u32(at + 8);
|
||||
entries[u16(at)] = value;
|
||||
}
|
||||
auto const number = [&](std::uint32_t const Tag, std::uint32_t const Index) -> std::uint32_t {
|
||||
auto const found{entries.find(Tag)};
|
||||
if (found == entries.end() || Index >= found->second.count)
|
||||
return 0;
|
||||
return found->second.type == 3 ? u16(found->second.at + Index * 2) : u32(found->second.at + Index * 4);
|
||||
};
|
||||
auto const real = [&](std::uint32_t const Tag, std::uint32_t const Index) {
|
||||
auto const found{entries.find(Tag)};
|
||||
return found == entries.end() || Index >= found->second.count ? 0.0 : f64(found->second.at + Index * 8);
|
||||
};
|
||||
auto const width{static_cast<int>(number(256, 0))};
|
||||
auto const height{static_cast<int>(number(257, 0))};
|
||||
if (width < 2 || height < 2 || number(258, 0) != 32 || number(339, 0) != 3 || std::max(1u, number(259, 0)) != 1 || false == entries.contains(273) || false == entries.contains(33550) ||
|
||||
false == entries.contains(33922))
|
||||
return false;
|
||||
auto const rows{std::max(1u, number(278, 0))};
|
||||
Out.width = width;
|
||||
Out.height = height;
|
||||
Out.heights.assign(static_cast<std::size_t>(width) * height, no_data - 1.f);
|
||||
auto const strips{entries[273].count};
|
||||
for (std::uint32_t strip = 0; strip < strips; ++strip)
|
||||
{
|
||||
auto const offset{static_cast<std::size_t>(number(273, strip))};
|
||||
auto const first{strip * rows};
|
||||
for (std::uint32_t row = first; row < std::min<std::uint32_t>(first + rows, height); ++row)
|
||||
for (int column = 0; column < width; ++column)
|
||||
{
|
||||
auto const bits{u32(offset + ((row - first) * width + column) * 4)};
|
||||
float value;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
Out.heights[static_cast<std::size_t>(row) * width + column] = value;
|
||||
}
|
||||
}
|
||||
Out.pixel = real(33550, 0);
|
||||
Out.east = real(33922, 3) - real(33922, 0) * Out.pixel;
|
||||
Out.north = real(33922, 4) + real(33922, 1) * Out.pixel;
|
||||
return Out.pixel > 0.0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
editor_nmt::editor_nmt() = default;
|
||||
|
||||
editor_nmt::~editor_nmt()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
m_stop = true;
|
||||
m_queue.clear();
|
||||
}
|
||||
m_wake.notify_all();
|
||||
for (auto &worker : m_workers)
|
||||
if (worker.joinable())
|
||||
worker.join();
|
||||
}
|
||||
|
||||
bool editor_nmt::can_download()
|
||||
{
|
||||
return editor_http::available();
|
||||
}
|
||||
|
||||
void editor_nmt::place(double const North, double const East)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
m_north = North;
|
||||
m_east = East;
|
||||
}
|
||||
|
||||
bool editor_nmt::placed() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
return m_north != 0.0 || m_east != 0.0;
|
||||
}
|
||||
|
||||
editor_nmt::key editor_nmt::key_of(double const East, double const North) const
|
||||
{
|
||||
return {static_cast<int>(std::floor(East / square_size)), static_cast<int>(std::floor(North / square_size))};
|
||||
}
|
||||
|
||||
bool editor_nmt::height(double const X, double const Z, double &Height) const
|
||||
{
|
||||
std::shared_ptr<square const> found;
|
||||
double east, north;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
east = m_east - X;
|
||||
north = m_north + Z;
|
||||
auto const it{m_squares.find(key_of(east, north))};
|
||||
if (it == m_squares.end())
|
||||
return false;
|
||||
found = it->second;
|
||||
}
|
||||
auto const &grid{*found};
|
||||
if (grid.failed)
|
||||
return false;
|
||||
// the values lie in the middles of the pixels
|
||||
auto const column{(east - grid.east) / grid.pixel - 0.5};
|
||||
auto const row{(grid.north - north) / grid.pixel - 0.5};
|
||||
auto const c0{std::clamp(static_cast<int>(std::floor(column)), 0, grid.width - 2)};
|
||||
auto const r0{std::clamp(static_cast<int>(std::floor(row)), 0, grid.height - 2)};
|
||||
auto const fc{std::clamp(column - c0, 0.0, 1.0)};
|
||||
auto const fr{std::clamp(row - r0, 0.0, 1.0)};
|
||||
auto const at = [&](int const C, int const R) { return grid.heights[static_cast<std::size_t>(R) * grid.width + C]; };
|
||||
float const corners[4]{at(c0, r0), at(c0 + 1, r0), at(c0, r0 + 1), at(c0 + 1, r0 + 1)};
|
||||
if (std::all_of(std::begin(corners), std::end(corners), [](float const Value) { return std::isfinite(Value) && Value > no_data; }))
|
||||
{
|
||||
Height = (corners[0] * (1.0 - fc) + corners[1] * fc) * (1.0 - fr) + (corners[2] * (1.0 - fc) + corners[3] * fc) * fr;
|
||||
return true;
|
||||
}
|
||||
// at the edge of the data: the nearest value there is
|
||||
auto const nearest{corners[(fr > 0.5 ? 2 : 0) + (fc > 0.5 ? 1 : 0)]};
|
||||
if (false == std::isfinite(nearest) || nearest <= no_data)
|
||||
return false;
|
||||
Height = nearest;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_nmt::request(glm::dvec2 const &Min, glm::dvec2 const &Max)
|
||||
{
|
||||
std::vector<key> wanted;
|
||||
bool all{true};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
auto const low{key_of(m_east - Max.x, m_north + Min.y)};
|
||||
auto const high{key_of(m_east - Min.x, m_north + Max.y)};
|
||||
auto const middle{key_of(m_east - 0.5 * (Min.x + Max.x), m_north + 0.5 * (Min.y + Max.y))};
|
||||
for (auto e = low.first; e <= high.first; ++e)
|
||||
for (auto n = low.second; n <= high.second; ++n)
|
||||
{
|
||||
key const square{e, n};
|
||||
if (m_squares.contains(square))
|
||||
continue;
|
||||
all = false;
|
||||
if (false == m_queued.contains(square))
|
||||
wanted.push_back(square);
|
||||
}
|
||||
std::sort(wanted.begin(), wanted.end(), [&](key const &Left, key const &Right) {
|
||||
auto const distance = [&](key const &Square) { return std::abs(Square.first - middle.first) + std::abs(Square.second - middle.second); };
|
||||
return distance(Left) < distance(Right);
|
||||
});
|
||||
for (auto const &square : wanted)
|
||||
{
|
||||
m_queued.insert(square);
|
||||
m_queue.push_back(square);
|
||||
}
|
||||
if (false == wanted.empty() && m_workers.empty())
|
||||
for (int i = 0; i < worker_count; ++i)
|
||||
m_workers.emplace_back([this]() { work(); });
|
||||
}
|
||||
if (false == wanted.empty())
|
||||
m_wake.notify_all();
|
||||
return all;
|
||||
}
|
||||
|
||||
std::size_t editor_nmt::waiting() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
return m_queued.size();
|
||||
}
|
||||
|
||||
std::string editor_nmt::error() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
return m_error;
|
||||
}
|
||||
|
||||
void editor_nmt::clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
m_queue.clear();
|
||||
m_queued.clear();
|
||||
m_squares.clear();
|
||||
}
|
||||
|
||||
void editor_nmt::work()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
key square;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{m_mutex};
|
||||
m_wake.wait(lock, [this]() { return m_stop || false == m_queue.empty(); });
|
||||
if (m_stop)
|
||||
return;
|
||||
square = m_queue.front();
|
||||
m_queue.pop_front();
|
||||
}
|
||||
std::string error;
|
||||
auto fetched{fetch(square, error)};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
if (false == m_queued.contains(square))
|
||||
continue; // cleared meanwhile
|
||||
m_queued.erase(square);
|
||||
m_squares[square] = std::move(fetched);
|
||||
m_error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<editor_nmt::square const> editor_nmt::fetch(key const &Key, std::string &Error)
|
||||
{
|
||||
auto result{std::make_shared<square>()};
|
||||
std::error_code fault;
|
||||
fs::create_directories(cache_folder, fault);
|
||||
auto const file{std::string{cache_folder} + std::to_string(Key.first) + "_" + std::to_string(Key.second) + ".tif"};
|
||||
std::vector<std::uint8_t> data;
|
||||
if (std::ifstream cached{file, std::ios::binary}; cached.is_open())
|
||||
{
|
||||
data.assign(std::istreambuf_iterator<char>(cached), std::istreambuf_iterator<char>());
|
||||
if (read_geotiff(data, *result))
|
||||
return result;
|
||||
// damaged by a fetch broken off: fetched again
|
||||
fs::remove(file, fault);
|
||||
}
|
||||
if (false == can_download())
|
||||
{
|
||||
Error = "this build can't fetch from the internet";
|
||||
result->failed = true;
|
||||
return result;
|
||||
}
|
||||
editor_http::client client;
|
||||
auto const stop = [this]() {
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
return m_stop;
|
||||
};
|
||||
std::string coverage;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
coverage = m_coverage;
|
||||
}
|
||||
if (coverage.empty())
|
||||
{
|
||||
std::vector<std::uint8_t> capabilities;
|
||||
std::string unused;
|
||||
coverage = client.get(std::string{service} + "?SERVICE=WCS&VERSION=1.0.0&REQUEST=GetCapabilities", capabilities, unused, stop) ?
|
||||
coverage_of(std::string(capabilities.begin(), capabilities.end())) :
|
||||
fallback_coverage;
|
||||
std::lock_guard<std::mutex> lock{m_mutex};
|
||||
m_coverage = coverage;
|
||||
}
|
||||
// the square with a pixel of each neighbour around it
|
||||
auto const pixels{static_cast<int>(square_size) + 2 * border};
|
||||
auto const east{Key.first * square_size - border};
|
||||
auto const north{Key.second * square_size - border};
|
||||
char bbox[128];
|
||||
std::snprintf(bbox, sizeof(bbox), "%.0f,%.0f,%.0f,%.0f", east, north, east + pixels, north + pixels);
|
||||
auto const url{std::string{service} + "?SERVICE=WCS&VERSION=1.0.0&REQUEST=GetCoverage&COVERAGE=" + coverage + "&CRS=EPSG:2180&RESPONSE_CRS=EPSG:2180&BBOX=" + bbox +
|
||||
"&WIDTH=" + std::to_string(pixels) + "&HEIGHT=" + std::to_string(pixels) + "&FORMAT=GEOTIFF"};
|
||||
for (int attempt = 0; attempt < 3; ++attempt)
|
||||
{
|
||||
if (client.get(url, data, Error, stop) && read_geotiff(data, *result))
|
||||
{
|
||||
if (std::ofstream out{file, std::ios::binary}; out.is_open())
|
||||
out.write(reinterpret_cast<char const *>(data.data()), static_cast<std::streamsize>(data.size()));
|
||||
Error.clear();
|
||||
return result;
|
||||
}
|
||||
if (stop())
|
||||
break;
|
||||
if (Error.empty())
|
||||
Error = "the service gave no terrain model";
|
||||
}
|
||||
WriteLog("Editor: NMT square " + std::to_string(Key.first) + "," + std::to_string(Key.second) + " not fetched: " + Error, logtype::generic);
|
||||
result->failed = true;
|
||||
return result;
|
||||
}
|
||||
87
editor/editorNmt.hpp
Normal file
87
editor/editorNmt.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// heights of the ground from the digital terrain model of GUGiK (NMT, the WCS service of geoportal.gov.pl): squares of the
|
||||
// EPSG:2180 grid fetched in the background at 1 m, kept on the disk and sampled in the coordinates of the scenery. the
|
||||
// scenery is placed on the grid by the PUWG 1992 coordinates of its point (0,0,0), as the orthophoto is: easting = east - x,
|
||||
// northing = north + z. the heights are those of the model, metres over the sea
|
||||
class editor_nmt
|
||||
{
|
||||
public:
|
||||
editor_nmt();
|
||||
~editor_nmt();
|
||||
editor_nmt(editor_nmt const &) = delete;
|
||||
editor_nmt &operator=(editor_nmt const &) = delete;
|
||||
|
||||
// where the point (0,0,0) of the scenery lies in PUWG 1992; the squares fetched stay, only the mapping changes
|
||||
void place(double const North, double const East);
|
||||
// a scenery at (0,0) isn't placed anywhere
|
||||
bool placed() const;
|
||||
// the height of the model at the point of the scenery; false where its square isn't in yet or the model has no data
|
||||
bool height(double const X, double const Z, double &Height) const;
|
||||
// the squares under the rectangle of the scenery (x, z) asked for in the background, the ones nearest its middle first;
|
||||
// true if all of them are in already, those the service couldn't give included
|
||||
bool request(glm::dvec2 const &Min, glm::dvec2 const &Max);
|
||||
// squares asked for and not in yet
|
||||
std::size_t waiting() const;
|
||||
// what the last fetch said went wrong, empty when it went right
|
||||
std::string error() const;
|
||||
// the squares dropped from the memory, and those waiting; the files on the disk stay
|
||||
void clear();
|
||||
// whether this build can fetch from the service at all
|
||||
static bool can_download();
|
||||
|
||||
// side of a square of the grid, m
|
||||
static double constexpr square_size{500.0};
|
||||
|
||||
private:
|
||||
using key = std::pair<int, int>; // easting and northing index of the square on the grid
|
||||
struct square
|
||||
{
|
||||
double east{0.0}; // of the outer edge of the first column
|
||||
double north{0.0}; // of the outer edge of the first row, the northernmost
|
||||
double pixel{1.0};
|
||||
int width{0};
|
||||
int height{0};
|
||||
std::vector<float> heights; // rows from the north, columns from the west
|
||||
bool failed{false}; // the service gave nothing usable: the square has no data
|
||||
};
|
||||
|
||||
key key_of(double const East, double const North) const;
|
||||
void work();
|
||||
std::shared_ptr<square const> fetch(key const &Key, std::string &Error);
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_wake;
|
||||
std::vector<std::thread> m_workers;
|
||||
std::deque<key> m_queue;
|
||||
std::set<key> m_queued;
|
||||
std::map<key, std::shared_ptr<square const>> m_squares;
|
||||
std::string m_coverage; // the name of the model in the service, read from its capabilities once
|
||||
std::string m_error;
|
||||
double m_north{0.0};
|
||||
double m_east{0.0};
|
||||
bool m_stop{false};
|
||||
};
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorOrthophoto.hpp"
|
||||
#include "editor/editorHttp.hpp"
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "scene/scene.h"
|
||||
@@ -19,11 +20,6 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "imgui/imgui.h"
|
||||
#include "stb/stb_image.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <winhttp.h>
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -49,212 +45,14 @@ constexpr std::size_t upload_budget{16u << 20}; // bytes of texture data uploade
|
||||
constexpr std::size_t vertex_budget{60000}; // ImGui uses 16-bit indices and not every backend supports vertex offsets
|
||||
constexpr float near_w{0.5f};
|
||||
|
||||
using stop_check = std::function<bool()>;
|
||||
using stop_check = editor_http::stop_check;
|
||||
using http_client = editor_http::client;
|
||||
|
||||
double steady_seconds()
|
||||
{
|
||||
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// minimal blocking https GET; one instance per worker thread
|
||||
|
||||
class http_client
|
||||
{
|
||||
public:
|
||||
http_client();
|
||||
~http_client();
|
||||
http_client(http_client const &) = delete;
|
||||
http_client &operator=(http_client const &) = delete;
|
||||
|
||||
bool get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop);
|
||||
|
||||
private:
|
||||
#if defined(_WIN32)
|
||||
HINTERNET m_session{nullptr};
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
CURL *m_curl{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
http_client::http_client()
|
||||
{
|
||||
wchar_t const *agent = L"MaSzyna-editor";
|
||||
#ifdef WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
|
||||
m_session = ::WinHttpOpen(agent, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
#endif
|
||||
if (m_session == nullptr) // automatic proxy needs windows 8.1+
|
||||
m_session = ::WinHttpOpen(agent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (m_session != nullptr)
|
||||
::WinHttpSetTimeouts(m_session, 10000, 10000, 15000, 20000);
|
||||
}
|
||||
|
||||
http_client::~http_client()
|
||||
{
|
||||
if (m_session != nullptr)
|
||||
::WinHttpCloseHandle(m_session);
|
||||
}
|
||||
|
||||
bool http_client::get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop)
|
||||
{
|
||||
Out.clear();
|
||||
if (m_session == nullptr)
|
||||
{
|
||||
Error = "WinHttpOpen failed (" + std::to_string(::GetLastError()) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring const url(Url.begin(), Url.end()); // the requests are plain ascii
|
||||
URL_COMPONENTS parts{};
|
||||
parts.dwStructSize = sizeof(parts);
|
||||
parts.dwSchemeLength = static_cast<DWORD>(-1);
|
||||
parts.dwHostNameLength = static_cast<DWORD>(-1);
|
||||
parts.dwUrlPathLength = static_cast<DWORD>(-1);
|
||||
parts.dwExtraInfoLength = static_cast<DWORD>(-1);
|
||||
if (!::WinHttpCrackUrl(url.c_str(), 0, 0, &parts))
|
||||
{
|
||||
Error = "invalid url";
|
||||
return false;
|
||||
}
|
||||
std::wstring const host(parts.lpszHostName, parts.dwHostNameLength);
|
||||
std::wstring const path = std::wstring(parts.lpszUrlPath, parts.dwUrlPathLength) + std::wstring(parts.lpszExtraInfo, parts.dwExtraInfoLength);
|
||||
|
||||
HINTERNET connection = ::WinHttpConnect(m_session, host.c_str(), parts.nPort, 0);
|
||||
HINTERNET request = connection ? ::WinHttpOpenRequest(connection, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
parts.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0)
|
||||
: nullptr;
|
||||
auto const close = [&]() {
|
||||
if (request != nullptr)
|
||||
::WinHttpCloseHandle(request);
|
||||
if (connection != nullptr)
|
||||
::WinHttpCloseHandle(connection);
|
||||
};
|
||||
|
||||
if (request == nullptr || !::WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !::WinHttpReceiveResponse(request, nullptr))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD status{0};
|
||||
DWORD statussize{sizeof(status)};
|
||||
::WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &statussize, WINHTTP_NO_HEADER_INDEX);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (Stop())
|
||||
{
|
||||
Error = "cancelled";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
DWORD available{0};
|
||||
if (!::WinHttpQueryDataAvailable(request, &available))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
if (available == 0)
|
||||
break;
|
||||
std::size_t const offset = Out.size();
|
||||
Out.resize(offset + available);
|
||||
DWORD received{0};
|
||||
if (!::WinHttpReadData(request, Out.data() + offset, available, &received))
|
||||
{
|
||||
Error = "WinHTTP error " + std::to_string(::GetLastError());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
Out.resize(offset + received);
|
||||
}
|
||||
close();
|
||||
|
||||
if (status != 200)
|
||||
{
|
||||
Error = "HTTP " + std::to_string(status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(EU07_WITH_CURL)
|
||||
|
||||
http_client::http_client()
|
||||
{
|
||||
static std::once_flag initialized;
|
||||
std::call_once(initialized, []() { ::curl_global_init(CURL_GLOBAL_DEFAULT); });
|
||||
m_curl = ::curl_easy_init();
|
||||
}
|
||||
|
||||
http_client::~http_client()
|
||||
{
|
||||
if (m_curl != nullptr)
|
||||
::curl_easy_cleanup(m_curl);
|
||||
}
|
||||
|
||||
bool http_client::get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &Stop)
|
||||
{
|
||||
Out.clear();
|
||||
if (m_curl == nullptr)
|
||||
{
|
||||
Error = "curl_easy_init failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const write = +[](char *Data, std::size_t Size, std::size_t Count, void *User) -> std::size_t {
|
||||
auto *out = static_cast<std::vector<std::uint8_t> *>(User);
|
||||
out->insert(out->end(), Data, Data + Size * Count);
|
||||
return Size * Count;
|
||||
};
|
||||
auto const progress = +[](void *User, curl_off_t, curl_off_t, curl_off_t, curl_off_t) -> int { return (*static_cast<stop_check const *>(User))() ? 1 : 0; };
|
||||
|
||||
::curl_easy_reset(m_curl);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_URL, Url.c_str());
|
||||
::curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "MaSzyna-editor");
|
||||
::curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_TIME, 20L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &Out);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progress);
|
||||
::curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, const_cast<stop_check *>(&Stop));
|
||||
|
||||
CURLcode const code = ::curl_easy_perform(m_curl);
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
Error = ::curl_easy_strerror(code);
|
||||
return false;
|
||||
}
|
||||
long status{0};
|
||||
::curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
if (status != 200)
|
||||
{
|
||||
Error = "HTTP " + std::to_string(status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
http_client::http_client() = default;
|
||||
http_client::~http_client() = default;
|
||||
|
||||
bool http_client::get(std::string const &, std::vector<std::uint8_t> &Out, std::string &Error, stop_check const &)
|
||||
{
|
||||
Out.clear();
|
||||
Error = "no http client in this build";
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user