この記事は機械翻訳されたものです。

快適な動作

地図の方角合わせ用タッチ インターフェイス (機械翻訳)

Charles Petzold

コード サンプルのダウンロード

Charles Petzold私は少し迷ってショッピング モールや博物館にするたびに、マップについてを検索が同時にしばしば何を見つけることについていくつかの不安を感じる。 マップというラベルの付いた矢印を特色にする、「ここにいる」が、マップがどのように指向される確信? マップが垂直方向にマウントされている場合は、地図の右側にある実際に私の右に対応して下にある対応する私の後ろにあるものに? または、マップ精神からそのマウントを削除し、実際のレイアウトと整列するスペースでツイストする必要が?

角度または床と平行にマウントされているマップがはるかに優れて — つまり、彼らしている指向正しく始まるに提供します。 1 つの精神の敏捷性に関係なく空間的関係と、マップはとき彼らは地球に平行しているまたは地球と整列する前方回転できます読みやすくなります。 GPS の年齢前に、紙の道路地図を乱暴にそれらを右に左と逆さま検索で適切な向きねじれによってグラップ リングの人々 を参照するが一般的でした。

電話などのモバイル デバイス上のソフトウェアで実装されているマップには、コンパスの読書に基づいて自分自身を方向づける可能性があります。 これは、携帯電話からの相対回転します Windows Phone デバイス上、地図を表示私の探求の後ろの原動力です。 そのようなマップ自体、周囲の風景と合わせ、潜在的に私たちの間で失われたより有用であることができるはず。

マップを方向づける

最初の目標は、回転する地図よりも、もう少し野心的なでした。 それ常にコンパスを指向されているだけでなく、地球の表面に平行になるように私は想定プログラム マップ 3 D 空間で実際にフロートでしょう。

少し実験私はこのアプローチが私は必要なよりもやや贅沢だったことを確信しました。 自動車の GPS を表示のため傾斜マップと視点は罰金ですが、マップを常にだからと思うと同じ程度、傾いてし、それは多少はガラスを見ている何をまねます。 モバイル デバイス上の地図では、傾斜任意の追加情報を提供せずにマップ映像を圧縮する効果があります。 単純な二次元の回転は十分であるように見えた。

私の 11 月のコラムで私は Bing マップ SOAP サービスを使用してダウンロードし、マップに 256 ピクセルの正方形のタイルをアセンブルする方法について説明 (「組立 Bing のマップ タイルを Windows Phone」 msdn.microsoft.com/magazine/jj721603)。 この Web サービスから使用できるタイルがそれぞれ高いレベル ダブル各タイル次のより高いレベルで 4 つのタイルと同じエリアをカバーすることを意味する、以前のレベルの解像度を持つのズーム レベルに編成されます。

先月のコラムに含まれているアプリケーション バー ボタンのプログラムがプラス記号とマイナス記号の不連続なジャンプでズーム レベルを増減するというラベルの付いた。 そのインターフェイスの型は、Web サイト上の地図を十分です。 が、ための電話、適切と思われる唯一の説明は「完全に不自由」です。

つまり継続的に拡大したいマップできます本当のタッチ インターフェイスを実装する時間になりました。

一度そのタッチ インターフェイスを追加し始めた — 鍋に 1 本の指、2 本の指は、ズームインおよびズームアウトする — Windows Phone 上のマップ アプリケーションのと Silverlight マップ コントロールの深いと永続的な尊重を買収。 これらのマップは、明らかに私は管理することができるされてきたものよりもはるかに洗練されたタッチのインターフェイスを実装します。

たとえば、タイルが見つからないために地図 app を開け、黒い穴見たとは思わない。 画面が常に完全に覆われていること私の経験です — の認識の点を超えてを伸ばして明らかにも、タイルとが。 タイルはフェード アニメーションとより良い解像度のタイルが置き換えられます。 慣性非常に自然な方法で実装され、タイルがダウンロードされている間、UI は決してびくびく取得します。

どこにも実際の地図アプリケーションを閉じます (あなたがダウンロードすることができます) 私 OrientingMap プログラム付属しています。 パンおよび拡大はしばしばびくびく慣性がなくなったとタイルを迅速に十分なダウンロードではない場合は空白の領域が頻繁に表示されます。

これらの欠陥にもかかわらず、私のプログラムは、それを描く世界とマップの向きを維持する上で成功するわけ。

基本的な問題

Bing マップ SOAP サービス プログラムのアクセスは大きな複合マップを構築することができます 256 ピクセルの矩形マップ タイルを与えます。 道路と航空写真ビューに Bing Maps レベル 1 などのように 4 つのタイルで、レベル 2 レベル 3 16 タイル 64 と地球をカバーしてどこ利用の 21 のレベルのズーム、なります。 各レベルでは、二重の水平解像度と垂直解像度次の下位レベルの 2 倍を提供しています。

