パブリックタグ登録申請サンプルサイト ソース解説

場所情報コード入力・確認ページ2

概要

入力時に使用される次の機能について解説する。

解説

地図から緯度・経度を取得

地理院地図の準備
地理院地図のソースコードをhttps://github.com/gsi-cyberjapan/gsimapsより取得し、gsimap/ 以下に設置する。 地図画面に操作ボタンを用意するため、gsimap/js/ucode_sample.jおよびgsimap/css/ucode_sample.cssを後述する内容で作成し、gsimap/index.htmlで読み込む。地理院地図のJavaScriptよりも後に実行されることを確実にするため、gsimaps.jsよりも後に記述する。
<link rel="stylesheet" type="text/css" href="./css/gsimaps.css"/>
    <link rel="stylesheet" type="text/css" href="./css/ucode_sample.css"/>

    <script type="text/javascript" src="js/gsimaps.js"></script>
    <script type="text/javascript" src="js/ucode_sample.js"></script>
// gsimap/js/ucode_sample.js
$(function(){//ページ読み込み後に自動実行
    if(parent.edit_map){
        //「決定」および「戻る」ボタンを含む表示部分作成
        var div = $('<div>').attr('id','ucode_sample');
        //「決定」ボタン
        var elem1 = $('<a>').attr({
            'href' : 'javascript:void(0);'
        }).html('決定').click(function() {
            var zoom = GSI.GLOBALS.map.getZoom();
            if(zoom<15){
                alert('ズームレベル15以上にズームしてください。\n現在のズームレベルは'+zoom+'です。');
                return false;
            }
            var latlng = GSI.GLOBALS.map.getCenter();
            parent.set_latlng(latlng.lat,latlng.lng,zoom);
            parent.close_map();
        }).attr('id','ucode_sample_ok');

        div.append(elem1);

        //「戻る」ボタン
        var elem2 = $('<a>').attr({
            'href' : 'javascript:void(0);'
        }).html('戻る').click(function() {
            parent.close_map();
        }).attr('id','ucode_sample_cancel');

        div.append(elem2);

        //地理院地図の右下に追加
        var a = new GSI.Control.Button(div[0], {
            position : 'bottomright'
        });
        a.addTo(GSI.GLOBALS.map);

        //左端がフッターボタンに重なるときは、幅を半分にしてボタンを上下に並べる
        if($('#map').width()/3 < $('#ucode_sample').width()){
            $('#ucode_sample').width($('#ucode_sample').width()/2);
        }

    }
});

地理院地図では、jQueryが使用されており、実行順序が地理院地図のJavaScriptコードよりも後になるよう、$(function(){});を使用してボタンを画面に追加する。 $('<div>')はHTML要素を作成するjQueryの記述である。戻り値はHTML要素を表現するjQueryオブジェクトであり、このようなオブジェクトは、メソッドとしてattributeを設定する.attr(name,value)や、DOMオブジェクトのinnerHTMLを設定する.html(str)、クリック時のイベントハンドラ.click(func)などがある。 これらのメソッドは、返り値として対象のjQueryオブジェクトそのものをとるため、メソッドチェーンを利用して記述することができる。 地理院地図のコントロールとして「決定」および「戻る」の2つのボタンを備えるdivを作成する。

GSI.GLOBALS.mapは、GSI.Mapクラスのインスタンスであり、LeafletのMapを拡張しているので、LeafletのMapクラスと同じメソッドを使用することができる。 GSI.GLOBALS.map.getZoom();によってズームレベル、GSI.GLOBALS.getCenter();によって中心座標を取得している。

@CHARSET "UTF-8";
/* gsimap/css/ucode_sample.css */
#ucode_sample_ok {
    display: inline-block;
    background-color: #e80000;
    padding: 0.4em 18px;
    color: #ffffff;
    cursor: pointer;
    border: solid 1px #222;
    font-size: 110%;
    text-decoration: none;
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
    behavior: url(border-radius.htc);
    box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.3);
    -moz-box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.3); /* Firefox */
    -webkit-box-shadow: 0px 0px 2x 1px rgba(0, 0, 0, 0.3);
    margin: 0.1em 0.2em;
    /* Chrome, Safari */
}

