地図データの活用において、単なる平面表示から一歩進んだ3D表現や統計分析は、データの価値を最大限に引き出す重要な手法です。
今回は、弊社が保有する精密な道路標高データを使用して、3D地図表示と統計分析の両方を実践してみました。データ整備からフロントエンド実装まで一貫して対応できる技術力と、データベース化された高精度な地図データの活用例をご紹介します。

1.道路標高データを使用して道路を3D表示してみよう
1-1.表示場所の選定
今回の3D地図表示では、東京都目黒区にある大橋JCT(ジャンクション)を選択しました。この場所を選んだ理由は、その特徴的な立体構造にあります。
大橋JCTの特徴:
・首都高速3号渋谷線と中央環状線が交差する複雑な立体交差
・地上から地下深くまで、らせん状に巻き込むような独特の形状
データ整備の技術力:
特に注目すべきは、GPSの電波が届かない地下部分でも、弊社の道路標高データは精密に整備されていることです。一般的にGPSが使用できない地下構造物の測量は困難を伴いますが、弊社では高精度な測量技術により、このような複雑な立体構造も正確にデータ化しています。
1-2. 道路標高データからGeoJSONデータを作成
弊社の道路標高データはポイント形式で整備されていますが、今回は3D描画が目的のためラインデータに変換します。
変換時のポイント:
・Z座標に標高値を格納
・色分けと幅変更を可能にするため、道路ネットワークデータから道路表示種別(高速道路、国道等)を取得
データ構造の工夫:
弊社の道路標高データは非常にポイント数が多く、高精度な測量により収集されています。一般的な地図データでは標高情報を属性として別途管理することが多いですが、今回は以下の理由でZ座標に直接格納しています:
・描画パフォーマンスの向上: 属性参照の処理を省略し、直接座標として利用
・メモリ効率: データ構造の簡素化により処理速度を向上
・高精度データの活用: ポイント密度の高いデータを最大限活用し、詳細な高低差まで表現
この設計により、大橋JCTのような複雑な立体構造でも、地上から地下深くまでの詳細な標高変化を正確に表現することが可能になっています。
完成したGeoJSONデータの例:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": "road_001",
"roadclass": 11
},
"geometry": {
"type": "LineString",
"coordinates": [
[139.68720, 35.65056, 45.2],
[139.68725, 35.65060, 45.8],
[139.68730, 35.65065, 46.1]
]
}
}
]
}
配信方法についての補足:今回は狭い範囲での3D描画のため、GeoJSONデータをローカルに配置していますが、描画範囲が広い場合はベクタータイル化してサーバー配信する方が効率的です。
1-3. deck.glを使用して道路標高データを3D表示
ベース地図の選択:
ベース地図には弊社のベクター地図を使用します。
・MapFan API サービス概要
・ベクター地図 IF仕様書
実装のポイント:
ReactとTypeScriptによるコンポーネントベースの開発環境で、deck.glのPathLayerを使用し、WebGLベースの高性能3D描画で大量の高精度道路標高データを効率的に処理します。標高をZ座標として直接表現することで3D描画のパフォーマンスを最適化し、リアルタイムな3D地図表示を実現しています。
実装方法:
ベース地図の上にGeoJSON形式で作成した3D道路データをレイヤーとして重ね合わせて表示します。MapLibre GLによる2Dベクター地図の上に、deck.glの3Dレイヤーを重ねることで、平面的な地図情報と立体的な道路標高情報を同時に表示できます。これにより、地形の起伏や道路の高低差を直感的に把握することが可能になります。
完成したコード例:
import React, { useEffect, useState } from "react";
import { DeckGL } from '@deck.gl/react';
import { PathLayer } from "@deck.gl/layers";
import Map from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
// 初期表示設定
const INITIAL_VIEW_STATE = {
latitude: 35.65056,
longitude: 139.68720,
zoom: 16,
minZoom: 5,
pitch: 60,
bearing: 0,
dragRotate: false,
minPitch: 0,
maxPitch: 90
};
// 道路クラスごとの色設定
const getRoadColor = (roadClass: number) => {
if ([1, 2, 3, 4, 5].includes(roadClass)) return [30, 144, 255]; // 国道、県道、市道
if ([5, 6, 7].includes(roadClass)) return [0, 191, 255]; // その他道路
if ([8, 9].includes(roadClass)) return [135, 206, 235]; // 細街路
if ([11, 101, 102, 103, 104, 105, 106, 107, 108].includes(roadClass)) return [50, 200, 50]; // 高速道路、有料道路
return [0, 0, 0];
};
// 道路クラスごとの幅設定
const getRoadWidth = (roadClass: number) => {
if ([1, 2, 3, 4, 5].includes(roadClass)) return 7; // 国道、県道、市道
if ([5, 6, 7].includes(roadClass)) return 5; // その他道路
if ([8, 9].includes(roadClass)) return 2; // 細街路
if ([11, 101, 102, 103, 104, 105, 106, 107, 108].includes(roadClass)) return 11; // 高速道路、有料道路
return 2;
};
// GeoJSON読み込み関数
const fetchGeoJSON = async (url: string) => {
const response = await fetch(url);
return response.json();
};
// パスデータの型定義
interface PathData {
id: any;
path: number[][];
properties: any;
}
function App() {
const [viewState, setViewState] = useState(INITIAL_VIEW_STATE);
const [paths, setPaths] = useState<PathData[]>([]);
// GeoJSONデータの読み込み
useEffect(() => {
fetchGeoJSON('/ohashi_lines3d.geojson')
.then((geojson) => {
const convertedPaths = geojson.features.map((feature: any) => ({
id: feature.properties.id,
path: feature.geometry.coordinates,
properties: feature.properties,
}));
setPaths(convertedPaths);
})
.catch((error) => {
console.error('GeoJSONの読み込みに失敗しました:', error);
});
}, []);
// レイヤー設定
const layers = [
new PathLayer({
id: 'path-layer',
data: paths,
getPath: (d) => d.path,
getColor: (d) => getRoadColor(d.properties?.roadclass),
getWidth: (d) => getRoadWidth(d.properties?.roadclass),
widthMinPixels: 1,
widthMaxPixels: 10,
getElevation: (d) => d.path.map((p) => p[2] || 0),
elevationScale: 10,
pickable: true,
rounded: true,
coordinateSystem: 1,
visible: viewState.zoom >= 12,
}),
];
return (
<div style={{ position: "relative", width: "100vw", height: "100vh" }}>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
viewState={viewState}
layers={layers}
onViewStateChange={({ viewState }) => setViewState(viewState)}
>
<Map
reuseMaps
mapStyle="https://api-vectormap.mapfan.com/v2/styles/mapfan_light_nologo?api_key=API_KEY"
styleDiffing={false}
{...viewState}
onMove={(evt) => setViewState(evt.viewState)}
/>
</DeckGL>
{/* 座標・ズーム情報表示 */}
<div
style={{
position: "absolute",
top: 10,
left: 10,
padding: "8px 12px",
background: "rgba(255, 255, 255, 0.85)",
fontSize: "14px",
borderRadius: "8px",
zIndex: 100,
}}
>
<div><strong>Zoom:</strong> {viewState.zoom.toFixed(2)}</div>
<div><strong>Lat:</strong> {viewState.latitude.toFixed(5)}</div>
<div><strong>Lng:</strong> {viewState.longitude.toFixed(5)}</div>
</div>
</div>
);
}
export default App;
1-4.出来上がった道路標高データの参照:
大橋JCTの地上から地下へと螺旋状に潜るトンネル構造と多層にわたる複雑な立体交差が3Dで表現することが出来ました。平面地図では把握困難な道路の上下関係や螺旋状の接続構造を直感的に理解できるようになります。また、地下に隠れたトンネル内の道路ネットワークも可視化され、全体の構造を立体的に把握できます。
2.道路標高データを使用して統計分析してみよう
2-1.データベース化による統計処理の活用
弊社の道路標高データはデータベース化されているため、SQLを使用した統計分析が可能です。今回は「自動車が通行可能な道路で、日本で最も標高の高い場所」を調べてみました。
※弊社の道路標高データは自動車が走行可能な道路のみを整備対象としております
調査SQL:
SELECT *
FROM map.road_elevation
WHERE altitude = (SELECT MAX(altitude) FROM map.road_elevation);
2-2.調査結果
日本最高標高の道路は、長野県道84号乗鞍岳線の頂上でした。
この場所は日本一標高の高いバス停としても知られています。
※2025年9月1日現在、交通規制が実施されており、路線バス以外の一般車両は通行できません(長野県公式情報)
予想との比較:
個人的には富士山5合目が最高標高だと予想していましたが、結果は意外でした!データベース化されたデータならではの正確な分析結果を得ることができました。
まとめ
今回の実装を通じて、以下の技術的な強みを実証できました:
1.フルスタック対応: データ整備からフロントエンド実装まで一貫した開発
2.高精度データ: 精密に整備された道路標高データの活用
3.多様な活用方法: 3D表示から統計分析まで幅広い用途への対応
4.困難な環境での測量技術: GPSが届かない地下構造物も正確にデータ化
大橋JCTのような複雑な立体構造を3Dで可視化することで、データベース化された地図データは、単なる表示用途を超えて、様々な分析や洞察を提供できる貴重な資産であることを改めて実感しました。
今回使用した「道路標高データ」などの地図データベースにご興味をお持ちの方は、地図データベース (MapFan DB)をご参照ください。
地図データベース(MapFan DB)のお問い合わせはこちらから
執筆後記:
データ分析や結果の要約は日常業務で慣れ親しんでいますが、技術ブログの執筆は初挑戦でした。今回の記事構成は生成AIにサポートしてもらいながら作成しています!
kayo_chan
法人向けサービスに関するお問
い合わせはこちらから