タイルには親子関係があります。レベル 21 のタイルを除いて、すべてのタイル一緒に自体が二重の解像度と同じエリアをカバー次のより高いレベルでの 4 人の子供があります。

プログラムは積分のズーム レベルにスティックだ — 先月のコラムでプログラムが行うように — 個々 のタイルはそれらの実際のピクセルのサイズで表示できます。 先月のプログラムは、常に 1,280 ピクセル四方の合計サイズの 5 × 5 配列 25 タイルが表示されます。 センター タイル内のどこか場所をマップ上の携帯電話の場所を画面の中央を一致させるために、プログラムは常にこのタイルの配列を配置します。 計算して、画面の中央にセンターのタイルの隅に座っている場合でも、この 1,280 ピクセルの正方形のサイズが電話にかかわらずどのようにそれが回転の 480 × 800 画面サイズのために十分であることを見つけることができます。

先月のプログラムのみ離散ズーム レベルをサポートし、常に携帯電話の場所に基づいてタイルをセンターため、変更が発生するたびに完全にこれらの 25 のタイルを置き換えることに非常に単純なロジックを実装します。 幸いにも、ダウンロード キャッシュこのプロセスをダウンロード済みのタイルでタイルを交換する場合、かなり高速になります。

タッチのインターフェイスでは、この単純なアプローチは、もはや許容です。

ハードの部分は、間違いなくスケールです:たとえば、マップ タイル レベル 12 のそれらのピクセルのサイズで表示することによって、プログラムを開始します。 今、ユーザーは画面上の 2 本の指を置くし、離れて画面を展開する指を移動します。 プログラムその 256 ピクセル サイズを超えてタイルを縮小することによって応答する必要があります。 それはこれにタイル自体、scaletransform を組み合わせてまたはタイルの組み立てにはキャンバスに適用する scaletransform を組み合わせて行うことができます。

しかし、これらのタイルは無限にスケールする必要はありません ! 次のより高いレベルと倍率を半分の 4 つの子のタイルで各タイルを交換するいくつかの時点で。 この置換処理は子タイルが瞬時に利用可能であったが、もちろん、彼らはしていない場合は、かなりつまらないでしょう。 彼らは、つまり子タイルは視覚的に、親の上に配置する必要があり、すべての子供の 4 タイルだけダウンロードされている場合、親はカンバスから削除することができますダウンロードしなければなりません。

その逆の処理がズーム アウトの必要があります。 ユーザーが一緒に 2 本の指をペンチのように、ダウン、タイルの配列全体を拡張できますが、いくつかの時点で 4 つのタイルの各グループは視覚的に 4 つのタイルの下に親タイルを置き換える必要があります。 4 人の子供はその親タイルだけダウンロードされているときに削除できます。

追加のクラス

私は先月のコラムで説明したように、Bing Maps「クアッド キー」と呼ばれる番号システムを使用して、マップ タイルを一意に識別します。 クアッド キー ベース 4 番号です:桁数のクアッド キーでズーム レベルを示し、インターリーブの経度と緯度の数字自体をエンコードします。

OrientingMap プログラムとクアッドの作業を支援するには、親と子のクアッドを取得するプロパティを定義するクアッド キー クラスにはプロジェクトが含まれています。

OrientingMap プロジェクトは、UserControl から派生する新しい MapTile クラスもあります。 このコントロールの XAML ファイルを示しています図 1。 それをする scaletransform を組み合わせてと同様に、ビットマップのタイルを上下のタイル全体のスケーリングを表示するため BitmapImage オブジェクトに設定 Image 要素でその Source プロパティを持っています。 (実際には、個々 のタイルのみ 2 の正と負の整数累乗でスケーリングされます。)デバッグ用のクアッド キーが表示されます、XAML ファイルでは、TextBlock を置くし、その中を残してきた。単にそれを参照するには、表示する可視性の属性を変更します。

図 1 OrientingMap からの MapTile.xaml ファイル

<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>

分離コード ファイル MapTile のいくつかの便利なプロパティを定義します。クアッド キー プロパティは、MapTile クラス自体はマップ タイルにアクセスする URI を取得することができます。 Scale プロパティは外部コードのスケーリング係数を設定できます。 IsImageOpened プロパティは、ビットマップがダウンロードされているときを示します。 ImageOpened プロパティ、ImageOpened イベントの BitmapImage オブジェクトへの外部アクセスを提供します。 これらの最後の 2 つのプロパティは、プログラム イメージを置き換える任意のタイルを削除することができますのでイメージが読み込まれたを決定するプログラムを支援します。

