17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 08:39:19 +02:00
Files
maszyna/editor/plan_tilecache.cppm
maj00r 321c9dd7ab Editor: fold the plan library into the tree as modules, drop its own name
The track layout library came in as a subproject of its own -- its own
CMakeLists, its own install and export rules, its own include tree -- which only
made the build harder to follow now that everything else is a module. It is part
of the editor, so it lives where the editor lives.

- editor/plan_*.cppm / plan_*.cpp: the fifteen interface units and twelve
  implementation units, flat in editor/ beside editorTerrain and orthophoto, with
  the plan_ prefix keeping them clear of what was already there (editor/turnouts
  is the preset catalogue, so the geometry is plan_turnout_geometry).
- modules are named eu07.editor.plan_*, like every other module in the tree.
- namespaces move under the editor's own: editor::plan, editor::plan::geometry,
  ::io, ::render. The library's old name is gone from the sources and the docs.
- every moved file carries the MPL header the rest of the tree carries; it had
  none. version.hpp went with the subproject: macros only, and unused.
- the umbrella header is dropped -- nothing imported it.
- the unit tests survive the fold: editor/tests, behind EU07_EDITOR_PLAN_TESTS
  (off by default), compiles the interfaces a second time into a small library of
  its own, because a test can only import modules from a target it links and
  linking the game would drag the whole simulator in. The code under test needs
  nothing but a compiler. 7/7 pass.
- a switch may now be written with two dashes as well: --editor beside -editor.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF); -editor and --editor both come
up in the editor on empty ground.
2026-09-13 13:31:01 +02:00

