summaryrefslogtreecommitdiff
path: root/include/mbgl/storage/offline.hpp
blob: af12744f0d7ba6b8dc32154eb8cd5bfa04a69aca (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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#pragma once

#include <mbgl/util/geo.hpp>
#include <mbgl/util/range.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/storage/response.hpp>

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

#include <string>
#include <vector>
#include <functional>

namespace mbgl {

class TileID;

/*
 * An offline region defined by a style URL, geometry, zoom range, and
 * device pixel ratio.
 *
 * Both minZoom and maxZoom must be ≥ 0, and maxZoom must be ≥ minZoom.
 *
 * maxZoom may be ∞, in which case for each tile source, the region will include
 * tiles from minZoom up to the maximum zoom level provided by that source.
 *
 * pixelRatio must be ≥ 0 and should typically be 1.0 or 2.0.
 */
    
template <typename TGeometry>
struct OfflineFixedGeometryTraits { };
    
template <>
struct OfflineFixedGeometryTraits<LatLngBounds> {
    static const char typeName[];
    constexpr static const size_t typeNameLength = sizeof("PYRAMID") - 1;
    
    void encode(rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>& doc, LatLngBounds bounds);
    LatLngBounds decode(const rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>& doc);
};
    
template <>
struct OfflineFixedGeometryTraits<Polygon<double>> {
    static const char typeName[];
    constexpr static const size_t typeNameLength = sizeof("POLYGON") - 1;
    
    void encode(rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>&, const Polygon<double>&);
    Polygon<double> decode(const rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>&);
};
    
template <typename TGeometry>
class OfflineFixedGeometryRegionDefinition {
public:
    OfflineFixedGeometryRegionDefinition(std::string styleURL_, TGeometry geometry_, Range<double> zoomRange_, float pixelRatio_)
    : styleURL(std::move(styleURL_)),
    geometry(std::move(geometry_)),
    minZoom(zoomRange_.min),
    maxZoom(zoomRange_.max),
    pixelRatio(pixelRatio_) { }
    
    /* Private */
    std::vector<CanonicalTileID> tileCover(SourceType type, uint16_t tileSize, Range<uint8_t> sourceZoomRange) const {
        double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), sourceZoomRange.min);
        double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), sourceZoomRange.max);
        
        assert(minZ >= 0);
        assert(maxZ >= 0);
        assert(minZ < std::numeric_limits<uint8_t>::max());
        assert(maxZ < std::numeric_limits<uint8_t>::max());
        
        std::vector<CanonicalTileID> result;
        for (uint8_t z = minZ; z <= maxZ; ++z) {
            for (const auto& tile : util::tileCover(geometry, z)) {
                result.emplace_back(tile.canonical);
            }
        }
        
        return result;
    }
    
    std::string encode() const {
        OfflineFixedGeometryTraits<TGeometry> geometryTraits;
        
        rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc;
        doc.SetObject();
        
        doc.AddMember("type", rapidjson::StringRef(geometryTraits.typeName, geometryTraits.typeNameLength), doc.GetAllocator());
        doc.AddMember("style_url", rapidjson::StringRef(styleURL.data(), styleURL.length()), doc.GetAllocator());
        
        geometryTraits.encode(doc, geometry);
        
        doc.AddMember("min_zoom", minZoom, doc.GetAllocator());
        if (std::isfinite(maxZoom)) {
            doc.AddMember("max_zoom", maxZoom, doc.GetAllocator());
        }
        
        doc.AddMember("pixel_ratio", pixelRatio, doc.GetAllocator());
        
        rapidjson::StringBuffer buffer;
        rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
        doc.Accept(writer);
        
        return buffer.GetString();
    }
    
    static OfflineFixedGeometryRegionDefinition<TGeometry> decode(const rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>& doc) {
        OfflineFixedGeometryTraits<TGeometry> geometryTraits;
        
        if (doc.HasParseError() ||
            !doc.HasMember("style_url") || !doc["style_url"].IsString() ||
            !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 fixed geometry region definition");
        }
        
        std::string styleURL { doc["style_url"].GetString(), doc["style_url"].GetStringLength() };
        double minZoom = doc["min_zoom"].GetDouble();
        double maxZoom = doc.HasMember("max_zoom") ? doc["max_zoom"].GetDouble() : INFINITY;
        Range<double> zoomRange{ minZoom, maxZoom };
        float pixelRatio = doc["pixel_ratio"].GetDouble();
        
        auto geometry = geometryTraits.decode(doc);
        
        return { styleURL, geometry, zoomRange, pixelRatio };
    }
    
    const std::string styleURL;
    const TGeometry geometry;
    const double minZoom;
    const double maxZoom;
    const float pixelRatio;
};
    
using OfflineRegionDefinition = variant<OfflineFixedGeometryRegionDefinition<LatLngBounds>, OfflineFixedGeometryRegionDefinition<Polygon<double>>>;
    
float pixelRatio(const OfflineRegionDefinition&);
const std::string& styleURL(const OfflineRegionDefinition&);
std::vector<CanonicalTileID> tileCover(const OfflineRegionDefinition&, SourceType, uint16_t, Range<uint8_t>);
    
