dayjournal memo

Total 975 articles!!

Try #026 – AngularでLeafletとMapbox GL JSの開発環境を構築してみた

Yasunori Kirimoto's avatar

画像


画像


画像




画像


画像




AngularでLeafletとMapbox GL JSの開発環境を構築してみました!



事前準備


画像



AngularでLeafletとMapbox GL JSを利用する時に、ライブラリを直接読み込んでいるかたもいると思いますが、今回はAngular向けのラッパーライブラリを利用して開発環境を構築してみました!



Angular x Leaflet


AngularとLeafletの組み合わせの場合は、「ngx-leaflet」を利用します。


はじめに、各ライブラリをインストールします。

npm install leaflet
npm install @asymmetrik/ngx-leaflet
npm install @types/leaflet

画像




次に、ひな形に地図を表示させるためのコードを追記していきます。


全体構成

画像


package.json

{
  "name": "my-app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~8.2.9",
    "@angular/common": "~8.2.9",
    "@angular/compiler": "~8.2.9",
    "@angular/core": "~8.2.9",
    "@angular/forms": "~8.2.9",
    "@angular/platform-browser": "~8.2.9",
    "@angular/platform-browser-dynamic": "~8.2.9",
    "@angular/router": "~8.2.9",
    "@asymmetrik/ngx-leaflet": "^6.0.1",
    "@types/leaflet": "^1.5.3",
    "leaflet": "^1.5.1",
    "rxjs": "~6.4.0",
    "tslib": "^1.10.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.803.9",
    "@angular/cli": "~8.3.9",
    "@angular/compiler-cli": "~8.2.9",
    "@angular/language-service": "~8.2.9",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "^5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.5.3"
  }
}


angular.json

{
    "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
    "version": 1,
    "newProjectRoot": "projects",
    "projects": {
        "my-app": {
            "projectType": "application",
            "schematics": {},
            "root": "",
            "sourceRoot": "src",
            "prefix": "app",
            "architect": {
                "build": {
                    "builder": "@angular-devkit/build-angular:browser",
                    "options": {
                        "outputPath": "dist/my-app",
                        "index": "src/index.html",
                        "main": "src/main.ts",
                        "polyfills": "src/polyfills.ts",
                        "tsConfig": "tsconfig.app.json",
                        "aot": false,
                        "assets": [
                            "src/favicon.ico",
                            "src/assets"
                        ],
                        "styles": [
                            "src/styles.css",
                            "./node_modules/leaflet/dist/leaflet.css"
                        ],
                        "scripts": []
                    },
                    "configurations": {
                        "production": {
                            "fileReplacements": [
                                {
                                    "replace": "src/environments/environment.ts",
                                    "with": "src/environments/environment.prod.ts"
                                }
                            ],
                            "optimization": true,
                            "outputHashing": "all",
                            "sourceMap": false,
                            "extractCss": true,
                            "namedChunks": false,
                            "aot": true,
                            "extractLicenses": true,
                            "vendorChunk": false,
                            "buildOptimizer": true,
                            "budgets": [
                                {
                                    "type": "initial",
                                    "maximumWarning": "2mb",
                                    "maximumError": "5mb"
                                },
                                {
                                    "type": "anyComponentStyle",
                                    "maximumWarning": "6kb",
                                    "maximumError": "10kb"
                                }
                            ]
                        }
                    }
                },
                "serve": {
                    "builder": "@angular-devkit/build-angular:dev-server",
                    "options": {
                        "browserTarget": "my-app:build"
                    },
                    "configurations": {
                        "production": {
                            "browserTarget": "my-app:build:production"
                        }
                    }
                },
                "extract-i18n": {
                    "builder": "@angular-devkit/build-angular:extract-i18n",
                    "options": {
                        "browserTarget": "my-app:build"
                    }
                },
                "test": {
                    "builder": "@angular-devkit/build-angular:karma",
                    "options": {
                        "main": "src/test.ts",
                        "polyfills": "src/polyfills.ts",
                        "tsConfig": "tsconfig.spec.json",
                        "karmaConfig": "karma.conf.js",
                        "assets": [
                            "src/favicon.ico",
                            "src/assets"
                        ],
                        "styles": [
                            "src/styles.css"
                        ],
                        "scripts": []
                    }
                },
                "lint": {
                    "builder": "@angular-devkit/build-angular:tslint",
                    "options": {
                        "tsConfig": [
                            "tsconfig.app.json",
                            "tsconfig.spec.json",
                            "e2e/tsconfig.json"
                        ],
                        "exclude": [
                            "**/node_modules/**"
                        ]
                    }
                },
                "e2e": {
                    "builder": "@angular-devkit/build-angular:protractor",
                    "options": {
                        "protractorConfig": "e2e/protractor.conf.js",
                        "devServerTarget": "my-app:serve"
                    },
                    "configurations": {
                        "production": {
                            "devServerTarget": "my-app:serve:production"
                        }
                    }
                }
            }
        }},
    "defaultProject": "my-app"
}

