東京都高校マップの実装を解説
Leafletで東京都の地図を表示し、高校データをマーカーに変換して、偏差値・学校種別・学校名から探せるようにする流れを紹介します。
東京都高校マップは、東京都内の高校を地図上で確認できるページです。マーカーの色は偏差値帯、形や枠線は学校種別や高校募集の有無を表し、クリックすると学科、偏差値、所在地を確認できます。地図が表示できない場合も、ページ下部のHTML一覧から学校情報を確認できます。
地図ページは、PHPでサイト共通のヘッダーとフッターを読み込み、学校データをschools.jsonから取得しています。学校一覧や目的・出典・注意事項はサーバー側でHTMLとして出力し、地図と検索・絞り込みの操作をJavaScriptで追加しています。地図ライブラリにはLeafletを使い、背景地図にはOpenStreetMapのタイルを利用しています。ページ内ナビゲーションには地図と区・市町村別一覧へのリンクを置き、スクロール時はヘッダー下に吸着する構成です。
なお、掲載している偏差値や学校情報は進路選びの参考情報です。募集状況、学科、所在地、入試制度などは変更されることがあるため、最終的には各高校の公式サイトや東京都の公式資料を確認してください。
Leafletを読み込んで地図を表示する
Leafletは、ブラウザ上にインタラクティブな地図を表示するためのJavaScriptライブラリです。まずCSSとJavaScriptを読み込み、地図を表示するための空の要素を用意します。
<link rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<main>
<div id="map"></div>
</main>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js">
</script>LeafletのCSSを忘れると、ズームボタンやマーカーが正しく表示されません。地図本体には高さが必要なので、ページ側のCSSで#mapに高さを設定します。幅だけを指定しても地図領域の高さが0になり、何も表示されない点に注意します。
#map {
width: 90%;
height: 1000px;
margin: 0 auto;
}高さ1000pxはPCで学校を探しやすくするための設定です。実際のサイトでは、スマートフォンで見たときにも操作できるよう、画面幅に合わせた高さを検討します。地図の上に操作パネルを重ねるため、パネルには背景色と影を付けて地図と区別しています。
中心位置とズームを決める
地図を作成するときは、表示する要素のIDを渡し、最初に表示する中心座標とズームレベルを指定します。Leafletの座標は[緯度, 経度]の順です。
const map = L.map('map', {
center: [35.69, 139.55],
zoom: 11,
zoomControl: true
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);L.mapで地図を作成し、L.tileLayerで背景タイルを追加しています。タイルURLの{z}、{x}、{y}はLeafletが現在のズームと表示範囲に応じて置き換えます。地図データを利用するときは、提供元の利用条件と帰属表示を確認する必要があります。
高校データをオブジェクトで表す
地図上の学校を一つずつ個別に処理するのではなく、学校1校を一つのオブジェクトとして配列にまとめます。緯度と経度のほか、公立・私立の区別、所在地、学科ごとの偏差値を持たせています。
const schools = [
{
name: '日比谷高校',
lat: 35.676144,
lng: 139.740509,
type: 'public',
departments: [
{ name: '普通科', hensachi: 73 }
],
address: '千代田区永田町2-16-1'
},
{
name: '開成高校',
lat: 35.731831,
lng: 139.765305,
type: 'private',
departments: [
{ name: '普通科', hensachi: 78 }
],
address: '荒川区西日暮里4-2-4'
}
];departmentsを配列にしているのは、一つの高校に複数の学科やコースがあるためです。たとえば普通科と理数科で偏差値が異なる場合も、同じ学校の中に複数のオブジェクトを入れて表現できます。
高校募集がない学校には、必要な場合だけnoExam: trueを追加します。すべての学校に同じ値を持たせず、特別な状態だけを追加することで、データの見通しを保っています。
学科の最大偏差値を代表値にする
一つの学校に複数の学科があると、マーカーを一つ置くときの色や大きさを決める必要があります。そこで、学科配列から最大値を取り出し、学校を代表する偏差値として利用します。
schools.forEach(school => {
const maxHensachi = Math.max(
...school.departments.map(department => department.hensachi)
);
console.log(school.name, maxHensachi);
});日比谷高校 73
開成高校 78mapで学科だけの偏差値配列を作り、スプレッド構文...でMath.maxへ渡しています。ポップアップでは各学科の偏差値をすべて表示し、マーカーの色と半径だけは最大値を使う、という役割分担です。
偏差値帯を色に変換する
偏差値をそのままマーカーへ設定することはできないため、範囲と色の対応表を作ります。範囲を配列にしておくと、凡例の作成とマーカーの表示判定で同じ定義を再利用できます。
const colorRanges = [
{ min: 70, max: 100, color: '#e53e3e', label: '70以上', visible: true },
{ min: 65, max: 69, color: '#ed8936', label: '65〜69', visible: true },
{ min: 60, max: 64, color: '#ecc94b', label: '60〜64', visible: true },
{ min: 55, max: 59, color: '#48bb78', label: '55〜59', visible: true },
{ min: 50, max: 54, color: '#4299e1', label: '50〜54', visible: true },
{ min: 45, max: 49, color: '#9f7aea', label: '45〜49', visible: true },
{ min: 0, max: 44, color: '#a0aec0', label: '44以下', visible: true }
];
function getRange(hensachi) {
for (const range of colorRanges) {
if (hensachi >= range.min && hensachi <= range.max) {
return range;
}
}
return colorRanges[colorRanges.length - 1];
}上から順番に調べ、偏差値が含まれる範囲を返します。getColorはこの範囲のcolorを返し、getRangeは凡例の表示状態も参照できる範囲オブジェクトを返します。色と表示条件を同じ配列から取得することで、定義のずれを防いでいます。
function getRadius(hensachi) {
if (hensachi >= 70) return 14;
if (hensachi >= 65) return 13;
if (hensachi >= 60) return 12;
if (hensachi >= 55) return 11;
return 10;
}色だけでなく半径も変えると、地図上で情報を読み取りやすくなります。ただし、大きさの違いを正確なランキングと誤解させないよう、凡例や説明文で何を表しているかを明示します。
学校を円形マーカーにする
学校データを一件ずつ走査し、最大偏差値からマーカーの見た目を決めて地図へ追加します。LeafletのcircleMarkerは、ズームしても大きさが大きく変わりにくい円形マーカーです。
const markers = [];
const markerLayerGroup = L.layerGroup().addTo(map);
schools.forEach(school => {
const maxHensachi = Math.max(
...school.departments.map(d => d.hensachi)
);
const isPrivate = school.type === 'private';
const marker = L.circleMarker(
[school.lat, school.lng],
{
radius: getRadius(maxHensachi),
fillColor: getRange(maxHensachi).color,
color: isPrivate ? '#b7791f' : '#fff',
weight: isPrivate ? 2.5 : 2,
fillOpacity: 0.85
}
);
marker.schoolData = school;
marker.maxHensachi = maxHensachi;
markers.push(marker);
markerLayerGroup.addLayer(marker);
});marker.schoolDataに元の学校データを保存しておくと、後の検索やフィルターで対応する学校をすぐに参照できます。marker.maxHensachiも保存しておくことで、毎回学科配列を計算し直さずに済みます。
公立と私立の違いは、マーカーの枠線の色で表しています。地図上で色は偏差値に使っているため、学校種別まで同じ色で表すと意味が混ざります。複数の属性を表すときは、色、枠線、形など役割を分けると読みやすくなります。
クリックで詳細ポップアップを開く
マーカーをクリックしたときに、学校名、学校種別、学科別の偏差値、所在地、Googleマップへのリンクを表示します。学科配列をmapして、表の行を作ります。
const deptRows = school.departments.map(department =>
`<tr>
<td>${department.name}</td>
<td>${department.hensachi}</td>
</tr>`
).join('');
const popupContent = `
<div class="school-popup">
<h3>${school.name}</h3>
<table>${deptRows}</table>
<div class="address">${school.address}</div>
</div>`;
marker.bindPopup(popupContent, { maxWidth: 300 });表示する学科の数が学校ごとに異なっていても、配列から行を生成するため同じ処理で対応できます。実際のページでは、公立・私立のバッジや「高校募集なし」のバッジも条件に応じて追加しています。
ポップアップの最後には、住所と学校名をURLエンコードしてGoogleマップ検索へのリンクを作ります。外部ページを新しいタブで開くリンクには、target="_blank"とrel="noopener noreferrer"を設定しています。
ツールチップで概要をすぐに見る
ポップアップを開かなくても、マーカーにマウスを合わせれば学校名、種別、最大偏差値が分かるようにしています。詳細はポップアップ、概要はツールチップという使い分けです。
marker.bindTooltip(
`<b>${school.name}</b><br>偏差値 ${maxHensachi}`,
{ direction: 'top', offset: [0, -8] }
);このように一つのマーカーへ複数の操作を設定できます。スマートフォンではホバーが使えないため、タップで開くポップアップにも必要な情報を含めることが大切です。
公立・私立を切り替える
フィルターの状態は、学校データを書き換えずに別のオブジェクトで管理します。公立と私立のどちらを表示するかをtrueとfalseで持ち、条件が変わるたびに全マーカーの表示を再計算します。
const typeFilter = {
public: true,
private: true
};
function togglePublic() {
typeFilter.public = !typeFilter.public;
filterMarkers();
}
function togglePrivate() {
typeFilter.private = !typeFilter.private;
filterMarkers();
}データ配列から学校を削除せず、レイヤーグループからマーカーを外すだけにしているのがポイントです。再表示するときは同じマーカーをグループへ戻せるため、フィルターを何度切り替えても元データを失いません。
検索、偏差値帯、募集状況を一つにまとめる
検索欄、学校種別、偏差値帯、高校募集の有無をそれぞれ別の関数で処理すると、条件の組み合わせで表示がずれることがあります。そこで、すべてのマーカーを一つのfilterMarkersで調べ、すべての条件を満たすものだけを表示します。
function filterMarkers() {
const input = document.getElementById('searchInput');
const query = input.value.trim().toLowerCase();
let visibleCount = 0;
markers.forEach(marker => {
const school = marker.schoolData;
const rangeVisible = getRange(marker.maxHensachi).visible;
const typeVisible = typeFilter[school.type];
const examVisible = !(hideNoExam && school.noExam === true);
const matchesSearch = query === ''
|| school.name.toLowerCase().includes(query)
|| school.address.toLowerCase().includes(query)
|| school.departments.some(department =>
department.name.toLowerCase().includes(query)
);
const visible = rangeVisible && typeVisible
&& examVisible && matchesSearch;
if (visible) {
if (!markerLayerGroup.hasLayer(marker)) {
markerLayerGroup.addLayer(marker);
}
visibleCount++;
} else if (markerLayerGroup.hasLayer(marker)) {
markerLayerGroup.removeLayer(marker);
}
});
document.getElementById('searchHint').textContent =
`${visibleCount} / ${schools.length} 校表示中`;
}検索対象を学校名だけに限定せず、学科名と住所にも広げています。たとえば「情報」「文京区」のような入力でも候補を探せます。空文字列なら検索条件を無効にし、ほかのフィルターだけで表示します。
hideNoExamは、高校受験できない学校を除外するチェックボックスの状態です。学校データにnoExam: trueがある場合だけ除外されるため、通常の学校はそのまま表示されます。
凡例をクリックできる操作パネルにする
偏差値帯の凡例は、単なる説明ではなくクリックで表示を切り替えられる操作部品にしています。LeafletのL.controlを使うと、地図の四隅へ独自のHTMLパネルを配置できます。
const legend = L.control({ position: 'bottomright' });
legend.onAdd = function() {
const div = L.DomUtil.create('div', 'legend');
div.innerHTML = '<h4>偏差値(クリックで表示切替)</h4>';
colorRanges.forEach((range, index) => {
const item = L.DomUtil.create('div', 'legend-item', div);
item.innerHTML = `
<span class="legend-circle"
style="background:${range.color}"></span>
<span>${range.label}</span>`;
item.addEventListener('click', () => {
range.visible = !range.visible;
item.classList.toggle('disabled');
filterMarkers();
});
});
L.DomEvent.disableClickPropagation(div);
return div;
};
legend.addTo(map);地図をドラッグしたいのにパネルをクリックしただけで地図が動くと使いにくいため、disableClickPropagationでパネル内のクリックが地図へ伝わらないようにしています。凡例を押すとrange.visibleが反転し、同じfilterMarkersを呼び出します。
凡例には、現在の条件で表示できる学校数も表示しています。表示数を更新する処理でも学校種別と募集状況を確認し、地図上の表示と数字が一致するようにします。
Leafletのコントロールで検索欄を配置する
検索欄も地図の右上に配置するLeafletコントロールとして作成します。入力のたびにfilterMarkersを呼び出すと、送信ボタンなしで結果が更新されます。
const searchControl = L.control({ position: 'topright' });
searchControl.onAdd = function() {
const div = L.DomUtil.create('div', 'search-box');
div.innerHTML = `
<input type="text" id="searchInput"
placeholder="高校名・学科・住所で検索...">
<div class="hint" id="searchHint">
${schools.length} / ${schools.length} 校表示中
</div>`;
L.DomEvent.disableClickPropagation(div);
return div;
};
searchControl.addTo(map);
setTimeout(() => {
document.getElementById('searchInput')
.addEventListener('input', filterMarkers);
}, 100);コントロールをaddToしたあとで入力要素を取得しています。作成前にgetElementByIdすると要素が存在せず、イベントを登録できません。実際のページでは、コントロール追加後に少し待ってからイベントを登録しています。
情報パネルとデータの件数
地図の左上には、公立・私立・全体の掲載件数と、マーカーをクリックすると詳細を見られることを表示しています。件数は固定値を手入力せず、学校配列から計算します。検索エンジンやJavaScriptを利用できない環境でも主要情報を読めるよう、同じデータを通常のHTML一覧にも出力します。
const publicCount = schools.filter(
school => school.type === 'public'
).length;
const privateCount = schools.filter(
school => school.type === 'private'
).length;
const total = schools.length;
console.log(`公立 ${publicCount}校 / 私立 ${privateCount}校`);
console.log(`合計 ${total}校`);公立 166校 / 私立 137校
合計 303校学校データを追加・削除したときも、件数表示が自動的に変わります。実際の表示件数はデータの更新によって変わるため、上の数値は実装例の出力イメージです。
地図ページを確認するときのポイント
地図は、画面が表示されただけでは機能を十分に確認できません。次のようにデータと操作を組み合わせて確認します。
・地図の中心が東京都内になっている
・ズームイン、ズームアウト、ドラッグができる
・公立だけ、私立だけの表示に切り替えられる
・偏差値帯の凡例を押すと該当マーカーが消える
・高校名、学科名、住所で検索できる
・複数の学科を持つ学校のポップアップが崩れない
・高校募集なしの学校を除外できる
・マーカーのクリックで詳細が開く
・Googleマップへのリンクが正しい学校を検索する
・公式情報を確認する注意書きが表示されている検索や表示切り替えをしたあとに別の条件を変更し、条件が組み合わさっても正しく表示されるかを確かめます。たとえば「私立だけ」にしたあと「60〜64」を選び、さらに住所を入力するケースです。フィルターごとに別の表示リストを持たず、毎回すべての条件を評価する構造にしておくと、組み合わせの不具合を減らせます。
まとめ
東京都高校マップは、次の役割に分けて実装しています。
学校データを配列で管理する
↓
Leafletで地図と背景タイルを初期化する
↓
学校ごとに偏差値帯とマーカーを作る
↓
ポップアップとツールチップに詳細を入れる
↓
検索・学校種別・偏差値帯・募集状況を判定する
↓
レイヤーグループへ追加/削除して表示を更新する地図アプリでは、座標データと画面表示を分け、フィルター状態を独立して管理することが重要です。さらに、検索向けの説明文や学校一覧をサーバー側でHTMLとして返し、JavaScriptは地図操作の拡張に限定すると、クロールと利用者の両方に安定したページになります。この考え方は、高校マップだけでなく、避難所、AED、公園、店舗など、位置情報を持つさまざまな情報ページへ応用できます。
実際の操作を確認したい場合は、東京都高校マップをご覧ください。進路選びに利用する際は、必ず各高校の公式情報もあわせて確認してください。