Skip to content

Commit cf9f41c

Browse files
tdcosta100louwers
andauthored
Add Initial PMTiles support (#2882)
Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
1 parent c7e6b0c commit cf9f41c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+969
-32
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@
5555
[submodule "vendor/glslang"]
5656
path = vendor/glslang
5757
url = https://github.com/KhronosGroup/glslang.git
58+
[submodule "vendor/PMTiles"]
59+
path = vendor/PMTiles
60+
url = https://github.com/protomaps/PMTiles.git

BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ cc_library(
129129
"//vendor:eternal",
130130
"//vendor:mapbox-base",
131131
"//vendor:parsedate",
132+
"//vendor:pmtiles",
132133
"//vendor:polylabel",
133134
"//vendor:protozero",
134135
"//vendor:unique_resource",

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ option(MLN_WITH_OPENGL "Build with OpenGL renderer" ON)
1111
option(MLN_WITH_EGL "Build with EGL renderer" OFF)
1212
option(MLN_WITH_VULKAN "Build with Vulkan renderer" OFF)
1313
option(MLN_WITH_OSMESA "Build with OSMesa (Software) renderer" OFF)
14+
option(MLN_WITH_PMTILES "Build with PMTiles support" ON)
1415
option(MLN_WITH_WERROR "Make all compilation warnings errors" ON)
1516
option(MLN_LEGACY_RENDERER "Include the legacy rendering pathway" ON)
1617
option(MLN_DRAWABLE_RENDERER "Include the drawable rendering pathway" OFF)
@@ -1450,6 +1451,7 @@ include(${PROJECT_SOURCE_DIR}/vendor/earcut.hpp.cmake)
14501451
include(${PROJECT_SOURCE_DIR}/vendor/eternal.cmake)
14511452
include(${PROJECT_SOURCE_DIR}/vendor/mapbox-base.cmake)
14521453
include(${PROJECT_SOURCE_DIR}/vendor/parsedate.cmake)
1454+
include(${PROJECT_SOURCE_DIR}/vendor/pmtiles.cmake)
14531455
include(${PROJECT_SOURCE_DIR}/vendor/polylabel.cmake)
14541456
include(${PROJECT_SOURCE_DIR}/vendor/protozero.cmake)
14551457
include(${PROJECT_SOURCE_DIR}/vendor/tracy.cmake)
@@ -1473,6 +1475,7 @@ target_link_libraries(
14731475
mbgl-vendor-earcut.hpp
14741476
mbgl-vendor-eternal
14751477
mbgl-vendor-parsedate
1478+
mbgl-vendor-pmtiles
14761479
mbgl-vendor-polylabel
14771480
mbgl-vendor-protozero
14781481
mbgl-vendor-unique_resource
@@ -1512,6 +1515,7 @@ export(TARGETS
15121515
mbgl-vendor-earcut.hpp
15131516
mbgl-vendor-eternal
15141517
mbgl-vendor-parsedate
1518+
mbgl-vendor-pmtiles
15151519
mbgl-vendor-polylabel
15161520
mbgl-vendor-protozero
15171521
mbgl-vendor-unique_resource

bazel/core.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ MLN_CORE_SOURCE = [
377377
"src/mbgl/storage/local_file_source.hpp",
378378
"src/mbgl/storage/main_resource_loader.hpp",
379379
"src/mbgl/storage/network_status.cpp",
380+
"src/mbgl/storage/pmtiles_file_source.hpp",
380381
"src/mbgl/storage/resource.cpp",
381382
"src/mbgl/storage/resource_options.cpp",
382383
"src/mbgl/storage/resource_transform.cpp",

include/mbgl/storage/file_source.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ enum FileSourceType : uint8_t {
2525
FileSystem,
2626
Network,
2727
Mbtiles,
28+
Pmtiles,
2829
ResourceLoader ///< %Resource loader acts as a proxy and has logic
2930
/// for request delegation to Asset, Cache, and other
3031
/// file sources.

include/mbgl/storage/resource.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class Resource {
9595
// Includes auxiliary data if this is a tile request.
9696
std::optional<TileData> tileData;
9797

98+
std::optional<std::pair<uint64_t, uint64_t>> dataRange = std::nullopt;
9899
std::optional<Timestamp> priorModified = std::nullopt;
99100
std::optional<Timestamp> priorExpires = std::nullopt;
100101
std::optional<std::string> priorEtag = std::nullopt;

include/mbgl/util/constants.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ constexpr int DEFAULT_RATE_LIMIT_TIMEOUT = 5;
6060
constexpr const char* ASSET_PROTOCOL = "asset://";
6161
constexpr const char* FILE_PROTOCOL = "file://";
6262
constexpr const char* MBTILES_PROTOCOL = "mbtiles://";
63+
constexpr const char* PMTILES_PROTOCOL = "pmtiles://";
6364
constexpr uint32_t DEFAULT_MAXIMUM_CONCURRENT_REQUESTS = 20;
6465

6566
constexpr uint8_t TERRAIN_RGB_MAXZOOM = 15;

platform/android/MapLibreAndroid/src/cpp/http_file_source.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,15 @@ void RegisterNativeHTTPRequest(jni::JNIEnv& env) {
9191
HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_)
9292
: resource(resource_),
9393
callback(callback_) {
94+
std::string dataRangeStr;
9495
std::string etagStr;
9596
std::string modifiedStr;
9697

98+
if (resource.dataRange) {
99+
dataRangeStr = std::string("bytes=") + std::to_string(resource.dataRange->first) + std::string("-") +
100+
std::to_string(resource.dataRange->second);
101+
}
102+
97103
if (resource.priorEtag) {
98104
etagStr = *resource.priorEtag;
99105
} else if (resource.priorModified) {
@@ -104,13 +110,14 @@ HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource
104110

105111
static auto& javaClass = jni::Class<HTTPRequest>::Singleton(env);
106112
static auto constructor =
107-
javaClass.GetConstructor<jni::jlong, jni::String, jni::String, jni::String, jni::jboolean>(env);
113+
javaClass.GetConstructor<jni::jlong, jni::String, jni::String, jni::String, jni::String, jni::jboolean>(env);
108114

109115
javaRequest = jni::NewGlobal(env,
110116
javaClass.New(env,
111117
constructor,
112118
reinterpret_cast<jlong>(this),
113119
jni::Make<jni::String>(env, resource.url),
120+
jni::Make<jni::String>(env, dataRangeStr),
114121
jni::Make<jni::String>(env, etagStr),
115122
jni::Make<jni::String>(env, modifiedStr),
116123
(jboolean)(resource_.usage == Resource::Usage::Offline)));

platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/http/HttpRequest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ public interface HttpRequest {
1818
* @param httpRequest callback to be invoked when we receive a response
1919
* @param nativePtr the pointer associated to the request
2020
* @param resourceUrl the resource url to download
21+
* @param dataRange http header, used to indicate the part of a resource that the server should return
2122
* @param etag http header, identifier for a specific version of a resource
2223
* @param modified http header, used to determine if a resource hasn't been modified since
2324
* @param offlineUsage flag to indicate a resource will be used for offline, appends offline=true as a query parameter
2425
*/
2526
void executeRequest(HttpResponder httpRequest, long nativePtr, String resourceUrl,
26-
String etag, String modified, boolean offlineUsage);
27+
String dataRange, String etag, String modified, boolean offlineUsage);
2728

2829
/**
2930
* Cancels the request.

platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/http/NativeHttpRequest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ public class NativeHttpRequest implements HttpResponder {
1919
private long nativePtr;
2020

2121
@Keep
22-
private NativeHttpRequest(long nativePtr, String resourceUrl, String etag, String modified, boolean offlineUsage) {
22+
private NativeHttpRequest(long nativePtr, String resourceUrl, String dataRange, String etag, String modified,
23+
boolean offlineUsage) {
2324
this.nativePtr = nativePtr;
2425

2526
if (resourceUrl.startsWith("local://")) {
2627
// used by render test to serve files from assets
2728
executeLocalRequest(resourceUrl);
2829
return;
2930
}
30-
httpRequest.executeRequest(this, nativePtr, resourceUrl, etag, modified, offlineUsage);
31+
httpRequest.executeRequest(this, nativePtr, resourceUrl, dataRange, etag, modified, offlineUsage);
3132
}
3233

3334
public void cancel() {

platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/module/http/HttpRequestImpl.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public class HttpRequestImpl implements HttpRequest {
5858

5959
@Override
6060
public void executeRequest(HttpResponder httpRequest, long nativePtr, @NonNull String resourceUrl,
61-
@NonNull String etag, @NonNull String modified, boolean offlineUsage) {
61+
@NonNull String dataRange, @NonNull String etag, @NonNull String modified,
62+
boolean offlineUsage) {
6263
OkHttpCallback callback = new OkHttpCallback(httpRequest);
6364
try {
6465
HttpUrl httpUrl = HttpUrl.parse(resourceUrl);
@@ -74,6 +75,11 @@ public void executeRequest(HttpResponder httpRequest, long nativePtr, @NonNull S
7475
.url(resourceUrl)
7576
.tag(resourceUrl.toLowerCase(MapLibreConstants.MAPLIBRE_LOCALE))
7677
.addHeader("User-Agent", userAgentString);
78+
79+
if (dataRange.length() > 0) {
80+
builder.addHeader("Range", dataRange);
81+
}
82+
7783
if (etag.length() > 0) {
7884
builder.addHeader("If-None-Match", etag);
7985
} else if (modified.length() > 0) {

platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/utils/ExampleHttpRequestImpl.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import org.maplibre.android.module.http.HttpRequestImpl
1111
*/
1212
class ExampleHttpRequestImpl : HttpRequest {
1313
override fun executeRequest(httpRequest: HttpResponder, nativePtr: Long, resourceUrl: String,
14-
etag: String, modified: String, offlineUsage: Boolean)
14+
dataRange: String, etag: String, modified: String, offlineUsage: Boolean)
1515
{
1616
// Load all json documents and any pbf ending with a 0.
1717
if (resourceUrl.endsWith(".json") || resourceUrl.endsWith("0.pbf")) {
18-
impl.executeRequest(httpRequest, nativePtr, resourceUrl, etag, modified, offlineUsage)
18+
impl.executeRequest(httpRequest, nativePtr, resourceUrl, dataRange, etag, modified, offlineUsage)
1919
} else {
2020
// All other requests get an instant 404!
2121
httpRequest.onResponse(

platform/android/android.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ target_sources(
6161
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_database.cpp
6262
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_download.cpp
6363
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/online_file_source.cpp
64+
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/$<IF:$<BOOL:${MLN_WITH_PMTILES}>,pmtiles_file_source.cpp,pmtiles_file_source_stub.cpp>
6465
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp
6566
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp
6667
${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/compression.cpp

platform/darwin/src/http_file_source.mm

+8-1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ BOOL isValidMapboxEndpoint(NSURL *url) {
264264
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
265265
forHTTPHeaderField:@"If-Modified-Since"];
266266
}
267+
268+
if (resource.dataRange) {
269+
NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld",
270+
static_cast<long long>(resource.dataRange->first),
271+
static_cast<long long>(resource.dataRange->second)];
272+
[req setValue:rangeHeader forHTTPHeaderField:@"Range"];
273+
}
267274

268275
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
269276

@@ -360,7 +367,7 @@ BOOL isValidMapboxEndpoint(NSURL *url) {
360367
response.etag = std::string([etag UTF8String]);
361368
}
362369

363-
if (responseCode == 200) {
370+
if (responseCode == 200 || responseCode == 206) {
364371
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
365372
} else if (responseCode == 204 || (responseCode == 404 && isTile)) {
366373
response.noContent = true;

platform/default/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ cc_library(
5757
"src/mbgl/storage/offline_database.cpp",
5858
"src/mbgl/storage/offline_download.cpp",
5959
"src/mbgl/storage/online_file_source.cpp",
60+
"src/mbgl/storage/pmtiles_file_source.cpp",
6061
"src/mbgl/storage/sqlite3.cpp",
6162
"src/mbgl/text/bidi.cpp",
6263
"src/mbgl/util/compression.cpp",

platform/default/include/mbgl/storage/local_file_request.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ template <typename>
88
class ActorRef;
99
class FileSourceRequest;
1010

11-
void requestLocalFile(const std::string&, const ActorRef<FileSourceRequest>&);
11+
void requestLocalFile(const std::string& path,
12+
const ActorRef<FileSourceRequest>& req,
13+
const std::optional<std::pair<uint64_t, uint64_t>>& dataRange = std::nullopt);
1214

1315
} // namespace mbgl

platform/default/src/mbgl/storage/file_source_manager.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <mbgl/storage/main_resource_loader.hpp>
66
#include <mbgl/storage/online_file_source.hpp>
77
#include <mbgl/storage/mbtiles_file_source.hpp>
8+
#include <mbgl/storage/pmtiles_file_source.hpp>
89
#include <mbgl/storage/resource_options.hpp>
910

1011
namespace mbgl {
@@ -37,6 +38,11 @@ class DefaultFileSourceManagerImpl final : public FileSourceManager {
3738
return std::make_unique<MBTilesFileSource>(resourceOptions, clientOptions);
3839
});
3940

41+
registerFileSourceFactory(FileSourceType::Pmtiles,
42+
[](const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) {
43+
return std::make_unique<PMTilesFileSource>(resourceOptions, clientOptions);
44+
});
45+
4046
registerFileSourceFactory(FileSourceType::Network,
4147
[](const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) {
4248
return std::make_unique<OnlineFileSource>(resourceOptions, clientOptions);

platform/default/src/mbgl/storage/http_file_source.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ HTTPRequest::HTTPRequest(HTTPFileSource::Impl *context_, Resource resource_, Fil
269269
resource(std::move(resource_)),
270270
callback(std::move(callback_)),
271271
handle(context->getHandle()) {
272+
if (resource.dataRange) {
273+
const std::string header = std::string("Range: bytes=") + std::to_string(resource.dataRange->first) +
274+
std::string("-") + std::to_string(resource.dataRange->second);
275+
headers = curl_slist_append(headers, header.c_str());
276+
}
277+
272278
// If there's already a response, set the correct etags/modified headers to
273279
// make sure we are getting a 304 response if possible. This avoids
274280
// redownloading unchanged data.
@@ -417,7 +423,7 @@ void HTTPRequest::handleResult(CURLcode code) {
417423
long responseCode = 0;
418424
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
419425

420-
if (responseCode == 200) {
426+
if (responseCode == 200 || responseCode == 206) {
421427
if (data) {
422428
response->data = std::move(data);
423429
} else {

platform/default/src/mbgl/storage/local_file_request.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
namespace mbgl {
1313

14-
void requestLocalFile(const std::string& path, const ActorRef<FileSourceRequest>& req) {
14+
void requestLocalFile(const std::string& path,
15+
const ActorRef<FileSourceRequest>& req,
16+
const std::optional<std::pair<uint64_t, uint64_t>>& dataRange) {
1517
Response response;
1618
struct stat buf;
1719
int result = stat(path.c_str(), &buf);
@@ -21,7 +23,7 @@ void requestLocalFile(const std::string& path, const ActorRef<FileSourceRequest>
2123
} else if (result == -1 && errno == ENOENT) {
2224
response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound);
2325
} else {
24-
auto data = util::readFile(path);
26+
auto data = util::readFile(path, dataRange);
2527
if (!data) {
2628
response.error = std::make_unique<Response::Error>(Response::Error::Reason::Other,
2729
std::string("Cannot read file ") + path);

platform/default/src/mbgl/storage/local_file_source.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@ class LocalFileSource::Impl {
2525
: resourceOptions(resourceOptions_.clone()),
2626
clientOptions(clientOptions_.clone()) {}
2727

28-
void request(const std::string& url, const ActorRef<FileSourceRequest>& req) {
29-
if (!acceptsURL(url)) {
28+
void request(const Resource& resource, const ActorRef<FileSourceRequest>& req) {
29+
if (!acceptsURL(resource.url)) {
3030
Response response;
3131
response.error = std::make_unique<Response::Error>(Response::Error::Reason::Other, "Invalid file URL");
3232
req.invoke(&FileSourceRequest::setResponse, response);
3333
return;
3434
}
3535

3636
// Cut off the protocol and prefix with path.
37-
const auto path = mbgl::util::percentDecode(url.substr(std::char_traits<char>::length(util::FILE_PROTOCOL)));
38-
requestLocalFile(path, req);
37+
const auto path = mbgl::util::percentDecode(
38+
resource.url.substr(std::char_traits<char>::length(util::FILE_PROTOCOL)));
39+
requestLocalFile(path, req, resource.dataRange);
3940
}
4041

4142
void setResourceOptions(ResourceOptions options) {
@@ -77,7 +78,7 @@ LocalFileSource::~LocalFileSource() = default;
7778
std::unique_ptr<AsyncRequest> LocalFileSource::request(const Resource& resource, Callback callback) {
7879
auto req = std::make_unique<FileSourceRequest>(std::move(callback));
7980

80-
impl->actor().invoke(&Impl::request, resource.url, req->actor());
81+
impl->actor().invoke(&Impl::request, resource, req->actor());
8182

8283
return req;
8384
}

0 commit comments

Comments
 (0)