Compartir a través de


Este artículo proviene de un motor de traducción automática.

Tocar y listo

Una interfaz táctil para un mapa de orientación

Charles Petzold

Descargar el ejemplo de código

Charles PetzoldSiempre que estoy un poco perdido en un centro comercial o un museo, buscar un mapa, pero al mismo tiempo a menudo sienten cierta ansiedad sobre lo encontrarás. ¿Estoy bastante seguro el mapa contará con una flecha con la etiqueta, "Estás aquí", pero cómo será el mapa orientarse? ¿Si el mapa se monta verticalmente, el lado derecho del mapa corresponden realmente a mi derecha y la parte inferior corresponden a lo que está detrás de mí? ¿O el mapa tiene que ser mentalmente retirado de su soporte y retorcidos en espacio para alinear con el trazado actual?

Mapas que se montan en un ángulo o en paralelo con el suelo son mucho mejores, siempre, es decir, están orientados correctamente para comenzar con. Independientemente de la agilidad mental con relaciones espaciales, los mapas son fáciles de leer cuando está paralelos a la tierra, o puede girarse hacia adelante para alinear con la tierra. Antes de la edad de GPS, era común ver a personas luchando con mapas de carreteras de papel girándolas violentamente a la derecha, izquierda y hacia abajo en busca de la orientación correcta.

Mapas que se implementan en software en teléfonos y otros dispositivos móviles tienen el potencial para orientarse basada en una lectura de la brújula. Esto es el ímpetu detrás de mi búsqueda para mostrar un mapa en un dispositivo Windows Phone que gira en relación con el teléfono. Tal un mapa debe ser capaz de alinearse con el paisaje circundante y ser más útil a los perdidos entre nosotros.

Orientar el mapa

Mi objetivo inicial fue un poco más ambicioso que un mapa de rotación. El programa que prevé realmente haría flotar un mapa en el espacio 3D siempre sería paralela a la superficie de la tierra, así como estar orientado con la brújula.

Experimentación un poco me convenció de que este enfoque era algo más extravagante que necesitaba. Aunque un plano inclinado con perspectiva es fino para GPS muestra en automóviles, creo que es porque el mapa es siempre inclinada hacia el mismo grado, y algo imita lo que estás viendo hacia fuera el parabrisas. Con un mapa en un dispositivo móvil, la inclinación que tiene el efecto de comprimir las imágenes de mapa sin proporcionar cualquier información adicional. Una simple rotación bidimensional parecía ser suficiente.

En mi columna de noviembre, hablé de cómo utilizar los servicios de Bing Maps jabón para descargar y montar azulejos cuadrados de 256 píxeles en un mapa ("Montaje Bing mapas en Windows Phone," msdn.microsoft.com/magazine/jj721603). Los azulejos disponibles desde este servicio Web se organizan en niveles de zoom, donde cada nivel tiene el doble de resolución del nivel anterior, lo que significa que cada baldosa cubre la misma superficie como cuatro fichas en el siguiente nivel.

El programa en botones de la barra del mes pasado columna aplicación contenidos etiquetados con más y menos señales para aumentar y disminuir el nivel de zoom en saltos discretos. Ese tipo de interfaz es adecuado para un mapa de un sitio Web, pero para un teléfono, la única descripción que parece apropiada es "totalmente coja".

Esto significa que ahora es el momento de implementar una interfaz táctil real que permite el mapa para ampliar continuamente.

Una vez que comencé a agregar esa interfaz táctil — un solo dedo para pan, dos dedos para acercar y alejar con — adquirí un respeto profundo y duradero de la aplicación de mapas en Windows Phone y para el control de mapa de Silverlight. Obviamente, estos mapas implementan una interfaz táctil mucho más sofisticada que lo que he sido capaz de administrar.

Por ejemplo, no creo que nunca he visto un agujero negro abren en la aplicación de mapas, porque falta un azulejo. Es mi experiencia que la pantalla está siempre totalmente cubierta — aunque, obviamente, a veces con un azulejo que se han extendido más allá del punto de reconocimiento. Azulejos se reemplazan con azulejos de mejor resolución con una animación de desvanecimiento. Inercia es implementado de una manera muy natural, y la interfaz de usuario nunca se jumpy mientras se descargan los azulejos.

