KmlNode - WriteToAsync() - doesnt included referenced image files

1253
4
Jump to solution
01-29-2021 08:28 AM
justinfernandes
New Contributor II

I am reading in a KmlGroundOverlay that references a tiff file. 

I write the KmlGroundOverlay to a a Stream.

I save the Stream to a file.

Everything works as expected except the referenced tiff file is not included in the KMZ.  

Is this because I'm importing into the incorrect place when i first read the file?  

My  KmlGroundOverlays are displayed as excepted over my basemaps in my app.

 

 

private async Task LoadAndSaveKmz(StorageFile file, StorageFolder exportFolder)
{
  var localFile = await 
      ApplicationData.Current.TemporaryFolder.CreateFileAsync(
      file.Name, Windows.Storage.CreationCollisionOption.ReplaceExisting);
  await file.CopyAndReplaceAsync(localFile);
  source = new Uri(localFile.Path);
  KmlLayer layer = new KmlLayer(source);
  await layer.LoadAsync();
  //get access to kml node (omitting that code here)
  KmlNode node = GetNodeFromLayer(layer);
  string filename = "node.kmz";
  StorageFile file = await exportFolder.CreateFileAsync(filename, 
      CreationCollisionOption.ReplaceExisting);
  using (Stream stream = await file.OpenStreamForWriteAsync())
  {
     await node.WriteToAsync(stream);
  }
}

 

 

Tags (2)
0 Kudos
1 Solution

Accepted Solutions
justinfernandes
New Contributor II

changing versions did not help.

 

but i eventually solved the problem by reading the image file into a WriteableBitmap, writing that bitmap to my ApplicatoinData.Current.TemporaryFolder and then creating KmlGroundOverlay and writing that to the same folder.

code snippet:

