Closed hallahan closed 7 years ago
A few thoughts
type
keyWe can check to see if the type
key corresponds with the format
key in the MBTiles metadata table. This is probably a good idea rather than just arbitrarily trying to use the contents of an MBTiles and hope for the best. If there were a mismatch and we had a url
, then the MBTiles would start to be populated with tiles of a conflicting format...
If there is no url
parameter in the source, we will only try and retrieve a tile from the MBTiles. The HTTP request for data will be skipped.
If there is both a url
and mbtiles
in the source, we will try to get data from the url and populate the MBTiles with that data as well as render it. If the URL request fails, we will continue and try to get data from MBTiles.
If there is only url
, we will skip the MBTiles bit all together and continue with the current behavior.
If there is both a
url
andmbtiles
in the source, we will try to get data from the url and populate the MBTiles with that data as well as render it. If the URL request fails, we will continue and try to get data from MBTiles.
This gets into cache behavior. There's uses for preferring either mbtiles or url, and if preferring the url you need to figure out when you want to fall back to the mbtiles because timeouts can be slow.
Do you have any suggestions on what makes sense regarding cache behavior? Are you thinking that more fine-grained control should be specifiable in the scene.yaml
? Or, maybe there is a good best practice that should be implemented?
Here's the code where I'm doing what I described:
startUrlRequest
is implemented as platform specific, though I'm pretty sure every platform will let you specify timeout rules. At least, I do know that is the case for Android.
Android's timeout rules:
Do you have any suggestions on what makes sense regarding cache behavior? Are you thinking that more fine-grained control should be specifiable in the
scene.yaml
? Or, maybe there is a good best practice that should be implemented?
As a minimum
MBtiles should be quick, but URL resources can be slow, and it's hard to know in advance how slow they will be, so a nice to have would be to specify the timeout for which the client falls back to the mbtiles.
Of interest might be the mod_tile settings in https://github.com/openstreetmap/mod_tile/blob/master/mod_tile.conf. Matt also has experience in this area.
Cool. Maybe we could add the following source parameters?
max_timeout: 30 # default
cache_priority: false # default
if they're on the same level as the url and mbtiles map, will it be clear what they apply to?
My choice of parameter names might not be the best. I'm happy to do whatever is the consensus. This isn't a blocker right now, because this sort of thing is an extra I can do once the basic functionality works.
My choice of parameter names might not be the best
I don't have a great alternative eithe
This isn't a blocker right now
Agreed
Wohoo! Looks like we've got the basics of MBTiles functionality working!
Super cool. Havn't got a chance to look at this yet. But yes great progress.
On Sep 9, 2016 9:32 PM, "Nicholas Hallahan" notifications@github.com wrote:
Wohoo! Looks like we've got the basics of MBTiles functionality working!
[image: screenshot 2016-09-09 18 29 44] https://cloud.githubusercontent.com/assets/556367/18407057/c5026130-76bb-11e6-9818-2d7870b5e5c9.png
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/tangrams/tangram-es/issues/960#issuecomment-246079666, or mute the thread https://github.com/notifications/unsubscribe-auth/AAWAwcDyTr0dOLcDUavz1yBbWG4smX4Cks5qogjAgaJpZM4J4dHy .
By manually setting the file path for an MBTiles I have in ExternalStorage, I am now able to view my offline data on my Android device in airplane mode:
It works right now if we set our source as:
sources:
osm:
type: GeoJSON
mbtiles: /storage/emulated/0/mbtiles/winters-mapzen-geojson.mbtiles
max_zoom: 16
This is great, because it proves that SQLiteCpp in the core library plays nicely on Android as-is. However, we need to be able to dynamically set our MBTiles file for two reasons:
ExternalStorage
, as is this one, we have to first get the user's permission to access ExternalStorage
before trying to open the .mbtiles
file.Environment.getExternalStorageDirectory()
to figure out the path to our .mbtiles
file.Today I attempted to solve this with two approaches, with varying degrees of success. I'm actually trying to get this working on the Mac OS client first so that I can debug properly.
map->queueSceneUpdate("sources.osm.mbtiles", "/Users/njh/osm-data/mbtiles/winters-mapzen-geojson.mbtiles");
map->applySceneUpdates();
requestRender();
The nice thing about this approach is that we don't have to add anything to Tangram::Map
and JNI. We could work with the existing API. I was having a lot of problems earlier, but it seems to actually be behaving properly now (not sure why). Basically, a new scene with new sources would get built, but the reference to the source in the TileSet
s is a shared_ptr, and it's reference hasn't been updated to the new DataSource
object that was just built. It is instead referencing that old object.
// in tileManager.h
struct TileSet {
TileSet(std::shared_ptr<DataSource> _source, bool _clientDataSource)
: source(_source), clientDataSource(_clientDataSource) {}
std::shared_ptr<DataSource> source;
std::map<TileID, TileEntry> tiles;
int64_t sourceGeneration = 0;
bool clientDataSource;
};
It turns out, after quite a bit of tinkering, I hadn't added the mbtiles
property to the DataSource::equals
method. Without this being updated, the TileManager::setDataSources
was thinking that the new data source is the same as the old data source, therefore leaving the old source pointer in place.
void TileManager::setDataSources(const std::vector<std::shared_ptr<DataSource>>& _sources) {
m_tileCache->clear();
// remove sources that are not in new scene - there must be a better way..
auto it = std::remove_if(
m_tileSets.begin(), m_tileSets.end(),
[&](auto& tileSet) {
if (!tileSet.clientDataSource) {
auto sIt = std::find_if(_sources.begin(), _sources.end(),
[&](auto& source){ return source->equals(*tileSet.source); });
if (sIt == _sources.end() || !(*sIt)->generateGeometry()) {
LOGD("remove source %s", tileSet.source->name().c_str());
return true;
}
}
// Clear cache
tileSet.tiles.clear();
return false;
});
m_tileSets.erase(it, m_tileSets.end());
// add new sources
for (const auto& source : _sources) {
if (std::find_if(m_tileSets.begin(), m_tileSets.end(),
[&](const TileSet& a) {
return a.source->name() == source->name();
}) == m_tileSets.end()
&& source->generateGeometry()) {
LOGD("add source %s", source->name().c_str());
m_tileSets.push_back({ source, false });
}
}
}
Now this approach seems all good with the right DataSource::equals
.
bool DataSource::equals(const DataSource& other) const {
if (m_name != other.m_name) { return false; }
if (m_urlTemplate != other.m_urlTemplate) { return false; }
if (m_mbtilesPath != other.m_mbtilesPath) { return false; }
if (m_minDisplayZoom != other.m_minDisplayZoom) { return false; }
if (m_maxDisplayZoom != other.m_maxDisplayZoom) { return false; }
if (m_maxZoom != other.m_maxZoom) { return false; }
if (m_rasterSources.size() != other.m_rasterSources.size()) { return false; }
for (size_t i = 0, end = m_rasterSources.size(); i < end; ++i) {
if (!m_rasterSources[i]->equals(*other.m_rasterSources[i])) { return false; }
}
return true;
}
https://github.com/hallahan/tangram-es/commit/8c8de4b7aac7416e9b6cf3147d3a54d7cc724af5
This approach adds a few extensions to the existing API, but it is a bit of a more direct. I don't have it working properly quite yet, and I may ditch this, unless @tallytalwar and @blair1618 think this is a better direction.
I added to bool DataSource::setMBTiles(const std::string& _mbtilesPath, const bool _offlineOnly)
which added the MBTiles path to the data source. Offline only removes the url template so we stop trying to request from a URL if that was in the scene.yaml
.
I also added bool Map::setMBTiles(const std::string& _dataSourceName, const std::string& _mbtilesPath, const bool _offlineOnly)
which iterates through the scene's data sources and sets the mbtiles path and offline properties accordingly when it has a matching name.
The problems that I was having are:
I bet if I implement some of the dirty work done in TileManager::setDataSources
, this can be resolved.
I tried to get this working and ran into the following issue. If you go to http://osm2vectortiles.org they no longer offer any tile downloads and redirect you to https://openmaptiles.org/ which will give you download options. However these mbtile files do not appear to work with the example application. The metadata format claims it is pbf. Are these mbtile files supposed to work or did they change formats when they changed websites, does anybody know?
If I try the example application using the scene.yaml with the url and the cache (the first example above) I can get the cached mbtile file that results to work by itself in a new scene.yaml so I'm fairly confident that I've got things setup correctly.
Here is the output when trying to use the mbtile file from openmaptiles.com
DEBUG scene.cpp:51: Scene '' => '' : '' TANGRAM tangram.cpp:199: Loading scene file (async): /path_to_scene.yaml/scene.yaml DEBUG scene.cpp:51: Scene '/path_to_scene.yaml/scene.yaml' => '/path_to_scene.yaml/' : 'scene.yaml' DEBUG importer.cpp:80: Process: '/path_to_scene.yaml/scene.yaml' DEBUG importer.cpp:71: Processing scene import Stack: DEBUG importer.cpp:256: Starting importing Scene: /path_to_scene.yaml/scene.yaml TANGRAM mbtilesDataSource.cpp:241: SQLite database opened: /path_to_mbtiles/united_states_of_america.mbtiles WARNING sceneLoader.cpp:1694: Can't find data source touch for layer touch TANGRAM tangram.cpp:832: setup GL TANGRAM hardware.cpp:60: Driver supports map buffer: 0 TANGRAM hardware.cpp:61: Driver supports vaos: 1 TANGRAM hardware.cpp:62: Driver supports rgb8_rgba8: 0 TANGRAM hardware.cpp:63: Driver supports NPOT texture: 1 TANGRAM hardware.cpp:77: Hardware max texture size 16384 TANGRAM hardware.cpp:78: Hardware max combined texture units 16 ||snip GL info|| TANGRAM tangram.cpp:305: resize: 1600 x 1200 DEBUG tileManager.cpp:75: add source osm WARNING mbtilesDataSource.cpp:156: loaded tile: 4823/6160/14, 159525 TANGRAM tileWorker.cpp:51: Passed new TileBuilder to TileWorker WARNING mbtilesDataSource.cpp:156: loaded tile: 4824/6160/14, 169355 TANGRAM tileWorker.cpp:51: Passed new TileBuilder to TileWorker DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. recreate context TANGRAM tangram.cpp:832: setup GL TANGRAM hardware.cpp:60: Driver supports map buffer: 0 TANGRAM hardware.cpp:61: Driver supports vaos: 1 TANGRAM hardware.cpp:62: Driver supports rgb8_rgba8: 0 TANGRAM hardware.cpp:63: Driver supports NPOT texture: 1 TANGRAM hardware.cpp:77: Hardware max texture size 16384 TANGRAM hardware.cpp:78: Hardware max combined texture units 16 ||snip GL info|| TANGRAM tangram.cpp:305: resize: 1600 x 1200 DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined. DEBUG styleContext.cpp:468: duk evaluates JS method to null or undefined.
@lukasmartinelli do you what's going on with openmaptiles.org extracts?
On Sep 9, 2016, at 18:55, Varun notifications@github.com wrote:
Super cool. Havn't got a chance to look at this yet. But yes great progress.
On Sep 9, 2016 9:32 PM, "Nicholas Hallahan" notifications@github.com wrote:
Wohoo! Looks like we've got the basics of MBTiles functionality working!
[image: screenshot 2016-09-09 18 29 44] https://cloud.githubusercontent.com/assets/556367/18407057/c5026130-76bb-11e6-9818-2d7870b5e5c9.png
― You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/tangrams/tangram-es/issues/960#issuecomment-246079666, or mute the thread https://github.com/notifications/unsubscribe-auth/AAWAwcDyTr0dOLcDUavz1yBbWG4smX4Cks5qogjAgaJpZM4J4dHy .
― You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
I tried to get this working and ran into the following issue. If you go to http://osm2vectortiles.org they no longer offer any tile downloads and redirect you to https://openmaptiles.org/ which will give you download options. However these mbtile files do not appear to work with the example application. The metadata format claims it is pbf. Are these mbtile files supposed to work or did they change formats when they changed websites, does anybody know?
Nothing changed format wise as we used the same components - what is a bit tricky is that the PBFs are gzipped inside the MBTiles (but they were for OSM2VectorTiles as well).
I only have an example of using the Tangram WebGL client as I did here: https://openmaptiles.org/docs/website/tangram/
Ok thanks for letting me know, I've liberally sprinkled break points in the mvtSource.cpp file and in the parse method it does look like it is getting data as I can step through and watch it add points of interest and waterways etc to the tileData object. Chasing the data from the mvtSource file I've found that in the StyleBuilder.cpp addFeature method the line:
if (!checkRule(_rule)) { return false; }
always executes the return false because the breakpoint on the next block of code in the method never gets hit.
If I comment that return false bit then I see things getting drawn (although not correctly as things flash into and out of existance in nonsensical ways and I only seem to get waterways and continents). So any idea what I could be missing that makes that return fire every time?
@Serinox To use openmaptiles you need a scene file with styling rules that match the tile data. See https://openmaptiles.org/schema for the layers/feature properties to match.
I made some (incomplete) modifications in scene.yaml to show openmaptiles with tangram https://gist.github.com/hjanetzek/8f2d8c4c7e485fdc0479e5ace6eb5d14
@hjanetzek: Ok that got it working! Many Thanks!
Merged as part of #1015
@hjanetzek I use https://gist.github.com/hjanetzek/8f2d8c4c7e485fdc0479e5ace6eb5d14 but it can not show the map. Can you help me ,thank you!
@TGISer Can you please create a new issue describing what you are trying to do? Thanks
As discussed in #931, an important feature many would like to add is offline map tile support. After a bit of further discussion, it looks like the consensus is to build MBTiles support into the data sources.
MBTiles is a spec that has been maintained since 2011, primarily as a tile storage and transport container. Because it is a widely used method to store tiles, both vector and image, it seems only logical to continue using this schema to store vector tiles offline.
The spec itself describes the SQLite database schema that is required for the file to be considered MBTiles. Right now the spec only describes the contents of a tile to be
png
orjpg
. @pnorman, however, has submitted a PR that addsgeojson
,topojson
,o5m
, andmvt
to the list of valid formats. In addition, there will be acompression
field in the MBTiles metadata table that declares the codec in which tile contents may be compressed.In the wild, you can find MBTiles storing vector tiles in several places. The most notable example is osm2vectortiles.org.
My thought is that we want to use MBTiles in two key ways: 1) As a tile store and transport mechanism, and 2) as a cache.
Tile Store
There are currently several apps and map SDKs that store raster and vector tiles offline, however, the approach is often only of a caching nature. You will specify a bbox and zoom range in the application, and the application will fetch tiles from an HTTP endpoint, one-by-one. This approach is cumbersome and slow, particulary on a poor internet connection. An alternative workflow would be for Mapzen and others to generate and store pre-populated MBTiles on the server. This approach is consistent with osm2vectortiles, and it can potentially lend well as an addition to Mapzen Metro Extracts.
Cache
Regardless of whether we have an MBTiles data source that has been pre-populated, we want to be able to request tiles ad hoc as we currently do and add them to an MBTiles. At this time, there is an in-memory tile cache of 32MB, and this is a great way to cache tiles in session. However, a persistent cache will be a helpful way to keep tile data over a longer span of time. This should be more resilient than platform-specific caching mechanisms (#623).
scene.yaml
To satisfy having MBTiles as both a container store and cache, we can essentially add a new field to a data source called
mbtiles
.This source can both use the MBTiles file both as a store and a cache. I prepopulated an MBTiles file from Mapzen's tile API for a small town (Winters, CA). This was done with the NodeJS command line tool, tl.
With a
url
parameter specified for the source, when the device is online it will first request a tile from the HTTP endpoint. In addition to rendering that tile, it will store it in the MBTiles file. If we omit theurl
parameter, we will skip the HTTP request and instead just try to get the tile from the MBTiles store. This approach means that we try and get up-to-date data from HTTP every time if we are online. We fallback to MBTiles if we are offline.My thought is that the MBTiles work can happen in the tile worker when a tile task is being processed. To run the tile task, instead of just checking to see if the tile task has data, we can also enqueue the task if the tile has an MBTiles store. Then, rather than simply parse the tile's
rawData
buffer, we can instead select that raw data from SQLite if it's not there.To sum this up: rather than creating a new type of data source called
MBTilesSource
, we instead create a new property in the baseDataSource
. Any data source with anmbtiles:
property can be treated as such.SQLiteCpp
Though iOS and Android have their platform specific ways of accessing SQLite databases, it is straightforward using SQLiteCpp in our core C++ project. It is a CMake project, well documented and tested, and has a clean C++ API that wraps the core sqlite3 lib. I have been able to integrate this library into Tangram ES, and playing with it has proven successful so far.
Platforms
Right now I'm thinking it would be best to focus on adding the support on the core Tangram ES side of things. We can declare the path to the MBTiles file in the
scene.yaml
, however, it will also be important to add methods of adding an MBTiles data source in the Android and iOS APIs. In Android, we often need to use Android's API to get paths to files, particularly if we are interested in working in ExternalStorage. In those situations, reliably declaring the path to an MBTiles file inscene.yaml
will not work. Maybe we should save that for a later issue?I'm making a few assumptions as to how this should be done, so input from the team would be helpful as I'm getting started. Some of the implementation specifics I can work though, but I'd like a :thumbsup: / :thumbsdown: for the overall approach.
cc/ @tallytalwar @blair1618 @nvkelso @karimnaaji @bcamper @pnorman @mojodna @migurski