Skip to content

Commit bb153b5

Browse files
committed
Initial commit
0 parents  commit bb153b5

32 files changed

+5347
-0
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MAPBOX_ACCESS_TOKEN=YOUR_MAPBOX_ACCESS_TOKEN

.github/workflows/deploy.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Install mise and tools
25+
uses: jdx/mise-action@v2
26+
27+
- name: Get pnpm store directory
28+
shell: bash
29+
run: |
30+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
31+
32+
- name: Setup pnpm cache
33+
uses: actions/cache@v4
34+
with:
35+
path: ${{ env.STORE_PATH }}
36+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
37+
restore-keys: |
38+
${{ runner.os }}-pnpm-store-
39+
40+
- name: Install dependencies
41+
run: pnpm install
42+
43+
- name: Build
44+
run: pnpm build
45+
env:
46+
MAPBOX_ACCESS_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN }}
47+
48+
- name: Upload artifact
49+
uses: actions/upload-pages-artifact@v3
50+
with:
51+
path: ./out
52+
53+
deploy:
54+
environment:
55+
name: github-pages
56+
url: ${{ steps.deployment.outputs.page_url }}
57+
runs-on: ubuntu-latest
58+
needs: build
59+
steps:
60+
- name: Deploy to GitHub Pages
61+
id: deployment
62+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
42+
\n!.env.example