196 lines
8.1 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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/.
*/
module;
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
export module eu07.editor.plan_tilecache;
export {
namespace editor::plan {
/// A stable tile in the EPSG:2180 tiling pyramid. Tiles are addressed by their
/// zoom level and integer column/row, so the same ground square always maps to
/// the same key regardless of the current viewport — that is what makes the
/// cache reusable across pans and zooms.
struct TileKey {
int zoom{0};
int x{0}; ///< column (increases with easting)
int y{0}; ///< row (increases with northing)
friend bool operator==(const TileKey&, const TileKey&) = default;
};
struct TileKeyHash {
std::size_t operator()(const TileKey& k) const noexcept {
// Mix the three small integers into one hash.
std::uint64_t h = static_cast<std::uint64_t>(static_cast<std::uint32_t>(k.x));
h = h * 1099511628211ULL ^
static_cast<std::uint64_t>(static_cast<std::uint32_t>(k.y));
h = h * 1099511628211ULL ^ static_cast<std::uint64_t>(k.zoom);
return static_cast<std::size_t>(h);
}
};
/// A bounding box in EPSG:2180 metres (easting = x, northing = y).
struct TileBBox {
double min_x{0.0};
double min_y{0.0};
double max_x{0.0};
double max_y{0.0};
};
/// The tiling scheme. A pure, static value type defining a square grid over the
/// EPSG:2180 plane. The default grid is fixed 100 m × 100 m cells (key.zoom =
/// kLevel). Coarser overview grids encode the cell size in metres in
/// @c TileKey::zoom when that value is >= 100 (e.g. zoom 1000 → 1 km cells for
/// topo underlays). Nothing here touches the network.
class TileGrid {
public:
static constexpr int kTilePixels = 1024; ///< pixels per cell edge (GetMap WIDTH/HEIGHT)
static constexpr double kOriginX = 0.0; ///< easting of cell (0,0)'s left edge
static constexpr double kOriginY = 0.0; ///< northing of cell (0,0)'s bottom edge
/// Default cache cell size: 100 m × 100 m ground square.
static constexpr double kCellMetres = 100.0;
/// Default grid level stored in TileKey::zoom for the 100 m grid.
static constexpr int kLevel = 0;
/// Zoom values at or above this are treated as the cell edge in metres.
static constexpr int kCellZoomMin = 100;
/// Cell edge in metres for @p key (100 m default, or key.zoom when coarse).
[[nodiscard]] static double cell_metres(const TileKey& key) noexcept;
/// Ground resolution (metres per pixel) of a cell.
[[nodiscard]] static double resolution(int zoom = kLevel) noexcept;
/// Edge length of a cell in metres for the given zoom/cell encoding.
[[nodiscard]] static double tile_span(int zoom = kLevel) noexcept;
/// The cell whose ground square contains (@p x, @p y).
[[nodiscard]] static TileKey tile_at(double x, double y, int zoom = kLevel) noexcept;
/// The ground bounding box (EPSG:2180) of @p key.
[[nodiscard]] static TileBBox bbox(const TileKey& key) noexcept;
/// The grid level for a target resolution. Always kLevel for the fine grid.
[[nodiscard]] static int zoom_for_resolution(double metres_per_pixel) noexcept;
/// Every cell whose ground square intersects @p view, capped at @p max_tiles.
/// Pass a coarse cell size (e.g. 1000) as @p zoom to get a 1 km overview grid.
[[nodiscard]] static std::vector<TileKey> tiles_for_view(const TileBBox& view,
int zoom = kLevel,
int max_tiles = 64);
};
/// Configuration for a WMS GetMap source. Defaults target a public EPSG:2180
/// endpoint (Polish Geoportal). Every field is data, so a different backend is a
/// pure configuration change with no code edits.
struct WmsConfig {
std::string base_url;
std::string layers;
std::string format{"image/jpeg"};
std::string version{"1.1.1"}; ///< 1.1.1 keeps BBOX as minx,miny,maxx,maxy
std::string srs{"EPSG:2180"};
int tile_pixels{TileGrid::kTilePixels};
/// Geoportal ortophoto (ortofotomapa), EPSG:2180, JPEG.
[[nodiscard]] static WmsConfig geoportal_ortho();
/// Geoportal topographic base map (BDOT/raster), EPSG:2180, PNG.
[[nodiscard]] static WmsConfig geoportal_topo();
};
/// Builds the full WMS 1.1.1 GetMap URL for @p box using @p cfg. Numbers are
/// formatted locale-independently (std::to_chars), matching the rest of the
/// library's serialization.
[[nodiscard]] std::string wms_getmap_url(const WmsConfig& cfg, const TileBBox& box);
enum class TileStatus { Missing, Loading, Ready, Failed };
/// A cached tile image: raw encoded bytes (JPEG/PNG) plus their MIME type.
struct TileImage {
TileStatus status{TileStatus::Missing};
std::vector<std::uint8_t> bytes;
std::string content_type;
int attempts{0}; ///< failed fetch attempts so far (see TileService retry cap)
};
/// Owns the tile grid, the tile cache and the WMS fetch. This is the single
/// place that decides which tile is needed and whether it is already cached. The
/// download itself is the host's: it injects a Fetcher, so the tiling and caching
/// logic here stays transport-free and is exercised in tests without a network.
class TileService {
public:
/// Called when a fetch finishes: (success, bytes, content_type).
using DoneFn =
std::function<void(bool, std::vector<std::uint8_t>, std::string)>;
/// Injectable network backend: given a URL, eventually invoke DoneFn.
using Fetcher = std::function<void(const std::string& url, DoneFn)>;
explicit TileService(WmsConfig cfg = WmsConfig::geoportal_ortho());
[[nodiscard]] const WmsConfig& config() const noexcept { return cfg_; }
void set_config(WmsConfig cfg) { cfg_ = std::move(cfg); }
/// Sets the transport. When unset, a requested tile fails at once (there is
/// no built-in downloader — the host owns that).
void set_fetcher(Fetcher fetcher) { fetcher_ = std::move(fetcher); }
/// Notified (with the tile's key) whenever a tile transitions to Ready or
/// Failed. Lets the renderer schedule a redraw when an async tile arrives.
void set_on_ready(std::function<void(TileKey)> cb) { on_ready_ = std::move(cb); }
/// Ensures @p key is cached or being fetched, and returns its current status.
/// A cache hit returns Ready immediately and issues no network request.
TileStatus request(const TileKey& key);
/// The cached image for @p key, or nullptr if it is not Ready.
[[nodiscard]] const TileImage* peek(const TileKey& key) const;
/// Stores bytes for @p key directly, marking it Ready. Exposed so a caller
/// (or a test) can seed the cache without going through the fetcher.
void store(const TileKey& key, std::vector<std::uint8_t> bytes,
std::string content_type = {});
[[nodiscard]] bool has(const TileKey& key) const;
[[nodiscard]] std::size_t size() const noexcept { return cache_.size(); }
void clear() { cache_.clear(); }
/// The GetMap URL this service would use for @p key.
[[nodiscard]] std::string url_for(const TileKey& key) const;
/// Finalises an in-flight fetch: on success stores the bytes (Ready), on
/// failure marks the tile Failed, then fires the on_ready callback. Public so
/// the host's fetcher callback can hand the result back here.
void complete_fetch(const TileKey& key, bool ok, std::vector<std::uint8_t> bytes,
std::string content_type);
/// Re-issues the fetch for @p key.
void retry_now(const TileKey& key);
private:
void begin_fetch(const TileKey& key);
/// Re-issues a fetch for @p key after a failure — the service retries up to
/// kMaxRetries times, then marks the tile Failed. The fetcher owns the timing.
void schedule_retry(const TileKey& key);
WmsConfig cfg_;
std::unordered_map<TileKey, TileImage, TileKeyHash> cache_;
Fetcher fetcher_;
std::function<void(TileKey)> on_ready_;
};
} // namespace editor::plan
} // export