mbtiles不存在快速失败
This commit is contained in:
@@ -238,6 +238,7 @@ void Map_Tile_Source_Config::from_base_json(const Psc::JSON* that_json) {
|
||||
key = read_string_field(that_json, "key");
|
||||
name = read_string_field(that_json, "name");
|
||||
tile_type = read_optional_string_field(that_json, "tile_type", "imagery");
|
||||
request_method = read_optional_string_field(that_json, "request_method", "GET");
|
||||
url = read_optional_string_field(that_json, "url");
|
||||
resource.from_base_json(that_json->get("resource"));
|
||||
}
|
||||
@@ -246,6 +247,7 @@ Psc::JSON Map_Tile_Source_Config::to_base_json() const {
|
||||
ret.append({"key", key});
|
||||
ret.append({"name", name});
|
||||
ret.append({"tile_type", tile_type});
|
||||
ret.append({"request_method", request_method});
|
||||
if (!url.empty()) {
|
||||
ret.append({"url", url});
|
||||
}
|
||||
@@ -257,6 +259,7 @@ Psc::JSON Map_Tile_Source_Config::to_public_json(std::string_view local_url) con
|
||||
ret.append({"key", key});
|
||||
ret.append({"name", name});
|
||||
ret.append({"tile_type", tile_type});
|
||||
ret.append({"request_method", request_method});
|
||||
ret.append({"resource", resource.to_public_json(local() ? local_url : url)});
|
||||
return ret;
|
||||
}
|
||||
@@ -292,30 +295,27 @@ void Map_Resources_Config::from_base_json(const Psc::JSON* that_json) {
|
||||
if (current_imagery_key.empty()) {
|
||||
current_imagery_key = "imagery";
|
||||
}
|
||||
imagery.from_base_json(that_json->get("imagery"));
|
||||
google_imagery.from_base_json(that_json->get("google_imagery"));
|
||||
terrain.from_base_json(that_json->get("terrain"));
|
||||
tile_sources = read_optional_tile_source_array(that_json, "tile_sources");
|
||||
if (!is_imagery_key(current_imagery_key)) {
|
||||
current_imagery_key = "imagery";
|
||||
auto source = std::ranges::find_if(tile_sources, [](const Map_Tile_Source_Config& item) {
|
||||
return item.tile_type == "imagery";
|
||||
});
|
||||
current_imagery_key = source == tile_sources.end() ? "" : source->key;
|
||||
}
|
||||
}
|
||||
Psc::JSON Map_Resources_Config::to_base_json() const {
|
||||
auto ret = Psc::JSON::object();
|
||||
ret.append({"current_imagery_key", current_imagery_key});
|
||||
ret.append({"imagery", imagery.to_base_json()});
|
||||
ret.append({"google_imagery", google_imagery.to_base_json()});
|
||||
ret.append({"terrain", terrain.to_base_json()});
|
||||
ret.append(write_tile_source_array("tile_sources", tile_sources));
|
||||
return ret;
|
||||
}
|
||||
Psc::JSON Map_Resources_Config::to_public_json() const {
|
||||
auto ret = Psc::JSON::object();
|
||||
auto imagery_json = imagery.to_public_json("/map/imagery/{z}/{x}/{y}");
|
||||
auto google_imagery_json = google_imagery.to_public_json("/tiles/{z}/{x}/{y}");
|
||||
auto sources = Psc::JSON::array();
|
||||
auto imagery_sources = Psc::JSON::array();
|
||||
auto terrain_sources = Psc::JSON::array();
|
||||
auto terrain = Psc::JSON::object();
|
||||
bool has_terrain = false;
|
||||
for (const auto& source : tile_sources) {
|
||||
auto url = "/map/tile_sources/" + source.key + "/{z}/{x}/{y}";
|
||||
auto public_source = source.to_public_json(url);
|
||||
@@ -325,15 +325,19 @@ Psc::JSON Map_Resources_Config::to_public_json() const {
|
||||
}
|
||||
else if (source.tile_type == "terrain") {
|
||||
terrain_sources.append(public_source);
|
||||
if (!has_terrain) {
|
||||
terrain = *public_source.get("resource");
|
||||
has_terrain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.append({"current_imagery_key", current_imagery_key});
|
||||
ret.append({"tile_sources", sources});
|
||||
ret.append({"imagery_sources", imagery_sources});
|
||||
ret.append({"terrain_sources", terrain_sources});
|
||||
ret.append({"imagery", imagery_json});
|
||||
ret.append({"google_imagery", google_imagery_json});
|
||||
ret.append({"terrain", terrain.to_public_json("/map/terrain/{z}/{x}/{y}")});
|
||||
if (has_terrain) {
|
||||
ret.append({"terrain", std::move(terrain)});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
bool Map_Resources_Config::is_imagery_key(std::string_view key) const {
|
||||
|
||||
@@ -147,6 +147,7 @@ struct Map_Tile_Source_Config {
|
||||
std::string key;
|
||||
std::string name;
|
||||
std::string tile_type = "imagery";
|
||||
std::string request_method = "GET";
|
||||
std::string url;
|
||||
Map_Tile_Config resource;
|
||||
[[nodiscard]] bool local() const {
|
||||
@@ -158,9 +159,6 @@ struct Map_Tile_Source_Config {
|
||||
};
|
||||
struct Map_Resources_Config {
|
||||
std::string current_imagery_key = "imagery";
|
||||
Map_Tile_Config imagery;
|
||||
Map_Tile_Config google_imagery;
|
||||
Map_Tile_Config terrain;
|
||||
std::vector<Map_Tile_Source_Config> tile_sources;
|
||||
void from_base_json(const Psc::JSON* that_json);
|
||||
[[nodiscard]] Psc::JSON to_base_json() const;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "Global.h"
|
||||
#include "Performance_Monitor.h"
|
||||
#include "psc_global_include/codec.h"
|
||||
#include <SQLiteCpp/Database.h>
|
||||
#include <SQLiteCpp/Statement.h>
|
||||
#include <algorithm>
|
||||
#include <asio/co_spawn.hpp>
|
||||
#include <asio/thread_pool.hpp>
|
||||
#include <asio/use_awaitable.hpp>
|
||||
#include "Global.h"
|
||||
#include "Performance_Monitor.h"
|
||||
#include "psc_global_include/codec.h"
|
||||
#if defined(ASIO_HAS_FILE)
|
||||
#include <asio/random_access_file.hpp>
|
||||
#include <asio/read_at.hpp>
|
||||
@@ -29,11 +29,7 @@
|
||||
#include <vector>
|
||||
namespace {
|
||||
constexpr std::uint64_t kMaxTileFileSize = 32ull * 1024ull * 1024ull;
|
||||
enum class Tile_Load_Status {
|
||||
Ready,
|
||||
Not_Found,
|
||||
Not_Modified
|
||||
};
|
||||
enum class Tile_Load_Status { Ready, Not_Found, Not_Modified };
|
||||
struct Tile_Request {
|
||||
std::uint32_t z;
|
||||
std::uint32_t x;
|
||||
@@ -92,10 +88,12 @@ std::optional<File_Cache_Info> get_file_cache_info(const std::filesystem::path&
|
||||
result.last_modified = format_http_date(result.modified_time);
|
||||
return result;
|
||||
}
|
||||
File_Cache_Info make_mbtiles_cache_info(const File_Cache_Info& file_info, const Tile_Request& request, std::uint64_t content_size) {
|
||||
File_Cache_Info make_mbtiles_cache_info(const File_Cache_Info& file_info, const Tile_Request& request,
|
||||
std::uint64_t content_size) {
|
||||
auto result = file_info;
|
||||
result.size = content_size;
|
||||
result.etag = std::format("W/\"{:x}-{:x}-{}-{}-{}-{:x}\"", static_cast<std::uint64_t>(result.modified_time), file_info.size, request.z, request.x, request.y, content_size);
|
||||
result.etag = std::format("W/\"{:x}-{:x}-{}-{}-{}-{:x}\"", static_cast<std::uint64_t>(result.modified_time),
|
||||
file_info.size, request.z, request.x, request.y, content_size);
|
||||
return result;
|
||||
}
|
||||
std::string_view trim_http_ows(std::string_view value) {
|
||||
@@ -133,7 +131,8 @@ bool is_not_modified(const Tile_Cache_Request& request, const File_Cache_Info& i
|
||||
}
|
||||
return !request.if_modified_since.empty() && request.if_modified_since == info.last_modified;
|
||||
}
|
||||
void add_tile_cache_headers(const drogon::HttpResponsePtr& response, const File_Cache_Info& info, bool include_content_length) {
|
||||
void add_tile_cache_headers(const drogon::HttpResponsePtr& response, const File_Cache_Info& info,
|
||||
bool include_content_length) {
|
||||
response->addHeader("Cache-Control", "public, max-age=86400");
|
||||
response->addHeader("ETag", info.etag);
|
||||
response->addHeader("Last-Modified", info.last_modified);
|
||||
@@ -145,9 +144,7 @@ bool is_unsigned_path_component(std::string_view value) {
|
||||
if (value.empty()) {
|
||||
return false;
|
||||
}
|
||||
return std::ranges::all_of(value, [](unsigned char ch) {
|
||||
return std::isdigit(ch) != 0;
|
||||
});
|
||||
return std::ranges::all_of(value, [](unsigned char ch) { return std::isdigit(ch) != 0; });
|
||||
}
|
||||
std::uint32_t parse_uint32_component(std::string_view value) {
|
||||
std::uint64_t result = 0;
|
||||
@@ -159,14 +156,16 @@ std::uint32_t parse_uint32_component(std::string_view value) {
|
||||
}
|
||||
return static_cast<std::uint32_t>(result);
|
||||
}
|
||||
std::optional<Tile_Request> parse_tile_request(std::string_view path, std::string_view prefix, std::string_view default_extension) {
|
||||
std::optional<Tile_Request> parse_tile_request(std::string_view path, std::string_view prefix,
|
||||
std::string_view default_extension) {
|
||||
if (!path.starts_with(prefix)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
path.remove_prefix(prefix.size());
|
||||
auto first = path.find('/');
|
||||
auto second = first == std::string_view::npos ? std::string_view::npos : path.find('/', first + 1);
|
||||
if (first == std::string_view::npos || second == std::string_view::npos || path.find('/', second + 1) != std::string_view::npos) {
|
||||
if (first == std::string_view::npos || second == std::string_view::npos ||
|
||||
path.find('/', second + 1) != std::string_view::npos) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto z = path.substr(0, first);
|
||||
@@ -174,16 +173,18 @@ std::optional<Tile_Request> parse_tile_request(std::string_view path, std::strin
|
||||
auto y_with_extension = path.substr(second + 1);
|
||||
auto dot = y_with_extension.rfind('.');
|
||||
auto y = dot == std::string_view::npos ? y_with_extension : y_with_extension.substr(0, dot);
|
||||
auto extension = dot == std::string_view::npos ? std::string(default_extension) : std::string(y_with_extension.substr(dot));
|
||||
if (!is_unsigned_path_component(z) || !is_unsigned_path_component(x) || !is_unsigned_path_component(y) || extension != default_extension) {
|
||||
auto extension =
|
||||
dot == std::string_view::npos ? std::string(default_extension) : std::string(y_with_extension.substr(dot));
|
||||
if (!is_unsigned_path_component(z) || !is_unsigned_path_component(x) || !is_unsigned_path_component(y) ||
|
||||
extension != default_extension) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Tile_Request{parse_uint32_component(z), parse_uint32_component(x), parse_uint32_component(y), extension};
|
||||
}
|
||||
class Tile_Path_Resolver {
|
||||
public:
|
||||
explicit Tile_Path_Resolver(const Map_Tile_Config& config)
|
||||
: config_(config), directories_(config.get_true_directories()) {
|
||||
explicit Tile_Path_Resolver(const Map_Tile_Config& config) :
|
||||
config_(config), directories_(config.get_true_directories()) {
|
||||
if (config_.path_layout != "z/x/y" && config_.path_layout != "z/y/x") {
|
||||
throw std::invalid_argument("invalid tile path_layout: " + config_.path_layout);
|
||||
}
|
||||
@@ -219,46 +220,57 @@ private:
|
||||
};
|
||||
class Mbtiles_File {
|
||||
public:
|
||||
explicit Mbtiles_File(std::string path)
|
||||
: path_(std::move(path)), db_(std::make_unique<SQLite::Database>(path_, SQLite::OPEN_READONLY)) {}
|
||||
std::optional<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request, std::string_view content_type) {
|
||||
explicit Mbtiles_File(std::string path) : path_(std::move(path)) {
|
||||
auto file_info = get_file_cache_info(Psc::path_from_utf8(path_));
|
||||
if (!file_info) {
|
||||
return std::nullopt;
|
||||
std::string str = "找不到地图:" + VAR_STR_1(path_);
|
||||
Psc::fail_fast(str);
|
||||
}
|
||||
std::lock_guard lock(mtx_);
|
||||
SQLite::Statement statement(*db_, "SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ? LIMIT 1");
|
||||
statement.bind(1, static_cast<int>(request.z));
|
||||
statement.bind(2, static_cast<int>(request.x));
|
||||
statement.bind(3, static_cast<int>(request.y));
|
||||
if (!statement.executeStep()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto column = statement.getColumn(0);
|
||||
auto bytes = column.getBytes();
|
||||
if (bytes < 0 || static_cast<std::uint64_t>(bytes) > kMaxTileFileSize) {
|
||||
throw std::runtime_error("mbtiles tile is too large: " + path_);
|
||||
}
|
||||
auto cache_info = make_mbtiles_cache_info(*file_info, request, static_cast<std::uint64_t>(bytes));
|
||||
if (is_not_modified(cache_request, cache_info)) {
|
||||
return Tile_Load_Result{Tile_Load_Status::Not_Modified, path_, {}, std::string(content_type), std::move(cache_info)};
|
||||
}
|
||||
std::string content;
|
||||
if (cache_request.include_body) {
|
||||
auto data = static_cast<const char*>(column.getBlob());
|
||||
content.assign(data, static_cast<std::size_t>(bytes));
|
||||
}
|
||||
return Tile_Load_Result{Tile_Load_Status::Ready, path_, std::move(content), std::string(content_type), std::move(cache_info)};
|
||||
db_ = std::make_unique<SQLite::Database>(path_, SQLite::OPEN_READONLY);
|
||||
}
|
||||
std::optional<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request,
|
||||
std::string_view content_type);
|
||||
private:
|
||||
std::string path_;
|
||||
std::unique_ptr<SQLite::Database> db_;
|
||||
std::mutex mtx_;
|
||||
};
|
||||
std::optional<Tile_Load_Result> Mbtiles_File::load(const Tile_Request& request, const Tile_Cache_Request& cache_request,
|
||||
std::string_view content_type) {
|
||||
auto file_info = get_file_cache_info(Psc::path_from_utf8(path_));
|
||||
if (!file_info) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::lock_guard lock(mtx_);
|
||||
SQLite::Statement statement(
|
||||
*db_, "SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ? LIMIT 1");
|
||||
statement.bind(1, static_cast<int>(request.z));
|
||||
statement.bind(2, static_cast<int>(request.x));
|
||||
statement.bind(3, static_cast<int>(request.y));
|
||||
if (!statement.executeStep()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto column = statement.getColumn(0);
|
||||
auto bytes = column.getBytes();
|
||||
if (bytes < 0 || static_cast<std::uint64_t>(bytes) > kMaxTileFileSize) {
|
||||
throw std::runtime_error("mbtiles tile is too large: " + path_);
|
||||
}
|
||||
auto cache_info = make_mbtiles_cache_info(*file_info, request, static_cast<std::uint64_t>(bytes));
|
||||
if (is_not_modified(cache_request, cache_info)) {
|
||||
return Tile_Load_Result{
|
||||
Tile_Load_Status::Not_Modified, path_, {}, std::string(content_type), std::move(cache_info)};
|
||||
}
|
||||
std::string content;
|
||||
if (cache_request.include_body) {
|
||||
auto data = static_cast<const char*>(column.getBlob());
|
||||
content.assign(data, static_cast<std::size_t>(bytes));
|
||||
}
|
||||
return Tile_Load_Result{Tile_Load_Status::Ready, path_, std::move(content), std::string(content_type),
|
||||
std::move(cache_info)};
|
||||
}
|
||||
class Tile_Source {
|
||||
public:
|
||||
explicit Tile_Source(Map_Tile_Config config)
|
||||
: config_(std::move(config)) {
|
||||
explicit Tile_Source(Map_Tile_Config config) : config_(std::move(config)) {
|
||||
if (config_.extension.empty() || config_.extension.front() != '.') {
|
||||
throw std::invalid_argument("invalid tile extension: " + config_.extension);
|
||||
}
|
||||
@@ -287,7 +299,8 @@ public:
|
||||
return config_;
|
||||
}
|
||||
private:
|
||||
asio::awaitable<Tile_Load_Result> load_directory_tile(const Tile_Request& request, const Tile_Cache_Request& cache_request) const {
|
||||
asio::awaitable<Tile_Load_Result> load_directory_tile(const Tile_Request& request,
|
||||
const Tile_Cache_Request& cache_request) const {
|
||||
auto path = resolver_->find_file(request);
|
||||
if (!path) {
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||
@@ -302,7 +315,8 @@ private:
|
||||
auto generic_path = path->generic_string();
|
||||
auto content_type = httplib::detail::find_content_type(generic_path, {}, "application/octet-stream");
|
||||
if (is_not_modified(cache_request, *cache_info)) {
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Modified, std::move(generic_path), {}, std::move(content_type), *cache_info};
|
||||
co_return Tile_Load_Result{
|
||||
Tile_Load_Status::Not_Modified, std::move(generic_path), {}, std::move(content_type), *cache_info};
|
||||
}
|
||||
std::string content;
|
||||
if (cache_request.include_body) {
|
||||
@@ -311,17 +325,24 @@ private:
|
||||
asio::random_access_file file(executor, path->generic_string(), asio::file_base::read_only);
|
||||
content.resize(static_cast<std::size_t>(cache_info->size));
|
||||
if (!content.empty()) {
|
||||
co_await asio::async_read_at(file, 0, asio::buffer(content.data(), content.size()), asio::use_awaitable);
|
||||
co_await asio::async_read_at(file, 0, asio::buffer(content.data(), content.size()),
|
||||
asio::use_awaitable);
|
||||
}
|
||||
#else
|
||||
content = co_await read_file_fallback(*path, static_cast<std::size_t>(cache_info->size));
|
||||
#endif
|
||||
}
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Ready, std::move(generic_path), std::move(content), std::move(content_type), *cache_info};
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Ready, std::move(generic_path), std::move(content),
|
||||
std::move(content_type), *cache_info};
|
||||
}
|
||||
asio::awaitable<Tile_Load_Result> load_mbtiles_tile(Tile_Request request, Tile_Cache_Request cache_request) const {
|
||||
auto content_type = httplib::detail::find_content_type("tile" + config_.extension, {}, "application/octet-stream");
|
||||
co_return co_await asio::co_spawn(tile_file_pool(), [this, request, cache_request = std::move(cache_request), content_type = std::move(content_type)]() -> asio::awaitable<Tile_Load_Result> {
|
||||
std::cout << "加载 load_mbtiles_tile" << std::endl;
|
||||
auto content_type =
|
||||
httplib::detail::find_content_type("tile" + config_.extension, {}, "application/octet-stream");
|
||||
co_return co_await asio::co_spawn(
|
||||
tile_file_pool(),
|
||||
[this, request, cache_request = std::move(cache_request),
|
||||
content_type = std::move(content_type)]() -> asio::awaitable<Tile_Load_Result> {
|
||||
for (const auto& file : mbtiles_files_) {
|
||||
auto result = file->load(request, cache_request, content_type);
|
||||
if (result) {
|
||||
@@ -329,14 +350,17 @@ private:
|
||||
}
|
||||
}
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||
}, asio::use_awaitable);
|
||||
},
|
||||
asio::use_awaitable);
|
||||
}
|
||||
Map_Tile_Config config_;
|
||||
std::unique_ptr<Tile_Path_Resolver> resolver_;
|
||||
std::vector<std::unique_ptr<Mbtiles_File>> mbtiles_files_;
|
||||
#if !defined(ASIO_HAS_FILE)
|
||||
static asio::awaitable<std::string> read_file_fallback(std::filesystem::path path, std::size_t size) {
|
||||
co_return co_await asio::co_spawn(tile_file_pool(), [path = std::move(path), size]() -> asio::awaitable<std::string> {
|
||||
co_return co_await asio::co_spawn(
|
||||
tile_file_pool(),
|
||||
[path = std::move(path), size]() -> asio::awaitable<std::string> {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
throw std::runtime_error("failed to open tile file: " + path.generic_string());
|
||||
@@ -349,7 +373,8 @@ private:
|
||||
}
|
||||
}
|
||||
co_return content;
|
||||
}, asio::use_awaitable);
|
||||
},
|
||||
asio::use_awaitable);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
@@ -383,7 +408,8 @@ drogon::HttpResponsePtr make_tile_ok_response(Tile_Load_Result&& result, bool in
|
||||
}
|
||||
return response;
|
||||
}
|
||||
drogon::HttpResponsePtr make_tile_response(Tile_Load_Result&& result, std::string_view request_path, bool include_body) {
|
||||
drogon::HttpResponsePtr make_tile_response(Tile_Load_Result&& result, std::string_view request_path,
|
||||
bool include_body) {
|
||||
switch (result.status) {
|
||||
case Tile_Load_Status::Ready:
|
||||
return make_tile_ok_response(std::move(result), include_body);
|
||||
@@ -404,40 +430,36 @@ drogon::HttpResponsePtr make_tile_read_failed_response(const std::exception& err
|
||||
return response;
|
||||
}
|
||||
Tile_Cache_Request make_cache_request(const drogon::HttpRequestPtr& req) {
|
||||
return Tile_Cache_Request{
|
||||
req->getHeader("if-none-match"),
|
||||
req->getHeader("if-modified-since"),
|
||||
req->method() != drogon::Head
|
||||
};
|
||||
return Tile_Cache_Request{req->getHeader("if-none-match"), req->getHeader("if-modified-since"),
|
||||
req->method() != drogon::Head};
|
||||
}
|
||||
void register_tile_handler(std::string_view regex, std::string_view prefix, std::shared_ptr<Tile_Source> source) {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
std::string(regex),
|
||||
[prefix = std::string(prefix), source = std::move(source)](
|
||||
const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) mutable {
|
||||
auto cache_request = make_cache_request(req);
|
||||
auto request = parse_tile_request(req->path(), prefix, source->config().extension);
|
||||
if (!request) {
|
||||
callback(make_tile_not_found_response(req->path(), cache_request.include_body));
|
||||
return;
|
||||
std::string(regex),
|
||||
[prefix = std::string(prefix), source = std::move(source)](
|
||||
const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) mutable {
|
||||
auto cache_request = make_cache_request(req);
|
||||
auto request = parse_tile_request(req->path(), prefix, source->config().extension);
|
||||
if (!request) {
|
||||
callback(make_tile_not_found_response(req->path(), cache_request.include_body));
|
||||
return;
|
||||
}
|
||||
drogon::async_run([callback = std::move(callback), source, request = std::move(*request),
|
||||
cache_request = std::move(cache_request), path = req->path()]() mutable -> drogon::Task<> {
|
||||
try {
|
||||
auto result = co_await Ecap_Coro::to_drogon(source->load(request, cache_request));
|
||||
callback(make_tile_response(std::move(result), path, cache_request.include_body));
|
||||
}
|
||||
drogon::async_run(
|
||||
[callback = std::move(callback), source, request = std::move(*request), cache_request = std::move(cache_request), path = req->path()]() mutable -> drogon::Task<> {
|
||||
try {
|
||||
auto result = co_await Ecap_Coro::to_drogon(source->load(request, cache_request));
|
||||
callback(make_tile_response(std::move(result), path, cache_request.include_body));
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
if (server_logger) {
|
||||
server_logger->error("tiles", {}, LOG_POS + " " + Psc::platform_2_utf8(error.what()));
|
||||
}
|
||||
callback(make_tile_read_failed_response(error, cache_request.include_body));
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
},
|
||||
{drogon::Get, drogon::Head});
|
||||
catch (const std::exception& error) {
|
||||
if (server_logger) {
|
||||
server_logger->error("tiles", {}, LOG_POS + " " + Psc::platform_2_utf8(error.what()));
|
||||
}
|
||||
callback(make_tile_read_failed_response(error, cache_request.include_body));
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
},
|
||||
{drogon::Get, drogon::Head});
|
||||
}
|
||||
void register_tile_source_handler(const Map_Tile_Source_Config& config) {
|
||||
if (!config.local()) {
|
||||
@@ -450,74 +472,72 @@ void register_tile_source_handler(const Map_Tile_Source_Config& config) {
|
||||
}
|
||||
void register_map_resources_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/resources$)",
|
||||
[](const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
auto key = parsed.value().try_get_string("current_imagery_key");
|
||||
if (!key.has_value() || !global->map_resources_config.is_imagery_key(key.value())) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
global->map_resources_config.current_imagery_key = key.value();
|
||||
Global::save();
|
||||
R"(^/map/resources$)",
|
||||
[](const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->map_resources_config.to_public_json().to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
auto key = parsed.value().try_get_string("current_imagery_key");
|
||||
if (!key.has_value() || !global->map_resources_config.is_imagery_key(key.value())) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
global->map_resources_config.current_imagery_key = key.value();
|
||||
Global::save();
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->map_resources_config.to_public_json().to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
void register_map_graphics_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/graphics$)",
|
||||
[](const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid cesium_graphics");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
global->cesium_graphics_config.from_base_json(&parsed.value());
|
||||
Global::save();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody(Psc::platform_2_utf8(error.what()));
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
R"(^/map/graphics$)",
|
||||
[](const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid cesium_graphics");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->cesium_graphics_config.to_base_json().to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
try {
|
||||
global->cesium_graphics_config.from_base_json(&parsed.value());
|
||||
Global::save();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody(Psc::platform_2_utf8(error.what()));
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->cesium_graphics_config.to_base_json().to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
Psc::JSON make_map_view_response(Global* global) {
|
||||
auto ret = global->map_view_config.to_base_json();
|
||||
@@ -529,9 +549,8 @@ bool is_multipart_request(const drogon::HttpRequestPtr& req) {
|
||||
return content_type.find("multipart/form-data") != std::string::npos;
|
||||
}
|
||||
std::string normalize_upload_extension(std::string extension) {
|
||||
std::ranges::transform(extension, extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
std::ranges::transform(extension, extension.begin(),
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
if (!extension.empty() && extension.front() != '.') {
|
||||
extension.insert(extension.begin(), '.');
|
||||
}
|
||||
@@ -544,9 +563,8 @@ std::filesystem::path source_model_directory(Global* global) {
|
||||
return std::filesystem::path(global->web_server_config.get_true_webapp()).parent_path() / "model";
|
||||
}
|
||||
bool is_safe_aircraft_model_key(std::string_view key) {
|
||||
return !key.empty() && std::ranges::all_of(key, [](unsigned char ch) {
|
||||
return std::isalnum(ch) != 0 || ch == '_';
|
||||
});
|
||||
return !key.empty() &&
|
||||
std::ranges::all_of(key, [](unsigned char ch) { return std::isalnum(ch) != 0 || ch == '_'; });
|
||||
}
|
||||
std::string model_file_name(std::string_view model_type, std::string_view aircraft_model_key) {
|
||||
if (model_type == "aircraft") {
|
||||
@@ -568,15 +586,14 @@ std::string model_file_name(std::string_view model_type, std::string_view aircra
|
||||
}
|
||||
throw std::invalid_argument("invalid model_type");
|
||||
}
|
||||
void save_uploaded_model_file(const drogon::HttpFile& upload,
|
||||
std::string_view model_type,
|
||||
std::string_view aircraft_model_key,
|
||||
Global* global) {
|
||||
void save_uploaded_model_file(const drogon::HttpFile& upload, std::string_view model_type,
|
||||
std::string_view aircraft_model_key, Global* global) {
|
||||
auto extension = normalize_upload_extension(std::string(upload.getFileExtension()));
|
||||
if (extension != ".glb") {
|
||||
throw std::invalid_argument("model file must be .glb");
|
||||
}
|
||||
if (model_type == "aircraft_type" && !global->map_model_config.aircraft_models.contains(std::string(aircraft_model_key))) {
|
||||
if (model_type == "aircraft_type" &&
|
||||
!global->map_model_config.aircraft_models.contains(std::string(aircraft_model_key))) {
|
||||
throw std::invalid_argument("unknown aircraft_model_key");
|
||||
}
|
||||
auto upload_dir = std::filesystem::path(drogon::app().getUploadPath());
|
||||
@@ -631,90 +648,85 @@ void handle_map_model_config_update(const drogon::HttpRequestPtr& req, Global* g
|
||||
}
|
||||
void register_map_models_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/models$)",
|
||||
[](const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
try {
|
||||
if (req->method() == drogon::Post) {
|
||||
if (is_multipart_request(req)) {
|
||||
handle_map_model_upload(req, global);
|
||||
}
|
||||
else {
|
||||
handle_map_model_config_update(req, global);
|
||||
}
|
||||
R"(^/map/models$)",
|
||||
[](const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
try {
|
||||
if (req->method() == drogon::Post) {
|
||||
if (is_multipart_request(req)) {
|
||||
handle_map_model_upload(req, global);
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->map_model_config.to_base_json().to_json_string());
|
||||
else {
|
||||
handle_map_model_config_update(req, global);
|
||||
}
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(global->map_model_config.to_base_json().to_json_string());
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody(Psc::platform_2_utf8(error.what()));
|
||||
}
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
void register_map_view_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/view$)",
|
||||
[](const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid map_view");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
auto key = parsed.value().try_get_string("current_imagery_key");
|
||||
if (!key.has_value() || !global->map_resources_config.is_imagery_key(key.value())) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto next_view = global->map_view_config;
|
||||
next_view.from_base_json(&parsed.value());
|
||||
if (!global->map_resources_config.is_imagery_key(next_view.map2d.current_imagery_key) ||
|
||||
!global->map_resources_config.is_imagery_key(next_view.map3d.current_imagery_key)) {
|
||||
throw std::invalid_argument("invalid current_imagery_key");
|
||||
}
|
||||
global->map_resources_config.current_imagery_key = key.value();
|
||||
global->map_view_config = next_view;
|
||||
Global::save();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody(Psc::platform_2_utf8(error.what()));
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
void register_map_view_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/view$)",
|
||||
[](const drogon::HttpRequestPtr& req,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
auto global = Global::instance();
|
||||
auto response = drogon::HttpResponse::newHttpResponse();
|
||||
if (req->method() == drogon::Post) {
|
||||
auto parsed = Psc::try_parse_json(req->body().data());
|
||||
if (!parsed.has_value()) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid map_view");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
auto key = parsed.value().try_get_string("current_imagery_key");
|
||||
if (!key.has_value() || !global->map_resources_config.is_imagery_key(key.value())) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody("invalid current_imagery_key");
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto next_view = global->map_view_config;
|
||||
next_view.from_base_json(&parsed.value());
|
||||
if (!global->map_resources_config.is_imagery_key(next_view.map2d.current_imagery_key) ||
|
||||
!global->map_resources_config.is_imagery_key(next_view.map3d.current_imagery_key)) {
|
||||
throw std::invalid_argument("invalid current_imagery_key");
|
||||
}
|
||||
global->map_resources_config.current_imagery_key = key.value();
|
||||
global->map_view_config = next_view;
|
||||
Global::save();
|
||||
}
|
||||
catch (const std::exception& error) {
|
||||
response->setStatusCode(drogon::k400BadRequest);
|
||||
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
|
||||
response->setBody(Psc::platform_2_utf8(error.what()));
|
||||
callback(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(make_map_view_response(global).to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
}
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||
response->setBody(make_map_view_response(global).to_json_string());
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
callback(response);
|
||||
},
|
||||
{drogon::Get, drogon::Post});
|
||||
}
|
||||
} // namespace
|
||||
void Global::init_tiles() {
|
||||
auto imagery = std::make_shared<Tile_Source>(map_resources_config.imagery);
|
||||
auto google_imagery = std::make_shared<Tile_Source>(map_resources_config.google_imagery);
|
||||
auto terrain = std::make_shared<Tile_Source>(map_resources_config.terrain);
|
||||
register_map_resources_handler();
|
||||
register_map_graphics_handler();
|
||||
register_map_models_handler();
|
||||
@@ -722,7 +734,4 @@ void Global::init_tiles() {
|
||||
for (const auto& source : map_resources_config.tile_sources) {
|
||||
register_tile_source_handler(source);
|
||||
}
|
||||
register_tile_handler(R"(^/map/imagery/.*$)", "/map/imagery/", imagery);
|
||||
register_tile_handler(R"(^/tiles/.*$)", "/tiles/", google_imagery);
|
||||
register_tile_handler(R"(^/map/terrain/.*$)", "/map/terrain/", terrain);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user