Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Touch-and-Go

Assemblaggio di riquadri Bing Map in Windows Phone

Charles Petzold

Scarica il codice di esempio

Charles PetzoldIl sensore di movimento Windows Phone consolida informazioni del telefono bussola e accelerometro per creare una matrice di rotazione che descrive l'orientamento del telefono nello spazio 3D.

Recentemente ho cominciato a riflettere come l'orientamento del telefono potrebbe essere utilizzato in combinazione con Bing Maps. Ho anticipato un mashup di sveltina, ma il lavoro si è rivelato piuttosto più complessa.

Se hai sperimentato con il programma di mappe standard sul tuo Windows Phone, si sa che il display sia allineato solitamente così che il nord è verso la parte superiore del telefono. (L'unica eccezione è quando si utilizza la mappa per le indicazioni per una posizione, nel qual caso la mappa è orientata per indicare la direzione che tu stia viaggiando). Per il nord è la convenzione per le mappe, ma in alcuni casi che è possibile mappa del telefono di ruotare rispetto al telefono, così che il nord sulla mappa in realtà punta nord.

Sembra così semplice, giusto?

Limiti di controllo mappa

Nella mia ricerca per implementare una mappa rotante sul telefono, ho iniziato con il controllo Silverlight di Bing Maps per Windows Phone, al centro del quale è un controllo denominato semplicemente mappa nello spazio dei nomi di Maps.

Per iniziare con il controllo della mappa (o qualcosa che coinvolge l'accesso a Bing Maps a livello di codice) è necessario registrarsi al Bing Maps Account Center at bingmapsportal.com. È un dritto­avanti il processo per ottenere una chiave di credenziali che dà l'accesso del programma a Bing Maps.

Nel progetto Windows Phone che utilizza il controllo della mappa, avrete bisogno di un riferimento all'assembly Maps e probabilmente vorrete una dichiarazione di spazio dei nomi XML nel file XAML (in una sola riga):

xmlns:maps="clr-namespace:Microsoft.Phone.Controls.Maps;
  assembly=Microsoft.Phone.Controls.Maps"

Un'istanza di una mappa di base poi è banale:

    <maps:Map CredentialsProvider="credentials-key" />

Inserire la chiave di credenziali reali che hai ottenuto da Bing Maps Account Center.

Sì, ottenere una mappa sullo schermo è facile, ma quando ho cercato il modo di ruotare, mi è venuto secco. Di sicuro, la mappa eredita una proprietà intestazione dalla classe MapBase, ma a quanto pare questa proprietà è rilevante solo per "occhio d'uccello" mappe e il controllo della mappa supporta solo la strada standard e vedute aeree.

Naturalmente, è abbastanza banale per ruotare il controllo mappa impostando un oggetto RotateTransform alla proprietà RenderTransform, ma non ero felice a tutti con tale soluzione. Per prima cosa, tende a oscurare il copyright nella parte inferiore della mappa e fare che sembrava essere in violazione delle condizioni che d'accordo a quando ottenere una chiave di credenziali.

Ho deciso di abbandonare il controllo di mappe e invece cercare la mia fortuna su un livello molto più basso di accedere ai servizi SOAP di Bing Maps. Questo insieme di servizi Web consente a un programma ottenere le tessere reali bitmap da cui sono costruite le mappe più grandi.

L'accesso al servizio SOAP

In un servizio Web costruito intorno SOAP Simple Object Access Protocol (), le informazioni vengono trasferite tra il programma e il server utilizzando documenti XML. Molto spesso questi documenti riguardano piuttosto complesse strutture di dati, così invece di manipolazione XML direttamente, un approccio molto più facile è che Visual Studio per creare una classe proxy. Questo permette al programma di accedere al servizio Web con chiamate e classi c# normale (anche se asincrono).

L'interfaccia di servizi Bing Maps SOAP è documentato a bit.ly/S3R4lGe si compone di quattro servizi distinti:

  • Servizio Geocode: Corrispondere indirizzi con latitudine e longitudine.
  • Servizio di immagini: Ottenere mappe e piastrelle.
  • Servizio di route: Ottenere indicazioni stradali.
  • Servizio di ricerca: Individuare le persone e le imprese.

Ero solo interessato al servizio di immagini.

Il codice scaricabile di questo articolo è un unico progetto denominato RotatingMapTiles. Ho aggiunto un proxy per il servizio di immagini a questo progetto, scegliendo Aggiungi riferimento al servizio dal menu progetto. Nel campo indirizzo della finestra di dialogo Aggiungi riferimento al servizio, ho copiato l'URL elencato nella sezione indirizzi di Bing mappe SOAP servizi di documentazione e premuto Go. Quando il servizio è stato situato, ho dato un nome di ImageryService nel campo Namespace.

Il codice c# generato per questo servizio ha uno spazio dei nomi che è lo spazio dei nomi di progetto normale, seguita dallo spazio dei nomi specificato durante la creazione del riferimento al servizio, quindi il file MainPage.xaml.cs nel programma RotatingMapTile contiene i seguenti direttiva using:

using RotatingMapTiles.ImageryService;

Il servizio di immagini supporta due tipi di richieste che coinvolgono la funzione chiamate GetMapUriAsync e GetImageryMetadataAsync. La prima chiamata permette di ottenere mappe statiche di un particolare livello di zoom, le dimensioni e posizione, mentre il secondo funziona a un livello inferiore. Tra i metadati che questa seconda chiamata restituisce è un modello URI che consente di accedere alle piastrelle bitmap effettivi utilizzati per assemblare tutte le Bing maps.

Figura 1 viene illustrato il gestore caricato per la classe MainPage nel RotatingMapTiles progetto facendo due chiamate al servizio Web di immagini per ottenere i metadati per gli stili MapStyle.Road e MapStyle.Aerial. (MapStyle.Birdseye è anche disponibile, ma è piuttosto più complesso da utilizzare).

Figura 1 codice per accedere al servizio Web di Bing Mappe immagini

void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
  // Initialize the Bing Maps imagery service
  ImageryServiceClient imageryServiceClient =
    new ImageryServiceClient("BasicHttpBinding_IImageryService");
    imageryServiceClient.GetImageryMetadataCompleted +=
      GetImageryMetadataCompleted;
  // Make a request for the road metadata
  ImageryMetadataRequest request = new ImageryMetadataRequest
  {
    Credentials = new Credentials
    {
      ApplicationId = "credentials-key"
    },
    Style = MapStyle.Road
  };
  imageryServiceClient.GetImageryMetadataAsync(request, "road");
  // Make a request for the aerial metadata
  request.Style = MapStyle.Aerial;
  imageryServiceClient.GetImageryMetadataAsync(request, "aerial");
}

Devi proprio Bing Maps credenziali chiave per sostituire il segnaposto.

Figura 2 viene illustrato il gestore per l'evento Completed della chiamata asincrona. Le informazioni includono un URI per una bitmap con il logo di Bing, così è facile al credito Bing Maps sullo schermo del programma.

Figura 2 il gestore completato per il servizio Web di Bing Maps

void GetImageryMetadataCompleted(object sender,
   GetImageryMetadataCompletedEventArgs args)
{
  if (!args.Cancelled && args.Error == null)
  {
    // Get the "powered by" bitmap
    poweredByBitmap.UriSource = args.Result.BrandLogoUri;
    poweredByDisplay.Visibility = Visibility.Visible;
    // Get the range of map levels available
    ImageryMetadataResult result = args.Result.Results[0];
    minimumLevel = result.ZoomRange.From;
    maximumLevel = result.ZoomRange.To;
    // Get the URI and make some substitutions
    string uri = result.ImageUri;
    uri = uri.Replace("{subdomain}", result.ImageUriSubdomains[0]);
    uri = uri.Replace("&token={token}", "");
    uri = uri.Replace("{culture}", "en-US");
    if (args.UserState as string == "road")
      roadUriTemplate = uri;
    else
      aerialUriTemplate = uri;
    if (roadUriTemplate != null && aerialUriTemplate != null)
      RefreshDisplay();
  }
  else
  {
    errorTextBlock.Text =
      "Cannot access Bing Maps: " + args.Error.Message;
  }
}

Altri URI è quella che userai per accedere le piastrelle mappa. Ci sono separati gli URI per la strada e le vedute aeree e URI contiene un segnaposto per un numero che identifica proprio la tegola che si desidera.

Mappe e piastrelle

Le piastrelle che formano la base di Bing Maps sono immagini bitmap che sono sempre Piazza di 256 pixel. Ogni piastrella è associato con un particolare livello di zoom, la latitudine e la longitudine e contiene un'immagine di un'area quadrata sulla superficie della terra bidimensionale utilizzando la proiezione di Mercatore comune.

La vista di zoom-out più estrema è noto come livello 1, e solo quattro piastrelle sono tenuti a coprire il mondo intero — o almeno la parte del mondo con latitudini tra positivi e negativi 85,05 ° — come mostrato Figura 3.

The Four Level 1 Tiles
Figura 3 le quattro piastrelle di livello 1

Mi spiego i numeri sulle piastrelle in un attimo. Perché le piastrelle sono 256 square pixel, all'equatore ogni pixel è equivalenti a circa 49 km.

Livello 2 è più granulare e ora 16 piastrelle coprono la terra, come mostrato Figura 4.

The 16 Level 2 Tiles
Figura 4 16 piastrelle di livello 2

Le piastrelle in Figura 4 sono anche 256 pixel quadrati, quindi all'equatore ogni pixel è circa 24 km. Si noti che ogni piastrella nel livello 1 copre la stessa area di 4 tessere nel livello 2.

Continua questo schema: Livello 3 ha 64 mattonelle, livello 4 ha 256 piastrelle e fino fino e fino a livello 21, che copre la terra con un totale di oltre 4 trilioni piastrelle — 2 milioni in orizzontale e 2 milioni in verticale per una risoluzione (all'equatore) di 3 centimetri per pixel.

Numerazione delle piastrelle

Perché almeno alcuni di questi miliardi di piastrelle devono essere specificati singolarmente da un programma che si desidera utilizzarli, devono essere identificati in modo chiaro e coerenza. Ci sono tre dimensioni coinvolte — livello di zoom, la latitudine e la longitudine e un esame pratico pure: Per ridurre al minimo gli accessi su disco sul server, piastrelle associati con la stessa area devono essere conservati vicino a vicenda, che implica un sistema di numerazione unico che comprende tutte le tre dimensioni in modo molto intelligente.

L'intelligente sistema di numerazione per queste mappe è chiamato un "quadkey". L'articolo di MSDN Library, "Bing Maps Tile System," di Joe Schwartz (bit.ly/SxVojI) è una davvero buona spiegazione del sistema (incluso il codice utile) ma prendo un approccio un po' diverso qui.

Ogni piastrella è un quadkey unico. La piastrella URIs ottenuto dal servizio Web contengono una stringa segnaposto "{quadkey}". Prima di utilizzare uno degli URI per accedere una tegola, è necessario sostituire questo segnaposto con un quadkey reale.

Figura 3 e Figura 4 Visualizza quadkeys per zoom livelli 1 e 2.  Zeri sono importanti quadkeys. (Infatti, è possibile pensare la quadkey come una stringa, piuttosto che un numero). Il numero di cifre in un quadkey è sempre uguale al livello di zoom della piastrella. Le piastrelle in livello 21 sono identificate con 21 cifre quadkeys.

Le singole cifre di un quadkey sono sempre 0, 1, 2 o 3. Così, il quadkey è davvero un numero di base-4. Guardate queste quattro cifre binarie (00, 01, 10, 11) e come appaiono in un gruppo di quattro piastrelle. In ogni cifra base-4, il secondo bit è davvero una coordinata orizzontale e il primo bit è una coordinata verticale. I bit corrispondono a una longitudine e latitudine, che sono effettivamente interfogliati nella quadkey.

Ogni piastrella nel livello 1 copre la stessa area di un gruppo di quattro piastrelle nel livello 2. Si può pensare di una piastrella nel livello 1 come "padre" a quattro "figli" di livello 2. Quadkey di una piastrella bambino inizia sempre con le stesse cifre come suo padre e poi aggiunge un'altra cifra (0, 1, 2 o 3) a seconda della sua posizione all'interno dell'area di suo padre. Andando da padre a figlio è uno zoom. Zumando giù è simile: Per qualsiasi quadkey bambino, ottenere il quadkey padre semplicemente potature fuori l'ultima cifra.

Qui viene illustrato come derivare una quadkey da una latitudine e longitudine geografica reale.

Gli intervalli di longitudine da-180 ° l'Inter­linea data nazionale e poi aumenta andando est a 180 ° riga di dati internazionale di nuovo. Per qualsiasi longitudine, prima di calcolare una longitudine relativa che varia da 0 a 1 con 0,5 che rappresenta il meridiano di Greenwich:

double relativeLongitude = (180 + longitude) / 360;

Ora che convertire in un numero intero di un numero fisso di bit:

int integerLongitude =
  (int)(relativeLongitude * (1 << BITRES));

Nel mio programma ho impostato BITRES a 29 per i livelli di zoom 21 oltre a 8 bit per la dimensione in pixel della piastrella. Così, questo intero identifica una longitudine precisa al pixel più vicino di una piastrella al più alto livello di zoom.

Il calcolo del integerLatitude è un po' più complesso perché la proiezione di Mercatore mappa comprime latitudini come ci si allontana dall'equatore:

double sinTerm = Math.Sin(Math.PI * latitude / 180);
double relativeLatitude =
  0.5 - Math.Log((1 + sinTerm) / (1 - sinTerm)) 
    / (4 * Math.PI);
int integerLatitude = (int)(relativeLatitude * (1 << BITRES));

L'integerLatitude varia da 0 a 85,05 ° a nord dell'equatore al valore massimo a 85,05 ° a sud dell'equatore.

Il centro di Central Park a New York City ha una-73.965368 ° longitudine e una latitudine di 40.783271 °. I valori relativi sono (con pochi decimali) 0.29454 e 0.37572. 29-Bit integer longitudine e latitudine sono i valori (mostrato in binario e raggruppati per una più facile leggibilità):

0 1001 0110 1100 1110 0000 1000 0000
0 1100 0000 0101 1110 1011 0000 0000

Si supponga una tegola che mostra il centro di Central Park in uno zoom di livello 12. Prendere il top 12 bit dell'intero longitudini e latitudini (attenzione — le cifre seguenti sono raggruppate in un po ' diverso rispetto alle versioni 29 bit):

0100 1011 0110
0110 0000 0010

Queste sono due numeri binari, ma abbiamo bisogno di unirli per formare un numero di base-4. Non non c'è nessun modo per fare questo nel codice utilizzando semplici operatori aritmetici. Hai bisogno di un po ' di routine che in realtà passa attraverso i singoli bit e costruisce un intero lungo o una stringa. A scopo illustrativo, potete semplicemente doppio tutti i bit in latitudine e aggiungere i due valori, come se fossero i valori di base-4:

0100 1011 0110
0220 0000 0020
0320 1011 0130

Il risultato è la quadkey 12 cifre è necessario sostituire il segnaposto "{quadkey}" nell'URI si ottiene dal servizio Web di accedere la tegola mappa.

Figura 5 Mostra una routine per costruire una quadkey dall'intero tronco longitudini e latitudini. Per chiarezza, io ho separato la logica in sezioni che generano un valore long integer quadkey e una stringa di quadkey.

Figura 5 procedura per calcolare un Quadkey

string ToQuadKey(int longitude, int latitude, int level)
{
  long quadkey = 0;
  int mask = 1 << (level - 1);
  for (int i = 0; i < level; i++)
  {
    quadkey <<= 2;
    if ((longitude & mask) != 0)
      quadkey |= 1;
    if ((latitude & mask) != 0)
      quadkey |= 2;
    mask >>= 1;
  }
  strBuilder.Clear();
  for (int i = 0; i < level; i++)
  {
    strBuilder.Insert(0, (quadkey & 3).ToString());
    quadkey >>= 2;
  }
  return strBuilder.ToString();
}

Figura 6 Mostra la strada e il tessere aeree per questo quadkey. Il centro di Central Park è in realtà senso giù in fondo a queste immagini, un po ' a sinistra del centro. Questo è prevedibile dall'intero longitudini e latitudini. Guarda il prossimo 8 bit della longitudine intero dopo i primi 12 bit: I bit sono 0000 0111 o 112. Il prossimo 8 bit di latitudine sono 1111 0101 o 245. Ciò significa che il centro di Central Park è 112a pixel dalla sinistra e le forze pixel giù in quelle piastrelle.

The Tiles for Quadkey “032010110130”
Figura 6 piastrelle per Quadkey "032010110130"

Affiancamento delle piastrelle

Una volta che hai troncato un intero longitudine e latitudine di un certo numero di bit corrispondente a un livello di zoom particolare, ottenere tessere adiacenti è un gioco da ragazzi: Semplicemente incrementare e decrementare i numeri interi di longitudine e latitudine e quadkeys nuova forma.

Hai già visto tre metodi dalla classe MainPage del progetto RotatingMapTiles. Il programma utilizza un GeoCoordinateWatcher per ottenere la longitudine e la latitudine del telefono e le coordinate vengono convertiti in valori integer come illustrato in precedenza. La barra delle applicazioni ha tre pulsanti: per passare dalla strada e vedute aeree e per aumentare e diminuire il livello di zoom.

Il programma non ha nessun altra interfaccia touch oltre i pulsanti. Sempre Visualizza la posizione ottenuta da GeoCoordinateWatcher al centro dello schermo e costruisce la mappa totale con 25 elementi di immagine in una matrice di 5 x 5, una configurazione che riempie sempre lo schermo 480 x 800 pixel, anche con rotazione. Classe MainPage crea questi 25 elementi di immagine e gli oggetti BitmapImage nel relativo costruttore.

Ogni volta che GeoCoordinateWatcher si presenta con un nuovo percorso, o modifiche di stile mappa o livello di zoom, il metodo RefreshDisplay in Figura 7 viene chiamato. Questo metodo mostra come il nuovo URI sono ottenuti e impostare semplicemente agli oggetti BitmapImage esistenti.

Figura 7 il metodo RefreshDisplay in RotatingMapTiles

void RefreshDisplay()
{
  if (roadUriTemplate == null || aerialUriTemplate == null)
    return;
  if (integerLongitude == -1 || integerLatitude == -1)
    return;
  // Get coordinates and pixel offsets based on current zoom level
  int croppedLongitude = integerLongitude >> BITRES - zoomLevel;
  int croppedLatitude = integerLatitude >> BITRES - zoomLevel;
  int xPixelOffset = (integerLongitude >> BITRES - zoomLevel - 8) % 256;
  int yPixelOffset = (integerLatitude >> BITRES - zoomLevel - 8) % 256;
  // Prepare for the loop
  string uriTemplate = mapStyle ==
    MapStyle.Road ?
roadUriTemplate : aerialUriTemplate;
  int index = 0;
  int maxValue = (1 << zoomLevel) - 1;
  // Loop through the 5x5 array of Image elements
  for (int row = -2; row <= 2; row++)
    for (int col = -2; col <= 2; col++)
    {
      // Get the Image and BitmapImage
      Image image = imageCanvas.Children[index] as Image;
      BitmapImage bitmap = image.Source as BitmapImage;
      index++;
      // Check if you've gone beyond the bounds
      if (croppedLongitude + col < 0 ||
        croppedLongitude + col > maxValue ||
        croppedLatitude + row < 0 ||
        croppedLatitude + row > maxValue)
      {
        bitmap.UriSource = null;
      }
      else
      {
        // Calculate a quadkey and set URI to bitmap
        int longitude = croppedLongitude + col;
        int latitude = croppedLatitude + row;
        string strQuadkey =
          ToQuadKey(longitude, latitude, zoomLevel);
        string uri = uriTemplate.Replace("{quadkey}", strQuadkey);
        bitmap.UriSource = new Uri(uri);
      }
      // Position the Image element
      Canvas.SetLeft(image, col * 256 - xPixelOffset);
      Canvas.SetTop(image, row * 256 - yPixelOffset);
    }
}

Per mantenere questo programma ragionevolmente semplice, non tenta di passare le transizioni tra vista e livelli di zoom. Spesso tutto lo schermo si spegne come nuove piastrelle vengono caricati.

Ma il programma ruotare la mappa. La logica di rotazione è basata su sensore di movimento e un oggetto RotateTransform ed è praticamente indipendente dal resto del programma. Figura 8 me prendendo il mio Windows Phone (o forse l'emulatore Windows Phone) mostra una passeggiata attraverso il ponte di Brooklyn. La parte superiore del telefono è punta in direzione sto camminando, e sulla piccola freccia nell'angolo in alto a sinistra indica a nord.

The RotatingMapTiles Display
Figura 8 la visualizzazione RotatingMapTiles

Charles Petzold è un collaboratore di lunga data di MSDN Magazine e autore di "Programming Windows, 6a edizione" (o ' Reilly Media, 2012), un libro sulla scrittura di applicazioni per Windows 8. Il suo sito Web è charlespetzold.com.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Thomas Petchel