支持mbtiles
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
#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>
|
||||
#if defined(ASIO_HAS_FILE)
|
||||
#include <asio/random_access_file.hpp>
|
||||
#include <asio/read_at.hpp>
|
||||
#else
|
||||
#include <asio/co_spawn.hpp>
|
||||
#include <asio/thread_pool.hpp>
|
||||
#include <fstream>
|
||||
#endif
|
||||
#include <cctype>
|
||||
@@ -16,6 +19,8 @@
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -53,6 +58,10 @@ struct Tile_Load_Result {
|
||||
std::string content_type;
|
||||
File_Cache_Info cache_info;
|
||||
};
|
||||
asio::thread_pool& tile_file_pool() {
|
||||
static asio::thread_pool pool(4);
|
||||
return pool;
|
||||
}
|
||||
std::string format_http_date(std::time_t time) {
|
||||
std::tm tm{};
|
||||
#ifdef _WIN32
|
||||
@@ -83,6 +92,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) {
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
std::string_view trim_http_ows(std::string_view value) {
|
||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
|
||||
value.remove_prefix(1);
|
||||
@@ -177,12 +192,9 @@ public:
|
||||
}
|
||||
}
|
||||
std::optional<std::filesystem::path> find_file(const Tile_Request& request) const {
|
||||
if (request.z < config_.minimum_level || request.z > config_.maximum_level) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto relative = relative_path(request);
|
||||
for (const auto& directory : directories_) {
|
||||
auto path = std::filesystem::path(directory) / relative;
|
||||
auto path = Psc::path_from_utf8(directory) / relative;
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
return path;
|
||||
}
|
||||
@@ -205,12 +217,78 @@ private:
|
||||
Map_Tile_Config config_;
|
||||
std::vector<std::string> directories_;
|
||||
};
|
||||
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) {
|
||||
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)};
|
||||
}
|
||||
private:
|
||||
std::string path_;
|
||||
std::unique_ptr<SQLite::Database> db_;
|
||||
std::mutex mtx_;
|
||||
};
|
||||
class Tile_Source {
|
||||
public:
|
||||
explicit Tile_Source(Map_Tile_Config config)
|
||||
: resolver_(config) {}
|
||||
: config_(std::move(config)) {
|
||||
if (config_.extension.empty() || config_.extension.front() != '.') {
|
||||
throw std::invalid_argument("invalid tile extension: " + config_.extension);
|
||||
}
|
||||
if (config_.file_format == "directory") {
|
||||
resolver_ = std::make_unique<Tile_Path_Resolver>(config_);
|
||||
}
|
||||
else if (config_.file_format == "mbtiles") {
|
||||
for (auto& file : config_.get_true_files()) {
|
||||
mbtiles_files_.push_back(std::make_unique<Mbtiles_File>(std::move(file)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw std::invalid_argument("invalid tile file_format: " + config_.file_format);
|
||||
}
|
||||
}
|
||||
asio::awaitable<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request) const {
|
||||
auto path = resolver_.find_file(request);
|
||||
if (request.z < config_.minimum_level || request.z > config_.maximum_level) {
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||
}
|
||||
if (config_.file_format == "mbtiles") {
|
||||
co_return co_await load_mbtiles_tile(request, cache_request);
|
||||
}
|
||||
co_return co_await load_directory_tile(request, cache_request);
|
||||
}
|
||||
const Map_Tile_Config& config() const {
|
||||
return config_;
|
||||
}
|
||||
private:
|
||||
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};
|
||||
}
|
||||
@@ -241,16 +319,22 @@ public:
|
||||
}
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Ready, std::move(generic_path), std::move(content), std::move(content_type), *cache_info};
|
||||
}
|
||||
const Map_Tile_Config& config() const {
|
||||
return resolver_.config();
|
||||
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> {
|
||||
for (const auto& file : mbtiles_files_) {
|
||||
auto result = file->load(request, cache_request, content_type);
|
||||
if (result) {
|
||||
co_return std::move(*result);
|
||||
}
|
||||
}
|
||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||
}, asio::use_awaitable);
|
||||
}
|
||||
private:
|
||||
Tile_Path_Resolver resolver_;
|
||||
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::thread_pool& tile_file_pool() {
|
||||
static asio::thread_pool pool(4);
|
||||
return pool;
|
||||
}
|
||||
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> {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
@@ -355,6 +439,15 @@ void register_tile_handler(std::string_view regex, std::string_view prefix, std:
|
||||
},
|
||||
{drogon::Get, drogon::Head});
|
||||
}
|
||||
void register_tile_source_handler(const Map_Tile_Source_Config& config) {
|
||||
if (!config.local()) {
|
||||
return;
|
||||
}
|
||||
auto source = std::make_shared<Tile_Source>(config.resource);
|
||||
auto prefix = "/map/tile_sources/" + config.key + "/";
|
||||
auto regex = "^" + prefix + ".*$";
|
||||
register_tile_handler(regex, prefix, source);
|
||||
}
|
||||
void register_map_resources_handler() {
|
||||
drogon::app().registerHandlerViaRegex(
|
||||
R"(^/map/resources$)",
|
||||
@@ -626,6 +719,9 @@ void Global::init_tiles() {
|
||||
register_map_graphics_handler();
|
||||
register_map_models_handler();
|
||||
register_map_view_handler();
|
||||
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