angular.jsonのstyleでleaflet.cssを読み込みます。

"architect": {
    "build": {
        "builder": "@angular-devkit/build-angular:browser",
        "options": {
            "outputPath": "dist/my-app",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": false,
            "assets": [
                "src/favicon.ico",
                "src/assets"
            ],
            "styles": [
                "src/styles.css",
                "./node_modules/leaflet/dist/leaflet.css"
            ],
            "scripts": []
        },


/src/app


app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

// ngx-leaflet読み込み
import { LeafletModule } from '@asymmetrik/ngx-leaflet';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        // ngx-leafletモジュール読み込み
        LeafletModule.forRoot()
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

app.module.tsでngx-leafletのモジュールを読み込みます。

// ngx-leaflet読み込み
import { LeafletModule } from '@asymmetrik/ngx-leaflet';

    imports: [
        BrowserModule,
        AppRoutingModule,
        // ngx-leafletモジュール読み込み
        LeafletModule.forRoot()
    ],


/src/app


app.component.html

    <!-- 上部省略 -->

    <!-- Footer -->
    <footer>
        Love Angular?&nbsp;
        <a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
            <div class="github-star-badge">
                <svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
                Star
            </div>
        </a>
        <a href="https://github.com/angular/angular" target="_blank" rel="noopener">
            <svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
        </a>
    </footer>

    <svg id="clouds" alt="Gray Clouds Background" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
        <path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
    </svg>

</div>


<!--Map-->
<div id="map"
     leaflet
     [leafletOptions]="options"
     [leafletLayersControl]="layersControl">
</div>


<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->

<router-outlet></router-outlet>

app.component.htmlでマップのタグを指定します。

<!--Map-->
<div id="map"
     leaflet
     [leafletOptions]="options"
     [leafletLayersControl]="layersControl">
</div>


/src/app


app.component.css

app.component.cssでマップのサイズを指定します。

#map {
    height: 600px;
    padding: 0;
}


/src/app


app.component.ts

import { Component } from '@angular/core';

// Leaflet読み込み
import { tileLayer } from 'leaflet';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})

export class AppComponent {
    title = 'my-app';

        // 経緯度設定
        lat = 35.681;
        lng = 139.763;

        // MIERUNE MONO読み込み
        mMono = tileLayer('https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png', {
            attribution: 'Maptiles by <a href="http://mierune.co.jp/" target="_blank">MIERUNE</a>,' +
                ' under CC BY. Data by <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a>' +
                ' contributors, under ODbL.'
        });

        // MIERUNE Color読み込み
        mColor = tileLayer('https://tile.mierune.co.jp/mierune/{z}/{x}/{y}.png', {
            attribution: 'Maptiles by <a href="http://mierune.co.jp/" target="_blank">MIERUNE</a>,' +
                ' under CC BY. Data by <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a>' +
                ' contributors, under ODbL.'
        });

        // マップ設定
        options = {
            center: [this.lat, this.lng],
            zoom: 14,
            zoomControl: true,
            layers: [this.mMono]
        };

        // レイヤ設定
        layersControl = {
            baseLayers: {
                'MIERUNE MONO': this.mMono,
                'MIERUNE Color': this.mColor
            }
        };

}

app.component.tsでマップとレイヤの設定をします。

// Leaflet読み込み
import { tileLayer } from 'leaflet';

        // 経緯度設定
        lat = 35.681;
        lng = 139.763;

        // MIERUNE MONO読み込み
        mMono = tileLayer('https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png', {
            attribution: 'Maptiles by <a href="http://mierune.co.jp/" target="_blank">MIERUNE</a>,' +
                ' under CC BY. Data by <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a>' +
                ' contributors, under ODbL.'
        });

        // MIERUNE Color読み込み
        mColor = tileLayer('https://tile.mierune.co.jp/mierune/{z}/{x}/{y}.png', {
            attribution: 'Maptiles by <a href="http://mierune.co.jp/" target="_blank">MIERUNE</a>,' +
                ' under CC BY. Data by <a href="http://osm.org/copyright" target="_blank">OpenStreetMap</a>' +
                ' contributors, under ODbL.'
        });

        // マップ設定
        options = {
            center: [this.lat, this.lng],
            zoom: 14,
            zoomControl: true,
            layers: [this.mMono]
        };

        // レイヤ設定
        layersControl = {
            baseLayers: {
                'MIERUNE MONO': this.mMono,
                'MIERUNE Color': this.mColor
            }
        };


簡易ローカルサーバーで確認

ng serve --open


ローカルサーバーを立ち上げると、マップが表示されます。

画像





Angular x Mapbox GL JS


AngularとMapbox GL JSの組み合わせの場合は、「ngx-mapbox-gl」を利用します。


はじめに、各ライブラリをインストールします。ngx-mapbox-glは、対応しているライブラリが決まっているのでインストールバージョンを固定にします。

