/*! \file */ #ifndef _TILE_DATA_H #define _TILE_DATA_H #include #include #include #include #include "append_vector.h" #include "clip_cache.h" #include "mmap_allocator.h" #include "tile_coordinates_set.h" #include "tile_data_base.h" typedef std::vector SourceList; class TileBbox; template void sortOutputObjects( const unsigned int indexZoom, const size_t threadNum, typename AppendVectorNS::AppendVector::Iterator begin, typename AppendVectorNS::AppendVector::Iterator end ); template void finalizeObjects( const std::string& name, const size_t& threadNum, const unsigned int& indexZoom, typename std::vector>::iterator begin, typename std::vector>::iterator end, typename std::vector>& lowZoom ) { size_t z6OffsetDivisor = indexZoom >= CLUSTER_ZOOM ? (1 << (indexZoom - CLUSTER_ZOOM)) : 1; #ifdef CLOCK_MONOTONIC timespec startTs, endTs; clock_gettime(CLOCK_MONOTONIC, &startTs); #endif int i = -1; for (auto it = begin; it != end; it++) { i++; if (it->size() > 0 || i % 50 == 0 || i == 4095) { std::cout << "\r" << name << ": finalizing z6 tile " << (i + 1) << "/" << CLUSTER_ZOOM_AREA; #ifdef CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &endTs); uint64_t elapsedNs = 1e9 * (endTs.tv_sec - startTs.tv_sec) + endTs.tv_nsec - startTs.tv_nsec; std::cout << " (" << std::to_string((uint32_t)(elapsedNs / 1e6)) << " ms)"; #endif std::cout << std::flush; } if (it->size() == 0) continue; // We track a separate copy of low zoom objects to avoid scanning large // lists of objects that may be on slow disk storage. for (auto objectIt = it->begin(); objectIt != it->end(); objectIt++) if (objectIt->oo.minZoom < CLUSTER_ZOOM) lowZoom[i].push_back(*objectIt); sortOutputObjects(indexZoom, threadNum, it->begin(), it->end()); } std::cout << std::endl; } template void collectTilesWithObjectsAtZoomTemplate( const unsigned int& indexZoom, const typename std::vector>::iterator objects, const size_t size, std::vector>& zooms ) { size_t maxZoom = zooms.size() - 1; uint16_t z6OffsetDivisor = indexZoom >= CLUSTER_ZOOM ? (1 << (indexZoom - CLUSTER_ZOOM)) : 1; int64_t lastX = -1; int64_t lastY = -1; for (size_t i = 0; i < size; i++) { const size_t z6x = i / CLUSTER_ZOOM_WIDTH; const size_t z6y = i % CLUSTER_ZOOM_WIDTH; for (size_t j = 0; j < objects[i].size(); j++) { // Compute the x, y at the base zoom level TileCoordinate baseX = z6x * z6OffsetDivisor + objects[i][j].x; TileCoordinate baseY = z6y * z6OffsetDivisor + objects[i][j].y; // Translate the x, y at the requested zoom level TileCoordinate x = baseX / (1 << (indexZoom - maxZoom)); TileCoordinate y = baseY / (1 << (indexZoom - maxZoom)); if (lastX != x || lastY != y) { lastX = x; lastY = y; for (int zoom = maxZoom; zoom >= 0; zoom--) { zooms[zoom]->set(x, y); x /= 2; y /= 2; } } } } } template OutputObjectID outputObjectWithId(const OO& input) { return OutputObjectID({ input.oo, 0 }); } template<> inline OutputObjectID outputObjectWithId(const OutputObjectXYID& input) { return OutputObjectID({ input.oo, input.id }); } template void collectLowZoomObjectsForTile( const unsigned int& indexZoom, typename std::vector> objects, unsigned int zoom, const TileCoordinates& dstIndex, std::vector& output ) { if (zoom >= CLUSTER_ZOOM) throw std::runtime_error("collectLowZoomObjectsForTile should not be called for high zooms"); uint16_t z6OffsetDivisor = indexZoom >= CLUSTER_ZOOM ? (1 << (indexZoom - CLUSTER_ZOOM)) : 1; for (size_t i = 0; i < objects.size(); i++) { const size_t z6x = i / CLUSTER_ZOOM_WIDTH; const size_t z6y = i % CLUSTER_ZOOM_WIDTH; for (size_t j = 0; j < objects[i].size(); j++) { // Compute the x, y at the base zoom level TileCoordinate baseX = z6x * z6OffsetDivisor + objects[i][j].x; TileCoordinate baseY = z6y * z6OffsetDivisor + objects[i][j].y; // Translate the x, y at the requested zoom level TileCoordinate x = baseX / (1 << (indexZoom - zoom)); TileCoordinate y = baseY / (1 << (indexZoom - zoom)); if (dstIndex.x == x && dstIndex.y == y) { if (objects[i][j].oo.minZoom <= zoom) { output.push_back(outputObjectWithId(objects[i][j])); } } } } } template void collectObjectsForTileTemplate( const unsigned int& indexZoom, typename std::vector>::iterator objects, size_t iStart, size_t iEnd, unsigned int zoom, TileCoordinates dstIndex, std::vector& output ) { if (zoom < CLUSTER_ZOOM) throw std::runtime_error("collectObjectsForTileTemplate should not be called for low zooms"); // When base zoom is z15 or higher, we need to scale down to z14. unsigned int clampedZoom = zoom; while(clampedZoom > indexZoom) { clampedZoom--; dstIndex.x /= 2; dstIndex.y /= 2; } uint16_t z6OffsetDivisor = indexZoom >= CLUSTER_ZOOM ? (1 << (indexZoom - CLUSTER_ZOOM)) : 1; for (size_t i = iStart; i < iEnd; i++) { // If z >= 6, we can compute the exact bounds within the objects array. // Translate to the base zoom, then do a binary search to find // the starting point. TileCoordinate z6x = dstIndex.x / (1 << (clampedZoom - CLUSTER_ZOOM)); TileCoordinate z6y = dstIndex.y / (1 << (clampedZoom - CLUSTER_ZOOM)); TileCoordinate baseX = dstIndex.x * (1 << (indexZoom - clampedZoom)); TileCoordinate baseY = dstIndex.y * (1 << (indexZoom - clampedZoom)); Z6Offset needleX = baseX - z6x * z6OffsetDivisor; Z6Offset needleY = baseY - z6y * z6OffsetDivisor; // Kind of gross that we have to do this. Might be better if we split // into two arrays, one of x/y and one of OOs. Would have better locality for // searching, too. OutputObject dummyOo(POINT_, 0, 0, 0, 0); const size_t bz = indexZoom; const OO targetXY = {dummyOo, needleX, needleY }; auto iter = std::lower_bound( objects[i].begin(), objects[i].end(), targetXY, [bz](const OO& a, const OO& b) { // Cluster by parent zoom, so that a subsequent search // can find a contiguous range of entries for any tile // at zoom 6 or higher. const size_t aX = a.x; const size_t aY = a.y; const size_t bX = b.x; const size_t bY = b.y; for (size_t z = CLUSTER_ZOOM; z <= bz; z++) { const auto aXz = aX / (1 << (bz - z)); const auto aYz = aY / (1 << (bz - z)); const auto bXz = bX / (1 << (bz - z)); const auto bYz = bY / (1 << (bz - z)); if (aXz != bXz) return aXz < bXz; if (aYz != bYz) return aYz < bYz; } return false; } ); for (; iter != objects[i].end(); iter++) { // Compute the x, y at the base zoom level TileCoordinate baseX = z6x * z6OffsetDivisor + iter->x; TileCoordinate baseY = z6y * z6OffsetDivisor + iter->y; // Translate the x, y at the requested zoom level TileCoordinate x = baseX / (1 << (indexZoom - clampedZoom)); TileCoordinate y = baseY / (1 << (indexZoom - clampedZoom)); if (dstIndex.x == x && dstIndex.y == y) { if (iter->oo.minZoom <= zoom) { output.push_back(outputObjectWithId(*iter)); } } else { // Short-circuit when we're confident we'd no longer see relevant matches. // We've ordered the entries in `objects` such that all objects that // share the same tile at any zoom are in contiguous runs. // // Thus, as soon as we fail to find a match, we can stop looking. break; } } } } class TileDataSource { public: // Store for generated geometries using point_store_t = std::vector; using linestring_t = boost::geometry::model::linestring; using linestring_store_t = std::vector; using multi_linestring_t = boost::geometry::model::multi_linestring; using multi_linestring_store_t = std::vector; using polygon_t = boost::geometry::model::polygon; using multi_polygon_t = boost::geometry::model::multi_polygon; using multi_polygon_store_t = std::vector; std::mutex storeMutex; // Threads can grab one of the stores and work on them in a thread local. std::vector> availablePointStoreLeases; std::vector> availableLinestringStoreLeases; std::vector> availableMultiLinestringStoreLeases; std::vector> availableMultiPolygonStoreLeases; virtual std::string name() const = 0; protected: size_t numShards; uint8_t shardBits; std::mutex mutex; bool includeID; uint16_t z6OffsetDivisor; // Guards objects, objectsWithIds. std::vector objectsMutex; // The top-level vector has 1 entry per z6 tile, indexed by x*64 + y // The inner vector contains the output objects that are contained in that z6 tile // // In general, only one of these vectors will be populated. // // If config.include_ids is true, objectsWithIds will be populated. // Otherwise, objects. std::vector> objects; std::vector> lowZoomObjects; std::vector> objectsWithIds; std::vector> lowZoomObjectsWithIds; // rtree index of large objects using oo_rtree_param_type = boost::geometry::index::quadratic<128>; boost::geometry::index::rtree< std::pair, oo_rtree_param_type> boxRtree; boost::geometry::index::rtree< std::pair, oo_rtree_param_type> boxRtreeWithIds; unsigned int indexZoom; std::vector pointStores; std::vector linestringStores; std::vector multilinestringStores; std::vector multipolygonStores; ClipCache multiPolygonClipCache; ClipCache multiLinestringClipCache; std::deque>> pendingSmallIndexObjects; public: TileDataSource(size_t threadNum, unsigned int indexZoom, bool includeID); void collectTilesWithObjectsAtZoom(std::vector>& zooms); void collectTilesWithLargeObjectsAtZoom(std::vector>& zooms); void collectObjectsForTile(uint zoom, TileCoordinates dstIndex, std::vector& output); void finalize(size_t threadNum); void addGeometryToIndex( const Linestring& geom, const std::vector& outputs, const uint64_t id ); void addGeometryToIndex( const MultiLinestring& geom, const std::vector& outputs, const uint64_t id ); void addGeometryToIndex( const MultiPolygon& geom, std::vector& outputs, // so we can mutate objectID to skip clip cache const uint64_t id ); void addObjectToSmallIndex(const TileCoordinates& index, const OutputObject& oo, uint64_t id); void addObjectToSmallIndex(const TileCoordinates& index, const OutputObject& oo, uint64_t id, bool needsLock); void addObjectToSmallIndexUnsafe(const TileCoordinates& index, const OutputObject& oo, uint64_t id); void addObjectToLargeIndex(const Box& envelope, const OutputObject& oo, uint64_t id) { std::lock_guard lock(mutex); if (id == 0 || !includeID) boxRtree.insert(std::make_pair(envelope, oo)); else boxRtreeWithIds.insert(std::make_pair(envelope, OutputObjectID({oo, id}))); } void collectLargeObjectsForTile(uint zoom, TileCoordinates dstIndex, std::vector& output); std::vector getObjectsForTile( const std::vector& sortOrders, unsigned int zoom, TileCoordinates coordinates ); virtual Geometry buildWayGeometry(OutputGeometryType const geomType, NodeID const objectID, const TileBbox &bbox); virtual LatpLon buildNodeGeometry(NodeID const objectID, const TileBbox &bbox) const; void open() { // Put something at index 0 of all stores so that 0 can be used // as a sentinel. pointStores[0].push_back(Point(0,0)); linestringStores[0].push_back(linestring_t()); multipolygonStores[0].push_back(multi_polygon_t()); multilinestringStores[0].push_back(multi_linestring_t()); } void reportSize() const; // Accessors for generated geometries using handle_t = void *; NodeID storePoint(Point const &input); inline size_t getShard(NodeID id) const { // Note: we only allocate 34 bits for the IDs. This allows us to // use bits 35 and 36 for TileDataSource-specific handling (e.g., // OsmMemTiles may want to generate points/ways on the fly by // referring to the WayStore). return id >> (TILE_DATA_ID_SIZE - shardBits); } virtual void populateMultiPolygon(MultiPolygon& dst, NodeID objectID); unsigned int getIndexZoom() const { return indexZoom; } inline size_t getId(NodeID id) const { return id & (~(~0ull << (TILE_DATA_ID_SIZE - shardBits))); } const Point& retrievePoint(NodeID id) const { const auto& shardId = getShard(id); const auto& shard = pointStores[shardId]; const auto offset = getId(id); if (offset > shard.size()) throw std::out_of_range("Could not find generated node with id " + std::to_string(id) + ", shard " + std::to_string(shardId) + ", offset=" + std::to_string(offset)); return shard.at(offset); } NodeID storeLinestring(const Linestring& src); const linestring_t& retrieveLinestring(NodeID id) const { const auto& shardId = getShard(id); const auto& shard = linestringStores[shardId]; const auto offset = getId(id); if (offset > shard.size()) throw std::out_of_range("Could not find generated linestring with id " + std::to_string(id) + ", shard " + std::to_string(shardId) + ", offset=" + std::to_string(offset)); return shard.at(offset); } NodeID storeMultiLinestring(const MultiLinestring& src); multi_linestring_t const &retrieveMultiLinestring(NodeID id) const { const auto& shardId = getShard(id); const auto& shard = multilinestringStores[shardId]; const auto offset = getId(id); if (offset > shard.size()) throw std::out_of_range("Could not find generated multi-linestring with id " + std::to_string(id) + ", shard " + std::to_string(shardId) + ", offset=" + std::to_string(offset)); return shard.at(offset); } NodeID storeMultiPolygon(const MultiPolygon& src); multi_polygon_t const &retrieveMultiPolygon(NodeID id) const { const auto& shardId = getShard(id); const auto& shard = multipolygonStores[shardId]; const auto offset = getId(id); if (offset > shard.size()) throw std::out_of_range("Could not find generated multi-polygon with id " + std::to_string(id) + ", shard " + std::to_string(shardId) + ", offset=" + std::to_string(offset)); return shard.at(offset); } }; void populateTilesAtZoom( const std::vector& sources, std::vector>& zooms ); #endif //_TILE_DATA_H