Using SVG markers in google_maps_flutter Flutter plugin
Asked Answered
U

6

11

Is it possible to use SVG paths to create a marker with the google_maps_flutter plugin? I know you can use .png files by using:

icon: BitmapDescriptor.fromAsset("images/myFile.png")

How about SVG paths?

Thanks

Underslung answered 12/4, 2019 at 16:1 Comment(0)
B
30

This can be achieved using the flutter_svg package.

import 'dart:ui' as ui; // imported as ui to prevent conflict between ui.Image and the Image widget
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

Future<BitmapDescriptor> _bitmapDescriptorFromSvgAsset(BuildContext context, String assetName) async {
    // Read SVG file as String
    String svgString = await DefaultAssetBundle.of(context).loadString(assetName);
    // Create DrawableRoot from SVG String
    DrawableRoot svgDrawableRoot = await svg.fromSvgString(svgString, null);

    // toPicture() and toImage() don't seem to be pixel ratio aware, so we calculate the actual sizes here
    MediaQueryData queryData = MediaQuery.of(context);
    double devicePixelRatio = queryData.devicePixelRatio;
    double width = 32 * devicePixelRatio; // where 32 is your SVG's original width
    double height = 32 * devicePixelRatio; // same thing

    // Convert to ui.Picture
    ui.Picture picture = svgDrawableRoot.toPicture(size: Size(width, height));

    // Convert to ui.Image. toImage() takes width and height as parameters
    // you need to find the best size to suit your needs and take into account the
    // screen DPI
    ui.Image image = await picture.toImage(width, height);
    ByteData bytes = await image.toByteData(format: ui.ImageByteFormat.png);
    return BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
}

You can then use the BitmapDescriptor to create a Marker as usual:

BitmapDescriptor bitmapDescriptor = await _bitmapDescriptorFromSvgAsset(context, 'assets/images/someimage.svg');
Marker marker = Marker(markerId: MarkerId('someId'), icon: bitmapDescriptor, position: LatLng(someLatitude, someLongitude));
Barge answered 22/8, 2019 at 12:54 Comment(3)
Awesome solution! One thing to take into account tho is that markers loaded from bytes are waay slower to work with (12x slower) even once loaded due to a missing optimization in the google_maps_flutter plugin. I created an issue for this problem, github.com/flutter/flutter/issues/41731 feel free to upvote it ;)Supination
DrawableRoot not foundGrasping
DrawableRoot is removed github.com/dnfield/flutter_svg/issues/916Frenchpolish
P
18

With the latest version of flutter_svg (2.0.1 in my case) you can achieve it in that way:

class BitmapDescriptorHelper {
  
  static Future<BitmapDescriptor> getBitmapDescriptorFromSvgAsset(
    String assetName, [
    Size size = const Size(48, 48),
  ]) async {
    final pictureInfo = await vg.loadPicture(SvgAssetLoader(assetName), null);

    double devicePixelRatio = ui.window.devicePixelRatio;
    int width = (size.width * devicePixelRatio).toInt();
    int height = (size.height * devicePixelRatio).toInt();

    final scaleFactor = math.min(
      width / pictureInfo.size.width,
      height / pictureInfo.size.height,
    );

    final recorder = ui.PictureRecorder();

    ui.Canvas(recorder)
      ..scale(scaleFactor)
      ..drawPicture(pictureInfo.picture);

    final rasterPicture = recorder.endRecording();

    final image = rasterPicture.toImageSync(width, height);
    final bytes = (await image.toByteData(format: ui.ImageByteFormat.png))!;

    return BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
  }
}
Phobe answered 20/2, 2023 at 12:17 Comment(3)
Thanks a lot! This is a great approach and works much better than the algorithm listed in ReadMe. This fixed my marker scaling issue! Should be marked as the best answer for 2.0.0+ flutter_svg version :)Cuvette
This is a great answer. It scaled the SVG very well, but there was a slight blurriness (just a hint). Is there a work around the blurriness?. Pls note: I'm using the approach to save the png into the user device.Tague
Now that ui.window is deprecated you should use ui.PlatformDispatcher.instance.views.first.devicePixelRatio.Auden
G
4

Here are few options:

  • getBitmapDescriptorFromSvgAsset - svg from assets
  • getBitmapDescriptorFromSvgString - svg as string from db/remote/whatever
class BitmapDescriptorHelper{
  static Future<BitmapDescriptor> getBitmapDescriptorFromSvgAsset(
      BuildContext context, String svgAssetLink) async {
    final svgImage = await _getSvgImageFromAssets(context, svgAssetLink);
    final sizedSvgImage = await _getSizedSvgImage(svgImage);

    final pngSizedBytes = await sizedSvgImage.toByteData(format: ui.ImageByteFormat.png);
    final unit8List = pngSizedBytes.buffer.asUint8List();
    return BitmapDescriptor.fromBytes(unit8List);
  }

