Global Mapper v24.1

GM_ExportRaster / GM_ExportRasterEx referencing more than 1 layer

codemouse
codemouse Global Mapper UserTrusted User
edited August 2013 in SDK
Hi,

Assume I load 4 different rectified image files (MrSID, TIFF, JPEG, etc.) into 4 separate GM_LayerHandle_t32* (layerLists).

GM_ExportRaster / GM_ExportRasterEx allows me to specify an AOI / rectangle / bounds to export only a segment of ONE of those 4 layers (via one layerlist pointer - GM_LayerHandle_t32*). However, in the Global Mapper GUI, I could manually specify or draw a rectangle across 2,3, or all 4 of the loaded layers and it could export an image that would span all of the selected layers - also taking into account any z-ordering I believe.

I have yet to discover how to do this programatically, as it seems there is an SDK limitation to only specify one layer/layerlist at a time. Is this the only programatic method available to export a raster? If so, I could envision testing/scanning the world bounds of loaded layers and then just select one automatically. Unfortunately, any AOIS/bounds that span between layers would then have to be stitched together "manually" by my program.

Thoughts on a better way to accomplish this by being able to specify more than one loaded layer/layerlist at a time? Do I need to LOAD the layers differently into a larger layerlist? (not sure how to do that).

Thank you!

Brian

