It’s Such a Drag

So, in my day job as a GIS developer, I’ve been making an addin for ArcGIS. Nothing fancy, just a data loader to make it easier for our users to pick out the exact item they want from the many datasets we provide. Choose a few categories, select some items from the list, click the load button, done! But what if you want to just drag the items onto the map? ArcObjects doesn’t appear to offer drag and drop straight out of the box, but it is possible. If you’re also developing ArcGIS addins, I’ll save you some time and explain how it is done.

Implementing drag and drop in .NET is actually pretty straightforward. There are two parts to the transaction – the drag source (the control you are dragging from) and the drop target (the control you are dragging to). All you have to do is add an ItemDrag event to your source control (which calls DoDragDrop to start the drag action), then enable the AllowDrop property on your target control, and add the DragDrop event to handle the dropped item. There are some other events that can be handled, including DragEnter, DragLeave, DragOver, GiveFeedback, and QueryContinueDrag, but that is the basic procedure.

When developing addins for ArcGIS, the drop target is already handled for you – the main window in ArcMap is listening for drop events, such as dropping mxds or datasets from the Catalog window or from ArcCatalog. All you have to do is package your drag source into the correct format and you’re good to go. It couldn’t be easier, right?

Well, it all depends on being able to format the dragged item correctly. As this post from The Sandpit states, ArcMap accepts two kinds of input. When dragging from ArcCatalog, an ESRI Names object is created, which is an enumerator that provides the names of the datasets to be imported. Dragging from ArcMap on the other hand, creates an ESRI Layers object, which is a collection of layers and tables. Apparently setting the drag source to an ESRI Layers object is not supported, and in my addin we are just loading datasets from the database by name, so it looks like I need to use ESRI Names.

Incidentally, if you’re doing any development with clipboard objects, I found two helpful applications. Clipboard Viewer shows whatever is currently on the clipboard and DataObjectViewer shows whatever has just been dragged onto it. You can use either of these to drag a selection of layers or datasets from ArcMap or ArcCatalog to confirm which object type is being used.

So, the example code on the Sandpit post went some way towards constructing an ESRI Names object and setting it as the drag source, but it relied on a third-party dll which does not work on recent versions of ArcGIS. However, according to this Geonet post, you can instead use a MemoryStream to pass the IEnumName to a DataObject, then use the DataObject as the drag source.

I did this, and it worked! The dataset name dropped onto the map and was loaded as a layer. However, the debugger showed a log of exceptions with the message, “A first chance exception of type ‘System.Runtime.InteropServices.COMException’ occurred in System.Windows.Forms.dll”. This is not ideal – I always try to get my code to run as cleanly as possible – so I took a closer look at the DataObjectViewer page above, as it goes into a lot of detail about DataObjects and how to construct one from scratch. I managed to do this, but it didn’t make any difference unfortunately. The consensus among developers on Stack Overflow seems to be that first chance exceptions can be ignored as users don’t see them. Hey ho, as long as it works, right?

The full code is shown below. lvDatasets is the ListView that contains the dataset names to be dragged. To keep this example simple, it has been set up so all the items added to it are IDatasetNames. It may be more useful though, to create a custom class to hold the IDatasetName, the alias, and other related information.

    using System;
    using System.IO;
    using System.Windows.Forms;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.esriSystem;
    using ESRI.ArcGIS.Geodatabase;
    using DataObject = System.Windows.Forms.DataObject;

    private void LoadSelectedLayers()
    {
        // declare name objects to method scope and populate if dragging
        INameFactory nameFactory = null;
        IEnumName enumName = null;
        IEnumNameEdit enumNameEdit = null;
        DragDropEffects dropStatus = DragDropEffects.All;
        nameFactory = new NameFactoryClass();
        enumName = new NamesEnumeratorClass();
        enumNameEdit = (IEnumNameEdit)enumName;

        // iterate backwards over selected items so they are added in reverse order (so TOC order is same as list view order)
        for (int i = lvDatasets.SelectedItems.Count - 1; i >= 0; i--)
        {
            IDatasetName datasetName = (IDatasetName)lvDatasets.SelectedItems[i];
            IName tempName = (IName)datasetName;
            enumNameEdit.Add(tempName);
        }

        // Create a new data object.
        DataObject myDataObject = new DataObject();

        // Add the names to the DataObject.
        MemoryStream memoryStream = new MemoryStream((byte[])nameFactory.PackageNames(enumName));
        myDataObject.SetData("ESRI Names", memoryStream);

        // set the data as a drop source
        dropStatus = lvDatasets.DoDragDrop(myDataObject, DragDropEffects.All);

        if (dropStatus != DragDropEffects.None)
        {
            // reset list view
            ClearSelectedItems();
        }
    }

 

