2020/04/21 20:13
CANVASを使ってGooglemap取込→さらにマウスでマーキングしてjpg保存可能にする方法
WEBサイトのリニューアル案件で「Googlemapに任意の印(自由にマーキング)をつけた状態の画像を送ってもらえるメールフォーム」というものを依頼されまして、何となく近いものが完成したかな…?という訳で今回作成した方法をご紹介いたします。
※注意
この記事の投稿の後にGooglemapのAPIの仕様が変わった?のか、課金が必要なのか分からないですが
すんなりと使えない仕様になってます。そこはまだ調査中で不明なまま。
今回、実現した仕様
①Googlemapの埋め込み
まずはグーグルマップの埋め込みの実装。
これはAPIを使えば簡単にできます。
任意の場所を選んだら一度クリックして場所を決定させる必要があります。
(経度と緯度を確定させる為)
②マップの任意の場所にマウスで線を引いたりする
次に自由にマーキングする為の処理です。
これはCANVASを使えば実現できるかと思い、CANVASを採用しました。
上記①で取り込んだマップをCANVAS上に読み込む処理となります。
③マップ+線を画像として保存
線を引いた状態でのマップ画像として保存させる処理です。
ここに関しては右クリックで「画像を保存」とすれば簡単にダウンロードできる状態となってましたので、特にボタン等をつける事なく、それでやってもらう方向にしました。
ここまでの3つの処理のステップを2つに分けました。
埋め込みマップの読込・確定・場所の取り込み
↓
CANVASで取り込んだ画像へ線を引いたものを右クリックで保存
なので、実際に訪問者が作業する箇所は2箇所に分かれてます。
一応すぐに前のステップの確認ができたり、やり直したりできるというメリットはあります。
DEMOページ
下記デモページとなります。
詳しい操作説明も書いてます。
CANVASでGooglemapにマーキングするサンプルデモ
SAMPLE DEMO PAGE : デモページ
実装に必要なもの
Google Maps API
公式 : https://developers.google.com/maps
GooglemapのAPIを使った埋め込みになるので、API KEYの取得が必要となります。
先にAPIキーの取得から始めましょう。
【APIキー取得】
Googleアカウントを取得してる事が前提となります。
①Google Cloud Platform アクセス
Google Cloud : https://cloud.google.com/
②コンソール > プロジェクト作成
③プロジェクト選択
④APIの有効化
⑤APIキー取得
…と、なかなか面倒な流れとなりますが、この辺はググりながら頑張ってみてください。
fabric.jsファイルの読込み
「fabric.js」というのはCANVAS用のJavascriptライブラリです。
今回、これがあった方がよさげだったので使ってます。
ファイルDLして読み込んで使ってください。
「fabric.js」ファイル : ZIPファイルダウンロード
サンプルコード
必要となる箇所のサンプルコードです。
・【APIキーを記述】の箇所はご自身で取得したGooglemapのAPIキーを書いてください。
・「fabric.js」ファイルを読み込ませてください
<script src="https://maps.googleapis.com/maps/api/js?key=【APIキーを記述】" async defer></script>
<script src="https://maps.googleapis.com/maps/api/staticmap?key=【APIキーを記述】&signature=fo0rbfmtTh4rTBgRYo-oNjJiPDk="></script>
<script src="fabric.js"></script>
<form class="p-document-search" onsubmit="return false;">
住所 <input type="text" class="p-document-formarea" value="" placeholder="〇〇県〇〇市" id="address" name="address">
<button type="button" class="p-document-formbtn" value="検索" id="map_button">検索</button>
</form>
<div id="map-canvas"></div>
緯度 <input type="text" id="lat" name="lat" cols="15" val="" style="width: 140px; border-bottom: 1px solid #888;" disabled>
経度 <input type="text" id="lng" name="lng" cols="15" val="" style="width: 140px; border-bottom: 1px solid #888;" disabled>
<div class="p-document-form-btn-area">
<input type="button" id="screenshot" class="p-document-formbtn" value="場所を決定">
</div>
<canvas id="mycanvas" width="900" height="450">
Canvasに対応したブラウザを用意してください。
</canvas>
<div class="p-document-form-btn-area">
<p class="p-document-formlabel">線の色
<select id="penColor" class="p-document-formselect">
<option value="">選択</option>
<option value="white">白</option>
<option value="black">黒</option>
<option value="red" selected>赤</option>
<option value="green">緑</option>
</select>
線太さ
<select id="penWidth" class="p-document-formselect">
<option value="">選択</option>
<option value="3">細</option>
<option value="4">中</option>
<option value="5" selected>太</option>
</select>
<input type="button" id="erase" class="p-document-formbtn" value="消去">
</p>
</div>
<script>
$(function(){
var zoom = 8;
var format = 'png';
var imagerySet = 'r';
var lat = 34.679589;
var lng = 135.498813;
var map = null;
var height = 450;
var width = 900;
$(function(){
console.log('window loaded');
GetMap();
});
var canvas = new fabric.Canvas('mycanvas', {
});
$.extend(fabric.Canvas.prototype, {
loadBackground: function(img){
var self = this;
self.setHeight(img.height).setWidth(img.width);
self.object_scale_factor = img.height > img.width? img.width / 450 : img.height / 450;
self.font_size = self.font_size*self.object_scale_factor;
self.setBackgroundImage(img.src, self.renderAll.bind(self), {
originX: 'left',
originY: 'top',
left: 0,
top: 0,
scaleY: self.height / img.height,
scaleX: self.width / img.width
});
document.getElementById("penColor").selectedIndex = 3;
document.getElementById("penWidth").selectedIndex = 3;
var canvas = document.getElementById('mycanvas');
if(!canvas || !canvas.getContext) return false;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = $('#penColor').val();
ctx.lineWidth = $('#penWidth').val();
alert('「2」へお進みいただき、画像がうまく表示されているかをご確認ください。');
// 02.へ遷移
$('body,html').animate({scrollTop: $('#secondDesc').offset().top}, 400);
return;
}
});
canvas.clear();
function GetMap(){
map = new google.maps.Map(document.getElementById('map-canvas'), {
center: {lat: lat, lng: lng}, // 地図の中央位置を指定
zoom: zoom, // 地図のズームを指定
mapTypeId: google.maps.MapTypeId.HYBRID,
panControl: false, //上下左右のコントロールを表示するかどうか
zoomControl: true, //ズームのコントロールを表示するかどうか
mapTypeControl: true, // マップのタイプ切り替えを表示するかどうか
scaleControl: false, // 地図の縮尺要素を表示するかどうか
streetViewControl: false, // ストリートビュー表示のアイコンを表示するかどうか
overviewMapControl: false, // 地図右下に広域表示のアイコンを表示するかどうか
});
// クリックイベントを追加
map.addListener('click', function(e) {
getClickLatLng(e.latLng, map);
});
$('#map_button').click(function(){
var address = document.getElementById("address").value;
attrLatLngFromAddress(address);
});
//inputのvalueで検索して地図を表示
function attrLatLngFromAddress(address){
var geocoder = new google.maps.Geocoder();
geocoder.geocode({'address': address}, function(results, status){
if(status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();
var lng = results[0].geometry.location.lng();
// 小数点第六位以下を四捨五入した値を緯度経度にセット、小数点以下の値が第六位に満たない場合は0埋め
document.getElementById("lat").value = (Math.round(lat * 1000000) / 1000000).toFixed(6);
document.getElementById("lng").value = (Math.round(lng * 1000000) / 1000000).toFixed(6);
map.panTo(results[0].geometry.location);
map.setZoom(15);
}
});
}
}
function getClickLatLng(lat_lng, map) {
// 座標を表示
document.getElementById('lat').value = lat_lng.lat();
document.getElementById('lng').value = lat_lng.lng();
var lat = lat_lng.lat();
var lng = lat_lng.lng();
// マーカーを設置
var marker = new google.maps.Marker({
position: lat_lng,
map: map
});
// 座標の中心をずらす
// http://syncer.jp/google-maps-javascript-api-matome/map/method/panTo/
map.panTo(lat_lng);
}
$('#screenshot').bind('click', function() {
captureScreen(map);
});
function captureScreen(map) {
var center = map.center;
height = map.__gm.innerContainer.clientHeight;
width = map.__gm.innerContainer.clientWidth;
mapSize = map.__gm.innerContainer.clientWidth + "x" + map.__gm.innerContainer.clientHeight; //w,h
zoom = map.zoom;
//imagerySet = map.getImageryId();
if(imagerySet!='Road')imagerySet = 'AerialWithLabels';
lat = $('#lat').val();
lng = $('#lng').val();
var img = new Image();
img.src= "https://maps.googleapis.com/maps/api/staticmap?center="+lat+","+lng+"&zoom="+zoom+"&size="+mapSize+"&maptype="+map.mapTypeId+"&key=【APIキーを記述】";
$(img).one("load", function() {
canvas.loadBackground(img);
})
}
});
</script>
<script type="text/javascript">
$(function(){
var canvas = document.getElementById('mycanvas');
if(!canvas || !canvas.getContext) return false;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = $('#penColor').val();
ctx.lineWidth = $('#penWidth').val();
var startX,
startY,
x,
y,
borderWidth = 5,
isDrawing = false;
$('#mycanvas').mousedown(function(e) {
isDrawing = true;
startX = e.pageX - $(this).offset().left - borderWidth;
startY = e.pageY - $(this).offset().top - borderWidth;
})
.mousemove(function(e) {
if(!isDrawing) return;
x = e.pageX - $(this).offset().left - borderWidth;
y = e.pageY - $(this).offset().top - borderWidth;
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(x, y);
ctx.stroke();
startX = x;
startY = y;
})
.mouseup(function() {
isDrawing = false;
})
.mouseleave(function() {
isDrawing = false;
});
$('#penColor').change(function() {
ctx.strokeStyle = $(this).val();
});
$('#penWidth').change(function() {
ctx.lineWidth = $(this).val();
});
$('#erase').click(function() {
if(!confirm('本当に消去しますか?')) return;
ctx.clearRect(0,0,canvas.width,canvas.height);
});
});
</script>
※スタイル(css)はご自由にあててください
まとめ
・Google Maps API取得
・fabric.jsファイルの読込み
・コード記述
・各cssは適当にあてる
これでだいたいは動くと思います。
以上がCANVASを使ってさらにマウスで線を描いてさらに画像として保存するページ作成の方法となります。
もうちょっとスマートな書き方があるのでしょうけど、取り合えずの実装として作ってみました。
埋め込みのマップからクリックして画像を取り込んで、そこから線引っ張って右クリで保存…
というステップの長さがちょっと気になるところですね。
やりたい事は実現できたので今回はここまでとなります。
現場から以上です!
3743