SharpGIS

#GIS from a .NET developer's perspective

Using SharpMap as a Google Earth Network Link

Since Google Earth is so popular, I thought why not join in and have SharpMap serve maps for GE? It is quite simple to make your own GE Network Link, and here are a few pointers, and you can download a demo web application you can use as well.

The GE Network Link basically works by requesting an XML webpage (well Google calls it KML), telling GE where to get the image tiles and where they should be placed on the globe. GE also adds a 'BBOX=minx,miny,maxx,maxy' querystring to the request so you can return a custom KML response containing the most appropriate tile(s). A response could look like this:

<?xml version="1.0" encoding="UTF-8" ?>
<kml xmlns="
http://earth.google.com/kml/2.0">
<Document>
  <open>1</open>
  <GroundOverlay>
    <open>1</open>
    <Icon>
      <href>http://localhost/TileServer.ashx?BBOX=-180,-90,0,90&size=512</href>
    </Icon>
    <LatLonBox>
      <north>90</north>
      <south>-90</south>
      <east>0</east>
      <west>-180</west>
      <rotation>0</rotation>
    </LatLonBox>
  </GroundOverlay>
</Document>
</kml>

You can add as many "GroundOverlays" as you like. For instance you can cover a requested BBOX with as many tiles as you like. That way you can increase the image size/resolution or cover a larger or smaller area than initially requested while getting quicker responses. You can read more about the KML format here.

Creating the KML is pretty straight-forward in ASP.NET, so I wont cover that here (but download the example and see for yourself).

Creating the image tile is easily done using SharpMap. Add an HttpHandler to you webpage, and create the following ProcessRequest method to render and return the map:

public void ProcessRequest(HttpContext context)

{

    //Get tilesize in request url

    int tilesize = int.Parse(context.Request.QueryString["size"]);

    //Get boundingbox requested

    string[] strbox = context.Request.QueryString["BBOX"].Split(new char[] { ',' });

    SharpMap.Geometries.BoundingBox box = new SharpMap.Geometries.BoundingBox

            (double.Parse(strbox[0]), double.Parse(strbox[1]),

            double.Parse(strbox[2]), double.Parse(strbox[3]));

 

    //Call custom method that sets up the map with the requested tilesize

    SharpMap.Map myMap = MapHelper.InitializeMap(new System.Drawing.Size(tilesize, tilesize));

    //Center on requested tile and set the appropriate view width

    myMap.Center = box.GetCentroid();

    myMap.Zoom = box.Width;

 

    //Render the map

    System.Drawing.Bitmap b = (System.Drawing.Bitmap)myMap.GetMap();

    //Create a PNG image which supports transparency

    context.Response.Clear();

    context.Response.Expires = 10000000;

    context.Response.ContentType = "image/png";

    System.IO.MemoryStream MS = new System.IO.MemoryStream();

    //Set background to the transparent color which will be see-through in Google Earth

    b.MakeTransparent(myMap.BackColor);

    b.Save(MS, System.Drawing.Imaging.ImageFormat.Png);

    byte[] buffer = MS.ToArray();

    //Send image response

    context.Response.OutputStream.Write(buffer, 0, buffer.Length);

    // tidy up 

    b.Dispose();

    context.Response.End();

}

The last thing we need to do is add the network link to GE. The easiest way to do this is to do this from within GE. From the menu Select Add –> Network link. Type a name for your network link and set the location to the URL of the XML service. Under “View-Based Refresh” , set the “When” parameter to “After camera stops”, and click OK, and you should be set to go !

Unfortunately GE doesn't provide you with the same smooth image transitions that it use for its own tiles, so you will have to do with the rather crude way of showing big red squares until the tiles have been loaded. Furthermore the BBOX GE requests isn't always very accurate, especially if you are looking south at an angle you will notice the BBOX is very much off.

The small demo application you can download below shows a world map with each country’s population density shown on a colorscale from blue to red. Copy the files to a virtual directory (make sure it runs as its own web-application, at least the sub-folders are located at the root of the webapplication). Set the network-link to point to the URL of the default.aspx page.

Download GoogleEarthServer.zip (270,65 KB)

GE also supports vectordata to be served as KML. It could be interesting to see who comes up with a "SharpMap KML Vector Server" first :-)

Delaunay Triangulation in .NET 2.0

Based on an article by Paul Bourke, I've created a small light-weight .NET 2.0 library to triangulate point data. The library includes a small Windows Forms example showing how the library works.

Using the generic point type 'Point<T>' you can triangulate points with any attribute added to it. For instance to triangulate points with a Z-height, create a list with double-types for Z like this:

  List<Point<double>> Vertices = new List<Point<double>>();
//Add vertices ...
//Do triangulation
List<Triangulator.Geometry.Triangle> tris = Triangulator.Delauney.Triangulate(Vertices);


Otherwise you can just derive from and extend the 'Geometry.Point' class to triangulate points with more methods and properties.

 

The Delauney triangulation doesn't handle duplicate points very well (that is multiple points whose X and Y properties are equal). Luckily we now have anonymous methods to set up a simple predicate to check if we already have a point in my list before I add it:

  Triangulator.Geometry.Point pNew = new Triangulator.Geometry.Point(234.4,782.1); //New point to add
if(!Vertices.Exists(delegate(Triangulator.Geometry.Point p) { return pNew.Equals2D(p); }))
Vertices.Add(pNew);

 

The library can be downloaded here including a small demo-app. Feel free to use it as you like.

Using GDAL from C#

Recently Frank Warmerdam and Michael Paul posted some examples on calling GDAL from .NET. This is pretty cool, since this enables you to access a great set of tools that I for one have been accessing by generating batch-files in a .NET application and then running them using the shell-commands (not pretty but it worked :-)

Their posts used several links to pointers and didn't have automatical garbage collection. Since disposing objects is very important when calling unmanaged code, and .NET developers isn't that used to pointers, I decided to wrap some of the GDAL functionaly into a few classes with automatic disposer etc. It still needs some brushing off and to include some more GDAL functionality, but let me know what you think. You can download it here.

Differential GPS using RTCM from NTRIP

This post will focus on how to get data from a NTRIP RTCM datastream. RTCM can greatly enhance the accuracy of your GPS receiver by downloading live correction data from the Internet using the NTRIP protocol. This example has been tested on Pocket PC 2002, but should work any other .NET capable OS as well.

The NTRIP data stream is basically an ordinary HTTP stream. My initial thought was just to use .NET's System.Net.WebRequest class to download the stream, but I quickly ended up with an infinite loop. The reason for this, is that the NTRIP stream never ends, and the WebRequest therefore never finishes.

Instead we have to do a bit more work ourselves. Luckily we have System.Net.Sockets to helps us out here. Lets initialize the socket, by calling a server at IP address '129.217.182.51' through port 80:

IPAddress BroadCasterIP = IPAddress.Parse("129.217.182.51");
int BroadCasterPort = 80;
 Socket sckt = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
 sckt.Blocking = true;
 sckt.Connect(new IPEndPoint(BroadCasterIP,BroadCasterPort));

The next thing we will have to do, is to send a request to the server, telling them what we want. In this case I'm requesting the datastream "FLEN0".

string msg = "GET /FLEN0 HTTP/1.1\r\n";
 msg += "User-Agent: NTRIP iter.dk\r\n";
 msg += "Accept: */*\r\nConnection: close\r\n";
 msg += "\r\n";

If the stream uses authentification, we would need to add the following, where username and password are strings containing, well... the username and password (surprise!). We need to encode the username and password to Base64 as well. A small funktion is provided for this. The request will then look like this:

string auth = ToBase64(username + ":" + password);
 string msg = "GET /FLEN0 HTTP/1.1\r\n";
 msg += "User-Agent: NTRIP iter.dk\r\n";
 msg += "Authorization: Basic " + auth + "\r\n";
 msg += "Accept: */*\r\nConnection: close\r\n";
 msg += "\r\n";
 [...]
 private string ToBase64(string str) {
   Encoding asciiEncoding = Encoding.ASCII;
   byte[] byteArray = new byte[asciiEncoding.GetByteCount(str)];
   byteArray = asciiEncoding.GetBytes(str);
   return Convert.ToBase64String(byteArray,0,byteArray.Length);
}

OK, now lets send the request:

byte[] data = System.Text.Encoding.ASCII.GetBytes(msg);
 sckt.Send(data);

Well now two things might happen. Either we start receiving the NTRIP RTCM stream, OR if we made some kind of error, we will receive a list of available data streams instead. I will not cover how to decode the data, but just show you how to pass the data on to your GPS. It's actually pretty straight forward. First of all, to receive the data, you can use the Sockets.Receive method:

byte[] returndata = new byte[256];
 sckt.Receive(returndata);

If the data returned is valid RTCM you can just throw it out to your serial port and directly on to the GPS device. Using the PocketGpsLib that is described elsewhere on this site, you would just write:

GPSHandler GPS = new GPSHandler(this); //Initialize GPS handler
//Insert code to set up the GPS here...
// [...]
if(GPS.IsPortOpen) { GPS.WriteToGPS(returndata); //Send RTCM data to GPS }

Now to make this really work, you would create a timer that with a short interval checked the socket for new data, and pass it on to the GPS device. I used an interval of 100ms, which seemed to work quite well.

When you are done using the socket, remember to close it again:

sckt.Shutdown(SocketShutdown.Both);
 sckt.Close();

Click here to view an example of how to implement the above. Note: This is not a full application. You still have to do a little work yourselves, but hopefully this should get you going.