Advertisements

Could not load file or assembly

So, I changed jobs recently* and have been mostly brushing off my C# skills to write ArcGIS Addins. This has been largely frustrating, as Esri and Microsoft have joined forces to make things as difficult as possible. For example:

 

 

This error message was particularly annoying, as it is fairly ambiguous. The full text isn’t much more enlightening:

Could not load file or assembly xxxx or one of its dependencies. The system cannot find the file specified. C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\TeamTest\Microsoft.TeamTest.targets

If only there was a way for the system to tell my which dependency it was having a problem with. Never mind, I’ll go through them all one by one and get there eventually.

For the record, the culprit was ESRI.ArcGIS.Desktop.AddIns, and the solution is to change the CopyLocal flag to true. Makes sense that if the file isn’t copied locally, it can’t be found.

The question is, why was CopyLocal set to false in the first place? I’m sure it was true last time I looked. Not only that, but this bug has come back a few times. At least I know what the fix is this time. I still didn’t know why that flag kept changing, even though I’ve done many searches.

Finally today, I worked out what was going on – the CopyLocal flag is reset every time you update the file Config.esriaddinx. Why should it do this? Who knows, but at least I know the solution now, and I’ve written it down, so when I start searching for the answer in three months time, I’ll find it!

*Actually four months ago, but geologically speaking, that’s just a blink of an eye.

Thinking inside the box

This week’s @visualoop Vintage Infodesign roundup had a number of interesting entries, but this Soviet admin map caught my eye:

[Original image can be found here.]

Main Administration Regions of the Soviet Union in 1944

It reminds me of a map I made a few years ago. I had a dream where I saw a completely rectangular map of the lower 48 states of the USA. Perhaps I’d been reading too much Dr Seuss, but it made perfect sense, so when I woke up, I tried to recreate it.

Rectangular USA
Amazing, isn’t it?

I took a US States dataset and edited each state polygon down to just four points, then moved them around and added one or two more points where necessary. The result is quite pleasing, and most of the states are still a recognisable shape, though there are some notable exceptions. When distorting features in this way, there has to be some compromise and in the end, I chose to prioritise the state areas, then try to preserve  the shapes as much as possible. Some states, like Florida and Texas were never going to fit neatly into a rectangle, but I can at least make sure they stay the same size.

Let’s take a closer look at how the new “improved” map compares with the old.

USA NE

New England doesn’t really work at all when compressed into a rectangle, but most of the borders stay intact. Vermont and New Hampshire have shuffled westwards, so they now border New York instead of Massachusetts and Maine has moved across to take their place.

USA Midwest

Not such a bad fit in the Midwest, although the Great Lakes have dried up and Michigan now has a land border with New York.

USA NW

The larger states in the north west are probably the closest match to their original outlines.

USA SW

Likewise the southwest, though Texas has had a large part sliced off.

USA SE

Florida is probably the worst victim of this arrangement, but the panhandle was never going to fit into a neat rectangle.

Anyway, enough silliness for today, let’s get back to work.

Labelling related features

There hasn’t been much GIS on this blog recently, so I’m going to talk about an interesting problem I saw recently. A user was trying to add some road labels to a map, but wanted the label to show the name of the next town along the road, much like the John Ogilby strip maps below: “to Thorp”, etc.

Ogilby 1675  Ogilby Garstang-Carlisle

The house style he was working with showed districts, roads and cities, but only a single district of interest was shown at one time. No features were visible outside this district polygon. I tried to achieve the effect he was after, and I think I managed, although the method isn’t completely straightforward. I’m going to go through the required steps here, using one of my US datasets. Obviously, you’ll need to change some of the layer and attribute names if you want to follow the example with your own data.

Open Arcmap and add the data to the map. In my case, a point layer (Cities), a line layer (USHigh), and a polygon layer (States).We only need to show a single state of interest, so right-click States in the Table of Contents (TOC) > Properties > Definition Query tab and enter the query: Name = ‘Indiana’.

My data is showing too many cities, so I’ll thin them out in the same way, using a definition query to only show features with a population greater than 100,000.

Image 1

Now the highways need to be clipped so only the features within Indiana are showing. Open the Clip Geoprocessing (GP) tool and run with the following inputs:

Input:USHigh
Clip: States
Output:USHigh_Indiana

Image 2

It gets a bit tricky here, as Maplex can only place labels at the beginning or the end of a line. Unfortunately, some of the lines will need labels at the beginning and some at the end, depending upon which direction they were digitised. You may be lucky and find that all the features were digitised starting at the centre of the state and moving towards the boundary (or vice versa), but it’s not very likely. Open the Label Manager and set the Label Field for USHigh_Indiana to OBJECTID, Label position should be Centered curved, and the properties in the Offset dialog should look like this to push the label off the end of the line:

Offset properties

It is also a good idea to go to the Label Density tab, uncheck Connect features, and make sure the combo is set to One label per feature. It’s likely that some roads will point to the same city, so we don’t want Maplex to “helpfully” connect up those features and remove some labels

Image 3

As you can see, some of the labels are placed at the end of the line features, outside the state boundary, but some are at the other end of the line, inside the state. These latter labels will need to be placed differently, using the After end of line option. Create two new label classes on the USHigh_Indiana layer, called Start and End, and change the label offset to After end of line for the End label class.  Then create SQL queries for each class to select the correct features (Label Manager > SQL Query button). Now you should have one label coming off the end of each highway.

Image 4

Now we need to relate each highway feature to the nearest city. We don’t want to consider the cities within Indiana, so we need to create a selection layer that only contains cities outside the state. There are two ways of doing this: select all then remove the features within the state, or select the features within the state, then switch the selection. I’m going to use the second method.

Go to Selection > Select by location. Selection method = select features from, Target layer = Cities, Source layer = States, Spatial selection method = intersect the source feature layer. Click OK.

Right-click Cities in the TOC > selection > switch selection

Right-click Cities in the TOC> selection > create layer from selected features

Now we can run the Near GP tool with the following inputs:

Input features: USHigh_Indiana
Near features: Cities selection

This creates a field called NEAR_FID on each highway feature, which can be used to create a join to the Cities layer.

Right-click USHigh_Indiana in the TOC > Properties > Joins & Relates tab > Joins > Add… 1. Layer join field = NEAR_FID, 2. Table = Cities selection, 3. Table join field = OBJECTID.

Now we can change the label field to Cities.CITY_NAME, or even better, a label expression of: “To “ & Cities.CITY_NAME. Also, the SQL queries will need to be updated so OBJECTID becomes USHigh_Indiana.OBJECTID. I find it easier to copy the whole query, paste into a text editor, such as Notepad++, do a find and replace, then copy and paste it back into the query window, rather than editing each clause in the query. [Note: some dialogs in Arcmap do have find and replace functionality, e.g. the Convert Labels to Annotation dialog. It’s not documented, but you can click on the Annotation Feature Class column and press ctrl-H to quickly change part of the class name for every layer at once.]

Image 5

This should give us the result we’re after, but something is not right. If you look at the western border of the state, the two higways that point to Springfield are actually labelled “To Cincinatti”, and the highway to the south is labelled “To Nashville-Davidson”, when it should be St. Louis. The problem is that the Near tool finds the nearest feature  to any part of the input feature – the eastern end of the highway is closer to Cincinatti than the western end is to Springfield.

To get round this, we could shorten the feature, so only the end near the border is used. To isolate part of the highway near the state boundary, we need to create a clip shape of the inner edge. First run the Feature to line GP tool with States as the input feature, then run the Buffer GP tool with a distance of 0.25 Degrees. Now Clip USHigh_Indiana, calling the output layer USHigh_Indiana_Labels. The state border (red), buffer (yellow), and clipped highways (green) are shown here.

Image 6

Rerun the steps above, using the USHigh_Indiana_Labels layer. Change the symbology to have no colour – we’ll leave the USHigh_Indiana layer on to show the location of the highways within the state (but make sure you turn off the labels for this layer).

Image 7

Now the highway labels are showing correctly, we need to declutter the map by removing the outside cities. Create another selection layer of just the cities within Indiana and label this instead. To copy the symbology from the Cities layer, go to Layer properties > Symbology tab > Import… and to copy the label properties over, go to the Label manager > right-click on Default label class of Cities layer > Copy parameters > right-click on Default label class of selection layer > Paste.

Image 8

As the highway labels are offset from the state boundary, you might get better results by clipping the portion of the highway that is immediately outside the state instead. I’ve done this here and left the longer highways showing, which may fit better with some house styles.

Image 9

So, in this blog post, I’ve shown how to label one feature layer using the attributes of nearby features from a different layer. Though the final setup needs to be done by hand, most of the steps in this process can be automated, using model builder or Python scripts. Maybe I should describe how to do that in a later post, though.

Linguistic Geographies

Just a quick post about something interesting I saw earlier today.

If you search Manuscripts Online for ‘map’, there are 1320 results to wade through, but the resources section also contains a link to the Gough Map – apparently one of the earliest maps to show Britain in a geographically-recognizable form. There is plenty of background reading about the origin and purpose of the map and also a section on the digitisation process (spoiler – they used ArcGIS).