std::string encodeOfflineRegionDefinition(const OfflineRegionDefinition&);
OfflineRegionDefinition decodeOfflineRegionDefinition(const std::string&);

/*
 * Arbitrary binary region metadata. The contents are opaque to the mbgl implementation;
 * it just stores and retrieves a BLOB. SDK bindings should leave the interpretation of
 * this data up to the application; they _should not_ enforce a higher-level data format.
 * In the future we want offline database to be portable across target platforms, and a
 * platform-specific metadata format would prevent that.
 */
using OfflineRegionMetadata = std::vector<uint8_t>;

/*
 * A region is either inactive (not downloading, but previously-downloaded
 * resources are available for use), or active (resources are being downloaded
 * or will be downloaded, if necessary, when network access is available).
 *
 * This state is independent of whether or not the complete set of resources
 * is currently available for offline use. To check if that is the case, use
 * `OfflineRegionStatus::complete()`.
 */
enum class OfflineRegionDownloadState {
    Inactive,
    Active
};

/*
 * A region's status includes its active/inactive state as well as counts
 * of the number of resources that have completed downloading, their total
 * size in bytes, and the total number of resources that are required.
 *
 * Note that the total required size in bytes is not currently available. A
 * future API release may provide an estimate of this number.
 */
class OfflineRegionStatus {
public:
    OfflineRegionDownloadState downloadState = OfflineRegionDownloadState::Inactive;

    /**
     * The number of resources that have been fully downloaded and are ready for
     * offline access.
     */
    uint64_t completedResourceCount = 0;

    /**
     * The cumulative size, in bytes, of all resources (inclusive of tiles) that have
     * been fully downloaded.
     */
    uint64_t completedResourceSize = 0;

    /**
     * The number of tiles that are known to be required for this region. This is a
     * subset of `completedResourceCount`.
     */
    uint64_t completedTileCount = 0;

    /**
     * The cumulative size, in bytes, of all tiles that have been fully downloaded.
     * This is a subset of `completedResourceSize`.
     */
    uint64_t completedTileSize = 0;

    /**
     * The number of resources that are known to be required for this region. See the
     * documentation for `requiredResourceCountIsPrecise` for an important caveat
     * about this number.
     */
    uint64_t requiredResourceCount = 0;

    /**
     * This property is true when the value of requiredResourceCount is a precise
     * count of the number of required resources, and false when it is merely a lower
     * bound.
     *
     * Specifically, it is false during early phases of an offline download. Once
     * style and tile sources have been downloaded, it is possible to calculate the
     * precise number of required resources, at which point it is set to true.
     */
    bool requiredResourceCountIsPrecise = false;

    bool complete() const {
        return completedResourceCount == requiredResourceCount;
    }
};

/*
 * A region can have a single observer, which gets notified whenever a change
 * to the region's status occurs.
 */
class OfflineRegionObserver {
public:
    virtual ~OfflineRegionObserver() = default;

    /*
     * Implement this method to be notified of a change in the status of an
     * offline region. Status changes include any change in state of the members
     * of OfflineRegionStatus.
     *
     * Note that this method will be executed on the database thread; it is the
     * responsibility of the SDK bindings to wrap this object in an interface that
     * re-executes the user-provided implementation on the main thread.
     */
    virtual void statusChanged(OfflineRegionStatus) {}

    /*
     * Implement this method to be notified of errors encountered while downloading
     * regional resources. Such errors may be recoverable; for example the implementation
     * will attempt to re-request failed resources based on an exponential backoff
     * algorithm, or when it detects that network access has been restored.
     *
     * Note that this method will be executed on the database thread; it is the
     * responsibility of the SDK bindings to wrap this object in an interface that
     * re-executes the user-provided implementation on the main thread.
     */
    virtual void responseError(Response::Error) {}

    /*
     * Implement this method to be notified when the limit on the number of Mapbox
     * tiles stored for offline regions has been reached.
     *
     * Once the limit has been reached, the SDK will not download further offline
     * tiles from Mapbox APIs until existing tiles have been removed. Contact your
     * Mapbox sales representative to raise the limit.
     *
     * This limit does not apply to non-Mapbox tile sources.
     *
     * Note that this method will be executed on the database thread; it is the
     * responsibility of the SDK bindings to wrap this object in an interface that
     * re-executes the user-provided implementation on the main thread.
     */
    virtual void mapboxTileCountLimitExceeded(uint64_t /* limit */) {}
};

class OfflineRegion {
public:
    // Move-only; not publicly constructible.
    OfflineRegion(OfflineRegion&&);
    OfflineRegion& operator=(OfflineRegion&&);
    ~OfflineRegion();

    OfflineRegion() = delete;
    OfflineRegion(const OfflineRegion&) = delete;
    OfflineRegion& operator=(const OfflineRegion&) = delete;

    int64_t getID() const;
    const OfflineRegionDefinition& getDefinition() const;
    const OfflineRegionMetadata& getMetadata() const;

private:
    friend class OfflineDatabase;

    OfflineRegion(int64_t id,
                  OfflineRegionDefinition,
                  OfflineRegionMetadata);

    const int64_t id;
    const OfflineRegionDefinition definition;
    const OfflineRegionMetadata metadata;
};

} // namespace mbgl