#ucode_sample_cancel {
    opacity: .90;
    filter: alpha(opacity = 90);
    -ms-filter: "alpha(opacity=90)";
    -khtml-opacity: .90;
    -moz-opacity: .90;
    display: inline-block;
    background-color: #333;
    padding: 0.4em 18px;
    color: #ffffff;
    cursor: pointer;
    border: solid 1px #222;
    font-size: 110%;
    text-decoration: none;
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
    behavior: url(border-radius.htc);
    box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.3);
    -moz-box-shadow: 0px 0px 2px 1px rgba(0, 0, 0, 0.3); /* Firefox */
    -webkit-box-shadow: 0px 0px 2x 1px rgba(0, 0, 0, 0.3);
    margin: 0.1em 0.2em;
    /* Chrome, Safari */

}
表示ページ側の準備
地図を表示するためのiframeとフォームを表示するためのdivを用意する。
<iframe id="map"></iframe>
<div id="contents">
フォーム
</div>
初期状態では地図のiframeは非表示とし、また、地図を表示する際には画面いっぱいに表示するために次のようにCSSを設定する。
<style>
#map,#geocode{
    display: none;
    width: 100%;
    height: 100vh;
    border: none;
}
</style>
表示用のJavaScriptは下記のようになる。なお、init()はbodyタグにonload="init()"と記述することによりページ読み込み時に自動実行される。
var map = null;
var edit_map = true;
function init(){
    map = document.getElementById('map');
}
function open_map(){
    document.getElementById('contents').style.display = 'none';
    map.style.display = 'block';
    var latlng = get_latlng();
    if(!(latlng.lat > 0 && latlng.lng > 0)){
        latlng = {lat:36.104638,lng:140.084619};
    }
    map.src="gsimap/#15/"+latlng.lat+'/'+latlng.lng;
}
function close_map(){
    map.style.display = 'none';
    document.getElementById('contents').style.display = 'block';
}
function get_latlng(){
    return {lat:document.getElementsByName('data[latitude]')[0].value,lng:document.getElementsByName('data[longitude]')[0].value};
}
function set_latlng(lat,lng,zoom){
    document.getElementsByName('data[latitude]')[0].value = lat;
    document.getElementsByName('data[longitude]')[0].value = lng;
    if(zoom){
        if(zoom>=18){
            document.getElementsByName('data[horizontal_accuracy]')[0].value = 20;
            accuracy_change('horizontal');
        }else{
            document.getElementsByName('data[horizontal_accuracy]')[0].value = 30;
            accuracy_change('horizontal');
        }
    }
    display_latlng();
    get_altitude();
}

地図を表示する際にはopen_map()を呼び出す。document.getElementById('contents').style.display = 'none';によってフォームを非表示とし、map.style.display = 'block';によって地図を表示させる。 既に緯度・経度が入力されている場合には、その場所を中心とし、入力されていない場合には、あらかじめ決められた場所を中心とする。 map.src="gsimap/#15/"+latlng.lat+'/'+latlng.lng;とすることで、指定した場所を中心とした地図を表示する。

地図の「戻る」または「決定」ボタンを押すと、close_map()が呼び出される。

「決定」ボタンが押された際には、set_latlng(lat,lng,zoom)が呼び出され、緯度・経度が入力欄にセットされる。 document.getElementsByName('data[horizontal_accuracy]')[0].value = 20;は、ズームレベルに応じて水平精度をセットしている。水平精度の入力欄は相対精度及び絶対精度に分割されているため、値をセットした後にaccuracy_change('horizontal');を呼び出す必要がある。 display_latlng();は、緯度・経度を10進と60進の切り替えを可能としているため、緯度経度をセットした後に呼び出さなければならない。 get_altitude()は、高度を自動的に取得して入力する関数である。

現在地から緯度・経度を取得

現在地の緯度・経度を取得するために、JavaScriptのgeolocationクラスを使用する。以下のdevice_latlng()を実行することで、緯度・経度を取得して入力欄に入力する一連の処理を開始する。
function device_latlng(){
    var btn = document.getElementById('device_latlng_btn');
    btn.setAttribute('disabled','disabled');
    btn.value = '現在地取得中…';
    if( navigator.geolocation ){
        navigator.geolocation.getCurrentPosition( device_latlng_got , device_latlng_error , {'enableHighAccuracy':true,'timeout':'5000'} ) ;
    }else{
        alert( "お使いの機器またはブラウザは位置情報の取得に対応していません。" ) ;
    }
}
function device_latlng_got(position){
    var btn = document.getElementById('device_latlng_btn');
    btn.removeAttribute('disabled');
    btn.value = '現在地を入力';
    if(position.coords.latitude>0 && position.coords.longitude>0){
        set_latlng(position.coords.latitude,position.coords.longitude);
        alert('位置情報の取得に成功しました。');
    }else{
        alert('位置情報の取得に失敗しました。\n緯度:'+position.coords.latitude+' 経度:'+position.coords.longitude);
    }
}
function device_latlng_error(error){
    var btn = document.getElementById('device_latlng_btn');
    btn.removeAttribute('disabled');
    btn.value = '現在地を入力';
    if(error.code == 1)
        alert('ブラウザの設定を確認し、現在地の使用を許可してください。');
    else
        alert('位置情報の取得に失敗しました。\nエラーコード:'+error.code);
}
geolocationが使用できない(機能がない)ブラウザでは、navigator.geolocationがundefinedとなるので、エラーメッセージを出力する。 それ以外のブラウザでは、navigator.geolocation.getCurrentPosition()を実行することで現在地を取得することができる。 引数のdevice_latlng_gotおよびdevice_latlng_errorは位置取得成功時及び失敗時に呼び出される関数であり、第3引数は動作を設定するオプションである。 device_latlng_btn(position)は位置取得時に実行される。position.coords.latitudeおよびposition.coords.longitudeによって緯度・経度を取得できる。 device_latlng_error(error)は位置取得失敗時に実行され、error.codeによってエラーコードを取得することができる。