  static Future<BitmapDescriptor> getBitmapDescriptorFromSvgString(String svgString) async {
    final svgImage = await _getSvgImageFromString(svgString);
    final sizedSvgImage = await _getSizedSvgImage(svgImage);

    final pngSizedBytes = await sizedSvgImage.toByteData(format: ui.ImageByteFormat.png);
    final unit8List = pngSizedBytes.buffer.asUint8List();
    return BitmapDescriptor.fromBytes(unit8List);
  }

  static Future<ui.Image> _getSvgImageFromAssets(BuildContext context, String svgAssertLink) async {
    String svgString = await DefaultAssetBundle.of(context).loadString(svgAssertLink);
    DrawableRoot drawableRoot = svg.fromSvgString(svgString, null);
    ui.Picture picture = drawableRoot.toPicture();
    ui.Image image = await picture.toImage(
        drawableRoot.viewport.width.toInt(), drawableRoot.viewport.height.toInt());
    return image;
  }

  static Future<ui.Image> _getSvgImageFromString(String svgString) async {
    DrawableRoot drawableRoot = svg.fromSvgString(svgString, null);
    ui.Picture picture = drawableRoot.toPicture();
    ui.Image image = await picture.toImage(
        drawableRoot.viewport.width.toInt(), drawableRoot.viewport.height.toInt());
    return image;
  }

  static Future<ui.Image> _getSizedSvgImage(ui.Image svgImage) async {
    final size = 50 * ui.window.devicePixelRatio;
    final width = size;
    final height = width;

    final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(pictureRecorder);
    final Rect svgRect =
        Rect.fromLTRB(0.0, 0.0, svgImage.width.toDouble(), svgImage.height.toDouble());
    final Rect sizedRect = Rect.fromLTRB(0.0, 0.0, width, height); // owr size here
    canvas.drawImageRect(svgImage, svgRect, sizedRect, Paint());
    return await pictureRecorder.endRecording().toImage(width.toInt(), height.toInt());
  }
}

from your widget class:

      final icon = await BitmapDescriptorHelper.getBitmapDescriptorFromSvgAsset(
                                      context, "assets/icons/some_svg_icon.svg");

      final Marker marker = Marker(
        icon: icon,
        ...
      );

enjoy :)

Gisele answered 23/8, 2019 at 11:11 Comment(2)
error: Undefined name 'ui'. (undefined_identifier... and a 3rd party package disclaimer missing?Argentina
Don't Forget the necessary import for this code => import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:flutter/material.dart'; import 'dart:ui' as ui; import 'dart:async'; import 'package:flutter_svg/flutter_svg.dart';Dissentient
Q
1

Here is my solution:

  1. Add these helper functions:
String busSvg() {
  return '''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<text x="40" y="40" font-size="40" text-anchor="middle" fill="black">🚌</text>
</svg>''';
}

Future<BitmapDescriptor> getSvgIcon() async {
  String svgString = busSvg();

  final PictureInfo pictureInfo =
      await vg.loadPicture(SvgStringLoader(svgString), null);

  final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);
  canvas.drawPicture(pictureInfo.picture);
  final ui.Image image = await pictureRecorder.endRecording().toImage(100, 100);
  final ByteData? byteData =
      await image.toByteData(format: ui.ImageByteFormat.png);
  final Uint8List uint8list = byteData!.buffer.asUint8List();

  return BitmapDescriptor.fromBytes(uint8list);
}
  1. And use it in your GoogleMap's Marker like this:
Marker(
    icon: await getSvgIcon(),
    markerId:...
)
Questionable answered 26/12, 2023 at 3:54 Comment(0)
B
0

Taking from all of the answer above this is what's working for me with the latest version of flutter_svg (2.0.10+1) :

1.Add the following import

import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter_svg flutter_svg.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

2.Add this svgToBitmap function to your code

Future<BitmapDescriptor> svgToBitmap(String asset) async {
  final String svgString = await rootBundle.loadString(asset);
  final fsvg.PictureInfo pictureInfo =
      await fsvg.vg.loadPicture(fsvg.SvgStringLoader(svgString), null);
  final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);
  canvas.drawPicture(pictureInfo.picture);
  final ui.Image image =
      await pictureRecorder.endRecording().toImage(100, 100);
  final ByteData? byteData =
      await image.toByteData(format: ui.ImageByteFormat.png);
  final Uint8List uint8list = byteData!.buffer.asUint8List();
  return BitmapDescriptor.fromBytes(uint8list);
}

3.To use it just call and await the function in the GoogleMap Marker

Marker(
icon: await svgToBitmap(YourSVGAssetPath),
...
)
Bedcover answered 2/8 at 8:24 Comment(0)
L
-2

just checked out the docs for BitmapDescriptor class You can try the fromAssetImage() class method

https://pub.dev/documentation/google_maps_flutter/latest/google_maps_flutter/BitmapDescriptor/fromAssetImage.html

Lagos answered 17/5, 2019 at 4:44 Comment(3)
fromAssetImage() does not accept SVG files, only bitmap images.Barge
and the link is brokenArgentina
Invalid link. 404 Not foundAnagoge

© 2022 - 2024 — McMap. All rights reserved.