Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[google_maps_flutter_platform_interface] add custom tile support #3418

Merged
Merged
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.2.0

* Add TileOverlay support.

## 1.1.0

* Add support for holes in Polygons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
return _channels[mapId];
}

// Keep a collection of id -> TileOverlay.
final Map<int, TileOverlay> _tileOverlays = {};

/// Initializes the platform interface with [id].
///
/// This method is called when the plugin is first initialized.
Expand Down Expand Up @@ -184,6 +187,15 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
LatLng.fromJson(call.arguments['position']),
));
break;
case 'tileOverlay#getTile':
final TileOverlay tileOverlay = _tileOverlays[mapId];
assert(tileOverlay.tileProvider.getTile != null);
final Tile tile = await tileOverlay.tileProvider.getTile(
call.arguments['x'],
call.arguments['y'],
call.arguments['zoom'],
);
return tile.toJson();
default:
throw MissingPluginException();
}
Expand Down Expand Up @@ -281,6 +293,42 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
);
}

/// Updates tile overlay configuration.
///
/// Change listeners are notified once the update has been made on the
/// platform side.
///
/// The returned [Future] completes after listeners have been notified.
@override
Future<void> updateTileOverlays(
TileOverlayUpdates tileOverlayUpdates, {
@required int mapId,
}) {
assert(tileOverlayUpdates != null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to update _tileOverlays here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, also removed setTileOverlays

return channel(mapId).invokeMethod<void>(
'tileOverlays#update',
tileOverlayUpdates.toJson(),
);
}

/// Clears the tile cache so that all tiles will be requested again from the
/// [TileProvider].
///
/// The current tiles from this tile overlay will also be
/// cleared from the map after calling this method. The Google Map SDK maintains a small
/// in-memory cache of tiles. If you want to cache tiles for longer, you
/// should implement an on-disk cache.
@override
Future<void> clearTileCache(
TileOverlayId tileOverlayId, {
@required int mapId,
}) {
return channel(mapId)
.invokeMethod<void>('tileOverlays#clearTileCache', <String, dynamic>{
'tileOverlayId': tileOverlayId.value,
});
}

/// Starts an animated change of the map camera position.
///
/// The returned [Future] completes after the change has been started on the
Expand Down Expand Up @@ -451,6 +499,16 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
return channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
}

/// Sets the `MapGetTileCallback` with mapId.
///
/// `mapId` and `callback` must not be null.
@override
void setTileOverlay(
{@required int mapId, @required TileOverlay tileOverlay}) {
assert(mapId != null && tileOverlay != null);
_tileOverlays[mapId] = tileOverlay;
}

/// This method builds the appropriate platform view where the map
/// can be rendered.
/// The `mapId` is passed as a parameter from the framework on the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import 'package:google_maps_flutter_platform_interface/src/method_channel/method
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

/// Callback method for when a [Tile] is requested from a [TileProvider].
typedef Future<Tile> MapGetTileCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

String tileOverlayIdString, int x, int y, int zoom);

/// The interface that platform-specific implementations of `google_maps_flutter` must extend.
///
/// Avoid `implements` of this interface. Using `implements` makes adding any new
Expand Down Expand Up @@ -115,6 +119,33 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface {
throw UnimplementedError('updateCircles() has not been implemented.');
}

/// Updates tile overlay configuration.
///
/// Change listeners are notified once the update has been made on the
/// platform side.
///
/// The returned [Future] completes after listeners have been notified.
Future<void> updateTileOverlays(
TileOverlayUpdates tileOverlayUpdates, {
@required int mapId,
}) {
throw UnimplementedError('updateTileOverlays() has not been implemented.');
}

/// Clears the tile cache so that all tiles will be requested again from the
cyanglaz marked this conversation as resolved.
Show resolved Hide resolved
/// [TileProvider].
///
/// The current tiles from this tile overlay will also be
/// cleared from the map after calling this method. The Google Maps SDK maintains a small
/// in-memory cache of tiles. If you want to cache tiles for longer, you
/// should implement an on-disk cache.
Future<void> clearTileCache(
TileOverlayId tileOverlayId, {
@required int mapId,
}) {
throw UnimplementedError('clearTileCache() has not been implemented.');
}

/// Starts an animated change of the map camera position.
///
/// The returned [Future] completes after the change has been started on the
Expand Down Expand Up @@ -246,6 +277,14 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface {
throw UnimplementedError('takeSnapshot() has not been implemented.');
}

/// Set the [TileOverlay] for the map.
///
/// The [TileProvider] is called when a [Tile] is requested.
void setTileOverlay(
{@required int mapId, @required TileOverlay tileOverlay}) {
throw UnimplementedError('onGetTile() has not been implemented.');
}

// The following are the 11 possible streams of data from the native side
// into the plugin

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';
import 'package:meta/meta.dart' show immutable;

