Saturday, May 16, 2009

Accessing ESRI Style Files using ADO.NET

ESRI's products ArcGIS Engine, ArcGIS Desktop and ArcGIS Server use style files to store and manage collections of symbols. A symbol is a graphic used to represent a geographic feature or class of features. Styles contains named and categorized symbols for various geographic entities like points, lines and polygons.

ArcGIS Engine and ArcGIS Desktop style files are Microsoft Access databases with a style file extension.

ArcGIS Server style files use a proprietary file type and have a serverstyle file extension. Developers can only use the ArcObjects API to access this file.

ArcGIS Engine developers can use the SymbologyControl to browse symbols contained in a server style. Whereas ArcGIS Desktop developers can re-use the desktop dialogs like the StyleManagerDialog, SymbolEditor and the SymbolPickerDialog.

In this post I will present a sample that demonstrates how to access ESRI style files using ADO.NET. The sample will also show how to convert an ESRI symbol into a .NET bitmap. This sample may be useful to developers that want to:
1) access styles without using ArcObjects, or
2) store custom information in an ESRI style file, or
3) convert ESRI symbols to bitmaps, or
4) create a custom symbol browser.

Please note that the code and methodology used in this post is not endorsed or supported by ESRI. Use this code at your own risk.

This sample was developed in C# using Microsoft Visual Studio 2005.  The windows application consists of four controls and one component, these are:
1) textBox1: Path name to the style file,
2) button1: Opens the style file and adds entries to the ListView,
3) listView1: Display symbol icon and descriptions,
4) axLicenseControl1: Checks out an ArcGIS Engine or ArcGIS Desktop at runtime,
5) imageList1: Store a collection of images for the ListView items.

image

When the form loads:
1) A default path name is added to the style TextBox,
2) Four columns are added to the ListView,
3) The ListView is view style is set to details,
4) The ImageList is assigned to the SmallImageList property of the ListView.

The application, when loaded, looks like this.

image

If you run this application in debug mode, Microsoft Visual Studio 2005 may throw a PInvokeStackImbalance exception.  This can be overcome by running the application normally from the executable or by disabling this exception.  This and other debug exceptions can be ignored using the Exception window (Debug > Exceptions...).

To load the point, line and polygon symbols from the style to the ListView click GO!

image

Here is the source code for the sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using stdole;

using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Framework;

namespace BrowseEsriSymbols {
    public partial class Form1 : Form {
        private const string GDB_TABLE_MARKER = "MARKER SYMBOLS";
        private const string GDB_TABLE_LINE = "LINE SYMBOLS";
        private const string GDB_TABLE_AREA = "FILL SYMBOLS";
        private const string JET_SCHEMA_TABLE = "TABLE_NAME";
        private const string JET_SCHEMA_COLUMN = "COLUMN_NAME";
        private const string FIELD_ID = "ID";
        private const string FIELD_CATEGORY = "CATEGORY";
        private const string FIELD_NAME = "NAME";

        public Form1() {
            // Initialize Components
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e) {
            // Set Button Name from Resource
            this.textBox1.Text = "C:\\Program Files\\ArcGIS\\Styles\\Conservation.style";
            this.listView1.SmallImageList = this.imageList1;
            this.listView1.View = View.Details;
            this.listView1.Columns.Add("Table");
            this.listView1.Columns.Add("Id");
            this.listView1.Columns.Add("Category");
            this.listView1.Columns.Add("Name");
        }
        private void button1_Click(object sender, EventArgs e) {
            // Get Drawing Context
            Graphics graphics = this.CreateGraphics();

            // Display Hourglass Cursor
            this.Cursor = Cursors.WaitCursor;

            // Remove all rows (if any)
            this.listView1.Items.Clear();

            // Stop ListView Redraws
            this.listView1.BeginUpdate();

            // Connect to Style (using ADO.NET)
            string connection = "Provider=Microsoft.Jet.OleDb.4.0;Data Source=" + this.textBox1.Text + ";";
            OleDbConnection oleConnection = new OleDbConnection(connection);
            oleConnection.Open();

            // Style Hardcode Table Names and Fields
            string[] tables = new string[] { GDB_TABLE_MARKER, GDB_TABLE_LINE, GDB_TABLE_AREA };

            // Loop For Each Symbol Table in Style
            foreach (string table in tables) {
                // Construct SQL statement
                string query = "SELECT * FROM [" + table + "] ";

                // Connect to the Access File and create reader
                OleDbCommand command = new OleDbCommand(query, oleConnection);
                OleDbDataReader dataReader = command.ExecuteReader();
                if (!dataReader.HasRows) { continue; }

                // Find column indexes
                int indexId = dataReader.GetOrdinal(FIELD_ID);
                int indexCategory = dataReader.GetOrdinal(FIELD_CATEGORY);
                int indexName = dataReader.GetOrdinal(FIELD_NAME);

                // Read each row in table
                while (dataReader.Read()) {
                    // Get row values
                    int id = dataReader.GetInt32(indexId);
                    string category = dataReader.GetString(indexCategory);
                    string name = dataReader.GetString(indexName);

                    // Get ESRI Symbol
                    ISymbol symbol = WindowsAPI.GetSymbol(this.textBox1.Text, table, id);

                    // Convert ESRI Symbol to Bitmap
                    Bitmap bitmap = WindowsAPI.SymbolToBitmap(
                        symbol,
                        new Size(16, 16),
                        graphics,
                        ColorTranslator.ToWin32(this.listView1.BackColor));

                    // Add Image to the listview's image list
                    int index = this.listView1.SmallImageList.Images.Add(bitmap, Color.Transparent);

                    // Add ListViewItem to ListView
                    string[] cells = new string[] { table, id.ToString(), category, name };
                    ListViewItem item = new ListViewItem(cells);
                    this.listView1.Items.Add(item);
                    item.ImageIndex = index;
                }

                // Close Reader
                dataReader.Close();
            }

            // Close Connection
            oleConnection.Close();

            // Resume ListView Drawing
            this.listView1.EndUpdate();

            // Display Default Cursor
            this.Cursor = Cursors.Default;
        }
    }

