毎年、クリスマスの雰囲気にちょっとした抵抗感を感じ、ふんわりと病みがちになります。
メンヘラテクノロジーのらんらんと申します。
そんなクリスマスシーズン病みを少しでも癒すべく、漫画『ハルロック』に出てくる「ぼっち・ザ・LED」1のオマージュアプリをつくってみました。
自分以外にもいま病んでる人がいるのを知ってちょっと安心できる地図
実際にできたWebアプリを簡単に紹介します。
まず、自分のいまの気分に合致するアイコンを選択します。
「共有する」を押下すると、自分の現在地の位置情報にアイコンが地図上に表示され、同時に24時間以内に他の人たちが入力したアイコンも確認することができます。
(いまはまだ私しか使っていないので、私が共有したアイコンのみが表示されています…)
開発環境
- Python 3.10.2
- Flask 2.2.2
- Flask-SQLAlchemy 3.0.2
- Flask-Marshmallow
使用API
- HERE Maps API for JavaScript
- GeoJSON
ファイル構成
- app.py
- templates
- index.html
- here_map.html
- resources
- emoji
- base.css
- instance
- db.todo
つくってみた
HERE Maps API for JavaScriptを使ってみる
HERE Maps API for JavaScriptのマーカーオブジェクトを使用して、ユーザーが選択したアイコンを現在地に表示させました。
APIキーの取得
まず、HERE Maps API for JavaScriptを使用するには、HERE platformのアカウントを作成し、APIキーの取得が必要です。
アカウント作成方法については以下の記事を参考にさせていただきました。
▼地図 位置情報サービス HERE 無料アカウント有効化手順
https://qiita.com/kekomat/items/d2e29bac84a27c1ec521
アカウントが作成できたら、「アクセスマネージャ」から「アプリ」タブを開き、「新しいアプリを登録」から必要情報を入力して、アプリを作成しました。
作成したアプリを開き、「資格情報」にある「APIキー」に移動し、「APIキーを作成」を押下しAPIキーを取得します。
マーカーの表示
参考にさせてもらった公式のドキュメントは以下です。
▼チュートリアル - 日本のデータを使い始める
https://jp.developer.here.com/documentation/maps/3.1.32.0/dev_guide/topics/get-started-japan.html
▼オブジェクトをマップ - マーカーオブジェクト
https://jp.developer.here.com/documentation/maps/3.1.32.0/dev_guide/topics/marker-objects.html
まずは日本の地図で指定した緯度・経度に、指定したアイコンを表示させれるかを確認しました。ほとんどサンプルコードのままですが、APIキーと東京駅の緯度・経度(24.2867, 153.9807)に絵文字のアイコンを指定しました。
<!doctype html>
<html>
<head>
<!-- HEREのモジュールの読み込み -->
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div style="width: 640px; height: 480px" id="mapContainer"></div>
<script>
// APIキーを設定し、オブジェクトを初期化
var platform = new H.service.Platform({
'apikey': '(自分のAPIキー)'
});
// 地図の初期化
var baseUrl = 'https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/';
var style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);
var omvProvider = new H.service.omv.Provider(omvService, style);
var omvlayer = new H.map.layer.TileLayer(omvProvider, {max: 22});
var map = new H.Map(
document.getElementById('mapContainer'),
omvlayer,
{
zoom: 12,
center: {lat: 24.2867, lng: 153.9807} // 地図の中心地点の設定
});
// マーカーの表示
var lat = 24.2867; // 東京駅の緯度
var lng = 153.9807; // 東京駅の経度
var icon = new H.map.Icon('アイコンのファイル名');
map.addObject(new H.map.Marker({lat:lat, lng:lng}, {icon: icon}));
</script>
</body>
</html>
このソースコードの緯度・経度とアイコンの種類を、ユーザーの入力値にあわせて変動させれるようにしていきます。
Flaskでサーバーサイドをつくる
サーバーサイドの実装はFlaskを使い、データベースにはSQLAlchemyを使用しました。こちらも公式のドキュメントを参考に作成しました。
▼Flask-SQLAlchemy - クイックスタート
https://flask-sqlalchemy.palletsprojects.com/en/2.x/quickstart/
データベースの作成
以下のようなデータ構造のテーブルを作成します。
- ID:自動で設定される番号(整数型)
- lat:緯度(浮動小数点型)
- lng:経度(浮動小数点型)
- emotion_no:アイコンの選定用(整数型)
- shered_at:共有時間(時間型)
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__, static_folder='resources')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.todo'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class MapData(db.Model):
id = db.Column(db.Integer, primary_key=True)
lat = db.Column(db.Float)
lng = db.Column(db.Float)
emotion_no = db.Column(db.Integer)
shered_at = db.Column(db.DateTime, nullable=False)
def __init__(self, lat, lng, emotion_no, shered_at):
self.lat = lat
self.lng = lng
self.emotion_no = emotion_no
self.shered_at = shered_at
POST・GETメソッド
次にPOST、GETを受け取ったときの処理を実装します。POSTの処理で現在地を取得する部分では、GeoJSONを使用しました。また、GETの処理でデータベースから取得したデータをJSON形式に整えるために、Flask-Marshmallowを使用しました。
▼GeoJSON
https://geojson.org/
▼Flask-Marshmallow
https://flask-marshmallow.readthedocs.io/en/latest/
from flask import Flask, render_template, request, redirect, url_for,jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import requests
from flask_marshmallow import Marshmallow
ma = Marshmallow(app)
class MapDataSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = MapData
load_instance = True
@app.route('/')
def index():
return render_template('index.html')
@app.route('/mapdata', methods=['GET','POST'])
def map():
#POSTの処理
if request.method == 'POST':
#現在地の取得
geo_request_url = 'https://get.geojs.io/v1/ip/geo.json'
geo_data = requests.get(geo_request_url).json()
lat = float(geo_data['latitude'])
lng = float(geo_data['longitude'])
#アイコンの指定
emotion = request.form['emotion']
emotion_no = emotion[-1]
#現在時刻
shered_at = datetime.now()
#データベースに書き込み
map_data = MapData(lat, lng, emotion_no, shered_at)
db.session.add(map_data)
db.session.commit()
return render_template('here_map.html')
#GETの処理
elif request.method == 'GET':
#現在時刻を取得
today = datetime.now()
yestaday = today - timedelta(days=1)
#24時間以内のデータをデータベースから取得
map_date_today = MapData.query.filter(MapData.shered_at > yestaday)
#JSON形式に整形
map_data_schema = MapDataSchema(many=True)
map_data_json = map_data_schema.dump(map_date_today)
print(map_data_json)
return jsonify(map_data_json)
はじめに表示される入力画面は以下のように実装しました。
入力フォームは以下のサイトを参考にCSSを作成しました。
▼ラジオボタン・チェックボタンの代わりに画像選択にする方法
https://0forest.com/css-raido-button/
<!doctype html>
<html>
<head>
<title>いまの気分をきく画面</title>
<link rel="stylesheet" href="/resources/base.css">
</head>
<body>
<h1>自分以外にも病んでる人がいるのを知ってちょっと安心できる地図</h1>
<!-- FlaskでPOSTを受けとるために、method="POST"を設定 -->
<form action="/mapdata" method="POST">
<p>いまの気分は?</p>
<div class="sample-form">
<!-- valueの値がFlaskに送られる入力値 -->
<input id="image_a" type="radio" value="emotion1" name="emotion" checked>
<label for="image_a"><img src="/resources/emoji/1.png" width="40" height="40"></label>
<!-- 中略 -->
<input id="image_l" type="radio" value="emotion12" name="emotion">
<label for="image_l"><img src="/resources/emoji/12.png" width="40" height="40"></label>
</div>
<div>
<input type="submit" value="共有する"/>
</div>
</form>
</body>
</html>
Flaskから受け取ったデータを地図上に表示させる
最後にFlaskからJsonデータを受け取り、HERE Maps API for JavaScriptを使用して、地図上にマーカーを表示させます。
<script>
// 中略
var map = new H.Map(
document.getElementById('mapContainer'),
omvlayer,
{
zoom: 12,
center: {lat: 35.68026, lng: 139.76744}
});
// ↑ここまでは先ほどと同じ
fetch("/mapdata")
.then((res)=>{
return(res.json()); //JSONデータを取得
})
.then((json)=>{
let result = JSON.stringify(json);
let map_data = JSON.parse(result);
map_data.forEach(function(value) { //順番に地図上にアイコンを反映
map.addObject(
new H.map.Marker({lat: value.lat, lng: value.lng},
{ icon: new H.map.Icon(`/resources/emoji/${value.emotion_no}.png`)}
));
});
});
</script>
おわりに
今回、アドベントカレンダーに参加し、以前から気になっていた地図情報のAPIを触る良い機会となりました。普段はPythonを使ったデータ分析系の実装しかしないので、苦戦した部分も多かったですが、フロントエンドの知識が乏しい私でもHERE Maps API for JavaScriptはかなりお手軽に実装できた印象でした。
本当はもっと可愛い見た目のWebアプリに仕上げたかったのですが、時間&スキルが足りなかったので、社内のフロントエンドエンジニアを召喚して作りきれたらなと思っています。実装部分もかなり怪しいところが多いので、そこもあわせて改修していきたい所存です。
Webアプリとして公開できるようになったらまたどこかで共有したいと思います!
-
「ぼっち」という言葉を含むツイートをしている人の場所を地図上に表示し、ぼっちは自分だけではないというのを可視化するツール ↩