Comments

  • global_mapper
    global_mapper Administrator
    edited August 2013
    Brian,

    When you load a file and it returns a layer list that is just the list of layers loaded by that one load operation. You should copy the layer handles out of that list and put them in your own list of everything that is loaded, or if you have the latest SDK use the GM_GetLoadedLayerList function to get the list of all currently loaded layers. If you look at the C++ sample application whenever it loads layers it copies the layer handles to it's own list of layer handles. Those are then available to pass in to exports and other operations, like draws, in whatever order you need.

    Thanks,

    Mike
    Global Mapper Guru
    geohelp@bluemarblegeo.com
    Blue Marble Geographics for Coordinate Conversion, Image Reprojection and Vector Translation
  • codemouse
    codemouse Global Mapper User Trusted User
    edited August 2013
    Mike,

    This makes sense. Let me take a look - I need to figure out how to abstract a list of IntPtr via C#, but I think I should be able to do that. Worst case, I just write that part in C++ instead (then abstract that to C# via CLI or P/invoke).

    Brian
  • global_mapper
    global_mapper Administrator
    edited August 2013
    Brian,

    The C# sample application included with the SDK also stores a list of loaded layers and extracts that from the list returned by GM_LoadLayerList, so just use that as a reference.

    Thanks,

    Mike
    Global Mapper Guru
    geohelp@bluemarblegeo.com
    Blue Marble Geographics for Coordinate Conversion, Image Reprojection and Vector Translation
  • codemouse
    codemouse Global Mapper User Trusted User
    edited August 2013
    Mike,

    Yes - I totally forgot about that in the example. Thanks.

    One thing to note that I just realized, in 64-bit applications, pointers are 8 bytes. So your example c# code line of:

    IntPtr theLayerHandlePtr = (IntPtr)((UInt32)theLayerList + i * 4);

    should be


    IntPtr theLayerHandlePtr = (IntPtr)((UInt32)theLayerList + i * 8);

    when using x64 compilation.

    Brian
  • global_mapper
    global_mapper Administrator
    edited August 2013
    Brian,

    Ah yes that part of the sample is very old. I have changed the 4 to IntPtr.Size so it should work on either platform seamlessly now.

    Thanks,

    Mike
    Global Mapper Guru
    geohelp@bluemarblegeo.com
    Blue Marble Geographics for Coordinate Conversion, Image Reprojection and Vector Translation
  • codemouse
    codemouse Global Mapper User Trusted User
    edited August 2013
    Mike,

    Thanks - that worked perfectly.

    For the lazy - here's basically what I did:

    I created my own type to hold my layer stuffs of types of Raster and Elevation (or more when I feel like it - this example shows Elevation types):
    public class GmLoadedDataLayer
    {
        public IntPtr LayerHandle { get; set; }
        public LayerType LayerType { get; set; }
        public GM_LayerInfo_t LayerInfo { get; set; }
    }
    

    LayerType just looks like this:
        public enum LayerType
        {
            Elevation,
            Terrain,
            Other
        }
    

    and a new List<T> to hold them:
    public List<GmLoadedDataLayer> GmLoadedDataLayers = new List<GmLoadedDataLayer>();
    

    My LoadLayerEx struct looks like this:
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate GM_Error_t32 GM_LoadLayerListEx(string aFilename, out IntPtr aLayerList, out uint aNumLoadedLayers, GM_LoadFlags_t32 aLoadFlags, string aExtraLoadOptions);
    

    My abstracted wrapper looks like this:
    public int LoadLayerListEx(string path, out IntPtr dataLayerList, out uint dataLayerCount, out string gmErrorDesc)
            {
                var loadLayerListEx = (GlobalMapper.GM_LoadLayerListEx)Marshal.GetDelegateForFunctionPointer(NativeMethods.GetProcAddress(GlobalMapperInterfaceDllAddress, typeof(GlobalMapper.GM_LoadLayerListEx).Name), typeof(GlobalMapper.GM_LoadLayerListEx));
    
    
                const GM_LoadFlags_t32 flags = GM_LoadFlags_t32.GM_LoadFlags_HideAllPrompts | GM_LoadFlags_t32.GM_LoadFlags_HideProgress;
    
    
                var lastGmError = loadLayerListEx(path, out dataLayerList, out dataLayerCount, flags, string.Empty);
    
    
                gmErrorDesc = lastGmError.GetDescription();
    
    
                return (int)lastGmError;
            }
    

    Then call that wrapper like this to add 1 to n layers based on file paths:

    IntPtr dataLayerList;
                    uint dataLayerCount = 0;
                    string gmErrorDesc;
    
    
                    var result = LoadLayerListEx(filePathToLoad, out dataLayerList, out dataLayerCount, out gmErrorDesc);
    
    
                    if (result != (int)GM_Error_t32.GM_Error_None)
                        return gmErrorDesc;
    
    
                    // Add each layer that was just loaded from that one file path
                    for (var i = 0; i < dataLayerCount; i++)
                    {
                        // Extract the layer handle from the list
                        var dataLayerHandlePtr = (IntPtr)((uint)dataLayerList + i * IntPtr.Size);
                        var dataLayerHandle = (IntPtr)Marshal.PtrToStructure(dataLayerHandlePtr, typeof(IntPtr));
    
    
                        // Add the layer
                        var dataLayerInfo = new GM_LayerInfo_t();
    
    
                        var resultString = GetLayerInfo(dataLayerHandle, out dataLayerInfo);
    
    
                        if (!string.IsNullOrWhiteSpace(resultString))
                            return resultString;
    
    
                        GmLoadedDataLayers.Add(new GmLoadedDataLayer { LayerType = LayerType.Elevation, LayerHandle = dataLayerHandle, LayerInfo = dataLayerInfo });
                    }
    
    Then the final call to do stuff here:
                var elevationLayers = GmLoadedDataLayers.Where(x => x.LayerType == LayerType.Elevation).ToList();
                var elevationLayersCount = elevationLayers.Count();
    
    
                if (elevationLayersCount <= 0)
                    return "No loaded Elevation Layers Found.";
    
    
                // allocate a buffer to hold layer list              
                var layerListPtr = new IntPtr();
    
    
                try
                {
                    var layerHandle = new IntPtr();
    
    
                    if (elevationLayersCount > 0)
                        layerListPtr = Marshal.AllocCoTaskMem((int)(elevationLayersCount * Marshal.SizeOf(layerHandle)));
    
    
                    // fill the layer list
                    for (var i = 0; i < elevationLayersCount; i++)
                    {
                        // extract the data from the buffer
                        layerHandle = (IntPtr)elevationLayers[i].LayerHandle;
                        var destinationPtr = (IntPtr)((uint)layerListPtr + i * Marshal.SizeOf(layerHandle));
                        Marshal.StructureToPtr(layerHandle, destinationPtr, false);
                    }
    
    
                    var gmResult = WhateverGMCommandYouWantToRunHere - passing in layerListPtr as aLayerList;
    
    
                    if (!gmResult.Equals(GM_Error_t32.GM_Error_None.GetDescription()))
                        return gmResult;
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
                finally
                {
                    // Free the layer list memory
                    if (elevationLayersCount > 0)
                        Marshal.FreeCoTaskMem(layerListPtr);
                }
    

    That's more or less it, in a nutshell. I hope this is helpful and not confusing.

    Brian