REACTIVE-ando información de la DGT en Windows Phone 7

Desde hace algún tiempo he estado mirando y estudiando las posibilidades de las Reactive Extensions -Rx-, las cuales se encuentran actualmente en los Labs de Microsoft. Con la aparicición de Windows Phone 7, tenemos a nuestra disposición parte de estas extensiones dentro de los ensamblados Microsoft.Phone.Reactive y System.Observable. De hecho existe una versión específica de Rx para WIndows Phone 7 además de las que vienen instaladas en la ROM por defecto pero cuya instalación no está incluida en la ROM.

El caso es que durante el fin de semana se me ocurrió la idea de obtener la información que la Dirección General de Tráfico expone a través de un feed en XML de forma que pueda ser consultada por una aplicación WP7. Recordaba algunos ejemplos, básicamente orientados a la API de Twitter, en los que mostraba toda la potencia de la Rx en operaciones asincronas y manejo de eventos.

Lo que inicialmente se convirtió en un ejercicio de refresco y puesta en práctica de las Rx finalmente se ha convertido en una aplicación que publicaré en el marketplace de WP7 en breve. Además, compartiré el código íntegro de dicha aplicación en codeplex. Anunciaré tales novedades en este mismo blog.

Información de la DGT

Antes de entrar en profundidad vamos a ver que es lo ofrece la API de la DGT. La url es : http://dgt.es/incidencias.xml y el resultado:

image

Es decir que un elemento o incidencia tiene el siguiente formato XML:

   1: <incidencia>
   2:     <tipo>METEOROLOGICA</tipo> 
   3:     <autonomia>ANDALUCIA</autonomia> 
   4:     <provincia>JAEN</provincia> 
   5:     <matricula>J</matricula> 
   6:     <causa>LLUVIA</causa> 
   7:     <poblacion>SANTA ELENA</poblacion> 
   8:     <fechahora_ini>2011-03-12 19:48</fechahora_ini> 
   9:     <nivel>VERDE</nivel> 
  10:     <carretera>A-4</carretera> 
  11:     <pk_inicial>245.0</pk_inicial> 
  12:     <pk_final>288.0</pk_final> 
  13:     <sentido>Ambos sentidos</sentido> 
  14:     <hacia>Ambos</hacia> 
  15: </incidencia>

 

Y la representación en C# es básicamente….

   1: public class Incidencia 
   2: {
   3:  
   4:     public Incidencia()
   5:     { }
   6:  
   7:     public string Tipo { get; set; }
   8:     public string Autonomia { get; set; }
   9:     public string Matricula { get; set; }
  10:     public string Causa { get; set; }
  11:     public string Poblacion { get; set; }
  12:     public string FechaHora { get; set; }
  13:     public string Nivel { get; set; }
  14:     public string Carretera { get; set; }
  15:     public string Incial { get; set; }
  16:     public string Final { get; set; }
  17:     public string Sentido { get; set; }
  18:     public string Hacia { get; set; }
  19: }

 

En realidad, en el proyecto final aparecen un par de campos más utilizados para notificar informaciones a la pantalla, hablaremos de ello más adelante.

 

REACTIVEando la solución

Decidí utilizar las Rx para, por un lado englobar la operación WebRequest y posterior deserialización del XML en una colección IObservable y por otro lado utilizar las extensiones de Rx para que de forma reactiva consulte la información expuesta en el API de la DGT cada 3 minutos y notifique los cambios a un subscritor que no es más que una colección ObservableCollection enlazada a una aplicación Windows Phone 7.

Vayamos por partes. Un esquema aproximado seria el siguiente:

 

Esquema

En primer lugar creamos la petición a la dirección URL donde se aloja el XML indicandole el método y el contenido.

   1: var webRequest = 
   2:     WebRequest.Create(new Uri("http://dgt.es/incidencias.xml"));
   3:  
   4: webRequest.Method = "POST";
   5: webRequest.ContentType = "text/XML";

 

A continuación realizamos la llamada asíncrona mediante BeginGetResponse y EndGetResponse y es aqui dónde entra en escena las Rx. Utilizando el método Observable.FromAsynPattern<T> podemos convertir la función Begin/End a una función asíncrona de la siguiente forma:

   1: var peticion = Observable.FromAsyncPattern<WebResponse>(
   2:                     webRequest.BeginGetResponse,
   3:                     webRequest.EndGetResponse);

