Archive for October, 2008

AGS Tile Caching Explored

October 31, 2008

Recently I’ve noticed that ArcGIS does not store its tile-caching data in the standard service configuration file [Program Files\ArcGIS\server\user\cfg\myService.MapServer.cfg]. Instead these get saved to the cache directory [arcgisserver\arcgiscache\myService\Layers\conf.xml].

Why? The ArcGIS Server Manager gives us a clue if we try to edit the caching properties of a stopped service: “Caching properties and tools are available only when the service is started.”

This is interesting. The AGS Server Object Manager can edit the standard “myService.MapServer.cfg” files directly via the IServerObjectAdmin interface.  An AGS Server Object Container, or more precisely an IMapCooker2 instance, is needed to write the “conf.xml” file.  Here’s the general code:


IMapCooker2 cooker = pServerContext.CreateObject("esriCarto.MapCooker") as IMapCooker2;
cooker.Connect(physicalCacheDirectory, myMapName, myMap);
ITileCacheInfo pCacheInfo = cooker.TileCacheInfo;
ITileImageInfo2 pTileImageInfo = (ITileImageInfo2)cooker.TileImageInfo;
// do things here
cooker.WriteTilingScheme(pCacheInfo, pTileImageInfo, physicalCacheDirectory, LayerName);

Interesting, but here’s the catch:  I can’t find a way to access an AGS MapService’s IMapCooker2 instance at runtime.  My last post showed how to modify an AGS’s service layers at load.  However, this relied on runtime access to the IMap.  In essence, you can’t [AFAIK] change an AGS Mapservice’s SOC’s caching characteristics at runtime; you must restart the service to reload this data from the “conf.xml” file.

AGS + SDE – MXD + SOE

October 29, 2008

This little bit of ArcGIS Server Object Extension code can take an ArcGIS Service based on an empty MXD, and dynamically add all the feature classes from an SDE instance to it.  Its one of my first SOEs, but it seems to run just fine. Somewhat unique about this code is that this SOE has no callable methods, its mere presence is its power. :P

 

using System;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Server;

namespace com.wordpress.mapwrecker
{
    [AutomationProxy(true), ClassInterface(ClassInterfaceType.None), GuidAttribute("a9ed9e17-d2b2-461c-9da3-2915af9a3f00")]
    public class SDE_Eater : ServicedComponent, IServerObjectExtension, ILogSupport
    {
        private ESRI.ArcGIS.esriSystem.ILog2 m_log;

        private object getSingleton(string progId)
        {
            return Activator.CreateInstance(Type.GetTypeFromProgID(progId));
        }
        public void addAllSdeLayersService(IServerObjectHelper m_SOH)
        {
            IWorkspaceFactory sdeWorkFact = null;
            try
            {
                MapServer ms = m_SOH.ServerObject as MapServer;
                IMapServerObjects mso = ms as IMapServerObjects;
                IMap map = (IMap)mso.get_Map(ms.DefaultMapName);

                sdeWorkFact = (IWorkspaceFactory)getSingleton("esriDataSourcesGDB.SdeWorkspaceFactory");
                IPropertySet propertySet = getSdeProperties();
                IWorkspace pFWorkspace = sdeWorkFact.Open(propertySet, 0);
                IEnumDataset penumDataset = pFWorkspace.get_Datasets(esriDatasetType.esriDTFeatureClass);

                IDataset pDataset = penumDataset.Next();
                while (pDataset != null)
                {
                    IFeatureLayer featureLayer = new FeatureLayer();
                    featureLayer.FeatureClass = (IFeatureClass)pDataset;
                    m_log.AddMessage(3, 110000, "SDE SOE found: " + pDataset.Name);
                    featureLayer.Name = pDataset.Name;
                    map.AddLayer(featureLayer);
                    pDataset = penumDataset.Next();
                }
                mso.RefreshServerObjects();

            } catch (Exception e) {
                m_log.AddMessage(1, 110000, "SDE SOE error: " + e.StackTrace);
            } finally {
                int refsLeft = 0;
                do { refsLeft = Marshal.ReleaseComObject(sdeWorkFact); } while (refsLeft > 0);
            }
        }

        private IPropertySet getSdeProperties()
        {
            IPropertySet propertySet = new PropertySet();
            propertySet.SetProperty("SERVER", "blah");
            propertySet.SetProperty("INSTANCE", "sde:sqlserver:blah");
            propertySet.SetProperty("DATABASE", "SDE");
            propertySet.SetProperty("USER", "blah");
            propertySet.SetProperty("PASSWORD", "blah");
            propertySet.SetProperty("VERSION", "sde.DEFAULT");
            propertySet.SetProperty("AUTHENTICATION_MODE", "DBMS");
            return propertySet;
        }

        public void Init(IServerObjectHelper pSOH)
        {
            m_log.AddMessage(2, 110000, "SDE SOE is loaded");
            addAllSdeLayersService(pSOH);
        }

        public void Shutdown()
        {
            m_log = null;
        }

        public void InitLogging(ILog Log)
        {
            m_log = (ESRI.ArcGIS.esriSystem.ILog2)Log;
        }
    }
}

Json rtrees

October 12, 2008

I’m having fun posting this from my iPhone, in NYC. Nate Irwin and I are working on implementing r-tree spatial indexing in Javascript.

Some quick testing proved that building an index clientside was too slow, so we’re building the r-tree serverside and serializing to Json.

On a 6500 feature dataset we’re getting .01s (FF) to .03s (IE) search times — fast enough for on-mouse-move GUI response.

The only caveat is that JSON nodes consisting of integer IDs and double-precision bounding-boxes run ~85 bytes per feature. Above 2500 features you must consider wire-size. Including attribute data would quickly bring this over the top.

However, optimizing the leaf node structure for point data and g-zipping the r-trees should run <3 bytes per feature — making clientside indexing of 100,000+ features within reason.