Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pmtiles support #198

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 7.2.0

* added `PmTilesVectorTileProvider` for pmtiles format archives

## 7.1.0

* support vector theme raster layers
Expand Down
62 changes: 59 additions & 3 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
version: "3.4.10"
async:
dependency: transitive
description:
Expand Down Expand Up @@ -41,6 +49,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
executor_lib:
dependency: transitive
description:
Expand Down Expand Up @@ -103,10 +127,10 @@ packages:
dependency: transitive
description:
name: http
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.2.0"
http_parser:
dependency: transitive
description:
Expand All @@ -123,6 +147,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
latlong2:
dependency: "direct main"
description:
Expand Down Expand Up @@ -259,6 +291,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pmtiles:
dependency: transitive
description:
name: pmtiles
sha256: "7e3135c64ec3647e1dd8929fd3c371c8e1c6809b24f1d910d3198a7f41e2068c"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
polylabel:
dependency: transitive
description:
Expand All @@ -267,6 +315,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
proj4dart:
dependency: transitive
description:
Expand Down Expand Up @@ -358,7 +414,7 @@ packages:
path: ".."
relative: true
source: path
version: "7.0.1"
version: "7.1.0"
vector_math:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion lib/src/cache/atlas_image_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'dart:ui';
import 'package:executor_lib/executor_lib.dart';
import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import 'storage_cache.dart';
import 'extensions.dart';
import 'storage_cache.dart';

