↓↓クリックして頂けると励みになります。
【54 | Web Socketの設定】 << 【ホーム】 >> 【56 | マップ リアルタイム表示】
Redisをインストールします。
5分くらいかかります。
コマンド
brew install redis
channels-redisをインストールします。
コマンド
pip install channels-redis==3.2.0
バージョンを確認します。
コマンド
pip freeze > requirements.txt
確認 【Desktop/crowdsource/requirements.txt】
aioredis==1.3.1 asgiref==3.7.2 async-timeout==4.0.3 attrs==23.1.0 autobahn==23.6.2 Automat==22.10.0 beautifulsoup4==4.12.2 CacheControl==0.13.1 cachetools==5.3.1 certifi==2023.7.22 cffi==1.15.1 channels==3.0.3 channels-redis==3.2.0 chardet==3.0.4 charset-normalizer==3.2.0 constantly==15.1.0 cryptography==41.0.3 daphne==3.0.2 defusedxml==0.7.1 Django==3.2.20 django-bootstrap4==2.3.1 firebase-admin==4.4.0 google-api-core==1.34.0 google-api-python-client==2.97.0 google-auth==2.22.0 google-auth-httplib2==0.1.0 google-cloud-core==2.3.3 google-cloud-firestore==2.11.1 google-cloud-storage==2.10.0 google-crc32c==1.5.0 google-resumable-media==2.5.0 googleapis-common-protos==1.60.0 grpcio==1.57.0 grpcio-status==1.48.2 hiredis==2.2.3 httplib2==0.22.0 hyperlink==21.0.0 idna==2.10 incremental==22.10.0 msgpack==1.0.5 oauthlib==3.2.2 paypalrestsdk==1.13.1 Pillow==10.0.0 proto-plus==1.22.3 protobuf==3.20.3 pyasn1==0.5.0 pyasn1-modules==0.3.0 pycparser==2.21 PyJWT==2.8.0 pyOpenSSL==23.2.0 pyparsing==3.1.1 python3-openid==3.2.0 pytz==2023.3 requests==2.25.0 requests-oauthlib==1.3.1 rsa==4.9 service-identity==23.1.0 six==1.16.0 social-auth-app-django==4.0.0 social-auth-core==4.4.2 soupsieve==2.5 sqlparse==0.4.4 stripe==2.55.1 Twisted==23.8.0 txaio==23.1.1 typing_extensions==4.7.1 uritemplate==4.1.1 urllib3==1.26.16 zope.interface==6.0
ターミナルを分割し、Redisサーバーを起動します。
コマンド
redis-server
「crowdsource/settings.py」ファイルを編集します。
記述編集 【Desktop/crowdsource/crowdsource/settings.py】179行目(末尾)
# Channels CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, }
「core/consumers.py」ファイルを編集します。
記述編集 【Desktop/crowdsource/core/consumers.py】
import json from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer from . import models class JobConsumer(WebsocketConsumer): def connect(self): self.job_id = self.scope['url_route']['kwargs']['job_id'] self.job_group_name = 'job_%s' % self.job_id # Join room group async_to_sync(self.channel_layer.group_add)( self.job_group_name, self.channel_name ) self.accept() def disconnect(self, close_code): # Leave room group async_to_sync(self.channel_layer.group_discard)( self.job_group_name, self.channel_name ) def receive(self, text_data): text_data_json = json.loads(text_data) job = text_data_json['job'] # print("Job", job) if job.get('courier_lat') and job.get('courier_lng'): self.scope['user'].courier.lat = job['courier_lat'] self.scope['user'].courier.lng = job['courier_lng'] self.scope['user'].courier.save() # Send message to job group async_to_sync(self.channel_layer.group_send)( self.job_group_name, { 'type': 'job_update', 'job': job } ) # Receive message from job group def job_update(self, event): job = event['job'] # Send message to WebSocket self.send(text_data=json.dumps({ 'job': job }))
「core/templates/customer/job.html」ファイルを編集します。
記述編集 【Desktop/crowdsource/core/templates/customer/job.html】
{% extends 'customer/base.html' %}
{% load static %}
{% block head %}
<style>
.photo {
object-fit: cover;
}
.photo-blank {
border: 2px dashed #DFDFDF;
height: 130px;
width: 130px;
border-radius: 5px;
align-items: center;
display: flex;
justify-content: center;
text-align: center;
padding: 10px;
}
</style>
<script
src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAP_API_KEY }}&callback=initMap&libraries=places&v=weekly"
defer></script>
<script>
var pickupLat = parseFloat(" {{ job.pickup_lat }} ");
var pickupLng = parseFloat(" {{ job.pickup_lng }} ");
var deliveryLat = parseFloat(" {{ job.delivery_lat }} ");
var deliveryLng = parseFloat(" {{ job.delivery_lng }} ");
var courierLat = parseFloat(" {{ job.courier.lat }} ");
var courierLng = parseFloat(" {{ job.courier.lng }} ");
function initMap() {
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer();
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 43.062087, lng: 141.354404 },
});
directionsRenderer.setMap(map);
calculateAndDisplayRoute(map, directionsService, directionsRenderer);
}
function calculateAndDisplayRoute(map, directionsService, directionsRenderer) {
directionsService.route(
{
origin: new google.maps.LatLng(pickupLat, pickupLng),
destination: new google.maps.LatLng(deliveryLat, deliveryLng),
travelMode: google.maps.TravelMode.DRIVING,
},
(response, status) => {
if (status === "OK") {
new google.maps.DirectionsRenderer({
map: map,
directions: response,
suppressMarkers: true,
polylineOptions: {
strokeColor: "#000",
strokeWeight: 5,
strokeOpacity: 0.8
}
});
var leg = response.routes[0].legs[0];
new google.maps.Marker({
position: leg.start_location,
map: map,
icon: "{% static 'img/start.png' %}"
});
new google.maps.Marker({
position: leg.end_location,
map: map,
icon: "{% static 'img/end.png' %}"
});
window.courierMarker = new google.maps.Marker({
position: new google.maps.LatLng(courierLat, courierLng),
map,
icon: '/static/img/courier.png',
})
} else {
window.alert("リクエストは次の理由で失敗しました:" + status);
}
}
);
}
</script>
{% endblock %}
{% block main %}
<!-- JOB DESCRIPTION -->
<div class="media mb-4">
<img src="{{ job.photo.url }}" class="rounded-lg mr-3" width="150" height="150">
<div class="media-body">
{% if job.status == 'processing' %}
<form method="POST" class="float-right">
{% csrf_token %}
<button type="submit" class="btn btn-warning">配達依頼をキャンセル</button>
</form>
{% endif %}
<h4>{{ job.name }}</h4>
<p class="text-secondary">{{ job.description }}</p>
<div class="row">
<div class="col-lg-3">
<small class="text-secondary">カテゴリー</small><br />
<span><b>{{ job.category.name }}</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">サイズ</small><br />
<span><b>{{ job.get_size_display }}</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">料金</small><br />
<span><b>{{ job.price }}円</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">数量</small><br />
<span><b>{{ job.quantity }}</b></span>
</div>
</div>
</div>
</div>
<!-- DELIVERY INFORMATION -->
<b class="text-secondary">配達依頼情報</b><br />
<div class="card bg-white mt-2 mb-5">
<div class="card-body p-4">
<h4 class="mb-3">
荷物受取先
</h4>
<div class="row">
<div class="col-lg-4">
<b>荷物受取先住所</b><br />
<span>{{ job.pickup_address }}</span>
</div>
<div class="col-lg-4">
<b>{{ job.pickup_name }}</b><br />
<span>{{ job.pickup_phone }}</span>
</div>
<div id="pickup_photo" class="col-lg-4">
{% if job.pickup_photo %}
<img src="{{ job.pickup_photo.url }}" class="rounded-lg photo" width="130" height="130">
{% else %}
<div class="photo-blank">写真があればここに表示されます。</div>
{% endif %}
</div>
</div>
<hr class="my-4" />
<h4 class="mb-3">
配達先
</h4>
<div class="row">
<div class="col-lg-4">
<b>配達先住所</b><br />
<span>{{ job.delivery_address }}</span>
</div>
<div class="col-lg-4">
<b>{{ job.delivery_name }}</b><br />
<span>{{ job.delivery_phone }}</span>
</div>
<div id="delivery_photo" class="col-lg-4">
{% if job.delivery_photo %}
<img src="{{ job.delivery_photo.url }}" class="rounded-lg photo" width="130" height="130">
{% else %}
<div class="photo-blank">写真があればここに表示されます。</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- マップ -->
<div class="d-flex justify-content-between">
<b class="text-secondary">配達の追跡</b>
<div>
<span id="job_status" class="badge badge-warning">
{% if job.get_status_display == 'Processing' %}
進行中 {{ job.get_status_display }}
{% else %}
完了 {{ job.get_status_display }}
{% endif %}
</span>
</div>
</div>
<div class="card bg-white mt-2">
<div class="card-body p-0">
<div id="map" style="height: 500px;"></div>
</div>
</div>
{% endblock %}
ブラウザを確認します。
配達依頼を受けた配達人の位置が配達依頼をした人のマップに表示されるようになりました。

