summaryrefslogtreecommitdiff
path: root/src/mbgl/style/tile_source.cpp
blob: 022693f49905a29bf9574cade68019e3ea1c020b (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
#include <mbgl/style/tile_source.hpp>
#include <mbgl/style/source_observer.hpp>
#include <mbgl/style/parser.hpp>
#include <mbgl/util/tileset.hpp>
#include <mbgl/util/mapbox.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/platform/log.hpp>

#include <rapidjson/document.h>
#include <rapidjson/error/en.h>

#include <sstream>

namespace mbgl {
namespace style {

namespace {

void parseTileJSONMember(const JSValue& value, std::vector<std::string>& target, const char* name) {
    if (!value.HasMember(name)) {
        return;
    }

    const JSValue& property = value[name];
    if (!property.IsArray()) {
        return;
    }

    for (rapidjson::SizeType i = 0; i < property.Size(); i++) {
        if (!property[i].IsString()) {
            return;
        }
    }

    for (rapidjson::SizeType i = 0; i < property.Size(); i++) {
        target.emplace_back(std::string(property[i].GetString(), property[i].GetStringLength()));
    }
}

void parseTileJSONMember(const JSValue& value, std::string& target, const char* name) {
    if (!value.HasMember(name)) {
        return;
    }

    const JSValue& property = value[name];
    if (!property.IsString()) {
        return;
    }

    target = { property.GetString(), property.GetStringLength() };
}

void parseTileJSONMember(const JSValue& value, uint8_t& target, const char* name) {
    if (!value.HasMember(name)) {
        return;
    }

    const JSValue& property = value[name];
    if (!property.IsUint()) {
        return;
    }

    unsigned int uint = property.GetUint();
    if (uint > std::numeric_limits<uint8_t>::max()) {
        return;
    }

    target = uint;
}

void parseTileJSONMember(const JSValue& value, std::array<double, 4>& target, const char* name) {
    if (!value.HasMember(name)) {
        return;
    }

    const JSValue& property = value[name];
    if (!property.IsArray() || property.Size() > 4) {
        return;
    }

    for (rapidjson::SizeType i = 0; i < property.Size(); i++) {
        if (!property[i].IsNumber()) {
            return;
        }
    }

    for (rapidjson::SizeType i = 0; i < property.Size(); i++) {
        target[i] = property[i].GetDouble();
    }
}

Tileset parseTileJSON(const JSValue& value) {
    Tileset result;

    parseTileJSONMember(value, result.tiles, "tiles");
    parseTileJSONMember(value, result.zoomRange.min, "minzoom");
    parseTileJSONMember(value, result.zoomRange.max, "maxzoom");
    parseTileJSONMember(value, result.attribution, "attribution");

    std::array<double, 4> array;
    parseTileJSONMember(value, array, "center");
    result.center = { array[0], array[1] };
    result.zoom = array[2];
    parseTileJSONMember(value, array, "bounds");
    result.bounds = LatLngBounds::hull({ array[0], array[1] }, { array[2], array[3] });

    return result;
}

} // end namespace

Tileset TileSource::parseTileJSON(const std::string& json, const std::string& sourceURL, SourceType type, uint16_t tileSize) {
    rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> document;
    document.Parse<0>(json.c_str());

    if (document.HasParseError()) {
        std::stringstream message;
        message << document.GetErrorOffset() << " - " << rapidjson::GetParseError_En(document.GetParseError());
        throw std::runtime_error(message.str());
    }

    Tileset result = mbgl::style::parseTileJSON(document);

    // TODO: Remove this hack by delivering proper URLs in the TileJSON to begin with.
    if (util::mapbox::isMapboxURL(sourceURL)) {
        for (auto& url : result.tiles) {
            url = util::mapbox::canonicalizeTileURL(url, type, tileSize);
        }
    }

    return result;
}

optional<variant<std::string, Tileset>> TileSource::parseURLOrTileset(const JSValue& value) {
    if (!value.HasMember("url")) {
        return { mbgl::style::parseTileJSON(value) };
    }

    const JSValue& urlVal = value["url"];
    if (!urlVal.IsString()) {
        Log::Error(Event::ParseStyle, "source url must be a string");
        return {};
    }

    return { std::string(urlVal.GetString(), urlVal.GetStringLength()) };
}

TileSource::TileSource(SourceType type_,
                       std::string id_,
                       variant<std::string, Tileset> urlOrTileset_,
                       uint16_t tileSize_)
    : Source(type_, std::move(id_)),
      urlOrTileset(std::move(urlOrTileset_)),
      tileSize(tileSize_) {
}

TileSource::~TileSource() = default;

void TileSource::load(FileSource& fileSource) {
    if (urlOrTileset.is<Tileset>()) {
        tileset = urlOrTileset.get<Tileset>();
        loaded = true;
        return;
    }

    if (req) {
        return;
    }

    const std::string& url = urlOrTileset.get<std::string>();
    req = fileSource.request(Resource::source(url), [this, url](Response res) {
        if (res.error) {
            observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(res.error->message)));
        } else if (res.notModified) {
            return;
        } else if (res.noContent) {
            observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error("unexpectedly empty TileJSON")));
        } else {
            Tileset newTileset;

            // Create a new copy of the Tileset object that holds the base values we've parsed
            // from the stylesheet. Then merge in the values parsed from the TileJSON we retrieved
            // via the URL.
            try {
                newTileset = parseTileJSON(*res.data, url, type, tileSize);
            } catch (...) {
                observer->onSourceError(*this, std::current_exception());
                return;
            }

            // Check whether previous information specifies different tile
            if (tileset.tiles != newTileset.tiles) {
                // Tile URLs changed: force tiles to be reloaded.
                invalidateTiles();

                // Tile size changed: We need to recalculate the tiles we need to load because we
                // might have to load tiles for a different zoom level
                // This is done automatically when we trigger the onSourceLoaded observer below.

                // Min/Max zoom changed: We need to recalculate what tiles to load, if we have tiles
                // loaded that are outside the new zoom range
                // This is done automatically when we trigger the onSourceLoaded observer below.

                // Attribution changed: We need to notify the embedding application that this
                // changed. See https://github.com/mapbox/mapbox-gl-native/issues/2723
                // This is not yet implemented.

                // Center/bounds changed: We're not using these values currently
            }

            tileset = newTileset;
            loaded = true;

            observer->onSourceLoaded(*this);
        }
    });
}

Range<uint8_t> TileSource::getZoomRange() {
    assert(loaded);
    return tileset.zoomRange;
}

} // namespace style
} // namespace mbgl