Movable TypeでGoogleMAP座標のコンテンツタイプ追加してみた。

らら
らら

はじめに

コンテンツタイプにGoogleMAPの座標設定あると便利かとおもって・・・

チャレンジしてみた・・

コードはっく。

ヒントは、コンテンツデータに日付と時間が2つのエリアあるのでこちらを参考にしようかと・・・

lib\MT\ContentFieldTypeにそれぽい・・感じが・・

DateTime.pmとCommon.pmが関係ありそう・・

field_html_params、data_load_handlerとかぐらいでいけそう??

lib\MT\ContentFieldType.pmでレジストリの登録は参考にできそう

データベースは、mt_content_typeでblob形式だったので・・・参考にできなかった・・

コンテンツデータはmt_cd、こっちもblob・・・

最終のディレクトリ構造


ContentMapCoordtField
│  config.yaml
├─lib
│  │  ContentMapCoordtField.pm
│  └─ContentMapCoordtField
│	  │  App.pm
│	  └─ContentFieldType
│			  ContentMapCoordtField.pm
│			  
└─tmpl
	│  config.tmpl
	│  selectmap.tmpl
	├─content_field_type_options
	│	  googlemap_coordinate.tmpl
	└─field_html
			field_html_googlemap_coordinate.tmpl

config.yaml

ContentFieldType.pmからレジストリの値とか参考にyaml形式に置き換えて・・


id: ContentMapCoordtField
name: ContentMapCoordtField
version: 1.0
author_name: 
author_link: 
description: <__trans phrase="This plugin adds a GoogleMap coordinate content field type.">
l10n_lexicon:
	ja:
		This plugin adds a GoogleMap coordinate content field type.: "GoogleMap座標コンテンツフィールドを追加"
		GoogleMap Coordinate: GoogleMap座標
		Google Maps API key: Google Maps API key
		Google Maps API ID: Google Maps ID
		Google Maps latitude : 緯度(lat)
		Google Maps longitude : 経度(lng)
		Google Maps Default Latitude : 地図中心緯度(lat)
		Google Maps Default Longitude : 地図中心 経度(lng)
applications:
	cms:
		methods:
			google_map_popup: ContentMapCoordtField::App::google_map_popup
tags:
	function:
		GoogleMapKey:	   $ContentMapCoordtField::ContentMapCoordtField::_hdlr_googlemap_key
		GoogleMapId:		$ContentMapCoordtField::ContentMapCoordtField::_hdlr_googlemap_id
		GoogleMapLatitude:  $ContentMapCoordtField::ContentMapCoordtField::_hdlr_googlemap_latitude
		GoogleMapLongitude: $ContentMapCoordtField::ContentMapCoordtField::_hdlr_googlemap_longitude
content_field_types:
	googlemap_coordinate:
		label: GoogleMap Coordinate
		data_type: 'text'
		order: 300,
		icon_class: 'ic_singleline'
		can_data_label_field: 0
		data_load_handler: $ContentMapCoordtField::ContentMapCoordtField::ContentFieldType::ContentMapCoordtField::data_load_handler
		field_value_handler: $ContentMapCoordtField::ContentMapCoordtField::ContentFieldType::ContentMapCoordtField::field_value_handler
		field_html: 'field_html/field_html_googlemap_coordinate.tmpl'
		field_html_params: $ContentMapCoordtField::ContentMapCoordtField::ContentFieldType::ContentMapCoordtField::field_html_params
		list_props:
			googlemap_coordinate:
				base: '__virtual.string'
				col: 'value_varchar'
				terms: $Core::MT::ContentFieldType::Common::terms_text
		options_html: 'content_field_type_options/googlemap_coordinate.tmpl'
		options:
			- label
			- description
			- required
			- display
			- initial_value
settings:
	googlemaps_api_key:
		default:
	googlemaps_id:
		default:
	googlemaps_def_latitude:
		default:
	googlemaps_def_longitude:
		default:
system_config_template: config.tmpl

lib\ContentMapCoordtField.pm

ここでは、プラグインの設定の値をMTタグとしてどこでも使えるように・・タグをつくる


