2.クライアントサイド - Kazunori-Kimura/express-gis GitHub Wiki

Client Side

Google Mapに選択された行政区域のデータを表示します。

画面の制御には AngularJS を使用しています。

views/index.ejs

html/head

<!doctype html>
<html lang="ja" ng-app="myModule">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title><%= title %></title>

    <link rel="stylesheet" href="bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/style.css">

    <script src="angular/angular.min.js"></script>
    <script src="http://maps.googleapis.com/maps/api/js?key=<%= key %>&sensor=false"></script>
    <script src="js/index.js"></script>
</head>

bootstrap, angular, google maps apiとクライアントサイドのスクリプト本体であるjs/index.jsを読み込んでいます。

htmlタグにはangularと紐付けるためのng-appの定義があります。

このアプリケーションは各scriptが読み込まれている前提になっているため、headタグ内にscriptタグを書いています。

体感速度向上のために描画を優先したい場合は、bodyタグの最後にscriptタグを書くべきです。

ヘッダー部分

<body>

    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="#"><%= title %></a>
            </div>
        </div>
    </div>

画面上部に固定でタイトルを表示しています。

Map部分

    <div class="container-fluid">
        <div class="row">
            <!-- map -->
            <div id="map_canvas" class="col-md-9"></div>

左カラムにGoogle Mapを表示する領域を確保します。

縦横のサイズはスタイルシートで定義しています。

行政区域リスト部分

            <div id="controll" class="col-md-3" ng-controller="GisController">
                <form class="form-inline" role="form">
                    <div class="form-group">
                        <label class="sr-only" for="searchAddress">Search Address</label>
                        <input type="text" class="form-control input-lg" id="searchAddress"
                            ng-model="searchText"
                            placeholder="Search">
                    </div>
                </form>
                <hr>
                <div class="list-group">
                    <a herf="#" class="list-group-item"
                        ng-repeat="area in areas | filter:searchText"
                        ng-class="{ active: isSelected(area.code) }"
                        ng-click="selectArea(area.code)">
                        {{area.city}}
                    </a>
                </div>
            </div>
        </div>
    </div><!-- /.container -->

</body>
</html>

ng-controller="GisController"で、angularのコントローラと紐付けています。

form部分はリストのフィルタリング機能です。 ng-model="searchText"に、入力された文字列がリアルタイムに反映されます。

ng-repeat="area in areas"により、取得した行政区域データの件数だけaタグが生成されます。 | filter:searchText は上記のsearchTextにより表示する行政区域を絞り込んでいます。

ng-click="selectArea(area.code)"により、aタグのクリック時にselectArea()メソッドを実行するように定義しています。

ng-class="{ active: isSelected(area.code) }"は、選択された要素を反転表示するために、 class="active"の表示を制御しています。

public/js/index.js

angularとgoogle map objectの定義

