Skip to content

Commit

Permalink
[google_maps_flutter_platform_interface] add custom tile support (flu…
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Yang authored Jan 22, 2021
1 parent c923ef8 commit 6fa2ead
Show file tree
Hide file tree
Showing 15 changed files with 724 additions and 4 deletions.
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 @@ -12,6 +12,8 @@ import 'package:flutter/gestures.dart';

import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:stream_transform/stream_transform.dart';
import '../types/tile_overlay_updates.dart';
import '../types/utils/tile_overlay.dart';

/// An implementation of [GoogleMapsFlutterPlatform] that uses [MethodChannel] to communicate with the native code.
///
Expand All @@ -33,6 +35,9 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
return _channels[mapId];
}

// Keep a collection of mapId to a map of TileOverlays.
final Map<int, Map<TileOverlayId, TileOverlay>> _tileOverlays = {};

/// Initializes the platform interface with [id].
///
/// This method is called when the plugin is first initialized.
Expand Down Expand Up @@ -184,6 +189,18 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform {
LatLng.fromJson(call.arguments['position']),
));
break;
case 'tileOverlay#getTile':
final Map<TileOverlayId, TileOverlay> tileOverlaysForThisMap =
_tileOverlays[mapId];
final String tileOverlayId = call.arguments['tileOverlayId'];
final TileOverlay tileOverlay = tileOverlaysForThisMap[tileOverlayId];
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 +298,50 @@ 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.
///
/// If `newTileOverlays` is null, all the [TileOverlays] are removed for the Map with `mapId`.
@override
Future<void> updateTileOverlays({
Set<TileOverlay> newTileOverlays,
@required int mapId,
}) {
final Map<TileOverlayId, TileOverlay> currentTileOverlays =
_tileOverlays[mapId];
Set<TileOverlay> previousSet =
currentTileOverlays != null ? currentTileOverlays.values.toSet() : null;
final TileOverlayUpdates updates =
TileOverlayUpdates.from(previousSet, newTileOverlays);
_tileOverlays[mapId] = keyTileOverlayId(newTileOverlays);
return channel(mapId).invokeMethod<void>(
'tileOverlays#update',
updates.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
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,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({
Set<TileOverlay> newTileOverlays,
@required int mapId,
}) {
throw UnimplementedError('updateTileOverlays() has not been implemented.');
}

/// 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 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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.
///
/// The image data format must be natively supported for decoding by the platform.
/// e.g on Android it can only be one of the [supported image formats for decoding](https://developer.android.com/guide/topics/media/media-formats#image-formats).
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;

@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

0 comments on commit 6fa2ead

Please sign in to comment.