void OsmAnd::ObfMapSectionReader_P::readTreeNodeChildren(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& treeNode,
    MapFoundationType& foundation,
    QList< std::shared_ptr<ObfMapSectionLevelTreeNode> >* nodesWithData,
    const AreaI* bbox31,
    IQueryController* controller)
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndMapIndex_MapDataBox::kBoxesFieldNumber:
            {
                auto length = ObfReaderUtilities::readBigEndianInt(cis);
                auto offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(length);
                std::shared_ptr<ObfMapSectionLevelTreeNode> childNode(new ObfMapSectionLevelTreeNode());
                childNode->_foundation = treeNode->_foundation;
                childNode->_offset = offset;
                childNode->_length = length;
                readTreeNode(reader, section, treeNode->_area31, childNode);
                if(bbox31 && !bbox31->intersects(childNode->_area31))
                {
                    cis->Skip(cis->BytesUntilLimit());
                    cis->PopLimit(oldLimit);
                    break;
                }
                cis->PopLimit(oldLimit);

                if(childNode->_foundation != MapFoundationType::Undefined)
                {
                    if(foundation == MapFoundationType::Undefined)
                        foundation = childNode->_foundation;
                    else if(foundation != childNode->_foundation)
                        foundation = MapFoundationType::Mixed;
                }
                
                if(nodesWithData && childNode->_dataOffset > 0)
                    nodesWithData->push_back(childNode);

                cis->Seek(offset);
                oldLimit = cis->PushLimit(length);
                cis->Skip(childNode->_childrenInnerOffset);
                readTreeNodeChildren(reader, section, childNode, foundation, nodesWithData, bbox31, controller);
                assert(cis->BytesUntilLimit() == 0);
                cis->PopLimit(oldLimit);
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfPoiSectionReader_P::readCategories(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfPoiSectionInfo>& section,
    QList< std::shared_ptr<const Model::AmenityCategory> >& categories )
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndPoiIndex::kCategoriesTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                std::shared_ptr<Model::AmenityCategory> category(new Model::AmenityCategory());
                readCategory(reader, category);
                cis->PopLimit(oldLimit);
                categories.push_back(category);
            }
            break;
        case OBF::OsmAndPoiIndex::kNameIndexFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfPoiSectionReader_P::read( const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<ObfPoiSectionInfo>& section )
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndPoiIndex::kNameFieldNumber:
            ObfReaderUtilities::readQString(cis, section->_name);
            break;
        case OBF::OsmAndPoiIndex::kBoundariesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                readBoundaries(reader, section);
                cis->PopLimit(oldLimit);
            }
            break; 
        case OBF::OsmAndPoiIndex::kCategoriesTableFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfPoiSectionReader_P::readAmenities(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfPoiSectionInfo>& section,
    QSet<uint32_t>* desiredCategories,
    QList< std::shared_ptr<const Model::Amenity> >* amenitiesOut,
    const ZoomLevel& zoom, uint32_t zoomDepth, const AreaI* bbox31,
    std::function<bool (const std::shared_ptr<const Model::Amenity>&)> visitor,
    IQueryController* controller)
{
    auto cis = reader->_codedInputStream.get();
    QList< std::shared_ptr<Tile> > tiles;
    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndPoiIndex::kBoxesFieldNumber:
            {
                auto length = ObfReaderUtilities::readBigEndianInt(cis);
                auto oldLimit = cis->PushLimit(length);
                readTile(reader, section, tiles, nullptr, desiredCategories, zoom, zoomDepth, bbox31, controller, nullptr);
                cis->PopLimit(oldLimit);
                if(controller && controller->isAborted())
                    return;
            }
            break;
        case OBF::OsmAndPoiIndex::kPoiDataFieldNumber:
            {
                // Sort tiles byte data offset, to all cache-friendly with I/O system
                qSort(tiles.begin(), tiles.end(), [](const std::shared_ptr<Tile>& l, const std::shared_ptr<Tile>& r) -> bool
                {
                    return l->_hash < r->_hash;
                });

                for(auto itTile = tiles.begin(); itTile != tiles.end(); ++itTile)
                {
                    const auto& tile = *itTile;

                    cis->Seek(section->_offset + tile->_offset);
                    auto length = ObfReaderUtilities::readBigEndianInt(cis);
                    auto oldLimit = cis->PushLimit(length);
                    readAmenitiesFromTile(reader, section, tile.get(), desiredCategories, amenitiesOut, zoom, zoomDepth, bbox31, visitor, controller, nullptr);
                    cis->PopLimit(oldLimit);
                    if(controller && controller->isAborted())
                        return;
                }
                cis->Skip(cis->BytesUntilLimit());
            }
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfRoutingSectionReader_P::readBorderLinePoint(
    const std::unique_ptr<ObfReader_P>& reader,
    const std::shared_ptr<ObfRoutingBorderLinePoint>& point)
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndRoutingIndex_RouteBorderPoint::kDxFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&point->_location.x));
            break;
        case OBF::OsmAndRoutingIndex_RouteBorderPoint::kDyFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&point->_location.y));
            break;
        case OBF::OsmAndRoutingIndex_RouteBorderPoint::kRoadIdFieldNumber:
            cis->ReadVarint64(&point->_id);
            break;
        case OBF::OsmAndRoutingIndex_RouteBorderPoint::kDirectionFieldNumber:
            {
                gpb::uint32 value;
                cis->ReadVarint32(&value);
                //TODO:p.direction = codedIS.readBool();
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteBorderPoint::kTypesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                while(cis->BytesUntilLimit() > 0)
                {
                    gpb::uint32 value;
                    cis->ReadVarint32(&value);
                    point->_types.push_back(value);
                }
                cis->PopLimit(oldLimit);
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapLevelHeader(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<ObfMapSectionLevel>& level )
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        const auto tagPos = cis->CurrentPosition();
        const auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndMapIndex_MapRootLevel::kMaxZoomFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_maxZoom));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kMinZoomFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_minZoom));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kLeftFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_area31.left));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kRightFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_area31.right));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kTopFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_area31.top));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kBottomFieldNumber:
            cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->_area31.bottom));
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kBoxesFieldNumber:
            {
                // Save boxes offset
                level->_boxesInnerOffset = tagPos - level->_offset;

                // Skip reading boxes and surely, following blocks
                cis->Skip(cis->BytesUntilLimit());
            }
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfRoutingSectionReader_P::readSubsectionChildrenHeaders(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<ObfRoutingSubsectionInfo>& subsection,
    uint32_t depth /*= std::numeric_limits<uint32_t>::max()*/ )
{
    if(!subsection->_subsectionsOffset)
        return;

    const auto shouldReadSubsections = (depth > 0 || subsection->_dataOffset != 0);
    if(!shouldReadSubsections)
        return;

    auto cis = reader->_codedInputStream.get();
    for(;;)
    {
        auto lastPos = cis->CurrentPosition();
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kBoxesFieldNumber:
            {
                if(!shouldReadSubsections)
                {
                    cis->Skip(cis->BytesUntilLimit());
                    break;
                }
                const std::shared_ptr<ObfRoutingSubsectionInfo> childSubsection(new ObfRoutingSubsectionInfo(subsection));
                childSubsection->_length = ObfReaderUtilities::readBigEndianInt(cis);
                childSubsection->_offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(childSubsection->_length);
                readSubsectionHeader(reader, childSubsection, subsection, depth - 1);
                
                cis->PopLimit(oldLimit);

                subsection->_subsections.push_back(qMove(childSubsection));
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfPoiSection::readCategories( ObfReader* reader, ObfPoiSection* section, QList< std::shared_ptr<OsmAnd::Model::Amenity::Category> >& categories )
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndPoiIndex::kNameFieldNumber:
            ObfReader::readQString(cis, section->_name);
            break;
        case OBF::OsmAndPoiIndex::kBoundariesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                readBoundaries(reader, section);
                cis->PopLimit(oldLimit);
            }
            break; 
        case OBF::OsmAndPoiIndex::kCategoriesTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                std::shared_ptr<Model::Amenity::Category> category(new Model::Amenity::Category());
                readCategory(reader, category.get());
                cis->PopLimit(oldLimit);
                categories.push_back(category);
            }
            break;
        case OBF::OsmAndPoiIndex::kNameIndexFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            return;
        default:
            ObfReader::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfPoiSectionReader_P::readCategories(
    const ObfReader_P& reader, const std::shared_ptr<const ObfPoiSectionInfo>& section,
    QList< std::shared_ptr<const AmenityCategory> >& categories )
{
    const auto cis = reader.getCodedInputStream().get();

    for(;;)
    {
        const auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            if (!ObfReaderUtilities::reachedDataEnd(cis))
                return;

            return;
        case OBF::OsmAndPoiIndex::kCategoriesTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                const auto offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(length);
                
                std::shared_ptr<AmenityCategory> category(new AmenityCategory());
                readCategory(reader, category);
                
                ObfReaderUtilities::ensureAllDataWasRead(cis);
                cis->PopLimit(oldLimit);

                categories.push_back(qMove(category));
            }
            break;
        case OBF::OsmAndPoiIndex::kNameIndexFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapLevelTreeNodes(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<const ObfMapSectionLevel>& level, QList< std::shared_ptr<ObfMapSectionLevelTreeNode> >& trees )
{
    auto cis = reader->_codedInputStream.get();
    for(;;)
    {
        gpb::uint32 tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndMapIndex_MapRootLevel::kBoxesFieldNumber:
            {
                const auto length = ObfReaderUtilities::readBigEndianInt(cis);
                const auto offset = cis->CurrentPosition();
                const auto oldLimit = cis->PushLimit(length);
                
                std::shared_ptr<ObfMapSectionLevelTreeNode> levelTree(new ObfMapSectionLevelTreeNode(level));
                levelTree->_offset = offset;
                levelTree->_length = length;
                readTreeNode(reader, section, level->area31, levelTree);
                
                cis->PopLimit(oldLimit);

                trees.push_back(qMove(levelTree));
            }
            break;
        case OBF::OsmAndMapIndex_MapRootLevel::kBlocksFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfRoutingSectionReader_P::readRoad(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfRoutingSubsectionInfo>& subsection,
    const QList<uint64_t>& idsTable, uint32_t& internalId, const std::shared_ptr<Model::Road>& road )
{
    auto cis = reader->_codedInputStream.get();
    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::RouteData::kPointsFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                auto dx = subsection->_area31.left >> ShiftCoordinates;
                auto dy = subsection->_area31.top >> ShiftCoordinates;
                while(cis->BytesUntilLimit() > 0)
                {
                    const uint32_t x = ObfReaderUtilities::readSInt32(cis) + dx;
                    const uint32_t y = ObfReaderUtilities::readSInt32(cis) + dy;

                    road->_points.push_back(qMove(PointI(
                        x << ShiftCoordinates,
                        y << ShiftCoordinates
                        )));

                    dx = x;
                    dy = y;
                }
                cis->PopLimit(oldLimit);
            }
            break;
        case OBF::RouteData::kPointTypesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                while(cis->BytesUntilLimit() > 0)
                {
                    gpb::uint32 pointIdx;
                    cis->ReadVarint32(&pointIdx);

                    gpb::uint32 innerLength;
                    cis->ReadVarint32(&innerLength);
                    auto innerOldLimit = cis->PushLimit(innerLength);

                    auto& pointTypes = road->_pointsTypes.insert(pointIdx, QVector<uint32_t>()).value();
                    while(cis->BytesUntilLimit() > 0)
                    {
                        gpb::uint32 pointType;
                        cis->ReadVarint32(&pointType);
                        pointTypes.push_back(pointType);
                    }
                    cis->PopLimit(innerOldLimit);
                }
                cis->PopLimit(oldLimit);
            }
            break;
        case OBF::RouteData::kTypesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                while(cis->BytesUntilLimit() > 0)
                {
                    gpb::uint32 type;
                    cis->ReadVarint32(&type);
                    road->_types.push_back(type);
                }
                cis->PopLimit(oldLimit);
            }
            break;
        case OBF::RouteData::kRouteIdFieldNumber:
            {
                gpb::uint32 id;
                cis->ReadVarint32(&id);
                internalId = id;
                if(id < idsTable.size())
                    road->_id = idsTable[id];
                else
                    road->_id = id;
            }
            break;
        case OBF::RouteData::kStringNamesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                while(cis->BytesUntilLimit() > 0)
                {
                    gpb::uint32 stringTag;
                    cis->ReadVarint32(&stringTag);
                    gpb::uint32 stringId;
                    cis->ReadVarint32(&stringId);

                    road->_names.insert(stringTag, ObfReaderUtilities::encodeIntegerToString(stringId));
                }
                cis->PopLimit(oldLimit);
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
bool OsmAnd::ObfPoiSectionReader_P::readTile(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfPoiSectionInfo>& section,
    QList< std::shared_ptr<Tile> >& tiles,
    Tile* parent,
    QSet<uint32_t>* desiredCategories,
    uint32_t zoom, uint32_t zoomDepth, const AreaI* bbox31,
    IQueryController* controller,
    QSet< uint64_t >* tilesToSkip)
{
    auto cis = reader->_codedInputStream.get();

    const auto zoomToSkip = zoom + zoomDepth;
    QSet< uint64_t > tilesToSkip_;
    if(parent == nullptr && !tilesToSkip)
        tilesToSkip = &tilesToSkip_;

    std::shared_ptr<Tile> tile(new Tile());
    gpb::uint32 lzoom;

    for(;;)
    {
        if(controller && controller->isAborted())
            return false;
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            tiles.push_back(tile);
            return true;
        case OBF::OsmAndPoiBox::kZoomFieldNumber:
            {
                cis->ReadVarint32(&lzoom);
                tile->_zoom = lzoom;
                if(parent)
                    tile->_zoom += parent->_zoom;
            }
            break;
        case OBF::OsmAndPoiBox::kLeftFieldNumber:
            {
                auto x = ObfReaderUtilities::readSInt32(cis);
                if(parent)
                    tile->_x = x + (parent->_x << lzoom);
                else
                    tile->_x = x;
            }
            break;
        case OBF::OsmAndPoiBox::kTopFieldNumber:
            {
                auto y = ObfReaderUtilities::readSInt32(cis);
                if(parent)
                    tile->_y = y + (parent->_y << lzoom);
                else
                    tile->_y = y;

                // Check that we're inside bounding box, if requested
                if(bbox31)
                {
                    AreaI area31;
                    area31.left = tile->_x << (31 - tile->_zoom);
                    area31.right = (tile->_x + 1) << (31 - tile->_zoom);
                    area31.top = tile->_y << (31 - tile->_zoom);
                    area31.bottom = (tile->_y + 1) << (31 - tile->_zoom);

                    if(!bbox31->intersects(area31))
                    {
                        // This tile is outside of bounding box
                        cis->Skip(cis->BytesUntilLimit());
                        return false;
                    }
                }
            }
            break;
        case OBF::OsmAndPoiBox::kCategoriesFieldNumber:
            {
                if(!desiredCategories)
                {
                    ObfReaderUtilities::skipUnknownField(cis, tag);
                    break;
                }
                gpb::uint32 length;
                cis->ReadLittleEndian32(&length);
                auto oldLimit = cis->PushLimit(length);
                const auto containsDesired = checkTileCategories(reader, section, desiredCategories);
                cis->PopLimit(oldLimit);
                if(!containsDesired)
                {
                    cis->Skip(cis->BytesUntilLimit());
                    return false;
                }
            }
            break;
        case OBF::OsmAndPoiBox::kSubBoxesFieldNumber:
            {
                auto length = ObfReaderUtilities::readBigEndianInt(cis);
                auto oldLimit = cis->PushLimit(length);
                auto tileOmitted = readTile(reader, section, tiles, tile.get(), desiredCategories, zoom, zoomDepth, bbox31, controller, tilesToSkip);
                cis->PopLimit(oldLimit);

                if(tilesToSkip && tile->_zoom >= zoomToSkip && tileOmitted)
                {
                    auto skipHash = (static_cast<uint64_t>(tile->_x) >> (tile->_zoom - zoomToSkip)) << zoomToSkip;
                    skipHash |= static_cast<uint64_t>(tile->_y) >> (tile->_zoom - zoomToSkip);
                    if(tilesToSkip->contains(skipHash))
                    {
                        cis->Skip(cis->BytesUntilLimit());
                        return true;
                    }
                }
            }
            break;
        case OBF::OsmAndPoiBox::kShiftToDataFieldNumber:
            {
                tile->_offset = ObfReaderUtilities::readBigEndianInt(cis);
                tile->_hash  = static_cast<uint64_t>(tile->_x) << tile->_zoom;
                tile->_hash |= static_cast<uint64_t>(tile->_y);
                tile->_hash |= tile->_zoom;

                // skipTiles - these tiles are going to be ignored, since we need only 1 POI object (x;y)@zoom
                if(tilesToSkip && tile->_zoom >= zoomToSkip)
                {
                    auto skipHash = (static_cast<uint64_t>(tile->_x) >> (tile->_zoom - zoomToSkip)) << zoomToSkip;
                    skipHash |= static_cast<uint64_t>(tile->_y) >> (tile->_zoom - zoomToSkip);
                    tilesToSkip->insert(skipHash);
                }
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
void OsmAnd::ObfMapSectionReader_P::readTreeNode(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const AreaI& parentArea,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& treeNode )
{
    auto cis = reader->_codedInputStream.get();

    for(;;)
    {
        const auto tagPos = cis->CurrentPosition();
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndMapIndex_MapDataBox::kLeftFieldNumber:
            {
                auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->_area31.left = d + parentArea.left;
            }
            break;
        case OBF::OsmAndMapIndex_MapDataBox::kRightFieldNumber:
            {
                auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->_area31.right = d + parentArea.right;
            }
            break;   
        case OBF::OsmAndMapIndex_MapDataBox::kTopFieldNumber:
            {
                auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->_area31.top = d + parentArea.top;
            }
            break;
        case OBF::OsmAndMapIndex_MapDataBox::kBottomFieldNumber:
            {
                auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->_area31.bottom = d + parentArea.bottom;
            }
            break;
        case OBF::OsmAndMapIndex_MapDataBox::kShiftToMapDataFieldNumber:
            treeNode->_dataOffset = ObfReaderUtilities::readBigEndianInt(cis) + treeNode->_offset;
            break;
        case OBF::OsmAndMapIndex_MapDataBox::kOceanFieldNumber:
            {
                gpb::uint32 value;
                cis->ReadVarint32(&value);

                treeNode->_foundation = (value != 0) ? MapFoundationType::FullWater : MapFoundationType::FullLand;
            }
            break;
        case OBF::OsmAndMapIndex_MapDataBox::kBoxesFieldNumber:
            {
                // Save children relative offset and skip their data
                treeNode->_childrenInnerOffset = tagPos - treeNode->_offset;
                cis->Skip(cis->BytesUntilLimit());
            }
            return;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readTreeNodeChildren(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& treeNode,
    MapFoundationType& foundation,
    QList< std::shared_ptr<ObfMapSectionLevelTreeNode> >* nodesWithData,
    const AreaI* bbox31,
    const IQueryController* const controller,
    ObfMapSectionReader_Metrics::Metric_loadMapObjects* const metric)
{
    auto cis = reader->_codedInputStream.get();

    foundation = MapFoundationType::Undefined;

    for(;;)
    {
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndMapIndex_MapDataBox::kBoxesFieldNumber:
            {
                auto length = ObfReaderUtilities::readBigEndianInt(cis);
                auto offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(length);

                std::shared_ptr<ObfMapSectionLevelTreeNode> childNode(new ObfMapSectionLevelTreeNode(treeNode->level));
                childNode->_foundation = treeNode->_foundation;
                childNode->_offset = offset;
                childNode->_length = length;
                readTreeNode(reader, section, treeNode->_area31, childNode);

                // Update metric
                if(metric)
                    metric->visitedNodes++;

                if(bbox31)
                {
                    const auto shouldSkip =
                        !bbox31->contains(childNode->_area31) &&
                        !childNode->_area31.contains(*bbox31) &&
                        !bbox31->intersects(childNode->_area31);
                    if(shouldSkip)
                    {
                        cis->Skip(cis->BytesUntilLimit());
                        cis->PopLimit(oldLimit);
                        break;
                    }
                }
                cis->PopLimit(oldLimit);

                // Update metric
                if(metric)
                    metric->acceptedNodes++;

                if(nodesWithData && childNode->_dataOffset > 0)
                    nodesWithData->push_back(childNode);

                auto childrenFoundation = MapFoundationType::Undefined;
                if(childNode->_childrenInnerOffset > 0)
                {
                    cis->Seek(offset);
                    oldLimit = cis->PushLimit(length);

                    cis->Skip(childNode->_childrenInnerOffset);
                    readTreeNodeChildren(reader, section, childNode, childrenFoundation, nodesWithData, bbox31, controller, metric);
                    assert(cis->BytesUntilLimit() == 0);

                    cis->PopLimit(oldLimit);
                }

                const auto foundationToMerge = (childrenFoundation != MapFoundationType::Undefined) ? childrenFoundation : childNode->_foundation;
                if(foundationToMerge != MapFoundationType::Undefined)
                {
                    if(foundation == MapFoundationType::Undefined)
                        foundation = foundationToMerge;
                    else if(foundation != foundationToMerge)
                        foundation = MapFoundationType::Mixed;
                }
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapObject(
    const ObfReader_P& reader,
    const std::shared_ptr<const ObfMapSectionInfo>& section,
    uint64_t baseId,
    const std::shared_ptr<const ObfMapSectionLevelTreeNode>& treeNode,
    std::shared_ptr<OsmAnd::BinaryMapObject>& mapObject,
    const AreaI* bbox31,
    ObfMapSectionReader_Metrics::Metric_loadMapObjects* const metric)
{
    const auto cis = reader.getCodedInputStream().get();
    const auto baseOffset = cis->CurrentPosition();

    for (;;)
    {
        const auto tag = cis->ReadTag();
        const auto tgn = gpb::internal::WireFormatLite::GetTagFieldNumber(tag);
        switch (tgn)
        {
            case 0:
            {
                if (!ObfReaderUtilities::reachedDataEnd(cis))
                    return;

                if (mapObject && mapObject->points31.isEmpty())
                {
                    LogPrintf(LogSeverityLevel::Warning,
                        "Empty BinaryMapObject %s detected in section '%s'",
                        qPrintable(mapObject->id.toString()),
                        qPrintable(section->name));
                    mapObject.reset();
                }

                return;
            }
            case OBF::MapData::kAreaCoordinatesFieldNumber:
            case OBF::MapData::kCoordinatesFieldNumber:
            {
                const Stopwatch mapObjectPointsStopwatch(metric != nullptr);

                gpb::uint32 length;
                cis->ReadVarint32(&length);
                const auto oldLimit = cis->PushLimit(length);

                PointI p;
                p.x = treeNode->area31.left() & MaskToRead;
                p.y = treeNode->area31.top() & MaskToRead;

                AreaI objectBBox;
                objectBBox.top() = objectBBox.left() = std::numeric_limits<int32_t>::max();
                objectBBox.bottom() = objectBBox.right() = 0;
                auto lastUnprocessedVertexForBBox = 0;

                // In protobuf, a sint32 can be encoded using [1..4] bytes,
                // so try to guess size of array, and preallocate it.
                // (BytesUntilLimit/2) is ~= number of vertices, and is always larger than needed.
                // So it's impossible that a buffer overflow will ever happen. But assert on that.
                const auto probableVerticesCount = (cis->BytesUntilLimit() / 2);
                QVector< PointI > points31(probableVerticesCount);

                auto pPoint = points31.data();
                auto verticesCount = 0;
                bool shouldNotSkip = (bbox31 == nullptr);
                while (cis->BytesUntilLimit() > 0)
                {
                    PointI d;
                    d.x = (ObfReaderUtilities::readSInt32(cis) << ShiftCoordinates);
                    d.y = (ObfReaderUtilities::readSInt32(cis) << ShiftCoordinates);

                    p += d;

                    // Save point into storage
                    assert(points31.size() > verticesCount);
                    *(pPoint++) = p;
                    verticesCount++;

                    // Check if map object should be maintained
                    if (!shouldNotSkip && bbox31)
                    {
                        const Stopwatch mapObjectBboxStopwatch(metric != nullptr);

                        shouldNotSkip = bbox31->contains(p);
                        objectBBox.enlargeToInclude(p);

                        if (metric)
                            metric->elapsedTimeForMapObjectsBbox += mapObjectBboxStopwatch.elapsed();

                        lastUnprocessedVertexForBBox = verticesCount;
                    }
                }

                cis->PopLimit(oldLimit);

                // Since reserved space may be larger than actual amount of data,
                // shrink the vertices array
                points31.resize(verticesCount);

                // If map object has no vertices, retain it in a special way to report later, when
                // it's identifier will be known
                if (points31.isEmpty())
                {
                    // Fake that this object is inside bbox
                    shouldNotSkip = true;
                    objectBBox = treeNode->area31;
                }

                // Even if no vertex lays inside bbox, an edge
                // may intersect the bbox
                if (!shouldNotSkip && bbox31)
                {
                    assert(lastUnprocessedVertexForBBox == points31.size());

                    shouldNotSkip =
                        objectBBox.contains(*bbox31) ||
                        bbox31->intersects(objectBBox);
                }

                // If map object didn't fit, skip it's entire content
                if (!shouldNotSkip)
                {
                    if (metric)
                    {
                        metric->elapsedTimeForSkippedMapObjectsPoints += mapObjectPointsStopwatch.elapsed();
                        metric->skippedMapObjectsPoints += points31.size();
                    }

                    cis->Skip(cis->BytesUntilLimit());
                    break;
                }

                // Update metric
                if (metric)
                {
                    metric->elapsedTimeForNotSkippedMapObjectsPoints += mapObjectPointsStopwatch.elapsed();
                    metric->notSkippedMapObjectsPoints += points31.size();
                }

                // In case bbox is not fully calculated, complete this task
                auto pPointForBBox = points31.data() + lastUnprocessedVertexForBBox;
                while (lastUnprocessedVertexForBBox < points31.size())
                {
                    const Stopwatch mapObjectBboxStopwatch(metric != nullptr);

                    objectBBox.enlargeToInclude(*pPointForBBox);

                    if (metric)
                        metric->elapsedTimeForMapObjectsBbox += mapObjectBboxStopwatch.elapsed();

                    lastUnprocessedVertexForBBox++;
                    pPointForBBox++;
                }

                // Finally, create the object
                if (!mapObject)
                    mapObject.reset(new OsmAnd::BinaryMapObject(section, treeNode->level));
                mapObject->isArea = (tgn == OBF::MapData::kAreaCoordinatesFieldNumber);
                mapObject->points31 = qMove(points31);
                mapObject->bbox31 = objectBBox;
                assert(treeNode->area31.top() - mapObject->bbox31.top() <= 32);
                assert(treeNode->area31.left() - mapObject->bbox31.left() <= 32);
                assert(mapObject->bbox31.bottom() - treeNode->area31.bottom() <= 1);
                assert(mapObject->bbox31.right() - treeNode->area31.right() <= 1);
                assert(mapObject->bbox31.right() >= mapObject->bbox31.left());
                assert(mapObject->bbox31.bottom() >= mapObject->bbox31.top());

                break;
            }
            case OBF::MapData::kPolygonInnerCoordinatesFieldNumber:
            {
                if (!mapObject)
                    mapObject.reset(new OsmAnd::BinaryMapObject(section, treeNode->level));

                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);

                PointI p;
                p.x = treeNode->area31.left() & MaskToRead;
                p.y = treeNode->area31.top() & MaskToRead;

                // Preallocate memory
                const auto probableVerticesCount = (cis->BytesUntilLimit() / 2);
                mapObject->innerPolygonsPoints31.push_back(qMove(QVector< PointI >(probableVerticesCount)));
                auto& polygon = mapObject->innerPolygonsPoints31.last();

                auto pPoint = polygon.data();
                auto verticesCount = 0;
                while (cis->BytesUntilLimit() > 0)
                {
                    PointI d;
                    d.x = (ObfReaderUtilities::readSInt32(cis) << ShiftCoordinates);
                    d.y = (ObfReaderUtilities::readSInt32(cis) << ShiftCoordinates);

                    p += d;

                    // Save point into storage
                    assert(polygon.size() > verticesCount);
                    *(pPoint++) = p;
                    verticesCount++;
                }

                // Shrink memory
                polygon.resize(verticesCount);

                cis->PopLimit(oldLimit);

                break;
            }
            case OBF::MapData::kAdditionalTypesFieldNumber:
            case OBF::MapData::kTypesFieldNumber:
            {
                if (!mapObject)
                    mapObject.reset(new OsmAnd::BinaryMapObject(section, treeNode->level));

                auto& attributeIds = (tgn == OBF::MapData::kAdditionalTypesFieldNumber)
                    ? mapObject->additionalAttributeIds
                    : mapObject->attributeIds;

                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);

                // Preallocate space
                attributeIds.reserve(cis->BytesUntilLimit());

                while (cis->BytesUntilLimit() > 0)
                {
                    gpb::uint32 attributeId;
                    cis->ReadVarint32(&attributeId);

                    attributeIds.push_back(attributeId);
                }

                // Shrink preallocated space
                attributeIds.squeeze();

                cis->PopLimit(oldLimit);

                break;
            }
            case OBF::MapData::kStringNamesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);

                while (cis->BytesUntilLimit() > 0)
                {
                    bool ok;

                    gpb::uint32 stringRuleId;
                    ok = cis->ReadVarint32(&stringRuleId);
                    assert(ok);
                    gpb::uint32 stringId;
                    ok = cis->ReadVarint32(&stringId);
                    assert(ok);

                    mapObject->captions.insert(stringRuleId, qMove(ObfReaderUtilities::encodeIntegerToString(stringId)));
                    mapObject->captionsOrder.push_back(stringRuleId);
                }

                cis->PopLimit(oldLimit);

                break;
            }
            case OBF::MapData::kIdFieldNumber:
            {
                const auto d = ObfReaderUtilities::readSInt64(cis);
                const auto rawId = static_cast<uint64_t>(d + baseId);
                mapObject->id = ObfObjectId::generateUniqueId(rawId, baseOffset, section);

                //////////////////////////////////////////////////////////////////////////
                //if (mapObject->id.getOsmId() == 49048972u)
                //{
                //    int i = 5;
                //}
                //////////////////////////////////////////////////////////////////////////

                break;
            }
            default:
                ObfReaderUtilities::skipUnknownField(cis, tag);
                break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapObjectsBlock(
    const ObfReader_P& reader,
    const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<const ObfMapSectionLevelTreeNode>& tree,
    QList< std::shared_ptr<const OsmAnd::BinaryMapObject> >* resultOut,
    const AreaI* bbox31,
    const FilterReadingByIdFunction filterById,
    const VisitorFunction visitor,
    const std::shared_ptr<const IQueryController>& queryController,
    ObfMapSectionReader_Metrics::Metric_loadMapObjects* const metric)
{
    const auto cis = reader.getCodedInputStream().get();

    QList< std::shared_ptr<BinaryMapObject> > intermediateResult;
    QStringList mapObjectsCaptionsTable;
    gpb::uint64 baseId = 0;
    for (;;)
    {
        const auto tag = cis->ReadTag();
        switch (gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
            case 0:
            {
                if (!ObfReaderUtilities::reachedDataEnd(cis))
                    return;

                for (const auto& mapObject : constOf(intermediateResult))
                {
                    // Fill mapObject captions from string-table
                    for (auto& caption : mapObject->captions)
                    {
                        const auto stringId = ObfReaderUtilities::decodeIntegerFromString(caption);

                        if (stringId >= mapObjectsCaptionsTable.size())
                        {
                            LogPrintf(LogSeverityLevel::Error,
                                "Data mismatch: string #%d (map object %s not found in string table (size %d) in section '%s'",
                                stringId,
                                qPrintable(mapObject->id.toString()),
                                mapObjectsCaptionsTable.size(), qPrintable(section->name));
                            caption = QString::fromLatin1("#%1 NOT FOUND").arg(stringId);
                            continue;
                        }
                        caption = mapObjectsCaptionsTable[stringId];
                    }

                    //////////////////////////////////////////////////////////////////////////
                    //if (mapObject->id.getOsmId() == 49048972u)
                    //{
                    //    int i = 5;
                    //}
                    //////////////////////////////////////////////////////////////////////////

                    if (!visitor || visitor(mapObject))
                    {
                        if (resultOut)
                            resultOut->push_back(qMove(mapObject));
                    }
                }

                return;
            }
            case OBF::MapDataBlock::kBaseIdFieldNumber:
            {
                cis->ReadVarint64(&baseId);
                //////////////////////////////////////////////////////////////////////////
                //if (bbox31)
                //    LogPrintf(LogSeverityLevel::Debug, "BBOX %d %d %d %d - MAP BLOCK %" PRIi64, bbox31->top, bbox31->left, bbox31->bottom, bbox31->right, baseId);
                //////////////////////////////////////////////////////////////////////////
                break;
            }
            case OBF::MapDataBlock::kDataObjectsFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                const auto offset = cis->CurrentPosition();

                // Read map object content
                const Stopwatch readMapObjectStopwatch(metric != nullptr);
                std::shared_ptr<OsmAnd::BinaryMapObject> mapObject;
                auto oldLimit = cis->PushLimit(length);
                
                readMapObject(reader, section, baseId, tree, mapObject, bbox31, metric);

                ObfReaderUtilities::ensureAllDataWasRead(cis);
                cis->PopLimit(oldLimit);

                // Update metric
                if (metric)
                    metric->visitedMapObjects++;

                // If map object was not read, skip it
                if (!mapObject)
                {
                    if (metric)
                        metric->elapsedTimeForOnlyVisitedMapObjects += readMapObjectStopwatch.elapsed();

                    break;
                }

                // Update metric
                if (metric)
                {
                    metric->elapsedTimeForOnlyAcceptedMapObjects += readMapObjectStopwatch.elapsed();

                    metric->acceptedMapObjects++;
                }

                //////////////////////////////////////////////////////////////////////////
                //if (mapObject->id.getOsmId() == 49048972u)
                //{
                //    int i = 5;
                //}
                //////////////////////////////////////////////////////////////////////////

                // Check if map object is desired
                const auto shouldReject = filterById && !filterById(
                    section,
                    mapObject->id,
                    mapObject->bbox31,
                    mapObject->level->minZoom,
                    mapObject->level->maxZoom);
                if (shouldReject)
                    break;

                // Save object
                intermediateResult.push_back(qMove(mapObject));

                break;
            }
            case OBF::MapDataBlock::kStringTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                const auto offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(length);
                if (intermediateResult.isEmpty())
                {
                    cis->Skip(cis->BytesUntilLimit());
                    cis->PopLimit(oldLimit);
                    break;
                }
                
                ObfReaderUtilities::readStringTable(cis, mapObjectsCaptionsTable);

                ObfReaderUtilities::ensureAllDataWasRead(cis);
                cis->PopLimit(oldLimit);

                break;
            }
            default:
                ObfReaderUtilities::skipUnknownField(cis, tag);
                break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readTreeNode(
    const ObfReader_P& reader,
    const std::shared_ptr<const ObfMapSectionInfo>& section,
    const AreaI& parentArea,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& treeNode)
{
    const auto cis = reader.getCodedInputStream().get();

    uint64_t fieldsMask = 0u;
    const auto safeToSkipFieldsMask =
        (1ull << OBF::OsmAndMapIndex_MapDataBox::kLeftFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapDataBox::kRightFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapDataBox::kTopFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapDataBox::kBottomFieldNumber);
    bool kBoxesFieldNumberProcessed = false;

    for (;;)
    {
        const auto tagPos = cis->CurrentPosition();
        const auto tag = cis->ReadTag();
        const auto tfn = gpb::internal::WireFormatLite::GetTagFieldNumber(tag);
        switch (tfn)
        {
            case 0:
                if (!ObfReaderUtilities::reachedDataEnd(cis))
                    return;

                return;
            case OBF::OsmAndMapIndex_MapDataBox::kLeftFieldNumber:
            {
                const auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->area31.left() = d + parentArea.left();

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kRightFieldNumber:
            {
                const auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->area31.right() = d + parentArea.right();

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kTopFieldNumber:
            {
                const auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->area31.top() = d + parentArea.top();

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kBottomFieldNumber:
            {
                const auto d = ObfReaderUtilities::readSInt32(cis);
                treeNode->area31.bottom() = d + parentArea.bottom();

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kShiftToMapDataFieldNumber:
            {
                const auto offset = ObfReaderUtilities::readBigEndianInt(cis);
                treeNode->dataOffset = offset + treeNode->offset;

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kOceanFieldNumber:
            {
                gpb::uint32 value;
                cis->ReadVarint32(&value);

                treeNode->surfaceType = (value != 0) ? MapSurfaceType::FullWater : MapSurfaceType::FullLand;
                assert(
                    (treeNode->surfaceType != MapSurfaceType::FullWater) ||
                    (treeNode->surfaceType == MapSurfaceType::FullWater && section->isBasemapWithCoastlines));

                fieldsMask |= (1ull << tfn);
                break;
            }
            case OBF::OsmAndMapIndex_MapDataBox::kBoxesFieldNumber:
            {
                if (!kBoxesFieldNumberProcessed)
                {
                    treeNode->hasChildrenDataBoxes = true;
                    treeNode->firstDataBoxInnerOffset = tagPos - treeNode->offset;
                    kBoxesFieldNumberProcessed = true;

                    if (fieldsMask == safeToSkipFieldsMask)
                    {
                        cis->Skip(cis->BytesUntilLimit());
                        return;
                    }
                }
                ObfReaderUtilities::skipUnknownField(cis, tag);
                fieldsMask |= (1ull << tfn);
                break;
            }
            default:
                ObfReaderUtilities::skipUnknownField(cis, tag);
                break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapLevelHeader(
    const ObfReader_P& reader,
    const std::shared_ptr<ObfMapSectionLevel>& level)
{
    const auto cis = reader.getCodedInputStream().get();

    uint64_t fieldsMask = 0u;
    const auto safeToSkipFieldsMask =
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kMaxZoomFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kMinZoomFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kLeftFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kRightFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kTopFieldNumber) |
        (1ull << OBF::OsmAndMapIndex_MapRootLevel::kBottomFieldNumber);
    bool kBoxesFieldNumberProcessed = false;

    for (;;)
    {
        const auto tagPos = cis->CurrentPosition();
        const auto tag = cis->ReadTag();
        const auto tfn = gpb::internal::WireFormatLite::GetTagFieldNumber(tag);
        switch (tfn)
        {
            case 0:
                if (!ObfReaderUtilities::reachedDataEnd(cis))
                    return;

                return;
            case OBF::OsmAndMapIndex_MapRootLevel::kMaxZoomFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->maxZoom));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kMinZoomFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->minZoom));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kLeftFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->area31.left()));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kRightFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->area31.right()));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kTopFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->area31.top()));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kBottomFieldNumber:
                cis->ReadVarint32(reinterpret_cast<gpb::uint32*>(&level->area31.bottom()));
                fieldsMask |= (1ull << tfn);
                break;
            case OBF::OsmAndMapIndex_MapRootLevel::kBoxesFieldNumber:
                if (!kBoxesFieldNumberProcessed)
                {
                    level->firstDataBoxInnerOffset = tagPos - level->offset;
                    kBoxesFieldNumberProcessed = true;

                    if (fieldsMask == safeToSkipFieldsMask)
                    {
                        cis->Skip(cis->BytesUntilLimit());
                        return;
                    }
                }
                ObfReaderUtilities::skipUnknownField(cis, tag);
                fieldsMask |= (1ull << tfn);
                break;
            default:
                ObfReaderUtilities::skipUnknownField(cis, tag);
                break;
        }
    }
}
void OsmAnd::ObfMapSectionReader_P::readMapObjectsBlock(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& tree,
    QList< std::shared_ptr<const OsmAnd::Model::MapObject> >* resultOut,
    const AreaI* bbox31,
    const FilterMapObjectsByIdSignature filterById,
    std::function<bool (const std::shared_ptr<const OsmAnd::Model::MapObject>&)> visitor,
    const IQueryController* const controller,
    ObfMapSectionReader_Metrics::Metric_loadMapObjects* const metric)
{
    auto cis = reader->_codedInputStream.get();

    QList< std::shared_ptr<Model::MapObject> > intermediateResult;
    QStringList mapObjectsNamesTable;
    gpb::uint64 baseId = 0;
    for(;;)
    {
        if(controller && controller->isAborted())
            return;
        
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            for(const auto& mapObject : constOf(intermediateResult))
            {
                // Fill mapObject names from stringtable
                for(auto& nameValue : mapObject->_names)
                {
                    const auto stringId = ObfReaderUtilities::decodeIntegerFromString(nameValue);

                    if(stringId >= mapObjectsNamesTable.size())
                    {
                        LogPrintf(LogSeverityLevel::Error,
                            "Data mismatch: string #%d (map object #%" PRIu64 " (%" PRIi64 ") not found in string table (size %d) in section '%s'",
                            stringId,
                            mapObject->id >> 1, static_cast<int64_t>(mapObject->id) / 2,
                            mapObjectsNamesTable.size(), qPrintable(section->name));
                        nameValue = QString::fromLatin1("#%1 NOT FOUND").arg(stringId);
                        continue;
                    }
                    nameValue = mapObjectsNamesTable[stringId];
                }

                if(!visitor || visitor(mapObject))
                {
                    if(resultOut)
                        resultOut->push_back(qMove(mapObject));
                }
            }
            return;
        case OBF::MapDataBlock::kBaseIdFieldNumber:
            cis->ReadVarint64(&baseId);
            break;
        case OBF::MapDataBlock::kDataObjectsFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);

                // Update metric
                std::chrono::high_resolution_clock::time_point readMapObject_begin;
                if(metric)
                    readMapObject_begin = std::chrono::high_resolution_clock::now();

                // Read map object content
                std::shared_ptr<OsmAnd::Model::MapObject> mapObject;
                {
                    auto oldLimit = cis->PushLimit(length);

                    readMapObject(reader, section, baseId, tree, mapObject, bbox31);
                    assert(cis->BytesUntilLimit() == 0);

                    // Update metric
                    if(metric)
                        metric->visitedMapObjects++;

                    cis->PopLimit(oldLimit);
                }

                // If map object was not read, skip it
                if(!mapObject)
                {
                    // Update metric
                    if(metric)
                    {
                        const std::chrono::duration<float> readMapObject_elapsed = std::chrono::high_resolution_clock::now() - readMapObject_begin;
                        metric->elapsedTimeForOnlyVisitedMapObjects += readMapObject_elapsed.count();
                    }

                    break;
                }

                // Update metric
                if(metric)
                {
                    const std::chrono::duration<float> readMapObject_elapsed = std::chrono::high_resolution_clock::now() - readMapObject_begin;
                    metric->elapsedTimeForOnlyAcceptedMapObjects += readMapObject_elapsed.count();

                    metric->acceptedMapObjects++;
                }

                // Make unique map object identifier
                mapObject->_id = Model::MapObject::getUniqueId(mapObject->_id, section);

                // Check if map object is desired
                if(filterById && !filterById(section, mapObject->id, mapObject->bbox31, mapObject->level->minZoom, mapObject->level->maxZoom))
                    break;

                // Save object
                mapObject->_foundation = tree->_foundation;
                intermediateResult.push_back(qMove(mapObject));
            }
            break;
        case OBF::MapDataBlock::kStringTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                if(intermediateResult.isEmpty())
                {
                    cis->Skip(cis->BytesUntilLimit());
                    cis->PopLimit(oldLimit);
                    break;
                }
                ObfReaderUtilities::readStringTable(cis, mapObjectsNamesTable);
                assert(cis->BytesUntilLimit() == 0);
                cis->PopLimit(oldLimit);
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
void OsmAnd::ObfMapSectionReader_P::readMapObjectsBlock(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<const ObfMapSectionInfo>& section,
    const std::shared_ptr<ObfMapSectionLevelTreeNode>& tree,
    QList< std::shared_ptr<const OsmAnd::Model::MapObject> >* resultOut,
    const AreaI* bbox31,
    std::function<bool (const std::shared_ptr<const OsmAnd::Model::MapObject>&)> visitor,
    IQueryController* controller)
{
    auto cis = reader->_codedInputStream.get();

    QList< std::shared_ptr<OsmAnd::Model::MapObject> > intermediateResult;
    QStringList mapObjectsNamesTable;
    gpb::uint64 baseId = 0;
    for(;;)
    {
        if(controller && controller->isAborted())
            return;
        
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            for(auto itEntry = intermediateResult.begin(); itEntry != intermediateResult.end(); ++itEntry)
            {
                const auto& entry = *itEntry;

                // Fill names of roads from stringtable
                for(auto itNameEntry = entry->_names.begin(); itNameEntry != entry->_names.end(); ++itNameEntry)
                {
                    const auto& encodedId = itNameEntry.value();
                    uint32_t stringId = 0;
                    stringId |= (encodedId.at(1 + 0).unicode() & 0xff) << 8*0;
                    stringId |= (encodedId.at(1 + 1).unicode() & 0xff) << 8*1;
                    stringId |= (encodedId.at(1 + 2).unicode() & 0xff) << 8*2;
                    stringId |= (encodedId.at(1 + 3).unicode() & 0xff) << 8*3;

                    if(stringId >= mapObjectsNamesTable.size())
                    {
                        LogPrintf(LogSeverityLevel::Error,
                            "Data mismatch: string #%d (map object #%" PRIu64 " (%" PRIi64 ") not found in string table(%d) in section '%s'",
                            stringId,
                            entry->id >> 1, static_cast<int64_t>(entry->id) / 2,
                            mapObjectsNamesTable.size(), qPrintable(section->name));
                        itNameEntry.value() = QString::fromLatin1("#%1 NOT FOUND").arg(stringId);
                        continue;
                    }
                    itNameEntry.value() = mapObjectsNamesTable[stringId];
                }

                if(!visitor || visitor(entry))
                {
                    if(resultOut)
                        resultOut->push_back(entry);
                }
            }
            return;
        case OBF::MapDataBlock::kBaseIdFieldNumber:
            cis->ReadVarint64(&baseId);
            break;
        case OBF::MapDataBlock::kDataObjectsFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                auto pos = cis->CurrentPosition();
                std::shared_ptr<OsmAnd::Model::MapObject> mapObject;
                readMapObject(reader, section, tree, baseId, mapObject, bbox31);
                if(mapObject)
                {
                    mapObject->_foundation = tree->_foundation;
                    intermediateResult.push_back(mapObject);
                }
                cis->PopLimit(oldLimit);
            }
            break;
        case OBF::MapDataBlock::kStringTableFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);
                if(intermediateResult.isEmpty())
                {
                    cis->Skip(cis->BytesUntilLimit());
                    cis->PopLimit(oldLimit);
                    break;
                }
                ObfReaderUtilities::readStringTable(cis, mapObjectsNamesTable);
                cis->PopLimit(oldLimit);
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
void OsmAnd::ObfRoutingSectionReader_P::readSubsectionHeader(
    const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<ObfRoutingSubsectionInfo>& subsection,
    const std::shared_ptr<ObfRoutingSubsectionInfo>& parent, uint32_t depth /*= std::numeric_limits<uint32_t>::max()*/)
{
    auto shouldReadSubsections = (depth > 0);

    auto cis = reader->_codedInputStream.get();
    for(;;)
    {
        auto lastPos = cis->CurrentPosition();
        auto tag = cis->ReadTag();
        switch(gpb::internal::WireFormatLite::GetTagFieldNumber(tag))
        {
        case 0:
            return;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kLeftFieldNumber:
            {
                auto dleft = ObfReaderUtilities::readSInt32(cis);
                subsection->_area31.left = dleft + (parent ? parent->_area31.left : 0);
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kRightFieldNumber:
            {
                auto dright = ObfReaderUtilities::readSInt32(cis);
                subsection->_area31.right = dright + (parent ? parent->_area31.right : 0);
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kTopFieldNumber:
            {
                auto dtop = ObfReaderUtilities::readSInt32(cis);
                subsection->_area31.top = dtop + (parent ? parent->_area31.top : 0);
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kBottomFieldNumber:
            {
                auto dbottom = ObfReaderUtilities::readSInt32(cis);
                subsection->_area31.bottom = dbottom + (parent ? parent->_area31.bottom : 0);
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kShiftToDataFieldNumber:
            {
                subsection->_dataOffset = ObfReaderUtilities::readBigEndianInt(cis);

                // In case we have data, we must read all subsections
                shouldReadSubsections = true;
            }
            break;
        case OBF::OsmAndRoutingIndex_RouteDataBox::kBoxesFieldNumber:
            {
                if(subsection->_subsectionsOffset == 0)
                    subsection->_subsectionsOffset = lastPos;
                if(!shouldReadSubsections)
                {
                    cis->Skip(cis->BytesUntilLimit());
                    break;
                }

                const std::shared_ptr<ObfRoutingSubsectionInfo> childSubsection(new ObfRoutingSubsectionInfo(subsection));
                childSubsection->_length = ObfReaderUtilities::readBigEndianInt(cis);
                childSubsection->_offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(childSubsection->_length);
                readSubsectionHeader(reader, childSubsection, subsection, depth - 1);
                
                cis->PopLimit(oldLimit);

                subsection->_subsections.push_back(qMove(childSubsection));
            }
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}
void OsmAnd::ObfRoutingSectionReader_P::read( const std::unique_ptr<ObfReader_P>& reader, const std::shared_ptr<ObfRoutingSectionInfo>& section )
{
    auto cis = reader->_codedInputStream.get();

    uint32_t routeEncodingRuleId = 1;
    for(;;)
    {
        auto tag = cis->ReadTag();
        auto tfn = gpb::internal::WireFormatLite::GetTagFieldNumber(tag);
        switch(tfn)
        {
        case 0:
            return;
        case OBF::OsmAndRoutingIndex::kNameFieldNumber:
            ObfReaderUtilities::readQString(cis, section->_name);
            break;
        case OBF::OsmAndRoutingIndex::kRulesFieldNumber:
            {
                gpb::uint32 length;
                cis->ReadVarint32(&length);
                auto oldLimit = cis->PushLimit(length);

                const std::shared_ptr<ObfRoutingSectionInfo_P::EncodingRule> encodingRule(new ObfRoutingSectionInfo_P::EncodingRule());
                encodingRule->_id = routeEncodingRuleId++;
                readEncodingRule(reader, section, encodingRule);

                cis->PopLimit(oldLimit);

                while((unsigned)section->_d->_encodingRules.size() < encodingRule->_id)
                    section->_d->_encodingRules.push_back(qMove(std::shared_ptr<ObfRoutingSectionInfo_P::EncodingRule>()));
                section->_d->_encodingRules.push_back(qMove(encodingRule));
            } 
            break;
        case OBF::OsmAndRoutingIndex::kRootBoxesFieldNumber:
        case OBF::OsmAndRoutingIndex::kBasemapBoxesFieldNumber:
            {
                const std::shared_ptr<ObfRoutingSubsectionInfo> subsection(new ObfRoutingSubsectionInfo(section));
                subsection->_length = ObfReaderUtilities::readBigEndianInt(cis);
                subsection->_offset = cis->CurrentPosition();
                auto oldLimit = cis->PushLimit(subsection->_length);

                readSubsectionHeader(reader, subsection, nullptr, 0);

                cis->PopLimit(oldLimit);

                if(tfn == OBF::OsmAndRoutingIndex::kRootBoxesFieldNumber)
                    section->_subsections.push_back(qMove(subsection));
                else
                    section->_baseSubsections.push_back(qMove(subsection));
            }
            break;
        case OBF::OsmAndRoutingIndex::kBorderBoxFieldNumber:
        case OBF::OsmAndRoutingIndex::kBaseBorderBoxFieldNumber:
            {
                auto length = ObfReaderUtilities::readBigEndianInt(cis);
                auto offset = cis->CurrentPosition();
                if(tfn == OBF::OsmAndRoutingIndex::kBorderBoxFieldNumber)
                {
                    section->_d->_borderBoxLength = length;
                    section->_d->_borderBoxOffset = offset;
                }
                else
                {
                    section->_d->_baseBorderBoxLength = length;
                    section->_d->_baseBorderBoxOffset = offset;
                }
                cis->Skip(length);
            }
            break;
        case OBF::OsmAndRoutingIndex::kBlocksFieldNumber:
            cis->Skip(cis->BytesUntilLimit());
            break;
        default:
            ObfReaderUtilities::skipUnknownField(cis, tag);
            break;
        }
    }
}