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

Cannot customize marker with base64 png url #49

Open
yiannis-spyridakis opened this issue Sep 30, 2024 · 2 comments
Open

Cannot customize marker with base64 png url #49

yiannis-spyridakis opened this issue Sep 30, 2024 · 2 comments
Labels

Comments

@yiannis-spyridakis
Copy link

yiannis-spyridakis commented Sep 30, 2024

Bug Report

I am adding markers with code like this:

const marker: Marker = {
    coordinate: gasStation.location,
    iconUrl: 'data:image/png;base64,...', // Base64 png url, tested and working with the js api, cordova plugin
    iconSize: {
        width: 38,
        height: 48
    },
}
const markerId = await nativeMap.addMarker(marker);

The image is ignored, I just get the standard marker icon.

Is this a known missing feature? By investigating, I was able to find some discussions regarding base64 svg icons but no direct confirmation that data urls are unsupported.

Can you please confirm?

Plugin(s)

@capacitor/google-maps

Capacitor Version

Latest Dependencies:

  @capacitor/cli: 6.1.2
  @capacitor/core: 6.1.2
  @capacitor/android: 6.1.2
  @capacitor/ios: 6.1.2

Installed Dependencies:

  @capacitor/cli: 6.1.2
  @capacitor/core: 6.1.2
  @capacitor/android: 6.1.2
  @capacitor/ios: 6.1.2

[success] iOS looking great! 👌
[success] Android looking great! 👌

Platform(s)

Android
IOS? (only tested on Android so far)

Current Behavior

Data urls are ignored when adding Markers.

Expected Behavior

A workaround or confirmation that this is expected.

Other Technical Details

The data url is exported from an HTML canvas like so:

// Export to base64 string
const ret = canvas.toDataURL('image/png');
@ionitron-bot ionitron-bot bot added the triage label Sep 30, 2024
@yiannis-spyridakis
Copy link
Author

Hi,
For Android I have confirmed that the plugin currently doesn't support data urls. It either opens a url starting with https:// or it looks for a sub-path under public:

(code in CapacitorGoogleMap.buildMarker)

  var stream: InputStream? = null
  if (marker.iconUrl!!.startsWith("https:")) {
      stream = URL(marker.iconUrl).openConnection().getInputStream()
  } else {
      stream = this.delegate.context.assets.open("public/${marker.iconUrl}")
  }
  var bitmap = BitmapFactory.decodeStream(stream)
  this.markerIcons[marker.iconUrl!!] = bitmap
  markerOptions.icon(getResizedIcon(bitmap, marker))

This works for me:

  var stream: InputStream? = null
  if (marker.iconUrl!!.startsWith("data:")) {
      // Extract the base64 part for the data URL
      val base64Data = marker.iconUrl!!.split(",")[1]
  
      // Decode the base64 string into a byte array
      val decodedBytes = Base64.decode(base64Data, Base64.DEFAULT)
  
      // Convert the byte array to an InputStream
      stream = ByteArrayInputStream(decodedBytes)
  
  }
  else if (marker.iconUrl!!.startsWith("https:")) {
      stream = URL(marker.iconUrl).openConnection().getInputStream()
  } else {
      stream = this.delegate.context.assets.open("public/${marker.iconUrl}")
  }
  val bitmap = BitmapFactory.decodeStream(stream)
  this.markerIcons[marker.iconUrl!!] = bitmap
  markerOptions.icon(getResizedIcon(bitmap, marker))

Please let me know if you are interested in accepting contributions. Is the plugin actively maintained?

@yiannis-spyridakis
Copy link
Author

For completeness, as expected the behavior is the same on IOS.

The relevant function is Map.buildMarker

The relevant code section is here:

        // cache and reuse marker icon uiimages
        if let iconUrl = marker.iconUrl {
            if let iconImage = self.markerIcons[iconUrl] {
                newMarker.icon = getResizedIcon(iconImage, marker)
            } else {
                if iconUrl.starts(with: "https:") {
                    if let url = URL(string: iconUrl) {
                        URLSession.shared.dataTask(with: url) { (data, _, _) in
                            DispatchQueue.main.async {
                                if let data = data, let iconImage = UIImage(data: data) {
                                    self.markerIcons[iconUrl] = iconImage
                                    newMarker.icon = getResizedIcon(iconImage, marker)
                                }
                            }
                        }.resume()
                    }
                } else if let iconImage = UIImage(named: "public/\(iconUrl)") {
                    self.markerIcons[iconUrl] = iconImage
                    newMarker.icon = getResizedIcon(iconImage, marker)

And this fix works for me:

       if let iconUrl = marker.iconUrl {
            if let iconImage = self.markerIcons[iconUrl] {
                newMarker.icon = getResizedIcon(iconImage, marker)
            } else {
                if iconUrl.starts(with: "data") {
                  // Handle base64 encoded image
                  if let dataRange = iconUrl.range(of: "base64,") {
                    let base64String = String(iconUrl[dataRange.upperBound...])
                    if let imageData = Data(base64Encoded: base64String, options: .ignoreUnknownCharacters),
                      let iconImage = UIImage(data: imageData) {
                        self.markerIcons[iconUrl] = iconImage
                        newMarker.icon = getResizedIcon(iconImage, marker)
                    } else {
                      print("CapacitorGoogleMaps Wanring: could not decode base64 image. Using default marker icon.")
                    }
                  } else {
                    print("CapacitorGoogleMaps Warning: invalid data URL format. Using default marker icon.")
                  }
                } else if iconUrl.starts(with: "https:") {
                    if let url = URL(string: iconUrl) {
                        URLSession.shared.dataTask(with: url) { (data, _, _) in
                            DispatchQueue.main.async {
                                if let data = data, let iconImage = UIImage(data: data) {
                                    self.markerIcons[iconUrl] = iconImage
                                    newMarker.icon = getResizedIcon(iconImage, marker)
                                }
                            }
                        }.resume()
                    }
                } else if let iconImage = UIImage(named: "public/\(iconUrl)") {
                    self.markerIcons[iconUrl] = iconImage
                    newMarker.icon = getResizedIcon(iconImage, marker)
                }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant