Category Archives: gis

Custom Component Depoloyment (ESRI) – better to use TARGETDIR

I think ESRI may be wrong on how to properly set up a custom installer for your custom dll’s for ArcMap etc.

How to deploy a custom component using a setup project

 

custom install

 

It’s basically implying that you should use:

[ProgramFilesFolder]\[ProductName]\bin\foo.dll

Which translates to soemthing like “C:\Program Files(x86)\MyCompany\bin\foo.dll”

But this may not always be true, what happens when your user wants to install to a custom installation directory?  The ESRIRegAsm.exe tool will always fire against – C:\Program Files(x86)\MyCompany\bin\foo.dll, and not where the user selected to install! – it’s basically hard wired so that means your customers could have problems trying to install your software.  It’s better to use:

[TARGETDIR]foo.dll

Which is basically the value you get back from this stage in the installation process (although it’s not very clear as you don’t see that as a variable in the properties window for this stage!):

install folder

 

This will then run ESRIRegAsm.exe against where the user decided to install your dll’s, properly installing the dlls into ArcMap etc.  This is, I believe more foolproof.  However if I have got this wrong, I’m happy to be proved wrong 🙂

Advertisements

Convert Latitude Longitude to Easting Northings

Take a look and hopefully it works in your code.


private double toRad(double val)
{
	return val * (Math.PI / 180);
}
        
public void LatLongToEastNorth(double latitude, double longitude)
{
       //This will not work unless you have your lats and longs in decimal degrees.
       latitude = toRad(latitude);
       longitude = toRad(longitude);

       double a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
       //double a = 6378137.0, b = 6356752.314245; WGS84 major & minor semi-axes

       double F0 = 0.9996012717; // NatGrid scale factor on central meridian
       double lat0 = toRad(49);
       double lon0 = toRad(-2); // NatGrid true origin
       double N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
       double e2 = 1 - (b * b) / (a * a); // eccentricity squared
       double n = (a - b) / (a + b), n2 = n * n, n3 = n * n * n;

       double cosLat = Math.Cos(latitude), sinLat = Math.Sin(latitude);
       double nu = a * F0 / Math.Sqrt(1 - e2 * sinLat * sinLat) ; // transverse radius of curvature
       double rho = a * F0 * (1 - e2) / Math.Pow(1 - e2 * sinLat * sinLat, 1.5); // meridional radius of curvature
   
       double eta2 = nu / rho - 1;

       double Ma = (1 + n + (5 / 4) * n2 + (5 / 4) * n3) * (latitude - lat0);
       double Mb = (3 * n + 3 * n * n + (21/8)*n3) * Math.Sin(latitude - lat0) * Math.Cos(latitude + lat0);
       double Mc = ((15/8)*n2 + (15/8)*n3) * Math.Sin(2 * (latitude - lat0)) * Math.Cos(2 * (latitude + lat0));
       double Md = (35 / 24) * n3 * Math.Sin(3 * (latitude - lat0)) * Math.Cos(3 * (latitude + lat0));
       double M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc

       double cos3lat = cosLat * cosLat * cosLat;
       double cos5lat = cos3lat * cosLat * cosLat;
       double tan2lat = Math.Tan(latitude) * Math.Tan(latitude);
       double tan4lat = tan2lat * tan2lat;

       double I = M + N0;
       double II = (nu / 2) * sinLat * cosLat;
       double III = (nu / 24) * sinLat * cos3lat * (5 - tan2lat + 9 * eta2);
       double IIIA = (nu / 720) * sinLat * cos5lat * (61 - 58 * tan2lat + tan4lat);
       double IV = nu * cosLat;
       double V = (nu / 6) * cos3lat * (nu / rho - tan2lat);
       double VI = (nu / 120) * cos5lat * (5 - 18 * tan2lat + tan4lat + 14 * eta2 - 58 * tan2lat * eta2);

       double dLon = longitude - lon0;
       double dLon2 = dLon * dLon, dLon3 = dLon2 * dLon, dLon4 = dLon3 * dLon, dLon5 = dLon4 * dLon, dLon6 = dLon5 * dLon;

       double N = I + II * dLon2 + III * dLon4 + IIIA * dLon6; //This is the northing
       double E = E0 + IV * dLon + V * dLon3 + VI * dLon5; //This is the easting  
}

How to query ArcIMS services with ArcObjects

IIMSServiceDescription imsServiceDescription = new IMSServiceNameClass();
imsServiceDescription.URL = @"you server here";                 
imsServiceDescription.Name = "catalog";

IIMSAxlRequest axlRequest = (IIMSAxlRequest)imsServiceDescription;
String result = axlRequest.SendAxlRequest("<GETCLIENTSERVICES/>", false, null, false, true);

IXmlPropertySet2 xmlProps = new XmlPropertySetClass();
xmlProps.SetXml(result);
object val = new object();
xmlProps.GetAttribute("RESPONSE/SERVICES/SERVICE", "NAME", out val);

object[] vals = (object[]) val;

for (int i = 0; i &lt; vals.Length; i++)
{
     Debug.WriteLine(vals[i].ToString());
}

Issues with persisting changes to the scale in PageLayout (LayoutView) using ArcObjects

Scenario

Now and again you may find yourself developing an application outside of ArcMap i.e. essentially an ArcEngine app.

I was developing an application that accessed an mxd via the IMapDocument interface and tried to persist a change in the map scale in the Layout View.  Every time I changed the scale and saved the mxd – however the original scale would not change and the programmatic new scale was ignored!

This was very frustrating indeed, I was sure that there must be a way to persist this change as it’s not exactly NASA missile launch code – I’m just trying to change a scale damn it!

ArcMap Scale

Highlight of the scale control in the layoutview of ArcMap

Solution

Anyway, it so happens that there is a fix, but it’s kind of unusual and not exactly intuitive or obvious to the developer coming at this problem cold.

Firstly we have to make use of some win32 API calls and most importantly to call IActiveView.Activate and pass our call to GetDesktopWindow().  This does the trick and is absolutely essential if you want to get this kind of change persisted.  Take a look at the code below to see how it’s done:

  
[DllImport("User32.dll")]
public static extern int GetDesktopWindow();

private void ChangeMyScale()
{
    IMapDocument MapDoc;
    mapDoc = new MapDocumentClass();
    mapDoc.Open(@"C:\Foo\Bar.mxd",null);
    IPageLayout pageLayout = mapDoc.PageLayout;
    IActiveView activeView = (IActiveView)pageLayout;
    activeView = (IActiveView) mapDoc.PageLayout;
    activeView.Activate(GetDesktopWindow()); //Key line!!
    mapDoc.ActiveView.FocusMap.MapScale = 10000; //Now we can change the scale!!
    activeView.Refresh();

    MessageBox.Show(@"We have an mxd called:" + mapDoc.DocumentFilename +
    "\nExtent: 1 to " + mapDoc.ActiveView.FocusMap.MapScale);

    mapDoc.Save(true, false)
    mapDoc.Close();
}

Other things to bear in mind, code such as:

MapDoc.SetActiveView((IActiveView)pageLayout)

only sets the view for the application as PageLayout, in order to get control of the specified window you have to use the Activate method:

<pre>activeView.Activate(GetDesktopWindow())

When working with the IActiveView interface on a MapDocument object, you should always first call IActiveView::Activate() in order to properly initialize the display of the PageLayout or Map object.

The MxDocument, MapServer objects, MapControl and PageLayoutControl all initialise display objects automatically after opening an MXD, but MapDocument DOES NOT DO THIS, so you should call Activate() before working with any other members of IActiveView.

So to summarise, if your application has a user interface, you should call Activate() with the hWnd of the application’s client area.  If your application runs in the background and has no windows, you retrieve a valid hWnd from the GDI GetDesktopWindow() function, part of the Win32 API.

Conclusion

I am hopeful that ESRI will change this so that you don’t have to, what I view totally unnecessary calls to the Windows API etc.  Maybe it will be fixed properly in version 10 or some other update to that, but I wouldn’t hold your breath!

Anyway I hope it’s useful to you and stops you burning lots of valuable dev time!

ESRI Snippet: Adding a graphic to a map

Really handy sometimes for debugging to make sure you are dealing with the correct geometry:

Public Sub AddGraphicToMap(ByVal map As IMap, ByVal geometry As IGeometry)

 Dim graphicsContainer As IGraphicsContainer
 Set graphicsContainer = map

 Dim element As IElement
 Set element = Nothing

 Dim rgbColor As IRgbColor
 Set rgbColor = New rgbColor
 With rgbColor
 .Red = 111
 .Green = 111
 .Blue = 111
 .UseWindowsDithering = True
 End With

 Dim simpleFillSymbol As ISimpleFillSymbol
 Set simpleFillSymbol = New simpleFillSymbol

 simpleFillSymbol.Color = rgbColor
 simpleFillSymbol.Style = esriSFSForwardDiagonal

 Dim fillShapeElement As IFillShapeElement
 Set fillShapeElement = New PolygonElement

 fillShapeElement.Symbol = simpleFillSymbol
 Set element = fillShapeElement

 If Not (element Is Nothing) Then
 element.geometry = geometry
 graphicsContainer.AddElement element, 0
 End If

End Sub

ESRI Code Snippet: Find Layer by Name

A handy code snippet from Neil Clemmons who is a prolific trouble shooter on the ESRI forums.  Sometimes it’s handy to have a bit of VBA code to find a layer by name and this code does the job very well.  Of course you can apply the same logic in any language and application context you see fit.

Public Function GetLayerByFeatureClass(vMap As Variant, sFeatureClassName As String, Optional bMessages As Boolean = True) As ILayer
     Dim pApplication As IApplication
     Dim pMxDocument As IMxDocument
     Dim pCompositeLayer As ICompositeLayer
     Dim pMap As IMap
     Dim l As Long
     Dim m As Long
     
     On Error GoTo ErrorHandler
     
     ' Get map reference from vMap parameter.
     If TypeOf vMap Is IMxApplication Then
          Set pApplication = vMap
          Set pMxDocument = pApplication.Document
          Set pMap = pMxDocument.FocusMap
     ElseIf TypeOf vMap Is IMxDocument Then
          Set pMxDocument = vMap
          Set pMap = pMxDocument.FocusMap
     ElseIf TypeOf vMap Is IMap Then
          Set pMap = vMap
     Else
          If bMessages Then MsgBox "vMap parameter does not implement an interface supported by this function.", vbOKOnly, "GetLayerByFeatureClass()"
          
          Exit Function
     End If
     
     ' Process each layer in the map.
     For l = 0 To pMap.LayerCount - 1
          Set GetLayerByFeatureClass = LayerByFeatureClass(pMap.Layer(l), sFeatureClassName, bMessages)
          If Not GetLayerByFeatureClass Is Nothing Then Exit Function
     Next l
     
     ' Give layer not found message.
     If bMessages Then MsgBox "The layer '" & sFeatureClassName & "' could not be found in the map.", vbOKOnly, "GetLayerByFeatureClass()"
Exit Function
ErrorHandler:
     If bMessages Then ErrorMessage "modAOUtilities:GetLayerByFeatureClass()"
End Function

' Written by: Neil Clemmons
' Date Created: 12/26/2002
' Date Modified:
' Description:  Helper function for GetLayerByFeatureClass().  Uses recursion to find layers that may be inside nested
'               group layers.  If this function returns a value other than Nothing, the layer has been found and
'               the search will stop.  Otherwise, the search will continue until all layers have been processed.
Private Function LayerByFeatureClass(pLayer As ILayer, sName As String, bMessages As Boolean) As ILayer
     Dim pDataset As IDataset
     Dim pReturnLayer As ILayer
     Dim pFeatureLayer As IFeatureLayer
     Dim pCompositeLayer As ICompositeLayer
     Dim l As Long
     
     On Error GoTo ErrorHandler
     
     ' If layer is a feature layer, get it's underlying dataset.
     If TypeOf pLayer Is IFeatureLayer Then
          Set pFeatureLayer = pLayer
          Set pDataset = pFeatureLayer.FeatureClass
          
          ' Set return layer and exit if this layer is the layer being sought.
          If UCase$(pDataset.Name) = UCase$(sName) Then
               Set LayerByFeatureClass = pLayer
               Exit Function
          End If
     End If
     
     ' Exit if layer is not a group layer.  Not setting return value will cause search to continue.
     If Not TypeOf pLayer Is IGroupLayer Then Exit Function
     
     ' Process each layer in the group layer.
     Set pCompositeLayer = pLayer
     For l = 0 To pCompositeLayer.Count - 1
          ' Use recursion to traverse nested group layers.
          Set pReturnLayer = LayerByFeatureClass(pCompositeLayer.Layer(l), sName, bMessages)
          
          ' If layer was found, set return value and exit.
          If Not pReturnLayer Is Nothing Then
               Set LayerByFeatureClass = pReturnLayer
               Exit Function
          End If
     Next l
Exit Function
ErrorHandler:
     If bMessages Then ErrorMessage "modAOUtilitiesLayerByName()"
End Function

Update: Bing Maps and Ordnance Survey data in ArcGIS Server 9.3.1

Apparently it is a known problem for OS data to not line up with Bing Maps service and in order to get round this you have to re-project all your British National Grid data to the WGS 1984 Web Mercator (Auxiliary Sphere) coordinate system.  How rubbish is that!

Anyway this problem is apparently known to ESRI and it is apparently fixed in verison 10 (it was tested by ESRI support in a beta 10 version).  So I guess we will have to wait until thats out and a few service packs have been released until we can feel confident with rolling out version 10.

On that note I’ll talk about this issue in about another year to 18 months (I would have thought!)