テクテクテクテクの稼ぎ場所をオープンデータから求める（初級編）

こんにちは、みなさん [テクテクテクテク](https://www.teku4.jp/) してますか？

このゲームはIngressやPokemon Goと同じ位置情報ゲームです。なので実際に出歩く必要があります。ただ前者と違って速度制限が特にないようなので新幹線に乗ったり、たどり着きにくいところに船で行くなどの「リアル課金」がものを言うゲームです。

私は ~~デブ性~~ 出不精なのですが、電車で移動したりしてランク119、レベル29まで来ました (2018/12/10現在)。

今のところ特に人と協力・競争する要素はないのでのんびりマイペースで進めてもいいのですが、ここは「楽して稼げる」方法を考えてから行動することにしてみました。

（この記事はアドベントカレンダー駆動で書かれています）

# 初級編：東京都内での大字の密集地帯を探そう

このテクテクテクテクは自分の周りの道路で囲まれた場所を「塗る」ことでTPP (経験値のようなもの) を稼いでいくのですが、「大字」をすべて塗ると100%ボーナスがもらえます。このボーナスは毎日もらえるため、たくさんの「大字」を塗りつぶすことで出歩かなくても多く稼ぐことができるわけです。

ちなみに「大字」って知ってますか？ざっというと市区町村の次に小さい単位だと思ってください。
例えば「東京都千代田区内幸町一丁目」とか「千代田区神田東紺屋町」とかそういう区分です。

今回は大字が一番密集している場所をプログラムで探すことで短時間で大量の大字100%ボーナスをもらっちゃおう！というのをやってみます。

## 大字の情報をダウンロード

日本国内の大字の情報は国土交通省のページからダウンロードすることができます。

http://nlftp.mlit.go.jp/isj/data.html

この中には、

- 都道府県名
- 市区町村名
- 大字名
- 緯度
- 経度

などが含まれているため、今回はこのデータを元に、大字の密集している地域を求めることにします。

ダウンロード時に利用規約を見ましたが、このような使い方は問題ないようでした。

今回は私が住んでいるのが東京なので、東京都のデータだけを取得してみました。

zipをダウンロードし、展開すると `13_2017.csv` というファイルが得られます。
このファイルによると東京都には大字が5,359箇所あるようです。

## 求める方法

今回は初級編ということもあり、適当な矩形を動かしながら、その中に最も多くの大字の緯度経度が含まれる矩形の位置を探すことにしました。

[緯度経度は0.01度で1.1km程度](https://ja.wikipedia.org/wiki/Wikipedia:%E7%B7%AF%E5%BA%A6%E3%83%BB%E7%B5%8C%E5%BA%A6%E3%81%AE%E8%AA%BF%E3%81%B9%E6%96%B9#%E9%81%8E%E5%89%B0%E3%81%AA%E7%B2%BE%E5%BA%A6%E3%81%AF%E9%81%BF%E3%81%91%E3%82%8B)とのことなので、0.02度 x 0.02度の矩形内に含まれる大字の緯度経度が最大になる地点を0.001度ずつずらしながら探してみます。

実装はGoを使いました。
CSVを読み込み（ShiftJISになってるので注意）、ループするだけなので1時間程度で書けました。実行時間は自分のPCで5秒程度です。

## 結果

今回の結果は 緯度 35.695558999999555, 経度 139.7745240000037 の地点が、
0.1度以内に大字を83含む最大の地点になりました。

だいたい [都営新宿線の岩本町駅](https://www.google.com/maps?q=35.695558999999555,139.7745240000037) あたりのようです。

含まれる大字は次のとおりです。

|市区町村|大字|緯度|経度|
|--------|----|----|----|
|千代田区|岩本町一丁目|35.692346|139.777635|
|千代田区|神田西福田町|35.691334|139.774244|
|千代田区|神田東紺屋町|35.692833|139.774711|
|千代田区|神田北乗物町|35.692455|139.773897|
|千代田区|神田富山町|35.693673|139.773588|
|千代田区|神田鍛冶町三丁目|35.693613|139.770976|
|千代田区|内神田二丁目|35.690601|139.768027|
|千代田区|神田紺屋町|35.692021|139.774233|
|千代田区|東神田二丁目|35.696189|139.780146|
|千代田区|神田多町二丁目|35.693856|139.769189|
|千代田区|内神田一丁目|35.69058|139.765604|
|千代田区|神田司町二丁目|35.693464|139.767523|
|千代田区|大手町二丁目|35.686301|139.768087|
|千代田区|神田美土代町|35.693283|139.765581|
|千代田区|神田佐久間町四丁目|35.697658|139.779837|
|千代田区|東神田三丁目|35.697364|139.781365|
|千代田区|岩本町二丁目|35.693526|139.776933|
|千代田区|東神田一丁目|35.694328|139.780497|
|千代田区|岩本町三丁目|35.695636|139.776931|
|千代田区|神田東松下町|35.694239|139.773572|
|千代田区|神田佐久間町三丁目|35.697983|139.777936|
|千代田区|神田平河町|35.698036|139.775653|
|千代田区|神田岩本町|35.695506|139.775004|
|千代田区|神田佐久間河岸|35.697048|139.777798|
|千代田区|神田花岡町|35.698802|139.773971|
|千代田区|鍛冶町二丁目|35.692583|139.772023|
|千代田区|内神田三丁目|35.691005|139.769658|
|千代田区|外神田五丁目|35.704021|139.772366|
|千代田区|神田佐久間町二丁目|35.698009|139.776368|
|千代田区|神田佐久間町一丁目|35.697979|139.774222|
|千代田区|神田和泉町|35.699704|139.777907|
|千代田区|神田練塀町|35.70071|139.774037|
|千代田区|鍛冶町一丁目|35.690198|139.77154|
|千代田区|外神田四丁目|35.70143|139.77237|
|千代田区|神田相生町|35.699524|139.773602|
|千代田区|神田松永町|35.70031|139.775293|
|千代田区|外神田三丁目|35.701275|139.770496|
|千代田区|神田淡路町一丁目|35.695759|139.767031|
|千代田区|神田淡路町二丁目|35.697519|139.767838|
|千代田区|神田小川町一丁目|35.694724|139.766412|
|千代田区|神田須田町一丁目|35.696223|139.769841|
|千代田区|神田須田町二丁目|35.695815|139.773059|
|千代田区|外神田一丁目|35.698406|139.770885|
|千代田区|外神田二丁目|35.700675|139.768552|
|千代田区|外神田六丁目|35.704066|139.770971|
|千代田区|神田駿河台三丁目|35.696732|139.764721|
|千代田区|神田美倉町|35.690853|139.774501|
|千代田区|神田駿河台四丁目|35.698594|139.765849|
|中央区|日本橋久松町|35.689545|139.784211|
|中央区|日本橋堀留町二丁目|35.688584|139.781188|
|中央区|日本橋人形町三丁目|35.686653|139.781547|
|中央区|日本橋小舟町|35.686858|139.778267|
|中央区|日本橋本町二丁目|35.687852|139.775799|
|中央区|日本橋本町一丁目|35.685747|139.776872|
|中央区|日本橋本石町二丁目|35.686261|139.771288|
|中央区|日本橋本石町三丁目|35.687534|139.770761|
|中央区|日本橋本町三丁目|35.688895|139.77566|
|中央区|日本橋室町三丁目|35.687974|139.772824|
|中央区|日本橋室町二丁目|35.686841|139.773767|
|中央区|日本橋本石町四丁目|35.688585|139.770806|
|中央区|日本橋富沢町|35.688951|139.782661|
|中央区|東日本橋三丁目|35.69155|139.783599|
|中央区|日本橋横山町|35.693024|139.783587|
|中央区|日本橋小伝馬町|35.691464|139.778699|
|中央区|日本橋室町四丁目|35.689122|139.77239|
|中央区|日本橋堀留町一丁目|35.687931|139.779894|
|中央区|日本橋大伝馬町|35.690253|139.779661|
|中央区|日本橋本町四丁目|35.690183|139.775106|
|中央区|日本橋馬喰町一丁目|35.693636|139.782375|
|中央区|日本橋馬喰町二丁目|35.695734|139.783754|
|文京区|湯島一丁目|35.701413|139.764617|
|文京区|湯島二丁目|35.705198|139.765922|
|台東区|台東二丁目|35.703252|139.778437|
|台東区|台東三丁目|35.704838|139.778737|
|台東区|台東一丁目|35.701458|139.778197|
|台東区|浅草橋五丁目|35.700562|139.782102|
|台東区|上野三丁目|35.705408|139.773096|
|台東区|秋葉原|35.701896|139.774417|
|台東区|鳥越一丁目|35.70292|139.78296|
|台東区|上野五丁目|35.705188|139.775309|
|台東区|小島一丁目|35.70429|139.782968|
|台東区|浅草橋四丁目|35.698503|139.781432|
|台東区|浅草橋一丁目|35.697415|139.784498|

## ソースコード

今回の調査に利用したソースコードは https://github.com/mtgto/teku4easy/ にpushしています。

```go:teku4easy.go
package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
	"math"
	"os"
	"strconv"
)

// 大字の定義
type oaza struct {
	name string
	city string
	pos position
}

type position struct {
	latitude float64
	longitude float64
}

func main() {
	flag.Parse()
	if flag.NArg() < 1 {
		fmt.Fprintf(os.Stderr, "引数に大字のCSVファイルを渡してください\n")
		os.Exit(1)
	}
	oazas := loadCsv(flag.Arg(0))
	//fmt.Printf("%v\n", oazas)
	minLat, minLong := 10000.0, 10000.0
	maxLat, maxLong := 0.0, 0.0
	for _, oaza := range oazas {
		minLat = math.Min(minLat, oaza.pos.latitude)
		maxLat = math.Max(maxLat, oaza.pos.latitude)
		minLong = math.Min(minLong, oaza.pos.longitude)
		maxLong = math.Max(maxLong, oaza.pos.longitude)
	}
	pos, result := findMostCongested(&oazas, 0.01, 0.01, position{latitude: minLat, longitude: minLong}, position{latitude: maxLat, longitude: maxLong})
	fmt.Printf("緯度 %v, 経度 %v 大字の数%v\n", pos.latitude, pos.longitude, len(result))
	for _, oaza := range result {
		fmt.Printf("%v, %v (%v, %v)\n", oaza.city, oaza.name, oaza.pos.latitude, oaza.pos.longitude)
	}
}

// min - maxの矩形内で、width, height (単位は緯度経度) 以内に一番多くの大字を含む地点とその数を返す
func findMostCongested(oazas *[]oaza, width, height float64, minPos, maxPos position) (position, []oaza) {
	var bestPos position
	var bestResult []oaza = make([]oaza, 0, 0)
	for lat := minPos.latitude; lat < maxPos.latitude; lat += width/10 {
		for long := minPos.longitude; long < maxPos.longitude; long += height/10 {
			result := make([]oaza, 0)
			for _, oaza := range *oazas {
				if lat - width <= oaza.pos.latitude && oaza.pos.latitude <= lat + width && long - height <= oaza.pos.longitude && oaza.pos.longitude <= long + height {
					result = append(result, oaza)
				}
			}
			if len(bestResult) < len(result) {
				//fmt.Printf("%v, %v, %v\n", lat, long, result)
				bestResult = result
				bestPos = position{latitude: lat, longitude: long}
			}
		}
	}
	return bestPos, bestResult
}

// 大字CSVデータを読み込む。ShiftJISになっているのとヘッダ行があるので処理する
func loadCsv(in string) []oaza {
	// 今回は離島は計算しない
	count := 5283
	oazas := make([]oaza, 0, count)
	file, err := os.Open(in)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	reader := csv.NewReader(transform.NewReader(file, japanese.ShiftJIS.NewDecoder()))
	records, err := reader.ReadAll()
	if err != nil {
		panic(err)
	}
	for i, record := range records {
		if i >= 1 && i < count {
			latitude, err := strconv.ParseFloat(record[6], 64)
			if err != nil {
				panic(err)
			}
			longitude, err := strconv.ParseFloat(record[7], 64)
			if err != nil {
				panic(err)
			}
			oazas = append(oazas, oaza{name: record[5], city: record[3], pos: position{latitude: latitude, longitude: longitude}})
		}
	}
	return oazas
}
```

# 終わりに

今回は「初級編」ということで、国土交通省の緯度経度を含む大字のオープンデータを使い、大字が密集している場所を探してみました。

実際にゲームをやってみるとわかるのですが、100%コンプしやすさにはこれだけではなく大字自体が小さいことだったり、電車だけでコンプできる大字の情報がほしいと感じます。それらは今後の課題（中級編・上級編）としましょう。

ではみなさん、レッツ[テクテクテクテク](https://www.teku4.jp/)！