1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTMLで律令国と城の地図を表示する

Posted at

律令国(日本の旧国)国境と城の位置を自由に表示したくなったのでやっていきます

プロジェクトの作成

何でもいいのですが何となくVue.jsとTypeScriptを使っていきます
vueは^3.5.18

npm create vue@latest

image.png

npm installしてついでにgitコミット

cd shiro-map
npm install
git init && git add -A && git commit -m "initial commit"

実行

npm run dev

http://localhost:5173/にアクセスして動作確認

image.png

地図を表示する

地図描画ライブラリのLeafletをインストールします
バージョンは ^1.9.4

npm install leaflet

App.vueを編集
divタグを配置してそこに地図を表示する

地図描画処理はLeafletProc.tsに書くことにしました

App.vue
<script setup lang="ts">
import "leaflet/dist/leaflet.css";
import { onMounted } from 'vue';
import { drawMap } from './LeafletProc';
onMounted( async () => { await drawMap("chizu"); });
</script>

<template>
  <div id="chizu"></div>
</template>

<style scoped>
#chizu {
  height: 500px;
  width: 500px;
}
</style>
LeafletProc.ts
import L from 'leaflet';

export const drawMap = async (elementId: string) => {
  const leafletMap = await createMap(elementId);
};

const createMap = async (elementId: string): L.Map => {
  // L.Mapオブジェクト生成
  const leafletMap = L.map(elementId, {
    center: [35.8, 140.2], // 地図の中心座標
    zoom: 8, // ズーム
  });

  // 地図の読み込み
  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { // OpenStreetMapのオープンデータ
    attribution: "&copy; OpenStreetMap contributors", //地図のデータ提供元を明示するためのクレジット表記
  }).addTo(leafletMap);

  return leafletMap;
};

ここまでやるとこう
image.png

国境を描画する

地理データ(GeoJSON)を取ってきて描画します

下総、上総、安房を試しにやってみます

// LeafletProc.ts

import L from 'leaflet';
+ import { countries } from './countries-def';

export const drawMap = async (elementId: string) => {
  const leafletMap = await createMap(elementId);

+   // 各国の国境を描画
+   countries.forEach(async (n) => {
+     const layer = await drawCountry(leafletMap, n.source);
+     n.layer = layer;
+     leafletMap.fitBounds(layer.getBounds(), { padding: [20, 20] });
+   });

+   // レイヤ切替設定
+   const config = countries.reduce((acc, cur) => {
+     acc[cur.label] = cur.layer;
+     return acc;
+   }, {});
+   
+   // collapsed: false でレイヤー切り替えのUIが展開表示される
+   L.control.layers(null, config, { collapsed: false }).addTo(leafletMap);
};

...

+ /** 指定した国の国境を描画する */
+ const drawCountry = async (leafletMap: L.Map, url: string): Promise<L.GeoJSON> => {
+   const res = await fetch(url);
+   const geo = await res.json();
+   return L.geoJSON(geo).addTo(leafletMap);
+ }

国オブジェクトの定義情報だけ書いておく

countries-def.ts
import { Country } from "./types/country";

export const countries: Country[] = [
  {
    source: "https://geoshape.ex.nii.ac.jp/kg/geojson/K19.geojson",
    label: "下総",
    layer: null,
  },
  {
    source: "https://geoshape.ex.nii.ac.jp/kg/geojson/K18.geojson",
    label: "上総",
    layer: null,
  },
  {
    source: "https://geoshape.ex.nii.ac.jp/kg/geojson/K17.geojson",
    label: "安房",
    layer: null,
  },
];

/src/types/country.d.ts
export interface Country {
    source: string;
    label: string;
    layer: L.GeoJSON | null;
}

ここまでで地図はこう
image.png

レイヤーを選択するとちょっと色が濃くなる
image.png

城マーカーを表示する