package ContentMapCoordtField;
use strict;
use utf8;
use warnings;
my $PluginKey = 'ContentMapCoordtField';
sub instance {
	my ($app) = @_;
	$app ||= 'MT';
	$app->component($PluginKey);
}
#<$MTGoogleMapKey$>
sub _hdlr_googlemap_key {
	my $plugin  = instance();
	return $plugin->get_config_value('googlemaps_api_key');
}
#<$MTGoogleMapID$>
sub _hdlr_googlemap_id {
	my $plugin  = instance();
	return $plugin->get_config_value('googlemaps_id');
}
#<$MTGoogleMapLatitude$>
sub _hdlr_googlemap_latitude {
	my $plugin  = instance();
	return $plugin->get_config_value('googlemaps_def_latitude');
}
#<$MTGoogleMapLongitude$>
sub _hdlr_googlemap_longitude {
	my $plugin  = instance();
	return $plugin->get_config_value('googlemaps_def_longitude');
}
1;

lib\ContentMapCoordtField\App.pm

ここでは、GoogleMap座標選択ようのPOPUPだすテンプレート・・

一応MT経由で表示するように・・


package ContentMapCoordtField::App;
use strict;
use File::Basename;
my $PluginKey = 'ContentMapCoordtField';
sub instance {
	my ($app) = @_;
	$app ||= 'MT';
	$app->component($PluginKey);
}
sub google_map_popup {
	my $app = shift;
	my $param = {};
	my $q	= $app->param;
	my $lat	= $q->param('lat');
	my $lng	= $q->param('lng');
	my $lat_id	= $q->param('lat_id');
	my $lng_id	= $q->param('lng_id');
	my $plugin  = instance();
	my $googlemaps_api_key			= $plugin->get_config_value('googlemaps_api_key');
	my $googlemaps_def_latitude		= $plugin->get_config_value('googlemaps_def_latitude');
	my $googlemaps_def_longitude	= $plugin->get_config_value('googlemaps_def_longitude');
	my $website_terms = undef;
	if (my $blog_id = $app->param('blog_id')) {
		$website_terms = { 'id' => $blog_id };
	}
	$param->{lat}						= $lat;
	$param->{lng}						= $lng;
	$param->{lat_id}					= $lat_id;
	$param->{lng_id}					= $lng_id;
	$param->{googlemaps_api_key}		= $googlemaps_api_key;
	$param->{googlemaps_def_latitude}	= $googlemaps_def_latitude;
	$param->{googlemaps_def_longitude}	= $googlemaps_def_longitude;
	my $plugin = MT->component('ContentMapCoordtField');
	$plugin->load_tmpl('selectmap.tmpl', $param);
}
1;

lib\ContentMapCoordtField\ContentFieldType\ContentMapCoordtField.pm

ここではコンテンツタイプの定義、必須とか・・設定

あとContentFieldValueでの加工、保存のデータ構築?


package ContentMapCoordtField::ContentFieldType::ContentMapCoordtField;
use strict;
use warnings;
sub field_html_params {
	my ( $app, $field_data ) = @_;
	my ( $lat, $lng );
	my $value = $field_data->{value} || '';
	# for initial_value.
	$value = '' unless defined $value;
	$lat = '';
	$lng = '';
	if ( defined $value && $value ne '' ) {
		( $lat, $lng )	= split ', ', $value;
	}
	my $required = $field_data->{options}{required} ? 'required' : '';
	{	googlemap_coordinate_data => $value,
		 lat => $lat,
		 lng => $lng,
		 required => $required,
	};
}
sub data_load_handler {
	my ( $app, $field_data ) = @_;
	my $id   = $field_data->{id};
	my ( $lat, $lng );
	$lat = $app->param( 'lat-' . $id );
	$lng = $app->param( 'lng-' . $id );
	my $coordinate = $lat .", ". $lng;
	return $coordinate;
}
sub field_value_handler {
	my ( $ctx, $args, $cond, $field_data, $value ) = @_;
	my $coordinate = lc $args->{coordinate} || 'lng';
	my ( $lat, $lng );
	return if ( $coordinate ne 'lat' and $coordinate ne 'lng' );
	( $lat, $lng )	= split ', ', $value;
	if($coordinate eq 'lat') {
		return $lat;
	}elsif ($coordinate eq 'lng') {
		return $lng;
	} else {
		return $value;
	}
}
1;

tmpl\config.tmpl

プラグインの設定でつかうHTMLコード