De esta forma obtenemos un delegado del tipo System.Func<System.IObservable<System.Net.WebResponse>> representado por la variable implícita peticion, vamos, una función de salida con un iterador del tipo IObservable. Ahora es el turno de LINQ –de ahí q en ocasiones se conozcan las Rx como LINQ to Events. Lo q vamos a hacer es seleccionar la respuesta Web y retornarla en forma de IEnumerable<Incidencias>. La sentencia LINQ tendria el siguiente aspecto:

   1: IEnumerable<Incidencia> res = from elemento in peticion()
   2:     .Select(respuestaWeb =>
   3:     {
   4:         using (var rs = respuestaWeb.GetResponseStream())
   5:         {
   6:             return rs.Deserialize(respuestaWeb.ContentLength);
   7:         }
   8:     })
   9:     select elemento;

La responsabilidad de transformar el Stream que contiene todo el XML en un IEnumerable<Incidencias> recae sobre el método extensor –de Stream- Deserialize. Básicamente lo que hace es obtener del stream todo el XML y mediante LINQ to XML “deserializarlo” –no es exactamente una deserialización- en una lista generica del tipo  Incidendica tal y como se muestra a continuación:

   1: public static IEnumerable<Incidencia> Deserialize(this Stream objeto, long lenght)
   2: {
   3:     var readBuffer = new byte[lenght];
   4:  
   5:     IEnumerable<Incidencia> nuevasIncidencias = null;
   6:  
   7:     objeto.BeginRead(readBuffer, 0, readBuffer.Length, 
   8:                         readAr =>
   9:                             {
  10:                                 var read = objeto.EndRead(readAr);
  11:                                 var readText = Encoding.UTF8
  12:                                     .GetString(readBuffer, 0, readBuffer.Length);
  13:  
  14:                                 nuevasIncidencias = from elemento in XDocument.Parse(readText)
  15:                                                         .Descendants("raiz").Descendants("incidencia")
  16:                                                     select new Incidencia 
  17:                                                             {
  18:                                                                 Autonomia = elemento.Element(XName.Get("autonomia")).Value,
  19:                                                                 Carretera =
  20:                                                                     elemento.Element(XName.Get("carretera")).Value,
  21:                                                                 Causa = elemento.Element(XName.Get("causa")).Value,
  22:                                                                 FechaHora =
  23:                                                                     elemento.Element(XName.Get("fechahora_ini")).Value,
  24:                                                                 Final = elemento.Element(XName.Get("pk_final")).Value,
  25:                                                                 Hacia = elemento.Element(XName.Get("hacia")).Value,
  26:                                                                 Incial = elemento.Element(XName.Get("pk_inicial")).Value,
  27:                                                                 Matricula =
  28:                                                                     elemento.Element(XName.Get("matricula")).Value,
  29:                                                                 Nivel = elemento.Element(XName.Get("nivel")).Value,
  30:                                                                 Poblacion =
  31:                                                                     elemento.Element(XName.Get("poblacion")).Value,
  32:                                                                 Sentido = elemento.Element(XName.Get("sentido")).Value,
  33:                                                                 Tipo = elemento.Element(XName.Get("tipo")).Value
  34:                                                             };
  35:                             }, null);
  36:  
  37:     return nuevasIncidencias;
  38: }

 

Hasta aquí tenemos los puntos 1 y 2 del esquema resuelto. Todo esto deberíamos encapsularlo en un método de forma que podamos, posteriormente aplicarle algún tipo de comportamiento como por ejemplo exponerlo como Observable. Una forma de hacerlo seria apoyándonos en el método estático Defer de Observable tal y como se muestra a continuación.

   1: internal IObservable<IEnumerable<Incidencia>> PrepararPeticion()
   2: {
   3:     return Observable.Defer(() =>
   4:     {
   5:         var webRequest = 
   6:             WebRequest.Create(new Uri("http://dgt.es/incidencias.xml"));
   7:  
   8:         webRequest.Method = "POST";
   9:         webRequest.ContentType = "text/XML";
  10:  
  11:         var peticion = Observable.FromAsyncPattern<WebResponse>(
  12:             webRequest.BeginGetResponse,
  13:             webRequest.EndGetResponse);
  14:  
  15:         return from elemento in peticion()
  16:             .Select(respuestaWeb =>
  17:             {
  18:                 using (var rs = respuestaWeb.GetResponseStream())
  19:                 {
  20:                     return rs.Deserialize(respuestaWeb.ContentLength);
  21:                 }
  22:             })
  23:             select elemento;
  24:     });
  25: }

 