Mi programa de OrientingMap (que se puede descargar) viene en ninguna parte cerca de la real aplicación de mapas. La panorámica y la expansión es a menudo jumpy, no hay inercia y áreas en blanco aparecen con frecuencia si los azulejos no descargados con la suficiente rapidez.

A pesar de estas deficiencias, mi programa tiene éxito en el mantenimiento de una orientación del mapa con el mundo que retrata.

La cuestión básica

Los servicios de Bing Maps jabón dan un programa acceso a 256 píxeles cuadrados mapas desde el cual pueden construir mapas compuestos más grandes. Para carretera y vistas aéreas, Bing Maps hace disponibles 21 niveles de zoom, donde el nivel 1 cubre la tierra con cuatro fichas, nivel 2 con 16 azulejos, nivel 3 con 64, etcétera. Cada nivel proporciona el doble de la resolución horizontal y doble la resolución vertical del siguiente nivel inferior.

Azulejos tienen una relación de padres e hijos: Con excepción de azulejos en nivel 21, cada azulejo tiene cuatro hijos en el nivel más alto siguiente que juntos cubren la misma área que sí pero con el doble de resolución.

Cuando un programa se pega a niveles de zoom integral — igual que el programa presentado en la columna del mes pasado, los azulejos individuales se pueden mostrar en sus tamaños de pixel real. Programa del mes pasado muestra siempre 25 azulejos en una matriz de 5 × 5, para un tamaño total de 1.280 píxeles cuadrados. El programa siempre posiciones de esta matriz de azulejos para que el centro de la pantalla corresponde a la ubicación del teléfono en el mapa, que es un lugar en alguna parte en el mosaico central. No las matemáticas y usted encontrará que incluso si se coloca en una esquina de la baldosa de centro en el centro de la pantalla, este tamaño de 1.280 píxeles cuadrados es adecuada para el tamaño de pantalla de 480 × 800 del teléfono, sin importar cómo se gira.

Porque programa el mes pasado soporta niveles de zoom sólo discretos y siempre centros los azulejos basados en la ubicación del teléfono, implementa una lógica muy simplista sustituyendo completamente estos 25 azulejos cuando se produce un cambio. Afortunadamente, la caché de descarga hace este proceso bastante rápido si los azulejos están siendo reemplazados con azulejos previamente descargados.

Con una interfaz táctil, este enfoque simple ya no es aceptable.

La parte difícil es sin duda la escala: Por ejemplo, supongamos que el programa comienza mostrando mapas de nivel 12 en sus tamaños de píxeles. Ahora el usuario pone dos dedos en la pantalla y mueve los dedos para ampliar la pantalla. El programa debe responder al escalar los azulejos más allá de sus tamaños 256 píxeles. Puede hacerlo con ScaleTransform sobre las baldosas de sí mismos, o con ScaleTransform aplicado a un lienzo en el que se montan los azulejos.

Pero no quieres escalar estos azulejos indefinidamente! En algún momento desea reemplazar cada azulejo con cuatro fichas del niño del nivel siguiente superior y medio el factor de escala. Este proceso de reemplazo sería bastante trivial si los azulejos del niño estaban disponibles al instante, pero, por supuesto, no son. Se debe descargar, lo que significa que niño azulejos deben colocarse visualmente sobre el padre, y sólo cuando todas las fichas de cuatro niños han sido descargadas puede quitar el padre de la lona.

El proceso contrario debe producirse en un zoom out. Como el usuario aprieta dos dedos juntos, toda la gama de azulejos puede ser reducida, pero en algún momento debe reemplazarse cada grupo de cuatro fichas con un azulejo de padres visualmente por debajo de las cuatro fichas. Sólo cuando se ha descargado ese mosaico de padres pueden retirar los cuatro niños.

Clases adicionales

