Help us understand the problem. What is going on with this article?

映画館の近くに住みたくなった時に便利なサイトを作る

映画館の近くに住みたい。

東京ならとりあえず渋谷か新宿あたりに住めば良さそうだけど、大阪や京都だったらどこだろうかと気になったので、映画館がたくさんある駅がわかるサイトを作りました。映画館の近くに住みたくなった時の参考にしていただけたら幸いです。

Google Mapsのプレイスを初めて使ったので、そこら辺もまとめています。Maps関連のAPIは、気前のいい無料枠が設定されていますが、調子に乗って使いすぎると危ないので注意が必要です。

not-far-from-home.gif
映画館の近くに住みたい

「映画館が近くにたくさん」の定義

駅から半径2kmに映画館が何個あるか。
同数の場合は、距離の総和が小さい方を上位とします。
2kmについては、キリがいい以上の理由はないです。

アンケートによると、徒歩圏は10分以下と考えている人が多いらしいのですが、さすがに800mじゃ映画館が集まらなそうだったので・・・歩こう。

2012年4月20日「不動産広告に関するアンケート」調査結果

情報の取得

「映画館」と「駅」の名前と緯度・経度が必要。

Google Mapsで映画館のデータ

映画館のデータを提供してくれるような、ニッチなAPIはなかったので、Google Mapsのプレイスで、映画館の名称と緯度・経度を取得しました。使いたいNearby Search requestsの検索範囲が、最大で半径50kmという制約があったので、そこを気合で解決しつつ。

映画.comなどのサイトをスクレイピングできれば一発ですが、シンプルに転載になりそうなので・・・最後の答え合わせに、いくつかの映画情報サイトを見比べましたが、それは問題ないでしょう。

下準備 - 緯度・経度を用意

半径50kmが最大なので適当なポイントを決めよう

とりあえず県庁所在地で => 足りない

やり過ぎ感はあるが全国の市役所で => データどこにある

住所といえば郵便局 => 事業所の個別郵便番号データ

スプレッドシートで市役所に絞る

Geocoding APIで住所から緯度・経度へ

使用・再配布・移植・改良について
大口事業所個別番号データに限っては日本郵便株式会社は著作権を主張しません。自由に配布していただいて結構です。日本郵便株式会社への許諾も必要ありません。

大口事業所個別番号データの説明

住所といえば郵便局という安直な発想で調べてみると、事業所の個別郵便番号データなるものに、配達物数の多い大口事業所の一覧があり、だいたいの市役所がありそうだった。厳密に全市役所を網羅したいわけではないので、これで事足りそう。

const googleMapsClient = require('@google/maps').createClient({
  key: 'API_KEY',
  Promise
})

async function main() {
  const data = await googleMapsClient.geocode({
    address: '北海道札幌市中央区北一条西2丁目',
  }).asPromise()

  const { lat, lng } = data.json.results[0].geometry.location

  console.log(lat, lng) // 43.0634672 141.3544036
}

main()

Geocoding APIを使うと、こんな感じで住所から緯度・経度を引けます。ライブラリは @google/maps を使いました。

hokkaido_tarinai.png

全国の市役所で大体カバーできましたが、北海道などで微妙に隙間ができてしまったので、手動で緯度・経度を引っ張って来て埋めました。

映画館検索

const googleMapsClient = require('@google/maps').createClient({
  key: 'API_KEY',
  Promise: Promise
})

async function main() {
  // 札幌市役所
  const options = {
    radius: 50000,
    language: 'ja',
    location: '43.0634672,141.3544036',
    type: 'movie_theater',
  }

  const data = await googleMapsClient.placesNearby(options).asPromise()

  console.log(data.json.results)
}

main()

[
  {
    geometry: { location: [Object], viewport: [Object] },
    icon: 'https://maps.gstatic.com/mapfiles/place_api/icons/movies-71.png',
    id: '1661e774928b2c7d5c02c6d9a0ed944371cfd809',
    name: '札幌シネマフロンティア',
    photos: [ [Object] ],
    place_id: 'ChIJJ0N9wXQpC18Rt_ADN1jHKak',
    plus_code: { compound_code: '3992+59 日本、北海道 札幌市', global_code: '8RM33992+59' },
    rating: 4,
    reference: 'ChIJJ0N9wXQpC18Rt_ADN1jHKak',
    scope: 'GOOGLE',
    types: [ 'movie_theater', 'point_of_interest', 'establishment' ],
    user_ratings_total: 793,
    vicinity: '札幌市中央区北5条西2丁目5−番地 JRタワーステラプレイス7F'
  },
  ...
]