Para facilitarnos el trabajo vamos a encapsular el método en una clase llamada DgtContexto y dentro del constructor de éste vamos a exponer la colección obtenida del Request y representada por el método PrepararPeticion() de la siguiente forma:

   1: public DgtContexto(int frecuencia)
   2: {
   3:     PrepararPeticion()
   4:         .ObserveOnDispatcher()
   5:         .Subscribe(incidencias =>
   6:                         {
   7:                            //TODO
   8:                         });
   9: }

Dentro de la clase DgtContexto debemos exponer una propiedad ObservableCollection que será la que posteriormente servirá como origen de datos para la aplicación Windows Phone 7.

   1: public class DgtContexto: INotifyPropertyChanged
   2: {
   3:     public ObservableCollection<Incidencia> Incidencias{ get; set; }
   4:     public DateTime Hora { get; set; }
   5:  
   6:     public DgtContexto() {}
   7:  
   8:     internal IObservable<IEnumerable<Incidencia>> PrepararPeticion() {}
   9:  
  10:     public event PropertyChangedEventHandler PropertyChanged;
  11:  
  12: }  

Además creamos una propiedad Hora para conocer la fecha y hora de la información que nos hemos descargado y por útlimo implementamos, la interfaz INotifyPropertyChanged para propagar las modificaciones a la UI de la aplicación.

Ahora toca el turno a cómo relacionamos la lista IEnumerable<Incidencia> descargada del servidor de la DGT con la ObservableCollection de la clase DgtContexto. Esta operación se realizará en la subscripción a la colección retornada por el método PrepararPeticion(). Mediante expresiones lambda podemos obtener la acción onNext devuelta por el IObservable en forma de Action<IEnumerable<Incidencia>>, asignamos la lista iterativa IEnumerable<Incidencia> a la ObservableCollection y notificamos el cambio mediante la llamada a PropertyChanged de la interfaz INotifyPropertyChanged implementada anteriormente. Si, ya lo se, no es posible hacer un cast implícito de una colección IEnumerable<Incidencia> a una ObservableCollection<Incidencia> y para ello vamos a crear un método extensor que lo haga:

   1: public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> enumerable)
   2: {
   3:     var col = new ObservableCollection<T>();
   4:             
   5:     foreach (var cur in enumerable)
   6:     {
   7:         col.Add(cur);
   8:     } 
   9:             
  10:     return col;
  11: } 

Finalmente modificamos la propiedad Hora y notificamos y en definitiva el constructor con la suscripción completa quedaría de la siguiente forma:

   1: public DgtContexto()
   2: {
   3:     PrepararPeticion()
   4:         .ObserveOnDispatcher()
   5:         .Subscribe(incidencias =>
   6:                         {
   7:                             Incidencias = incidencias.ToObservableCollection();
   8:                             NotifyPropertyChanged("Incidencias");
   9:                             Hora = DateTime.Now;
  10:                             NotifyPropertyChanged("Hora");
  11:                         });
  12: }

Tenemos creada la infraestructura pero no estamos sacando provecho –del todo- a las Rx. Si queremos que la aplicación esté monitorizando las modificaciones de la información de la DGT podriamos crear un Timer o un Thread progamado para que lo consulte cada 3 minutos, por ejemplo. Otra opción seria utilizar las extensiones de Rx, más concretamente con los métodos Timer y Select, conjuntamente. El primero temporizará un periodo de tiempo representado por valores long desde 0 hasta TimeSpan.FromMinutes(frecuencia) donde el valor pasado por defecto desde el constructor vacío es 3. Luego proyectamos todos los valores devueltos por PrepararPeticion() desde el XML de la DGT y lo transformamos la collección IObservable más reciente mediante el método Switch(), con lo que el constructor quedará de la siguiente forma:

   1: public DgtContexto(int frecuencia)
   2: {
   3:  
   4:     Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(frecuencia))
   5:         .Select(_ => PrepararPeticion())
   6:         .Switch()
   7:         .ObserveOnDispatcher()
   8:         .Subscribe(incidencias =>
   9:                         {
  10:                             Incidencias = incidencias.ToObservableCollection();
  11:                             NotifyPropertyChanged("Incidencias");
  12:                             Hora = DateTime.Now;
  13:                             NotifyPropertyChanged("Hora");
  14:                             System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + " " +
  15:                                                                 Incidencias.Count.ToString());
  16:                             IsDataLoaded = true;
  17:                         });
  18: }

