I am working on The XamWebMap control to render a set of big shapefiles. I would like to optimize my application so i could download the shapefiles in ziped format and put it in the Isolated storage, and to re use the same file over the life of the application. However the Shapefile reader only takes URI. Is there a way i could make it read from the Isolated storage? Are there any other optimization techniques i could use to avoid downloading of the big shapefiles?
Many Thanks
A point I have not elaborated, I am also interested in creating an out of browser application once i have the In browser Experience in place. So that is also another reason why I need the loading from the Isolated Storage.
I don't think its possible, currently, to read a file out of isolated storage using a URI, and I'm not sure if there is any way to register your own URI handlers with Silverlight. You're best bet would probably be to make a feature request to be able to provide a stream to ShapeFileReader. http://devcenter.infragistics.com/protected/requestfeature.aspx
If you are feeling courageous you could try using the SQLShapeReader which takes an IEnumerable. You can see the SqlShapeReader help for more info, but this would require that you converted your shape files into that format, and that you could serialize the data into files in isolated storage, so the feature request may be a better fit, depending on your situation.
There may be some way of redirecting a URI get to the isolated storage, but I haven't come accross one yet. Make sure to let us know if you do!
-Graham
Actually, it turns out you can redirect URI requests by registering custom prefixes in Silverlight 3 and later, here's a sample I've worked up that should get you started. You will notice two buttons, one that downloads the map files into isolated storage, and the other that will load the shape files from isolated storage using the ShapeFileReader:
The Xaml:
<Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition /> </Grid.RowDefinitions> <Button Grid.Row="0" x:Name="download" Click="download_Click" Content="Download Map" /> <Button Grid.Row="1" x:Name="loadFromIso" Click="loadFromIso_Click" Content="Load from ISO" /> <igMap:XamWebMap Grid.Row="2" x:Name="theMap"> <igMap:XamWebMap.Layers> <igMap:MapLayer x:Name="layer1" /> </igMap:XamWebMap.Layers> </igMap:XamWebMap> </Grid>
The code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); WebRequest.RegisterPrefix("iso", new IsoStorageRequestCreator()); } private void download_Click(object sender, RoutedEventArgs e) { wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted); wc.OpenReadAsync(new Uri("ShapeFiles/usa_st.shp", UriKind.RelativeOrAbsolute), "usa_st.shp"); } private WebClient wc = new WebClient(); void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication()) { using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(e.UserState.ToString(), FileMode.Create, isf)) { using (StreamWriter sw = new StreamWriter(isfs)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = e.Result.Read(buffer, 0, buffer.Length)) != 0) { isfs.Write(buffer, 0, bytesRead); } } } } if (e.UserState.ToString().EndsWith("shp")) { wc.OpenReadAsync(new Uri("ShapeFiles/usa_st.dbf", UriKind.RelativeOrAbsolute), "usa_st.dbf"); } } private void loadFromIso_Click(object sender, RoutedEventArgs e) { ShapeFileReader reader = new ShapeFileReader(); reader.Uri = "iso://usa_st"; theMap.Layers[0].Reader = reader; theMap.Layers[0].Imported += new MapLayerImportEventHandler(MainPage_Imported); theMap.Layers[0].ImportAsync(); } void MainPage_Imported(object sender, MapLayerImportEventArgs e) { theMap.WindowFit(); } }
The important line here being:
WebRequest.RegisterPrefix("iso", new IsoStorageRequestCreator());
Which will register the iso:// prefix with our custom request creator which is defined as such:
public class IsoStorageRequestCreator : IWebRequestCreate { #region IWebRequestCreate Members public WebRequest Create(Uri uri) { return new IsoStorageRequest(uri); } #endregion }
Which this will make sure that for the iso scheme the WebClient will create IsoStorageRequests.The IsoStorageRequests will, in turn, create IsoStorageResponses, which will return streams for the appropriate files in Isolated storage.
public class IsoStorageRequest : WebRequest { public override Uri RequestUri { get { return requestUri; } } private readonly Uri requestUri; public IsoStorageRequest(Uri uri) { this.requestUri = uri; } public override void Abort() { throw new NotImplementedException(); } public override IAsyncResult BeginGetRequestStream( AsyncCallback callback, object state) { throw new NotImplementedException(); } public override IAsyncResult BeginGetResponse( AsyncCallback callback, object state) { IsoStorageAsyncResult result = new IsoStorageAsyncResult() { AsyncState = state }; callback(result); return result; } public override string ContentType { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override Stream EndGetRequestStream( IAsyncResult asyncResult) { throw new NotImplementedException(); } public override WebResponse EndGetResponse( IAsyncResult asyncResult) { return new IsoStorageResponse(requestUri); } public override WebHeaderCollection Headers { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override string Method { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } } public class IsoStorageResponse : WebResponse { private Uri uri; private long contentLength = 0; public IsoStorageResponse(Uri uri) { this.uri = uri; } public override long ContentLength { get { return contentLength; } } public override string ContentType { get { return @"application/octet-stream"; } } public override Stream GetResponseStream() { using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication()) { string file = uri.AbsoluteUri.Replace("iso://", ""); if (file.EndsWith("/")) { file = file.Substring(0, file.Length - 1); } using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream( file, FileMode.Open, isf)) { contentLength = isfs.Length; byte[] buffer = new byte[4096]; int bytesRead; MemoryStream mem = new MemoryStream(); while ((bytesRead = isfs.Read(buffer, 0, buffer.Length)) != 0) { mem.Write(buffer, 0, bytesRead); } mem.Seek(0, SeekOrigin.Begin); return mem; } } } public override void Close() { throw new NotImplementedException(); } public override Uri ResponseUri { get { return uri; } } } public class IsoStorageAsyncResult : IAsyncResult { #region IAsyncResult Members private object _asyncState; public object AsyncState { get { return _asyncState; } set { _asyncState = value; } } public System.Threading.WaitHandle AsyncWaitHandle { get { throw new NotImplementedException(); } } public bool CompletedSynchronously { get { throw new NotImplementedException(); } } public bool IsCompleted { get { return true; } } #endregion }
Let me know if you have any questions.-Graham
Hi
I'm not able to use your sourcecode. I add to my WebRequest prefix iso but I never enter in
Do you have any idea?
Thnaks
First of all, are your trying this with the map or are you trying to use it with something else in Silverlight? Unfortunately many Silverlight components like the Image control seem like they make their web requests with lower level functionality that is not possible to redirect with the above logic. Since we request the shapefiles using a higher level API than, say, the image control, the above redirect functions to insert the custom request handler when you use the iso prefix.
If you are trying this with the map, make certain you are adding the prefix before the map would be initialized. You may even want to add it to the application initialization.
Yes, I'm relatively certain that Silverlight functionality for multi scale images byspasses the redirects. I wanted to try something like that myself earlier to try and generate the image tiles on the client side. But it did not appear to work properly.
I suspect a lot of the web requests made by the Silverlight runtime itself are handled in native code (unfounded speculation), and don't have the same hooks to call your custom request handler, unfortunately. But this approach should work for any shape file redirection.
I do wonder if there is something browser specific you can do to try and intercept the requests that Silverlight is sending through the browser, and route them back into the SL runtime, but I have not investigated this.
I use it with MultiScaleSource component. I thinks it's the reason?
Thanks