April 15, 2014

Announcing GeoSIK v1.0

Hi there, long time no see! The GeoSIK project is over 2 years now, and the latest post on this blog is nearly as old.

But this does not mean that the project has been abandoned. On the contrary, GeoSIK is used on a daily basis at +Isogeo and it constitutes the basic building block of geomatics and interoperability in our flagship product. Specifically it is used to handle our geodata (in combination with SQL Server spatial data types), our serialization to GeoJSON, our projection operations (in combination with DotSpatial.Projections), our CSW services and our CSW harvesting operations.

So we think the time has come to release GeoSIK version 1.0, in the form of a proper set of NuGet packages. This will happen in the next few weeks, just in time for the French FOSS4G conference where a short demonstration will be made.

Stay tuned for more information about this release.

April 03, 2012

CSW Server Implementation (Part III)

Last time we laid the basis for a rough CSW server implementation. The main missing point to it is the ability to plug it to a database, which would make it much more useful…

But before doing this, I will take some time to polish our server a little bit so that it is easier to test, and more compliant to the CSW standard. These are hardly complicated tasks, but they are necessary and other posts are long enough already. So here we go:

  • Support the GET+XML protocol.
  • Complete the GetCapabilities operation.

The GET+XML protocol

What we need here is another service, set to another WCF entry point. As we will see, this service will be reusable as is for every OGC web service you wish to implement using GeoSIK. Thus we will call it Ows:

image

Add a reference to the System.ServiceModel.Web assembly, and just add a simple operation to the contract:

[ServiceContract]
public interface IOws {

[OperationContract]
[FaultContract(typeof(GeoSik.Ogc.Ows.V100.Types.ExceptionReport), Name="ExceptionReport")]
[WebGet(UriTemplate="?service={service}&version={version}&request={request}", BodyStyle=WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml, ResponseFormat=WebMessageFormat.Xml)]
Message Execute(string service, string version, string request);
}

This is quite simple, and you can recognize the 3 mandatory parameters that are common to all OGC web services (cf. specification §9.2.1). Now we have our contract, let’s implement it using the GeoSik.Ogc.Ows.ServiceLocator class. This class is able to find OWS implementations in an assembly and dynamically call its services. To make it work, just add an attribute to the Discovery service we implemented last time:

[GeoSik.Ogc.OwsDescription(Discovery.Service, Discovery.Version)]
public class Discovery:
GeoSik.Ogc.WebCatalog.Csw.V202.Discovery
{
// ...
}

Now use the ServiceLocator to find and call this service at runtime:

public class Ows:
IOws
{

public Message Execute(string service, string version, string request)
{
NameValueCollection parameters=WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;

var locator=new GeoSik.Ogc.Ows.ServiceLocator(Assembly.GetExecutingAssembly());
IXmlSerializable response=locator.InvokeService(parameters);

return CreateMessage(response, OperationContext.Current.IncomingMessageVersion);
}

private static Message CreateMessage(IXmlSerializable response, MessageVersion version)
{
StringBuilder serialized=new StringBuilder();
using (var xw=XmlWriter.Create(serialized))
response.WriteXml(xw);
var xr=XmlReader.Create(new StringReader(serialized.ToString()));
return Message.CreateMessage(version, "ExecuteResponse", xr);
}
}

Our service is implemented. We just have to tie it to a proper WCF endpoint in the Web.config file:

<service name="Tutorial.Ows" behaviorConfiguration="ows100PoxBehavior">
<endpoint address="" contract="Tutorial.IOws" binding="webHttpBinding" behaviorConfiguration="poxBehavior" />
</service>

That’s it. Let’s try to start the application and call the GetCapabilities operation at http://localhost:6155/Ows.svc/?service=CSW&version=2.0.2&request=GetCapabilities (your port number may vary). Works on my machine


The GetCapabilities operation


Now you can see the result of a GetCapabilities call, you can see that no operation is defined. The main reason for this is that GeoSIK is unable to detect the address of our services. We will have to help it out by implementing our own GetCapabilitiesProcessor class. Just override the GetEndPoints method and provide the proper parameters for our 2 services:

public class GetCapabilitiesProcessor:
GeoSik.Ogc.WebCatalog.Csw.V202.Discovery.GetCapabilitiesProcessorBase
{

public GetCapabilitiesProcessor(Discovery service):
base(service)
{ }

protected override IEnumerable<GeoSik.Ogc.OwsEndPoint> GetEndPoints()
{
var ret=new List<GeoSik.Ogc.OwsEndPoint>();

Uri baseUri=new Uri(
string.Format(
CultureInfo.InvariantCulture,
"{0}://{1}/",
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.Scheme,
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.Authority
)
);

ret.Add(
new GeoSik.Ogc.OwsEndPoint() {
AddOperationName=false,
BaseUri=new Uri(baseUri, "Ows.svc/"),
Method=GeoSik.Ogc.OwsMethod.Get
}
);
ret.Add(
new GeoSik.Ogc.OwsEndPoint() {
AddOperationName=true,
BaseUri=new Uri(baseUri, "Csw.svc/xml/"),
Method=GeoSik.Ogc.OwsMethod.Xml
}
);

return ret;
}
}

Make it the processor to use in the Discovery service:

protected override Discovery.GetCapabilitiesProcessorBase CreateGetCapabilitiesProcessor()
{
return new GetCapabilitiesProcessor(this);
}

Try the GetCapabilities operation again: we are compliant! If you delve a bit more in the GeoSIK libraries you will notice that there are many other opportunities for customization for this operation and that every other operation can be customized in pretty much the same way we just customized GetCapabilities. This gives you (the developer) all the power you need to implement the CSW server you have been dreaming of all this time (I may be a bit carried away here…).


Enjoy your CSW server for now. As is now usual, the complete source code for this tutorial can be downloaded here. Next time we will plug our server to a proper database: get set for some Sql Server and Entity Framework adventures!…

March 10, 2012

CSW Server Implementation (Part II)

After last time introduction to GeoSIK and CSW, now is the time to implement our first CSW server. We will setup the basic infrastructure for our application and implement a basic service. A more complete (and useful) implementation will be shown in a future post.
For this tutorial you will need Visual Studio 2010 (Express Edition should be enough), NuGet Package Manager 1.6.1 and GeoSIK 0.9 Preview 1. This latest package is just a zip containing the GeoSIK binaries in a preview version that seems usable in a tutorial like this one. Proper releases will be delivered as NuGet packages.

The application

What we need first is an ASP .NET Application that will host our CSW service:

image
Then we will add the needed references. Add the following NuGet packages: LinqToXsd 2.0.2, Common.Logging 2.0.0 and Json.NET 4.0.8. Then add the following assemblies from the GeoSIK Preview package: GeoSik.dll, GeoSik.Services.dll, Irony.dll, Irony.Interpreter.dll, ProjNet.dll (you should recognize most of the dependencies I introduced in the previous post).

The service (I)

Then we need a WCF Service named Csw.svc:

image
Change the definition of the ICsw interface to the following:
[ServiceContract]
public interface ICsw:
GeoSik.Ogc.WebCatalog.Csw.V202.IDiscovery
{
}
Easy, right? Now implement the associated Csw class. You should have 5 methods: make them throw NotImplementedExceptions for now, we’ll implement them later.
public class Csw:
ICsw
{

public DescribeRecordResponse DescribeRecord(DescribeRecord request)
{
throw new NotImplementedException();
}

public Capabilities GetCapabilities(GetCapabilities request)
{
throw new NotImplementedException();
}

public GetDomainResponse GetDomain(GetDomain request)
{
throw new NotImplementedException();
}

public GetRecordByIdResponse GetRecordById(GetRecordById request)
{
throw new NotImplementedException();
}

public IGetRecordsResponse GetRecords(GetRecords request)
{
throw new NotImplementedException();
}
}
Now configure the web service by editing the Web.config file in a usual way. We will provide the CSW service in a POST+XML fashion (cf. specification §10.3.1). The GET+KVP encoding will be provided later. For this service to be fully CSW compliant, we will need to specify a custom behavior so that exceptions will be returned in XML too (cf. specification §10.3.5). This behavior is defined in the GeoSik.Services assembly, so the configuration reads as follows:
<system.serviceModel>
<services>
<service name="Tutorial.Csw" behaviorConfiguration="ows100PoxBehavior">
<endpoint address="xml" contract="Tutorial.ICsw" binding="webHttpBinding" behaviorConfiguration="poxBehavior" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="poxBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="ows100PoxBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="True" />
<ows100PoxFaultBehavior />
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="ows100PoxFaultBehavior" type="GeoSik.Ogc.Ows.V100.PoxFaultBehaviorExtensionElement, GeoSik.Services" />
</behaviorExtensions>
</extensions>
</system.serviceModel>

Nothing fancy here if you are used to WCF services configurations: our service endpoint is defined.


The data source

Then we need to define a data source for our CSW server: our metadata store. This will usually be a database. In the context of this post though, we will define our metadata statically in memory. The link to a real database will be the subject of a following post.