/// Contains information about a Tile that is returned by a [TileProvider].
@immutable
class Tile {
/// Creates an immutable representation of a [Tile] to draw by [TileProvider].
const Tile(this.width, this.height, this.data);

/// The width of the image encoded by data in logical pixels.
final int width;

/// The height of the image encoded by data in logical pixels.
final int height;

/// A byte array containing the image data.
cyanglaz marked this conversation as resolved.
Show resolved Hide resolved
///
/// The image data format accepted by platforms various based on platforms and versions.
final Uint8List data;

/// Converts this object to JSON.
Map<String, dynamic> toJson() {
final Map<String, dynamic> json = <String, dynamic>{};

void addIfPresent(String fieldName, dynamic value) {
if (value != null) {
json[fieldName] = value;
}
}

addIfPresent('width', width);
addIfPresent('height', height);
addIfPresent('data', data);

return json;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui' show hashValues;
import 'package:flutter/foundation.dart';

import 'types.dart';
import 'package:meta/meta.dart' show immutable, required;

/// Uniquely identifies a [TileOverlay] among [GoogleMap] tile overlays.
@immutable
class TileOverlayId {
/// Creates an immutable identifier for a [TileOverlay].
TileOverlayId(this.value) : assert(value != null);

/// The value of the [TileOverlayId].
final String value;
cyanglaz marked this conversation as resolved.
Show resolved Hide resolved

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is TileOverlayId && other.value == value;
}

@override
int get hashCode => value.hashCode;

@override
String toString() => '${objectRuntimeType(this, 'TileOverlayId')}($value)';
}

/// A set of images which are displayed on top of the base map tiles.
///
/// These tiles may be transparent, allowing you to add features to existing maps.
///
/// ## Tile Coordinates
///
/// Note that the world is projected using the Mercator projection
/// (see [Wikipedia](https://en.wikipedia.org/wiki/Mercator_projection)) with the left (west) side
/// of the map corresponding to -180 degrees of longitude and the right (east) side of the map
/// corresponding to 180 degrees of longitude. To make the map square, the top (north) side of the
/// map corresponds to 85.0511 degrees of latitude and the bottom (south) side of the map
/// corresponds to -85.0511 degrees of latitude. Areas outside this latitude range are not rendered.
///
/// At each zoom level, the map is divided into tiles and only the tiles that overlap the screen are
/// downloaded and rendered. Each tile is square and the map is divided into tiles as follows:
///
/// * At zoom level 0, one tile represents the entire world. The coordinates of that tile are
/// (x, y) = (0, 0).
/// * At zoom level 1, the world is divided into 4 tiles arranged in a 2 x 2 grid.
/// * ...
/// * At zoom level N, the world is divided into 4N tiles arranged in a 2N x 2N grid.
///
/// Note that the minimum zoom level that the camera supports (which can depend on various factors)
/// is GoogleMap.getMinZoomLevel and the maximum zoom level is GoogleMap.getMaxZoomLevel.
///
/// The coordinates of the tiles are measured from the top left (northwest) corner of the map.
/// At zoom level N, the x values of the tile coordinates range from 0 to 2N - 1 and increase from
/// west to east and the y values range from 0 to 2N - 1 and increase from north to south.
///
class TileOverlay {
/// Creates an immutable representation of a [TileOverlay] to draw on [GoogleMap].
const TileOverlay({
@required this.tileOverlayId,
this.fadeIn = true,
this.tileProvider,
this.transparency = 0.0,
this.zIndex,
this.visible = true,
this.tileSize = 256,
}) : assert(transparency >= 0.0 && transparency <= 1.0);

/// Uniquely identifies a [TileOverlay].
final TileOverlayId tileOverlayId;

/// Whether the tiles should fade in. The default is true.
final bool fadeIn;

/// The tile provider to use for this tile overlay.
final TileProvider tileProvider;

/// The transparency of the tile overlay. The default transparency is 0 (opaque).
final double transparency;

/// The tile overlay's zIndex, i.e., the order in which it will be drawn where
/// overlays with larger values are drawn above those with lower values
final int zIndex;

/// The visibility for the tile overlay. The default visibility is true.
final bool visible;

/// Specifies the number of logical pixels (not points) that the returned tile images will prefer
/// to display as. iOS only.
///
/// Defaults to 256, which is the traditional size of Google Maps tiles.
/// As an example, an application developer may wish to provide retina tiles (512 pixel edge length)
/// on retina devices, to keep the same number of tiles per view as the default value of 256
/// would give on a non-retina device.
final int tileSize;

/// Creates a new [TileOverlay] object whose values are the same as this instance,
/// unless overwritten by the specified parameters.
TileOverlay copyWith({
TileOverlayId tileOverlayId,
bool fadeInParam,
double transparencyParam,
int zIndexParam,
bool visibleParam,
int tileSizeParam,
}) {
return TileOverlay(
tileOverlayId: tileOverlayId,
fadeIn: fadeInParam ?? fadeIn,
transparency: transparencyParam ?? transparency,
zIndex: zIndexParam ?? zIndex,
visible: visibleParam ?? visible,
tileSize: tileSizeParam ?? tileSize,
);
}

/// Converts this object to JSON.
Map<String, dynamic> toJson() {
final Map<String, dynamic> json = <String, dynamic>{};

void addIfPresent(String fieldName, dynamic value) {
if (value != null) {
json[fieldName] = value;
}
}

addIfPresent('tileOverlayId', tileOverlayId.value);
addIfPresent('fadeIn', fadeIn);
addIfPresent('transparency', transparency);
addIfPresent('zIndex', zIndex);
addIfPresent('visible', visible);
addIfPresent('tileSize', tileSize);

return json;
}

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is TileOverlay &&
tileOverlayId == other.tileOverlayId &&
fadeIn == other.fadeIn &&
transparency == other.transparency &&
zIndex == other.zIndex &&
visible == other.visible &&
tileSize == other.tileSize;
}

@override
int get hashCode => hashValues(
tileOverlayId, fadeIn, transparency, zIndex, visible, tileSize);
}
Loading