.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"cSpell.words": [
3+
"EPSG",
4+
"eufo",
5+
"bbox",
6+
"mapboxgl",
7+
"Mapbox"
8+
]
9+
}

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Mapbox Demo with Next.js
2+
3+
Next.js と Mapbox GL JS を使用した地図アプリケーションのデモプロジェクトです。
4+
デプロイ先は GitHub Pages です。
5+
6+
## 概要
7+
8+
このプロジェクトは、Next.js (PageRouter/SSG) 上で Mapbox を動作させ、美しくインタラクティブな地図機能を提供するデモアプリです。
9+
10+
## 技術スタック
11+
12+
- **Framework**: Next.js (Page Router, SSG export mode)
13+
- **Map Library**: Mapbox GL JS (react-map-gl)
14+
- **Styling**: Tailwind CSS 4 + tailwind-variants (tv)
15+
- **Language**: TypeScript
16+
- **Runtime**: Node.js 24 (required)
17+
- **Package Manager**: pnpm >=10 (npm/yarn/bun forbidden)
18+
- **Icons**: lucide-react
19+
- **Notifications**: sonner (toast)
20+
21+
## 機能
22+
23+
- **森林マップ (WMS) の表示**: [EU Forest Observatory](https://forest.jrc.ec.europa.eu/en/activities/forest-observatory/) が提供する WMS (Web Mapping Service) を利用し、2020年の森林タイプ分類マップ (`gft2020_v1`) を表示します。
24+
- **ベースマップ切り替え**: Mapbox の衛星写真 (`mapbox://styles/mapbox/satellite-v9`) をベースマップとして使用します。
25+
- **レイヤー透過度調整**: 森林マップレイヤーの透過度をスライダーで調整可能です。
26+
- **座標表示**: クリックした地点の経度・緯度を表示します(デバッグ用)。
27+
28+
## データソース
29+
30+
本デモでは、以下の外部データサービスを使用しています。
31+
32+
- **EU Forest Observatory (Public JRC API)**
33+
- **Service**: WMS 1.3.0
34+
- **Layer**: `gft2020_v1` (Global map of forest types 2020 v1)
35+
- **Details**: [WMS仕様書](./docs/wms_specification.md) を参照してください。
36+
37+
## セットアップ
38+
39+
### 前提条件
40+
41+
- **mise**: 環境管理ツール (Node.js, pnpm のバージョン管理に使用)
42+
- Mapbox アカウントとアクセストークン
43+
44+
### 1. 環境構築 (mise)
45+
46+
```bash
47+
mise install
48+
```
49+
50+
### 2. プロジェクトの作成 (未実施の場合)
51+
52+
```bash
53+
pnpm create next-app . --typescript --tailwind --eslint
54+
```
55+
56+
### 3. 依存関係のインストール
57+
58+
```bash
59+
pnpm install react-map-gl mapbox-gl
60+
```
61+
62+
### 3. 環境変数の設定
63+
64+
`.env.local` ファイルを作成し、Mapbox のアクセストークンを設定してください。
65+
66+
```bash
67+
MAPBOX_ACCESS_TOKEN=your_access_token_here
68+
```
69+
70+
### 4. 開発サーバーの起動
71+
72+
```bash
73+
pnpm dev
74+
```
75+
76+
## Mapbox トークンのセキュリティ
77+
78+
Mapbox を使用する際は、トークンのセキュリティ管理が重要です。特に SSG (Static Site Generation) や SPA (Single Page Application) では、トークンがブラウザで公開されるため、適切な設定が必要です。
79+
80+
### 1. トークンの種類と使い分け
81+
82+
- **Public Token (`pk.xxxx...`)**:
83+
- **用途**: フロントエンド (Mapbox GL JS) での地図表示。
84+
- **公開範囲**: ブラウザに配信されるため、**公開されます**
85+
- **対策**: **URL制限 (Allowed URLs)** を設定し、許可されたドメイン以外での利用を防ぎます。
86+
87+
- **Secret Token (`sk.xxxx...`)**:
88+
- **用途**: バックエンド (Python等) での Geocoding API, Directions API 利用や、タイルのアップロード。
89+
- **公開範囲**: **絶対に公開してはいけません**
90+
- **対策**: `.env` で管理し、クライアントサイドには一切含めないでください。
91+
92+
### 2. URL制限の設定 (必須)
93+
94+
Public Token を保護するため、Mapbox アカウント設定で **Allowed URLs** を設定してください。
95+
96+
1. [Mapbox Account Dashboard](https://account.mapbox.com/) にログインします。
97+
2. 対象の Public Token の設定 ("Configure") を開きます。
98+
3. **Allowed URLs** に以下のドメインを追加します。
99+
- `http://localhost:3000/` (開発用)
100+
- `https://your-production-domain.com/` (本番用)
101+
102+
これにより、万が一トークンが漏洩しても、許可されたドメイン以外からは使用できなくなります。

docs/demo.png

7.75 MB
Loading

docs/index.html

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Mapbox WMS Demo</title>
6+
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
7+
<link href="https://api.mapbox.com/mapbox-gl-js/v3.9.4/mapbox-gl.css" rel="stylesheet">
8+
<script src="https://api.mapbox.com/mapbox-gl-js/v3.9.4/mapbox-gl.js"></script>
9+
<style>
10+
body { margin: 0; padding: 0; }
11+
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
12+
13+
.map-overlay {
14+
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
15+
position: absolute;
16+
width: 200px;
17+
top: 0;
18+
left: 0;
19+
padding: 10px;
20+
}
21+
22+
.map-overlay-inner {
23+
background-color: #fff;
24+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
25+
border-radius: 3px;
26+
padding: 10px;
27+
margin-bottom: 10px;
28+
}
29+
30+
.map-overlay-inner label {
31+
display: block;
32+
margin: 0 0 10px;
33+
}
34+
35+
.map-overlay-inner input {
36+
background-color: transparent;
37+
display: inline-block;
38+
width: 100%;
39+
position: relative;
40+
margin: 0;
41+
cursor: ew-resize;
42+
}
43+
</style>
44+
</head>
45+
<body>
46+
47+
<div id="map"></div>
48+
<div class="map-overlay top">
49+
<div class="map-overlay-inner">
50+
<label>森林レイヤー (2020): <span id="forest-opacity-val">60%</span></label>
51+
<input id="forest-slider" type="range" min="0" max="100" step="1" value="60">
52+
<hr>
53+
<label>ベースマップ (衛星写真): <span id="mapbox-opacity-val">100%</span></label>
54+
<input id="mapbox-slider" type="range" min="0" max="100" step="1" value="100">
55+
</div>
56+
</div>
57+
58+
<script>
59+
// IMPORTANT: Please replace this with your own Mapbox Access Token
60+
// or it will fall back to a public token/error.
61+
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
62+
63+
const map = new mapboxgl.Map({
64+
container: "map",
65+
style: "mapbox://styles/mapbox/satellite-v9", // 衛星写真
66+
center: [-79.957141, -2.024311], // [経度, 緯度]
67+
zoom: 17
68+
});
69+
70+
map.on("load", () => {
71+
// ソースの追加
72+
map.addSource("eufo-wms-source", {
73+
type: "raster",
74+
tiles: [
75+
"https://ies-ows.jrc.ec.europa.eu/iforce/gft2020/wms.py?" +
76+
"SERVICE=WMS&" +
77+
"VERSION=1.3.0&" +
78+
"REQUEST=GetMap&" +
79+
"LAYERS=gft2020_v1&" +
80+
"STYLES=&" +
81+
"FORMAT=image/png&" +
82+
"TRANSPARENT=true&" +
83+
"WIDTH=512&" +
84+
"HEIGHT=512&" +
85+
"CRS=EPSG:3857&" +
86+
"BBOX={bbox-epsg-3857}"
87+
],
88+
tileSize: 512
89+
});
90+
91+
// レイヤーの追加
92+
map.addLayer({
93+
id: "eufo-wms-layer",
94+
type: "raster",
95+
source: "eufo-wms-source",
96+
paint: {
97+
"raster-opacity": 0.6,
98+
"raster-fade-duration": 0
99+
}
100+
});
101+
102+
// 森林レイヤーのスライダー
103+
const forestSlider = document.getElementById("forest-slider");
104+
forestSlider.addEventListener("input", (e) => {
105+
const value = parseFloat(e.target.value) / 100;
106+
map.setPaintProperty("eufo-wms-layer", "raster-opacity", value);
107+
document.getElementById(
108+
"forest-opacity-val"
109+
).textContent = `${e.target.value}%`;
110+
});
111+
112+
// Mapbox ベースマップ(衛星写真)のスライダー
113+
const mapboxSlider = document.getElementById("mapbox-slider");
114+
mapboxSlider.addEventListener("input", (e) => {
115+
const value = parseFloat(e.target.value) / 100;
116+
// Mapbox公式スタイル 'satellite-v9' 内のレイヤーIDは通常 'satellite' です
117+
if (map.getLayer("satellite")) {
118+
map.setPaintProperty("satellite", "raster-opacity", value);
119+
}
120+
document.getElementById(
121+
"mapbox-opacity-val"
122+
).textContent = `${e.target.value}%`;
123+
});
124+
});
125+
126+
// 座標表示のデバッグ
127+
map.on("click", (e) => {
128+
window.alert(
129+
`クリック座標: ${e.lngLat.lng.toFixed(6)}, ${e.lngLat.lat.toFixed(6)}`
130+
);
131+
});
132+
</script>
133+
134+
</body>
135+
</html>

0 commit comments

Comments
 (0)