引き続き「core/templates/customer/job.html」ファイルを編集します。
記述編集 【Desktop/crowdsource/core/templates/customer/job.html】
{% extends 'customer/base.html' %}
{% load static %}
{% block head %}
<style>
.photo {
object-fit: cover;
}
.photo-blank {
border: 2px dashed #DFDFDF;
height: 130px;
width: 130px;
border-radius: 5px;
align-items: center;
display: flex;
justify-content: center;
text-align: center;
padding: 10px;
}
</style>
<script
src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAP_API_KEY }}&callback=initMap&libraries=places&v=weekly"
defer></script>
<script>
var pickupLat = parseFloat(" {{ job.pickup_lat }} ");
var pickupLng = parseFloat(" {{ job.pickup_lng }} ");
var deliveryLat = parseFloat(" {{ job.delivery_lat }} ");
var deliveryLng = parseFloat(" {{ job.delivery_lng }} ");
var courierLat = parseFloat(" {{ job.courier.lat }} ");
var courierLng = parseFloat(" {{ job.courier.lng }} ");
function initMap() {
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer();
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 43.062087, lng: 141.354404 },
});
directionsRenderer.setMap(map);
calculateAndDisplayRoute(map, directionsService, directionsRenderer);
}
function calculateAndDisplayRoute(map, directionsService, directionsRenderer) {
directionsService.route(
{
origin: new google.maps.LatLng(pickupLat, pickupLng),
destination: new google.maps.LatLng(deliveryLat, deliveryLng),
travelMode: google.maps.TravelMode.DRIVING,
},
(response, status) => {
if (status === "OK") {
new google.maps.DirectionsRenderer({
map: map,
directions: response,
suppressMarkers: true,
polylineOptions: {
strokeColor: "#000",
strokeWeight: 5,
strokeOpacity: 0.8
}
});
var leg = response.routes[0].legs[0];
new google.maps.Marker({
position: leg.start_location,
map: map,
icon: "{% static 'img/start.png' %}"
});
new google.maps.Marker({
position: leg.end_location,
map: map,
icon: "{% static 'img/end.png' %}"
});
window.courierMarker = new google.maps.Marker({
position: new google.maps.LatLng(courierLat, courierLng),
map,
icon: '/static/img/courier.png',
})
} else {
window.alert("リクエストは次の理由で失敗しました:" + status);
}
}
);
}
const jobSocket = new WebSocket(
"ws://" + window.location.host + "/ws/jobs/{{ job.id }}/"
);
// このページが WebSocket 経由でイベントを受信するたびにこの関数を実行します
jobSocket.onmessage = function (e) {
var data = JSON.parse(e.data);
var job = data.job;
console.log(job);
if (job.courier_lat && job.courier_lng) {
var courierPosition = new google.maps.LatLng(job.courier_lat, job.courier_lng);
window.courierMarker.setPosition(courierPosition);
}
}
</script>
{% endblock %}
{% block main %}
<!-- JOB DESCRIPTION -->
<div class="media mb-4">
<img src="{{ job.photo.url }}" class="rounded-lg mr-3" width="150" height="150">
<div class="media-body">
{% if job.status == 'processing' %}
<form method="POST" class="float-right">
{% csrf_token %}
<button type="submit" class="btn btn-warning">配達依頼をキャンセル</button>
</form>
{% endif %}
<h4>{{ job.name }}</h4>
<p class="text-secondary">{{ job.description }}</p>
<div class="row">
<div class="col-lg-3">
<small class="text-secondary">カテゴリー</small><br />
<span><b>{{ job.category.name }}</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">サイズ</small><br />
<span><b>{{ job.get_size_display }}</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">料金</small><br />
<span><b>{{ job.price }}円</b></span>
</div>
<div class="col-lg-3">
<small class="text-secondary">数量</small><br />
<span><b>{{ job.quantity }}</b></span>
</div>
</div>
</div>
</div>
<!-- DELIVERY INFORMATION -->
<b class="text-secondary">配達依頼情報</b><br />
<div class="card bg-white mt-2 mb-5">
<div class="card-body p-4">
<h4 class="mb-3">
荷物受取先
</h4>
<div class="row">
<div class="col-lg-4">
<b>荷物受取先住所</b><br />
<span>{{ job.pickup_address }}</span>
</div>
<div class="col-lg-4">
<b>{{ job.pickup_name }}</b><br />
<span>{{ job.pickup_phone }}</span>
</div>
<div id="pickup_photo" class="col-lg-4">
{% if job.pickup_photo %}
<img src="{{ job.pickup_photo.url }}" class="rounded-lg photo" width="130" height="130">
{% else %}
<div class="photo-blank">写真があればここに表示されます。</div>
{% endif %}
</div>
</div>
<hr class="my-4" />
<h4 class="mb-3">
配達先
</h4>
<div class="row">
<div class="col-lg-4">
<b>配達先住所</b><br />
<span>{{ job.delivery_address }}</span>
</div>
<div class="col-lg-4">
<b>{{ job.delivery_name }}</b><br />
<span>{{ job.delivery_phone }}</span>
</div>
<div id="delivery_photo" class="col-lg-4">
{% if job.delivery_photo %}
<img src="{{ job.delivery_photo.url }}" class="rounded-lg photo" width="130" height="130">
{% else %}
<div class="photo-blank">写真があればここに表示されます。</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- マップ -->
<div class="d-flex justify-content-between">
<b class="text-secondary">配達の追跡</b>
<div>
<span id="job_status" class="badge badge-warning">
{% if job.get_status_display == 'Processing' %}
進行中 {{ job.get_status_display }}
{% else %}
完了 {{ job.get_status_display }}
{% endif %}
</span>
</div>
</div>
<div class="card bg-white mt-2">
<div class="card-body p-0">
<div id="map" style="height: 500px;"></div>
</div>
</div>
{% endblock %}
配達人の位置が配達依頼人の依頼ページで連動して見られるようになりました。

↓↓クリックして頂けると励みになります。
【54 | Web Socketの設定】 << 【ホーム】 >> 【56 | マップ リアルタイム表示】