What we need first is define a class that contains our metadata (a record, in cswspeak). To keep things simple, we will only support the basic, Dublin Core based, information like an identifier, a title… What we need to take into account, though, is that a CSW client can query our metadata in 2 ways:


  1. with an alias (a core queryable, cf. specification §6.3.2). These will be handled thanks to the GeoSik.Ogc.WebCatalog.Csw.V202.CoreQueryableAttribute that can be applied to any property.
  2. with an XPath expression (cf. specification §10.6.4.9). To that effect, our record class has to be serializable in XML with the XmlSerializer. Do not worry: it will never be serialized this way. But if it were, the GeoSik XPath resolver would be able to find the nodes according to the resulting XML. Thus we will use the usual XML serialization attributes to customize the serialization.
Then our class has to implement the GeoSik.Ogc.WebCatalog.Csw.V202.IRecord interface. The only method in this interface resulting class must create a GeoSik.Ogc.WebCatalog.Csw.V202.IRecordConverter interface. This is because directly serializing our record in XML is not enough to have a fully compliant CSW server: a client can cherry pick the piece of data it wants returned. This is the role of a record converter. For now, just return an instance of the GeoSik.Ogc.WebCatalog.Csw.V202.RecordConverter class. Our class looks like the following:
[XmlRoot("Record", Namespace=Namespaces.OgcWebCatalogCswV202, IsNullable=false)]
public class Record:
IRecord
{

[XmlElement("identifier", Namespace=Namespaces.DublinCoreElementsV11, DataType="string", Order=0, IsNullable=false)]
[CoreQueryable(CoreQueryableNames.Identifier)]
public string Identifier { get; set; }

[XmlElement("title", Namespace=Namespaces.DublinCoreElementsV11, DataType="string", Order=1, IsNullable=false)]
[CoreQueryable(CoreQueryableNames.Title)]
public string Title { get; set; }

[XmlElement("subject", Namespace=Namespaces.DublinCoreElementsV11, DataType="string", Order=2, IsNullable=false)]
[CoreQueryable(CoreQueryableNames.Subject)]
public string Subject { get; set; }

[XmlElement("BoundingBox", Namespace=Namespaces.OgcOws, Order=3, IsNullable=false)]
[CoreQueryable(CoreQueryableNames.BoundingBox)]
public ISimpleGeometry Coverage { get; set; }

[XmlElement("AnyText", Namespace=Namespaces.OgcWebCatalogCswV202, DataType="string", Order=4, IsNullable=false)]
[CoreQueryable(CoreQueryableNames.AnyText)]
public string AnyText
{
get
{
return string.Join(" ", Identifier, Title, Subject);
}
}

public IRecordConverter GetConverter(XmlNamespaceManager namespaceManager)
{
return new RecordConverter(namespaceManager);
}
}
Notice how the virtual AnyText element is a simple combination of the text elements of our record.

Our data source for this tutorial will simply be a manually created list of Record instances. But, you may ask, how do I initialize the Coverage property, which is a ISimpleGeometry? First of all: congratulations if you noticed that! The handling of geometries in GeoSik is way beyond this tutorial, so for now let me just show you an easy way to create geometries from their WKT definitions:
public static ISimpleGeometry CreateGeometry(string wkt)
{
var builder=new GeoSik.Ogc.Gml.V311.GmlGeometryBuilder();
builder.Parse(wkt, ProjNet.CoordinateSystems.GeographicCoordinateSystem.WGS84);
return builder.ConstructedGeometry;
}
We will come back to this in another post. And I will let you initialize your Record instances as an exercise.


The service (II)

Let’s do this thing! What we need now is to plug our previous data source into the GeoSik CSW base implementation. For this we need to implement the abstract GeoSik.Ogc.WebCatalog.Csw.V202.Discovery class (the name of this class is a reference to the class defined in the specification §7.2.4). As you will see, there are only 2 methods to implement, and they are not overly difficult:
public class Discovery:
GeoSik.Ogc.WebCatalog.Csw.V202.Discovery
{

protected override IQueryable GetRecordsSource(Uri outputSchema)
{
return _RecordList.AsQueryable();
}

public override string ProviderName
{
get { return "CSW Tutorial"; }
}

private static List<Record> _RecordList=new List<Record>();
}
Obviously, you would have to properly initialize a list of records: this data source is pretty empty…