public async Task ExportGroundOverlay(StorageFolder folder, WriteableBitmap image, KmlPlacemark placemark, string filename)
        {
            try
            {
                //dont know why we need to flip these! but we do for change detection ground overlays.
                image = image.Flip(WriteableBitmapExtensions.FlipMode.Horizontal);

                foreach (char c in System.IO.Path.GetInvalidFileNameChars())
                {
                    filename = filename.Replace(c, '_');
                }

                StorageFile imageFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(filename + ".jpg", CreationCollisionOption.ReplaceExisting);
                using (IRandomAccessStream stream = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
                {
                    await image.ToStream(stream, BitmapEncoder.JpegEncoderId);
                }

                KmlIcon icon = new KmlIcon(new Uri(imageFile.Path));
                
                KmlGroundOverlay groundOverlay = new KmlGroundOverlay(placemark.Geometries[0].Geometry, icon);
                StorageFile tempFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(filename + ".kmz", CreationCollisionOption.ReplaceExisting);
                using (Stream stream = await tempFile.OpenStreamForWriteAsync())
                {
                    await groundOverlay.WriteToAsync(stream);
                }


                StorageFile file = await folder.CreateFileAsync(filename + ".kmz", CreationCollisionOption.ReplaceExisting);
                await tempFile.CopyAndReplaceAsync(file);
            }
            catch (Exception e)
            {
                Debug.WriteLine("ExportGroundOverlay() - exception = " + e.ToString());
            }
        }

View solution in original post

0 Kudos
4 Replies
MatveiStefarov
Esri Contributor

Hello Justin!

I'm guessing that the GroundOverlay references a remote file (i.e. GroundOverlay/Icon/href is an HTTP/HTTPS URL rather than a file path).  This would explain why the TIFF is not being saved in the KMZ archive.  When a KMLNode is saved, only the URLs to remote files are saved -- the remote content does not downloaded and does not get zipped up. You can find more details about the saving process in the remarks of KmlNode.SaveAsAsync(...) method.

As a workaround, you can edit the KMLGroundOverlay before saving. First get its Icon, then download the Uri to a temporary file, create a new KMLIcon with a path to the temporary file, and finally replace the overlay Icon. Here's an example of how you could do it:

 

// Ensures that GroundOverlays's image will be saved as an embedded file
private async Task DownloadOverlayImageAsync(KmlGroundOverlay original)
{
    var icon = original.Icon;
    if (icon == null || icon.Uri == null || !icon.Uri.IsAbsoluteUri)
        return; // Skip invalid and already-local overlays

    // Create a temporary file to save to.
    // Filename is based on the URL, with any invalid characters replaced by "_".
    var imageFileName = Regex.Replace(icon.Uri.LocalPath, @"[^a-zA-Z0-9_\-\.]", "_");
    var temp = ApplicationData.Current.TemporaryFolder;
    var tempSubfolder = await temp.CreateFolderAsync("KmlGroundOverlayDownloads", CreationCollisionOption.OpenIfExists);
    var tempFile = await tempSubfolder.CreateFileAsync(imageFileName, CreationCollisionOption.GenerateUniqueName);

    // Download using Runtime's HTTP handler (to integrate with AuthenticationManager)
    var client = new System.Net.Http.HttpClient(new ArcGISHttpClientHandler());
    using (var stream = await client.GetStreamAsync(icon.Uri))
    {
        using (var fileStream = await tempFile.OpenStreamForWriteAsync())
        {
            await stream.CopyToAsync(fileStream);
        }
    }

    // Replace existing remote-URL icon with new local-URL icon
    var newIcon = new KmlIcon(new Uri(tempFile.Path));
    original.Icon = newIcon;
}

 

justinfernandes
New Contributor II

Thanks for the detailed response! Lots of good stuff in there.

 

Unfortunately that is not my case.  The image file is within a KMZ that was initially loaded by my App.  The entire KMZ is copied to my Temporary Folder within my apps "sandbox".  

 

I suppose i could manually unzip that kmz and move that file to somewhere in my local sandbox, and then specify a relative to path to that location.  

So my main question becomes:  How should i define the relative path within my new kml?  where should the path be relative to such that WriteToSync() works successfully?

 

Also, is it possible to specify a path such that it finds a a file within another kmz (to prevent need for unzipping).

thanks!

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

@justinfernandes  One more thing to check - which version are you using? If it's not v100.10, can you update to 100.10 and retest?

0 Kudos
justinfernandes
New Contributor II

changing versions did not help.

 

but i eventually solved the problem by reading the image file into a WriteableBitmap, writing that bitmap to my ApplicatoinData.Current.TemporaryFolder and then creating KmlGroundOverlay and writing that to the same folder.

code snippet:

public async Task ExportGroundOverlay(StorageFolder folder, WriteableBitmap image, KmlPlacemark placemark, string filename)
        {
            try
            {
                //dont know why we need to flip these! but we do for change detection ground overlays.
                image = image.Flip(WriteableBitmapExtensions.FlipMode.Horizontal);

                foreach (char c in System.IO.Path.GetInvalidFileNameChars())
                {
                    filename = filename.Replace(c, '_');
                }

                StorageFile imageFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(filename + ".jpg", CreationCollisionOption.ReplaceExisting);
                using (IRandomAccessStream stream = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
                {
                    await image.ToStream(stream, BitmapEncoder.JpegEncoderId);
                }

                KmlIcon icon = new KmlIcon(new Uri(imageFile.Path));
                
                KmlGroundOverlay groundOverlay = new KmlGroundOverlay(placemark.Geometries[0].Geometry, icon);
                StorageFile tempFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(filename + ".kmz", CreationCollisionOption.ReplaceExisting);
                using (Stream stream = await tempFile.OpenStreamForWriteAsync())
                {
                    await groundOverlay.WriteToAsync(stream);
                }


                StorageFile file = await folder.CreateFileAsync(filename + ".kmz", CreationCollisionOption.ReplaceExisting);
                await tempFile.CopyAndReplaceAsync(file);
            }
            catch (Exception e)
            {
                Debug.WriteLine("ExportGroundOverlay() - exception = " + e.ToString());
            }
        }
0 Kudos