このプログラムの開発中は、私は当初、スキームときそれは 4 つの子 MapTile オブジェクトまたは親 MapTile のグループで置き換える必要がありますを決定するそのスケール プロパティ各 MapTile オブジェクトが使用します追求。 MapTile 自体は作成し、これらの新しいオブジェクトの位置決めの ImageOpened イベント ハンドラーを設定を処理してまた自体からキャンバスを削除するために責任があるでしょう。

しかし、この方式が非常にうまく機能するを得ることができなかった。 タッチのインターフェイスを通じて、ユーザーを展開する 25 のマップ タイルの配列を検討してください。 これらの 25 のタイルは、100 のタイルが置き換えられます、100 タイル 400 のタイルが置き換えられます。 わかりますよね。 いいえ、それはスケーリング効果的にこれらの潜在的な新しいタイルの多く表示されるようにあまりにも遠く離れて、画面を移動したため、ありません。 それらのほとんどは作成またはまったくダウンロードべきではない !

代わりに、MainPage にこのロジックをシフトしました。 このクラスは型辞書 < クアッド キー、MapTile > の currentMapTiles フィールドを保持します。 場合でも、彼らはまだダウンロードの過程でこのすべての MapTile オブジェクト現在ディスプレイに格納します。 RefreshDisplay という名前のメソッドでは、マップと倍率の現在の場所を使用して型リスト <QuadKey> の validQuadKeys フィールドを組み立てるします。 CurrentMapTiles では validQuadKeys のクアッド キー オブジェクトが存在する場合は、新しい MapTile 作成し、キャンバスと currentMapTiles の両方に追加します。

RefreshDisplay は、もはや必要な MapTile オブジェクトは削除されませんどちらか彼らは画面の外パンまたは両親や子供たちに置き換えされてため。 それはクリーンアップという 2 番目の重要な方法の責任です。 このメソッドは、validQuadKeys コレクション currentMapTiles と比較します。 ValidQuadKeys ではない currentMapTiles でのアイテムを検索する場合は、それだけその MapTile validQuadKeys には子供がないまたは validQuadKeys の子供たちは、すべてのダウンロードされている場合は、または validQuadKeys が含まれているその MapTile の親、その親がダウンロードされている場合は削除します。

RefreshDisplay およびクリーンアップ メソッドがより効率的に行う — とそれらの少ない頻繁に呼び出す — OrientingMap のパフォーマンスを向上させる方法の 1 つです。

入れ子になったキャンバス

OrientingMap プログラムの UI グラフィックス変換の 2 種類が必要です。翻訳シングル指パニングとスケーリングのための 2 本の指のピンチを操作します。 さらに、北の方向と地図を方向づける回転変換が必要です。 これらの効率的な Silverlight 変換を実装するには、MainPage.xaml ファイル キャンバス パネルの 3 つのレベルで示すように含む図 2

図 2、MainPage.xaml の多くのファイルの 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>

ContentPanel という名前のグリッドには、最も外側のキャンバスとして常に画面上の固定された場所に表示される 3 つの要素が含まれています。TextBlock の初期化エラーの報告を北の方向を表示するには回転する矢印を含む国境とビングのロゴを表示する別の国境。

最も外側のキャンバス、HorizontalAlignment と垂直している­アライメント プロパティの設定をグリッドの中心に配置サイズは 0 にキャンバスを縮小するセンター。 (0, 0) 座標このキャンバスのです、したがって表示の中心。 この中心のタイル、位置決めのために便利で、スケーリングや回転が原点の周囲に発生することもできます。

最も外側のキャンバスは北の方向に基づいて回転したものです。 この最も外側のキャンバス内、TranslateTransform を持つ 2 番目のキャンバスです。 これは、パンのためです。 画面上で 1 本の指をスイープするたびに、この TranslateTransform の X および Y プロパティを設定するだけでマップ全体を移動できます。

この 2 番目のキャンバス内で、マップの中心と相対的な電話の現在の場所を示すために使用は楕円です。 ユーザー マップをパンするときに、この楕円にも移動します。 しかし、携帯電話の GPS 位置、別の翻訳の変更が報告された場合­変換楕円に相対的なマップに移動します。

最も内側のキャンバス imageCanvas、という名前です、それはここでマップ タイルが実際に組み立てていることです。 このキャンバスに適用、scaletransform を組み合わせてプログラムをこの全体の群集のズームインまたはズームアウト、ピンチ操作とユーザーに基づいてマップ タイルを増減することができます。

連続的なズームに対応するには、プログラムの zoomFactor フィールド型の二重に保持します。 この zoomFactor はタイル レベルとして同じの範囲を持っています — 1 ~ 21 — つまり合計マップ スケール要因のベース-2 対数実際にであること。 ZoomFactor が 1 ずつ増加するたびに、地図の拡大縮小 2 倍になります。