npm install mapbox-gl@1.1.0
npm install rxjs@6.5.0
npm install ngx-mapbox-gl
npm install @types/mapbox-gl

画像




次に、ひな形に地図を表示させるためのコードを追記していきます。


全体構成

画像


package.json

{
  "name": "my-app2",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~8.2.9",
    "@angular/common": "~8.2.9",
    "@angular/compiler": "~8.2.9",
    "@angular/core": "~8.2.9",
    "@angular/forms": "~8.2.9",
    "@angular/platform-browser": "~8.2.9",
    "@angular/platform-browser-dynamic": "~8.2.9",
    "@angular/router": "~8.2.9",
    "@types/mapbox-gl": "^0.54.4",
    "mapbox-gl": "^1.1.0",
    "ngx-mapbox-gl": "^4.0.0",
    "rxjs": "^6.5.0",
    "tslib": "^1.10.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.803.9",
    "@angular/cli": "~8.3.9",
    "@angular/compiler-cli": "~8.2.9",
    "@angular/language-service": "~8.2.9",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "^5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.5.3"
  }
}


/src


styles.css

styles.cssでmapbox-gl.cssを読み込みます。

@import "~mapbox-gl/dist/mapbox-gl.css";


/src


polyfills.ts

polyfills.tsでglobal is undefinedを解消するために設定を追記します。

// 上部省略

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone';  // Included with Angular CLI.


/***************************************************************************************************
 * APPLICATION IMPORTS
 */

(window as any).global = window;


/src/app


app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

// ngx-mapbox-gl読み込み
import { NgxMapboxGLModule } from 'ngx-mapbox-gl';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        // ngx-mapbox-glモジュール読み込み
        NgxMapboxGLModule.withConfig({
            accessToken: 'APIキーを入力'
        })
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

app.module.tsでngx-mapbox-glのモジュールを読み込みます。

// ngx-mapbox-gl読み込み
import { NgxMapboxGLModule } from 'ngx-mapbox-gl';

    imports: [
        BrowserModule,
        AppRoutingModule,
        // ngx-mapbox-glモジュール読み込み
        NgxMapboxGLModule.withConfig({
            accessToken: 'APIキーを入力'
        })
    ],


/src/app


app.component.html

    <!-- 上部省略 -->

    <!-- Footer -->
    <footer>
        Love Angular?&nbsp;
        <a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
            <div class="github-star-badge">
                <svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
                Star
            </div>
        </a>
        <a href="https://github.com/angular/angular" target="_blank" rel="noopener">
            <svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
        </a>
    </footer>

    <svg id="clouds" alt="Gray Clouds Background" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
        <path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
    </svg>

</div>


<!--Map-->
<mgl-map
    [style]="'mapbox://styles/スタイルキーを入力'"
    [zoom]="13"
    [center]="[139.767, 35.681]"
>

    <!--ナビゲーションコントロール-->
    <mgl-control
        mglNavigation
    ></mgl-control>

</mgl-map>


<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->

<router-outlet></router-outlet>

app.component.htmlでマップのタグを指定します。

<!--Map-->
<mgl-map
    [style]="'mapbox://styles/スタイルキーを入力'"
    [zoom]="13"
    [center]="[139.767, 35.681]"
>

    <!--ナビゲーションコントロール-->
    <mgl-control
        mglNavigation
    ></mgl-control>

</mgl-map>


/src/app


app.component.css

app.component.cssでマップのサイズを指定します。

mgl-map {
    height: 600px;
}


/src/app


app.component.ts

app.component.tsは変更なしです。

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title = 'my-app2';

}


簡易ローカルサーバーで確認

ng serve --open


ローカルサーバーを立ち上げると、マップが表示されます。

画像




Angularとngx-leafletとngx-mapbox-glを利用することで、手軽にAngularでLeafletとMapbox GL JSの開発環境の構築ができました!


Leaflet・Mapbox GL JSのライブラリを、直接読み込んで利用することもできると思いますが、ラッパーライブラリを利用することである程度は手軽に操作ができると思います。ただ、用途により直接読み込むかラッパーライブラリを利用するかの選択は必要となりそうです。

メリット

  • 環境構築が比較的手軽にできる
  • HTMLのタグで直感的に指定できる
  • JavaScriptフレームワークのルールにある程度合わせたコードになる
  • JavaScriptフレームワークとの相性が悪いDOMやライフサイクル関係が操作しやすいかも
    ※今回は地図表示部分の確認だったのでなんとも言えないですが…

デメリット

  • Leaflet・Mapbox GL JSのピュアな全機能や、プラグインが利用できるわけではない
  • ラッパーライブラリ独自のコードを覚える必要がある
  • 利用者が少ないためエラー時の解決策のヒントが少ない

次回は、Vue.jsとの連携についても調べてみたいと思います!



Angular・Leaflet・Mapbox GL JSについて、他にも記事を書いています。よろしければぜひ。
tags - angular
tags - Leaflet
tags - Mapbox GL JS



book

Q&A