Help parsing ESRI .lyr file - and then merge resulting code back into DotSpatial

Jul 8, 2012 at 11:24 PM

I'm using DotSpatial to read shapefiles and then render in my own engine, and need to look inside .lyr files for colors and other rendering information. From various websites and msdn and such, I've manage to cobble together a Compound File reader (which is what .lyr files are) - and can get down to the point of reading the bytes in the data stream...but that's about as far as my Hacker Fu gets me. I can't figure out what the data means (looks like maybe some binary serialized objects of some sort...???).

Here's the working code I'm using if anyone else wants to take a crack at this. Eventually we could clearly merge this functionality back into DotSpatial so everyone could read .lyr files...but I need some help figuring out the fundamental contents.

Start by instantiating a ParseLyrFile object and calling Parse with the filename (the public one...it will recurse to the IStorage one as necessary)

 

#region LyrFile

#region COM Imports
[ComImport]
[Guid("0000000d-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IEnumSTATSTG
{
    // The user needs to allocate an STATSTG array whose size is celt.
    [PreserveSig]
    uint Next(
        uint celt,
        [MarshalAs(UnmanagedType.LPArray), Out]
        System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt,
        out uint pceltFetched
    );

    void Skip(uint celt);

    void Reset();

    [return: MarshalAs(UnmanagedType.Interface)]
    IEnumSTATSTG Clone();
}

[ComImport]
[Guid("0000000b-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IStorage
{
    void CreateStream(
        /* [string][in] */ string pwcsName,
        /* [in] */ uint grfMode,
        /* [in] */ uint reserved1,
        /* [in] */ uint reserved2,
        /* [out] */ out IStream ppstm);

    void OpenStream(
        /* [string][in] */ string pwcsName,
        /* [unique][in] */ IntPtr reserved1,
        /* [in] */ uint grfMode,
        /* [in] */ uint reserved2,
        /* [out] */ out IStream ppstm);

    void CreateStorage(
        /* [string][in] */ string pwcsName,
        /* [in] */ uint grfMode,
        /* [in] */ uint reserved1,
        /* [in] */ uint reserved2,
        /* [out] */ out IStorage ppstg);

    void OpenStorage(
        /* [string][unique][in] */ string pwcsName,
        /* [unique][in] */ IStorage pstgPriority,
        /* [in] */ uint grfMode,
        /* [unique][in] */ IntPtr snbExclude,
        /* [in] */ uint reserved,
        /* [out] */ out IStorage ppstg);

    void CopyTo(
        /* [in] */ uint ciidExclude,
        /* [size_is][unique][in] */ Guid rgiidExclude, // should this be an array?
        /* [unique][in] */ IntPtr snbExclude,
        /* [unique][in] */ IStorage pstgDest);

    void MoveElementTo(
        /* [string][in] */ string pwcsName,
        /* [unique][in] */ IStorage pstgDest,
        /* [string][in] */ string pwcsNewName,
        /* [in] */ uint grfFlags);

    void Commit(
        /* [in] */ uint grfCommitFlags);

    void Revert();

    void EnumElements(
        /* [in] */ uint reserved1,
        /* [size_is][unique][in] */ IntPtr reserved2,
        /* [in] */ uint reserved3,
        /* [out] */ out IEnumSTATSTG ppenum);

    void DestroyElement(
        /* [string][in] */ string pwcsName);

    void RenameElement(
        /* [string][in] */ string pwcsOldName,
        /* [string][in] */ string pwcsNewName);

    void SetElementTimes(
        /* [string][unique][in] */ string pwcsName,
        /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pctime,
        /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME patime,
        /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pmtime);

    void SetClass(
        /* [in] */ Guid clsid);

    void SetStateBits(
        /* [in] */ uint grfStateBits,
        /* [in] */ uint grfMask);

    void Stat(
        /* [out] */ out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg,
        /* [in] */ uint grfStatFlag);

}

[Flags]
public enum STGM : int
{
    DIRECT = 0x00000000,
    TRANSACTED = 0x00010000,
    SIMPLE = 0x08000000,
    READ = 0x00000000,
    WRITE = 0x00000001,
    READWRITE = 0x00000002,
    SHARE_DENY_NONE = 0x00000040,
    SHARE_DENY_READ = 0x00000030,
    SHARE_DENY_WRITE = 0x00000020,
    SHARE_EXCLUSIVE = 0x00000010,
    PRIORITY = 0x00040000,
    DELETEONRELEASE = 0x04000000,
    NOSCRATCH = 0x00100000,
    CREATE = 0x00001000,
    CONVERT = 0x00020000,
    FAILIFTHERE = 0x00000000,
    NOSNAPSHOT = 0x00200000,
    DIRECT_SWMR = 0x00400000,
}

public enum STATFLAG : uint
{
    STATFLAG_DEFAULT = 0,
    STATFLAG_NONAME = 1,
    STATFLAG_NOOPEN = 2
}

public enum STGTY : int
{
    STGTY_STORAGE = 1,
    STGTY_STREAM = 2,
    STGTY_LOCKBYTES = 3,
    STGTY_PROPERTY = 4
}
#endregion

internal class ParseLyrFile
{
    [DllImport("ole32.dll")]
    private static extern int StgIsStorageFile(
        [MarshalAs(UnmanagedType.LPWStr)] string pwcsName);

    [DllImport("ole32.dll")]
    static extern int StgOpenStorage(
        [MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
        IStorage pstgPriority,
        STGM grfMode,
        IntPtr snbExclude,
        uint reserved,
        out IStorage ppstgOpen);

    private void Parse(IStorage storage)
    {
		// Get stats about the current storage object
        System.Runtime.InteropServices.ComTypes.STATSTG statstg;
        storage.Stat(out statstg, (uint) STATFLAG.STATFLAG_DEFAULT);

		// Enum all contents
        IEnumSTATSTG enumStatStg = null;
        storage.EnumElements(0, IntPtr.Zero, 0, out enumStatStg);

        System.Runtime.InteropServices.ComTypes.STATSTG[] stats = { new System.Runtime.InteropServices.ComTypes.STATSTG() };
        uint fetched = 0;
        uint res = enumStatStg.Next(1, stats, out fetched);

		while (res == 0 && fetched == 1)
		{
			switch (stats[0].type)
			{
				case (int) STGTY.STGTY_STORAGE:
					{
						IStorage istorage;
						storage.OpenStorage(
							stats[0].pwcsName,
							null, 
							(uint)(STGM.READ | STGM.SHARE_EXCLUSIVE),
							IntPtr.Zero, 
							0, 
							out istorage);
								
						Parse(istorage);
						Marshal.ReleaseComObject(istorage);
					}
					break;

				case (int) STGTY.STGTY_STREAM:
					{
						IStream istream;
						storage.OpenStream(
							stats[0].pwcsName,
							IntPtr.Zero,
							(uint)(STGM.READ | STGM.SHARE_EXCLUSIVE),
							0,
							out istream);

						byte[] bytes = new byte[stats[0].cbSize];
						istream.Read(bytes, (int)stats[0].cbSize, IntPtr.Zero);
						// This doesn't work...data is not a string.
						string val = System.Text.Encoding.ASCII.GetString(bytes);
						Marshal.ReleaseComObject(istream);
					}
					break;
			}

			res = enumStatStg.Next(1, stats, out fetched);
		}

		Marshal.ReleaseComObject(enumStatStg);
		Marshal.ReleaseComObject(storage);
	}

    public void Parse(string fileName)
    {
        if (StgIsStorageFile(fileName) == 0)
        {
            IStorage storage = null;
            if (StgOpenStorage(
                fileName,
                null,
                STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE,
                IntPtr.Zero,
                0,
                out storage) == 0)
		{
				Parse(storage);
				Marshal.ReleaseComObject(storage);
		}
	}

}

#endregion

Developer
Jul 9, 2012 at 5:38 PM

Once you do have this ready to go, you might want to implement it as an extension (like the MW4 conversion extension).

Jul 9, 2012 at 6:00 PM

I'd be happy to - I'd actually like to migrate more and more of my app (http://www.mysticetus.com - it's a data recording and realtime mapping app for observational scientists, specifically those in airplanes or boats) to using DotSpatial - it currently just pokes around the fringes for a feature or two (GPS/NMEA, shapefile loading and reprojection). I've also done some interesting things with DEMs and shaded relief in the realtime navigation portion of the app, and could contribute some of that to DotSpatial when I get some time to fully grok how DotSpatial works as an app platform rather than the "helper api" mode I'm currently using it for.

In the meantime, I'm stymied figuring out these darned .lyr files. arg.

Nov 26, 2014 at 1:58 PM
Hey Entiat, did you get it working? I need this as well.
Nov 26, 2014 at 7:40 PM
Nope. I gave up on doing anything with lyr files. Trying to hack between IStorage and typical ESRI "let's invent yet another proprietary format, bwahaha" BS was sucking the very soul out of the center of my being. So I just decide shapefile colors based on depth/elevation and stick with my own palette.

If you can run with what I have above and do anything with it, good luck and let us know how it goes :-)