Podreis encontrar la clase completa a final del post.

 

Enlace con la UI

Para enlazar con la UI, basta con crear un proyecto Pivot o Panorama. En este blog indicaré como crear los datos de ejemplo y como queda el XAML del MainPage.xaml con dos campos enlazados. Básicamente el enlace de datos es identico a como se hace normalmente. Además de lo comentado anteriormente unicamente hay que modificar el archivo App.cs. Por partes, el App.cs quedará así:

   1: public partial class App : Application
   2: {
   3:     private static DgtContexto viewModel = null;
   4:  
   5:     /// <summary>
   6:     /// A static ViewModel used by the views to bind against.
   7:     /// </summary>
   8:     /// <returns>The MainViewModel object.</returns>
   9:     public static DgtContexto ViewModel
  10:     {
  11:         get
  12:         {
  13:             // Delay creation of the view model until necessary
  14:             if (viewModel == null)
  15:                 viewModel = new DgtContexto();
  16:  
  17:             return viewModel;
  18:         }
  19:     }
  20:  
  21: //el resto idéntico....
  22: //NOTE: clase incompleta
  23: }

NOTA: Unicamente he utilizado un par de campos en modo de test para comprobar el comportamiento de las Rx más que de la propia pantalla

El archivo xml de muestra:

   1: <DGT:DgtContexto 
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:DGT="clr-namespace:desarrolloMobile.DGT;assembly=desarrolloMobile.DGT"
   5:     Hora="12/12/2011 12:00:00">
   6:     
   7:     <DGT:DgtContexto.Incidencias>
   8:         <DGT:Incidencia Autonomia="CAT" Carretera="A2"/>
   9:         <DGT:Incidencia Autonomia="MAD" Carretera="A1"/>
  10:     </DGT:DgtContexto.Incidencias>
  11:     
  12: </DGT:DgtContexto>

Y el XAML de MainPage:

   1: <phone:PhoneApplicationPage
   2:     x:Class="desarrolloMobile.DGTViewer.MainPage"
   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   6:     xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   7:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   9:     mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" 
  10:     d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
  11:     FontFamily="{StaticResource PhoneFontFamilyNormal}"
  12:     FontSize="{StaticResource PhoneFontSizeNormal}"
  13:     Foreground="{StaticResource PhoneForegroundBrush}"
  14:     SupportedOrientations="Portrait"  Orientation="Portrait"
  15:     shell:SystemTray.IsVisible="True">
  16:  
  17:     <!--Data context is set to sample data above and LayoutRoot contains the root grid where all other page content is placed-->
  18:     <Grid x:Name="LayoutRoot" Background="Transparent">
  19:         <Grid.RowDefinitions>
  20:             <RowDefinition Height="Auto"/>
  21:             <RowDefinition Height="*"/>
  22:         </Grid.RowDefinitions>
  23:  
  24:         <!--TitlePanel contains the name of the application and page title-->
  25:         <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
  26:             <TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
  27:             <TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
  28:         </StackPanel>
  29:  
  30:         <!--ContentPanel contains ListBox and ListBox ItemTemplate. Place additional content here-->
  31:         <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  32:             <ListBox x:Name="MainListBox" Margin="8,-44,-20,44" ItemsSource="{Binding Incidencias}" SelectionChanged="MainListBox_SelectionChanged">
  33:                 <ListBox.ItemTemplate>
  34:                     <DataTemplate>
  35:                       <StackPanel Margin="0,0,0,17" Width="432">
  36:                           <TextBlock Text="{Binding Autonomia}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
  37:                           <TextBlock Text="{Binding Carretera}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
  38:                       </StackPanel>
  39:                     </DataTemplate>
  40:                 </ListBox.ItemTemplate>
  41:             </ListBox>
  42:             <TextBox Height="80" Margin="0,0,181,8" TextWrapping="Wrap" Text="{Binding Ticks, Mode=OneWay}" VerticalAlignment="Bottom" DataContext="{Binding Hora.TimeOfDay, Mode=OneWay}"/>
  43:         </Grid>
  44:     </Grid>
  45:  
  46: </phone:PhoneApplicationPage>
 