Como ya os comentamos en la columna del mes pasado, Bing Maps utiliza un sistema de numeración llamado un "quadkey" para identificar mapas. Un quadkey es un número de base-4: El número de dígitos en el quadkey indica el nivel de zoom, y los dígitos codifican un entrelazado de la longitud y la latitud.

Para ayudar al programa de OrientingMap en el trabajo con quadkeys, el proyecto incluye una clase de QuadKey que define las propiedades para obtener quadkeys de padre e hijo.

El proyecto OrientingMap tiene también una nueva clase de MapTile que se deriva de UserControl. El archivo XAML para este control se muestra en la figura 1. Tiene un elemento de imagen con su propiedad origen a un objeto BitmapImage para mostrar el azulejo de mapa de bits, así como también ScaleTransform para escalar el azulejo todo hacia arriba o hacia abajo. (En la práctica, baldosas individuales sólo escalan por potencias integrales positivos y negativos de 2.) Para la depuración, pongo un TextBlock en el archivo XAML que muestra la quadkey, y he dejado en: Simplemente cambiar el atributo de visibilidad a Visible para verlo.

Figura 1 el archivo MapTile.xaml de OrientingMap

<UserControl x:Class="OrientingMap.MapTile" ...>
  <Grid>
    <Image Stretch="None">
      <Image.Source>
        <BitmapImage x:Name="bitmapImage"
                     ImageOpened="OnBitmapImageOpened" />
      </Image.Source>       
    </Image>
    <!-- Display quadkey for debugging purposes -->
    <TextBlock Name="txtblk"
               Visibility="Collapsed"
               Foreground="Red" />
  </Grid>
  <UserControl.RenderTransform>
    <ScaleTransform x:Name="scale" />
  </UserControl.RenderTransform>
</UserControl></span>

El archivo de código subyacente para MapTile define varias propiedades útiles: La propiedad QuadKey permite a la clase MapTile para obtener el identificador URI para tener acceso al mapa; una propiedad de escala permite código externo establecer el factor de escala; una propiedad IsImageOpened indica cuando se ha descargado el mapa de bits; y una propiedad ImageOpened proporciona acceso externo al evento ImageOpened del objeto BitmapImage. Estas dos últimas propiedades ayudan al programa a determinar cuando una imagen se ha cargado por lo que el programa puede quitar los azulejos que reemplaza la imagen.

Mientras se desarrolla este programa, persiguió inicialmente un esquema donde cada objeto MapTile utilizaría su propiedad de escala para determinar cuando debe ser sustituido con un grupo de cuatro objetos de MapTile de niño, o un padre MapTile. El MapTile manejaría la creación y posicionamiento de estos nuevos objetos, configuración de controladores para los eventos ImageOpened y también sería responsable de sí mismo quitar del lienzo.

Pero no pude conseguir este esquema para trabajar muy bien. Considerar un conjunto de 25 mapas que el usuario se expande a través de la interfaz táctil. Estos 25 azulejos se reemplazan con 100 piezas, y luego se reemplazan las 100 piezas con 400 azulejos. ¿Tiene esto sentido? No, no, porque la escala efectivamente ha muchos de estos potenciales nuevos azulejos demasiado lejos la pantalla sea visible. La mayoría de ellos no debería ser creada o descargada a todos!

En cambio, cambió esta lógica a la página principal. Esta clase mantiene un campo de currentMapTiles de tipo diccionario < QuadKey, MapTile >. Aquí almacenan todos los objetos de MapTile actualmente en la pantalla, incluso si están todavía en el proceso de descarga. Un método denominado RefreshDisplay utiliza la ubicación actual del mapa y un factor de escala para montar un campo de validQuadKeys de tipo lista <QuadKey>. Si existe un objeto de QuadKey en validQuadKeys pero no en currentMapTiles, un MapTile nuevo y se agrega a la lona y el currentMapTiles.

RefreshDisplay no elimina objetos de MapTile que ya no son necesarios, ya sea porque ha sido criticadas fuera de la pantalla o reemplazado con padres o hijos. Es la responsabilidad de un segundo método importante llamada limpieza. Este método compara la colección validQuadKeys con currentMapTiles. Si encuentra un artículo en currentMapTiles que no está en validQuadKeys, sólo quita esa MapTile si validQuadKeys no tiene hijos, o si los niños en validQuadKeys se todos han descargado o si validQuadKeys contiene uno de los padres de MapTile y que los padres se ha descargado.

