diff --git a/config/config.json b/config/config.json index e7ab86f..7bc9290 100644 --- a/config/config.json +++ b/config/config.json @@ -42,65 +42,27 @@ }, "map_resources": { "current_imagery_key": "mixed_imagery", - "imagery": { - "directories": [ - "D:/ae/tiles/Esri_World_Imagery_0-9", - "D:/ae/tiles/Esri_World_Imagery_10-12" - ], - "path_layout": "z/x/y", - "y_axis": "xyz", - "projection": "web_mercator", - "extension": ".png", - "tile_size": 256, - "minimum_level": 0, - "maximum_level": 11 - }, - "google_imagery": { - "directories": [ - "D:/tiles" - ], - "path_layout": "z/x/y", - "y_axis": "xyz", - "projection": "web_mercator", - "extension": ".png", - "tile_size": 256, - "minimum_level": 1, - "maximum_level": 10 - }, - "terrain": { - "file_format": "mbtiles", - "directories": [ - ], - "files": [ - "C:/tiles/高程 server.arcgisonline.com.mbtiles" - ], - "path_layout": "z/x/y", - "y_axis": "tms", - "projection": "web_mercator", - "extension": ".png", - "tile_size": 256, - "minimum_level": 11, - "maximum_level": 11, - "encoding": "terrarium" - }, "tile_sources": [ { "key": "mixed_imagery", "name": "本地混合图", "tile_type": "imagery", + "request_method": "GET", "resource": { "file_format": "mbtiles", "directories": [ ], "files": [ - "C:/tiles/混合图.mbtiles" + "C:/tiles/卫星-世界-0-8.mbtiles", + "C:/tiles/卫星-中国-9-10.mbtiles", + "C:/tiles/卫星-中国-11.mbtiles" ], "path_layout": "z/x/y", "y_axis": "tms", "projection": "web_mercator", "extension": ".png", "tile_size": 256, - "minimum_level": 11, + "minimum_level": 0, "maximum_level": 11 } }, @@ -108,20 +70,22 @@ "key": "height_terrain", "name": "本地高程", "tile_type": "terrain", + "request_method": "GET", "resource": { "file_format": "mbtiles", "directories": [ ], "files": [ - "C:/tiles/高程 server.arcgisonline.com.mbtiles", - "C:/tiles/中国高程9-10.mbtiles" + "C:/tiles/高程-世界-0-8.mbtiles", + "C:/tiles/高程-中国-9-10.mbtiles", + "C:/tiles/高程-中国-11.mbtiles" ], "path_layout": "z/x/y", "y_axis": "tms", "projection": "web_mercator", "extension": ".png", "tile_size": 256, - "minimum_level": 11, + "minimum_level": 0, "maximum_level": 11, "encoding": "terrarium" } @@ -130,6 +94,7 @@ "key": "imagery", "name": "本地卫星影像", "tile_type": "imagery", + "request_method": "GET", "resource": { "file_format": "directory", "directories": [ @@ -151,6 +116,7 @@ "key": "google_imagery", "name": "本地Google瓦片", "tile_type": "imagery", + "request_method": "GET", "resource": { "file_format": "directory", "directories": [ @@ -171,6 +137,7 @@ "key": "osm_online", "name": "OpenStreetMap在线", "tile_type": "imagery", + "request_method": "GET", "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "resource": { "file_format": "online", @@ -186,6 +153,153 @@ "minimum_level": 0, "maximum_level": 19 } + }, + { + "key": "osm_hot_online", + "name": "OpenStreetMap HOT在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 19 + } + }, + { + "key": "cyclosm_online", + "name": "CyclOSM在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 20 + } + }, + { + "key": "carto_light_online", + "name": "Carto Light在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 20 + } + }, + { + "key": "carto_dark_online", + "name": "Carto Dark在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 20 + } + }, + { + "key": "carto_voyager_online", + "name": "Carto Voyager在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 20 + } + }, + { + "key": "carto_dark_nolabels_online", + "name": "Carto Dark无标注在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 20 + } + }, + { + "key": "opentopomap_online", + "name": "OpenTopoMap在线", + "tile_type": "imagery", + "request_method": "GET", + "url": "https://a.tile.opentopomap.org/{z}/{x}/{y}.png", + "resource": { + "file_format": "online", + "directories": [ + ], + "files": [ + ], + "path_layout": "z/x/y", + "y_axis": "xyz", + "projection": "web_mercator", + "extension": ".png", + "tile_size": 256, + "minimum_level": 0, + "maximum_level": 17 + } } ] }, diff --git a/module/Local_Server/server/Config.cpp b/module/Local_Server/server/Config.cpp index 04728ce..2495e6f 100644 --- a/module/Local_Server/server/Config.cpp +++ b/module/Local_Server/server/Config.cpp @@ -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 { diff --git a/module/Local_Server/server/Config.h b/module/Local_Server/server/Config.h index fc8ea72..72d84b5 100644 --- a/module/Local_Server/server/Config.h +++ b/module/Local_Server/server/Config.h @@ -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 tile_sources; void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; diff --git a/module/Local_Server/server/tiles.cpp b/module/Local_Server/server/tiles.cpp index 24a540f..c8c3c9a 100644 --- a/module/Local_Server/server/tiles.cpp +++ b/module/Local_Server/server/tiles.cpp @@ -1,12 +1,12 @@ -#include "Global.h" -#include "Performance_Monitor.h" -#include "psc_global_include/codec.h" #include #include #include #include #include #include +#include "Global.h" +#include "Performance_Monitor.h" +#include "psc_global_include/codec.h" #if defined(ASIO_HAS_FILE) #include #include @@ -29,11 +29,7 @@ #include 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 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(result.modified_time), file_info.size, request.z, request.x, request.y, content_size); + result.etag = std::format("W/\"{:x}-{:x}-{}-{}-{}-{:x}\"", static_cast(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(result); } -std::optional parse_tile_request(std::string_view path, std::string_view prefix, std::string_view default_extension) { +std::optional 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 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(path_, SQLite::OPEN_READONLY)) {} - std::optional 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(request.z)); - statement.bind(2, static_cast(request.x)); - statement.bind(3, static_cast(request.y)); - if (!statement.executeStep()) { - return std::nullopt; - } - auto column = statement.getColumn(0); - auto bytes = column.getBytes(); - if (bytes < 0 || static_cast(bytes) > kMaxTileFileSize) { - throw std::runtime_error("mbtiles tile is too large: " + path_); - } - auto cache_info = make_mbtiles_cache_info(*file_info, request, static_cast(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(column.getBlob()); - content.assign(data, static_cast(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(path_, SQLite::OPEN_READONLY); } + std::optional load(const Tile_Request& request, const Tile_Cache_Request& cache_request, + std::string_view content_type); private: std::string path_; std::unique_ptr db_; std::mutex mtx_; }; +std::optional 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(request.z)); + statement.bind(2, static_cast(request.x)); + statement.bind(3, static_cast(request.y)); + if (!statement.executeStep()) { + return std::nullopt; + } + auto column = statement.getColumn(0); + auto bytes = column.getBytes(); + if (bytes < 0 || static_cast(bytes) > kMaxTileFileSize) { + throw std::runtime_error("mbtiles tile is too large: " + path_); + } + auto cache_info = make_mbtiles_cache_info(*file_info, request, static_cast(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(column.getBlob()); + content.assign(data, static_cast(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 load_directory_tile(const Tile_Request& request, const Tile_Cache_Request& cache_request) const { + asio::awaitable 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(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(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 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 { + 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 { 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 resolver_; std::vector> mbtiles_files_; #if !defined(ASIO_HAS_FILE) static asio::awaitable 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 { + co_return co_await asio::co_spawn( + tile_file_pool(), + [path = std::move(path), size]() -> asio::awaitable { 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 source) { drogon::app().registerHandlerViaRegex( - std::string(regex), - [prefix = std::string(prefix), source = std::move(source)]( - const drogon::HttpRequestPtr& req, - std::function&& 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&& 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&& 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&& 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&& 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&& 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(std::tolower(ch)); - }); + std::ranges::transform(extension, extension.begin(), + [](unsigned char ch) { return static_cast(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&& 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&& 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&& 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&& 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(map_resources_config.imagery); - auto google_imagery = std::make_shared(map_resources_config.google_imagery); - auto terrain = std::make_shared(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); }