class AtlasImageCache {
final Theme _theme;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/grid/tile_layer_composer.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import '../style/style.dart';
import 'tile_layer_model.dart';
import 'tile_model.dart';
import '../style/style.dart';

class TileLayerComposer {
List<TileLayerModel> compose(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import 'dart:io';

import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import 'style/style.dart';
import 'tile_offset.dart';
import 'tile_providers.dart';
import 'vector_tile_layer.dart' as vmt;
import 'vector_tile_layer_mode.dart';
import 'style/style.dart';

class VectorTileLayerOptions {
final TileProviders tileProviders;
Expand Down
36 changes: 36 additions & 0 deletions lib/src/provider/memory_vector_tile_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:typed_data';

import '../cache/memory_cache.dart';
import '../tile_identity.dart';
import '../vector_tile_provider.dart';

class MemoryCacheVectorTileProvider extends VectorTileProvider {
final VectorTileProvider delegate;
late final MemoryCache _cache;

@override
int get maximumZoom => delegate.maximumZoom;

@override
int get minimumZoom => delegate.minimumZoom;

MemoryCacheVectorTileProvider(
{required this.delegate, required int maxSizeBytes}) {
_cache = MemoryCache(maxSizeBytes: maxSizeBytes);
}

@override
Future<Uint8List> provide(TileIdentity tile) async {
final key = tile.toCacheKey();
var value = _cache.get(key);
if (value == null) {
value = await delegate.provide(tile);
_cache.put(key, value);
}
return value;
}
}

extension _TileCacheKey on TileIdentity {
String toCacheKey() => '$z.$x.$y';
}
94 changes: 94 additions & 0 deletions lib/src/provider/network_vector_tile_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'dart:typed_data';

import 'package:http/http.dart';
import 'package:http/retry.dart';

import '../provider_exception.dart';
import '../tile_identity.dart';
import '../vector_tile_provider.dart';

class NetworkVectorTileProvider extends VectorTileProvider {
@override
final TileProviderType type;
final _UrlProvider _urlProvider;
final Map<String, String>? httpHeaders;

@override
final int maximumZoom;

@override
final int minimumZoom;

/// [urlTemplate] the URL template, e.g. `'https://tiles.stadiamaps.com/data/openmaptiles/{z}/{x}/{y}.pbf?api_key=$apiKey'`
/// [httpHeaders] HTTP headers to include in requests, suitable for passing
/// `Authentication` header instead of an `api_key` in the URL template
/// [maximumZoom] the maximum zoom supported by the tile provider, not to be
/// confused with the maximum zoom of the map widget. The map widget will
/// automatically use vector tiles from lower zoom levels once the maximum
/// supported by this provider is reached.
NetworkVectorTileProvider(
{required String urlTemplate,
this.type = TileProviderType.vector,
this.httpHeaders,
this.maximumZoom = 16,
this.minimumZoom = 1})
: _urlProvider = _UrlProvider(urlTemplate);

@override
Future<Uint8List> provide(TileIdentity tile) async {
_checkTile(tile);
final uri = Uri.parse(_urlProvider.url(tile));
final client = RetryClient(Client());
try {
final response = await client.get(uri, headers: httpHeaders);
if (response.statusCode == 200) {
return response.bodyBytes;
}
final logSafeUri = uri.toString().split(RegExp(r'\?')).first;
throw ProviderException(
message:
'Cannot retrieve tile: HTTP ${response.statusCode}: $logSafeUri ${response.body}',
statusCode: response.statusCode,
retryable: _isRetryable(response.statusCode)
? Retryable.retry
: Retryable.none);
} on ClientException catch (e) {
throw ProviderException(message: e.message, retryable: Retryable.retry);
} finally {
client.close();
}
}

void _checkTile(TileIdentity tile) {
if (tile.z > maximumZoom || tile.z < minimumZoom || !tile.isValid()) {
throw ProviderException(
message: 'Invalid tile coordinates $tile',
retryable: Retryable.none,
statusCode: 400);
}
}

_isRetryable(int statusCode) => statusCode == 503 || statusCode == 408;
}

class _UrlProvider {
final String urlTemplate;

_UrlProvider(this.urlTemplate);

String url(TileIdentity identity) {
return urlTemplate.replaceAllMapped(RegExp(r'\{(x|y|z)\}'), (match) {
switch (match.group(1)) {
case 'x':
return identity.x.toInt().toString();
case 'y':
return identity.y.toInt().toString();
case 'z':
return identity.z.toInt().toString();
default:
throw Exception(
'unexpected url template: $urlTemplate - token ${match.group(1)} is not supported');
}
});
}
}
58 changes: 58 additions & 0 deletions lib/src/provider/pmtiles_vector_tile_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'dart:typed_data';

import 'package:pmtiles/pmtiles.dart';

import '../../vector_map_tiles.dart';
import '../provider_exception.dart';

/// A network tile provider that uses HTTP range requests with
/// a [pmtiles archive](https://docs.protomaps.com/pmtiles/).
/// A [PmTilesProvider] is stateful since it must load the pmtiles
/// index before loading any tiles.
///
/// Instances of [PmTilesArchive] should
/// be long-lived to reduce network calls, and [PmTilesArchive.close] must be called
/// to release resources when it is no longer needed.
class PmTilesVectorTileProvider extends VectorTileProvider {
PmTilesArchive archive;
@override
final TileProviderType type;

@override
final int maximumZoom;
@override
final int minimumZoom;

PmTilesVectorTileProvider(
{required this.archive,
required this.type,
required this.minimumZoom,
required this.maximumZoom});

@override
Future<Uint8List> provide(TileIdentity tile) async {
_checkZoom(archive, tile);
final tileId = ZXY(tile.z, tile.x, tile.y).toTileId();
try {
final t = await archive.tile(tileId);
return Uint8List.fromList(t.bytes());
} catch (e) {
if (e is TileNotFoundException) {
throw ProviderException(
message: 'not found: $tile',
retryable: Retryable.none,
statusCode: 404);
}
rethrow;
}
}

void _checkZoom(PmTilesArchive archive, TileIdentity tile) {
if (tile.z < archive.header.minZoom || tile.z > archive.header.maxZoom) {
throw ProviderException(
message:
'${tile.z} must be in [${archive.header.minZoom}..${archive.header.maxZoom}]',
retryable: Retryable.none);
}
}
}
1 change: 1 addition & 0 deletions lib/src/style/style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:http/http.dart';
import 'package:latlong2/latlong.dart';
import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import '../provider/network_vector_tile_provider.dart';
import '../tile_providers.dart';
import '../vector_tile_provider.dart';
import 'uri_mapper.dart';
Expand Down
4 changes: 2 additions & 2 deletions lib/src/vector_tile_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import 'dart:io';

import 'package:flutter/material.dart' hide Theme;
import 'package:flutter_map/flutter_map.dart';
import 'style/style.dart';
import 'vector_tile_layer_mode.dart';
import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import 'extensions.dart';
import 'grid/grid_layer.dart';
import 'options.dart';
import 'style/style.dart';
import 'tile_offset.dart';
import 'tile_providers.dart';
import 'vector_tile_layer_mode.dart';
import 'vector_tile_provider.dart';

/// A widget for a vector tile layer, to be used as a child
Expand Down
Loading
Loading