このクイック スタートでは、GeoCatalog の衛星画像と地理空間データを対話型マップに表示する Web アプリケーションを構築します。 Microsoft Entra ID を使用してユーザーを認証し、STAC コレクションに対してクエリを実行し、マップ タイルをレンダリングします(すべてブラウザーの JavaScript から)。
学習内容:
- MSAL.js を使用してユーザーを認証し、アクセス トークンを取得する
- STAC API にクエリを実行してコレクションと項目を検出する
- 承認ヘッダーを使用して MapLibre GL マップにラスター タイルを表示する
- コレクション全体でシームレスなモザイク レイヤーを作成する
- SAS トークンを使用して生アセットをダウンロードする
コード パターンは、最新の JavaScript フレームワーク (React、Vue、Angular) またはバニラ JavaScript で動作します。 GeoCatalog API には完全な CORS サポートがあるため、開発中に localhost から直接呼び出すことができます。プロキシは必要ありません。
このコードは、 Microsoft Planetary Computer Pro パブリック GitHub リポジトリからダウンロードしてテストできます。
[前提条件]
- アクティブなサブスクリプションを持つ Azure アカウント。 無料でアカウントを作成できます。
- 少なくとも 1 つのコレクションに項目が含まれる、デプロイされた GeoCatalog リソース 。
- ユーザー ID には、 GeoCatalog リソースへの GeoCatalog 閲覧者 (またはそれ以上) のアクセス権が必要です。 GeoCatalog リソースへのアクセスの管理を参照してください。
- 18以降のNode.js バージョン。
アーキテクチャの概要
一般的な GeoCatalog Web アプリケーションは、次のアーキテクチャに従います。
Microsoft Entra ID でアプリケーションを登録する
Web アプリケーションでユーザーを認証する前に、Microsoft Entra ID に登録します。 このクイック スタートでは、 シングル ページ アプリケーション (SPA) の登録を使用します。これは、クライアント側の JavaScript アプリケーションやローカル開発に最適です。 後の手順で示す API 統合パターンは、任意のアプリケーションの種類で動作します。
注
バックエンド サーバーを使用する運用アプリケーションの場合は、別の登録の種類 (Web、ネイティブなど) を選択することを検討してください。 シナリオに適した方法の選択に関するガイダンスについては、「 アプリケーション認証の構成」 を参照してください。
シングルページ アプリケーションとして登録する
- Azure portal で Microsoft Entra ID に移動します。
- サイド パネルから [アプリの登録 ] を選択します。
- [新規登録] を選択します。
- アプリケーションの名前 ("GeoCatalog Web App" など) を入力します。
- [サポートされているアカウントの種類] で、 [この組織のディレクトリ内のアカウントのみ] を選択します。
- [ リダイレクト URI] で、[ シングルページ アプリケーション (SPA)] を選択し、開発 URL (
http://localhost:5173など) を入力します。 - 登録 を選択します。
登録後、[ 概要 ] ページで次の値に注意してください。
- アプリケーション (クライアント) ID
- ディレクトリ (テナント) ID
詳細については、 クイック スタート アプリの登録を確認してください。
API アクセス許可を付与する
アプリケーションには、サインインしているユーザーの代わりに GeoCatalog API を呼び出すアクセス許可が必要です。
- アプリの登録で、[API のアクセス許可] を選択します>アクセス許可を追加します。
- 組織が使用する API を選択し、Azure Orbital Spatio を検索します。
- 委任されたアクセス許可 を選択し、user_impersonation をチェックします。
- アクセス許可の追加 を選択します。
- 管理者の場合は、[テナント内のすべてのユーザーに代わって 同意する管理者の同意を付与 する] を選択します。
アプリケーションの作成
アプリケーションには、次の構成値が必要です。 これらの値を指定する方法は、ビルド ツール (環境変数、構成ファイルなど) によって異なります。
| コンフィギュレーション | 価値 | Description |
|---|---|---|
| カタログ URL | https://{name}.{region}.geocatalog.spatio.azure.com |
GeoCatalog エンドポイント |
| テナント ID | アプリの登録から | あなたの Microsoft Entra テナント |
| クライアントID | アプリの登録から | アプリケーションのクライアント ID |
| API スコープ | https://geocatalog.spatio.azure.com/.default |
常にこの正確な値を使用する |
依存関係のインストール
ブラウザー アプリケーションとマップ ライブラリ用の Microsoft Authentication Library (MSAL) をインストールします。
npm install @azure/msal-browser maplibre-gl
- @azure/msal-browser - Microsoft Entra ID を使用して OAuth 2.0 認証を処理します
- maplibre-gl - タイル視覚化用のオープン ソース マップ ライブラリ
ヒント
プロジェクト構造: このクイック スタートのコード サンプルは、必要に応じて整理できるスタンドアロン関数です。 一般的なパターン:
-
auth.js: MSAL 構成およびトークン関数 -
api.js: STAC API、Tiler API、SAS トークン関数 -
map.js: MapLibre の初期化とタイル レイヤーの管理 -
App.jsまたはmain.js: UI と一緒にすべてを結び付け
各関数は、依存関係 (アクセス トークン、URL) をパラメーターとして受け取り、任意のフレームワークまたはプロジェクト構造に簡単に統合できます。
MSAL 認証を実装する
ブラウザー認証用に MSAL を構成します。 次の例は、キーの構成とトークンの取得パターンを示しています。
import { PublicClientApplication, InteractionRequiredAuthError } from '@azure/msal-browser';
// Configuration - replace with your values or load from environment/config
const msalConfig = {
auth: {
clientId: 'YOUR_CLIENT_ID',
authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID',
redirectUri: window.location.origin,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
};
// Create MSAL instance
const msalInstance = new PublicClientApplication(msalConfig);
// GeoCatalog API scope - always use this exact value
const scopes = ['https://geocatalog.spatio.azure.com/.default'];
/**
* Acquire an access token for GeoCatalog API calls.
* Tries silent acquisition first, falls back to popup if needed.
*/
async function getAccessToken() {
const account = msalInstance.getActiveAccount() || msalInstance.getAllAccounts()[0];
if (!account) {
throw new Error('No authenticated account. Call login() first.');
}
try {
// Try silent token acquisition (uses cached token)
const result = await msalInstance.acquireTokenSilent({ account, scopes });
return result.accessToken;
} catch (error) {
// If silent fails (token expired), fall back to popup
if (error instanceof InteractionRequiredAuthError) {
const result = await msalInstance.acquireTokenPopup({ scopes });
return result.accessToken;
}
throw error;
}
}
/**
* Sign in the user via popup.
*/
async function login() {
const result = await msalInstance.loginPopup({ scopes });
msalInstance.setActiveAccount(result.account);
return result.account;
}
/**
* Sign out the user.
*/
function logout() {
msalInstance.logoutPopup();
}
STAC API: コレクションと項目のクエリ
GeoCatalog STAC API は、地理空間データを検出およびクエリするためのエンドポイントを提供します。 すべての要求には、Authorizationから取得したベアラー トークンを含む ヘッダーが必要です。
コレクションの一覧表示
const API_VERSION = '2025-04-30-preview';
async function listCollections(accessToken, catalogUrl) {
const url = `${catalogUrl}/stac/collections?api-version=${API_VERSION}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Failed to list collections: ${response.statusText}`);
}
const data = await response.json();
return data.collections; // Array of STAC Collection objects
}
コレクション内のアイテムを一覧表示する
const API_VERSION = '2025-04-30-preview';
async function listItems(accessToken, catalogUrl, collectionId, limit = 10) {
const url = `${catalogUrl}/stac/collections/${collectionId}/items?limit=${limit}&api-version=${API_VERSION}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Failed to list items: ${response.statusText}`);
}
const data = await response.json();
return data.features; // Array of STAC Item objects
}
コレクション間で検索する
const API_VERSION = '2025-04-30-preview';
async function searchItems(accessToken, catalogUrl, searchParams) {
const url = `${catalogUrl}/stac/search?api-version=${API_VERSION}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(searchParams),
});
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
return await response.json();
}
// Example usage:
const results = await searchItems(token, catalogUrl, {
collections: ['my-collection'],
bbox: [-122.5, 37.5, -122.0, 38.0], // [west, south, east, north]
datetime: '2024-01-01/2024-12-31',
limit: 20,
});
タイル URL: マップの視覚化用の URL を作成する
GeoCatalog Tiler API は、ラスター データをマップ タイルとして提供します。 次のパターンでタイル URL を構築します。
1 つのアイテムのタイル
{catalogUrl}/data/collections/{collectionId}/items/{itemId}/tiles/{z}/{x}/{y}@1x.png
?api-version=2025-04-30-preview
&tileMatrixSetId=WebMercatorQuad
&assets=visual
タイル URL ビルダー関数
const API_VERSION = '2025-04-30-preview';
/**
* Build a tile URL template for a STAC item.
* Returns a URL with {z}/{x}/{y} placeholders for use with map libraries.
*/
function buildTileUrl(catalogUrl, collectionId, itemId, options = {}) {
const { assets = 'visual', colormap, rescale } = options;
const base = `${catalogUrl}/data/collections/${collectionId}/items/${itemId}/tiles/{z}/{x}/{y}@1x.png`;
const params = new URLSearchParams();
params.set('api-version', API_VERSION);
params.set('tileMatrixSetId', 'WebMercatorQuad');
params.set('assets', assets);
if (colormap) params.set('colormap_name', colormap);
if (rescale) params.set('rescale', rescale);
return `${base}?${params.toString()}`;
}
// Example usage:
const tileUrl = buildTileUrl(
'https://mygeocatalog.northcentralus.geocatalog.spatio.azure.com',
'aerial-imagery',
'image-001',
{ assets: 'visual' }
);
主要なタイル パラメーター
| パラメーター | 必須 | Description |
|---|---|---|
api-version |
イエス | API バージョン (2025-04-30-preview) |
tileMatrixSetId |
イエス | Web マップに WebMercatorQuad を使用する |
assets |
イエス | レンダリングするアセット名 (例: visual、 image) |
colormap_name |
いいえ | 名前付きカラーマップ (例: viridis、 terrain) |
rescale |
いいえ | スケーリングの値範囲 (例: 0,255) |
asset_bidx |
いいえ | バンド インデックス (例: RGB の image\|1,2,3 ) |
注
4 バンドの画像を含むコレクション (RGB + NIR を使用した NAIP など) の場合は、 asset_bidx=image|1,2,3 を使用して RGB バンドのみを選択します。 タイルは、4 つのバンドを PNG としてエンコードできません。
マップ統合: MapLibre GL を使用してタイルを表示する
MapLibre GL、Leaflet、OpenLayers などのマップ ライブラリでは、ラスター タイルを表示できます。 これらのライブラリはタイルを直接フェッチするため、タイルの要求に承認ヘッダーを追加することが重要です。
MapLibre GL の例
MapLibre GL には、ヘッダーを挿入するための transformRequest オプションが用意されています。
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
// Store the current access token
let currentAccessToken = null;
function initializeMap(containerId, accessToken) {
currentAccessToken = accessToken;
const map = new maplibregl.Map({
container: containerId,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors',
},
},
layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
},
center: [0, 0],
zoom: 2,
// Add authorization header to tile requests
transformRequest: (url, resourceType) => {
// Only add auth for GeoCatalog tile requests
if (url.includes('geocatalog.spatio.azure.com') && currentAccessToken) {
return {
url,
headers: { 'Authorization': `Bearer ${currentAccessToken}` },
};
}
return { url };
},
});
return map;
}
function addTileLayer(map, tileUrl, bounds) {
// Remove existing layer and source if present
if (map.getLayer('data-layer')) {
map.removeLayer('data-layer');
}
if (map.getSource('data-tiles')) {
map.removeSource('data-tiles');
}
// Add tile source
map.addSource('data-tiles', {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
minzoom: 10, // Many aerial collections require zoom 10+
maxzoom: 18,
});
// Add tile layer
map.addLayer({
id: 'data-layer',
type: 'raster',
source: 'data-tiles',
});
// Zoom to bounds [west, south, east, north]
if (bounds) {
map.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], { padding: 50 });
}
}
Important
transformRequest関数は、すべてのタイル要求に対して呼び出されます。
transformRequestアクセスできる変数にアクセス トークンを格納し、トークンの更新時に更新します。
モザイク タイル: コレクション全体の画像を表示する
コレクション内のすべてのアイテムをシームレス レイヤーとして表示するには、モザイク検索を登録し、返された検索 ID を使用します。
const API_VERSION = '2025-04-30-preview';
/**
* Register a mosaic search for a collection.
* Returns a search ID that can be used to fetch mosaic tiles.
*/
async function registerMosaic(catalogUrl, collectionId, accessToken) {
const url = `${catalogUrl}/data/mosaic/register?api-version=${API_VERSION}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
collections: [collectionId],
}),
});
if (!response.ok) {
throw new Error(`Failed to register mosaic: ${response.statusText}`);
}
const data = await response.json();
// Note: API returns 'searchid' (lowercase), not 'searchId'
return data.searchid;
}
/**
* Build a mosaic tile URL template.
*/
function buildMosaicTileUrl(catalogUrl, searchId, collectionId, options = {}) {
const { assets = 'visual' } = options;
const base = `${catalogUrl}/data/mosaic/${searchId}/tiles/{z}/{x}/{y}@1x.png`;
const params = new URLSearchParams();
params.set('api-version', API_VERSION);
params.set('tileMatrixSetId', 'WebMercatorQuad');
params.set('collection', collectionId);
params.set('assets', assets);
return `${base}?${params.toString()}`;
}
SAS トークン: 生の資産をダウンロードする
SAS API は、Azure Blob Storage から直接生の資産ファイル (GeoTIF、COG、およびその他のファイル) をダウンロードするための時間制限付きトークンを提供します。 レンダリングされたタイルではなく元のソース ファイルが必要な場合は、このオプションを使用します。
Important
SAS トークンは、ブラウザー アプリケーション でのみダウンロード を有効にします。 Azure Blob Storage CORS ポリシーにより、ブラウザーは JavaScript fetch()経由で BLOB データを読み取ることはできません。 「 ブラウザーの制限事項」セクションを 参照してください。
SAS トークンを取得する
const API_VERSION = '2025-04-30-preview';
/**
* Get a SAS token for accessing assets in a collection.
* Returns a token string that can be appended to asset URLs.
*/
async function getCollectionSasToken(accessToken, catalogUrl, collectionId) {
const url = `${catalogUrl}/sas/token/${collectionId}?api-version=${API_VERSION}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Failed to get SAS token: ${response.statusText}`);
}
const data = await response.json();
return data.token; // SAS token string
}
署名付きダウンロード URL を作成する
/**
* Build a signed URL for downloading an asset.
* Appends the SAS token to the asset's href.
*/
function buildSignedAssetUrl(assetHref, sasToken) {
const separator = assetHref.includes('?') ? '&' : '?';
return `${assetHref}${separator}${sasToken}`;
}
// Example usage:
const sasToken = await getCollectionSasToken(accessToken, catalogUrl, 'my-collection');
const assetHref = item.assets['visual'].href;
const signedUrl = buildSignedAssetUrl(assetHref, sasToken);
ファイルのダウンロードをトリガーする
/**
* Trigger a browser download for an asset file.
* Works by creating a temporary anchor element.
*/
function downloadAsset(signedUrl, filename) {
const link = document.createElement('a');
link.href = signedUrl;
link.download = filename || 'download';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Example: Download an asset
downloadAsset(signedUrl, 'aerial-image.tif');
ブラウザーの制限事項
SAS トークンは、ブラウザーとサーバー側のコードで動作が異なります。
| 使用事例 | Browser | Server-side |
|---|---|---|
| ファイルをダウンロードする (ユーザーがリンクを選択) | ✅ が機能している | ✅ が機能している |
fetch() を使用して BLOB データを読み取る |
❌ CORS がブロックされました | ✅ が機能している |
| JavaScript で生ピクセルを処理する | ❌ 無理です | ✅ が機能している |
ナビゲーション (リンクをクリック) すると CORS がバイパスされるため、ブラウザーのダウンロードは機能します。 ただし、ストレージ アカウントにアプリケーションの配信元が CORS ポリシーに含まれていないため、Azure Blob Storage への fetch() 要求はブロックされます。
アプリケーションがブラウザーで生資産データを読み取って処理する必要がある場合は、サーバー側プロキシを実装します。
注
次のコードは、プロキシ パターンを示す簡略化された例です。 運用アプリケーションの場合、プロキシ エンドポイントは要求を認証し (たとえば、ユーザーの Bearer トークンを転送するか、セッション認証を使用して)、要求されたリソースへのアクセスがユーザーに承認されていることを検証する必要があります。
// ❌ Browser: This fails due to CORS
const response = await fetch(signedUrl);
const data = await response.arrayBuffer(); // Error!
// ✅ Browser: Call your backend instead
const response = await fetch('/api/proxy-asset', {
method: 'POST',
body: JSON.stringify({ collectionId, itemId, assetName })
});
const data = await response.json(); // Works!
バックエンドは、共有アクセス署名 (SAS) トークンを使用して BLOB を取得し、処理された結果を返すことが可能です。
開発に関する考慮事項
CORS サポート
GeoCatalog API には、 Access-Control-Allow-Origin: *を使用した完全な CORS サポートが含まれています。 ブラウザー ベースのアプリケーションは、開発中に http://localhost を含め、任意の配信元から GeoCatalog に直接要求できます。 プロキシや回避策は必要ありません。
API では CORS 要求で Authorization ヘッダーが許可されるため、認証された fetch() 呼び出しはブラウザーの JavaScript から直接機能します。
適切なデータ アクセス方法の選択
| メソッド | ブラウザー fetch() |
ブラウザーのダウンロード | Server-side | 最適な対象者 |
|---|---|---|---|---|
| Tiler API | ✅ が完全にサポートされている | ✅ はい | ✅ はい | マップの視覚化 |
| SAS トークン | ❌ CORS がブロックされました | ✅ はい | ✅ はい | 未加工ファイルのダウンロード |
Tiler API: マップに画像を表示するために使用します。 完全な CORS をサポートするレンダリングされた PNG タイルまたは WebP タイルを返します。 「タイル URL」と「マップの統合」を参照してください。
SAS トークン: 元のソース ファイル (GeoTIF、COG) のダウンロードに使用します。 ブラウザーのダウンロードは機能しますが、
fetch()は Azure Blob Storage CORS ポリシーによってブロックされます。 詳細と回避策については 、SAS トークン を参照してください。
トークンの更新
通常、アクセス トークンの有効期限は 1 時間後です。 アプリケーションでは、次のようにする必要があります。
- 新しいトークンを取得して、401 エラーを処理します。
- 期限切れのトークンを自動的に更新する MSAL のサイレント トークン取得を使用します。
- マップの
transformRequestで使用されるトークン参照を更新します。
エラー処理
一般的なエラー シナリオを処理します。
| HTTP の状態 | 原因 | 解決策 |
|---|---|---|
| 401 | トークンの有効期限が切れているか無効です | アクセス トークンを更新する |
| 404 | アイテムまたはコレクションが見つかりません | ID が存在するかどうかを確認する |
| 424 | データ範囲外のタイル | 予期される - 適切に処理する |
トラブルシューティング
| エラー | 原因 | 解決策 |
|---|---|---|
| "AADSTS50011: 応答 URL の不一致" | コード内のリダイレクト URI が Microsoft Entra ID 登録と一致しない | 開発 URL ( http://localhost:3000 など) を SPA リダイレクト URI としてアプリ登録に追加します |
| "無効なスコープ" エラー | API スコープの代わりに GeoCatalog URL を使用する | スコープとして https://geocatalog.spatio.azure.com/.default を使用する |
| 401 タイル要求に対する未承認エラー | 認証ヘッダーを含まないマップ ライブラリ |
transformRequest (MapLibre) を使用してベアラー トークンを追加します。トークンが最新であることを確認します |
| タイルがベースマップと一致しない | 間違ったタイル マトリックス セット | Web メルカトルプロジェクションに tileMatrixSetId=WebMercatorQuad を使用する (EPSG:3857) |
| "イメージをデコードできませんでした" | 不適切な資産名、マルチバンド画像、またはデータ範囲外 | 有効な名前については item_assets を確認してください。RGB には asset_bidx=image\|1,2,3 を使用します。404/424 の外部カバレッジは予想されています。 |