ThinkGeo.com    |     Documentation    |     Premium Support

Custom FeatureLayer - Asynchron sever call in Silverlight

Hello,


I've written a custom FeatureLeayer, that inherits from FeatureLayer.

I override the FeatureSource too, and especially the Collection<Feature> GetFeaturesInsideBoundingBoxCore(RectangleShape boundingBox, IEnumerable<string> returningColumnNames) method.


My CustomLayerSource calls a custom server, in order to load data and transform them into features.


With the Wpf MapSuite product, this works perfectly.

But in silevrlight, I have to do everything in an asynchron way. So my problem is : how to implement this in an asynchron way ?

I tried a solution implementing ManualResetEvent, but this locks the main Thread, and my browser dies.


Or am I using the wrong solution, and do I have to implement some other class, with some callback ?




Many Thanks,

Guillaume.



Hello,  I still didn't find any solution for wath I'm trying to do. Here are some code, in order to explain in details what I'm trying to do :



public class CustomWfsFeatureSource : FeatureSource
{
        private const string WfsRequestFormat = @"{0}?Request=GetFeatures&TypeName={1}&bbox={2}&FORMAT=Binary&WIDTH={3}&height={4}";

        protected override Collection<Feature> GetFeaturesInsideBoundingBoxCore(RectangleShape boundingBox, IEnumerable<string> returningColumnNames)
        {
            try
            {
                // When MapSuite is configured with multithread
                // this method can be called from 2 different threads and an exception can be thrown.
                // => We have to use a locker.
                lock (_locker)
                {
                    if (TypeNames == TypeNames.NoTypeName)
                    {
                        // No typeName selected
                        return new Collection<Feature>();
                    }

                     string typeName = TypeNamesHelper.GetStringFromTypeNames(TypeNames);

                    double minX = boundingBox.UpperLeftPoint.X;
                    double maxY = boundingBox.UpperLeftPoint.Y;
                    double maxX = boundingBox.LowerRightPoint.X;
                    double minY = boundingBox.LowerRightPoint.Y;
                    string bbox = String.Format("{0}, {1}, {2}, {3}", minX, minY, maxX, maxY);

                    string request = String.Format(WfsRequestFormat, GisParameters.WfsUri, typeName, bbox, Width, Height);

                    byte[] featuresAsByteArray = GetFeaturesFromWfs(request);

                    var features = FromByteArray(featuresAsByteArray);
                    return features;
                }
            }
            catch (Exception e)
            {
                KubaLogger.Trace(KubaTraceLevel.Error, e.ToString());
                return new Collection<Feature>();
            }
        }

        private byte[] GetFeaturesFromWfs(string url)
        {
            var uri = new Uri(url);
            WebRequest webRequest = SingletonManager.Instance.Get<ServiceFactory>().CreateHttpWebRequestForGis(url); // Do not use new HttpWebRequest security reason of authentication (lsr)
            var webResponse = (HttpWebResponse) webRequest.GetResponse();
            using (Stream stream = webResponse.GetResponseStream())
            {
                // We use a temporary MemoryStream, otherwise in some case, reading directly the stream from GetResponseStream()
                // throws an exception.
                byte[] buffer = new byte[1024];
                using (var ms = new MemoryStream())
                {
                    int read = stream.Read(buffer, 0, buffer.Length);
                    while (read > 0)
                    {
                        ms.Write(buffer, 0, read);
                        read = stream.Read(buffer, 0, buffer.Length);
                    }

                    ms.Position = 0;
                    return ms.ToArray();
                }
            }
        }