あとは、集めた緯度・経度をもとに、Nearby Search requestsのパラメータで、typemovie_theaterを指定して、ひたすら取得しました。

表記修正・目視チェック

MOVIX Amagasaki => MOVIX あまがさき

パラメータで日本語を設定したはずなのに、英語やローマ字になっているところが結構あったので手動で修正しました。映画の興行会社や成人向けの映画館、そもそも映画館じゃない施設も含まれていたので、こちらも地道に手動で修正。最終的に、全国約550箇所の映画館を確認したので、丸一日かかってしまった。

駅データ.jp

名前がそのまんまの、ありがたいサービス。
駅データの利用活性化のために、データを無料で公開しているとのこと。
フリガナやローマ字を含む完全なデータが欲しい場合には、有料データもあります。

APIでも提供されていますが、一括で欲しかったのでCSVで。

駅データ.jp

station_cd,station_g_cd,station_name,station_name_k,station_name_r,line_cd,pref_cd,post,add,lon,lat,open_ymd,close_ymd,e_status,e_sort
1110101,1110101,函館,,,11101,1,040-0063,北海道函館市若松町12-13,140.726413,41.773709,1902-12-10,,0,1110101
1110102,1110102,五稜郭,,,11101,1,041-0813,函館市亀田本町,140.733539,41.803557,,,0,1110102
1110103,1110103,桔梗,,,11101,1,041-0801,北海道函館市桔梗3丁目41-36,140.722952,41.846457,1902-12-10,,0,1110103

ランキングの作成

データは揃ったので、駅と映画館の距離からランキングを作成していきます。
緯度・経度から出す値なので、駅から映画館までの道のりではなく、直線距離です。

緯度・経度の差から距離を計算

最初は、計算方法を調べてライブラリを使わずにやっていたんですが、だんだんと計算式が複雑になっていき、緯度・経度からの距離計算とか需要が多そうだし、良さげなライブラリあるでしょ、と思って探したらあった。ダウンロード数(80k/週)、スター(2.8k)、ライセンス(MIT)、更新日(1時間前)あたりを参考に決めました。
manuelbieh/geolib

getDistance と getPreciseDistance

このライブラリでは地理の計算全般に使える便利な関数が提供されていて、緯度・経度から距離を算出できる関数は getDistancegetPreciseDistance の2つがありました。名前のとおり正確さの違いなんですが、前者は地球を球体として考えた場合の簡易的な計算になっており、後者はVincenty法の逆解法を使っているため精度が高いぶん時間がかかるとのこと。公式と説明を見てもピンとこなかったので、詳しく聞かれたら困る。

フロントエンドで使うわけではなく、バッチでまとめて計算するので、精度が高い方で。

getDistance.ts
// 簡易的な計算
// https://github.com/manuelbieh/geolib/blob/master/src/getDistance.ts
const distance =
  Math.acos(
    normalizeACosArg(
      Math.sin(toRad(toLat)) * Math.sin(toRad(fromLat)) +
        Math.cos(toRad(toLat)) *
          Math.cos(toRad(fromLat)) *
          Math.cos(toRad(fromLon) - toRad(toLon))
      )
  ) * earthRadius;
getPreciseDistance.ts
// Vincenty法の逆解法
// https://github.com/manuelbieh/geolib/blob/master/src/getPreciseDistance.ts

// Calculates geodetic distance between two points specified by latitude/longitude using
// Vincenty inverse formula for ellipsoids. Taken from:
// https://www.movable-type.co.uk/scripts/latlong-vincenty.html

計算してみる

const { getPreciseDistance } = require('geolib')

const shinsaibashi = { latitude: 34.675012, longitude: 135.500330 }
const burg7 = { latitude: 34.7005079, longitude: 135.4984645 }
const distance = getPreciseDistance(shinsaibashi, burg7)

// 心斎橋駅 → 梅田ブルク7 = 2,834m
console.log(distance)

Google Map上の直線距離と比べてみる

Google Map上で 直線距離を図る機能 があったので、計算結果と比べてみました。
2地点をクリックしないといけないので厳密ではないですが、大きな差はなかったのでこれで良しとします。