Now we can implement the Csw service class that we partially implemented sooner. I will show you all of it now:
public class Csw:
ICsw
{

public DescribeRecordResponse DescribeRecord(DescribeRecord request)
{
return _Implementation.DescribeRecord(request);
}

public Capabilities GetCapabilities(GetCapabilities request)
{
return _Implementation.GetCapabilities(request);
}

public GetDomainResponse GetDomain(GetDomain request)
{
return _Implementation.GetDomain(request);
}

public GetRecordByIdResponse GetRecordById(GetRecordById request)
{
return _Implementation.GetRecordById(request);
}

public IGetRecordsResponse GetRecords(GetRecords request)
{
return _Implementation.GetRecords(request);
}

private Discovery _Implementation=new Discovery();
}

The server

That’s it: our server is complete. Granted, there are a few drawbacks:


  • only the POST+XML protocol is supported, which makes it quite hard to test unless you have a tool like Fiddler (and you know how to use it).
  • if you manage to launch a GetCapabilities request (the address of the service is something like ~/Csw.svc/xml/GetCapabilities), you will notice that no operation has been defined.

image
This could be fixed quite easily, but it goes beyond the scope of this post. And this will give us some work to do in the next tutorial, besides the connection to a database.

Those of you readers who are not very accustomed to the intricacies of the CSW specifications (though I think you should be if you want to mess with a CSW server) may not have been very impressed by all the above, but I know I have been surprised by the few lines of code this tutorial comprises. And even though there is still a lot of work to do, this makes me think that the project heads on a good direction. But let me know what you think.

For those interested, the source code of this tutorial can be downloaded heredownloaded here.

February 10, 2012

CSW Server Implementation (Part I)

This post is the first in series that will describe how to implement a CSW 2.0.2 server using GeoSIK.

A basic server is not very hard to implement and could be described in a single post, but I will take this opportunity to describe the architecture of GeoSIK, the choices that have been made so far and the reasons behind them. If you want to go straight away to the implementation of a server, you can browse the source code of the WebSample application that is included with the GeoSIK source code (this is the application we use to perform the OGC CITE tests).

I will describe this application in a later post, and will rather begin by a global description of the architecture of GeoSIK, in relation to CSW.

CSW 2.0.2

CSW 2.0.2 is the latest version of a standard defined by the OGC that allows the publication and search of metadata about geospatial data, services and related resources. This standard defines a set operations that can be performed by a server and is pretty loose about the protocol binding (Z39.50, CORBA, Web Services…).

GeoSIK focuses for now on implementing the mandatory operations only, over web services. The technology of choice here is obviously WCF, that allows the most code reuse in the implementation of a CSW server over SOAP and what the standard calls the KVP (HTTP GET query, XML answer) and XML (HTTP POST + XML query, XML answer) protocols.

As you can guess, there is a lot of XML manipulation involved in CSW, that I will describe in the following post. LinqToXsd has been chosen for this.

CSW 2.0.2 defines 2 languages (!) to query metadata on the server. One is XML based (it is the Filter 1.1 standard) and is pretty straightforward to interpret with LinqToXsd. The other one is text based (and called CQL), and we have used Irony to handle this syntax.

Then these queries are transformed into LINQ expressions, which give us the power to query virtually any kind of metadata store (provided there is a LINQ provider for it, of course). We have focused here on an implementation of CSW based on LINQ to Entities, over SQL Server 2008, to match the requirements of Isogeo.

The following posts will have me detail each of these subjects more precisely.

Note: the following link is used to claim this blog on Technorati 6VMQA52N2BQM

February 03, 2012

Introducing GeoSIK

GeoSIK is a brand new open source project which goal is to provide .NET developers with a set of libraries that would allow them to implement standard OGC Web Services with as much ease as possible.

GeoSIK is supported and currently developed by Isogeo, a French startup specialized in the management of geographical data. The (soon to be released) Isogeo product will feature a CSW server (CSW is an OGC standard for sharing geographical metadata) based on GeoSIK.

While there are already many tools and libraries that attempt to fill this purpose (like deegree, OWSLib or GĂ©oSource for instance), we felt that none of them were quite suited for our needs:

  • ease of use (for the developer as well as for the final user).
  • performance.
  • .NET technology.

So we started working on GeoSIK. This work is still in the early stages and much more is to come. You will be able to follow its development on CodePlex and on this blog in the coming months.

My name is Mathieu Cartoixa, I am the lead developer on GeoSIK and your host on the GeoSIK blog. I will keep you updated with the development of the project as well as with all the technical issues related to the implementation of OGC Web Services in .NET. I hope you will find as much pleasure in this journey as I already do.

Stay tuned!