//LeafletProc.ts
import L from 'leaflet';
import { countries } from './countries-def';
+ import { castles } from './castles-def';
+ import { Castle } from './types/castle';

export const drawMap = async (elementId: string) => {
  const leafletMap = await createMap(elementId);

  // 各国の国境を描画
  countries.forEach(async (n) => {
    const layer = await drawCountry(leafletMap, n.source);
    n.layer = layer;
    leafletMap.fitBounds(layer.getBounds(), { padding: [20, 20] });
  });

+  //  城マーカーを設定
+  const castleLayer = L.featureGroup().addTo(leafletMap);
+  castles.forEach( n => {
+    drawCastleMarker(castleLayer, n);
+  });
+  config[""] = castleLayer;

  // レイヤ切替設定
  const config = countries.reduce((acc, cur) => {
    acc[cur.label] = cur.layer;
    return acc;
  }, {});
  L.control.layers(null, config, { collapsed: false }).addTo(leafletMap);
};

const createMap = async (elementId: string): Promise<L.Map> => {
  ...

  // 地図の読み込み
  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { // OpenStreetMapのオープンデータ
    //地図のデータ提供元を明示するためのクレジット表記
-    attribution: "&copy; OpenStreetMap contributors"
+    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a> | ' +
+    '© <a href="https://geoshape.ex.nii.ac.jp/">Geoshape Repository</a>, NII (CC BY 4.0)'
  }).addTo(leafletMap);

  return leafletMap;
};


+ const drawCastleMarker = (castleLayer, c: Castle) => {
+   const p = L.marker([parseDMS(c.latDms), parseDMS(c.lonDms)]);
+   p.bindPopup(
+     `<b>${c.name}</b>`, {
+       autoClose: false, // 複数ポップアップできるようにする
+       closeOnClick: false, // 複数ポップアップできるようにする
+     }
+   );
+   castleLayer.addLayer(p);
+ }


+ /** DMS形式の文字列を小数度に変換する */
+ const parseDMS = (dmsStr:string): number => {
+   // 方位をチェック
+   const isSouth = dmsStr.includes("南緯");
+   const isWest = dmsStr.includes("西経");
+ 
+   // 数値部分を抽出
+   const regex = /(\d+)(\d+)([\d.]+)秒/;
+   const match = dmsStr.match(regex);
+ 
+   if (!match) {
+     throw new Error("Invalid DMS string: " + dmsStr);
+   }
+ 
+   const degrees = parseFloat(match[1]);
+   const minutes = parseFloat(match[2]);
+   const seconds = parseFloat(match[3]);
+ 
+   // 小数度に変換
+   let decimal = degrees + minutes / 60 + seconds / 3600;
+ 
+   // 南緯・西経なら負の値に
+   if (isSouth || isWest) {
+     decimal *= -1;
+   }
+ 
+   return decimal;
+ }

城情報の定義

castles-def.ts
import { Castle } from "./types/castle";

export const castles: Castle[] = [
  {
    name: "小弓城",
    latDms: "北緯35度33分16.0秒",
    lonDms: "東経140度09分03.0秒",
  },
  {
    name: "久留里城",
    latDms: "北緯35度17分15.2秒",
    lonDms: "東経140度5分24秒",
  },
  {
    name: "岡本城",
    latDms: "北緯35度03分00.2秒",
    lonDms: "東経139度50分07.1秒",
  },
];
/src/types/castle.d.ts
export interface Castle {
  name: string;
  latDms: string;
  lonDms: string;
}

Wikipediaの緯度経度は度分秒表示(DMS、文字列)で、
Leafletに渡す緯度経度は十進度(Decimal Degrees、数値型)なので変換しています

城マーカーが表示される
image.png

クリックでポップアップする
image.png

レイヤー切り替えでOFFにすると消える
image.png

GeoJsonのデータ元はGeoshapeリポジトリの旧国一覧から

開発ツール>NetworkからGeoJsonのURLが見れる

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?