<mtapp:setting
  id="contentmapcoordtfield-template_type"
  label="<__trans phrase="GoogleMap Coordinate">">
  <label for="contentmapcoordtfield-googlemaps_api_key"><__trans phrase="Google Maps API key"></label>
  <input type="text" name="googlemaps_api_key" id="contentmapcoordtfield-googlemaps_api_key" class="form-control w-100" value="<mt:var name="googlemaps_api_key" escape="html">">
  <label for="contentmapcoordtfield-def_latitude"><__trans phrase="Google Maps ID"></label>
  <input type="text" name="googlemaps_id" id="contentmapcoordtfield-googlemaps_id" class="form-control w-100" value="<mt:var name="googlemaps_id" escape="html">">
  <label for="contentmapcoordtfield-def_latitude"><__trans phrase="Google Maps Default Latitude"></label>
  <input type="text" name="googlemaps_def_latitude" id="contentmapcoordtfield-googlemaps_def_latitude" class="form-control w-100" value="<mt:var name="googlemaps_def_latitude" escape="html">">
  <label for="contentmapcoordtfield-def_longitude"><__trans phrase="Google Maps Default Longitude"></label>
  <input type="text" name="googlemaps_def_longitude" id="contentmapcoordtfield-googlemaps_def_longitude" class="form-control w-100" value="<mt:var name="googlemaps_def_longitude" escape="html">">
</mtapp:setting>

tmpl\selectmap.tmpl

GoogleMAPを使って、座標を取得できるようにしたHTML

これはどこからか、拝借したもの・・拝借先・・覚えてません。。すみません・・

マーカーの画像を使わないタイプがよかったので・・mt-staticとか個別になると面倒になるので・・