        private byte[] GetFeaturesFromWfs(string url)
        {
            var uri = new Uri(url);
            WebRequest webRequest = SingletonManager.Instance.Get<ServiceFactory>().CreateHttpWebRequestForGis(url); // Do not use new HttpWebRequest security reason of authentication (lsr)
            var webResponse = (HttpWebResponse) webRequest.GetResponse();
            using (Stream stream = webResponse.GetResponseStream())
            {
                // We use a temporary MemoryStream, otherwise in some case, reading directly the stream from GetResponseStream()
                // throws an exception.
                byte[] buffer = new byte[1024];
                using (var ms = new MemoryStream())
                {
                    int read = stream.Read(buffer, 0, buffer.Length);
                    while (read > 0)
                    {
                        ms.Write(buffer, 0, read);
                        read = stream.Read(buffer, 0, buffer.Length);
                    }

                    ms.Position = 0;
                    return ms.ToArray();
                }
            }
        }
}

Actually, as you can see, I'm trying to get serialized features from a server. This is some kind of custom Wfs Server, it sends a list of features serialized. This code works really good with the WpfMapSuite client. The problem I have is : How to write this code for Silverlight ? The problem is that the GetFeaturesInsideBoundingBoxCore() method has a return value, but in Silverlight, every service call must be asynchron, and I can't see any callback property in the FeatureSource class.  Am I inheriting the wrong class ?  How can I achive my goal (loading data from a custom wfs server, and display them in a MapSuite layer or overlay) ?  Any answer or help would be really appreciated. Thanks, Guillaume.



 


Guillaume,
Just as you have got, there isn’t synchronous in Silverlight, and of course, we are unable to return the specified features in “GetFeaturesInsideBoundingBoxCore()” method. I recommend you to inherited from FeatureLayer instead of FeatureSource and overwrite the “OpenCore”, “CloseCore” and “DrawCore”. In the DrawCore method, we can use the Asyc request to get all the features and draw them to canvas. Here is the demo code:
public class WfsFeatureLayer : FeatureLayer 
    {
        protected override void OpenCore()
        {
            base.OpenCore();
        }
 
        protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            // Todo: Here we can get the features and draw them in the input canvas in CallBack method
 
            base.DrawCore(canvas, labelsInAllLayers);
        }
 
        protected override void CloseCore()
        {
           base.CloseCore();
        }
    }
 
 
Thanks,
Johnny

Hello Johnny, 
  
 Thanks for your answer. 
 I implemented something, and it seem to work well : the features seems to be sent correctly from the server to the client. 
  
 Now I’m having some trouble to draw the features to the GeoCanvas. 
 Here is the code : 
  
 
protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            //base.DrawCore(canvas, labelsInAllLayers);

            GetFeaturesInsideBoundingBoxCore(
                delegate(Collection<Feature> features, AsynchronServiceOperationResult result)
                    {
                        if (result.Sucessful && features.Count > 0)
                        {
                            canvas.BeginDrawing(canvas.NativeImage, canvas.CurrentWorldExtent, GeographyUnit.Meter);
                            try
                            {
                                foreach (var feature in features)
                                {
                                    canvas.DrawLine(feature, new GeoPen(KubaGisStyles.Kanton.OutlinePen.Color));
                                }
                            }
                            finally
                            {
                                canvas.EndDrawing();
                            }
                        }

                    }, canvas.CurrentWorldExtent);
        }
 
  
 I have some questions about this : 
  
 1/ When calling canvas.BeginDrawing(), I get the following exception : 
 “The GeoCanvas is currently not drawing.  Please call the BeginDraw method before calling this method.” 
 I’m nopt wure of the parameters, and I noticed that canvas.NativeImage is null.  
 => How can I implemented this correctly ? 
  
 2/ When calling base.DrawCore(canvas, labelsInAllLayers), I noticed that the GetAllFeaturesCore() of the FeatureSource is called. My problem is that I cannot implement this method in my code, becore I don’t want to ask the server for all features, it contains too many data, and I don’t need that.  
 => Do I have to call the base method or can I leave it commented ? 
  
 3/ When manually drawing the the features, I need a GeoPen parameter in the DrawLine() method. Furthermore, the server can send me not only lines, but area and point too.  
 => Do I have to manually switch every ShapeType and call the right method to draw the feature ? Is ther any base Draw to call ? 
  
 4/ I defined some ClassBreakStyle in my CustomWfsLayer, with several ZoomLevels.  
 => How can I use them in the DrawCore() method ? Do I have to implement something ? 
  
  
 Thanks, 
 Guillaume. 


