289 lines
8.0 KiB
C++
289 lines
8.0 KiB
C++
#include <llmr/map/map.hpp>
|
|
#include <llmr/map/tile.hpp>
|
|
//#include <llmr/util/vec2.hpp>
|
|
|
|
#include <iostream>
|
|
#include <thread>
|
|
|
|
#include <cmath>
|
|
#include <cassert>
|
|
|
|
using namespace llmr;
|
|
|
|
Map::Map(Settings& settings)
|
|
: settings(settings),
|
|
transform(),
|
|
painter(transform, settings),
|
|
min_zoom(0),
|
|
max_zoom(14) {
|
|
}
|
|
|
|
Map::~Map() {
|
|
}
|
|
|
|
void Map::setup() {
|
|
painter.setup();
|
|
}
|
|
|
|
void Map::loadSettings() {
|
|
transform.setAngle(settings.angle);
|
|
transform.setScale(settings.scale);
|
|
transform.setLonLat(settings.longitude, settings.latitude);
|
|
update();
|
|
}
|
|
|
|
void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height) {
|
|
transform.width = width;
|
|
transform.height = height;
|
|
transform.fb_width = fb_width;
|
|
transform.fb_height = fb_height;
|
|
update();
|
|
}
|
|
|
|
void Map::moveBy(double dx, double dy) {
|
|
transform.moveBy(dx, dy);
|
|
update();
|
|
|
|
transform.getLonLat(settings.longitude, settings.latitude);
|
|
settings.save();
|
|
}
|
|
|
|
void Map::scaleBy(double ds, double cx, double cy) {
|
|
transform.scaleBy(ds, cx, cy);
|
|
update();
|
|
|
|
transform.getLonLat(settings.longitude, settings.latitude);
|
|
settings.scale = transform.getScale();
|
|
settings.save();
|
|
}
|
|
|
|
void Map::rotateBy(double cx, double cy, double sx, double sy, double ex, double ey) {
|
|
transform.rotateBy(cx, cy, sx, sy, ex, ey);
|
|
update();
|
|
|
|
settings.angle = transform.getAngle();
|
|
settings.save();
|
|
}
|
|
|
|
void Map::resetNorth() {
|
|
transform.setAngle(0, 0.5); // 500 ms
|
|
update();
|
|
|
|
settings.angle = transform.getAngle();
|
|
settings.save();
|
|
}
|
|
|
|
void Map::resetPosition() {
|
|
transform.setAngle(0);
|
|
transform.setLonLat(0, 0);
|
|
transform.setZoom(0);
|
|
update();
|
|
|
|
transform.getLonLat(settings.longitude, settings.latitude);
|
|
settings.scale = transform.getScale();
|
|
settings.angle = transform.getAngle();
|
|
settings.save();
|
|
}
|
|
|
|
void Map::toggleDebug() {
|
|
settings.debug = !settings.debug;
|
|
update();
|
|
|
|
settings.save();
|
|
}
|
|
|
|
void Map::update() {
|
|
updateTiles();
|
|
platform::restart(this);
|
|
}
|
|
|
|
|
|
Tile::Ptr Map::hasTile(const Tile::ID& id) {
|
|
for (Tile::Ptr& tile : tiles) {
|
|
if (tile->id == id) {
|
|
return tile;
|
|
}
|
|
}
|
|
|
|
return Tile::Ptr();
|
|
}
|
|
|
|
Tile::Ptr Map::addTile(const Tile::ID& id) {
|
|
Tile::Ptr tile = hasTile(id);
|
|
|
|
if (!tile.get()) {
|
|
// We couldn't find the tile in the list. Create a new one.
|
|
tile = std::make_shared<Tile>(id);
|
|
assert(tile);
|
|
// std::cerr << "init " << id.z << "/" << id.x << "/" << id.y << std::endl;
|
|
// std::cerr << "add " << tile->toString() << std::endl;
|
|
tiles.push_front(tile);
|
|
}
|
|
|
|
return tile;
|
|
}
|
|
|
|
/**
|
|
* Recursively find children of the given tile that are already loaded.
|
|
*
|
|
* @param id The tile ID that we should find children for.
|
|
* @param maxCoveringZoom The maximum zoom level of children to look for.
|
|
* @param retain An object that we add the found tiles to.
|
|
*
|
|
* @return boolean Whether the children found completely cover the tile.
|
|
*/
|
|
bool Map::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) {
|
|
bool complete = true;
|
|
int32_t z = id.z;
|
|
|
|
auto ids = Tile::children(id, z + 1);
|
|
for (const Tile::ID& child_id : ids) {
|
|
const Tile::Ptr& tile = hasTile(child_id);
|
|
if (tile && tile->state == Tile::ready) {
|
|
assert(tile);
|
|
retain.emplace_front(tile->id);
|
|
} else {
|
|
complete = false;
|
|
if (z < maxCoveringZoom) {
|
|
// Go further down the hierarchy to find more unloaded children.
|
|
findLoadedChildren(child_id, maxCoveringZoom, retain);
|
|
}
|
|
}
|
|
}
|
|
return complete;
|
|
};
|
|
|
|
/**
|
|
* Find a loaded parent of the given tile.
|
|
*
|
|
* @param id The tile ID that we should find children for.
|
|
* @param minCoveringZoom The minimum zoom level of parents to look for.
|
|
* @param retain An object that we add the found tiles to.
|
|
*
|
|
* @return boolean Whether a parent was found.
|
|
*/
|
|
bool Map::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain) {
|
|
for (int32_t z = id.z - 1; z >= minCoveringZoom; z--) {
|
|
const Tile::ID parent_id = Tile::parent(id, z);
|
|
const Tile::Ptr tile = hasTile(parent_id);
|
|
if (tile && tile->state == Tile::ready) {
|
|
assert(tile);
|
|
retain.emplace_front(tile->id);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
void Map::updateTiles() {
|
|
// Figure out what tiles we need to load
|
|
int32_t zoom = transform.getZoom();
|
|
if (zoom > max_zoom) zoom = max_zoom;
|
|
if (zoom < min_zoom) zoom = min_zoom;
|
|
|
|
int32_t max_covering_zoom = zoom + 1;
|
|
if (max_covering_zoom > max_zoom) max_covering_zoom = max_zoom;
|
|
|
|
int32_t min_covering_zoom = zoom - 10;
|
|
if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom;
|
|
|
|
|
|
int32_t max_dim = pow(2, zoom);
|
|
|
|
// Map four viewport corners to pixel coordinates
|
|
box box;
|
|
transform.mapCornersToBox(zoom, box);
|
|
|
|
vec2<int32_t> tl, br;
|
|
tl.x = fmax(0, floor(fmin(box.tl.x, box.bl.x)));
|
|
tl.y = fmax(0, floor(fmin(box.tl.y, box.tr.y)));
|
|
br.x = fmin(max_dim, ceil(fmax(box.tr.x, box.br.x)));
|
|
br.y = fmin(max_dim, ceil(fmax(box.bl.y, box.br.y)));
|
|
|
|
|
|
// TODO: Discard tiles that are outside the viewport
|
|
std::forward_list<Tile::ID> required;
|
|
for (int32_t y = tl.y; y < br.y; y++) {
|
|
for (int32_t x = tl.x; x < br.x; x++) {
|
|
required.emplace_front(x, y, zoom);
|
|
}
|
|
}
|
|
|
|
// Retain is a list of tiles that we shouldn't delete, even if they are not
|
|
// the most ideal tile for the current viewport. This may include tiles like
|
|
// parent or child tiles that are *already* loaded.
|
|
std::forward_list<Tile::ID> retain(required);
|
|
|
|
// Add existing child/parent tiles if the actual tile is not yet loaded
|
|
for (const Tile::ID& id : required) {
|
|
Tile::Ptr tile = addTile(id);
|
|
assert(tile);
|
|
|
|
if (tile->state != Tile::ready) {
|
|
// The tile we require is not yet loaded. Try to find a parent or
|
|
// child tile that we already have.
|
|
|
|
// First, try to find existing child tiles that completely cover the
|
|
// missing tile.
|
|
bool complete = findLoadedChildren(id, max_covering_zoom, retain);
|
|
|
|
// Then, if there are no complete child tiles, try to find existing
|
|
// parent tiles that completely cover the missing tile.
|
|
if (!complete) {
|
|
findLoadedParent(id, min_covering_zoom, retain);
|
|
}
|
|
}
|
|
|
|
if (tile->state == Tile::initial) {
|
|
// If the tile is new, we have to make sure to load it.
|
|
tile->state = Tile::loading;
|
|
platform::request(this, tile);
|
|
}
|
|
}
|
|
|
|
// Remove tiles that we definitely don't need, i.e. tiles that are not on
|
|
// the required list.
|
|
tiles.remove_if([&retain](const Tile::Ptr& tile) {
|
|
assert(tile);
|
|
bool obsolete = std::find(retain.begin(), retain.end(), tile->id) == retain.end();
|
|
if (obsolete) {
|
|
tile->cancel();
|
|
}
|
|
return obsolete;
|
|
});
|
|
|
|
// Sort tiles by zoom level, front to back.
|
|
// We're painting front-to-back, so we want to draw more detailed tiles first
|
|
// before filling in other parts with lower zoom levels.
|
|
tiles.sort([](const Tile::Ptr& a, const Tile::Ptr& b) {
|
|
return a->id.z > b->id.z;
|
|
});
|
|
}
|
|
|
|
bool Map::render() {
|
|
transform.updateAnimations();
|
|
|
|
painter.clear();
|
|
|
|
for (Tile::Ptr& tile : tiles) {
|
|
assert(tile);
|
|
if (tile->state == Tile::ready) {
|
|
painter.render(tile);
|
|
}
|
|
}
|
|
|
|
return transform.needsAnimation();
|
|
}
|
|
|
|
void Map::tileLoaded(Tile::Ptr tile) {
|
|
// std::cerr << "loaded " << tile->toString() << std::endl;
|
|
update();
|
|
}
|
|
|
|
void Map::tileFailed(Tile::Ptr tile) {
|
|
// fprintf(stderr, "[%8zx] tile failed to load %d/%d/%d\n",
|
|
// std::hash<std::thread::id>()(std::this_thread::get_id()),
|
|
// tile->z, tile->x, tile->y);
|
|
}
|