初めてプログラムを実行 zoomFactor 12 に初期化されますが、ユーザー 2 本の指で画面に触れて初めてそれ整数以外の値になります、非常に可能性がその後に整数以外の値のまま。 プログラム zoomFactor でユーザー設定として保存されます、次回プログラムを実行再読み込みします。 初期の不可欠な共存は、単純な切り捨てと計算されます。

baseLevel = (int)zoomFactor;

この共存は常に整数 1 から 21 日までの範囲であり、それゆえ直接タイルを取得するために適しています。 これら 2 つの数値からプログラムは double 型の非対数スケール ファクターを計算します。

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

これは、最も内側のキャンバスに適用されるスケーリング係数です。 たとえば、zoomFactor 10.5 の場合は、[タイルを取得するために使用共存 10 で、canvasScale 値は 1.414 です。

初期の zoomFactor 10.9 の場合は、11 と 0.933 で canvasZoom での共存を設定するより多くの意味を作る可能性があります。 プログラムにしないが、それは明らかに可能な洗練されたです。

1 と 2 本の指タッチ入力

タッチ入力の場合、私は XNA タッチパネルより Silverlight 操作イベントを使用してより快適に感じた。 MainPage コンス トラクター 4 種類 XNA ジェスチャーのことができます。FreeDrag (パン)、DragComplete、ピンチ、PinchComplete。 タッチパネルは CompositionTarget.Rendering イベントのハンドラーでの入力に示すようにチェックされて図 3。 その複雑さ、少しだけのためのピンチ処理を次に示します。

図 3 タッチの 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);
        ...
RefreshDisplay();
        break;
      case GestureType.PinchComplete:
        Cleanup();
        break;
    }
  }
}

FreeDrag 入力位置とデルタ値によって (型 Vector2 の両方) と一緒に伴われる、指と指をタッチパネルの最後のイベント以降移動しましたどのようにの現在位置を示します。 ピンチ入力 Position2 と Delta2 の値を 2 番目の指のサプリメントします。

ただし、これらの Vector2 値画面の座標であることに注意してください ! 画面を基準にマップを回転させるため — と指の動きとして、同じ方向にパンするには、マップのユーザーの期待 — これらの値は現在のマップの回転は、TransformGestureToMap という名前はほとんどのメソッドで発生するに基づいて回転する必要があります。

処理 FreeDrag のデルタ値は、XAML ファイルとして centerRelativeLongitude と centerRelativeLatitude という 2 つの浮動小数点フィールド TranslateTransform に適用されます。 これらの値の範囲は 0 から 1 に、画面の中央に対応する緯度と経度を示します。

いくつかの時点では、ユーザーは新しいタイルは読み込む必要がある十分な程度にマップをパン可能性があります。 その可能性を各タッチ イベントのチェックを回避するには、プログラム マップ タイルのピクセル サイズである 256 上記いずれかの値を行くとき accumulatedDeltaX と accumulatedDeltaY、および RefreshDisplay の呼び出しのみという名前の 2 つのフィールドを保持します。

RefreshDisplay を行うには大きな仕事があるので — すべきことは、画面上に表示に基づいて centerRelativeLongitude と centerRelativeLatitude と現在の canvasScale どのようなタイルを決定し、必要に応じて新しいタイルを作成 — それはタッチ入力ですべての変更を呼び出さないでこと最高です。 1 つの明確な強化プログラムに RefreshDisplay の呼び出しはピンチ入力中に制限します。

タッチの処理中にクリーンアップ メソッドが呼ばれるは、指や指が画面に残っているときにのみです。 マップ タイルのダウンロードが完了するたびにクリーンアップのとも呼ばれます。

共存を変更するための基準 — と、置換、または親によって子による親マップ タイルをそれにより開始 — 非常にリラックスしています。 CanvasScale 0.5 未満に低下すると canvasScale 2 より大きいと減少になると、共存だけインクリメントされます。 良い遷移点の設定別の明白な強化です。

プログラムは今 2 つだけのアプリケーション バーのボタンを持っています。現在の場所は中心部では最初の地図道路と航空写真ビュー、および 2 番目の位置の間を切り替えます。

私は私のショッピング モールや博物館をナビゲートを助けるプログラムを作る方法を把握するだけです。

Charles Petzold は長年の貢献、MSDN Magazine と「プログラミング Windows、第 6 版」の著者しています (オライリー メディア、2012)、Windows 8 用アプリケーションの作成についての本。 彼の Web サイト charlespetzold.com

この記事のレビュー、次技術専門家のおかげで:トーマス ・ Petchel