Hello, 
  
 Sorry to insist, but I really need some explanations on the questions in my previous post. 
 Pease can someone give me some advices on how to correct implement the DrawCore method, regarding the 4 points above ? 
  
 Many thanks, 
 Guillaume.

 


Hi, Guillaume
Sorry for so delayed response.
For your questions, here are our suggestions for you:
1. The exception is caused by the multi-thread operation. You can add lock or other thread synchronization methods to overcome that.
2. I suggest that you override the method of GetFeaturesForDrawingCore method, and it’s the first method called when the FeatureLayer needs to get the features to render. If not, we will call the GetAllFeaturesCore method in the GetFeaturesInsideBoundingBoxCore for the FeatueSource by default
3.  I don’t suggest you to draw the features mutually. If you have set the right styles for the FeatureLayer, and we will in charge of the rendering. The feature source represents feature data and encapsulates much of the logic for handling transactions and ensuring the data is consistent regardless of any projections applied.
4. The custom styles we set on the ZoomLevels, so first off you need to get the current zoom level and get the styles from that.
 
Another thing is that if you want to use WFS service, and I suggest that you give a try for the WfsFeatureLayer we have included it in our MapSuiteCore assembly.
 
If you still have additional questions please let us know.
 
Thanks,
 
Khalil

Hello Khalil, and thanks for your answer. 
  
 1/ This exception was not due to a multithread operation. Actually, I noticed the following : 
  
 
private void DrawCoreInternal(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers, RectangleShape worldExtent, WriteableBitmap image, Collection<Feature> features)
        {
            if (!image.Dispatcher.CheckAccess())
                throw new KubaTechException(“Invalid UI Thread ! Consider using Dispatcher.BeginInvoke() instead !”);

            lock (image)
            {
                canvas.BeginDrawing(image, worldExtent, GeographyUnit.Meter);
                this.OpenLayer();
                try
                {
                    // Let’s write a little trick to have the right features in the GetFeaturesForDrawingCore() method
                    // We will save the feature to draw in a dictionary in the FeatureSource, and the key of the dictionary is the worldExtent object.
                    CustomWfsFeatureSource.FeaturesToDraw = features;
                    base.DrawCore(canvas, labelsInAllLayers);
                    CustomWfsFeatureSource.FeaturesToDraw = null;
                }
                finally
                {
                    this.CloseLayer();
                    canvas.EndDrawing();
                }
            }
        }

protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            var worldExtent = canvas.CurrentWorldExtent;
            var image = (WriteableBitmap)canvas.NativeImage;
            GetFeaturesInsideBoundingBoxCore(
                delegate(Collection<Feature> features, AsynchronServiceOperationResult result)
                    {
                        // canvas.CurrentWorldExtent and canvas.NativeImage are not valid anymore when coming back from the asynch call.
                        // I don’t understand why, but the canvas object is modified.
                        // => To solve this we have to use the original objects to draw the features on the map
                        if (result.Sucessful && features.Count > 0)
                        {
                            image.Dispatcher.BeginInvoke(() => DrawCoreInternal(canvas, labelsInAllLayers, worldExtent, image, features));
                        }
                    }, worldExtent);
        }
 
  
 Before the call of the GetFeaturesInsideBoundingBoxCore asynchron method, the canvas object seems to be valid. But when we enter the callback method, the canvas.CurrentWorldExetnt throws an exception, and the canvas.NativeImage is null. Thus I cannot call the canvas.BeginDrawing() method with those parameters. In the previous code, I tried to remember the original values and use them to call BeginDraw(), but I’m having another problem. 
  
  
 2/ I override the DrawCore method ni order to insert an asynchron call to load features from a server. Overriding the GetFeaturesForDrawingCore() method was a good tip, the GetAllFeaturesCore() method is not called any more. 
 But the load of the features is made by the DrawCore() method. So I have the loaded features in the delegate. Then when I call base.DrawCore(canvas, labelsInAllLayers), the GetFeaturesForDrawingCore() method of the featureSource is called, and must send a list of features…  
 I think it should be to the GetFeaturesForDrawingCore() to load the daat synchron, but this is not possible, because it has a erturn value, and no callback. 
 Si what I’m doing is : 
 - override Layer.DrawCore in order to load feature. 
 - In the callback, I somehow set into the FeatureSource the Feature list. 
 - Then I call the base.DrawCore() method. 
 - The overriden FeatureSource.GetFeaturesForDrawingCore() sends as return value the list of the loaded features. 
 - And then… here is the problem… I get these ArgumentException : 
  
  
"Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt."

Translation : An element with the same Id already exists.
StackTrace :
   bei System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   bei System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   bei System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   bei ThinkGeo.MapSuite.SilverlightCore.FeatureSource.GetFeaturesForDrawing(RectangleShape boundingBox, Double screenWidth, Double screenHeight, IEnumerable`1 returningColumnNames)
   bei ThinkGeo.MapSuite.SilverlightCore.FeatureLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)
   bei CadRz.Kuba.Framework.Presentation.Web.Gis.Wfs.CustomWfsFeatureLayer.DrawCoreInternal(GeoCanvas canvas, Collection`1 labelsInAllLayers, RectangleShape worldExtent, WriteableBitmap image, Collection`1 features)
 
  
 I think I’m doing something wrong, but I cannot notice exaclty why. 
 The first problem for me is : 
 - Am i overriding the right methods in order to do what I’m trying to do ? 
 If yes : 
 - Why is the canvas.NativeImage null and why does Canvas.CurrentWorldExtent throw an Exception in the callback ? 
 - Is it right to remeber the original values for those properties, and to work with them ? 
 - Why do I get the ArgumentException ? I tried with only 1 feature loaded from the server, and I get the same exception. I really don’t understand. 
  
  
 I know there is a WfsOverlay, but at start I didn’t want to use it, because even if I called this a “WFS”, it hasn’t any of the standard of a right WFS Service. It’ simply a custom service that sends serialized data, and those data are desiarialized client side, and featuers are created clientside too. 
 Furthermore, I used a solution overriding a FeatureLayer for the WpfClient, and it worked perfectly. Some code is shared between the 2 client (Wpf and Silverlight), and I wanted to make it simple. 
  
 Therefore I would prefer implementing a solution with FeatureLayer. But if you says me your WFS Overlay can handle a total custom server with custom request like mine, I will consider using it. 
  
 Thanks again for you answer,  
 Guillaume.

 


Hi Guillaume,
Thanks for you detailed information, we also tried to find some wfs servers to overwrite a custom WfsFeatureLayer following your ideas, but unfortunately, workable wfs servers are unavailable, is it possible to give us a wfs server for testing?
From your description, i don’t think something is wrong with you except using “image” to lock. I’m not 100% sure it works normally, I checked the code again and the exception “The GeoCanvas is currently not drawing. Please call the BeginDraw method before calling this method." does occur because of the multithread. Additionally, the new exception “Am element with the same id already exists” is caused by the same reason.  In other words, the code “lock(image)” doesn’t work.
Please don’t worry the class and method that you are overwriting. I think it’s proper, the problem should be how to lock the “Drawing process”. Can you try define a static global variable to be the “Lock” parameter? Or can you send us a the sample code to forumsupport@thinkgeo.com ?
 
Thanks,
Johnny
 

Hi Johny, and thanks for your answer. 
  
 I’ve tried to use a static variable as the locker, but I’m having the same problem. 
  
 I’ve taken som time and I’ve made a small application reproducing my problem. It hasn’t any Wfs server, but I simulated the asynchron server call with a simple method that creates a collection of features and send them as result. 
 I have exactly the same problem as in my original code.  
  
 I’ve sent it at forumsupport@thinkgeo.com
  
 I’ll be glad if you can have a look and give me some advices or solution. 
 Many Thanks, 
 Guillaume. 


 


Hi Guillaume,
Thanks for your demo, but we’re unable to compile it, it always gives the error “Could not load type ‘System.Runtime.Versioning.TargetFramworkAttribute’” when we add the reference any of “CadRz” dlls. We tried removing them and rewrite some custom classes which is same to ones in “CadRz”, but it still runs into error. Is there anything wrong with us? Or can you send us another one?
After checking the code, it seems like you are using two different objects to be the locker, one is for GetFeatures, and another one is for DrawFeatures, could you try the same locker for both of them?
Thanks,
Johnny

Hello Johny. 
  
 Unfortunately I couldn’t find the problem with this sample solution. 
  
 I finally found another solution in order to do what I wanted to do : I made soma asynchron call, and hthe delegate adds features in an standard inMemoryLayer. I don’t overload the DrawCore() method anymore, beacuse I could not make it work. 
  
 Thanks for all your help, 
 Guillaume.

HI Guillaume, 
  
 Very glad to hear that you have made it sense. I guess you store all the features into an inMemoryFeatureLayer and use it for draw. Thanks a lot for sharing your experience 
  
 Johnny 


Hello, 
  
 I have a similar requirement. I need to be able to asynchronously render features on the Silverlight map. It looks like WFS may be the best approach. Can you tell me if you will be supporting the asynchronous requirement in the next version of the Silverlight control? 
  
 Thanks, 
 Matt

Matt, 
  
 We have already added it to the list and we will try to implement it before the next public release(May 1st), thank you and Guillaume for the good suggestions. 
  
 Thanks, 
  
 James 


Hi, 
  
 I’m having a similar issue and was wondering if this got implemented by the May 1st release… and if so if you had any documentation on how to use it.  Thanks, 
 .Ryan.

Hi Ryan, 
  
 I review our change log and code, I think we haven’t added that in silverlight version till now. 
  
 Could you let us know your problem? Maybe we can have workaround for that. 
  
 Regards, 
  
 Don

Hi Don, 
  
 I’m working on a demo to show the ability to port our WPF app to Silverlight.  We have data in a database on the server, and from that, I would like to be able to select data and display it on the map as a Marker.  Right now, I’m using a FeatureSource which connects to a controller on the backend which fetches the data and does all its magic to transfer it across the web and give it back to me in a format I can use.  This is all async, and so it doesn’t really play well with FeatureSource, which expects a synchronous call. 
  
 Thanks, 
 .Ryan.

Hi Ryan, 
  
 Thanks for your detail description, I will discuss your scenario with our silverlight developer and update this post. 
  
 Regards, 
  
 Don

Ryan, 
  
 The WFS still be asynchrony, I think we cannot implement a synchronous call in Silverlight. 
  
 As your requirement, we think overwirte FeatureSource shouldn’t work well, if you call asynchrony function in getfeatures function of featuresource, it won’t return result. If you want to implement marker, maybe you can try to overwrite InmemoryMarkerOverlay, then overwrite the DrawCore function, then asynchrony request data in DrawCore, and get all data in callback function, then convert data to marker and add them to map. This looks like WFS but they are difference. 
  
 Regards, 
  
 Don


Posted By Guillaume on 11-24-2010 03:22 AM


Hello Johny. 



Unfortunately I couldn’t find the problem with this sample solution. 



I finally found another solution in order to do what I wanted to do : I made soma asynchron call, and hthe delegate adds features in an standard inMemoryLayer. I don’t overload the DrawCore() method anymore, beacuse I could not make it work. 



Thanks for all your help, 

Guillaume.


Hi, could you share it? Thx a lot.