refs #101: first cut of basic hacky data sources
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
#ifndef LLMR_MAP_COVERAGE
|
||||
#define LLMR_MAP_COVERAGE
|
||||
|
||||
#include <llmr/map/tile.hpp>
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points, const bool use_raster = false, const bool use_retina = false);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
@@ -13,10 +12,12 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Settings;
|
||||
class Source;
|
||||
|
||||
class Map : private util::noncopyable {
|
||||
public:
|
||||
@@ -28,7 +29,6 @@ public:
|
||||
void loadStyle(const uint8_t *const data, uint32_t bytes);
|
||||
void loadSettings();
|
||||
void resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height);
|
||||
void toggleRaster();
|
||||
|
||||
/* callback */
|
||||
bool render();
|
||||
@@ -64,13 +64,13 @@ public:
|
||||
|
||||
void toggleDebug();
|
||||
|
||||
private:
|
||||
bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain);
|
||||
bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain);
|
||||
bool updateTiles();
|
||||
TileData::State addTile(const Tile::ID& id);
|
||||
TileData::State hasTile(const Tile::ID& id);
|
||||
box cornersToBox(uint32_t z) const;
|
||||
float getPixelRatio() const;
|
||||
Style& getStyle();
|
||||
GlyphAtlas& getGlyphAtlas();
|
||||
|
||||
private:
|
||||
bool updateTiles();
|
||||
void update();
|
||||
|
||||
private:
|
||||
@@ -81,15 +81,12 @@ private:
|
||||
GlyphAtlas glyphAtlas;
|
||||
Painter painter;
|
||||
|
||||
std::map<std::string, Source> sources;
|
||||
|
||||
int32_t min_zoom;
|
||||
int32_t max_zoom;
|
||||
|
||||
float pixel_ratio;
|
||||
|
||||
bool use_raster = false;
|
||||
|
||||
std::forward_list<Tile> tiles;
|
||||
std::forward_list<std::weak_ptr<TileData>> tile_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
67
include/llmr/map/source.hpp
Normal file
67
include/llmr/map/source.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef LLMR_MAP_SOURCE
|
||||
#define LLMR_MAP_SOURCE
|
||||
|
||||
#include <llmr/map/tile.hpp>
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
|
||||
#include <forward_list>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Map;
|
||||
class Transform;
|
||||
class Painter;
|
||||
class Texturepool;
|
||||
class TileData;
|
||||
|
||||
class Source : public std::enable_shared_from_this<Source> {
|
||||
public:
|
||||
enum class Type {
|
||||
vector,
|
||||
raster
|
||||
};
|
||||
|
||||
public:
|
||||
Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url = "", float pixel_ratio = 1.0, Type type = Type::vector, std::list<uint32_t> zooms = {0}, uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14, bool enabled = true);
|
||||
|
||||
bool update();
|
||||
void render();
|
||||
|
||||
private:
|
||||
bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain);
|
||||
bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain);
|
||||
std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points);
|
||||
|
||||
bool updateTiles();
|
||||
|
||||
TileData::State addTile(const Tile::ID& id);
|
||||
TileData::State hasTile(const Tile::ID& id);
|
||||
|
||||
void renderTile(const Tile::ID& id);
|
||||
|
||||
private:
|
||||
Map& map;
|
||||
Transform& transform;
|
||||
Painter& painter;
|
||||
Texturepool& texturepool;
|
||||
|
||||
Type type;
|
||||
std::list<uint32_t> zooms;
|
||||
const char *url;
|
||||
uint32_t tile_size;
|
||||
uint32_t min_zoom;
|
||||
uint32_t max_zoom;
|
||||
bool enabled;
|
||||
|
||||
float pixel_ratio;
|
||||
|
||||
std::forward_list<Tile> tiles;
|
||||
std::forward_list<std::weak_ptr<TileData>> tile_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -35,7 +35,6 @@ class Raster;
|
||||
class LayerDescription;
|
||||
class BucketDescription;
|
||||
|
||||
|
||||
class PlainShader;
|
||||
|
||||
class TileData : public std::enable_shared_from_this<TileData>,
|
||||
@@ -57,7 +56,7 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster = false, const bool use_retina = false);
|
||||
TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster);
|
||||
~TileData();
|
||||
|
||||
void request();
|
||||
@@ -68,8 +67,6 @@ public:
|
||||
|
||||
public:
|
||||
const Tile::ID id;
|
||||
const bool use_raster;
|
||||
const bool use_retina;
|
||||
std::atomic<State> state;
|
||||
std::shared_ptr<Raster> raster;
|
||||
|
||||
@@ -91,7 +88,8 @@ public:
|
||||
std::map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
|
||||
private:
|
||||
// Source data
|
||||
std::string url;
|
||||
bool is_raster = false;
|
||||
std::string data;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
|
||||
@@ -52,8 +52,7 @@ public:
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
|
||||
// Temporary
|
||||
box mapCornersToBox(uint32_t z) const;
|
||||
box cornersToBox(uint32_t z) const;
|
||||
|
||||
private:
|
||||
void setScaleXY(double new_scale, double xn, double yn, double duration = 0);
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
extern const char *kTileVectorURL;
|
||||
extern const char *kTileRasterURL;
|
||||
extern const char *kSpriteURL;
|
||||
extern const int32_t kTileVectorMaxZoom;
|
||||
extern const int32_t kTileRasterMaxZoom;
|
||||
|
||||
namespace platform {
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ class MBXMapView
|
||||
|
||||
- (void)toggleRaster
|
||||
{
|
||||
mapView->map.toggleRaster();
|
||||
// mapView->map.toggleRaster();
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
#include <llmr/map/coverage.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
#include <llmr/util/vec.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
|
||||
// Taken from polymaps src/Layer.js
|
||||
// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383
|
||||
|
||||
struct edge {
|
||||
double x0 = 0, y0 = 0;
|
||||
double x1 = 0, y1 = 0;
|
||||
double dx = 0, dy = 0;
|
||||
edge(double x0, double y0, double x1, double y1, double dx, double dy)
|
||||
: x0(x0), y0(y0), x1(x1), y1(y1), dx(dx), dy(dy) {}
|
||||
};
|
||||
|
||||
typedef const std::function<void(int32_t, int32_t, int32_t, int32_t)> ScanLine;
|
||||
|
||||
// scan-line conversion
|
||||
edge _edge(const llmr::vec2<double> a, const llmr::vec2<double> b) {
|
||||
if (a.y > b.y) {
|
||||
return { b.x, b.y, a.x, a.y, a.x - b.x, a.y - b.y };
|
||||
} else {
|
||||
return { a.x, a.y, b.x, b.y, b.x - a.x, b.y - a.y };
|
||||
}
|
||||
}
|
||||
|
||||
// scan-line conversion
|
||||
void _scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) {
|
||||
double y0 = fmax(ymin, floor(e1.y0)),
|
||||
y1 = fmin(ymax, ceil(e1.y1));
|
||||
|
||||
// sort edges by x-coordinate
|
||||
if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ?
|
||||
(e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) :
|
||||
(e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
|
||||
std::swap(e0, e1);
|
||||
}
|
||||
|
||||
// scan lines!
|
||||
double m0 = e0.dx / e0.dy,
|
||||
m1 = e1.dx / e1.dy,
|
||||
d0 = e0.dx > 0, // use y + 1 to compute x0
|
||||
d1 = e1.dx < 0; // use y + 1 to compute x1
|
||||
for (int32_t y = y0; y < y1; y++) {
|
||||
double x0 = m0 * fmax(0, fmin(e0.dy, y + d0 - e0.y0)) + e0.x0,
|
||||
x1 = m1 * fmax(0, fmin(e1.dy, y + d1 - e1.y0)) + e1.x0;
|
||||
scanLine(floor(x1), ceil(x0), y, ymax);
|
||||
}
|
||||
}
|
||||
|
||||
// scan-line conversion
|
||||
void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const llmr::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) {
|
||||
edge ab = _edge(a, b);
|
||||
edge bc = _edge(b, c);
|
||||
edge ca = _edge(c, a);
|
||||
|
||||
// sort edges by y-length
|
||||
if (ab.dy > bc.dy) { std::swap(ab, bc); }
|
||||
if (ab.dy > ca.dy) { std::swap(ab, ca); }
|
||||
if (bc.dy > ca.dy) { std::swap(bc, ca); }
|
||||
|
||||
// scan span! scan span!
|
||||
if (ab.dy) _scanSpans(ca, ab, ymin, ymax, scanLine);
|
||||
if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine);
|
||||
}
|
||||
|
||||
std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points, const bool use_raster, const bool use_retina) {
|
||||
int32_t dim = pow(2, zoom);
|
||||
std::forward_list<llmr::Tile::ID> tiles;
|
||||
|
||||
auto scanLine = [&tiles, zoom, use_raster, use_retina](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
|
||||
int32_t x;
|
||||
if (y >= 0 && y <= ymax) {
|
||||
for (x = x0; x < x1; x++) {
|
||||
if (use_raster) {
|
||||
uint32_t search_zoom = zoom;
|
||||
search_zoom += (uint32_t)((llmr::util::tileSize / 256.0f) - 1.0f);
|
||||
if (use_retina) {
|
||||
search_zoom += 1;
|
||||
}
|
||||
Tile::ID id = Tile::ID(zoom, x, y);
|
||||
auto ids = id.children(search_zoom);
|
||||
for (const Tile::ID& child_id : ids) {
|
||||
tiles.emplace_front(child_id.z, child_id.x, child_id.y);
|
||||
}
|
||||
} else {
|
||||
tiles.emplace_front(zoom, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Divide the screen up in two triangles and scan each of them:
|
||||
// \---+
|
||||
// | \ |
|
||||
// +---\.
|
||||
_scanTriangle(points.tl, points.tr, points.br, 0, dim, scanLine);
|
||||
_scanTriangle(points.br, points.bl, points.tl, 0, dim, scanLine);
|
||||
|
||||
const vec2<double>& center = points.center;
|
||||
tiles.sort([¢er](const Tile::ID& a, const Tile::ID& b) {
|
||||
// Sorts by distance from the box center
|
||||
return fabs(a.x - center.x) + fabs(a.y - center.y) <
|
||||
fabs(b.x - center.x) + fabs(b.y - center.y);
|
||||
});
|
||||
|
||||
tiles.unique();
|
||||
|
||||
return tiles;
|
||||
}
|
||||
269
src/map/map.cpp
269
src/map/map.cpp
@@ -1,12 +1,13 @@
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/settings.hpp>
|
||||
#include <llmr/map/source.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/style/resources.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/map/coverage.hpp>
|
||||
#include <llmr/util/animation.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -18,7 +19,7 @@ Map::Map(Settings& settings)
|
||||
glyphAtlas(1024, 1024),
|
||||
painter(transform, settings, style, glyphAtlas),
|
||||
min_zoom(0),
|
||||
max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) {
|
||||
max_zoom(21) {
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
@@ -35,6 +36,10 @@ void Map::setup(float pixelRatio) {
|
||||
style.sprite->load(kSpriteURL, pixel_ratio);
|
||||
|
||||
style.loadJSON(resources::style, resources::style_size);
|
||||
|
||||
sources.emplace("streets", Source(*this, transform, painter, texturepool, "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf", pixelRatio, Source::Type::vector, {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14}, 512, 0, 14, true));
|
||||
|
||||
// sources.emplace("raster", Source(*this, transform, painter, texturepool, "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256", pixelRatio, Source::Type::raster, {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}, 256, 0, 21, true));
|
||||
}
|
||||
|
||||
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
|
||||
@@ -60,11 +65,20 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::toggleRaster() {
|
||||
use_raster = ! use_raster;
|
||||
max_zoom = (use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom);
|
||||
tiles.clear();
|
||||
update();
|
||||
box Map::cornersToBox(uint32_t z) const {
|
||||
return transform.cornersToBox(z);
|
||||
}
|
||||
|
||||
float Map::getPixelRatio() const {
|
||||
return pixel_ratio;
|
||||
}
|
||||
|
||||
Style& Map::getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
GlyphAtlas& Map::getGlyphAtlas() {
|
||||
return glyphAtlas;
|
||||
}
|
||||
|
||||
void Map::moveBy(double dx, double dy, double duration) {
|
||||
@@ -244,197 +258,14 @@ void Map::update() {
|
||||
platform::restart();
|
||||
}
|
||||
|
||||
|
||||
TileData::State Map::hasTile(const Tile::ID& id) {
|
||||
for (const Tile& tile : tiles) {
|
||||
if (tile.id == id && tile.data) {
|
||||
return tile.data->state;
|
||||
}
|
||||
}
|
||||
|
||||
return TileData::State::invalid;
|
||||
}
|
||||
|
||||
TileData::State Map::addTile(const Tile::ID& id) {
|
||||
const TileData::State state = hasTile(id);
|
||||
|
||||
if (state != TileData::State::invalid) {
|
||||
return state;
|
||||
}
|
||||
|
||||
tiles.emplace_front(id);
|
||||
Tile& new_tile = tiles.front();
|
||||
|
||||
// We couldn't find the tile in the list. Create a new one.
|
||||
// Try to find the associated TileData object.
|
||||
const Tile::ID normalized_id = id.normalized();
|
||||
|
||||
auto it = std::find_if(tile_data.begin(), tile_data.end(), [&normalized_id](const std::weak_ptr<TileData>& tile_data) {
|
||||
return !tile_data.expired() && tile_data.lock()->id == normalized_id;
|
||||
});
|
||||
|
||||
if (it != tile_data.end()) {
|
||||
// Create a shared_ptr handle. Note that this might be empty!
|
||||
new_tile.data = it->lock();
|
||||
}
|
||||
|
||||
if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
|
||||
// Do not consider the tile if it's already obsolete.
|
||||
new_tile.data.reset();
|
||||
}
|
||||
|
||||
if (!new_tile.data) {
|
||||
// If we don't find working tile data, we're just going to load it.
|
||||
new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas, use_raster, (pixel_ratio > 1.0));
|
||||
new_tile.data->request();
|
||||
tile_data.push_front(new_tile.data);
|
||||
}
|
||||
|
||||
return new_tile.data->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = id.children(z + 1);
|
||||
for (const Tile::ID& child_id : ids) {
|
||||
const TileData::State state = hasTile(child_id);
|
||||
if (state == TileData::State::parsed) {
|
||||
retain.emplace_front(child_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 = id.parent(z);
|
||||
const TileData::State state = hasTile(parent_id);
|
||||
if (state == TileData::State::parsed) {
|
||||
retain.emplace_front(parent_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool Map::updateTiles() {
|
||||
bool changed = false;
|
||||
|
||||
// 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);
|
||||
|
||||
// Performs a scanline algorithm search that covers the rectangle of the box
|
||||
// and sorts them by proximity to the center.
|
||||
std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box, use_raster, (pixel_ratio > 1.0));
|
||||
|
||||
// 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) {
|
||||
const TileData::State state = addTile(id);
|
||||
|
||||
if (state != TileData::State::parsed) {
|
||||
if (use_raster && (transform.rotating || transform.scaling || transform.panning))
|
||||
break;
|
||||
|
||||
// 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 (state == TileData::State::initial) {
|
||||
changed = true;
|
||||
}
|
||||
for (std::pair<std::string, Source&> pair : sources) {
|
||||
Source& source = pair.second;
|
||||
changed = source.update() || changed;
|
||||
}
|
||||
|
||||
// Remove tiles that we definitely don't need, i.e. tiles that are not on
|
||||
// the required list.
|
||||
std::forward_list<Tile::ID> retain_data;
|
||||
tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) {
|
||||
bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
|
||||
if (obsolete) {
|
||||
changed = true;
|
||||
} else {
|
||||
retain_data.push_front(tile.data->id);
|
||||
}
|
||||
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 & a, const Tile & b) {
|
||||
return a.id.z > b.id.z;
|
||||
});
|
||||
|
||||
// Remove all the expired pointers from the list.
|
||||
tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) {
|
||||
const std::shared_ptr<TileData> tile = tile_data.lock();
|
||||
if (!tile) {
|
||||
return true;
|
||||
}
|
||||
bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end();
|
||||
if (obsolete) {
|
||||
tile->cancel();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -455,32 +286,40 @@ bool Map::render() {
|
||||
|
||||
// First, update all tile matrices with the new transform and render into
|
||||
// the stencil buffer.
|
||||
uint8_t i = 1;
|
||||
// uint8_t i = 1;
|
||||
|
||||
painter.prepareClippingMask();
|
||||
for (Tile& tile : tiles) {
|
||||
if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
// The position matrix.
|
||||
transform.matrixFor(tile.matrix, tile.id);
|
||||
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
|
||||
tile.clip_id = i++;
|
||||
painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster);
|
||||
}
|
||||
}
|
||||
painter.finishClippingMask();
|
||||
|
||||
for (const Tile& tile : tiles) {
|
||||
if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) {
|
||||
tile.data->raster->setTexturepool(&texturepool);
|
||||
tile.data->raster->beginFadeInAnimation();
|
||||
}
|
||||
if (tile.data->use_raster && tile.data->raster->needsAnimation()) {
|
||||
tile.data->raster->updateAnimations();
|
||||
}
|
||||
painter.render(tile);
|
||||
}
|
||||
// for (Tile& tile : tiles) {
|
||||
// if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
// // The position matrix.
|
||||
// transform.matrixFor(tile.matrix, tile.id);
|
||||
// matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
|
||||
// tile.clip_id = i++;
|
||||
// painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (std::pair<std::string, Source&> pair : sources) {
|
||||
Source& source = pair.second;
|
||||
source.render();
|
||||
}
|
||||
|
||||
// painter.finishClippingMask();
|
||||
|
||||
// for (const Tile& tile : tiles) {
|
||||
// if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
// if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) {
|
||||
// tile.data->raster->setTexturepool(&texturepool);
|
||||
// tile.data->raster->beginFadeInAnimation();
|
||||
// }
|
||||
// if (tile.data->use_raster && tile.data->raster->needsAnimation()) {
|
||||
// tile.data->raster->updateAnimations();
|
||||
// }
|
||||
// painter.render(tile);
|
||||
// }
|
||||
// }
|
||||
|
||||
painter.renderMatte();
|
||||
|
||||
return changed || transform.needsAnimation() || painter.needsAnimation();
|
||||
|
||||
386
src/map/source.cpp
Normal file
386
src/map/source.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
#include <llmr/map/source.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
#include <llmr/util/raster.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/texturepool.hpp>
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
Source::Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url, float pixel_ratio, Source::Type type, std::list<uint32_t> zooms, uint32_t tile_size, uint32_t min_zoom, uint32_t max_zoom, bool enabled)
|
||||
: map(map),
|
||||
transform(transform),
|
||||
painter(painter),
|
||||
texturepool(texturepool),
|
||||
url(url),
|
||||
pixel_ratio(pixel_ratio),
|
||||
type(type),
|
||||
zooms(zooms),
|
||||
tile_size(tile_size),
|
||||
min_zoom(min_zoom),
|
||||
max_zoom(max_zoom),
|
||||
enabled(enabled) {
|
||||
}
|
||||
|
||||
bool Source::update() {
|
||||
return updateTiles();
|
||||
}
|
||||
|
||||
void Source::render() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t i = 1;
|
||||
|
||||
for (Tile& tile : tiles) {
|
||||
if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
// The position matrix.
|
||||
transform.matrixFor(tile.matrix, tile.id);
|
||||
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
|
||||
tile.clip_id = i++;
|
||||
painter.drawClippingMask(tile.matrix, tile.clip_id, (type != Type::raster));
|
||||
}
|
||||
}
|
||||
|
||||
painter.finishClippingMask();
|
||||
|
||||
for (const Tile& tile : tiles) {
|
||||
if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
if (type == Type::raster && *tile.data->raster && !tile.data->raster->textured) {
|
||||
tile.data->raster->setTexturepool(&texturepool);
|
||||
tile.data->raster->beginFadeInAnimation();
|
||||
}
|
||||
if (type == Type::raster && tile.data->raster->needsAnimation()) {
|
||||
tile.data->raster->updateAnimations();
|
||||
}
|
||||
painter.render(tile);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TileData::State Source::hasTile(const Tile::ID& id) {
|
||||
for (const Tile& tile : tiles) {
|
||||
if (tile.id == id && tile.data) {
|
||||
return tile.data->state;
|
||||
}
|
||||
}
|
||||
|
||||
return TileData::State::invalid;
|
||||
}
|
||||
|
||||
TileData::State Source::addTile(const Tile::ID& id) {
|
||||
const TileData::State state = hasTile(id);
|
||||
|
||||
if (state != TileData::State::invalid) {
|
||||
return state;
|
||||
}
|
||||
|
||||
tiles.emplace_front(id);
|
||||
Tile& new_tile = tiles.front();
|
||||
|
||||
// We couldn't find the tile in the list. Create a new one.
|
||||
// Try to find the associated TileData object.
|
||||
const Tile::ID normalized_id = id.normalized();
|
||||
|
||||
auto it = std::find_if(tile_data.begin(), tile_data.end(), [&normalized_id](const std::weak_ptr<TileData>& tile_data) {
|
||||
return !tile_data.expired() && tile_data.lock()->id == normalized_id;
|
||||
});
|
||||
|
||||
if (it != tile_data.end()) {
|
||||
// Create a shared_ptr handle. Note that this might be empty!
|
||||
new_tile.data = it->lock();
|
||||
}
|
||||
|
||||
if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
|
||||
// Do not consider the tile if it's already obsolete.
|
||||
new_tile.data.reset();
|
||||
}
|
||||
|
||||
if (!new_tile.data) {
|
||||
// If we don't find working tile data, we're just going to load it.
|
||||
|
||||
std::string formed_url;
|
||||
|
||||
if (type == Source::Type::vector) {
|
||||
formed_url = util::sprintf(url, id.z, id.x, id.y);
|
||||
} else {
|
||||
formed_url = util::sprintf(url, id.z, id.x, id.y, (pixel_ratio > 1.0 ? "@2x" : ""));
|
||||
}
|
||||
|
||||
new_tile.data = std::make_shared<TileData>(normalized_id, map.getStyle(), map.getGlyphAtlas(), formed_url, (type == Source::Type::raster));
|
||||
new_tile.data->request();
|
||||
tile_data.push_front(new_tile.data);
|
||||
}
|
||||
|
||||
return new_tile.data->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Source::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) {
|
||||
bool complete = true;
|
||||
int32_t z = id.z;
|
||||
|
||||
|
||||
auto ids = id.children(z + 1);
|
||||
for (const Tile::ID& child_id : ids) {
|
||||
const TileData::State state = hasTile(child_id);
|
||||
if (state == TileData::State::parsed) {
|
||||
retain.emplace_front(child_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 Source::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 = id.parent(z);
|
||||
const TileData::State state = hasTile(parent_id);
|
||||
if (state == TileData::State::parsed) {
|
||||
retain.emplace_front(parent_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Source::updateTiles() {
|
||||
bool changed = false;
|
||||
|
||||
// Figure out what tiles we need to load
|
||||
int32_t zoom = map.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 = map.cornersToBox(zoom);
|
||||
|
||||
// Performs a scanline algorithm search that covers the rectangle of the box
|
||||
// and sorts them by proximity to the center.
|
||||
|
||||
std::forward_list<Tile::ID> required = covering_tiles(zoom, box);
|
||||
|
||||
// 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) {
|
||||
const TileData::State state = addTile(id);
|
||||
|
||||
if (state != TileData::State::parsed) {
|
||||
// if (use_raster && (transform.rotating || transform.scaling || transform.panning))
|
||||
// break;
|
||||
|
||||
// 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 (state == TileData::State::initial) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tiles that we definitely don't need, i.e. tiles that are not on
|
||||
// the required list.
|
||||
std::forward_list<Tile::ID> retain_data;
|
||||
tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) {
|
||||
bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
|
||||
if (obsolete) {
|
||||
changed = true;
|
||||
} else {
|
||||
retain_data.push_front(tile.data->id);
|
||||
}
|
||||
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 & a, const Tile & b) {
|
||||
return a.id.z > b.id.z;
|
||||
});
|
||||
|
||||
// Remove all the expired pointers from the list.
|
||||
tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) {
|
||||
const std::shared_ptr<TileData> tile = tile_data.lock();
|
||||
if (!tile) {
|
||||
return true;
|
||||
}
|
||||
bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end();
|
||||
if (obsolete) {
|
||||
tile->cancel();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void Source::renderTile(const Tile::ID& id) {
|
||||
|
||||
// painter.render(tile);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Taken from polymaps src/Layer.js
|
||||
// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383
|
||||
|
||||
struct edge {
|
||||
double x0 = 0, y0 = 0;
|
||||
double x1 = 0, y1 = 0;
|
||||
double dx = 0, dy = 0;
|
||||
edge(double x0, double y0, double x1, double y1, double dx, double dy)
|
||||
: x0(x0), y0(y0), x1(x1), y1(y1), dx(dx), dy(dy) {}
|
||||
};
|
||||
|
||||
typedef const std::function<void(int32_t, int32_t, int32_t, int32_t)> ScanLine;
|
||||
|
||||
// scan-line conversion
|
||||
edge _edge(const llmr::vec2<double> a, const llmr::vec2<double> b) {
|
||||
if (a.y > b.y) {
|
||||
return { b.x, b.y, a.x, a.y, a.x - b.x, a.y - b.y };
|
||||
} else {
|
||||
return { a.x, a.y, b.x, b.y, b.x - a.x, b.y - a.y };
|
||||
}
|
||||
}
|
||||
|
||||
// scan-line conversion
|
||||
void _scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) {
|
||||
double y0 = fmax(ymin, floor(e1.y0)),
|
||||
y1 = fmin(ymax, ceil(e1.y1));
|
||||
|
||||
// sort edges by x-coordinate
|
||||
if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ?
|
||||
(e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) :
|
||||
(e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
|
||||
std::swap(e0, e1);
|
||||
}
|
||||
|
||||
// scan lines!
|
||||
double m0 = e0.dx / e0.dy,
|
||||
m1 = e1.dx / e1.dy,
|
||||
d0 = e0.dx > 0, // use y + 1 to compute x0
|
||||
d1 = e1.dx < 0; // use y + 1 to compute x1
|
||||
for (int32_t y = y0; y < y1; y++) {
|
||||
double x0 = m0 * fmax(0, fmin(e0.dy, y + d0 - e0.y0)) + e0.x0,
|
||||
x1 = m1 * fmax(0, fmin(e1.dy, y + d1 - e1.y0)) + e1.x0;
|
||||
scanLine(floor(x1), ceil(x0), y, ymax);
|
||||
}
|
||||
}
|
||||
|
||||
// scan-line conversion
|
||||
void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const llmr::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) {
|
||||
edge ab = _edge(a, b);
|
||||
edge bc = _edge(b, c);
|
||||
edge ca = _edge(c, a);
|
||||
|
||||
// sort edges by y-length
|
||||
if (ab.dy > bc.dy) { std::swap(ab, bc); }
|
||||
if (ab.dy > ca.dy) { std::swap(ab, ca); }
|
||||
if (bc.dy > ca.dy) { std::swap(bc, ca); }
|
||||
|
||||
// scan span! scan span!
|
||||
if (ab.dy) _scanSpans(ca, ab, ymin, ymax, scanLine);
|
||||
if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine);
|
||||
}
|
||||
|
||||
std::forward_list<llmr::Tile::ID> Source::covering_tiles(int32_t zoom, const box& points) {
|
||||
int32_t dim = pow(2, zoom);
|
||||
std::forward_list<llmr::Tile::ID> tiles;
|
||||
Type source_type = type;
|
||||
uint32_t source_tile_size = tile_size;
|
||||
double map_zoom = map.getZoom();
|
||||
float pixel_ratio = map.getPixelRatio();
|
||||
|
||||
auto scanLine = [&tiles, zoom, source_type, source_tile_size, map_zoom, pixel_ratio](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
|
||||
int32_t x;
|
||||
if (y >= 0 && y <= ymax) {
|
||||
for (x = x0; x < x1; x++) {
|
||||
if (source_type == Type::raster) {
|
||||
uint32_t offset = (util::tileSize / source_tile_size) - 1;
|
||||
if (pixel_ratio > 1.0) {
|
||||
offset++;
|
||||
}
|
||||
int32_t search_zoom = map_zoom + offset;
|
||||
Tile::ID id = Tile::ID(zoom, x, y);
|
||||
auto ids = id.children(search_zoom);
|
||||
for (const Tile::ID& child_id : ids) {
|
||||
tiles.emplace_front(child_id.z, child_id.x, child_id.y);
|
||||
}
|
||||
} else {
|
||||
tiles.emplace_front(zoom, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Divide the screen up in two triangles and scan each of them:
|
||||
// \---+
|
||||
// | \ |
|
||||
// +---\.
|
||||
_scanTriangle(points.tl, points.tr, points.br, 0, dim, scanLine);
|
||||
_scanTriangle(points.br, points.bl, points.tl, 0, dim, scanLine);
|
||||
|
||||
const vec2<double>& center = points.center;
|
||||
tiles.sort([¢er](const Tile::ID& a, const Tile::ID& b) {
|
||||
// Sorts by distance from the box center
|
||||
return fabs(a.x - center.x) + fabs(a.y - center.y) <
|
||||
fabs(b.x - center.x) + fabs(b.y - center.y);
|
||||
});
|
||||
|
||||
tiles.unique();
|
||||
|
||||
return tiles;
|
||||
}
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster, const bool use_retina)
|
||||
TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster)
|
||||
: id(id),
|
||||
use_raster(use_raster),
|
||||
use_retina(use_retina),
|
||||
url(url),
|
||||
state(State::initial),
|
||||
is_raster(is_raster),
|
||||
raster(),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas) {
|
||||
@@ -38,14 +38,6 @@ const std::string TileData::toString() const {
|
||||
void TileData::request() {
|
||||
state = State::loading;
|
||||
|
||||
std::string url;
|
||||
|
||||
if (use_raster) {
|
||||
url = util::sprintf(kTileRasterURL, id.z, id.x, id.y, (use_retina ? "@2x" : ""));
|
||||
} else {
|
||||
url = util::sprintf(kTileVectorURL, id.z, id.x, id.y);
|
||||
}
|
||||
|
||||
// Note: Somehow this feels slower than the change to request_http()
|
||||
std::weak_ptr<TileData> weak_tile = shared_from_this();
|
||||
platform::Request *request = platform::request_http(url, [=](platform::Response& res) {
|
||||
@@ -78,7 +70,8 @@ bool TileData::parse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (use_raster) {
|
||||
if (is_raster) {
|
||||
// raster tiles don't need parsed, just decoded
|
||||
raster = std::make_shared<Raster>();
|
||||
raster->load(data);
|
||||
state = State::parsed;
|
||||
|
||||
@@ -272,7 +272,7 @@ double Transform::getAngle() const {
|
||||
return angle;
|
||||
}
|
||||
|
||||
box Transform::mapCornersToBox(uint32_t z) const {
|
||||
box Transform::cornersToBox(uint32_t z) const {
|
||||
const double ref_scale = pow(2, z);
|
||||
|
||||
const double angle_sin = sin(-angle);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
#include <llmr/platform/platform.hpp>
|
||||
|
||||
const char *llmr::kTileVectorURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf";
|
||||
const char *llmr::kTileRasterURL = "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256";
|
||||
const char *llmr::kSpriteURL = "http://mapbox-kkaefer.s3.amazonaws.com/static/sprite";
|
||||
|
||||
const int32_t llmr::kTileVectorMaxZoom = 14;
|
||||
const int32_t llmr::kTileRasterMaxZoom = 21;
|
||||
|
||||
@@ -162,7 +162,7 @@ void Painter::render(const Tile& tile) {
|
||||
matrix = tile.matrix;
|
||||
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
|
||||
|
||||
if (tile.data->use_raster) {
|
||||
if (tile.data->raster) {
|
||||
renderRaster(tile.data);
|
||||
} else {
|
||||
renderLayers(tile.data, style.layers);
|
||||
|
||||
Reference in New Issue
Block a user