LoginSignup
13
10

More than 3 years have passed since last update.

ドラクエウォークでも使われているらしい GeoHex を YOLP 地図に表示する

Last updated at Posted at 2019-10-11

GeoHex とは

GeoHex は地図上を六角形の領域で区切った位置情報表現。
六角形のひとつひとつに場所を表すコードが付いている。

geohex.png

GeoHex

Hexagonal geo-coding system / Original mapping project
GeoHex/ GEOHEX devide whole target area into honeycomb geometry regions.

ドラゴンクエストウォークで使われているらしい

ドラゴンクエストウォークはスマートフォン向け位置情報ゲーム。
参考情報: ドラゴンクエストウォーク 公式プロモーションサイト | SQUARE ENIX

ドラゴンクエストウォークを起動して、各種情報の画面を見ると、 GeoHex のライセンスが表示されている。

dragon-quest-walk.png

GeoHex をどのように使っているのかわからないが、もしかして距離を算出するのに使っているのだろうか?

GeoHex を YOLP 地図上に表示する

YOLP:Yahoo! JavaScriptマップAPI の上に GeoHex を表示するサンプルを作成した。

動作確認環境: macOS Mojave + Google Chrome 77.0.3865.90

geohex-on-yolp-1.png

geohex-on-yolp-2.png

動作サンプルページ: GeoHex on YOLP (Yahoo! Open Local Platform JavaScript Map API)

ソースコード

地図上に GeoHex を表示するための GeoHexLayer クラスを作成し、Map オブジェクトにレイヤー追加することで GeoHex 表示を実現している。

GeoHexLayer クラス (geohexlayer.js)

// GeoHex を表示するレイヤーです
class GeoHexLayer extends Y.Layer {

  constructor() {
    super();
    this.initializedLayer = false; // このレイヤー独自の初期化が終わっているか
  }

  // レイヤーを描画します
  // Y.Layer.drawLayer をオーバーライドしています
  drawLayer(force) {

    // レイヤーの初期化
    if (!this.initializedLayer) {
      this.initializedLayer = true; // レイヤー初期化済みフラグをONに
      this.hexLevel = 7; // Hex の大きさを表すレベル
      this.canvas = null; // 描画用 Canvas
      this.drawnItems = new Array(); // 描画済み Hex のリスト
      this.getMap().bind("move", this.onMove, this); // 地図移動時のイベントを捕捉する
    }

    // GeoHex 描画処理
    this.createCanvas();
    this.drawGeoHexList();
  }

  // 地図移動時に実行します
  onMove() {
    this.drawLayer();
  }

  // Canvas 要素を生成します
  createCanvas () {

    // すでに Canvas 要素が存在していたらクリアする
    if (this.canvas) {
      this.canvas.remove();
      this.drawnItems = new Array();
    }

    // Canvas 要素を生成
    let canvas = document.createElement("canvas");
    canvas.style.position = "fixed";
    canvas.style.top = 0;
    canvas.style.left = 0;

    // 地図を表示している要素を取得
    const container = this.getMapContainer();
    if (container && container[0]) {
      // Canvas 要素のサイズを地図表示している要素に合わせる
      canvas.width = container[0].offsetWidth;
      canvas.height = container[0].offsetHeight;
      // Canvas 要素を追加
      container[0].appendChild(canvas);
    }

    this.canvas = canvas;
  }

  // GeoHex を描画します
  drawGeoHexList() {

    // 地図表示領域の矩形を緯度経度座標で取得
    const bounds = this.getMap().getBounds(); // LatLngBounds
    const sw = bounds.getSouthWest();
    const ne = bounds.getNorthEast();

    // getXYListByRect.js を使用して
    // 矩形領域内の Hex リストを取得 ([{"x":x, "y":y}] 形式)
    const hexBuffer = true;
    const xyList = getXYListByRect(sw.lat(), sw.lng(), ne.lat(), ne.lng(), this.hexLevel, hexBuffer);

    // Hex をひとつずつ描画
    for(let xy of xyList) {
      // hex_v3.2_core.js を使用して Zone オブジェクトを取得
      const zone = GEOHEX.getZoneByXY(xy.x, xy.y, this.hexLevel);
      // Hex を描画
      this.drawGeoHex(zone);
    }
  }

  // Hex を描画します
  // zone: Hex 領域1つを表すオブジェクト
  drawGeoHex (zone) {

    // 描画済みなら何もしない
    if (this.drawnItems.includes(zone)) {
      return;
    }

    // 六角形の緯度経度をピクセル座標に変換
    const pixels = this.coordsToPixels(zone.getHexCoords());

    // Canvas に描画
    const ctx = this.canvas.getContext("2d");
    ctx.strokeStyle = "black";

    // 六角形を描画
    this.storokeHexagon(ctx, pixels);

    // Code を描画 (本来なら配置位置や文字の大きさを動的に調整すべき)
    ctx.fillStyle = "white"; // 背景色
    ctx.fillRect(pixels[1].x + 5, pixels[1].y + 6, 100, 16);
    ctx.fillStyle = "black"; // 文字色
    ctx.font = "14px serif";
    ctx.fillText(zone.code, pixels[1].x + 10, pixels[1].y + 20);

    // 描画済みリストに追加
    this.drawnItems.push(zone);
  }

  // GeoHex 形式の緯度経度の配列をコンテナ座標へ変換します
  // coords: GeoHex 形式の緯度経度オブジェクトの配列
  coordsToPixels(coords) {
    let pixels = new Array();
    for(let c of coords) {
      pixels.push(this.fromLatLngToContainerPixel(new Y.LatLng(c.lat, c.lon)));
    }
    return pixels;
  }

  // 六角形を描画します
  // ctx: CanvasRenderingContext2D オブジェクト
  // pixels: コンテナ座標の配列
  storokeHexagon(ctx, pixels) {
    ctx.moveTo(pixels[5].x, pixels[5].y);
    for(let p of pixels) {
      ctx.lineTo(p.x, p.y);
    }
    ctx.stroke();
  }
};

HTML

<!DOCTYPE html>
<html>
<head>
<title>GeoHex on YOLP (Yahoo! Open Local Platform JavaScript Map API)</title>
<meta charset="UTF-8">
<style>
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}
#map {
  width: 100%;
  height: 100%;
}
</style>
</head>
<body>

<div id="map"></div>

<!-- YOLP Yahoo! JavaScriptマップAPI -->
<script type="text/javascript" charset="utf-8" src="https://map.yahooapis.jp/js/V1/jsapi?appid=YOUR_APPLICATION_ID"></script>

<!-- GeoHex ライブラリ -->
<script src="http://geohex.net/src/script/hex_v3.2_core.js"></script>
<script src="http://geohex.net/src/script/getXYListByRect.js"></script>

<!-- 今回作成した GeoHexLayer -->
<script src="geohexlayer.js"></script>

<script>

// Map オブジェクトを生成
var ymap = new Y.Map("map", {
  "configure": {
    "dragging": true,
    "singleClickPan": false,
    "doubleClickZoom": true,
    "continuousZoom": true,
    "scrollWheelZoom": true
  }
});

// GeoHexLayer を追加
ymap.addLayer(new GeoHexLayer('map'));

// 名古屋駅付近の地図を表示
var p = {lat : 35.171962, lon : 136.8817322, zoom : 16};
ymap.drawMap(new Y.LatLng(p.lat, p.lon), p.zoom, Y.LayerSetId.NORMAL);

// リサイズ時に地図を再描画
window.addEventListener("resize", () => {
  ymap.updateSize();
});

</script>
</body>
</html>

参考資料

13
10
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
13
10