Hacer más eficientes los métodos RefreshDisplay y limpieza — y menos invocar con frecuencia — es una forma de mejorar el rendimiento de OrientingMap.

Lienzos anidados

La interfaz de usuario para el programa de OrientingMap requiere dos tipos de transformaciones de gráficos: traducción de single-dedo panorámica y escala para dos dedos pizca operaciones. Además, orientar el mapa con la dirección del Norte requiere una transformación de rotación. Para implementar estos con eficiente transforma de Silverlight, el archivo MainPage.xaml contiene tres niveles de paneles de tela, como se muestra en la figura 2.

Figura 2 gran parte de la MainPage.xaml archivo para OrientingMap

<phone:PhoneApplicationPage x:Class="OrientingMap.MainPage" ... >
  <Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12">
      <TextBlock Name="errorTextBlock"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Top"
                 TextWrapping="Wrap" />
      <!-- Rotating Canvas with origin in center of screen -->
      <Canvas HorizontalAlignment="Center"
              VerticalAlignment="Center">
        <!-- Translating Canvas for panning -->
        <Canvas>
          <!-- Scaled Canvas for images -->
          <Canvas Name="imageCanvas"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center">
              <Canvas.RenderTransform>
                <ScaleTransform x:Name="imageCanvasScale" />
              </Canvas.RenderTransform>
          </Canvas>
          <!-- Circle to show location -->
          <Ellipse Name="locationDisplay"
                   Width="24"
                   Height="24"
                   Stroke="Red"
                   StrokeThickness="3"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Visibility="Collapsed">
            <Ellipse.RenderTransform>
              <TranslateTransform x:Name="locationTranslate" />
            </Ellipse.RenderTransform>
          </Ellipse>
          <Canvas.RenderTransform>
            <TranslateTransform x:Name="imageCanvasTranslate" />
          </Canvas.RenderTransform>
        </Canvas>
        <Canvas.RenderTransform>
          <RotateTransform x:Name="imageCanvasRotate" />
        </Canvas.RenderTransform>
      </Canvas>
      <!-- Arrow to show north -->
      <Border HorizontalAlignment="Left"
              VerticalAlignment="Top"
              Background="Black"
              Width="36"
              Height="36"
              CornerRadius="18">
        <Path Stroke="White"
              StrokeThickness="3"
              Data="M 18 4 L 18 24 M 12 12 L 18 4 24 12">
          <Path.RenderTransform>
            <RotateTransform x:Name="northArrowRotate"
                             CenterX="18"
                             CenterY="18" />
          </Path.RenderTransform>
        </Path>
      </Border>
      <!-- "powered by bing" display -->
      <Border Background="Black"
              HorizontalAlignment="Center"
              VerticalAlignment="Bottom"
              CornerRadius="12"
              Padding="3">
        <StackPanel Name="poweredByDisplay"
                    Orientation="Horizontal"
                    Visibility="Collapsed">
          <TextBlock Text=" powered by "
                     Foreground="White"
                     VerticalAlignment="Center" />
          <Image Stretch="None">
            <Image.Source>
              <BitmapImage x:Name="poweredByBitmap" />
            </Image.Source>
          </Image>
        </StackPanel>
      </Border>
    </Grid>
  </Grid>
  ...
</phone:PhoneApplicationPage>

La red con el nombre ContentPanel contiene el lienzo ultraperiférica, así como tres elementos que se muestran siempre en ubicaciones fijas en la pantalla: un TextBlock para reportar errores de inicialización, una frontera que contiene una flecha para mostrar la dirección del Norte y la otra frontera para mostrar el logotipo de Bing.

El lienzo más externo tiene su HorizontalAlignment y Vertical­configurar propiedades de alineación al centro, lo que disminuye el lienzo a un tamaño cero situado en el centro de la cuadrícula. El (0, 0) coordenadas de este lienzo es por lo tanto, el centro de la pantalla. Este centro es conveniente para la colocación de azulejos y también permite escalado y rotación que se produzca alrededor del origen.