住所から緯度・経度を取得

検索ページの準備
予めGoogle Maps JavaScript APIで使用できるAPIキーを取得し、config.phpに記載しておく。
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap&key=<?php echo GOOGLE_API_KEY ?>"" type="text/javascript"></script>
以上の記述により、Google MapのJavaScriptを読み込み、地図やジオコーダーが使用できるようになる。 JavaScriptを読み込むときの引数にcallback=initMapを指定しているので、検索ページにinitMap()という関数を用意しておくと、これがページ読み込み後に自動実行される。 この関数で地図の初期化などの処理を行う。
<div id="contents">
<input type="text" name="address"><input type="button" value="検索" onclick="do_geocode()">
<div id="search_result">住所・ランドマークなどを入力して検索ボタンを押してください</div>
<input type="button" value="戻る" onclick="parent.close_geocode();">
<div id="map"></div>
</div>
以上のHTMLにより、検索の入力欄、結果表示欄、地図の表示スペースを用意する。 「検索」ボタンを押すと、do_geocode()が実行され、ジオコーディングが実行される。 「戻る」ボタンを押すと、input.phpのclose_geocode()が実行され、入力フォーム画面に戻る。
var map;
var mapdiv;
var geocoder;
function initMap(){
    mapdiv = document.getElementById("map");
    mapdiv.style.height = Math.min(mapdiv.clientWidth,window.innerHeight*0.8)+'px';
    window.onresize=function(){
        mapdiv.style.height = Math.min(mapdiv.clientWidth,window.innerHeight*0.8)+'px';
    }

    geocoder = new google.maps.Geocoder();

    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: 36.104638,lng:140.084619},
        zoom: 10,
    });
}
上記のJavaScriptコードによりGoogle Mapを表示する。
var markers = [];
var rr = [];
function do_geocode(){
    var address = document.getElementsByName("address")[0].value;
    if(address){
        geocoder.geocode( { 'address': address}, function(results, status) {
            var result_div = document.getElementById("search_result");
            result_div.innerHTML = '';
            while(m = markers.pop())m.setMap(null);
            rr = [];
            if (status == google.maps.GeocoderStatus.OK) {
                rr = results;
                var bounds = new google.maps.LatLngBounds();
                for(var i=0; i<results.length; i++){
                    var r = results[i];
                    var r_div = document.createElement('div');
                    r_div.classList.add('result');
                    result_div.appendChild(r_div);
                    var d1 = document.createElement('div');
                    d1.classList.add('formatted_address');
                    d1.innerHTML = r.formatted_address;
                    r_div.appendChild(d1);
                    var d2 = document.createElement('div');
                    d2.classList.add('buttons');
                    r_div.appendChild(d2);
                    var b1 = document.createElement('input');
                    b1.setAttribute('type','button');
                    b1.setAttribute('value','地図の中央に');
                    b1.onclick=callerVewMarker(i);
                    d2.appendChild(b1);
                    var b2 = document.createElement('input');
                    b2.setAttribute('type','button');
                    b2.setAttribute('value','決定');
                    b2.onclick=callerSelect(i);;
                    d2.appendChild(b2);
                    var marker = new google.maps.Marker({
                        map: map,
                        position: r.geometry.location
                    });
                    markers.push(marker);
                    bounds.union(r.geometry.viewport);
                }
                map.fitBounds(bounds);
            }else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) {
                result_div.innerHTML = '一致するものがありません';
            } else {
                alert("Geocode was not successful for the following reason: " + status);
            }
        });
    }
}
function callerVewMarker(i){
    return function(){viewMarker(i)};
}
function viewMarker(i){
    map.fitBounds(rr[i].geometry.viewport);
}
function callerSelect(i){
    return function(){select(i);};
}
function select(i){
    parent.set_latlng(rr[i].geometry.location.lat(),rr[i].geometry.location.lng());
    parent.close_geocode();
}
検索ボタンを押した際の動作は上記のコードによる。 Googleジオコーダーは、 var geocoder = new google.maps.Geocoder();とgeocoderのインスタンスを作成したうえ、 geocoder.geocode( { 'address': 住所},function(result,status){});として使用する。 結果処理関数function(result,status){}内に結果取得後の処理を記述する。 関数の引数statusによって結果の取得状況を取得することができる。 緯度・経度の取得に成功すると、status == google.maps.GeocoderStatus.OKとなる。 引数resultsにより結果の配列が渡される。結果は1つとは限らず、複数の候補が渡されることがあるので、一覧表示する。 なお、検索結果は大域変数rrに格納され、結果一覧から「選択」ボタンや「地図の中央に」ボタンを押された際にインデックスを使用して呼び出すが、 注意点として、これらのボタンが押された際のイベント処理関数内でループインデックスを使用してもうまく動作しない。 例として下記のような記述を行っても動作しない。
                    b2.onclick=function(){
                        parent.set_latlng(rr[i].geometry.location.lat(),rr[i].geometry.location.lng());
                        parent.close_geocode();
                    };