The map itself is searchable by modern name, medieval name, or appearance, so here is an example shot of where I live. The place marker is on Skipton – symbolised with a largish castle and labelled skiptou(n) – the hamlet of Manchester (labelled manch..) is to the right, and Bradford – a single building marked bra[dford] – above. There isn’t much other detail in this area, but a number of rivers originate nearby, suggesting it is high ground. At the top left of the image is the vast metropolis of York (Eborienc), clearly one of the most important cities in the country at that time.

Gough Map

Gough Map © 2011 King’s College London

Complicated BASIC schemes

Nowadays, most of us have used Google Maps or OSM to get directions to somewhere or plan a route. With the right datasets, you can perform more detailed analysis, such as this recent Esri blog post showing how to calculate the slope of a hiking trail. It wasn’t always this easy though.

I recently picked up a copy of that 1987 classic, The Ordnance Survey Outdoor Handbook (Macmillan London Ltd, ISBN 0-333-42505-7). It’s actually quite a useful and detailed book, covering map scales, grid references, the country code, weather, first aid, and so on. It has geological, botanical, and sociological histories of Britain, and there are sections on identifying plants, animals, and landscapes. There is also a list of radio station frequencies (both MW and VHF), and phone numbers for weather forecasts (with 01 codes for the London numbers – remember them?).

The navigation chapter was particularly interesting – after discussing identifying landmarks, taking bearings, and navigating without a compass, the author moved on to determining the difficulty of a walk.

Apparently, many guidebooks show difficulty but unless you know the scheme being used, it may not be useful.

“There is an international standard scheme for grading the difficulty of climbs and mountain walks, and efforts are being made to devise and agree a similar standard system for all walkers that will apply throughout Europe. Until such a system appears, you will have to improvise as best you can, and the scheme offered here may provide a basis for you.”

A scheme to calculate the difficulty of a walk

To be honest, I’m struggling a bit with this scheme. It’s too complicated and the scoring system is fairly arbitrary. I can’t imagine anyone calculating the percentage of their route that covers metalled roads, open ground, muddy ground, large boulders, etc. I’ve always used Naismith’s Rule, which is much more straight forward, and gives a pretty good estimate of the time needed to complete the route.

As the text states, there is a BASIC program to help you calculate it (though it doesn’t help you calculate the percentages, and only allows you to enter up to four, rounded up to the nearest 25%). Nothing dates a publication more than using the latest technology, and this listing really makes the book feel like it’s from another era.

Calculating the difficulty of a walk

In the interests of bringing things bang up-to-date (and firmly placing them in the early 2010s), here’s a Python version, so we can all have a go. Now, is that muddy ground or stony ground?

print “CALCULATING THE DIFFICULTY OF A WALK”
print “How long is the walk? Is it:”
print “less than 6km? Type 1;”
print “6-10km? Type 2;”
print “11-15km? Type 3;”
print “16-25km? Type 4;”
print “more than 25km? Type 5.”
a=int(raw_input())

print “\n\nWhat is the terrain like? Select from the list”
print “below. After each selection, type on the next”
print “line .25, .5, .75, or 1, to show the proportion”
print “of the walk accounted for by each type. You may”
print “choose up to 4. Type a zero (0) and on the”
print “following line a 1 for any choices you do not”
print “use. Is the terrain:”
print “metalled road? Type 1;”
print “well-made path? Type 2;”
print “firm beach? Type 2;”
print “open ground? Type 3;”
print “muddy ground? Type 4;”
print “stony ground? Type 4;”
print “loose sand? Type 4;”
print “large boulders? Type 5;”
print “heather or tussocky ground with no path? Type 5;”
print “ice or snow? Type 6.”
b=int(raw_input())
c=float(raw_input())
d=int(raw_input())
e=float(raw_input())
f=int(raw_input())
g=float(raw_input())
h=int(raw_input())
i=float(raw_input())

print “\n\nHow much climbing and descending will you do?”
print “less than 50m? Type 1;”
print “51-100m? Type 2;”
print “101-300m? Type 3;”
print “301-500m? Type 4;”
print “501-700m? Type 5;”
print “701-1000m? Type 6;”
print “more than 1000m? Type 7.”
j=int(raw_input())

k=(b*c)+(d*e)+(f*g)+(h*i)
l=a*k*j
a=6-a
m=a*j
n=(l+m)/5

print “n=” + str(n)
print “\n\n”
if n<6:
print “The walk will be easy”
elif n>=6 and n<11:
print “The walk will be moderate”
elif n>=11 and n<16:
print “The walk will be fairly strenuous”
elif n>=16 and n<21:
print “The walk will be strenuous”
elif n>=21 and n<26:
print “The walk will be very strenuous”
elif n>=26:
print “The walk will be very strenuous and difficult”