El lienzo más externo es la que se gira en función de la dirección del Norte. Dentro de este lienzo ultraperiférica es un segundo lienzo que tiene un TranslateTransform. Esto es para la panorámica. Siempre que un solo dedo barre la pantalla, se puede mover todo el mapa simplemente estableciendo las propiedades X e Y de este TranslateTransform.

Dentro de este segundo lienzo es una elipse que se utiliza para indicar la ubicación actual del teléfono con respecto al centro del mapa. Cuando el usuario gira el mapa, esta elipse se mueve así. Pero si el GPS del teléfono muestra un cambio en la ubicación, un Traductor independiente­transformación de la elipse, mueve en relación con el mapa.

El lienzo más interno se denomina imageCanvas, y aquí es que los mapas son realmente montados. El ScaleTransform aplicado a este lienzo permite que el programa aumentar o disminuir a este completo conjunto de mapas basados en el usuario zoom o con una pizca de manipulación.

Para acomodar el zoom continuo, el programa mantiene un campo zoomFactor de tipo doble. Este zoomFactor tiene el mismo rango como los niveles de azulejo: de 1 a 21 — lo que significa que es realmente el logaritmo en base 2 del factor de escala de mapa total. Cuando el zoomFactor se incrementa en 1, la escala del mapa se duplica.

La primera vez que se ejecuta el programa, zoomFactor se inicializa a 12, pero la primera vez que el usuario toca la pantalla con dos dedos, se convierte en un valor no integral y muy probablemente sigue siendo un valor no integral a partir de entonces. El programa guarda zoomFactor como una configuración de usuario y vuelve a cargar la próxima vez que se ejecuta el programa. Un baseLevel integral inicial se calcula con una simple truncamiento:

baseLevel = (int)zoomFactor;

Este baseLevel es siempre un número entero en el rango entre 1 y 21, y por lo tanto es conveniente directamente para la recuperación de los azulejos. De estos dos números, el sistema calcula un factor de escala no logarítmico de tipo doble:

canvasScale = Math.Pow(2, zoomFactor - baseLevel);

Este es el factor de escala aplicado a la lona más íntima. Por ejemplo, si el zoomFactor es 10.5, entonces el baseLevel utilizada para recuperar azulejos es 10, y canvasScale es 1,414.

Si el zoomFactor inicial es de 10,9, podría tener más sentido establecer baseLevel en 11 y canvasZoom en 0.933. El programa no hace eso, pero obviamente es un refinamiento posible.

Entrada táctil de uno y dos dedos

Para la entrada táctil, me sentí más cómodo usando el Touch panel de XNA que los eventos de manipulación de Silverlight. El constructor de página principal permite a cuatro tipos de gestos XNA: FreeDrag (panorámica), DragComplete, pizca y PinchComplete. El Touch panel está marcado para la entrada en un controlador para el evento CompositionTarget.Rendering, como se muestra en la figura 3. Debido a su complejidad, sólo un poco de la pizca procesamiento se muestra aquí.

Figura 3 toque de procesamiento en OrientingMap