名前 計算結果 Google Map
シネマート心斎橋 334m 334m
TOHOシネマズ なんば 1,055m 1,060m
なんばパークスシネマ 1,586m 1,590m
シネ・ヌーヴォ 2,283m 2,280m
日劇東映 2,602m 2,610m
新世界国際劇場 2,627m 2,630m
梅田ブルク7 2,834m 2,840m

※ いずれも心斎橋駅からの距離

全駅 × 全映画館 で直線距離を計算

全ての駅と、全ての映画館から、近い組み合わせのみ絞ります。
都道府県などで事前に組み合わせを絞ることも考えましたが、隣県だけど近い、とかもありそうなので全部で。
約1万駅 × 約550館 なので結構な量ですが15秒ぐらいで終わりました。マシンに感謝。

ランキングのJSONを作成

あとは都道府県ごとに、映画館が多い順にソートしてランキングを作成。
まるっとJSONで吐き出してS3に置きました。

[
  {
    "prefId": 1,
    "stations": [
      {
        "id": 9910109,
        "lineIds": [
          99101,
          ...
        ],
        "name": "大通",
        "point": 45.3,
        "cinemas": [
          {
            "id": 1,
            "distance": 0.7
          },
          ...
        ]
      },
      ...
    ]
  },
  ...
]

フロントエンドの構築

フロントエンドは、降ってくるJSONを表示しただけなので割愛します。TypeScriptじゃないNuxt.jsです。普通に作ってもつまらないと思い、 text-align: center 縛りという不可解な制約を設けたので、中央に寄ってます。

not-far-from-home.gif
映画館の近くに住みたい

いろいろな問題と解決策

駅とランキングにまつわる問題がいろいろ。

同名の駅が多すぎる問題

東京駅や渋谷駅などのターミナル駅は、複数の路線が通っているため、ランキングが同名の駅で埋め尽くされた。だいぶ雑な解決方法ですが、1km以内で同名の駅は大差ないという事にしてしまって、路線の情報だけマージすることに。

同名なのに遠くにある駅が存在する問題

だいぶ大雑把に解決したつもりが、それでもダメだったのが石川県の「野々市駅」。
JR北陸本線と北陸鉄道石川線の2つの駅があり、約2kmも離れていたので、別の駅として扱いました。

同名で遠くの駅を調べている過程で知ったんですが、京都アニメーションの最寄り駅の木幡駅は、京阪が「こわた」で、JRは「こはた」と読むらしい。

大阪・梅田・大阪梅田問題

もう面倒になってしまったので、別の駅としました。

渋谷駅が弱すぎる問題

感覚的には、渋谷駅が圧倒的な1位だと思っていたんですが、トップ5にも入らないという問題が。
代わりに1位になっていたのが代官山。代官山が映画館に行きやすい・・・?

ランキングの算出が、単純に半径2kmにある映画館の個数だったため、そこそこの距離にたくさん映画館がある絶妙な駅として、代官山がトップになっていました。渋谷にも行けるし、目黒にも恵比寿にも行ける。しかし、どれも遠い。これを1位にしてしまうと実感から離れてしまうので、距離で重みを付けよう。

映画館まで徒歩で行くとすると、20分以上は辛いので、80m/分で歩くとして1.6km以上をベースの10点に設定。それと比較して徒歩1分(以下)がどれぐらい嬉しいかと考えた時に、せいぜい倍ぐらいじゃないかという事にして、その間は点数が線形で変化するように。とても適当。

結果、晴れて渋谷駅が1位に。これでいいのか。(作為が過ぎる)

const BASE_POINT = 10
const ACCEPTABLE_DISTANCE = 1.6

function distanceToPoint(distance) {
  if (distance > ACCEPTABLE_DISTANCE) {
    return BASE_POINT
  }

  return BASE_POINT * (1 + (ACCEPTABLE_DISTANCE - distance) / ACCEPTABLE_DISTANCE)
}

埼玉の1位は熊谷で良いのか

熊谷が嫌いなわけではなく。

小さい頃からJR高崎線に育てられたので、熊谷にはむしろ親近感があるんですが、1位って。駅から行ける距離に映画館が2つあるから1位になっているだけなんですが。埼玉で映画の見やすさを考えた場合、東京への出やすさも重要なパラメータになりそう。そこまでやる根性はないので、とりあず熊谷おめでとう。あついぞ。

以上です。

Webサイト
https://not-far-from-home.cc

GitHub
https://github.com/nishinoshake/not-far-from-home

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away