はじめに
引っ越そうかなー、なんて
どうせ引っ越すなら犯罪が少ない地域がいいよね
web検索してみると各都道府県ごとに、警察の公式(?)のマップがあったので利用してみるけど、ちょっとモッサリしてたので自分で作りたくなった。自分用の記事として書いておく。
あとfoliumのクラスターの使い方も残しておきたかったから。
犯罪オープンデータサイトの活用
各都道府県ごとにオープンデータになってて自由に利用していいみたい。
たぶん種類は共通になってて以下の通り。
・ひったくり
・車上ねらい
・部品ねらい
・自動販売機ねらい
・自動車盗
・オートバイ盗
・自転車盗
種類ごとにcsvファイルになってて、場所や発生年月日なんかのちょっとした情報が入ってる。
試しに大阪府の2024~2022年のオープンデータをダウンロードしてみる。
ファイル名はこんな感じ
プライバシーのはなし
犯罪者マップを作って有料で見られるようにして儲けようという人がいて、プライバシーの問題が!というネットの記事を今年見た気がする。
あれは個人名とかが入ってるから問題なんだろうな。
このオープンデータには個人名は入っておらず、軽犯罪(?)だけにしてるから好きに利用していいよ~ってことなんでしょう。
重い犯罪のオープンデータは探してみたけど無かったから。
この記事は犯罪者マップじゃなくて、犯罪マップなので炎上したりしない。
出典の記載
この記事は、2022~2024年 大阪の犯罪オープンデータを利用し、マップを作成する方法について記述しています。
プログラム1個目
いつものようにChatGPTさんに手伝ってもらって書いた。
import os
import re
import glob
import csv
import folium
from folium.plugins import MarkerCluster
#
import points_get
# 1900年から2099年までの西暦を正規表現で検索
def find_first_year(text):
pattern = r"(19[0-9]{2}|20[0-9]{2})"
match = re.search(pattern, text)
if match:
return match.group()
return None
# 指定ファイルの内容を返す
# ・オコポイント1: タブ区切りだったりカンマ区切りだったりする
# ・オコポイント2: 文字コードがShift_jisだったりUTF-8だったりする
def load_csv_data(file_path):
def try_read(file, delimiter):
file.seek(0)
reader = csv.reader(file, delimiter=delimiter)
header = next(reader)
return header, list(reader)
def try_load(encoding):
with open(file_path, mode='r', encoding=encoding) as file:
header, rows = try_read(file, ',')
if len(header) == 1:
header, rows = try_read(file, '\t')
return header, rows
try:
return try_load('shift_jis')
except UnicodeDecodeError:
return try_load('utf-8')
# 指定されたファイルの犯罪情報を加工して返す
def crime_info_by_csv(file_path):
# ファイル名称に入っている西暦を取得する
year = find_first_year(os.path.basename(file_path))
header, crime_list = load_csv_data(file_path)
ans_dict_list = []
for data in crime_list:
address = data[5] + data[6] + data[7]
if not (points:= points_get.main(address)):
continue
data[8] = data[8].replace('[sanitize]', '/')
result_dict = dict(zip(header, data))
result_dict['位置'] = [points['lat'], points['lon']]
result_dict['格納ファイルの西暦'] = year
ans_dict_list.append(result_dict)
return ans_dict_list
# htmlを作るよ
def create_html(infos, save_name):
m = folium.Map(location=[34, 134],
zoom_start=8,
tiles='https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
attr='地理院タイル(国土地理院)')
year_groups = {}
cluster_groups = {}
for info in infos:
year = info['格納ファイルの西暦']
# 年ごとのグループとクラスターを初期化
if year not in year_groups:
print('追加んご!', year)
year_group = folium.FeatureGroup(name=f"{year} 年")
year_cluster = MarkerCluster()
year_cluster.add_to(year_group)
year_groups[year] = year_group
cluster_groups[year] = year_cluster
text = ''
for key, value in info.items():
if key != '格納ファイルの西暦' and \
key != '位置' and key != '罪名' and \
key != '管轄警察署(発生地)' and \
key != '管轄交番・駐在所(発生地)' and \
key != '市区町村コード(発生地)' and \
key != '都道府県(発生地)':
text += f"{key}={value} <br>"
popup = folium.Popup(text, max_width=300, min_width=150)
folium.Marker(
location=info['位置'],
popup = popup,
icon=folium.Icon(color="red"),
).add_to(cluster_groups[year])
# すべてのグループを地図に追加
for year_group in year_groups.values():
year_group.add_to(m)
# レイヤーコントロールを追加
folium.LayerControl().add_to(m)
print(year_groups)
m.save(save_name)
# 犯罪情報のhtmlを作る
def csv_to_html_crime_report(target_directory, output_html):
# 再帰的にCSVファイルを検索
csv_files = glob.glob(target_directory)
print(csv_files)
ans_dict_list = []
for csv_file in csv_files:
ans_dict_list.extend(crime_info_by_csv(csv_file))
create_html(ans_dict_list, output_html)
if __name__ == "__main__":
csv_to_html_crime_report(f"大阪の犯罪オープンデータ/*.csv", 'crime.html')
件数が多いと見づらいのでクラスターを使って年で分けてみる。
プログラム2個目 (位置情報の管理)
import json
import urllib
import requests
from functools import lru_cache
# 国土地理院APIを利用させて頂く
def by_msearch(city):
makeUrl = "https://msearch.gsi.go.jp/address-search/AddressSearch?q="
s_quote = urllib.parse.quote(city)
response = requests.get(makeUrl + s_quote, timeout = 60)
if not response.json():
return {}
# 緯度と経度を返却
iti = {}
iti['lat'] = response.json()[0]["geometry"]["coordinates"][1]
iti['lon'] = response.json()[0]["geometry"]["coordinates"][0]
return iti
# 過去の履歴ファイルから分かれば取得
@lru_cache(maxsize=1)
def by_history(city):
with open('points_history.json', 'r', encoding='utf-8') as file:
data = json.load(file)
return data.get(city, {})
# 1件、履歴ファイルに追記する
def add_history(city, lat, lon):
# JSONファイルを開いてデータを読み込む
try:
with open('points_history.json', mode="r", encoding="utf-8") as file:
data = json.load(file) # JSONファイルを辞書として読み込む
except FileNotFoundError:
data = {} # ファイルがない場合は空の辞書を作成
# 新しい情報を辞書に追加
data[city] = { "lat": lat,
"lon": lon }
# JSONファイルに書き戻す
with open('points_history.json', mode="w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=4)
by_history.cache_clear()
def main(city):
if ans := by_history(city):
return ans
if ans := by_msearch(city):
# 毎回、国土地理院APIを実行するのは悪い気がするので履歴に入れよう!
add_history(city, ans['lat'], ans['lon'])
return ans
print('この場所分かんない', city)
return None
if __name__ == "__main__":
iti = main('大阪府')
print(iti)
キャッシュ化して早くなる処理は最後に付けたので、効果は確認してないし、もしかしたら間違ってるかも。
なんとなくでやった。
実行すると
右上のアイコンにマウスカーソルを当てるとクラスターされた年が選べる。
数字をクリックするとズームされてどんどん数字が小さくなっていって、最後はアイコンをクリックすると詳細情報が見れる。
別にオープンデータだから情報を塗りつぶす必要はないはずなんだけど一応ね
終わりに
軽い犯罪が多い所は重い犯罪も多いと言える、でしょ。割れ窓理論だと思う、たぶん。
東京に就職した友達との会話を思い出す。
私:さっき駅で変な人おった!やっぱ東京は変な人多くね~?
友:あのね。東京は人が多いから変な人も多いだけなの。率で言ったらたぶんどこも変わらないよ?
私:おっ!? おぅ、そうか・・