ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
MapKitによる場所の可能性の実現
MapKitとMapKit JSを使用してアプリやWebサイトにマップを取り入れる、新しいパワフルな方法を学びましょう。 場所IDを使用して特定の場所を保存し、参照する方法を説明します。適切な場所をより効率的に検索できるようにするための機能強化のほか、 場所に関する豊富な情報を表示してユーザーがアプリから目的地を直接探せるようにする、新しいPlace Card APIもご紹介します。さらに、シンプルな方法によるトークンのプロビジョニングとWeb Embed APIを利用して、Webサイトにすばやくマップを埋め込む方法も習得できます。
関連する章
- 0:00 - Introduction
- 0:54 - Reference a place
- 6:12 - Display place details
- 12:05 - Find a place
リソース
- Displaying place information using the Maps Embed API
- Forum: Maps & Location
- Identifying unique locations with Place IDs
- Interacting with nearby points of interest
- Resources - Apple Maps - Apple Developer
関連ビデオ
WWDC23
-
ダウンロード
ようこそ Mikeです こんにちは Jeffです 私たちはMapKitのエンジニアです 今日は MapKitフレームワークと Web向けMapKit JSの両方の新機能を いくつかご紹介します 多くの場合 地図の役割は 目的の場所を見つけることです Appleマップを使用して 世界中の場所を探索したり ナビゲートしたりすると その場所に 関する様々な情報にアクセスできます 今年は これらの場所を皆さんのアプリに 新しい方法で取り込むための機能が MapKitに追加されました 説明したいことは沢山ありますが ここでは3つのユースケースを取り上げます 具体的には 識別子を使用して場所を参照する方法 アプリのUIに場所の詳細を表示する方法 機能強化された検索APIを使用して 場所を効果的に見つける方法です まず 場所を参照する方法を説明しましょう 今年は場所の識別子が導入されました この場所IDを使用すると MapKitフレームワークではマップアイテム MapKit JSではPlaceで表される 場所を参照できます 場所IDを使用して 様々な場所を参照できます 美術館 レストラン 公園 学校など 関心のある場所で場所IDを使用できます アプリで場所IDを使用すると 例えば 新刊を出した作家が サイン会で訪問する 書店の場所を確認できます この参照は一意であり 長期にわたって有効です 場所が存在する限り その参照も保持されます 本のサイン会ツアーアプリには 訪問する各書店のWebサイトリンクを 表示すると便利です WebサイトのURLなどの店舗データが Appleによって更新されると 場所IDを使用しているアプリに その最新情報を表示できます これらの識別子は一意なので アプリでは それぞれのデータ構造で 場所IDをキーとして使用できます 識別子は永続化と共有が可能です Appleではこのデータを追跡しているので アプリでいつでも参照できます Mikeが開発しているアプリでは 場所IDをはじめ 多数の新機能が使用されていると聞きました どのようなアプリか教えてくれますか? もちろんです! このアプリの目的は場所の収集なので 場所IDが非常に役立ちます こちらがApple Storeコレクターです 私は各国を歩き回って 特徴のある洗練された Apple Storeの情報を集めています 場所IDを使用すると このような素晴らしい 店舗インデックスができあがります 情報収集には出発点が必要ですが Apple Park Visitor Centerは このアプリの出発点として最適です インデックスを作成するには まず このCenterの場所IDを検出する必要があります 場所IDの検出では SearchやGeocodingなど 既存のAPIを使用できますが ここでは 新しい 場所IDの検索ツールを使用します 少数のIDの検索では このツールの方が高速だからです まずdeveloper.apple.com/jp/maps/ にアクセスして 画面右上にある リンクをクリックします ページを下方にスクロールすると があります
ここでApple Park Visitor Centerを 簡単に検索できます また 地図上でこの場所をタップすると 場所IDが表示されます 素晴らしい! 場所IDがわかったので アプリの地図上に この場所を表示してみましょう わずか数行のコードで場所を表示できます こちらは Apple Park Visitor Centerを 表示するためのコードです ここでは 検索ツールから取得した場所IDを 使用して識別子を作成しています この識別子を使用し 非同期リクエストで マップアイテムを取得します このアプリは 取得したアイテムの マーカーが追加された地図を表示します 座標をハードコードする代わりに 場所IDを使うことで Appleマップエディタの機能を活用します 例えば 場所の配置が誤っていて マーカーの移動が必要と 判断された場合は コードを更新しなくても その変更を引き継ぐことができます この場所IDは すべてのネイティブアプリや Webコンテンツツールと共有できます これは Webに同等の画面を作成するために 使用するJavaScriptコードです まず entryPoint関数を作成します この関数を初期化してから 呼び出すことをMapKit JSに指示します この関数では 場所IDの検索Webサイトで 検索した場所IDを別の関数に渡します そこでは 渡されたIDに該当する場所を検索し それをコールバックannotatePlaceに送ります annotatePlace関数は その場所を中心とする新しい地図を作成し 場所オブジェクトPlaceAnnotationを 表示するための 新しい種類の注釈を追加します 次に entryPoint関数が読み込まれたら それを呼び出すようMapKit JSに指示します 今年は MapKit JSを初期化するコードが これまでになく短くなりました まず この非同期スクリプトタグを追加して MapKit JSのsrc属性を指定します 次に entryPoint関数を data-callbackとして記述し 読み込み後に MapKit JSによって 呼び出されるようにします 最後の属性はdata-tokenです このトークンはどこから得るのでしょうか? 今年 動的なJWT生成プロセスを ドメイン固有のトークン文字列を生成する 合理化されたUIに置き換えました このトークンは 手動で無効にしない限り 期限切れになりません 新しいトークンプロビジョニングツールで 新しいトークンを作成します まずdeveloper.apple.com/jp/maps/ にアクセスして 画面右上にある リンクをクリックします ページを下方にスクロールすると があります
追加ボタンをクリックすると MapKit JS生成トークンを作成するための 簡単なフォームが表示されます このトークンは有効期限がなく 私のドメインでのみ有効です トークンを作成した後 それが不要になったときは この画面に戻って無効化できます ここから トークンをスクリプトタグに コピーするだけです 有効期限や動的なJWT生成に 留意する必要がなく mapkit.initを 再度呼び出す必要もありません これで アプリとWebで 最初のApple Storeを表示するための 設定が完了しました 素晴らしいアプリになりそうですね アプリとWebサイトで 同じ識別子を使用して 個々のApple Storeを参照できれば とても便利です 場所IDを使用してできることは たくさんあります この技術を使用して複数の場所IDを 独自に作成および 維持する方法について詳しくは このセッションのリソースにある 「Identifying Unique Locations With Place IDs」リンクを ご覧ください このように 場所IDを使用すると 目的の場所の最新情報を入手できます 次は その場所情報を アプリやWebサイトに表示しましょう Mapsアプリで場所をタップすると 場所カードが表示されます 場所カードには 営業時間や電話番号などの 情報が表示されています 今年は 皆さんのアプリやWebサイトに 場所カードを導入できるようになります 新しいSelectionAccessory APIを使用すると 地図上で場所を選択されたときにいつでも その場所の情報を表示できます その際 大きな全画面表示にするか
場所を取らないコンパクトな表示にするか Mapsアプリへのリンクのみを表示するかを それぞれのユースケースに合わせて 選択できます また デフォルトの 自動スタイルを使用すると マップビューのプラットフォームとサイズに 最適なスタイルが自動的に選択されます アプリやWebサイトにマップビュー機能が なくても 場所カードを表示できます MapItemDetail APIと PlaceDetail APIによって柔軟性が得られ 多彩なユースケースに対応できます 最後に Webサイトに地図を追加するための 簡単な方法として埋め込みがあります デベロッパWebサイトの ツールでは コピー&ペーストが可能な HTMLを生成できます Mikeがアプリに場所カードを 取り込んでいます 見せてもらえますか? もちろんです Visitor Centerを 地図上に表示できたので この場所に関する美しい最新情報を 見栄えよく表示しようと思います これは 訪れた全店舗の地図を アプリに表示したところです あとは コードを1行追加するだけです 出来上がりました 訪問したいずれかの店舗をタップすると その店舗に関する多くの詳細情報が アプリに表示されます 良質な場所データには価値があります Webサイトも以前の状態から 更新しましょう MapKit JSコードに2行追加しました 1行目で 新しいPlaceSelectionAccessoryを作成し 2行目で そのアクセサリを以前からの 既存の注釈に追加します Apple Park Visitor Centerを選択すると 豊富なデータがポップアップ表示されます 関心のある場所が複数あるページや 使用パターンが複雑なページでは MapKit JSを使用してこの情報を表示すると 非常に効果的です データを表示する場所が 1つのみのWebサイトでは Jeffが紹介した新しいローコード ソリューション「埋め込み」を使用できます このWebサイトでは 1つのApple Storeの 現時点での詳細のみが表示されるので 同じ機能を埋め込みで実現する方法を 考えたいと思います まずdeveloper.apple.com/jp/maps にアクセスして 画面右上にある リンクをクリックします ページを下方にスクロールすると があります
ここで 新しい ボタンを選択すると 埋め込んだ地図を場所IDやデベロッパ トークンなどでカスタマイズできます 目的の情報が地図に表示されたら 生成されたHTMLスニペットを Webサイトにコピーするだけで JavaScriptを1行記述しなくても 美しい地図が得られます 詳細な場所情報を表示するための 最後の方法は 地図の利用方法からは完全に外れています 収集した全店舗を 地図ではなく 迅速にスクロールできるリストに 表示したい場合もあります 各リスト項目の場所情報を確認できれば もっと便利になるはずです これは 気に入った店舗を リスト表示するコードです まず すべての店舗をリスト表示します 各リスト項目には 店舗の名前が表示されます この行により リスト項目をタップしたとき 場所カードが全画面スタイルで表示されます 気に入った店舗の詳細情報を Webでも見たいので Webサイトも更新しましょう これは以前のWebコードですが annotatePlace関数を わずか2行の ステートメントで置き換えています 最初の行ではページから要素を取得します 2番目の行では 検索した場所の 詳細情報をその要素に格納します さらに 新しいアダプティブな カラースキームを使用しており 場所の詳細をシステム推奨のカラースキーム (ライトまたはダーク)に 合わせることができます 同じ値を使用して アダプティブな MapKit JSマップも作成できます アプリとWebが 同じような機能を備えることになります どうでしょう? 本当に同じになりますね 新しいAPIを使用して 店舗について収集した場所情報を 表示しています MapItemDetail APIを使用すると マップビューがないアプリでも 場所カードを表示できます UIKit AppKit SwiftUIで APIを利用できます MapKit JSでは PlaceDetail APIを使用して 場所カードをWebページの コンテンツとして扱うことができます SelectionAccessory APIを使用すると 地図上に場所情報を表示できます MapKitには 様々なプラットフォームや テクノロジーで使用できる SelectionAccessory APIが用意されています
Mikeのおすすめの店舗がすべて 表示された地図を見ていたら 同じ道筋をたどって どれかの店舗へ行ってみたくなりました 娘たちを連れて行って 初めてのApple Tシャツを 選んでもらうのもいいですね 彼女たちはピクニックも大好きなので 公園を探して軽食をとるのもいいでしょう いい考えがあります SelectionAccessory APIを使用すれば Apple Storeだけでなく 地図上のすべての場所について 場所カードを表示できます 気に入った店舗の周辺をタップするだけで ピクニックにぴったりな公園が見つかります 訪問するApple Storeを選ぶときに この情報が役立ちます 試してみましょう 気に入った店舗を表すマップアイテムごとに アプリによってマーカーが追加されます SwiftUIでは MapSelectionを使用して 独自のコンテンツと 地図上のマップ機能の両方を選択できます このモディファイアを使用することで マップ機能が選択されたときに場所カードの コールアウトを表示できます SelectionAccessory APIを初めて 使用する場合 これはお勧めの方法です マップ機能について アプリの既存の コードでこのAPIを有効にするだけです アプリでは 注釈のコンテンツを これまでどおりに表示できるので ユーザーは 地図上の コンテンツ周辺の場所について 詳しく知ることができます この追加のコンテキストで 多くの価値を提供できます 場所IDで場所を参照する方法と 場所カードに有用な情報を 表示する方法がわかりました では目的の場所は どこにしましょうか 目的の場所が 場所IDの検索ツールを使用して 見つけた場所に限らないのであれば アプリ側で場所を検索する必要があります 今年は 検索APIの機能が向上し 新しい方法で検索結果を 絞り込めるようになりました コンサート会場 スケートパーク さらには城など 多彩な場所を検索できます 川や山脈など 地形的な特徴も検索できます 都市名や郵便番号など 住所の一部を 検索することも可能です 特定の領域に限定した検索もできるので 目的の地域のみを 検索対象とすることができます サーバAPIでは 改ページ機能を追加しています これで 大量の検索結果が返される場合は それらを複数のページに表示できます これらの新しい検索機能により 店舗の検索と収集がより効率的に なると思いますか? もちろんです 目的のApple Storeを探して ベイエリアを一日中動き回った後は 濃いコーヒーを飲んで 一休みしたくなるかもしれません これらの新しい検索機能を使用して Webサイトで事前に計画を立て 飲みたいときに好きなだけ コーヒーを飲めるようにしましょう さらに ネイティブアプリにも 同じ機能を追加します まず クパティーノ市を探す必要があります これは 新しいAddressFilterを 使用するとできそうです ここでは 市区町村のみを検索する AddressFilterを作成します 大半の国では これは都市に該当します 次に そのフィルタを 新しい検索オブジェクトに接続します 最後に「クパティーノ」を指定して 検索を実行し この都市を見つけます この時 例えばクパティーノの Classy Cleaners社が ヒットするようなことはありません 「クパティーノ」に一致する都市の 検索結果は 私が設定したshowMap関数へ送られます この関数では 最初の検索結果を取り それを使用して 新しい地図に渡す地域を作成します このコードによって 希望どおり クパティーノを中心とした 地図が得られます 次に この地域内での新しい検索を作成して コーヒーショップを探します 最後に コーヒー店の各検索結果について 新しいPlaceAnnotationを追加します すべての結果が地図上に表示されました しかし まだ少し問題があります 地図をズームアウトすると 最初のビューポートの外部で 地図に追加した注釈が 多すぎることがわかります こんな遠方のコーヒーショップまで 行く気はないので RegionPriorityを使用して これらを除外します このプロパティ1つで 検索に渡した地域内の 結果のみを返すように要求できます この変更によって 最初に指定した地域のみの 検索結果がページに表示されます これらは自転車で行ける範囲にある コーヒーショップです もちろん これらの機能は ネイティブアプリでも使用できます 私のアプリも まったく同じ方法で改善していきます ここでは 各コーヒーショップの マーカーを表示するビューを作成しました findCityを非同期で使用して都市の地域を 取得し findCoffeeを使用して その地域のコーヒーショップを検索します こちらがfindCityです JSコードと同様に addressFilterを使用して場所を検索します そして こちらがfindCoffeeです ここでクパティーノ地域を取り込み regionPriorityを使用して 指定の地域にある コーヒーショップを検索します さて この改善によって 素晴らしいApple Storeコレクターになる 準備ができました そのとおりですね コレクションが 充実していく様子を見るのが楽しみです パスポートのスタンプ収集まで 行き着くかもしれませんね
ここまで多くを説明してきました 最新バージョンのMapKitでは 場所IDによる場所の参照 場所カードによる有用な情報の表示 Webページへの場所の埋め込みを簡単に行えます MapKitを使用すると 様々な種類の場所を検索できるほか 検索範囲を特定の地域に 制限することもできます MapKitサーバAPIでは 改ページによって これまで以上に多くの結果を表示できます MapKit JSにはトークンの簡潔な プロビジョニングプロセスが用意されており MapKit JSの使用を容易に開始できます 最後に 皆さんに宿題です 場所IDを使用して 土曜日の朝に ファーマーズマーケットが 開かれる公園を見つけてください 場所カードを使用して アプリに場所情報を表示してください 場所カードを使用すると アプリの検索結果と 重要な特定の場所に関する 詳しい情報が得られます 地図上のすべての重要な地点で 場所カードを有効にして アプリで地域の情報を調べるユーザーに 有益なコンテキストを提供します 「Identifying Unique Locations With Place IDs」リンクで 場所IDの詳しい使用方法をご確認ください MapKitの新機能に興味を お持ちいただけましたら幸いです 地図作成を続けてください 私は収集を続けます!
-
-
3:06 - Display a visitor center annotation
// Display a visitor center annotation struct PlaceMapView: View { var placeID: String // "I63802885C8189B2B" @State private var item: MKMapItem? var body: some View { Map { if let item { Marker(item: item) } } .task { guard let identifier = MKMapItem.Identifier( rawValue: placeID ) else { return } let request = MKMapItemRequest( mapItemIdentifier: identifier ) item = try? await request.mapItem } } }
-
3:44 - Display an annotation for the center
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
7:32 - Display my favorite apple stores
// Display my favorite apple stores struct VisitedStoresView: View { var visitedStores: [MKMapItem] @State private var selection: MKMapItem? var body: some View { Map(selection: $selection) { ForEach(visitedStores, id: \.self) { store in Marker(item: store) } .mapItemDetailSelectionAccessory() } } }
-
7:50 - Display a selectable annotation
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); const accessory = new mapkit.PlaceSelectionAccessory(); annotation.selectionAccessory = accessory; }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
9:15 - List stores and show details when selected
// List stores and show details when selected struct StoreList: View { var stores: [MKMapItem] @State private var selectedStore: MKMapItem? var body: some View { List( stores, id: \.self, selection: $selectedStore ) { Text($0.name ?? "Apple Store") } .mapItemDetailSheet(item: $selectedStore) } }
-
9:37 - Show visitor center details
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const el = document.getElementById("place"); const detail = new mapkit.PlaceDetail(el, place, { colorScheme: mapkit.PlaceDetail.ColorSchemes.Adaptive }); }; </script> <div id="place"></div> </body> </html>
-
11:17 - Display a place card for the selected map feature, too
// Display a place card for the selected map feature, too struct VisitedStoresView: View { var visitedStores: [MKMapItem] @State private var selection: MapSelection<MKMapItem>? var body: some View { Map(selection: $selection) { ForEach(visitedStores, id: \.self) { store in Marker(item: store) .tag(MapSelection(store)) } .mapItemDetailSelectionAccessory(.callout) } .mapFeatureSelectionAccessory(.callout) } }
-
13:09 - Find Cupertino, then find coffee
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const addressFilter = mapkit.AddressFilter.including([ mapkit.AddressCategory.Locality ]); const citySearch = new mapkit.Search({ addressFilter }); citySearch.search("Cupertino", showMap); }; const showMap = (error, cities) => { const center = cities.places[0].coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const coffeeSearch = new mapkit.Search({ region, regionPriority: mapkit.Search.RegionPriority.Required, pointOfInterestFilter: mapkit.PointOfInterestFilter.including([ mapkit.PointOfInterestCategory.Cafe ]) }); coffeeSearch.search("coffee", (error, results) => { for (const place of results.places) { const marker = new mapkit.PlaceAnnotation(place); map.addAnnotation(marker); } }); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
14:41 - Finding coffee in Cupertino
// Finding coffee in Cupertino struct CoffeeMap: View { @State private var position: MapCameraPosition = .automatic @State private var coffeeShops: [MKMapItem] = [] var body: some View { Map(position: $position) { ForEach(coffeeShops, id: \.self) { café in Marker(item: cafe) } } .task { guard let cupertino = await findCity() else { return } coffeeShops = await findCoffee(in: cupertino) } } private func findCity() async -> MKMapItem? { let request = MKLocalSearch.Request() request.naturalLanguageQuery = "cupertino" request.addressFilter = MKAddressFilter( including: .locality ) let search = MKLocalSearch(request: request) let response = try? await search.start() return response?.mapItems.first } private func findCoffee(in city: MKMapItem ) async -> [MKMapItem] { let request = MKLocalSearch.Request() request.naturalLanguageQuery = "coffee" let downtown = MKCoordinateRegion( center: city.placemark.coordinate, span: .init( latitudeDelta: 0.01, longitudeDelta: 0.01 ) ) request.region = downtown request.regionPriority = .required let search = MKLocalSearch(request: request) let response = try? await search.start() return response?.mapItems ?? [] } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。