summaryrefslogtreecommitdiff
path: root/platform/default/mbgl/storage/offline.cpp
blob: ab878a2d5539f5065e2390d7e0d227e85ec56a81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include <mbgl/storage/offline.hpp>
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/util/tileset.hpp>
#include <mbgl/util/projection.hpp>

#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>

#include <cmath>

namespace mbgl {

OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition(
    std::string styleURL_, LatLngBounds bounds_, double minZoom_, double maxZoom_, float pixelRatio_)
    : styleURL(std::move(styleURL_)),
      bounds(std::move(bounds_)),
      minZoom(minZoom_),
      maxZoom(maxZoom_),
      pixelRatio(pixelRatio_) {
    if (minZoom < 0 || maxZoom < 0 || maxZoom < minZoom || pixelRatio < 0 ||
        !std::isfinite(minZoom) || std::isnan(maxZoom) || !std::isfinite(pixelRatio)) {
        throw std::invalid_argument("Invalid offline region definition");
    }
}

std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
    const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange);

    std::vector<CanonicalTileID> result;

    for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) {
        for (const auto& tile : util::tileCover(bounds, z)) {
            result.emplace_back(tile.canonical);
        }
    }

    return result;
}

uint64_t OfflineTilePyramidRegionDefinition::tileCount(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
    
    const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange);
    unsigned long result = 0;;
    for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) {
        result +=  util::tileCount(bounds, z, tileSize);
    }

    return result;
}

Range<uint8_t> OfflineTilePyramidRegionDefinition::coveringZoomRange(style::SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const {
    double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min);
    double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max);

    assert(minZ >= 0);
    assert(maxZ >= 0);
    assert(minZ < std::numeric_limits<uint8_t>::max());
    assert(maxZ < std::numeric_limits<uint8_t>::max());
    return { static_cast<uint8_t>(minZ), static_cast<uint8_t>(maxZ) };
}

OfflineRegionDefinition decodeOfflineRegionDefinition(const std::string& region) {
    rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
    doc.Parse<0>(region.c_str());

    if (doc.HasParseError() ||
        !doc.HasMember("style_url") || !doc["style_url"].IsString() ||
        !doc.HasMember("bounds") || !doc["bounds"].IsArray() || doc["bounds"].Size() != 4 ||
          !doc["bounds"][0].IsDouble() || !doc["bounds"][1].IsDouble() ||
          !doc["bounds"][2].IsDouble() || !doc["bounds"][3].IsDouble() ||
        !doc.HasMember("min_zoom") || !doc["min_zoom"].IsDouble() ||
        (doc.HasMember("max_zoom") && !doc["max_zoom"].IsDouble()) ||
        !doc.HasMember("pixel_ratio") || !doc["pixel_ratio"].IsDouble()) {
        throw std::runtime_error("Malformed offline region definition");
    }

    std::string styleURL { doc["style_url"].GetString(), doc["style_url"].GetStringLength() };
    LatLngBounds bounds = LatLngBounds::hull(
        LatLng(doc["bounds"][0].GetDouble(), doc["bounds"][1].GetDouble()),
        LatLng(doc["bounds"][2].GetDouble(), doc["bounds"][3].GetDouble()));
    double minZoom = doc["min_zoom"].GetDouble();
    double maxZoom = doc.HasMember("max_zoom") ? doc["max_zoom"].GetDouble() : INFINITY;
    float pixelRatio = doc["pixel_ratio"].GetDouble();

    return { styleURL, bounds, minZoom, maxZoom, pixelRatio };
}

std::string encodeOfflineRegionDefinition(const OfflineRegionDefinition& region) {
    rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
    doc.SetObject();

    doc.AddMember("style_url", rapidjson::StringRef(region.styleURL.data(), region.styleURL.length()), doc.GetAllocator());

    rapidjson::GenericValue<rapidjson::UTF8<>, rapidjson::CrtAllocator> bounds(rapidjson::kArrayType);
    bounds.PushBack(region.bounds.south(), doc.GetAllocator());
    bounds.PushBack(region.bounds.west(), doc.GetAllocator());
    bounds.PushBack(region.bounds.north(), doc.GetAllocator());
    bounds.PushBack(region.bounds.east(), doc.GetAllocator());
    doc.AddMember("bounds", bounds, doc.GetAllocator());

    doc.AddMember("min_zoom", region.minZoom, doc.GetAllocator());
    if (std::isfinite(region.maxZoom)) {
        doc.AddMember("max_zoom", region.maxZoom, doc.GetAllocator());
    }

    doc.AddMember("pixel_ratio", region.pixelRatio, doc.GetAllocator());

    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    doc.Accept(writer);

    return buffer.GetString();
}

OfflineRegion::OfflineRegion(int64_t id_,
                             OfflineRegionDefinition definition_,
                             OfflineRegionMetadata metadata_)
    : id(id_),
      definition(std::move(definition_)),
      metadata(std::move(metadata_)) {
}

OfflineRegion::OfflineRegion(OfflineRegion&&) = default;
OfflineRegion::~OfflineRegion() = default;

const OfflineRegionDefinition& OfflineRegion::getDefinition() const {
    return definition;
}

const OfflineRegionMetadata& OfflineRegion::getMetadata() const {
    return metadata;
}

int64_t OfflineRegion::getID() const {
    return id;
}

// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-tms_style-srs
// Computes the projected tiles for the lower left and upper right points of the bounds
// and uses that to compute the tile cover count
//
uint64_t OfflineRegion::getTileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_) const {

        auto sw = Projection::project(bounds.southwest().wrapped(), zoom, tileSize_);
        auto ne = Projection::project(bounds.northeast().wrapped(), zoom, tileSize_);

        auto x1 = floor(sw.x/ tileSize_);
        auto x2 = floor((ne.x - 1) / tileSize_);
        auto y1 = floor(sw.y/ tileSize_);
        auto y2 = floor((ne.y - 1) / tileSize_);

        auto minX = ::fmax(std::min(x1, x2), 0);
        auto maxX = std::max(x1, x2);
        auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2);
        auto maxY = (std::pow(2, zoom) - 1) - ::fmax(std::min(y1, y2), 0);

        return (maxX - minX + 1) * (maxY - minY + 1);
    }

} // namespace mbgl