La captura de pantalla de la aplicación funcionando mostrando un par de campos en modo de test:
Main
 
NOTA: Imagen de la futura aplicación q colgaré en el MarketPlace a fecha de hoy y que implementa el código mostrado en este post.

 

Otros retoques

Por lo que he podido observar existen un promedio de más de 250 incidencias activas y ya q pretendo subir la aplicación al Marketplace he tratado q fuera algo más util. Es por ello que además vamos a filtrar las incidencias por carretera, autonomia y población.

La forma más sencilla de hacerlo es utilizando la propia información de la DGT, es decir hacer un SELECT DISTINCT de carreteras, autonomias y población de las 200 y pico incidencias y las muestro en un formulario de forma que el usuario seleccione el valor por el que filtrar. El código es el siguiente para cada una de los filtros:

   1: var context = (from incidencia in App.ViewModel.Incidencias
   2:                 select incidencia.Poblacion)
   3:                 .Distinct()
   4:                 .OrderBy(incidencia => incidencia);

 

Ahora, una vez mostrados por pantalla el usuario selecciona un valor, es decir, una carretera, una autonomia o una población. Sea cual sea lo que al final tenemos que hacer para mostrar los resultados filtrados es pasarle el valor de un predicado al método Where en el método Subscribe() del constructor DgtContexto de de la siguiente forma:

   1: public DgtContexto(int frecuencia, Func<Incidencia, bool> predicado)
   2: {
   3:     Predicado = predicado;
   4:  
   5:     Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(frecuencia))
   6:         .Select(_ => PrepararPeticion())
   7:         .Switch()
   8:         .ObserveOnDispatcher()
   9:         .Subscribe(incidencias =>
  10:                         {
  11:                             IncidenciasTotal = incidencias.ToObservableCollection();
  12:                             Incidencias = (incidencias
  13:                                 .Where(Predicado))
  14:                                 .ToObservableCollection();
  15:                             NotifyPropertyChanged("Incidencias");
  16:                             Hora = DateTime.Now;
  17:                             NotifyPropertyChanged("Hora");
  18:                             IsDataLoaded = true;
  19:                         });
  20: }

El tipo del predicado en los tres casos –esto es, filtro por autonomia, población o carretera- sera del tipo Func<Incidencia,bool>. Lo único q hacemos es crear una propiedad de ese tipo llamado Predicado en la clase DgtContexto que asignaremos cuando el usuario seleccione un valor del filtro:

   1: private void lstBoxAutonomia_SelectionChanged(object sender, SelectionChangedEventArgs e)
   2: {
   3:     ((TextBlock) lstBoxAutonomia.SelectedItem).FontSize -= 6;
   4:     App.ViewModel.Predicado = (incidencia => incidencia.Autonomia == ((TextBlock) this.lstBoxAutonomia.SelectedItem).Text);
   5:     NavigationService.GoBack();
   6: }
   7:  
   8: private void lstBoxPoblacion_SelectionChanged(object sender, SelectionChangedEventArgs e)
   9: {
  10:     ((TextBlock)lstBoxPoblacion.SelectedItem).FontSize -= 6;
  11:     App.ViewModel.Predicado = (incidencia => incidencia.Poblacion == ((TextBlock)this.lstBoxPoblacion.SelectedItem).Text);
  12:     NavigationService.GoBack();
  13: }
  14:  
  15: private void lstBoxCarretera_SelectionChanged(object sender, SelectionChangedEventArgs e)
  16: {
  17:     ((TextBlock)lstBoxCarretera.SelectedItem).FontSize -= 6;
  18:     App.ViewModel.Predicado = (incidencia => incidencia.Carretera == ((TextBlock)this.lstBoxCarretera.SelectedItem).Text);
  19:     NavigationService.GoBack();
  20: }

El resultado desde la interfaz gráfica es:

image

Y el resultado del filtro por COMUNIDAD_CANARIA:

image