そのため、引数としてインデックスをとるイベント処理関数(select)を返す関数(callerSelect)を用意する必要がある。

緯度経度から市町村コードの取得

農研機構の簡易逆ジオコーディングサービスを使用する。 APIのURLは http://www.finds.jp/ws/rgeocode.php であると記載されているが、サイトをHTTPS化する場合には使用するWEB APIもHTTPS化しなければならないので、HTTPSを使ったWebサービス(試験)の記載に従い https://www.aginfo.jp/ws/rgeocode.php を使用する。
function get_city_code(){
    var latlng = get_latlng();
    if(!(latlng.lat>0&&latlng.lat<90&&latlng.lng>0&&latlng.lng<180)){
        alert('緯度・経度を正しく入力してください。');
        return false;
    }
    var myRequest = new XMLHttpRequest();
    myRequest.open('GET','https://www.aginfo.jp/ws/rgeocode.php?jsonp=city_code_got&lat='+latlng.lat+'&lon='+latlng.lng,true);
    myRequest.onreadystatechange = function(){
        if (myRequest.readyState === 4 && myRequest.status === 200){
            eval(myRequest.responseText);
        }else if(myRequest.status !== 200){
            alert('市町村コードが取得できませんでした。');
        }
    }
    myRequest.send(null);
    return false;
}
function city_code_got(result){
    document.getElementsByName('data[city_code]')[0].value = result.result.municipality.mcode
}
エラー処理などの点からXMLHttpRequestを使用した方法を使用する。 返送データのパースを簡略化するために、city_code_got()を用意し、JSONP形式で取得する。

緯度経度から標高の取得

国土地理院の標高APIを使用する。 国土地理院のAPIは、HTTPヘッダのAccess-Control-Allow-Originが設定されていないため、返却形式JSONでは使用できず、JSONPとする必要がある。また、XMLHttpRequestも使用できない。 そのため、scriptタグをdocumentに挿入する方法を用いる。
var altitude_script = null;
function get_altitude(){
    var latlng = get_latlng();
    if(!(latlng.lat>0&&latlng.lat<90&&latlng.lng>0&&latlng.lng<180)){
        alert('緯度・経度を正しく入力してください。');
        return false;
    }
    if(altitude_script != null)
        document.head.removeChild(altitude_script);
    altitude_script = document.createElement('script');
    altitude_script.src = 'https://cyberjapandata2.gsi.go.jp/general/dem/scripts/getelevation.php?callback=altitude_got&lat='+latlng.lat+'&lon='+latlng.lng;
    document.head.appendChild(altitude_script);
    return false;
}
function altitude_got(result){
    if(result.elevation == '-----')return false;//標高取得失敗
    document.getElementsByName('data[altitude]')[0].value = result.elevation;
    if(result.hsrc == '5m(レーザ)'){
        document.getElementsByName('data[altitude_accuracy]')[0].value = 10;
        accuracy_change('altitude');
    }else{
        document.getElementsByName('data[altitude_accuracy]')[0].value = 30;
        accuracy_change('altitude');
    }
    if(altitude_script != null){
        document.head.removeChild(altitude_script);
        altitude_script = null;
    }
}
document.createElement('script')としてscriptエレメントを作成し、altitude_script.src = 'https://cyberjapandata2.gsi.go.jp/general/dem/scripts/getelevation.php?callback=altitude_got&lat='+latlng.lat+'&lon='+latlng.lng とパラメータつきのURLをセットしたうえ、document.head.appendChild(altitude_script)とドキュメントに挿入する。 APIの返送データを取得すると、パラメータのcallbackに指定したaltitude_got(result)が実行されので、標高入力欄に標高をセットしたうえ、result.hsrcの値に応じて標高精度をセットする。 標高精度は絶対精度と相対精度に分割されているため、値をセットした後にaccuracy_change('altitude');を呼び出す。