    public static class WindowsAPI {
        private const int COLORONCOLOR = 3;
        private const int HORZSIZE = 4;
        private const int VERTSIZE = 6;
        private const int HORZRES = 8;
        private const int VERTRES = 10;
        private const int ASPECTX = 40;
        private const int ASPECTY = 42;
        private const int LOGPIXELSX = 88;
        private const int LOGPIXELSY = 90;

        private enum PictureTypeConstants {
            picTypeNone = 0,
            picTypeBitmap = 1,
            picTypeMetafile = 2,
            picTypeIcon = 3,
            picTypeEMetafile = 4
        }
        private struct PICTDESC {
            public int cbSizeOfStruct;
            public int picType;
            public IntPtr hPic;
            public IntPtr hpal;
            public int _pad;
        }
        private struct RECT {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        [DllImport("olepro32.dll", EntryPoint = "OleCreatePictureIndirect", PreserveSig = false)]
        private static extern int OleCreatePictureIndirect(
            ref PICTDESC pPictDesc, ref Guid riid, bool fOwn, out IPictureDisp ppvObj);

        [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        [DllImport("gdi32.dll", EntryPoint = "DeleteDC", ExactSpelling = true, SetLastError = true)]
        private static extern bool DeleteDC(IntPtr hdc);

        [DllImport("gdi32.dll", EntryPoint = "SelectObject", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject", ExactSpelling = true, SetLastError = true)]
        private static extern bool DeleteObject(IntPtr hObject);

        [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap", ExactSpelling = true,
            SetLastError = true)]
        private static extern IntPtr CreateCompatibleBitmap(IntPtr hObject, int width, int height);

        [DllImport("user32.dll", EntryPoint = "GetDC", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr GetDC(IntPtr ptr);

        [DllImport("user32.dll", EntryPoint = "ReleaseDC", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);

        [DllImport("gdi32", EntryPoint = "CreateSolidBrush", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateSolidBrush(int crColor);

        [DllImport("user32", EntryPoint = "FillRect", ExactSpelling = true, SetLastError = true)]
        private static extern int FillRect(IntPtr hdc, ref RECT lpRect, IntPtr hBrush);

        [DllImport("GDI32.dll", EntryPoint = "GetDeviceCaps", ExactSpelling = true, SetLastError = true)]
        private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

        [DllImport("user32", EntryPoint = "GetClientRect", ExactSpelling = true, SetLastError = true)]
        private static extern int GetClientRect(IntPtr hwnd, ref RECT lpRect);

        public static ISymbol GetSymbol(string style, string classname, int id) {
            ISymbol symbol = null;
            IStyleGallery styleGallery = new StyleGalleryClass();
            IStyleGalleryStorage styleGalleryStorage = (IStyleGalleryStorage)styleGallery;
            styleGalleryStorage.TargetFile = style;
            IEnumStyleGalleryItem styleGalleryItems = styleGallery.get_Items(classname, style, "");
            styleGalleryItems.Reset();
            IStyleGalleryItem styleGalleryItem = styleGalleryItems.Next();
            while (styleGalleryItem != null) {
                if (styleGalleryItem.ID == id) {
                    symbol = (ISymbol)styleGalleryItem.Item;
                    break;
                }
                styleGalleryItem = styleGalleryItems.Next();
            }
            styleGalleryItem = null;
            styleGalleryStorage = null;
            styleGallery = null;

            return symbol;
        }
        private static IPictureDisp CreatePictureFromSymbol(IntPtr hDCOld, ref IntPtr hBmpNew,
            ISymbol pSymbol, Size size, int lGap, int backColor) {
            IntPtr hDCNew = IntPtr.Zero;
            IntPtr hBmpOld = IntPtr.Zero;
            try {
                hDCNew = CreateCompatibleDC(hDCOld);
                hBmpNew = CreateCompatibleBitmap(hDCOld, size.Width, size.Height);
                hBmpOld = SelectObject(hDCNew, hBmpNew);

                // Draw the symbol to the new device context.
                bool lResult = DrawToDC(hDCNew, size, pSymbol, lGap, backColor);

                hBmpNew = SelectObject(hDCNew, hBmpOld);
                DeleteDC(hDCNew);

                // Return the Bitmap as an OLE Picture.
                return CreatePictureFromBitmap(hBmpNew);
            }
            catch (Exception error) {
                if (pSymbol != null) {
                    pSymbol.ResetDC();
                    if ((hBmpNew != IntPtr.Zero) && (hDCNew != IntPtr.Zero) && (hBmpOld != IntPtr.Zero)) {
                        hBmpNew = SelectObject(hDCNew, hBmpOld);
                        DeleteDC(hDCNew);
                    }
                }

                throw error;
            }
        }
        private static IPictureDisp CreatePictureFromBitmap(IntPtr hBmpNew) {
            try {
                Guid iidIPicture = new Guid("7BF80980-BF32-101A-8BBB-00AA00300CAB");

                PICTDESC picDesc = new PICTDESC();
                picDesc.cbSizeOfStruct = Marshal.SizeOf(picDesc);
                picDesc.picType = (int)PictureTypeConstants.picTypeBitmap;
                picDesc.hPic = (IntPtr)hBmpNew;
                picDesc.hpal = IntPtr.Zero;

                // Create Picture object.
                IPictureDisp newPic;
                OleCreatePictureIndirect(ref picDesc, ref iidIPicture, true, out newPic);

                // Return the new Picture object.
                return newPic;
            }
            catch (Exception error) {
                throw error;
            }
        }
        private static bool DrawToWnd(IntPtr hWnd, ISymbol pSymbol, int lGap, int backColor) {
            IntPtr hDC = IntPtr.Zero;
            try {
                if (hWnd != IntPtr.Zero) {
                    // Calculate size of window.
                    RECT udtRect = new RECT();
                    int lResult = GetClientRect(hWnd, ref udtRect);

                    if (lResult != 0) {
                        int lWidth = (udtRect.Right - udtRect.Left);
                        int lHeight = (udtRect.Bottom - udtRect.Top);

                        hDC = GetDC(hWnd);
                        // Must release the DC afterwards.
                        if (hDC != IntPtr.Zero) {
                            bool ok = DrawToDC(hDC, new Size(lWidth, lHeight), pSymbol, lGap, backColor);

                            // Release cached DC obtained with GetDC.
                            ReleaseDC(hWnd, hDC);

                            return ok;
                        }
                    }
                }
            }
            catch {
                if (pSymbol != null) {
                    // Try resetting DC, in case we have already called SetupDC for this symbol.
                    pSymbol.ResetDC();

                    if ((hWnd != IntPtr.Zero) && (hDC != IntPtr.Zero)) {
                        ReleaseDC(hWnd, hDC); // Try to release cached DC obtained with GetDC.
                    }
                }
                return false;
            }
            return true;
        }
        private static bool DrawToDC(IntPtr hDC, Size size, ISymbol pSymbol, int lGap, int backColor) {
            try {
                if (hDC != IntPtr.Zero) {
                    // First clear the existing device context.
                    if (!Clear(hDC, backColor, 0, 0, size.Width, size.Height)) {
                        throw new Exception("Could not clear the Device Context.");
                    }

                    // Create the Transformation and Geometry required by ISymbol::Draw.
                    ITransformation pTransformation = CreateTransFromDC(hDC, size.Width, size.Height);
                    IEnvelope pEnvelope = new EnvelopeClass();
                    pEnvelope.PutCoords(lGap, lGap, size.Width - lGap, size.Height - lGap);
                    IGeometry pGeom = CreateSymShape(pSymbol, pEnvelope);

                    // Perform the Draw operation.
                    if ((pTransformation != null) && (pGeom != null)) {
                        pSymbol.SetupDC(hDC.ToInt32(), pTransformation);
                        pSymbol.Draw(pGeom);
                        pSymbol.ResetDC();
                    }
                    else {
                        throw new Exception("Could not create required Transformation or Geometry.");
                    }
                }
            }
            catch {
                if (pSymbol != null) {
                    pSymbol.ResetDC();
                }
                return false;
            }

            return true;
        }
        private static bool Clear(IntPtr hDC, int backgroundColor, int xmin, int ymin, int xmax, int ymax) {
            // This function fill the passed in device context with a solid brush,
            // based on the OLE color passed in.
            IntPtr hBrushBackground = IntPtr.Zero;
            int lResult;
            bool ok;

            try {
                RECT udtBounds;
                udtBounds.Left = xmin;
                udtBounds.Top = ymin;
                udtBounds.Right = xmax;
                udtBounds.Bottom = ymax;

                hBrushBackground = CreateSolidBrush(backgroundColor);
                if (hBrushBackground == IntPtr.Zero) {
                    throw new Exception("Could not create GDI Brush.");
                }
                lResult = FillRect(hDC, ref udtBounds, hBrushBackground);
                if (hBrushBackground == IntPtr.Zero) {
                    throw new Exception("Could not fill Device Context.");
                }
                ok = DeleteObject(hBrushBackground);
                if (hBrushBackground == IntPtr.Zero) {
                    throw new Exception("Could not delete GDI Brush.");
                }
            }
            catch {
                if (hBrushBackground != IntPtr.Zero) {
                    ok = DeleteObject(hBrushBackground);
                }
                return false;
            }

            return true;
        }
        private static ITransformation CreateTransFromDC(IntPtr hDC, int lWidth, int lHeight) {
            // Calculate the parameters for the new transformation,
            // based on the dimensions passed to this function.
            try {
                IEnvelope pBoundsEnvelope = new EnvelopeClass();
                pBoundsEnvelope.PutCoords(0.0, 0.0, (double)lWidth, (double)lHeight);

                tagRECT deviceRect;
                deviceRect.left = 0;
                deviceRect.top = 0;
                deviceRect.right = lWidth;
                deviceRect.bottom = lHeight;

                int dpi = GetDeviceCaps(hDC, LOGPIXELSY);
                if (dpi == 0) {
                    throw new Exception("Could not retrieve Resolution from device context.");
                }

                // Create a new display transformation and set its properties.
                IDisplayTransformation newTrans = new DisplayTransformationClass();
                newTrans.VisibleBounds = pBoundsEnvelope;
                newTrans.Bounds = pBoundsEnvelope;
                newTrans.set_DeviceFrame(ref deviceRect);
                newTrans.Resolution = dpi;

                return newTrans;
            }
            catch {
                return null;
            }
        }
        private static IGeometry CreateSymShape(ISymbol pSymbol, IEnvelope pEnvelope) {
            // This function returns an appropriate Geometry type depending on the
            // Symbol type passed in.
            try {
                if (pSymbol is IMarkerSymbol) {
                    // For a MarkerSymbol return a Point.
                    IArea pArea = (IArea)pEnvelope;
                    return pArea.Centroid;
                }
                else if ((pSymbol is ILineSymbol) || (pSymbol is ITextSymbol)) {
                    // For a LineSymbol or TextSymbol return a Polyline.
                    IPolyline pPolyline = new PolylineClass();
                    pPolyline.FromPoint = pEnvelope.LowerLeft;
                    pPolyline.ToPoint = pEnvelope.UpperRight;
                    return pPolyline;
                }
                else {
                    // For any FillSymbol return an Envelope.
                    return pEnvelope;
                }
            }
            catch {
                return null;
            }
        }
        public static Bitmap SymbolToBitmap(ISymbol userSymbol, Size size, Graphics gr, int backColor) {
            IntPtr graphicsHdc = gr.GetHdc();
            IntPtr hBitmap = IntPtr.Zero;
            IPictureDisp newPic = CreatePictureFromSymbol(
                graphicsHdc, ref hBitmap, userSymbol, size, 1, backColor);
            Bitmap newBitmap = Bitmap.FromHbitmap(hBitmap);
            gr.ReleaseHdc(graphicsHdc);

            return newBitmap;
        }
    }
}

1 comment: