WMS Data

I was having a discussion with Kurt Guenther from Infusion yesterday on the topic of WMS servers and VE3D.  There is a large amount of very interesting spatial data out there served by Web Map Services, or WMS servers.  VE3D is able to process this data using ConnectionParameter-based DataSources.  Setting it up is pretty easy:

ConnectionParameters cp = new ConnectionParameters(https://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Portland/Portland_ESRI_Neighborhoods_AGO/MapServer/export?bbox={16},{17},{18},{19}&bboxSR=4326&layers=&layersDefs=&size=256,256&imageSR=102113&format=png&transparent=true&dpi=&f=image);

            string connectionParameters = cp.ToString();

            DataSourceLayerData layerData = new DataSourceLayerData(LayerName, Name, connectionParameters, DataSourceUsage.TextureMap);

            Host.DataSources.Add(layerData);

Drop this in to your Activate (plug-in) or Initialized handler (winforms etc) app and fly to Portland, OR to see the data (thanks to ArcGIS for the sample data and Kurt for the pointer).

I won't go into all the details of the WMS spec here, but I will call out a few important parts.  First, in order for this to work you must specify the bbox to be in Geographic coordinates (4326).  This is usually the default.  Second, you must pay attention to the returned projection.  In the above case, we have instructed the server to return 102113, which is Web Mercator, VE3D's native image projection.  This data can be consumed directly with very little overhead.  However, different servers support only certain formats and projections, and you must be careful for any particular server (it is possible to query this using WMS's GetCapabilities).  Nearly all support returning images in Geographic coordinates, 4326.  It is possible to instruct VE3D to reproject this to Web Mercator (this is the only reprojection VE3D will do for you, fortunately it will work for the majority of data).

Now, to look at ConnectionParameters and what it can do for you.  First, there's the big url.  Notice that the bbox has string.Format replacement variables in it:  {16}, {17}, etc.  Requests to the server are made on a per-tile basis (the T key is useful here).  For each request, these variables are replaced with data specific to the tile in question.  Here is a list of available parameters:

0 Reserved

1 Map Style

2 Round-robin integer

3 Reserved

4 Quadkey

5 Extension

6 Generation

7 Stripe (0-3)

8 Tile Host

9 App Host

10 Language code

11 Region code

12 Tile LOD

13 Tile X

14 Tile Y

15 Low order stripe (0-1)

16 MinLong

17 MinLat

18 MaxLong

19 MaxLat

20 RequestToken

21 Culture name

The most interesting ones are 4 (the main quad-key, very useful for already-tiled data), 12, 13, 14, and and 16-19.  To see some of this in action, check out the TerrainImages sample (also available in the sample code of course).  Programatically, you can investigate these values using the TileId class, using GetRequestCode, GetPosition, and GetLatLonBoundingBox.

As for other values on ConnectionParameters, you can control reprojection (if your data is in Geographic, specify CoordinateReferenceSystem = WGS84CoordinateReferenceSystem.Instance, as noted before this is the only reprojection natively supported, so otherwise leave this field uninitialized), the bounds of where the data is valid is terms of Lat/Long and LOD (useful to reducing unnecessary network requests), and caching behavior.

Caching behavior deserves special discussion, because WMS servers are often quite slow.  By default, data loaded using ConnectionParameters is not cached locally.  Once it moves out of memory, it must be re-queried.  If you set CacheRetention however, the data will be kept locally for the period of time specified (unless it is evicted due to space restrictions in the interim).  If you set CacheRetention = new TimeSpan(24, 0, 0), for example, for 24 hours from the time of query a given tile will not be re-queried.  Note that some servers are dynamic, such as weather data, so it's important to use an appropriate value here.

And just to give Kurt a poke in the ribs, please remember to only add DataSources after/during the Initialized event!

 Update:

John Fletcher from Latitude Geographics pointed out that the example I give above isn't actually WMS compliant in its query parameters.  He suggests an alternate sample, of the British Columbia, CA area:

ConnectionParameters

p = new ConnectionParameters("https://openmaps.gov.bc.ca/mapserver/libcwms2?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&LAYERS=DBM_7H_MIL_BATHYMETRIC_POLY,BC_LABEL,DBM_7H_MIL_DRAINAGE_LINE,DBM_7H_MIL_DRAINAGE_POLY,DBM_7H_MIL_ROADS_LINE,DBM_7H_MIL_POLITICAL_POLY_PS,DBM_7H_MIL_POPULATION_POINT&STYLES=,,,,,,&SRS=EPSG:4326&BBOX={16},{17},{18},{19}&WIDTH=256&HEIGHT=256&FORMAT=image/png&TRANSPARENT=TRUE&EXCEPTIONS=application/vnd.ogc.se_inimage&");

p.CoordinateReferenceSystem = Microsoft.MapPoint.CoordinateSystems.

Wgs84CoordinateReferenceSystem.Instance;

Host.DataSources.Add(

new DataSourceLayerData("foo", "bar", p.ToString(), DataSourceUsage.TextureMap));

John also provided a link to the WMS spec repository (https://www.opengeospatial.org/standards/wms). Note that the sample above is for WMS 1.1.1. The most current version is 1.3.0, but many servers use the older, or support both. No matter which you use (or even for something a little off spec like my original example) the interface with VE3D is the same.