<!DOCTYPE html>
<html lang="ja" itemscope itemtype="http://schema.org/WebPage">
<head>
<meta charset="UTF-8">
<title>経度・緯度</title>
<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=true&key=<mt:var name="googlemaps_api_key">"></script>
<script type="text/javascript">
function value_set(lat,lng){
	if(!lat && !lng) {
		return [<mt:var name="googlemaps_def_latitude">,<mt:var name="googlemaps_def_longitude">];
	}
	return [lat,lng]
}
	var Lat_id			= "<mt:var name="lat_id">";
	var Lng_id			= "<mt:var name="lng_id">";
	var Lat				= "<mt:var name="lat">";
	var Lng				= "<mt:var name="lng">";
	var latlon;
	latlon = value_set(Lat,Lng);
	// 初期設定項目
	var initLat			= latlon[0];
	var initLng			= latlon[1];		// 初期表示する経度
	var crossSize		= 19;				// クロスの長さ
	var zoomLevelMin	= 0;				// ズームレベルの最大値
	var zoomLevelMax	= 21;				// ズームレベルの最小値
	var	zoomLevelInit	= 18;				// ズームレベルの初期表示値
	var mapOptions = {						// mapオプション
		zoom: zoomLevelInit,
		center: new google.maps.LatLng(initLat,initLng),
		navigationControl: true,
		scaleControl: true,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	var map;								// gmapオブジェクト
	var controlCross;						// センターに表示するクロス
	var lat		= 0;						// 緯度保持
	var lng		= 0;						// 経度保持
	var geocoder;							// ジオコーダー
	function createGmap()
	{
		geocoder = new google.maps.Geocoder();
		map = new google.maps.Map(document.getElementById("gmap"),mapOptions);
		// 中心マーカーの表示
		createCenterMaker();
		// イベントリスナーの登録
		google.maps.event.addListener(map,'center_changed',OnMapDraged);			// ドラッグ終了
		OnMapDrag();
	}
	function CrossControl(controlDiv, map)
	{
		var mapDiv = map.getDiv();
		var x = (mapDiv.offsetWidth  - crossSize + 1) / 2;
		var y = (mapDiv.offsetHeight - crossSize + 1) / 2;
		var controlCrossV = document.createElement('DIV');
		controlCrossV.style.position = 'absolute'
		controlCrossV.style.borderStyle = 'none none none solid';
		controlCrossV.style.borderWidth = '1px';
		controlCrossV.style.borderColor = 'red';
		controlCrossV.style.height = crossSize + 'px';
		controlCrossV.style.left = 0+ "px";
		controlCrossV.style.top = y+ "px";
		controlDiv.appendChild(controlCrossV);
		var controlCrossH = document.createElement('DIV');
		controlCrossH.style.position	= 'absolute'
		controlCrossH.style.borderStyle = 'solid none none none';
		controlCrossH.style.borderWidth = '1px';
		controlCrossH.style.borderColor = 'red';
		controlCrossH.style.width		= crossSize + 'px';
		controlCrossH.style.left		= (crossSize / 2) * -1+ "px";
		controlCrossH.style.top = mapDiv.offsetHeight / 2+ "px"; 
		controlDiv.appendChild(controlCrossH);
	}
	function createCenterMaker() {
		var CrossControlDiv = document.createElement('DIV');
		var crossControl	= new CrossControl(CrossControlDiv,map);
		CrossControlDiv.index = 1;
		map.controls[google.maps.ControlPosition.TOP].push(CrossControlDiv);
	}
	function moveMap()
	{
		lng = parseFloat(document.points.lng.value);
		lat = parseFloat(document.points.lat.value);
		map.panTo(new google.maps.LatLng(lat,lng));
	}
	function OnMapDrag()
	{
		latlng = map.getCenter();
		document.points.lng.value = latlng.lng();
		document.points.lat.value = latlng.lat();
	}
	function OnMapDraged()
	{
		OnMapDrag();
	}
function popup_end(lng,lat)
{
	opener.document.getElementById(Lat_id).value = lat;
	opener.document.getElementById(Lng_id).value = lng;
	window.close();
}
</script>
</head>
<body onload="createGmap()">
<div id="gmap" style="width: 500px; height: 400px"></div>
<br />
<form name="points"  method="post" action="">
	<input type="text" id="lat" name="lat" size="31" maxlength="20">
	<input type="text" id="lng" name="lng" size="32" maxlength="20">
	<br />
	<input type="hidden" name="url" value="">
	<input type="hidden" name="lat_tky" value="">
	<input type="hidden" name="lon_tky" value="">
	<input type="button" value="決定" onclick="popup_end(this.form.lng.value,this.form.lat.value)">
	</font>
</form>
</body>
</html>

tmpl\content_field_type_options\googlemap_coordinate.tmpl

これは、コンテンツデータの方で、表示するHTML


<mt:app:ContentFieldOptionGroup
   type="googlemap_coordinate">
  <mtapp:ContentFieldOption
     id="googlemap-coordinate-initial-lat"
     label="<__trans phrase="Google Maps latitude">">
    <input ref="initial_lat" type="text" name="initial_lat" id="googlemap-coordinate-initial_date" class="form-control date-field w-25" value={ options.initial_lat } placeholder="">
  </mtapp:ContentFieldOption>
  <mtapp:ContentFieldOption
     id="googlemap-coordinate-initial-lng"
     label="<__trans phrase=" oogle Maps longitude">">
    <input ref="initial_lng" type="text" name="initial_lng" id="dgooglemap-coordinate-initial_time" class="form-control time-field w-25" value={ options.initial_lng } placeholder="">
  </mtapp:ContentFieldOption>
</mt:app:ContentFieldOptionGroup>

tmpl\field_html\field_html_googlemap_coordinate.tmpl

ここでは、MAP選択POPUPに値を渡す部分を仕込んでおく、また、修正時、MAP選択した場合、その座標で地図を表示するようにごにょごにょしておく。

一応id名を渡しておくので、複数あった場合も対応できるかと・・・


<div class="row form-inline googlemap-coordinate-field-container group-container">
	<div class="col d-none d-md-block mb-0">
		<span>
			<input type="text" name="lat-<mt:var name="content_field_id" escape="html">" id="lat-<mt:var name="content_field_id" escape="html">" class="lat-field form-control text group html5-form content-field" value="<mt:var name="lat" escape="html">" placeholder="" mt:watch-change="1" mt:raw-name="1" <mt:var name="required">>
		</span>
		<span class="separator"> <__trans phrase=", "></span>
		<span>
			<input type="text" name="lng-<mt:var name="content_field_id" escape="html">" id="lng-<mt:var name="content_field_id" escape="html">" class="lng-field form-control text group html5-form content-field" value="<mt:var name="lng" escape="html">" placeholder="" mt:watch-change="1" mt:raw-name="1" <mt:var name="required">>
		</span>
		<div class="mt-3">
			<a id="mappopup_<mt:var name="content_field_id" escape="html">">
			<svg role="img" class="mt-icon--primary mt-icon--sm first-child last-child"><title class="first-child">Add</title><use xlink:href="/test_mt/mt/mt-static/images/sprite.svg#ic_add" class="last-child"></use></svg>
			座標選択
			</a>
		</div>
	</div>
</div>
<mt:setvarblock name="jq_js_include" append="1">
	$(document).on("click", "[id^=mappopup_]", function(){
		var id_str = $(this).attr("id");
		stArray = id_str.split("_");
		var id_lat = "lat-"+stArray[1];
		var id_lng = "lng-"+stArray[1];
		var lat = $('#'+id_lat).val();
		var lng = $('#'+id_lng).val();
		var url ='<mt:var name="script_url">?__mode=google_map_popup&lat_id=' + id_lat + '&lng_id=' + id_lng + '&lat=' + lat + '&lng=' + lng;
		win = window.open(url,"map_win","toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=no,width=600,height=600");
	});
</mt:setvarblock>

完成のイメージ

プラグイン

GoogleMAP座標のコンテンツタイプ

プラグインの設定

GoogleMAP座標のコンテンツタイプ

コンテンツタイプのイメージ

GoogleMAP座標のコンテンツタイプ

コンテンツデータの入力方法

GoogleMAP座標のコンテンツタイプ

ContentMapCoordtFieldでできたMTタグ

プラグインの設定で設定した値を取得できます。

MTGoogleMapLatitude、MTGoogleMapLongitudeは地図の中心座標です。地図選択POPUP等でも使用しています、


<$MTGoogleMapId$>
<$MTGoogleMapKey$>
<$MTGoogleMapLatitude$>
<$MTGoogleMapLongitude$>

ContentFieldValue

おいらが解析不十分で・・・内部的な保存が"緯度, 経度"で保存しています。CSVなどでエクスポートした場合、35.16991042521872, 136.90756276140058で1つで入ります。

なので、分割するためにContentFieldValueに要素を追加しています。

coordinate要素 指定なしは、35.16991042521872, 136.90756276140058で出力されます。

lat指定で35.16991042521872

lng指定で136.90756276140058


<mt:ContentFieldValue coordinate="lat">		緯度
<mt:ContentFieldValue coordinate="lng">		経度

作成したプラグインの使用例

Googlemap複数マーカー

google.maps.Markerがサポートなくなるそうなので、 google.maps.marker.AdvancedMarkerViewを使っています。


<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=<$MTGoogleMapKey$>&callback=initMap&libraries=marker&v=beta" defer></script>
<script>
var markers  = [
<mt:Contents content_type="アクセスマップ">
['<p><mt:ContentField content_field="店舗名"><mt:ContentFieldValue></mt:ContentField></p>','<mt:ContentField content_field="GoogleMAP座標"><mt:ContentFieldValue coordinate="lat"></mt:ContentField>','<mt:ContentField content_field="GoogleMAP座標"><mt:ContentFieldValue coordinate="lng"></mt:ContentField>'],
</mt:Contents>
];
function initMap() {
	var myOptions = {
		zoom: 12,
		center: new google.maps.LatLng(<$MTGoogleMapLatitude$>,<$MTGoogleMapLongitude$>),
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		streetViewControl:false,
		mapId: 'DEMO_MAP_ID',		// 本番は<$MTGoogleMapId$>
	};
	var map = new google.maps.Map(document.getElementById("map"), myOptions);
	var infoWindow = new google.maps.InfoWindow();
	markers.forEach( function(value, i) {
		var gtitle	= markers[i][0];
		var glat	= parseFloat(markers[i][1]);
		var glng	= parseFloat(markers[i][2]);
		var Img = document.createElement("img");
		Img.src = "./marker1.png";
		Img.width	= 40;	// 横サイズ(px)
		Img.height	= 40;	// 縦サイズ(px)
		var marker = new google.maps.marker.AdvancedMarkerView({
			position : {
				'lat': glat,
				'lng': glng
			},
			map,
			title: gtitle,
			content: Img
		});
		marker.addListener("click", ({ domEvent, latLng }) => {
			const { target } = domEvent;
			infoWindow.close();
			infoWindow.setContent(marker.title);
			infoWindow.open(marker.map, marker);
		});
	});
}
window.initMap = initMap;
</script>
<style>
#map {
	height: 600px;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</code></pre>

上記ソースの結果はこんな感じ・・

GoogleMAP座標のコンテンツタイプ

制限事項

デフォルト値設定 デバックしてません・・・

入力値のチェック入っていません・・・

必須はどちらか1個かもしれません・・・

システムの検索・置換ロジックは、省いてます・・解析不足・・・

さいごに

GoogleMapは有償になったので、それでも問題ないって場合ですね・・

あたらしいのは・・mapIdとかの指定、div id="map"にサイズ指定しておかないとでなかったりと。。。不便・・・

製品版に標準につけてほしい。w

あ、あと。。サポートしていませんから。。うごかんぞぉーとか、クレーム入れないで・・w

あと、過去やったGoogleMapのコードが動かなくて・・?そっちに時間が・・かかったよぉ・・関連からどうぞ。。

では・・

関連

GoogleMapが動かない・・?
https://www.omakase.net/blog/2024/06/googlemap-1.html

関連記事