void OnCompositionTargetRendering(object sender, EventArgs args)
{
  while (TouchPanel.IsGestureAvailable)
  {
    GestureSample gesture = TouchPanel.ReadGesture();
    switch (gesture.GestureType)
    {
      case GestureType.FreeDrag:
        // Adjust delta for rotation of canvas
        Vector2 delta = TransformGestureToMap(gesture.Delta);
        // Translate the canvas
        imageCanvasTranslate.X += delta.X;
        imageCanvasTranslate.Y += delta.Y;
        // Adjust the center longitude and latitude
        centerRelativeLongitude -= delta.X / (1 << baseLevel + 8) / canvasScale;
        centerRelativeLatitude -= delta.Y / (1 << baseLevel + 8) / canvasScale;
        // Accumulate the panning distance
        accumulatedDeltaX += delta.X;
        accumulatedDeltaY += delta.Y;
        // Check if that's sufficient to warrant a screen refresh
        if (Math.Abs(accumulatedDeltaX) > 256 ||
            Math.Abs(accumulatedDeltaY) > 256)
        {
          RefreshDisplay();
          accumulatedDeltaX = 0;
          accumulatedDeltaY = 0;
        }
        break;
      case GestureType.DragComplete:
        Cleanup();
        break;
      case GestureType.Pinch:
        // Get the old and new finger positions relative to canvas origin
        Vector2 newPoint1 = gesture.Position - canvasOrigin;
        Vector2 oldPoint1 = newPoint1 - gesture.Delta;
        Vector2 newPoint2 = gesture.Position2 - canvasOrigin;
        Vector2 oldPoint2 = newPoint2 - gesture.Delta2;
        // Rotate in accordance with the current rotation angle
        oldPoint1 = TransformGestureToMap(oldPoint1);
        newPoint1 = TransformGestureToMap(newPoint1);
        oldPoint2 = TransformGestureToMap(oldPoint2);
        newPoint2 = TransformGestureToMap(newPoint2);
        ...</span>
          <span class="sentence">RefreshDisplay();
        break;
      case GestureType.PinchComplete:
        Cleanup();
        break;
    }
  }
}

La entrada de FreeDrag es acompañada por los valores de posición y Delta (ambos de tipo Vector2) que indica la posición actual del dedo, y cómo se ha movido el dedo desde el último evento Touch panel. La entrada de la pizca suplementos con valores posición2 y Delta2 para el segundo dedo.

Sin embargo, tenga en cuenta que estos valores de Vector2 son las coordenadas en la pantalla! Porque el mapa se gira con respecto a la pantalla, y se espera que el mapa a la cacerola en la misma dirección cuando se mueve un dedo — estos valores deben girarse en base a la actual rotación de mapa, que se produce en un pequeño método denominado TransformGestureToMap.

Para FreeDrag de procesamiento, el valor de delta se aplica a la TranslateTransform en el archivo XAML, así como dos campos de coma flotante con el nombre centerRelativeLongitude y centerRelativeLatitude. Estos valores de 0 a 1 e indican la longitud y latitud correspondientes al centro de la pantalla.

En algún momento, el usuario podría pan el mapa a un grado suficiente que nuevos azulejos necesitan ser cargados. Para evitar la comprobación de esa posibilidad con cada evento de toque, el programa mantiene dos campos con el nombre accumulatedDeltaX y accumulatedDeltaY y sólo llamadas RefreshDisplay cuando cualquier valor supera los 256, que es el tamaño en píxeles de los mapas.

Porque RefreshDisplay tiene un gran trabajo que hacer: determinar qué azulejos debe visible en la pantalla basado en centerRelativeLongitude y centerRelativeLatitude y la canvasScale actual y la creación de nuevos azulejos si es necesario — es mejor que no ser llamado por cada cambio de entrada táctil. Una mejora definitiva al programa limitaría RefreshDisplay llamadas durante la entrada de la pizca.

Durante el proceso de contacto, sólo se llama al método de limpieza cuando el dedo o dedos han dejado la pantalla. Limpieza también se llama cuando ha completado la descarga de un mapa.

Los criterios para el cambio de baseLevel — y así iniciar un reemplazo de un mapa de padres de niños, o por un padre — es muy relajado. El baseLevel sólo se incrementa cuando canvasScale es mayor que 2 y disminuye cuando canvasScale cae a menos de 0,5. Otra mejora obvia es mejores puntos de transición.

El programa cuenta ahora con sólo dos botones de la barra de aplicación: La primera alterna entre la carretera y vista aérea y los segundos puestos el mapa para que la ubicación actual es en el centro.

Ahora solo falta averiguar cómo hacer que el programa me ayudan a navegar por museos y centros comerciales.

Charles Petzold es colaborador desde hace mucho tiempo de MSDN Magazine y autor de "Programación Windows, 6ª edición" (o ' Reilly Media, 2012), un libro sobre cómo escribir aplicaciones para Windows 8. Su sitio Web es charlespetzold.com.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Thomas Petchel