Clase DgtContexto

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Collections.ObjectModel;
   4: using System.ComponentModel;
   5: using System.Linq;
   6: using System.Net;
   7: using Microsoft.Phone.Reactive;
   8:  
   9: namespace desarrolloMobile.DGT
  10: {
  11:     public class DgtContexto : INotifyPropertyChanged
  12:     {
  13:         public ObservableCollection<Incidencia> Incidencias { get; set; }
  14:         private ObservableCollection<Incidencia> IncidenciasTotal { get; set; }
  15:  
  16:         public DateTime Hora { get; set; }
  17:  
  18:         public DgtContexto()
  19:             : this(3, incidencia => true)
  20:         { }
  21:  
  22:         private Func<Incidencia, bool> _predicado;
  23:         public Func<Incidencia, bool> Predicado
  24:         {
  25:             get
  26:             {
  27:                 return _predicado;
  28:             }
  29:             set
  30:             {
  31:                 _predicado = value;
  32:                 if (Incidencias != null)
  33:                 {
  34:                     Incidencias = Incidencias
  35:                         .Where(Predicado)
  36:                         .ToObservableCollection();
  37:                     NotifyPropertyChanged("Incidencias");
  38:                     IsDataLoaded = true;
  39:                     NotifyPropertyChanged("Vacio");
  40:                 }
  41:             }
  42:         }
  43:  
  44:         public bool Vacio
  45:         {
  46:             get
  47:             {
  48:                 return !IsDataLoaded;
  49:             }
  50:         }
  51:  
  52:  
  53:         public DgtContexto(int frecuencia, Func<Incidencia, bool> predicado)
  54:         {
  55:             Predicado = predicado;
  56:  
  57:             Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(frecuencia))
  58:                 .Select(_ => PrepararPeticion())
  59:                 .Switch()
  60:                 .ObserveOnDispatcher()
  61:                 .Subscribe(incidencias =>
  62:                                {
  63:                                    IncidenciasTotal = incidencias.ToObservableCollection();
  64:                                    Incidencias = (incidencias
  65:                                        .Where(Predicado))
  66:                                        .ToObservableCollection();
  67:                                    NotifyPropertyChanged("Incidencias");
  68:                                    Hora = DateTime.Now;
  69:                                    NotifyPropertyChanged("Hora");
  70:                                    System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + " " +
  71:                                                                       Incidencias.Count.ToString());
  72:                                    IsDataLoaded = true;
  73:                                    NotifyPropertyChanged("Vacio");
  74:                                });
  75:         }
  76:  
  77:         public void Actualizar()
  78:         {
  79:             Incidencias = IncidenciasTotal;
  80:             NotifyPropertyChanged("Incidencias");
  81:             IsDataLoaded = false;
  82:             NotifyPropertyChanged("Vacio");
  83:         }
  84:  
  85:         internal IObservable<IEnumerable<Incidencia>> PrepararPeticion()
  86:         {
  87:             return Observable.Defer(() =>
  88:             {
  89:                 var webRequest =
  90:                     WebRequest.Create(new Uri("http://dgt.es/incidencias.xml"));
  91:  
  92:                 webRequest.Method = "POST";
  93:                 webRequest.ContentType = "text/XML";
  94:  
  95:                 var peticion = Observable.FromAsyncPattern<WebResponse>(
  96:                     webRequest.BeginGetResponse,
  97:                     webRequest.EndGetResponse);
  98:  
  99:                 return from elemento in peticion()
 100:                     .Select(respuestaWeb =>
 101:                     {
 102:                         using (var rs = respuestaWeb.GetResponseStream())
 103:                         {
 104:                             return rs.Deserialize(respuestaWeb.ContentLength);
 105:                         }
 106:                     })
 107:                        select elemento;
 108:             });
 109:         }
 110:  
 111:         public bool IsDataLoaded
 112:         {
 113:             get;
 114:             private set;
 115:         }
 116:  
 117:  
 118:         public event PropertyChangedEventHandler PropertyChanged;
 119:         private void NotifyPropertyChanged(String propertyName)
 120:         {
 121:             var handler = PropertyChanged;
 122:             if (null != handler)
 123:             {
 124:                 handler(this, new PropertyChangedEventArgs(propertyName));
 125:             }
 126:         }
 127:  
 128:         public void LoadData()
 129:         {
 130:             Incidencias = new ObservableCollection<Incidencia>
 131:                               {
 132:                                   new Incidencia
 133:                                       {
 134:                                           Autonomia = "---",
 135:                                           Carretera = "---"
 136:                                       }
 137:                               };
 138:         }
 139:     }
 140: }