Jim Jackson

Character Counts.
Do the right thing.

Sponsors

March 2009 - Posts

Silverlight 2 Drag and Scroll Image

This is somewhat based on Shawn Wildermuth’s blog post for drag and drop. I had two problems with his post.

  1. I’m not scrolling a canvas.
  2. If your mouse leaves the control with the button down you are still dragging when your mouse reenters the control.

So here’s my tweaked version:

The control has a scroll viewer as the root element and a simple grid with one column and one row inside that. There is a large image inside the grid cell. There are events firing from the grid’s mouse down, up, move and grid leave events.

<UserControl x:Class="TestMouseScroll.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Width="300" Height="300" Background
="Transparent" >
  <ScrollViewer
    x:Name="scrollViewImage"
    HorizontalScrollBarVisibility="Visible"
    VerticalScrollBarVisibility
="Visible" >
    <Grid
      x:Name="gridImageContainer"
      Background="Gray"
      ShowGridLines
="True"
      MouseLeftButtonDown
="grid_MouseLeftButtonDown"
      MouseLeftButtonUp="grid_MouseLeftButtonUp"
      MouseMove
="grid_MouseMove"
      MouseLeave
="grid_MouseLeave"
   
  >
      <Grid.ColumnDefinitions
>
        <ColumnDefinition></ColumnDefinition
>
      </Grid.ColumnDefinitions
>
      <Grid.RowDefinitions
>
        <RowDefinition></RowDefinition
>
      </Grid.RowDefinitions
>
      <Image Source
=http://www.inetres.com/gp/military/infantry/rifle/M107/M107_1.jpg
         Grid.Row="0" Grid.Column="0" >
      </
Image
>
    </Grid
>
  </ScrollViewer
>
</
UserControl
>

The code looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace TestMouseScroll
{
  public partial class Page : UserControl
  {

    bool isTracked = false;
    Point startDrag;

    public Page()
    {
      InitializeComponent();
    }

    private void grid_MouseLeftButtonDown(object sender,
      MouseButtonEventArgs e)
    {
      isTracked = true;
      // Get the starting mouseposition based on
      // where the mouse is positioned on the image,
      // not the relative postion inside the scroller
      startDrag = e.GetPosition(gridImageContainer);
    }

    private void grid_MouseLeftButtonUp(object sender, 
      MouseButtonEventArgs e)
    {
      isTracked = false;
    }

    private void grid_MouseLeave(object sender, MouseEventArgs e)
    {
      isTracked = false;
    }

    private void grid_MouseMove(object sender, MouseEventArgs e)
    {
      if (isTracked)
      {
        Point newPos = e.GetPosition(scrollViewImage);
        try
        {
          scrollViewImage.ScrollToHorizontalOffset(startDrag.X - newPos.X);
          scrollViewImage.ScrollToVerticalOffset(startDrag.Y - newPos.Y);
        }
        catch { }
      }
    }
  }
}

I think this is pretty straight forward and works nicely for me.

Incidentally, the Barrett was a sniper rifle introduced into the T/E of my unit some time between ‘91 and ‘93. I don’t know exactly when but it wasn’t there the first time I deployed and it was the second time. Used primarily as a vehicular countermeasure, it is still in use today. It’s an awesome addition to any infantry or STA unit firing accurately in the multi-kilometer range (v/s ~1,000 meters for standard 7.62). Note that the kevlar helmet has a USMC Corporal’s rank insignia on it. That’s E4. So a 21 year old professional warrior is trained and deployed with this little monster. Happens every day. There are people who have served in Congress and the House for many decades who cannot be trusted with a spitwad in a McDonalds straw but they have the power to send these fine gentlemen into harms way to voluntarily perform honorably and under stresses and conditions that would make normal men soil themselves. My point is that there ought to be a qualification process to be allowed to make our laws similar to the qualification it takes to become a Marine Sniper. An old Marine can wish…

Silverlight 2 Sys.InvalidOperationException: ImageError error #4001 in control Xaml1

I got this last night while trying to load 2,150 images into a grid. I’m working on a map tiling control for my application because the maps are very large and custom stitched for my application. Incidentally, when loaded up, my IE windows consumes 1.3 Gb of Ram. Um, probably not gonna work in production…

Anyway, it appears that there is a mechanism internal to the System.Windows.Media.Imaging.BitmapImage object that, when instanced using a Uri, will go in search of the thumbs.db object. I have not looked into the source code for this library and I have not tested to see if this is also happening when on the internet but it doesn’t really matter because it is an easy solution.

To resolve the issue I just opened the images folder in IE and viewed it in thumbnail mode. This magically generates the thumbs.db file and the error goes away when I run my application again.

Incidentally, Xaml1 is the auto-generated name of the <asp:Silverlight /> control that is dropped into the test aspx page.

Using Linq to XML with C# to Read Gpx Files

GPX is the standardized file format for GPS file exchanges. A GPX file can contain a lot of different kinds of information. Take a look at the schema here. In general, the major things that you will work with are:

Waypoints

A waypoint is a specific position that is manually marked by a user for future reference. So when you get to the suspension bridge, mark a waypoint and you can find it again later as well as tell everyone else about it.

Tracks

Tracks are where you've been. When I want to mark out a trail for users of my application, I set up my GPS on my bike and just go for a ride. GPS antennae have come a long way in the last few years and my inexpensive Garmin eTrex keeps pretty accurate markers even when I'm in a deep draw. When I get home I have a complete listing of a few hundred points on my route, depending on how far apart or how long to wait I've preset my GPS to mark between saved track points.

Routes

A route is what you load into your GPS. It's essentially a list of positions you build by looking at a map or a file you get from someone else's track. When loaded, it will direct you to each point along the route in the appropriate order.

My Garmin saves files in a GDB format which is proprietary for the product. I load this file onto a machine with Garmin MapSource and immediately save the file as a GPX. This gets it into the standardized format that nearly all other GPS units and mapping software can use and I'm ready to load my data. At the end of this post is a well formed (but incomplete) GPX file. The original file had about 6,500 lines in it.

The Code

It's really pretty straight forward once you realize that you need to pull in the namespace object and then include it in each call to an element. My initial run at this netted attribute values but no element values which was really frustrating. Also, when working with Xml, remember that an element that doesn't exist results in a null object reference so you'll see in the code how I handled that for each element. The biggest issue for me with Linq is the inability to debug line-by-line. Still it's crazy fast and I'm loading a lot of data in only a few lines of code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace LinqXMLTester
{
  public class GPXLoader
  {
    /// <summary>
    /// Load the Xml document for parsing
    /// </summary>
    /// <param name="sFile">Fully qualified file name (local)</param>
    /// <returns>XDocument</returns>
    private XDocument GetGpxDoc(string sFile)
    {
      XDocument gpxDoc = XDocument.Load(sFile);
      return gpxDoc;
    }

    /// <summary>
    /// Load the namespace for a standard GPX document
    /// </summary>
    /// <returns></returns>
    private XNamespace GetGpxNameSpace()
    {
      XNamespace gpx = XNamespace.Get("http://www.topografix.com/GPX/1/1");
      return gpx;
    }

    /// <summary>
    /// When passed a file, open it and parse all waypoints from it.
    /// </summary>
    /// <param name="sFile">Fully qualified file name (local)</param>
    /// <returns>string containing line delimited waypoints from
    /// the file (for test)
</returns>
    /// <remarks>Normally, this would be used to populate the
    /// appropriate object model
</remarks>
    public string LoadGPXWaypoints(string sFile)
    {
      XDocument gpxDoc = GetGpxDoc(sFile);
      XNamespace gpx = GetGpxNameSpace();

      var waypoints = from waypoint in gpxDoc.Descendants(gpx + "wpt")
              select new
              {
                Latitude = waypoint.Attribute("lat").Value,
                Longitude = waypoint.Attribute("lon").Value,
                Elevation = waypoint.Element(gpx + "ele") != null ?
                    waypoint.Element(gpx + "ele").Value : null,
                Name = waypoint.Element(gpx + "name") != null ?
                    waypoint.Element(gpx + "name").Value : null,
                Dt = waypoint.Element(gpx + "cmt") != null ?
                    waypoint.Element(gpx + "cmt").Value : null
              };

      StringBuilder sb = new StringBuilder();
      foreach (var wpt in waypoints)
      {
        // This is where we'd instantiate data
        // containers for the information retrieved.
        sb.Append(
          string.Format("Name:{0} Latitude:{1} Longitude:{2} Elevation:{3} Date:{4}\n",
          wpt.Name,wpt.Latitude,wpt.Longitude,
          wpt.Elevation, wpt.Dt));
      }

      return sb.ToString();
    }

    /// <summary>
    /// When passed a file, open it and parse all tracks
    /// and track segments from it.
    /// </summary>
    /// <param name="sFile">Fully qualified file name (local)</param>
    /// <returns>string containing line delimited waypoints from the
    /// file (for test)</returns>
    public string LoadGPXTracks(string sFile)
    {
      XDocument gpxDoc = GetGpxDoc(sFile);
      XNamespace gpx = GetGpxNameSpace();
      var tracks = from track in gpxDoc.Descendants(gpx + "trk")
             select new
             {
               Name = track.Element(gpx + "name") != null ?
                track.Element(gpx + "name").Value : null,
               Segs = (
                    from trackpoint in track.Descendants(gpx + "trkpt")
                    select new
                    {
                      Latitude = trackpoint.Attribute("lat").Value,
                      Longitude = trackpoint.Attribute("lon").Value,
                      Elevation = trackpoint.Element(gpx + "ele") != null ?
                        trackpoint.Element(gpx + "ele").Value : null,
                      Time = trackpoint.Element(gpx + "time") != null ?
                        trackpoint.Element(gpx + "time").Value : null
                    }
                  )
             };

      StringBuilder sb = new StringBuilder();
      foreach (var trk in tracks)
      {
        // Populate track data objects.
        foreach (var trkSeg in trk.Segs)
        {
          // Populate detailed track segments
          // in the object model here.
          sb.Append(
            string.Format("Track:{0} - Latitude:{1} Longitude:{2} " +
                         "Elevation:{3} Date:{4}\n"
,
            trk.Name, trkSeg.Latitude,
            trkSeg.Longitude, trkSeg.Elevation,
            trkSeg.Time));
        }
      }
      return sb.ToString();
    }
  }
}


 

GPX Sample File

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<
gpx xmlns="http://www.topografix.com/GPX/1/1"
   creator="MapSource 6.13.7"
   version="1.1"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.garmin.com/xmlschemas/GpxExtensions/v3
       http://www.garmin.com/xmlschemas/GpxExtensions/v3/GpxExtensionsv3.xsd
       http://www.topografix.com/GPX/1/1
       http://www.topografix.com/GPX/1/1/gpx.xsd
">

  <
metadata>
    <
link href="http://www.garmin.com">
      <
text>Garmin International</text>
    </
link>
    <
time>2009-03-08T20:11:54Z</time>
    <
bounds
      maxlat="39.2971185"
      maxlon="-76.6951826"
      minlat="39.2035537"
      minlon="-76.8203088"/>
  </
metadata>

  <
wpt lat="39.2445616" lon="-76.7194497">
    <
ele>88.8067627</ele>
    <
name>001</name>
    <
cmt>16-SEP-08 8:50:11AM</cmt>
    <
desc>16-SEP-08 8:50:11AM</desc>
    <
sym>Residence</sym>
    <
extensions>
       <
gpxx:WaypointExtension
       xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
         <
gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode>
      </
gpxx:WaypointExtension>
    </
extensions>
  </
wpt>

  <
wpt lat="39.2422711" lon="-76.7213488">
    <
ele>38.3380127</ele>
    <
name>009</name>
    <
cmt>16-SEP-08 9:40:46AM</cmt>
    <
desc>16-SEP-08 9:40:46AM</desc>
    <
sym>Residence</sym>
    <
extensions>
      <
gpxx:WaypointExtension
      xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
        <
gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode>
      </
gpxx:WaypointExtension>
    </
extensions>
  </
wpt>

  <
trk>
    <
name>Vinyard Springs Loop</name>

    <
extensions>
      <
gpxx:TrackExtension
     
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
        <
gpxx:DisplayColor>Transparent</gpxx:DisplayColor>
      </
gpxx:TrackExtension>
    </
extensions>

    <
trkseg>
      <
trkpt lat="39.2446415" lon="-76.7199907">
        <
ele>60.2197266</ele>
        <
time>2009-03-08T13:18:25Z</time>
      </
trkpt>
      <
trkpt lat="39.2445078" lon="-76.7193838">
        <
ele>88.0980225</ele>
        <
time>2009-03-08T13:19:23Z</time>
      </
trkpt>
      <
trkpt lat="39.2440145" lon="-76.7200792">
        <
ele>81.3687744</ele>
        <
time>2009-03-08T13:19:43Z</time>
      </
trkpt>
      <
trkpt lat="39.2435182" lon="-76.7208523">
        <
ele>82.3300781</ele>
        <
time>2009-03-08T13:20:04Z</time>
      </
trkpt>
      <
trkpt lat="39.2427701" lon="-76.7212304">
        <
ele>78.9654541</ele>
        <
time>2009-03-08T13:20:52Z</time>
      </
trkpt>
      <
trkpt lat="39.2417241" lon="-76.7208448">
        <
ele>65.0263672</ele>
        <
time>2009-03-08T13:21:43Z</time>
      </
trkpt>
    </
trkseg>
  </
trk>

  <
trk>
    <
name>Patapsco Boundry Ref</name>

    <
extensions>
      <
gpxx:TrackExtension
      xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
        <
gpxx:DisplayColor>DarkRed</gpxx:DisplayColor>
      </
gpxx:TrackExtension>
    </
extensions>
   
    <
trkseg>
      <
trkpt lat="39.2253216" lon="-76.7073387">
        <
ele>46.2806396</ele>
      </
trkpt>
      <
trkpt lat="39.2248415" lon="-76.7065894">
        <
ele>43.3966064</ele>
      </
trkpt>
      <
trkpt lat="39.2194385" lon="-76.7042182">
        <
ele>17.4411621</ele>
      </
trkpt>
      <
trkpt lat="39.2188285" lon="-76.7043919">
        <
ele>15.0377197</ele>
      </
trkpt>
      <
trkpt lat="39.2183665" lon="-76.7051846">
        <
ele>14.0764160</ele>
      </
trkpt>
      <
trkpt lat="39.2180424" lon="-76.7061647">
        <
ele>12.6343994</ele>
      </
trkpt>
      <
trkpt lat="39.2177812" lon="-76.7070982">
        <
ele>11.1925049</ele>
      </
trkpt>
      <
trkpt lat="39.2485097" lon="-76.8098993">
        <
ele>127.5119629</ele>
      </
trkpt>
    </
trkseg>
  </
trk>

</
gpx>


 

Posted: Mar 10 2009, 09:10 PM by axshon | with 6 comment(s)
Filed under: , , ,
SQL 2008 Geography and Geometry Data Type Problems

My plan was to take my application into SQL 08 from 05 since my hosting service now has it available. I was intent on killing all of my lat/long information and storing my geospatial data in geography columns.

Here is what I’m finding about the Geography and Geometry data types. Initially, I expected to that once in, I’d be able to get everything back out exactly as entered.

Not so much…

Here is what I am finding so far. When/if I figure each of these out I’ll post an update.

  1. Geography and Geometry are across a uniform surface. That means no elevation. So if I’m trying to track the distance between points A and B I can only do it as if looking directly down at a map.
  2. Data cannot be read by humans from geography and geometry types. Put the value in and see what it looks like when you select it back out. Not a big deal but it sucks when you’re trying to QA input procedures that have 600 points on a single track.
  3. Similar to the previous item, data is for input only. If I put in a geography point, I cannot easily extract just the latitude only from that column. I can do it by parsing and using .ToString() but ugh!

So in my data I’ll be storing the latitude, longitude, elevation and ALSO the geography-coded value for the latitude/longitude.

The upside is that it should make it easier to calculate trip plans for individual users since this is part of my plan to try and pay for that big expensive camera that I’ll be using to snap a zillion panoramas. In the end, what I’ve found is that the geography and geometry data types are primarily useful for making geospatial calculations, not for viewing the actual geospatial data itself.

More Posts