diff --git a/README.rst b/README.rst index 45fb7d5..c33bdf8 100644 --- a/README.rst +++ b/README.rst @@ -95,6 +95,11 @@ geometry: forcegeometry: Generate a map of the requested size, even if the world is smaller. +sqlite-cacheworldrow: + When using sqlite, read an entire world row at one, instead of reading + one block at a time. + This may improve performance when a large percentage of the world is mapped. + verbose: report some useful/ interesting information: - maximum coordinates of the world diff --git a/TileGenerator.cpp b/TileGenerator.cpp index f29508a..2a89af1 100644 --- a/TileGenerator.cpp +++ b/TileGenerator.cpp @@ -107,6 +107,7 @@ TileGenerator::TileGenerator(): m_border(0), m_backend("sqlite3"), m_forceGeom(false), + m_sqliteCacheWorldRow(false), m_image(0), m_xMin(INT_MAX/16-1), m_xMax(INT_MIN/16+1), @@ -142,6 +143,11 @@ void TileGenerator::setForceGeom(bool forceGeom) m_forceGeom = forceGeom; } +void TileGenerator::setSqliteCacheWorldRow(bool cacheWorldRow) +{ + m_sqliteCacheWorldRow = cacheWorldRow; +} + void TileGenerator::setScaleColor(const std::string &scaleColor) { m_scaleColor = parseColor(scaleColor); @@ -319,8 +325,11 @@ void TileGenerator::parseColorsStream(std::istream &in) void TileGenerator::openDb(const std::string &input) { - if(m_backend == "sqlite3") - m_db = new DBSQLite3(input); + if(m_backend == "sqlite3") { + DBSQLite3 *db; + m_db = db = new DBSQLite3(input); + db->cacheWorldRow = m_sqliteCacheWorldRow; + } #if USE_LEVELDB else if(m_backend == "leveldb") m_db = new DBLevelDB(input); @@ -469,28 +478,9 @@ void TileGenerator::createImage() gdImageFilledRectangle(m_image, 0, 0, m_mapWidth + m_border - 1, m_mapHeight + m_border -1, rgb2int(m_bgColor.r, m_bgColor.g, m_bgColor.b)); } -std::map TileGenerator::getBlocksOnZ(int zPos) -{ - DBBlockList in = m_db->getBlocksOnZ(zPos); - std::map out; - for(DBBlockList::const_iterator it = in.begin(); it != in.end(); ++it) { - Block b = Block(decodeBlockPos(it->first), it->second); - if(out.find(b.first.x) == out.end()) { - BlockList bl; - out[b.first.x] = bl; - } - out[b.first.x].push_back(b); - } - return out; -} - TileGenerator::Block TileGenerator::getBlockOnPos(BlockPos pos) { - int64_t iPos; - iPos = pos.x; - iPos += static_cast(pos.y) << 12; - iPos += static_cast(pos.z) << 24; - DBBlock in = m_db->getBlockOnPos(iPos); + DBBlock in = m_db->getBlockOnPos(pos.x, pos.y, pos.z); Block out(pos,(const unsigned char *)""); if (!in.second.empty()) { @@ -518,7 +508,6 @@ TileGenerator::Block TileGenerator::getBlockOnPos(BlockPos pos) void TileGenerator::renderMap() { - int blocks_selected = 0; int blocks_rendered = 0; BlockPos currentPos; currentPos.x = INT_MIN; @@ -539,7 +528,6 @@ void TileGenerator::renderMap() continue; } Block block = getBlockOnPos(pos); - blocks_selected++; if (!block.second.empty()) { const unsigned char *data = block.second.c_str(); size_t length = block.second.length(); @@ -636,7 +624,12 @@ void TileGenerator::renderMap() if(currentPos.z != INT_MIN && m_shading) renderShading(currentPos.z); if (verboseStatistics) - cout << "Statistics: Blocks selected: " << blocks_selected << "; blocks rendered: " << blocks_rendered << std::endl; + cout << "Statistics" + << ": blocks read: " << m_db->getBlocksReadCount() + << "; (" << m_db->getBlocksCachedCount() << " cached + " + << m_db->getBlocksUnCachedCount() << " uncached)" + << "; blocks rendered: " << blocks_rendered + << std::endl; } inline void TileGenerator::renderMapBlock(const unsigned_string &mapBlock, const BlockPos &pos, int version) diff --git a/TileGenerator.h b/TileGenerator.h index 1f62d9e..73c1e7a 100644 --- a/TileGenerator.h +++ b/TileGenerator.h @@ -98,6 +98,7 @@ public: void setMinY(int y); void setMaxY(int y); void setForceGeom(bool forceGeom); + void setSqliteCacheWorldRow(bool cacheWorldRow); void parseColorsFile(const std::string &fileName); void setBackend(std::string backend); void generate(const std::string &input, const std::string &output); @@ -111,7 +112,6 @@ private: void renderMap(); std::list getZValueList() const; Block getBlockOnPos(BlockPos pos); - std::map getBlocksOnZ(int zPos); void renderMapBlock(const unsigned_string &mapBlock, const BlockPos &pos, int version); void renderShading(int zPos); void renderScale(); @@ -138,6 +138,7 @@ private: int m_border; std::string m_backend; bool m_forceGeom; + bool m_sqliteCacheWorldRow; DB *m_db; gdImagePtr m_image; diff --git a/db-leveldb.cpp b/db-leveldb.cpp index 41712a7..b7fb16e 100644 --- a/db-leveldb.cpp +++ b/db-leveldb.cpp @@ -15,7 +15,11 @@ inline std::string i64tos(int64_t i) { return o.str(); } -DBLevelDB::DBLevelDB(const std::string &mapdir) { +DBLevelDB::DBLevelDB(const std::string &mapdir) : + m_blocksReadCount(0), + m_blocksCachedCount(0), + m_blocksUnCachedCount(0) +{ leveldb::Options options; options.create_if_missing = false; leveldb::Status status = leveldb::DB::Open(options, mapdir + "map.db", &m_db); @@ -27,6 +31,21 @@ DBLevelDB::~DBLevelDB() { delete m_db; } +int DBLevelDB::getBlocksReadCount(void) +{ + return m_blocksReadCount; +} + +int DBLevelDB::getBlocksCachedCount(void) +{ + return m_blocksCachedCount; +} + +int DBLevelDB::getBlocksUnCachedCount(void) +{ + return m_blocksUnCachedCount; +} + std::vector DBLevelDB::getBlockPos() { std::vector vec; std::set s; @@ -40,37 +59,23 @@ std::vector DBLevelDB::getBlockPos() { return vec; } -DBBlockList DBLevelDB::getBlocksOnZ(int zPos) -{ - DBBlockList blocks; - std::string datastr; - leveldb::Status status; - - int64_t psMin; - int64_t psMax; - psMin = (zPos * 16777216l) - 0x800000; - psMax = (zPos * 16777216l) + 0x7fffff; - - for(int64_t i = psMin; i <= psMax; i++) { // FIXME: This is still very very inefficent (even with m_bpcache) - if(m_bpcache.find(i) == m_bpcache.end()) - continue; - status = m_db->Get(leveldb::ReadOptions(), i64tos(i), &datastr); - if(status.ok()) - blocks.push_back( DBBlock( i, std::basic_string( (const unsigned char*) datastr.c_str(), datastr.size() ) ) ); - } - - return blocks; -} - -DBBlock DBLevelDB::getBlocksOnPos(int64_t iPos) +DBBlock DBLevelDB::getBlockOnPos(int x, int y, int z) { + int64_t iPos; DBBlock block(0,(const unsigned char *)""); std::string datastr; leveldb::Status status; - status = m_db->Get(leveldb::ReadOptions(), i64tos(Pos), &datastr); - if(status.ok()) + iPos = static_cast(x); + iPos += static_cast(y) << 12; + iPos += static_cast(z) << 24; + + status = m_db->Get(leveldb::ReadOptions(), i64tos(iPos), &datastr); + if(status.ok()) { block = DBBlock( iPos, std::basic_string( (const unsigned char*) datastr.c_str(), datastr.size() ) ); + m_blocksReadCount++; + m_blocksUnCachedCount++; + } return block; } diff --git a/db-leveldb.h b/db-leveldb.h index c25896f..c15e6aa 100644 --- a/db-leveldb.h +++ b/db-leveldb.h @@ -8,11 +8,16 @@ class DBLevelDB : public DB { public: DBLevelDB(const std::string &mapdir); + virtual int getBlocksUnCachedCount(void); + virtual int getBlocksCachedCount(void); + virtual int getBlocksReadCount(void); virtual std::vector getBlockPos(); - virtual DBBlockList getBlocksOnZ(int zPos); - virtual DBBlock getBlockOnPos(int64_t iPos); + virtual DBBlock getBlockOnPos(int x, int y, int z); ~DBLevelDB(); private: + int m_blocksReadCount; + int m_blocksCachedCount; + int m_blocksUnCachedCount; leveldb::DB *m_db; std::set m_bpcache; }; diff --git a/db-sqlite3.cpp b/db-sqlite3.cpp index e3fbabb..f556afd 100644 --- a/db-sqlite3.cpp +++ b/db-sqlite3.cpp @@ -2,10 +2,15 @@ #include #include // for usleep + DBSQLite3::DBSQLite3(const std::string &mapdir) : - m_getBlockPosStatement(NULL), - m_getBlocksOnZStatement(NULL), - m_getBlocksOnPosStatement(NULL) + cacheWorldRow(false), + m_blocksReadCount(0), + m_blocksCachedCount(0), + m_blocksUnCachedCount(0), + m_blockPosListStatement(NULL), + m_blocksOnZStatement(NULL), + m_blockOnPosStatement(NULL) { std::string db_name = mapdir + "map.sqlite"; @@ -15,21 +20,36 @@ DBSQLite3::DBSQLite3(const std::string &mapdir) : } DBSQLite3::~DBSQLite3() { - if (m_getBlockPosStatement) sqlite3_finalize(m_getBlockPosStatement); - if (m_getBlocksOnZStatement) sqlite3_finalize(m_getBlocksOnZStatement); - if (m_getBlocksOnPosStatement) sqlite3_finalize(m_getBlocksOnPosStatement); + if (m_blockPosListStatement) sqlite3_finalize(m_blockPosListStatement); + if (m_blocksOnZStatement) sqlite3_finalize(m_blocksOnZStatement); + if (m_blockOnPosStatement) sqlite3_finalize(m_blockOnPosStatement); sqlite3_close(m_db); } +int DBSQLite3::getBlocksReadCount(void) +{ + return m_blocksReadCount; +} + +int DBSQLite3::getBlocksCachedCount(void) +{ + return m_blocksCachedCount; +} + +int DBSQLite3::getBlocksUnCachedCount(void) +{ + return m_blocksUnCachedCount; +} + std::vector DBSQLite3::getBlockPos() { std::vector vec; std::string sql = "SELECT pos FROM blocks"; - if (m_getBlockPosStatement || sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_getBlockPosStatement, 0) == SQLITE_OK) { + if (m_blockPosListStatement || sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_blockPosListStatement, 0) == SQLITE_OK) { int result = 0; while (true) { - result = sqlite3_step(m_getBlockPosStatement); + result = sqlite3_step(m_blockPosListStatement); if(result == SQLITE_ROW) { - sqlite3_int64 blocknum = sqlite3_column_int64(m_getBlockPosStatement, 0); + sqlite3_int64 blocknum = sqlite3_column_int64(m_blockPosListStatement, 0); vec.push_back(blocknum); } else if (result == SQLITE_BUSY) // Wait some time and try again usleep(10000); @@ -37,17 +57,58 @@ std::vector DBSQLite3::getBlockPos() { break; } } else { + sqlite3_reset(m_blockPosListStatement); throw std::runtime_error("Failed to get list of MapBlocks"); } + sqlite3_reset(m_blockPosListStatement); return vec; } -DBBlockList DBSQLite3::getBlocksOnZ(int zPos) +void DBSQLite3::prepareBlocksOnZStatement(void) { - std::string sql = "SELECT pos, data FROM blocks WHERE (pos >= ? AND pos <= ?)"; - if (!m_getBlocksOnZStatement && sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_getBlocksOnZStatement, 0) != SQLITE_OK) { - throw std::runtime_error("Failed to prepare statement"); + //std::string sql = "SELECT pos, data FROM blocks WHERE (pos >= ? AND pos <= ?)"; + std::string sql = "SELECT pos, data FROM blocks WHERE (pos BETWEEN ? AND ?)"; + if (!m_blocksOnZStatement && sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_blocksOnZStatement, 0) != SQLITE_OK) { + throw std::runtime_error("Failed to prepare statement (blocksOnZStatement)"); } +} + +void DBSQLite3::prepareBlockOnPosStatement(void) +{ + std::string sql = "SELECT pos, data FROM blocks WHERE pos == ?"; + if (!m_blockOnPosStatement && sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_blockOnPosStatement, 0) != SQLITE_OK) { + throw std::runtime_error("Failed to prepare SQL statement (blockOnPosStatement)"); + } +} + +// Apparently, this attempt at being smart, is actually quite inefficient for sqlite. +// +// For larger subsections of the map, it performs much worse than caching an entire +// world row (i.e. z coordinate). In cases where it *is* more efficient, no caching is +// much more efficient still. +// +// It seems that any computation on pos severely affects the performance (?)... +// +// For the moment, this function is not used. +void DBSQLite3::prepareBlocksYRangeStatement(void) +{ + // This one seems to perform best: + std::string sql = "SELECT pos, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) AND (pos-?3)&4095 == 0 AND (pos-?3 BETWEEN ?4 AND ?5)"; + // These perform worse: + //std::string sql = "SELECT pos, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) AND (pos-?3 BETWEEN ?4 AND ?5) AND (pos-?3)&4095 == 0"; + //std::string sql = "SELECT pos, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) AND (pos-?3 BETWEEN ?4 AND ?5)"; + //std::string sql = "SELECT pos, data FROM (select pos, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) ) WHERE (pos-?3 BETWEEN ?4 AND ?5) AND (pos-?3)&4095 == 0"; + //std::string sql = "SELECT pos, data FROM (select pos, (pos-?3) AS pos3, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) ) WHERE (pos3 BETWEEN ?4 AND ?5) AND (pos3)&4095 == 0"; + //std::string sql = "SELECT pos, data FROM (select pos, (pos-?3) AS pos3, data FROM blocks WHERE (pos BETWEEN ?1 AND ?2) ) WHERE (pos3 BETWEEN ?4 AND ?5)"; + if (!m_blocksYRangeStatement && sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_blocksYRangeStatement, 0) != SQLITE_OK) { + throw std::runtime_error("Failed to prepare SQL statement (blocksYRangeStatement)"); + } +} + +void DBSQLite3::cacheBlocksOnZRaw(int zPos) +{ + prepareBlocksOnZStatement(); + DBBlockList blocks; sqlite3_int64 psMin; @@ -55,47 +116,31 @@ DBBlockList DBSQLite3::getBlocksOnZ(int zPos) psMin = (static_cast(zPos) * 16777216l) - 0x800000; psMax = (static_cast(zPos) * 16777216l) + 0x7fffff; - sqlite3_bind_int64(m_getBlocksOnZStatement, 1, psMin); - sqlite3_bind_int64(m_getBlocksOnZStatement, 2, psMax); - int result = 0; - while (true) { - result = sqlite3_step(m_getBlocksOnZStatement); - if(result == SQLITE_ROW) { - sqlite3_int64 blocknum = sqlite3_column_int64(m_getBlocksOnZStatement, 0); - const unsigned char *data = reinterpret_cast(sqlite3_column_blob(m_getBlocksOnZStatement, 1)); - int size = sqlite3_column_bytes(m_getBlocksOnZStatement, 1); - blocks.push_back(DBBlock(blocknum, std::basic_string(data, size))); - } else if (result == SQLITE_BUSY) { // Wait some time and try again - usleep(10000); - } else { - break; - } - } - sqlite3_reset(m_getBlocksOnZStatement); + sqlite3_bind_int64(m_blocksOnZStatement, 1, psMin); + sqlite3_bind_int64(m_blocksOnZStatement, 2, psMax); - return blocks; + cacheBlocks(m_blocksOnZStatement); } -DBBlock DBSQLite3::getBlockOnPos(int64_t iPos) +DBBlock DBSQLite3::getBlockOnPosRaw(sqlite3_int64 psPos) { - std::string sql = "SELECT pos, data FROM blocks WHERE pos == ?"; - if (!m_getBlocksOnPosStatement && sqlite3_prepare_v2(m_db, sql.c_str(), sql.length(), &m_getBlocksOnPosStatement, 0) != SQLITE_OK) { - throw std::runtime_error("Failed to prepare statement"); - } + prepareBlockOnPosStatement(); + DBBlock block(0,(const unsigned char *)""); - - sqlite3_int64 psPos = static_cast(iPos); - sqlite3_bind_int64(m_getBlocksOnPosStatement, 1, psPos); - int result = 0; + + sqlite3_bind_int64(m_blockOnPosStatement, 1, psPos); + while (true) { - result = sqlite3_step(m_getBlocksOnPosStatement); + result = sqlite3_step(m_blockOnPosStatement); if(result == SQLITE_ROW) { - sqlite3_int64 blocknum = sqlite3_column_int64(m_getBlocksOnPosStatement, 0); - const unsigned char *data = reinterpret_cast(sqlite3_column_blob(m_getBlocksOnPosStatement, 1)); - int size = sqlite3_column_bytes(m_getBlocksOnPosStatement, 1); + sqlite3_int64 blocknum = sqlite3_column_int64(m_blockOnPosStatement, 0); + const unsigned char *data = reinterpret_cast(sqlite3_column_blob(m_blockOnPosStatement, 1)); + int size = sqlite3_column_bytes(m_blockOnPosStatement, 1); block = DBBlock(blocknum, std::basic_string(data, size)); + m_blocksUnCachedCount++; + //std::cerr << "Read block " << blocknum << " from database" << std::endl; break; } else if (result == SQLITE_BUSY) { // Wait some time and try again usleep(10000); @@ -103,8 +148,82 @@ DBBlock DBSQLite3::getBlockOnPos(int64_t iPos) break; } } - sqlite3_reset(m_getBlocksOnPosStatement); + sqlite3_reset(m_blockOnPosStatement); return block; } +void DBSQLite3::cacheBlocksYRangeRaw(int x, int y, int z) +{ + prepareBlocksYRangeStatement(); + + sqlite3_int64 psZPosFrom = (static_cast(z) << 24) - 0x800000; + sqlite3_int64 psZPosTo = (static_cast(z) << 24) + 0x7fffff; + sqlite3_int64 psPosZero = static_cast(x); + psPosZero += static_cast(z) << 24; + sqlite3_int64 psYPosFrom = 0; + sqlite3_int64 psYPosTo = static_cast(y) << 12; + + sqlite3_bind_int64(m_blocksYRangeStatement, 1, psZPosFrom); + sqlite3_bind_int64(m_blocksYRangeStatement, 2, psZPosTo); + sqlite3_bind_int64(m_blocksYRangeStatement, 3, psPosZero); + sqlite3_bind_int64(m_blocksYRangeStatement, 4, psYPosFrom); + sqlite3_bind_int64(m_blocksYRangeStatement, 5, psYPosTo); + + cacheBlocks(m_blocksYRangeStatement); +} + +void DBSQLite3::cacheBlocks(sqlite3_stmt *SQLstatement) +{ + int result = 0; + while (true) { + result = sqlite3_step(SQLstatement); + if(result == SQLITE_ROW) { + sqlite3_int64 blocknum = sqlite3_column_int64(SQLstatement, 0); + const unsigned char *data = reinterpret_cast(sqlite3_column_blob(SQLstatement, 1)); + int size = sqlite3_column_bytes(SQLstatement, 1); + m_blockCache[blocknum] = DBBlock(blocknum, std::basic_string(data, size)); + m_blocksCachedCount++; + //std::cerr << "Cache block " << blocknum << " from database" << std::endl; + } else if (result == SQLITE_BUSY) { // Wait some time and try again + usleep(10000); + } else { + break; + } + } + sqlite3_reset(SQLstatement); +} + +DBBlock DBSQLite3::getBlockOnPos(int x, int y, int z) +{ + sqlite3_int64 psPos; + psPos = static_cast(x); + psPos += static_cast(y) << 12; + psPos += static_cast(z) << 24; + //std::cerr << "Block " << x << "," << y << "," << z << " -> " << psPos << std::endl; + + m_blocksReadCount++; + + BlockCache::const_iterator DBBlockSearch; + DBBlockSearch = m_blockCache.find(psPos); + if (DBBlockSearch == m_blockCache.end()) { + if (cacheWorldRow) { + m_blockCache.clear(); + cacheBlocksOnZRaw(z); + DBBlockSearch = m_blockCache.find(psPos); + if (DBBlockSearch != m_blockCache.end()) { + return DBBlockSearch->second; + } + else { + return DBBlock(0, (const unsigned char *)""); + } + } + else { + return getBlockOnPosRaw(psPos); + } + } + else { + return DBBlockSearch->second; + } +} + diff --git a/db-sqlite3.h b/db-sqlite3.h index 8cd0742..a467aef 100644 --- a/db-sqlite3.h +++ b/db-sqlite3.h @@ -3,20 +3,48 @@ #include "db.h" #include +#if _cplusplus == 201103L +#include +#else #include +#endif +#include +#include class DBSQLite3 : public DB { +#if _cplusplus == 201103L + typedef std::unordered_map BlockCache; +#else + typedef std::map BlockCache; +#endif public: + bool cacheWorldRow; DBSQLite3(const std::string &mapdir); + virtual int getBlocksUnCachedCount(void); + virtual int getBlocksCachedCount(void); + virtual int getBlocksReadCount(void); virtual std::vector getBlockPos(); - virtual DBBlockList getBlocksOnZ(int zPos); - virtual DBBlock getBlockOnPos(int64_t iPos); + virtual DBBlock getBlockOnPos(int x, int y, int z); ~DBSQLite3(); private: + int m_blocksReadCount; + int m_blocksCachedCount; + int m_blocksUnCachedCount; sqlite3 *m_db; - sqlite3_stmt *m_getBlockPosStatement; - sqlite3_stmt *m_getBlocksOnZStatement; - sqlite3_stmt *m_getBlocksOnPosStatement; + sqlite3_stmt *m_blockPosListStatement; + sqlite3_stmt *m_blocksOnZStatement; + sqlite3_stmt *m_blockOnPosStatement; + sqlite3_stmt *m_blocksYRangeStatement; + std::ostringstream m_getBlockSetStatementBlocks; + BlockCache m_blockCache; + + void prepareBlocksOnZStatement(void); + void prepareBlockOnPosStatement(void); + void prepareBlocksYRangeStatement(void); + void cacheBlocksYRangeRaw(int x, int y, int z); + void cacheBlocksOnZRaw(int zPos); + DBBlock getBlockOnPosRaw(sqlite3_int64 psPos); + void cacheBlocks(sqlite3_stmt *SQLstatement); }; #endif // _DB_SQLITE3_H diff --git a/db.h b/db.h index ed6e386..296a53b 100644 --- a/db.h +++ b/db.h @@ -14,8 +14,10 @@ typedef std::list DBBlockList; class DB { public: virtual std::vector getBlockPos()=0; - virtual DBBlockList getBlocksOnZ(int zPos)=0; - virtual DBBlock getBlockOnPos(int64_t iPos)=0; + virtual int getBlocksUnCachedCount(void)=0; + virtual int getBlocksCachedCount(void)=0; + virtual int getBlocksReadCount(void)=0; + virtual DBBlock getBlockOnPos(int x, int y, int z)=0; }; #endif // _DB_H diff --git a/mapper.cpp b/mapper.cpp index eccf792..4f1eb5c 100644 --- a/mapper.cpp +++ b/mapper.cpp @@ -18,6 +18,8 @@ using namespace std; +#define OPT_SQLITE_CACHEWORLDROW 0x81 + void usage() { const char *usage_text = "minetestmapper [options]\n" @@ -36,6 +38,7 @@ void usage() " --backend \n" " --geometry x:y+w+h\n" " --forcegeometry\n" + " --sqlite-cacheworldrow\n" " --verbose\n" "Color format: '#000000'\n"; std::cout << usage_text; @@ -61,6 +64,7 @@ int main(int argc, char *argv[]) {"min-y", required_argument, 0, 'a'}, {"max-y", required_argument, 0, 'c'}, {"backend", required_argument, 0, 'd'}, + {"sqlite-cacheworldrow", no_argument, 0, OPT_SQLITE_CACHEWORLDROW}, {"verbose", no_argument, 0, 'v'}, }; @@ -119,6 +123,9 @@ int main(int argc, char *argv[]) case 'H': generator.setShading(false); break; + case OPT_SQLITE_CACHEWORLDROW: + generator.setSqliteCacheWorldRow(true); + break; case 'a': { istringstream iss; iss.str(optarg);