ローカル発見系のプロダクトの成否は、たいてい一つの画面にかかっています。それがマップです。ユーザーは「今、自分の近くで何がある?」というシンプルな疑問に答えるためにあなたのアプリを開き、マップがその答えを示します。正しいピン、素早い絞り込み、そして検索エンジンが実際に読めるリスティング。これらを備えたマップを正しく作ることが、あらゆるイベントガイドの背後にある中核的なエンジニアリング作業です。
このガイドでは、都市イベントマップをゼロから作る流れを解説します。イベントデータのモデリング、会場の座標へのジオコーディング、マーカーのプロットとクラスタリング、カテゴリやエリアでの絞り込み、そしてリスティングを発見されやすくする方法です。ベースマップの描画方法はすでに理解している前提で進めます。まだの場合は、まずウェブサイトにインタラクティブマップを追加する方法のチュートリアルから始めて、イベント固有の部分についてはここに戻ってきてください。
会場を軸にイベントをモデリングする
イベントとは、ある場所で発生する時間の制約を持つ出来事です。マップに載るのはその「場所」なので、会場がデータモデルの起点になります。最小限のイベントレコードは次のようになります。
const event = {
id: 'evt_8471',
title: 'Late Night Jazz at The Vortex',
category: 'live-music',
venue: 'The Vortex Jazz Club, 11 Gillett Square, London N16 8AZ',
coords: null, // ジオコーディングで一度だけ埋める
neighbourhood: null, // リバースジオコーディングで一度だけ埋める
startsAt: '2026-07-09T20:30:00Z',
url: 'https://example.com/tickets/8471',
};
2つの null フィールドが重要です。マップを読み込むたびに会場をジオコーディングしたくはありません。イベントの作成時やインポート時に一度だけジオコーディングし、その後は座標を保存して恒久的なものとして扱います。
ステップ1: 各会場を座標にジオコーディングする
ジオコーディングは、人間向けの住所を、プロット可能な緯度・経度に変換します。会場の文字列をGeocoding APIに送信し、トップの結果から座標を読み取ります。
async function geocodeVenue(address) {
const url = new URL('https://api.mapatlas.eu/geocoding/v1/search');
url.searchParams.set('text', address);
url.searchParams.set('size', '1');
const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
const data = await res.json();
const top = data.features?.[0];
if (!top) return null;
const [lon, lat] = top.geometry.coordinates;
return { lat, lon, label: top.properties.label };
}
これは書き込み時、つまりイベントがシステムに入ってくるタイミングで実行し、返ってきた座標をレコードに保存します。正確なピンの位置がなぜ重要なのか、そして屋上の座標が人が実際に歩いて入るドアとどう違うのかについては、建物入口ジオコーディングで詳しく説明しています。
ステップ2: イベントをマーカーとしてプロットする
すべてのイベントに座標が付いていれば、プロットはループ処理になります。各カテゴリに固有の色を与えて、マップを一目で読めるようにしましょう。
const CATEGORY_COLORS = {
'live-music': '#E75480',
'theatre': '#006BA6',
'comedy': '#EE7C0E',
'food': '#16A34A',
'nightlife': '#7C3AED',
};
function plotEvents(map, events) {
events.forEach((evt) => {
if (!evt.coords) return;
const popup = new mapmetricsgl.Popup().setHTML(
`<strong>${evt.title}</strong><br>${new Date(evt.startsAt).toLocaleString()}` +
`<br><a href="${evt.url}">Tickets</a>`,
);
new mapmetricsgl.Marker({ color: CATEGORY_COLORS[evt.category] || '#0c3456' })
.setLngLat([evt.coords.lon, evt.coords.lat])
.setPopup(popup)
.addTo(map);
});
}
これは実際のプロダクトの裏側にあるパターンです。ライブな例としてOnlyHereがあります。ロンドンで楽しめることを日替わりで紹介するガイドで、すべての会場をジオコーディングし、その日のイベントをインタラクティブなMapAtlasマップにプロットします。ユーザーはフラットなリストをスクロールするのではなく、会場やエリアごとに何があるかを閲覧できます。
ステップ3: 街が埋まってきたらクラスタリングする
イベントが数件なら個別のマーカーで問題ありません。しかし都市全体のフィードは数件では済みません。おおよそ100〜200ピンを超えたら、イベントをGeoJSONソースに移し、マップにクラスタリングさせましょう。
map.addSource('events', {
type: 'geojson',
cluster: true,
clusterRadius: 50,
data: {
type: 'FeatureCollection',
features: events.filter((e) => e.coords).map((e) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [e.coords.lon, e.coords.lat] },
properties: { title: e.title, category: e.category },
})),
},
});
これで、街の中心部が賑わう夜でも、100個の重なり合ったピンではなく件数付きの単一バブルが表示され、ユーザーがズームインするとクラスタが分割されます。
ステップ4: カテゴリとエリアで絞り込む
絞り込みこそが、イベントマップをイベント「プロダクト」に変える部分です。全リストをメモリに保持して配列をフィルタリングし、手作業でマーカーを作り直すのではなく、絞り込んだ結果をマップのソースに渡します。
function applyFilters(map, allEvents, { category, neighbourhood }) {
const filtered = allEvents.filter((e) =>
(!category || e.category === category) &&
(!neighbourhood || e.neighbourhood === neighbourhood),
);
map.getSource('events').setData({
type: 'FeatureCollection',
features: filtered.filter((e) => e.coords).map((e) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [e.coords.lon, e.coords.lat] },
properties: { title: e.title },
})),
});
return filtered;
}
neighbourhood フィールドを埋めるには、各会場の座標を一度だけリバースジオコーディングし、行政エリア名を保存します。このたった一つの値が、エリアのドロップダウン、「このエリアで何がある」ビュー、そしてより整理されたリスティングページを支えます。しかも実行時に追加の検索を行う必要はありません。
ステップ5: リスティングを発見されやすくする
マップは、すでにあなたのアプリの中にいる人の疑問に答えます。しかし、検索エンジンやAIアシスタントに「今夜Shoreditchで何がある?」と尋ねている、はるかに大きな層には何も届きません。そうしたシステムが読むのはピクセルではなく構造化データです。
すべてのイベントを schema.org の Event フィールドでマークアップし、すでに持っている座標を再利用しましょう。
{
"@context": "https://schema.org",
"@type": "Event",
"name": "Late Night Jazz at The Vortex",
"startDate": "2026-07-09T20:30:00Z",
"location": {
"@type": "Place",
"name": "The Vortex Jazz Club",
"geo": { "@type": "GeoCoordinates", "latitude": 51.5462, "longitude": -0.0753 }
}
}
ピンを描く座標が、そのまま構造化データにも供給されます。つまり表示と発見が単一の信頼できる情報源から生まれるのです。これが、見た目の良いマップと、実際に見つけてもらえるプロダクトとの違いです。機械可読な位置情報がなぜAI検索での可視性を高めるのかについては、AI検索向けの地域別FAQのガイドをご覧ください。
まとめ
都市イベントマップは、5つの可動部品でできています。会場を軸にしたデータモデル、書き込み時のジオコーディング、色分けされたマーカー、規模に応じたクラスタリング、そして配列ベースの絞り込みです。これらすべてが、リスティングを発見されやすく保つ構造化データの上に載っています。これらの部品を一つの地理空間プラットフォームの上に構築すれば、座標はジオコーダーからマップ、そしてスキーマへとクリーンに流れ、検索の重複もデータのずれもなくなります。
MapAtlasは、ジオコーディング、リバースジオコーディング、場所検索、そしてマップ描画を提供し、そのすべてをEUホスティングでGDPR準拠のインフラ上で構築できます。Geocoding APIとSearch APIを試して、あなた自身の街をプロットし始めてください。
よくある質問
都市イベントマップを作るにはどんなデータが必要ですか?
最低限、各イベントには会場の住所、カテゴリ、開始時刻が必要です。イベントをマップ上のピンに変えるのは住所です。住所を一度ジオコーディングして緯度・経度を取得し、その座標を保存して、イベントを表示するたびに再利用します。チケットのリンク、画像、説明文などそれ以外の情報は、マーカーのポップアップ(popup)に付与するメタデータです。
会場の住所をマップの座標に変換するにはどうすればよいですか?
会場の住所文字列をジオコーディングAPIに渡し、レスポンスから緯度・経度を読み取ります。MapAtlas Geocoding APIでは、住所をテキストクエリとして https://api.mapatlas.eu/geocoding/v1/search に送信し、トップの結果の座標を使います。ジオコーディングはイベント作成時に一度だけ行い、ページを読み込むたびに実行しないでください。そうすればマップの表示は速いまま、API利用量も低く抑えられます。
イベントのマーカーはいくつまで表示すると重くなりますか?
個別のマーカーを描画する場合、ズームアウトした表示ではおおよそ100〜200ピンまでは滑らかに動きます。それを超えたら、GeoJSONソースのクラスタリング(clustering)に切り替えましょう。低ズーム時には近くのイベントをまとめて件数バブルとして表示し、ユーザーがズームインすると分割されます。数百件のリスティングを持つ都市全体のイベントフィードは、最初からクラスタリングを使うべきです。
ユーザーがカテゴリやエリアでイベントを絞り込めるようにするには?
イベントの全リストを配列(array)としてメモリに保持し、各項目にカテゴリとエリアを付与して、ユーザーの選択に応じてその配列をフィルタリングします。そのうえで、マーカーを破棄して作り直すのではなく、絞り込んだ結果でマップのソースを更新します。エリアごとの閲覧も同じ仕組みです。選択したエリア内に座標が入るイベント、またはリバースジオコーディングで同じ行政区名を持つイベントに絞り込みます。
イベントマップに構造化データが必要なのはなぜですか?
視覚的なマップは人間に役立ちますが、検索エンジンやAIアシスタントは構造化データを読み取ります。各イベントを schema.org のEventフィールド(会場名、座標、開始時刻を含む)でマークアップすることで、「今夜Shoreditchで何がある?」といった質問への回答にリスティングが表示されるようになります。マップにプロットするのと同じ座標が構造化データにも供給されるため、発見と表示が単一の信頼できる情報源から生まれます。

