Here comes the LiDAR!


QCoherent LiDAR Server
Fig 1 – LiDAR Server in Silverlight MapControl

Online LiDAR is reaching critical mass and there are a lot of interesting projects.

PAMAP is one example with LiDAR covering all of Western Pennsylvania, which due to the Alleghenies, has a good deal of interesting topography at 3.2 ft resolution. Unfortunately, the Get Capabilities shows that this is another of those no spherical mercator, EPSG:3857, WMS services. Meaning the image doesn’t line up with underlying web map services.

PAMAP LiDAR in Silverlight View
Fig 2 – PAMAP LiDAR WMS

The GEON website is another LiDAR project offering access to LiDAR sets via opentopography.org or OpenTopography Portal

Here is a view of a GeoEarthScope ISB LiDAR 0.5 m data set shown on GoogleEarth

GoogleEarth LiDAR
Fig 3 – GoogleEarth view of OpenTopography ISB LiDAR data (unfiltered)

In addition to providing raw data and GoogleEarth kml views, GEON has received NSF ClueE funding for a Cloud based service:

On Demand LiDAR
“The project will study dynamic strategies for provisioning such applications by doing a performance evaluation of alternative strategies for serving very large data sets. The cloud platforms that will be used in the project will be the Google-IBM CluE cluster and the HP-Intel-Yahoo cluster, both of which have been assembled in collaboration with NSF for cloud computing research. The LiDAR processing application hosted at the OpenTopography portal has been selected as the representative application for this study. The application allows users to (i) subset remote sensing data (stored as point cloud data sets), (ii) process it using different algorithms, and (iii) visualize the output. The project will study alternative implementations for each stepusing database technology as well as Hadoop (www.hadoop.org) and run a series of performance evaluation experiments. Cloud platforms with thousands of processors and access to hundreds of terabytes of storage provide a natural environment for implementing OpenTopography processing routines, which are highly data-parallel in nature.”

Looks like they hit all the sweet spots. Of course you don’t need to have access to specialized NSF funded Google-IBM CluE clusters to play. Amazon’s Elastic MapReduce lets ‘poor folk’ have a crack at Hadoop playgounds too.

On a less esoteric note, I pointed my new Silverlight MapControl CTP WMS viewer at a LiDAR Server project by QCoherent. QCoherent is nearly a neighbor with their office down the street a mile or two from me here in Colorado. I don’t really use their LiDAR software (I’m the online only sort), but I was fascinated to learn that they have a new LiDAR Server for providing WMS access to LiDAR data.

LiDAR Server is a WMS middle tier server that provides online access to LiDAR datasets with on the fly styling, tinning, and contouring.

  • WMS compliant map server
  • Multiple representations of LiDAR data: points, TIN, contours, etc.
  • Unique layers for each representation
  • Multiple ways of filtering and colorizing data (implemented through styles in the WMS)
  • High performance on-the-fly tinning engine
  • High performance on-the-fly contouring engine

It wasn’t much of a stretch to add a few of LiDAR Server sample urls to my Silverlight viewer and go exploring. The WMS is 1.3.0 compliant and does publish EPSG:3785 (now deprecated in favor of EPSG:3857), so Virtual Earth alignment is possible. Basically the GetCapabilities exposes four layers – Boundaries, Points, Contours, and TIN. Each of these layers has a series of selectable styles such as All or Ground Elevation, Classification, Intensity, and Return which are simply preset style coloring. The layers are queryable which means GetFeatureInfo requests are active.

Here are some examples using the Silverlight WMS Viewer:

LiDAR Server
Fig 4 – LiDAR Server Vancouver TIN AllElevation Building Footprints

I’ve turned down opacity slightly so the Aerial MapMode can just be seen beneath. Its interesting to note the footprint against the aerial imagery and see how well orthrectification worked, or not. Tough in the middle of hirise towers. Dark blue is Elevation (Z): -14.01

LiDAR Server
Fig 5 – LiDAR Server Vancouver TIN AllElevation Building Footprints + Contours

LiDAR Server
Fig 6 – LiDAR Server Cumberland TIN GroundElevation – Bare Earth

LiDAR Server
Fig 7 – LiDAR Server Cumberland TIN AllClassification

LiDAR Server
Fig 8 – LiDAR Server Cumberland Contour AutoGround

Note that all the heavy lifting is done by LiDAR Server. The Silverlight Viewer merely takes advantage of the exposed WMS service to show various sets of layers over Virtual Earth base. When I get around to it I’ll be adding other base as well: OSM, Yahoo, OpenAerial etc. I was surprised at the speed of the ‘on the fly’ TIN and contouring capability. The OpenGL 1.3 capable video card requirement prohibits use in an Amazon Cloud sense, since EC2 instances are all headless, i.e. graphic cards not expected anytime soon.

LiDAR is obviously a 3D oriented resource and at present Silverlight is 2D only. Too bad. Although there is a 2.5D capability in Silverlight 3.0 beta that lets a MapControl surface be twisted out of screen plane, this doesn’t help much with topography explorations. Since the Javascript VE SDK does allow a 3D mode it could drape images over terrain just like GoogleEarth. I assume Silverlight is headed toward 3D like WPF, but I’m just speculating. If any Microsoftee reads this please put in a good word for 3D Silverlight real soon now.

Keep in mind, however, that the actual WMS output is imagery, png or jpg, not mesh. In the GoogleEarth kml example the LiDAR is ground clamped <GroundOverlay> png images. In other words all the increased clarity of 0.5m resolution LiDAR is an illusion based on shaded imagery draped over existing Google terrain at whatever resolution it maintains. Of course Google and Microsoft are in a race to provide the highest resolution DEM available so it is possible 1m PAMAP is already included.

I’m sure there are other significant online LiDAR projects out there, and I haven’t even looked at BIM LiDAR which is heating up as well.

Summary
Looking forward to national submeter coverage!

Posted in Uncategorized

EPSG confusion and Silverlight png transparency clarification

First the confusion:

I ran across this today:EPSG Craziness


EPSG:3857
Fig 1 – EPSG official, official spherical mercator EPSG:3857

Looks like last Feb there was a change. The official spherical mercator is not EPSG:3785 but EPSG:3857 !?

EPSG:3857
<ProjectedCRS
  xmlns=”http://www.opengis.net/gml”
  xmlns:gml=”http://www.opengis.net/gml” gml:id=”ogp-crs-3857″>
  <metaDataProperty>
    <epsg:CommonMetaData xmlns:epsg=”urn:x-ogp:spec:schema-xsd:EPSG:0.1:dataset”>
      <epsg:type>projected</epsg:type>
      <epsg:alias code=”5966″ codeSpace=”urn:ogc:def:naming-system:EPSG::7301″
       alias=”WGS 84 / Popular Visualisation Pseudo-Mercator” />
      <epsg:informationSource>Microsoft.</epsg:informationSource>
      <epsg:revisionDate>2009-02-09</epsg:revisionDate>
      <epsg:changes>
        <epsg:changeID xmlns:xlink=”http://www.w3.org/1999/xlink”
       xlink:href=”urn:ogc:def:change-request:EPSG::2008.114″ />
      </epsg:changes>
      <epsg:show>true</epsg:show>
      <epsg:isDeprecated>false</epsg:isDeprecated>
    </epsg:CommonMetaData>
  </metaDataProperty>
  <identifier codeSpace=”OGP”>urn:ogc:def:crs:EPSG::3857</identifier>
  <name>WGS 84 / Pseudo-Mercator</name>
  <remarks>Uses spherical development of ellipsoidal coordinates. Relative to an ellipsoidal development errors
       of up to 800 metres in position and 0.7 percent in scale may arise. It is not
       a recognised geodetic system: see WGS 84 / World Mercator (CRS code 3395).</remarks>
  <domainOfValidity xmlns:xlink=”http://www.w3.org/1999/xlink” xlink:href=”urn:ogc:def:area:EPSG::3544″ />
  <scope>Certain Web mapping and visualisation applications.</scope>
  <conversion xmlns:xlink=”http://www.w3.org/1999/xlink” xlink:href=”urn:ogc:def:coordinateOperation:EPSG::3856″ />
  <baseGeodeticCRS xmlns:xlink=”http://www.w3.org/1999/xlink” xlink:href=”urn:ogc:def:crs:EPSG::4326″ />
  <cartesianCS xmlns:xlink=”http://www.w3.org/1999/xlink” xlink:href=”urn:ogc:def:cs:EPSG::4499″ />
</ProjectedCRS>

IsDeprecated=True EPSG:3785
<ProjectedCRS
  xmlns=”http://www.opengis.net/gml”
  xmlns:gmd=”http://www.isotc211.org/2005/gmd”
  xmlns:gco=”http://www.isotc211.org/2005/gco”
  xmlns:epsg=”urn:x-ogp:spec:schema-xsd:EPSG:0.1:dataset”
  xmlns:gml=”http://www.opengis.net/gml”
  xmlns:xlink=”http://www.w3.org/1999/xlink”
  xmlns:rim=”urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0″
  gml:id=”epsg-crs-3785″>
  <metaDataProperty>
   <epsg:CommonMetaData>
    <epsg:type>projected</epsg:type>
    <epsg:alias alias=”Popular Vis CRS / Merc” code=”5516″
    codeSpace=”urn:ogc:def:naming-system:EPSG::7302″ />
    <epsg:informationSource>Microsoft.</epsg:informationSource>
    <epsg:revisionDate>2008-03-14</epsg:revisionDate>
    <epsg:changes>
    <epsg:changeID xlink:href=”urn:ogc:def:change-request:EPSG::2008.114″ />
    </epsg:changes>
    <epsg:show>true</epsg:show>
    <epsg:isDeprecated>true</epsg:isDeprecated>
   </epsg:CommonMetaData>
  </metaDataProperty>
  <identifier codeSpace=”OGP”>urn:ogc:def:crs:EPSG::3785</identifier>
  <name>Popular Visualisation CRS / Mercator</name>
  <remarks>Uses spherical development. Relative to an ellipsoidal development errors of up
     to 800 metres in position and 0.7% in scale may arise. Some applications call this WGS 84.
    It is not a recognised geodetic system: see WGS 84 / World Mercator (CRS code 3395)</remarks>
  <domainOfValidity xlink:href=”urn:ogc:def:area:EPSG::3544″ />
  <scope>Certain Web mapping and visualisation applications.</scope>
  <conversion xlink:href=”urn:ogc:def:coordinateOperation:EPSG::19847″ />
  <baseGeodeticCRS xlink:href=”urn:ogc:def:crs:EPSG::4055″ />
  <cartesianCS xlink:href=”urn:ogc:def:cs:EPSG::4499″ />
</ProjectedCRS>

Next the transparency

  • Clarification on Silverlight MapControl PNG tansparency support

After some more investigation on the transparency problem I was running into, here is the gdalInfo for an example that does not work. It’s apparently png8 with color table which is more compact and valid, but the NoData Value=0 is ignored by Silverlight MapControl:

Driver: PNG/Portable Network Graphi
Files: PA_Hillshade.png
Size is 1259, 700
Coordinate System is `'
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0,  700.0)
Upper Right ( 1259.0,    0.0)
Lower Right ( 1259.0,  700.0)
Center      (  629.5,  350.0)
Band 1 Block=1259x1 Type=Byte, Colo
  NoData Value=0
  Color Table (RGB with 256 entries
    0: 255,255,255,0
    1: 184,184,184,255
    2: 177,177,177,255
    3: 175,175,175,255
            .
            .

Next run gdal_translate -of png -expand rgba PA_Hillshade.png PA_Hillshade4b.png to expand to png32:

Driver: PNG/Portable Network Graphics
Files: PA_Hillshade4b.png
       PA_Hillshade4b.png.aux.xml
Size is 1259, 700
Coordinate System is `'
Image Structure Metadata:
  INTERLEAVE=PIXEL
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0,  700.0)
Upper Right ( 1259.0,    0.0)
Lower Right ( 1259.0,  700.0)
Center      (  629.5,  350.0)
Band 1 Block=1259x1 Type=Byte, ColorInterp=Red
  NoData Value=0
Band 2 Block=1259x1 Type=Byte, ColorInterp=Green
  NoData Value=0
Band 3 Block=1259x1 Type=Byte, ColorInterp=Blue
  NoData Value=0
Band 4 Block=1259x1 Type=Byte, ColorInterp=Alpha
  NoData Value=0

Silverlight png transparency now works. Of course the png produced by a public WMS is out of my control.

If Silverlight doesn’t support a png8 transparency it looks like an on the fly translate is in order, which is another reason to use a proxy .ashx for a WMS GetMap request.

Posted in Uncategorized

"Bing" chatter


Sound of Found
Bing Chatter – “The sound of found: Bing?”

Wait found what? Where’s the Bing? We Bing You decide.

Seems a bit late to be rebranding “Virtual Earth.” I know “Virtual Earth” is a long name but there was at least some symmetry with “Google Earth.”

Oops .. maybe that’s a problem?

Bing maps for enterprise

In the same vein as Fortune Cookie Bing: “Disease”
Google Language Bing =>冰 冰 => “Ice”
Babelfish Bing => 堆 堆 => “Piling”

I guess Chinese translation tools aren’t commutative. Babel, Piling, Ice, Bing, Bam for the Enterprise?

Do I detect a bit of embarrassment: rebranding-microsoft-virtual-earth-to …

Posted in Uncategorized

Next Gen UI for GIS

Interesting to think that GIS UI’s are in for some major changes. Next gen GIS users will bring UI expectations from the OGL, DirectX, WPF, X3D, XNA world. It seems the biggest UI transform is just navigating a camera (avatar) in 3D rather than 2.5D.
See VerySpatial’s Lost in the Virtual Fog

GIS UIs currently follow the usual 2.5D OS interactions with primarily Mouse event handling. There are 3D analogies to current 2.5D interaction.

MouseEnter, MouseLeave – rollover
Passive select – 3D intersect media changes (popups, text, film, photo, etc) would correspond to current 2.5D rollover events

MouseClick, Double Click
Active select – corresponding 3D click events are just as necessary in a 3D virtual world – perhaps a virtual laser pointer not much different than the shoot’em game scenario. The virtual point device has a straight line of sight intersect in the scene graph.
WPF 3D hit testing

    RayHitTestParameters hitParams =
        new RayHitTestParameters(
            new Point3D(0, 0, 0),
            new Vector3D(1, 0, 0)
            );
    VisualTreeHelper.HitTest(visual3d, null, ResultCallback, hitParams);

MouseMove, MouseWheel – Pan, Drag, and zoom
Pan and Zoom is analogous to camera control in 3D. Navigation in 3D is a bit more complex including not just 3D camera position, but a 3D vector ‘look direction’ , ‘up direction’, and also ‘field of view’: WPF 3D Tutorial

   <Viewport3D.Camera>
    <PerspectiveCamera
	  FarPlaneDistance="100"
	  LookDirection="-11,-10,-9"
	  UpDirection="0,1,0"
	  NearPlaneDistance="1"
	  Position="11,10,9"
	  FieldOfView="70" />
    </Viewport3D.Camera>

Manipulating a camera in 3D is cumbersome using typical Mouse event handling. 3D navigation replacement of pan and zoom is better suited to a new device altogether. iPhone/WII like inertial sensor navigation can’t be far away. A 3D mouse is just a mouse freed from its 2D pad, a stripped down inertial sensor device or more sensitive WII wand. Now there are direction and tilt events, as well as move events for a more natural camera view control.

Beyond 2.5D analogies
There seem to be some innovative gesture interactions arriving with thanks to the iPhone.


Touch Pack for Windows 7

Extending hand gestures to 3D is further down the road but obviously one step further in the immersive direction. GIS is headed down the virtual reality path.

Posted in Uncategorized

Follow up on BLM LSIS Transparency


Silverlight MapControl OWS Viewer
Fig 1 – BLM LSIS does offer transparency

Ah looks like I ran into a different problem in my exploration of BLM LSIS. It is not a problem with Transparent=True but a problem with Silverlight’s support of 24 bit png.

Here is some discussion: http://silverlight.net/forums/t/73949.aspx and http://silverlight.net/forums/p/91864/213282.aspx

Vish’s Rambling adds some discussion as well.

It is interesting to note how reluctant Microsoft is to admit to a bug of any kind. I guess it is a big target for the lawyers, but I’ve never seen that kind of reluctance in the Open Source world.

Hopefully Silverlight support for png transparency will be cleared up in a future release.

Posted in Uncategorized

More on Silverlight WMS Viewer


Silverlight MapControl OWS Viewer
Fig 1 – Silverlight WMS view of DRCOG

Exploring WMS services using my Silverlight MapControl viewer uncovered a few more areas worth exploring further.

spherical mercator support

The problem with coordinate systems resurfaced when looking at BLM WMS services. The only coordinates supported by BLM are EPSG:4326, which is unfortunate. All of the web map services use a spherical mercator tiled approach for better performance. This was not an arbitrary decision just to spite the bureaucrats at EPSG. Tile pyramids are best supported using a square quadtree as discussed here, which is why mercator was chosen. Using a spherical datum instead of an ellipsoid eases the transform calculations for faster map interaction. However, because the EPSG and OGC standards made their appearance late, many sites are stuck off the mainstream supporting only EPSG:4326.

The newer services, at least those using GeoServer or MapServer, have support for a large number of coordinate systems including the spherical mercator coordinate systems needed for Google, Virtual Earth, Open Street Maps, and Yahoo. Shown in the example above, DRCOG WMS service uses GeoServer and by default supports EPSG:900913. In contrast to BLM, DRCOG has support for all the features needed to make it useful to the web map services, including spherical mercator coordinates, transparent parameter, and cross domain access. Even though it is still lacking official EPSG:3785 support EPSG:900913 works just fine.

The BLM LSIS WMS appears to lack support for the “Transparent=True” parameter which makes stacking layers a bit cumbersome. Even though opacity can be adjusted, opaque fills are additive and eventually obscure lower level layers. However, the big problem is the lack of any spherical mercator coordinate system.

Silverlight MapControl OWS Viewer
Fig 2 – View of BLM LSIS misfit

crossdomain policy support

Both Flex and Silverlight restrict cross domain access. In other words access to site of origin is allowed, but not access to a url other than the site of origin. In Silverlight this is true even of image urls. The idea is to prevent url hijacking from client side code. Cross domain restrictions can be altered by including a crossdomain.xml or clientaccesspolicy.xml to grant access to a particular service, but these can only be added by the hosting service. So in the case of USGS Atlas, cross domain access seems to have been granted, but not by BLM LSIS.

You can see a summary of the Silverlight restrictions here: http://msdn.microsoft.com/en-us/library/cc645032(VS.95).aspx

Cross domain access doesn’t really need to be granted by the service host, since it is simple enough to use the Silverlight host server to proxy access and avoid the client side restrictions. In order to circumvent these restrictions it’s necessary to provide a proxy service on the Silverlight server that translates cross domain requests into a a site of origin proxy request. This approach was discussed for GetCapabilities and GetFeatureInfo requests with an example WCF. However, SOAP calls for producing an image would seem to add a good bit of overhead, and I switched to an .ashx service handler for my GetMap requests.

Peter Bromberg’s blog – Handling Cross-Domain Images gives an example which I adapted for use in the Silverlight WMS Viewer.

using System;
using System.Net;
using System.Web;
using System.Web.Services;

namespace VEOWS.Web
{
    [WebService(Namespace = "http://www.web-demographics.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class ImageHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string imageUrl = context.Request["url"].ToString();
            WebClient client = new WebClient();
            byte[] bytes = client.DownloadData(new Uri(imageUrl));
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.BufferOutput = false;
            context.Response.BinaryWrite(bytes);
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

Example of an .ashx proxy handler for GetMap image requests

Silverlight ProgressBar

Since there is a pause waiting for a WMS GetMap request I wanted to add a ProgressBar. In fact Silverlight 2.0 includes a ProgressBar Control

This seems like an easy solution, just add a ProgressBar control along with a ValueChanged event handler and I should be ready to go. However, it is not quite so easy as desribed here: Silverlight 2.0 Progressbar Control In the end I simplified life and instead of a real progress bar I just show a ProgressBar with IsIndeterminate=”True”. By toggling visibility I can provide a user hint to wait a moment on the GetMap request.

In order for this to be effective I need to set Visibility.Visible when starting and then Visibility.Collapsed when done. Using a WebClient Asynch call provides the framework for this:

WebClient imgLoader = new WebClient();
imgLoader.OpenReadCompleted += new OpenReadCompletedEventHandler(imgLoader_OpenReadCompleted);
imgLoader.OpenReadAsync(new Uri(imgUrl));

where imgUrl includes the .ashx url with the proxied url as a parameter for a GetMap request. This url parameter must be urlencoded with ‘%26′ replacing the ‘&’ :

http://localhost:61887/ClientBin/ImageHandler.ashx?url=http://localhost/geoserver/wms?SERVICE=WMS
%26Version=1.1.1%26REQUEST=GetMap%26LAYERS=geo:state%26STYLES=green%26TRANSPARENT=TRUE
%26SRS=EPSG:900913%26BBOX=-8277636.89618468,4953330.918209,-8192638.92073157,5006836.83800862
%26FORMAT=image/png%26EXCEPTIONS=text/xml%26WIDTH=1112%26HEIGHT=700

Now I can set DownloadProgress.Visibility = Visibility.Visible; before imgLoader.OpenReadAsync(new Uri(imgUrl));. Then in the callback handler set DownloadProgress.Visibility = Visibility.Collapsed;. This is not ideal, but the GetMap requests are seldom more than a second so the more complicated threaded ProgressValueChanged handler isn’t really that necessary.

Summary

  1. WMS services such as BLM LSIS need to update their service with the newer EPSG:3785 and add a Transparent parameter to their GetMap requests if they wish to be useful to web map services.

    In contrast services such as DRCOG are all set for useful client development in Silverlight MapControl as well as Google, Yahoo, and OSM.

  2. Silverlight ProgressBar should be easy but not yet! Perhaps Silverlight 3.0?
  3. .ashx handler proxy is a simple way to get around cross domain restrictions for GetMap Image requests
Posted in Uncategorized

Silverlight WMS Viewer


Silverlight MapControl OWS Viewer
Fig 1 – Silverlight MapControl CTP used as a Simple WMS Viewer

In the past couple of blogs I’ve looked at using SQL Server and PostGIS overlays on top of the Silverlight VE MapControl CTP. I’ve also looked at using PostGIS mediated by Geoserver as cached tile sources with GeoWebCache. As these investigations have shown, map layers can be provided using basic shape overlays directly from spatial database tables or as image tile sources such as GeoWebCache.

There is another possibility: using an Image element with its Source pointed at a WMS service. Although this approach is not as interactive as vector shape overlays or as efficient as a tile source, it does have one big advantage. It can be used to access public WMS services. Currently the most common OWS services in the public realm are WMS. WFS has never really seemed to take off as an OWS service and WCS is still a bit early to tell. Using a public WMS means conforming to the service as exposed by someone else. Many, probably most, WMS services do not have a tile caching capability, which means access to a public WMS in Silverlight MapControl requires a Silverlight Image element like this:

     <Image x:Name="wmsImage" Source="the WMS Source"></Image>

where ‘the WMS Source’ looks similar to this:
    http://localhost/geoserver/wms?SERVICE=WMS&Version=1.1.1
    &REQUEST=GetMap&LAYERS=topp:states&STYLES=population
    &TRANSPARENT=TRUE&SRS=EPSG:4326
    &BBOX=-84.2103683719356, 24.8162723396119,
      -78.1019699344356, 28.2556356649882
    &FORMAT=image/png&EXCEPTIONS=text/xml&WIDTH=1112&HEIGHT=700

If we use a Silverlight Image element to display a WMS GetMap request we have a basic WMS Viewer. However, there are some hurdles to overcome.

WMS servers provide several url encoded requests, GetCapabilities, GetMap, and for layers that are queryable GetFeatureInfo. The workflow is generally to look at a GetCapabilities result to discover the exposed layers and information about their extent, style, and query capabilities. In addition there is usually information about supported formats, coordinates systems, and possibly even scale ranges. After examining the GetCapabilities result, the next step is configuring a GetMap request, which is then placed in the Silverlight xaml hierarchy as an Image element. Once that is accomplished an optional third step is to use the map layer for GetFeatureInfo requests to look at attribute data for a selected feature. There is another request, DescribeLayer, which is not needed for this project.

GetCapabilities

The first hurdle in creating a Silverlight WMS viewer is accessing the XML returned from a GetCapabilities request and processing it into a useable Silverlight control. First there is a cross domain issue since our Silverlight viewer wants to show WMS map views from a different server. As in previous projects this is done by providing a WCF proxy service on the Silverlight server to provide access to different WMS services at the server rather than incurring the wrath of a client cross domain access.

Here is an example of a client initialization:
    svc = GetServiceClient();
    svc.GetCapabilitiesCompleted += svc_GetCapabilitiesCompleted;
    svc.GetCapabilitiesAsync(“http://localhost/geoserver/wms?
    Service=WMS&Version=1.1.1&Request=GetCapabilities”);

and here is WMSService.svc GetCapabilities, which simply takes a GetCapabilities url encoded query and feeds back out to the svc_GetCapabilitiesCompleted the result as an XML string.

[OperationContract]
public string GetCapabilities(string url)
{
  HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
  using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
  {
    StreamReader reader = new StreamReader(response.GetResponseStream());
    return (reader.ReadToEnd());
  }
}

On the xaml client side we then pick up the resulting xml using XLinq to Parse into an XDocument.

private void svc_GetCapabilitiesCompleted(object sender, GetCapabilitiesCompletedEventArgs e)
{
  if (e.Error == null)
  {
    layers.Children.Clear();
          .
          .  
    XDocument document = XDocument.Parse(e.Result, LoadOptions.None);
    foreach (XElement element in document.Element
("WMT_MS_Capabilities").Element("Capability").Element("Layer").Descendants("Layer"))
    {
            .
            .
    }
  }
}

XLinq is very easy to use for manipulating XML, much easier in C# than using DOM commands. The intent of XLinq XML is similar to Java JDOM in providing an access syntax more appropriate to a language like C#. For example, document.Element(“WMT_MS_Capabilities”).Element(“Capability”).Element(“Layer”).Descendants(“Layer”), provides a collection of all the layer elements descending from the head layer in our GetCapabilities result. Using the layer sub elements we can build a simple selection menu from CheckBox and ComboBox elements. The main sub elements of interest are:

<Name>topp:states</Name>

<SRS>EPSG:4326</SRS>

<LatLonBoundingBox minx=”-124.731422″ miny=”24.955967″ maxx=”-66.969849″ maxy=”49.371735″/>

<Style>
    .
    .
</Style>

Here is an example of the simplicity of XLinq’s query capability:

  XElement layer = document.Element("WMT_MS_Capabilities").Element("Capability").Element("Layer");
  var testsrs = from srs in layer.Elements("SRS") select (string)srs;
  if (testsrs.Contains("EPSG:900913")) EPSG900913.IsEnabled = true;
  else EPSG900913.IsEnabled = false;

XLinq is a pleasure to use combining aspects of JDOM, JAXB, and SQL, and making life easier for developers.

The final selection menu looks like this:

Silverlight MapControl Selection menu
Fig 1 – Silverlight Layer Selection Menu

An opacity slider is easy to add and lets users see through opaque layers. Not all WMS services support ‘Transparent=True’ parameters.

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    OpacityText.Text = string.Format("{0:F2}", e.NewValue);

    foreach (UIElement item in layers.Children)
    {
        if (item is CheckBox)
        {
            if ((bool)(item as CheckBox).IsChecked)
            {
                MapLayer lyr = VEMap.FindName((item as CheckBox).Content.ToString()) as MapLayer;
                lyr.Opacity = e.NewValue;
            }
        }
    }
}

GetMap

Now come a few more hurdles for GetMap requests. A layer selection or view change triggers a call to SetImage(MapLayer lyr, string style)

  private void SetImage(MapLayer lyr, string style)
  {
    if (lyr.Children.Count > 0) lyr.Children.Clear();// clear previous layer
    int aw = (int)VEMap.ActualWidth;
    int ah = (int)VEMap.ActualHeight;
    LocationRect bounds = VEMap.GetBoundingRectangle();
    Point sw = new Point(bounds.Southwest.Longitude, bounds.Southwest.Latitude);
    Point ne = new Point(bounds.Northeast.Longitude, bounds.Northeast.Latitude);
    string wmsurl = wmsServer + "?SERVICE=WMS&Version=1.1.1&REQUEST=GetMap&LAYERS=" +
    lyr.Name + "&STYLES=" + style + "&TRANSPARENT=TRUE&SRS=EPSG:4326&BBOX=" +
    sw.X + "," + sw.Y + "," + ne.X + "," + ne.Y +
    "&FORMAT=image/png&EXCEPTIONS=text/xml&WIDTH=" + aw + "&HEIGHT=" + ah;
    Image image = new Image();
    image.Source = new BitmapImage(new Uri(wmsurl, UriKind.RelativeOrAbsolute));
    lyr.AddChild(image, new Location(bounds.North, bounds.West));
    image.MouseLeftButtonDown += Image_MouseClick;
  }

Quite simple, however, there is a problem with this, which can be seen below.

Coordinate system problem
Fig 3 – Coordinate system problem

EPSG:4326 is versatile, since it’s supported by every WMS service I’ve run across, but this geographic coordinate projection system does not match Virtual Earth base maps, or Google, Yahoo, and OSM for that matter. All of the web mapping services use a tiled spherical mercator projection. Fortunately they all use the same one. At this point there is a relatively new official EPSG designated 3785. And, here is an interesting discussion including some commentary on EPSG politics: EPSG and 3785.

However, for awhile now Geoserver has included a pseudo EPSG number, “900913,” to do the same thing and it is already incorporated into my PostGIS spatial_ref_sys tables and the Geoserver version I am using. Unfortunately MapServer chose a different pseudo EPSG, “54004,” which illustrates the problem of an existing standards body getting miffed and dragging their feet. <SRS>EPSG:900913</SRS>, <SRS>EPSG:54004</SRS>, and EPSG:3785 are similar mathematically speaking, but it will take a few iterations of existing software before EPSG:3785 is distributed widely. In the meantime this is the definition distributed with Geoserver:

    900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
         DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]],
         PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295],
         AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
         PROJECTION["Mercator_1SP_Google"],
         PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0],
         PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0],
         PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
         AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]

In order to make a GetMap Image match a spherical Mercator we make a couple of modifications. First change “EPSG:4326″ to “EPSG:900913″ or “EPSG:54004.” Then provide a conversion like this for the BBox parameters (more details -Mercator projection)

  private Point Mercator(double lon, double lat)
  {
    /* spherical mercator for Google, VE, Yahoo etc
     * epsg:900913 R= 6378137
     * x = long
     * y= R*ln(tan(pi/4 +lat/2)
     */
    double x = 6378137.0 * Math.PI / 180 * lon;
    double y = 6378137.0 * Math.Log(Math.Tan(Math.PI/180*(45+lat/2.0)));
    return new Point(x,y);
  }

Note that VEMap.GetBoundingRectangle(); guarantees a bounds that will never reach outside the 85,-85 latitude limits. (more precisely -85.05112877980659, 85.05112877980659) Using the above to calculate a BBox in spherical coordinates results in:

  sw = Mercator(bounds.Southwest.Longitude, bounds.Southwest.Latitude);
  ne = Mercator(bounds.Northeast.Longitude, bounds.Northeast.Latitude);

This makes the necessary correction, but not all WMS services publish support for EPSG:900913, EPSG:54004, or EPSG:3785. How to work around this dilemma? Unfortunately I have not yet found a solution to this problem, short of doing an image warp with each view change. Since some WMS services do not support any of the spherical Mercator projections, I’m left with a default EPSG:4326 that will not overlay correctly until zoomed in beyond ZoomLevel 7.

higher zoom
Fig 4 – Coordinate system problem disappears at higher zoom

One more hurdle in GetMap is the problem with a dateline wrap or antimeridian spanning. This occurs when a view box spans the longitude -180/180 break. After checking around a bit, the best solution I could come up with is splitting the image overlay into west and east parts.
if (bounds.West > bounds.East) I know I will need more than one tile:

  LocationRect boundsE = new LocationRect(bounds.North, bounds.West, bounds.South, 180.0);
  LocationRect boundsW = new LocationRect(bounds.North, -180.0, bounds.South, bounds.East);

Although this works for a 360 degree viewport, zoom levels > 2.5 are actually greater than 360 degrees and I need to work out some sort of Modular Arithmetic for more than two parts. ZoomLevel 1 will require 4 separate tiles. My problem currently is that detecting the actual number of degrees in a viewport is problematic. VEMap.GetBoundingRectangle() will return East and West Extents but this will not indicate width in actual degrees for a clock arithmetic display that repeats itself. For now I’m taking the simplest expedient of turning off the overlay for ZoomLevels less than 2.5.

Multiple earths at ZoomLevel 1
Fig 5 – Virtual Earth ZoomLevel 1 with viewport > 360deg

GetFeatureInfo

GetFeatureInfo requests are used to send a position back to the server to look up a list of table attributes for feature(s) intersecting that position. Not all WMS layers expose GetFeatureInfo as indicated in the GetCapabilities <Layer queryable=”0″>.

The basic approach is to handle a click event which then builds the necessary WMS GetFeatureInfo Request. There is a mild complication since UIElements do not possess MouseClick events. Substituting a MouseLeftButtonDown event is a work around to this lack: image.MouseLeftButtonDown += Image_MouseClick; There are other possibilities such as using SharpGIS.MouseExtensions. However, the Silverlight MapControl seems to mask these extensions. Until I can work out that issue I defaulted to the simple MouseLeftButtonDown event.

   private void Image_MouseClick(object sender, MouseButtonEventArgs e)
  {
      Image img = sender as Image;
      Point pt = e.GetPosition(img);
      var location = VEMap.ViewportPointToLocation(e.GetPosition(VEMap));
      BitmapImage bi = img.Source as BitmapImage;
      string info = bi.UriSource.AbsoluteUri.ToString();
      int beg = info.IndexOf("LAYERS=")+7;
      int end = info.IndexOf('&',beg);
      string layer = info.Substring(beg, end-beg);
      info = info.Replace("GetMap", "GetFeatureInfo");
      info += "&QUERY_LAYERS=" + layer + "&INFO_FORMAT=application/vnd.ogc.gml
      &X=" + pt.X + "&Y=" + pt.Y;
      svc.GetFeatureInfoAsync(info);

      FeatureInfo.Children.Clear();
      InfoPanel.Visibility = Visibility.Collapsed;
      InfoPanel.SetValue(MapLayer.MapPositionProperty, location);
      MapLayer.SetMapPositionOffset(InfoPanel, new Point(10, -150));
  }

By attaching the event to each image element, I’m able to make use of bi.UriSource, just modifying the request parameter and adding the additional GetFeatureInfo parameters. There are some variations in the types of Info Formats exposed by different WMSs. I selected the gml which is supported by GeoServer and text/xml for use with USGS National Atlas.

private void svc_GetFeatureInfoCompleted(object sender, GetFeatureInfoCompletedEventArgs e)
{
  if (e.Error == null)
  {
    XDocument document = XDocument.Parse(e.Result, LoadOptions.None);
    XNamespace wfs = "http://www.opengis.net/wfs";
    XNamespace gml = "http://www.opengis.net/gml";
    XNamespace geo = "http://www.web-demographics.com/geo";
    if (document.Element("ServiceExceptionReport") == null)
    {
      if (document.Element("FeatureInfoResponse") == null)
      {
        XElement feature = document.Element(wfs + "FeatureCollection").Element(gml + "featureMember");
        if (feature != null)
        {
          XElement layer = (XElement)feature.FirstNode;
          if (layer != null)
          {
            TextBlock tb = new TextBlock();
            tb.Foreground = new SolidColorBrush(Colors.White);
            tb.Text = layer.Name.ToString().Substring(layer.Name.ToString().IndexOf('}') + 1);
            FeatureInfo.Children.Add(tb);
            InfoPanel.Visibility = Visibility.Visible;
            IEnumerable de = layer.Descendants();
            foreach (XElement element in de)
            {
              string name = element.Name.ToString();
              name = name.Substring(name.IndexOf('}') + 1);
              XNamespace ns = element.Name.Namespace;
              string prefix = element.GetPrefixOfNamespace(ns);
              if (!prefix.Equals("gml"))
              {
                tb = new TextBlock();
                tb.Foreground = new SolidColorBrush(Colors.White);
                tb.Text = name + ": " + element.Value.ToString();
                FeatureInfo.Children.Add(tb);
              }
            }
          }
        }
      }
    }
  }
}

Summary

There are some minor problems using Silverlight VE MapControl as a generic OWS Viewer.

  1. Lack of general support for EPSG:3785 in many public WMS servers
  2. Detecting viewport extents for ZoomLevels displaying more than 360 degrees
  3. Missing MouseClick and double click events for UIElements

None of these are show stoppers and given a couple more days I’d probably work out solutions for 2 and 3. The biggest hurdle is lack of support for spherical Mercator coordinate systems. As long as you have control of the WMS service this can be addressed, but it will be some time before EPSG:3785 propagates into all the WMS services out in the wild.

On the plus side, Silverlight’s sophistication makes adding nice design and interaction features relatively easy. Again having C# code behind makes life a whole lot easier than dusting off javascript. I’m also really liking XLinq for manipulating small xml sources.

Next on the project list will be adding some nicer animation to the information and menu panels. Further in the future will be adding WFS view capability.


Silverlight MapControl OWS Viewer
Fig 1 – Silverlight MapControl CTP WMS Viewer showing USGS Atlas

Posted in Uncategorized

Watching the Cloud


AWS logo
Amazon announces some welcome additions to their AWS tools.

The promise of auto scaling has been a large part of the Cloud since its inception. There have been 3rd party tools in the Amazon Cloud for awhile, but unfortunately involving some cost in complexity. Amazon’s new tools are a welcome addition. At present they are Beta command line APIs but it’s reasonable to assume they will be incorporated into the AWS Console at some point.

There are three api tool kits in this recent round of announcements:
    Cloud Watch
    Auto Scaling
    Elastic Load Balancing

Cloud Watch tracks a set of parameters for each running instance monitored:
    CPUUtilization
    DiskReadBytes
    DiskReadOps
    DiskWriteBytes
    DiskWriteOps
    NetworkIn
    NetworkOut

Auto Scaling then let’s an administrator set triggers for adjusting the number of EC2 instances in an instance pool to reflect demand. Triggers are based on the monitored parameters.

Elastic Load Balancing provides round robin web call distribution across a set of identical web instances. In addition to ease of administration it keeps an eye on the health of instances in the pool and auto routes traffic around any problem instances that show up.

Here is the summary from Amazon’s website:

  • Amazon CloudWatch – Amazon CloudWatch is a web service that provides monitoring for AWS cloud resources, starting with Amazon EC2. It provides you with visibility into resource utilization, operational performance, and overall demand patterns—including metrics such as CPU utilization, disk reads and writes, and network traffic. To use Amazon CloudWatch, simply select the Amazon EC2 instances that you’d like to monitor; within minutes, Amazon CloudWatch will begin aggregating and storing monitoring data that can be accessed using web service APIs or Command Line Tools.
    Fees: $0.015 per hour for each Amazon EC2 instance monitored which amounts to $10.95 per month for a single instance of any type.
  • Auto Scaling – Auto Scaling allows you to automatically scale your Amazon EC2 capacity up or down according to conditions you define. With Auto Scaling, you can ensure that the number of Amazon EC2 instances you’re using scales up seamlessly during demand spikes to maintain performance, and scales down automatically during demand lulls to minimize costs. Auto Scaling is particularly well suited for applications that experience hourly, daily, or weekly variability in usage. Auto Scaling is enabled by Amazon CloudWatch and available at no additional charge beyond Amazon CloudWatch fees.
    Fees: free to Amazon CloudWatch customers. Of course the usual fees for additional EC2 instances apply.
  • Elastic Load Balancing – Elastic Load Balancing automatically distributes incoming application traffic across multiple Amazon EC2 instances. It enables you to achieve even greater fault tolerance in your applications, seamlessly providing the amount of load balancing capacity needed in response to incoming application traffic. Elastic Load Balancing detects unhealthy instances within a pool and automatically reroutes traffic to healthy instances until the unhealthy instances have been restored. You can enable Elastic Load Balancing within a single Availability Zone or across multiple zones for even more consistent application performance. Amazon CloudWatch can be used to capture a specific Elastic Load Balancer’s operational metrics, such as request count and request latency, at no additional cost beyond Elastic Load Balancing fees.
    Fees: $0.025 per hour for each Elastic Load Balancer, plus $0.008 per GB of data transferred through an Elastic Load Balancer

The api tools can be downloaded here: Developer Tools. Installation involves simply downloading a zip file and unzipping to a convenient subdirectory, where it can be referenced in a setup batch similar to this:
    @echo off
    set EC2_HOME=C:\EC2-Test
    set MONITORING_HOME=C:\EC2-Test\Monitor
    set AUTO_SCALING_HOME=C:\EC2-Test\AutoScaling
    set JAVA_HOME=”C:\Program Files\Java\jdk1.6.0_03″
    set ELB_HOME=C:\EC2-Test\LoadBalance
    set PATH=%PATH%;%EC2_HOME%\bin;%EC2_HOME%\Monitor\bin;%EC2_HOME%\AutoScaling\bin;%EC2_HOME%\LoadBalance\bin
    set EC2_PRIVATE_KEY=C:\EC2-Test\PrivateKey.pem
    set EC2_CERT=C:\EC2-Test\509Certificate.pem

The only problem I ran into was making sure I properly quoted my JAVA_HOME directory to handle the space in its directory name.

Here is a quick look at the –help for each of the apis:
Cloud Watch

mon –help  
Command Name Description
———— ———–
help  
mon-get-stats Returns metric data
mon-list-metrics Returns a list of the metrics
version Prints the version of the CLI tool and the API.

Auto Scaling

as –help  
Command Name Description
———— ———–
as-create-auto-scaling-group Create a new auto scaling group
as-create-launch-config Create a new launch config
as-create-or-update-trigger Creates a new trigger or updates an existing trigger.
as-delete-auto-scaling-group Delete the specified auto scaling group
as-delete-launch-config Delete the specified launch configuration
as-delete-trigger Delete a trigger.
as-describe-auto-scaling-groups Describes the specified auto scaling group(s)
as-describe-launch-configs Describe the specified launch configurations
as-describe-scaling-activities Describe a set of activiti…ties belonging to a group.
as-describe-triggers Describes a trigger including its internal state.
as-set-desired-capacity Set the desired capacity of the auto scaling group
as-terminate-instance-in-auto-scaling-group Terminate a given instance.
as-update-auto-scaling-group Update specified auto scaling group

Elastic Load Balancing

elb –help
Command Name Description
———— ———–
elb-configure-healthcheck Configure the parameters f…tered with a LoadBalancer.
elb-create-lb Create a new LoadBalancer
elb-delete-lb Deletes an existing LoadBalancer
elb-deregister-instances-from-lb Deregisters Instances from a LoadBalancer
elb-describe-instance-health Describes the state of Instances
elb-describe-lbs Describes the state and properties of LoadBalancers
elb-disable-zones-for-lb Remove Availability Zones from an LoadBalancer
elb-enable-zones-for-lb Add Availability Zones to existing LoadBalancer
elb-register-instances-with-lb Registers Instances to a LoadBalancer

Summary:

These new tools round out the AWS Cloud offering with some welcome monitor and control capability. Amazon has to keep up in the ‘ease of use’ area to stay ahead of other Cloud vendors. These continue the trend of adding complexity to cost calculations, but Amazon’s fees seem reasonable compared to dedicated and co-located services. Including tools for auto scaling and load balancing helps administrators keep a handle on costs for maintaining instance pools. Once these tools are incorporated into the AWS Console, administration could fade into development / deployment phases of a project with only minimal ongoing maintenance.

There seems to be some room for additional graphing, charting, and spreadsheet capability for monitor results. These could conceivably be included as part of the AWS Console version of the current cmd tools.

The lure is increasing availability assurance with decreasing administration costs.

Posted in Uncategorized

Silverlight MapControl and PostGIS


PostGIS Silverlight MapControl
Fig 1 – PostGIS and Silverlight MapControl

It is worth revisiting connecting Silverlight MapControl to a database in order to try duplicating the SQL Server layers in PostgreSQL/PostGIS.

Why?
SQL Server 2008 is still lacking some of the spatial capabilities found in PostGIS and also a rough performance comparison between the two databases would be interesting.

The same process is used in accessing a database from inside the Silverlight MapController for either SQL Server or PostGIS. A Data service is used with an [OperationContract] to expose the results of a DB query to the Silverlight through a Service Reference on the Silverlight side. From there it is a matter of using Silverlight MapController events to send a request to the service and process the results into shapes on the map.

The main difference is the code to handle the PostGIS connection, command query, and result reader. Microsoft includes these for SQL Server in the System.Data.SqlClient namespace as part of a default project setup. Naturally PostGIS doesn’t happen to be in the Microsoft System.Data.dll, however, there is a nice project that provides access to PostgreSQL inside .NET: Npgsql – .Net Data Provider for Postgresql

I downloaded Npgsql version 2.04 binaries and referenced it into my previous GeoTest. Now I can add a new [OperationContract] to the existing SFBayDataService that uses an Npgsql connection to PostgreSQL. Since it would be interesting to compare SQL Server and PostgreSQL/PostGIS, my new OperationContract includes the ability to switch from one to the other.
   [OperationContract]
  public List<string> GetGeom(string dbtype, string table, string bbox, string layer)

dbtype can be ‘PostGIS’ or ‘SQL Server’. I also refactored to allow selection of table and layer as well as a single string bbox.

Morten Nielsen has a blog on using npgsql here: SharpMap Shp2pgsql He used npgsql to create a nice little import tool to load shp format data into PostGIS with a GUI similar to the SQL Server shp loader. I used it on the SF Bay test data after modifying by adding a necessary System.Int64 type to the Type2PgType list and also to the Type2NpgsqlType. It then worked fine for loading my three simple test tables.

I can use npgsql to open a connection and add a query command for a PostGIS database:
   NpgsqlConnection conn = new NpgsqlConnection(“Server=127.0.0.1;Port=5432;User Id=your id;Password=your password;Database=TestShp;”);
   conn.Open();
   NpgsqlCommand command = new NpgsqlCommand(“Select *, asText(the_geom) as geom from \”" + table + “\” WHERE the_geom && SetSRID(‘BOX3D(” + bbox + “)’::box3d,4326);”, conn);

Using the npgsql reader lets me process through the query results one record at a time:
  NpgsqlDataReader rdr = command.ExecuteReader();
  while (rdr.Read()) {…}

which leads to a decision. SQL Server lets me grab a geom field directly into a geography data type:
   SqlGeography geo = (SqlGeography)rdr["geom"];
but npgsql has no equivalent convenience. Rather than work out the WKB stream I took the easy way out and converted geom on the PostGIS query like this: asText(the_geom) as geom
Now I can take the text version of the geometry and put it back together as a result string for my Silverlight MapControl svc_GetGeomCompleted.

[OperationContract]
public List GetGeom(string dbtype, string table, string bbox, string layer)
{
    List records = new List();

    if (dbtype.Equals("SQL Server"))
    {
        SqlServer ss = new SqlServer(layer, table, bbox);
        records = ss.Query();
     }
    else if (dbtype.Equals("PostGIS"))
    {
        PostGIS pg = new PostGIS(layer, table, bbox);
        records = pg.Query();
    }
    return records;
}

Listing 1 – Data Service Operation Contract

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Npgsql;
using System.Text.RegularExpressions;
using System.Text;
using System.Configuration;

namespace GeoTest.Web
{
 public class PostGIS
 {
  private string _layer;
  private string _bbox;
  private string _table;

  public PostGIS(string layer, string table,string bbox )
  {
   this._layer = layer;
   this._table = table;
   this._bbox = bbox;
  }

  public List Query()
  {
    StringBuilder sb;
    List records = new List();
    string[] fields;
    string geomType;
    string connStr = ConfigurationManager.AppSettings["PGconnecttionStr"];
    NpgsqlConnection conn = new NpgsqlConnection(connStr);
    conn.Open();
    NpgsqlCommand command = new NpgsqlCommand("Select *, asText(the_geom) as geom from \"" +
 _table + "\" WHERE the_geom && SetSRID('BOX3D(" + _bbox + ")'::box3d,4326);", conn);
    try
    {
     NpgsqlDataReader rdr = command.ExecuteReader();
     while (rdr.Read())
     {
      sb = new StringBuilder(_layer + ";");
      for (int i = 0; i < rdr.FieldCount - 2; i++)
      {
       sb.Append(rdr[i].ToString() + ";");
      }
      fields = Regex.Split(rdr["geom"].ToString(), @"\(+(.*?)\)+");
      geomType = fields[0];
      switch (geomType) {
       case "POINT":
        {
         sb.Append((fields[1].Split(' '))[1]);//latitude
         sb.Append(" " + (fields[1].Split(' '))[0]);//longitude
         break;
        }
       case "LINESTRING":
       case "MULTILINESTRING":
        {
         string[] pts = fields[1].Split(',');
         for (int i = 0; i < pts.Length; i++)
         {
          if (i > 0) sb.Append(",");
          sb.Append((pts[i].Split(' '))[1]);//latitude
          sb.Append(" " + (pts[i].Split(' '))[0]);//longitude
         }
         break;
        }
      }

      records.Add(sb.ToString());
     }
     rdr.Close();
    }
    catch (Exception e)
    {
     records.Add(e.Message);
    }
    conn.Close();
    return records;
   }
 }
}

Listing 2 – PostGIS class with Query Result as Text

This works after a manner, as long as MultiLineStrings are really just simple LineStrings along with other simplifications, but it would be much better to have a generalized OGC Simple Feature reader as well as a more efficient WKB reader. I am too lazy to work out all of that, so it is better to look around for a solution. In java the wkbj4 project is useful, but I didn’t find an equivalent wkb4c#. The npgsqltypes list features like point, path, polygon, but these are PostgreSQL types not the more useful OGC Simple Features found in PostGIS geometries. However, this port of JTS, Java Topology Suite, to C# is useful: Net Topology Suite

Code Geometry Type Coordinates
0 GEOMETRY X,Y
1 POINT X,Y
2 LINESTRING X,Y
3 POLYGON X,Y
4 MULTIPOINTe X,Y
5 MULTILINESTRING X,Y
6 MULTIPOLYGON X,Y
7 GEOMCOLLECTION X,Y

Table 1: OGC Simple Feature Geometry type codes (not all features)

Modifying the PostGIS class to take advantage of Net Topology Suite’s WKB and WKT readers results in this improved version:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.IO;
using Npgsql;

namespace GeoTest.Web
{
  public class PostGIS
  {
    private string _layer;
    private string _bbox;
    private string _table;

    public PostGIS(string layer, string table, string bbox)
    {
      this._layer = layer;
      this._table = table;
      this._bbox = bbox;
    }

    public List Query(bool wkb)
    {
      StringBuilder sb;
      List records = new List();
      string connStr = ConfigurationManager.AppSettings["PGconnecttionStr"];
      NpgsqlConnection conn = new NpgsqlConnection(connStr);
      conn.Open();
      NpgsqlCommand command;

      if (wkb) command = new NpgsqlCommand("Select *, AsBinary(the_geom) as geom from \"" +
_table + "\" WHERE the_geom && SetSRID('BOX3D(" + _bbox + ")'::box3d,4326);", conn);
      else command = new NpgsqlCommand("Select *, AsText(the_geom) as geom from \"" +
_table + "\" WHERE the_geom && SetSRID('BOX3D(" + _bbox + ")'::box3d,4326);", conn);
      try
      {
        NpgsqlDataReader rdr = command.ExecuteReader();
        while (rdr.Read())
        {
          IGeometry g;
          sb = new StringBuilder(_layer + ";");
          //concatenate other fields
          for (int i = 0; i < rdr.FieldCount - 2; i++)
          {
            sb.Append(rdr[i].ToString() + ";");
          }

          if (wkb)
          {
            WKBReader binRdr = new WKBReader();
            Byte[] geoBuf = new Byte[Convert.ToInt32((rdr.GetBytes(rdr.FieldCount - 1,
 0, null, 0, Int32.MaxValue)))];
            long cnt = rdr.GetBytes(rdr.FieldCount - 1, 0, geoBuf, 0, geoBuf.Length);
            g = binRdr.Read(geoBuf);
          }
          else //WKT
          {
            WKTReader wktRdr = new WKTReader();
            g = wktRdr.Read(rdr["geom"].ToString());
          }
          switch (g.GeometryType.ToUpper())
          {
            case "POINT":
              {
                sb.Append(g.Coordinate.Y);//latitude
                sb.Append(" " + g.Coordinate.X);//longitude
                break;
              }
            case "LINESTRING":
            case "MULTILINESTRING":
              {
                for (int i = 0; i < g.Coordinates.Length; i++)
                {
                  if (i > 0) sb.Append(",");
                  sb.Append(g.Coordinates[i].Y);//latitude
                  sb.Append(" " + g.Coordinates[i].X);//longitude
                }
                break;
              }
            case "POLYGON":
            case "MULTIPOLYGON":
              {
                for (int i = 0; i < g.Coordinates.Length; i++)
                {
                  if (i > 0) sb.Append(",");
                  sb.Append(g.Coordinates[i].Y);//latitude
                  sb.Append(" " + g.Coordinates[i].X);//longitude
                }
                break;
              }
          }
          records.Add(sb.ToString());
        }
        rdr.Close();
      }
      catch (Exception e)
      {
        records.Add(e.Message);
      }
      conn.Close();
      return records;
    }
  }
}

Listing 3 – PostGIS class improved using Net Topology Suite

Summary

Using just a rough response sense, the peroformance in best to worst order looks like this:

  1. PostGIS WKB
  2. PostGIS WKT
  3. MS SQL Server 2008

No real surprise here. This isn’t a precise comparison, but it does help me get a feel for response using these three approaches. Once OR/M is available for the geography data type, it will be of interest to see how a Linq OR/M generated version compares. All versions could be improved by using Base64 encoding to reduce the latency of pushing query results to the client view. An OR/M generated version should handle the DataService export to clients more efficiently.

Both npgsql and the .Net Topology Suite will be useful to anyone adapting .net c# code for use with spatial databases, especially considering the ubiquity of OGC Simple Feature types.

Posted in Uncategorized