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 Graham, Your solution has really helped me alot. One final question How can I check existence of my files in the isolated storage because the filenames are differently stored in isolated storage. For example if I save Regions.dbf it is saving with different notation(probably Octet stream). But is there any way we can give the file name of our choice like "Regions" so as to check the existence of the file in isolated storage
If the data mapping isnt working, then it probably can't find the dbf file. Could you share a sample and I can help point out the issue?
The dbf file that it searches for uses a hard coded string in the above sample. Fix that so that it trims the shp and adds a dbf to whatever file name is presented, and it should resolve the issue.