// Google Map x PostGis x Express x AngularJs
// index.js
(function(){

    // AngularJS module
    var myModule = angular.module('myModule', []);

    // GoogleMap Object
    var map;

他のライブラリのオブジェクト等を汚さないように、(function(){ ... })(); でくくっています。

唯一プログラム全体で使い回す、map変数を定義しています。

angularのcontroller定義

    // Controller
    myModule.controller('GisController', function($scope, $http) {

        // 選択中のJIS5
        $scope.selectedCode = '';

        // 選択中の項目かどうかの判定
        $scope.isSelected = function(code){
            return $scope.selectedCode == code;
        }

$scope.selectedCodeは現在選択されている行政区域のコードを保持するプロパティです。

isSelected(code)ng-classで使用している、選択中の行政区域かどうかを判定するメソッドです。

行政区域リストのクリック

行政区域情報の取得処理

        // リストクリック時の処理
        $scope.selectArea = function(code){

            // 選択アイテムのJIS5を保持
            $scope.selectedCode = code;

            // 市区町村情報取得
            // GET: /areas/:code
            var url = '/areas/' + code;
            $http.get(url).success(function(data){

                // map表示中の要素を削除
                map.data.forEach(function(feature){
                    map.data.remove(feature);
                });

Ajaxにてクリックされた行政区域のコードを元に、情報を取得します。

取得に成功した場合、現在mapに表示している要素を全て削除します。

                // 選択された市区町村の中心点を取得
                // ST_Centroid で返される Point の coordinates の座標が google.maps.LatLng の
                // コンストラクタに渡す順番と逆転しているので注意。
                var center = JSON.parse(data.center);
                var centerLatlang = new google.maps.LatLng(
                    floor(center.coordinates[1], 4),
                    floor(center.coordinates[0], 4));

取得した行政区域情報から重心点を取得します。 後ほど、mapの中心をこの重心点に変更します。

                // GoogleMapに表示するGeoJSON(FeatureCollection)の定義。
                // http://geojson.org/
                var geojson = {
                    type: 'FeatureCollection',
                    features: []
                };

GoogleMapに表示するデータを格納するオブジェクト(GeoJSON)を用意しておきます。

                // 市区町村のGeoJSON
                // ST_AsGeoJSON は GoogleMapsAPI の addGeoJsonに必要な Feature の
                // geometry 部分のみになっているので、不足部分を補う
                var featureArea = {
                    type: 'Feature',
                    geometry: JSON.parse(data.geojson),
                    properties: {
                        type: 'area',
                        code: data.code,
                        name: data.city
                    }
                };

                // FeatureCollectionに市区町村を追加
                geojson.features.push(featureArea);

取得した行政区域情報ではGeoJSONが文字列になっているため、JSON.parseでJSONオブジェクトに変換しておきます。

propertiesの設定は、後ほどsetStyleにてポリゴンの色を設定する際に使用します。 propertiesには任意のkey/valueが設定できます。

小学校情報の取得処理

                // 学校情報の取得
                // GET: /areas/:code/schools
                $http.get(url + '/schools').success(function(schools){

                    // 学校情報をFeatureCollectionにセット
                    angular.forEach(schools, function(school){

                        // 学校のGeoJSON
                        var featureSchool = {
                            type: 'Feature',
                            geometry: JSON.parse(school.point),
                            properties: {
                                type: 'school',
                                name: school.school,
                                address: school.address
                            }
                        };

                        // FeatureCollectionに学校を追加
                        geojson.features.push(featureSchool);
                    });

つづいて、小学校の情報を取得します。

校区情報の取得処理

                    // 校区情報の取得
                    // GET: /areas/:code/districts
                    $http.get(url + '/districts').success(function(districts){

                        // 校区情報をFeatureCollectionにセット
                        angular.forEach(districts, function(district){

                            // 校区のGeoJSON
                            var featureDistrict = {
                                type: 'Feature',
                                geometry: JSON.parse(district.district),
                                properties: {
                                    type: 'district',
                                    name: district.school
                                }
                            };

                            // FeatureCollectionに校区を追加
                            geojson.features.push(featureDistrict);
                        });

同様に、校区の情報を取得します。

GoogleMapの表示処理

                        // GeoJsonをセット
                        map.data.addGeoJson(geojson);

取得したデータを成形・登録したGeoJSONをaddGeoJsonで突っ込むと、Mapに表示されます。

Styleの設定

各要素のpropertiesの内容によって、表示する内容を切り替えます。

行政区域は青、学校は赤、校区は緑にしています。 また、重ねあわせた際の順序をzIndexで定義しています。

                        // Style設定
                        // https://developers.google.com/maps/documentation/javascript/datalayer?hl=ja#style_geojson_data
                        map.data.setStyle(function(feature){
                            if(feature.getProperty('type') == 'area'){

                                // areaはPolygon
                                return {
                                    clickable: false,
                                    strokeWeight: 2,
                                    strokeColor: 'blue',
                                    zIndex: 4,
                                    fillColor: '#45A1CF',
                                    fillOpacity: 0.4,
                                    visible: true
                                };

                            }else if(feature.getProperty('type') == 'school'){

                                // schoolはPoint
                                return {
                                    icon: {
                                        path: google.maps.SymbolPath.CIRCLE,
                                        scale: 5,
                                        strokeWeight: 0,
                                        fillColor: 'red',
                                        fillOpacity: 0.8
                                    },
                                    zIndex: 12,
                                    visible: true
                                };

                            }else if(feature.getProperty('type') == 'district'){

                                // districtはPolygon
                                return {
                                    clickable: false,
                                    strokeWeight: 1,
                                    strokeColor: 'green',
                                    zIndex: 8,
                                    fillColor: '#3eba2b',
                                    fillOpacity: 0.4,
                                    visible: true
                                };

                            }
                        });

最後に、地図の中心点を選択された行政区域の重心に設定しています。

                        // 中心点を移動
                        map.setCenter(centerLatlang);
                    });
                });
            });
        };

初期化処理

順番が前後しますが、画面が表示された際の初期化処理および 全ての行政区域の取得の処理です。

        // 市区町村リスト取得処理
        $http.get('/areas').success(function(data){
            $scope.areas = data;
        });

        // GoogleMapの初期化
        initializeMap();
    });

Google Map初期化処理

    /**
     * GoogleMapの初期化
     */
    function initializeMap(){
        // option
        var options = {
            // 神戸市
            center: new google.maps.LatLng(34.6943, 135.1907),
            zoom: 13,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        // map生成
        map = new google.maps.Map(document.getElementById("map_canvas"), options);

初期表示時に神戸市が中央にくるように設定しています。

小学校をクリックした際のイベント設定

地図上で小学校をクリックした際に、名前と住所を吹き出しで表示する処理の定義です。

        // 吹き出し保持用のプロパティを定義
        map.infoWindow = false;

        // クリック時のイベント設定
        // https://developers.google.com/maps/documentation/javascript/datalayer?hl=ja#add_event_handlers
        map.data.addListener('click', function(event){

            if(event.feature){

                // 小学校をクリックされた場合
                if(event.feature.getProperty('type') == 'school'){

                    // 表示中の吹き出しがある場合は閉じる
                    if(map.infoWindow){
                        map.infoWindow.close();
                        map.infoWindow = false;
                    }

                    // 吹き出しを表示する
                    var point = event.feature.getGeometry().get();
                    var content = '<strong>' +
                        event.feature.getProperty('name') + 
                        '</strong><br>' +
                        event.feature.getProperty('address');

                    map.infoWindow = new google.maps.InfoWindow({
                        content: content,
                        position: point,
                        zIndex: 20
                    });

                    map.infoWindow.open(map);
                }
            }
        });
    }

クリックイベント発生時に、クリックされた要素(event.feature) が小学校かどうかをproperties.typeを元に判定しています。

既に表示している吹き出しがある場合は、それをcloseします。

properties.name, properties.addressを取得し、吹き出し(InfoWindow)を表示します。

ちなみに、map.infoWindowは吹き出しの状態を管理するために独自に定義したプロパティです。

小数点丸め処理

PostGISから返される緯度・経度情報の精度が高すぎるように思われたため、 小数点以下の桁数を指定して、それ以下の桁を切り捨てるメソッドです。

ただ、わざわざ切り捨てなくても表示できるかもしれません。 (試してません)

    /**
     * 小数点桁数切り捨て
     * @param Number num 切り捨て対象の数値
     * @param Number digit 小数点以下桁数
     */
    function floor(num, digit){
        var a = num * Math.pow(10, digit);
        a = Math.floor(a);
        return a / Math.pow(10, digit);
    }

})();
⚠️ **GitHub.com Fallback** ⚠️