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

Commit

Permalink
[google_maps_flutter] Take snapshot of map (#2607)
Browse files Browse the repository at this point in the history
  • Loading branch information
miyakeryo committed Mar 24, 2020
1 parent 899744f commit 03182d2
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 2 deletions.
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ Luigi Agosti <luigi@tengio.com>
Quentin Le Guennec <quentin@tengio.com>
Koushik Ravikumar <koushik@tengio.com>
Nissim Dsilva <nissim@tengio.com>
Giancarlo Rocha <giancarloiff@gmail.com>
Giancarlo Rocha <giancarloiff@gmail.com>
Ryo Miyake <ryo@miyake.id>
4 changes: 4 additions & 0 deletions packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.25+1

* Add takeSnapshot that takes a snapshot of the map.

## 0.5.25

* Add an optional param `mipmaps` for `BitmapDescriptor.fromAssetImage`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
Expand All @@ -27,6 +28,7 @@
import androidx.lifecycle.LifecycleOwner;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.OnMapReadyCallback;
Expand All @@ -44,6 +46,7 @@
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformView;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -276,6 +279,26 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
}
break;
}
case "map#takeSnapshot":
{
if (googleMap != null) {
final MethodChannel.Result _result = result;
googleMap.snapshot(
new SnapshotReadyCallback() {
@Override
public void onSnapshotReady(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
bitmap.recycle();
_result.success(byteArray);
}
});
} else {
result.error("GoogleMap uninitialized", "takeSnapshot", null);
}
break;
}
case "camera#move":
{
final CameraUpdate cameraUpdate =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'place_marker.dart';
import 'place_polygon.dart';
import 'place_polyline.dart';
import 'scrolling_map.dart';
import 'snapshot.dart';

final List<Page> _allPages = <Page>[
MapUiPage(),
Expand All @@ -32,6 +33,7 @@ final List<Page> _allPages = <Page>[
PlacePolygonPage(),
PlaceCirclePage(),
PaddingPage(),
SnapshotPage(),
];

class MapsDemo extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2020 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.

// ignore_for_file: public_member_api_docs

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

import 'page.dart';

const CameraPosition _kInitialPosition =
CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);

class SnapshotPage extends Page {
SnapshotPage()
: super(const Icon(Icons.camera_alt), 'Take a snapshot of the map');

@override
Widget build(BuildContext context) {
return _SnapshotBody();
}
}

class _SnapshotBody extends StatefulWidget {
@override
_SnapshotBodyState createState() => _SnapshotBodyState();
}

class _SnapshotBodyState extends State<_SnapshotBody> {
GoogleMapController _mapController;
Uint8List _imageBytes;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: 180,
child: GoogleMap(
onMapCreated: onMapCreated,
initialCameraPosition: _kInitialPosition,
),
),
FlatButton(
child: Text('Take a snapshot'),
onPressed: () async {
final imageBytes = await _mapController?.takeSnapshot();
setState(() {
_imageBytes = imageBytes;
});
},
),
Container(
decoration: BoxDecoration(color: Colors.blueGrey[50]),
height: 180,
child: _imageBytes != null ? Image.memory(_imageBytes) : null,
),
],
),
);
}

void onMapCreated(GoogleMapController controller) {
_mapController = controller;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. 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:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

Expand Down Expand Up @@ -63,4 +64,8 @@ class GoogleMapInspector {
Future<bool> isBuildingsEnabled() async {
return await _channel.invokeMethod<bool>('map#isBuildingsEnabled');
}

Future<Uint8List> takeSnapshot() async {
return await _channel.invokeMethod<Uint8List>('map#takeSnapshot');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:e2e/e2e.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:e2e/e2e.dart';

import 'google_map_inspector.dart';

Expand Down Expand Up @@ -838,4 +839,30 @@ void main() {
// ignore: invalid_use_of_visible_for_testing_member
expect(scaled.toJson()[2], 2);
});

testWidgets('testTakeSnapshot', (WidgetTester tester) async {
Completer<GoogleMapInspector> inspectorCompleter =
Completer<GoogleMapInspector>();

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
initialCameraPosition: _kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
final GoogleMapInspector inspector =
// ignore: invalid_use_of_visible_for_testing_member
GoogleMapInspector(controller.channel);
inspectorCompleter.complete(inspector);
},
),
),
);

await tester.pumpAndSettle(const Duration(seconds: 3));

final GoogleMapInspector inspector = await inspectorCompleter.future;
final Uint8List bytes = await inspector.takeSnapshot();
expect(bytes?.isNotEmpty, true);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,22 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
}
} else if ([call.method isEqualToString:@"map#waitForMap"]) {
result(nil);
} else if ([call.method isEqualToString:@"map#takeSnapshot"]) {
if (_mapView != nil) {
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat defaultFormat];
format.scale = [[UIScreen mainScreen] scale];
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:_mapView.frame.size format:format];

UIImage* image = [renderer imageWithActions:^(UIGraphicsImageRendererContext* context) {
[_mapView.layer renderInContext:context.CGContext];
}];
result([FlutterStandardTypedData typedDataWithBytes:UIImagePNGRepresentation(image)]);
} else {
result([FlutterError errorWithCode:@"GoogleMap uninitialized"
message:@"takeSnapshot called prior to map initialization"
details:nil]);
}
} else if ([call.method isEqualToString:@"markers#update"]) {
id markersToAdd = call.arguments[@"markersToAdd"];
if ([markersToAdd isKindOfClass:[NSArray class]]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,9 @@ class GoogleMapController {
await channel.invokeMethod<double>('map#getZoomLevel');
return zoomLevel;
}

/// Returns the image bytes of the map
Future<Uint8List> takeSnapshot() async {
return await channel.invokeMethod<Uint8List>('map#takeSnapshot');
}
}

0 comments on commit 03182d2

Please sign in to comment.