Open Street Map独自タイルサーバをAmazon Elastic Container Serviceで稼働させる

# はじめに

Google Maps API関連の契約が大きく変更され、Open Street Map(以下、OSM)を使うサイトが増えたかなと感じる今日この頃。
OSMは無料で使えるのだけど、少し日本人離れしているデザインなので、自前で環境構築した方がOSM本家に迷惑もかからないし、便利かなと思います。

この記事のサーバ環境は、Amazon Elastic Container Service（以下、ECS）を使っています。
オンプレミスのサーバやローカル環境でも、Dockerが使える環境であれば動かせます。

ベースとなるコンテナイメージは、海外の方が作られたものを、クローンさせて使わさせていただいております。
また、この記事で対象にしているOSMのリージョンは北海道です。

OSMタイルサーバをECSで動かしている記事が見当たらなかったので、思い切って記事にしました。


## 仕上がりイメージ

北海道大学付近です。めちゃくちゃ賑やかですね。
タイル画像は、ズームレベル20までレンダリングする設定になっていますので、拡大しても綺麗です。

<img width="1476" alt="スクリーンショット 2018-12-07 16.40.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/150734/f77e666b-9134-c073-96fc-f97d269b44e2.png">


# 事前準備

ある程度の知識と技術、用意するものが必要です。
Dockerコンテナイメージは、私が用意してあるものをお使いいただけます。

## 必要な知識

- Dockerに関する知識
- AWSに関する知識

## 用意するもの

- Dockerコンテナ実行環境
- AWS環境 （VPCやIAM周りの設定が事前に必要なので、各自で用意してくださいね）

マシンスペックは、1コア、2GB程度のメモリが必要です。

# サーバ準備

実行環境であるサーバを用意します。
コンソールからやるか、CloudFormation(CFn)でサクッと用意しちゃいましょう。
ECSのクラスタ名は、「osm-tile」にしました。

高スペックなマシーンは不要ですし、お安いスポットインスタンスを有効に使いましょう。
t2.smallくらいのスペックで十分かと思います。
ケチってt2.microにすると、osm2pgsqlでエラーになりました。
ECSは仮想メモリ割り当て設定がないので、ごにょごにょすれば行けそうな気もします。
インポート時のメモリ割り当てを変更するでも良いと思います。
最初のレンダリングには若干時間がかかりますが、キャッシュしてしまえばレスポンスも良いです。

ディスクは、12GBを割り当てています。
リージョンによってはもっと大きい容量が必要ですので、ご注意ください。
北海道は、インポート後5GBくらい、タイルのキャッシュに数GB必要になりますので、不要なタイル画像はクリーニングしましょう。
EBSは、オンラインで拡張することが出来ますので、あまり気にならないかもしれません。

ガチで運用するなら、リザーブドインスタンスにするとか、Blue/Green Deploymentとか、色々とやることはあるのですが、リバースプロキシのキャッシュ機能を使うことも出来るので、要件に応じて各自でいろいろとご検討ください。


```yml:osm-tile.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Open Street Map Tile Service ECS Template Spot Instance.'

Parameters:
  Ec2KeyName:
    Description: EC2 SSH KEY
    Type: AWS::EC2::KeyPair::KeyName
    Default: HOGEFUGA-KEY

  Ec2InstanceType:
    Description: EC2 Instance type
    Type: String
    Default: t2.small
    AllowedValues:
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type.

  Ec2ImageId:
    Description: amzn-ami-2018.03.h-amazon-ecs-optimized
    Type: String
    Default: ami-0edf19001c48838c7

Resources:
  Ec2InstanceLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      InstanceType: !Ref 'Ec2InstanceType'
      KeyName: !Ref 'Ec2KeyName'
      ImageId: !Ref 'Ec2ImageId'
      IamInstanceProfile: 'arn:aws:iam::************:instance-profile/ecsInstanceRole'
      BlockDeviceMappings:
        - DeviceName: '/dev/xvdcz'
          Ebs:
            VolumeSize: 22
            VolumeType: gp2
        - DeviceName: '/dev/xvda'
          Ebs:
            VolumeSize: 12
            VolumeType: gp2
      SecurityGroups:
        - sg-********
      AssociatePublicIpAddress: 'false'
      InstanceMonitoring: 'false'
      SpotPrice: "0.02"
      UserData:
        Fn::Base64: |
          #!/bin/bash

          # Specify the cluster that the container instance should register into
          cluster=osm-tile

          echo ECS_CLUSTER=$cluster >> /etc/ecs/ecs.config;echo ECS_BACKEND_HOST= >> /etc/ecs/ecs.config;
          # yum
          yum -y install jq wget aws-cli

  Ec2InstanceAutoScalingGroup2:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
        - 'subnet-********'
      LaunchConfigurationName: !Ref 'Ec2InstanceLaunchConfig'
      MinSize: 0
      MaxSize: 1
      DesiredCapacity: 1
      TerminationPolicies:
        - OldestInstance

  ECSClusterFront:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: osm-tile
```

awscliでスタックを作成します。
コマンドがよく分からない方は、Webコンソール画面からでも利用できます。

```
$ aws cloudformation create-stack \
--stack-name osm-tile \
--template-body file:///Users/******/osm-tile.yml \
--tags Key=Name,Value=osm-tile \
  Key=Author,Value=hogefuga \
--parameters ParameterKey=Ec2KeyName,ParameterValue=HOGEFUGA-KEY \
  ParameterKey=Ec2InstanceType,ParameterValue=t2.small

{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:************:stack/osm-tile/d04f2e50-f8fb-11e8-ac84-0e2c94******"
}
```

インスタンス起動まで待ちましょう。3分くらいかかります。
ダッシュボードの表示にインスタンスが増えればOKです。

<img width="1066" alt="スクリーンショット 2018-12-07 16.59.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/150734/6739a7d9-2d2b-1f45-8a7c-b681e3ff0d4b.png">


# Dockerコンテナの定義

コンテナイメージは以下のものを使います。
あれ？北海道しかないじゃん！！
そうです。
北海道エリアだけインポートしているので、使用する地域に合わせてDockerfileを変更してください。

* レンダリング
 https://hub.docker.com/r/akikinyan/osm-renderer/

* データベース
 https://hub.docker.com/r/akikinyan/osm-postgis/

ECSを使うので、タスク定義を作成しましょう。
docker-compose.ymlから作成することも出来ます。

* 公式ドキュメント [ecs-cli compose](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-compose.html)

コマンド例

```
$ ecs-cli compose \
  -f docker-compose.yml \
  --project-name osm-tile-project \
  --cluster osm-tile \
  create
```

リポジトリに置いてありますので、参考にしてください。
https://github.com/akikinyan/melbourne-map/blob/develop/docker-compose.yml

ECSは、相対ディレクトリ指定運用が難しいので、最新のコンテナイメージは相対パス指定を不要にしています。
コンテナ内に、config.jsonファイルを追加しただけなんですけどね。
「./data」はデータボリュームでもOKです。
最近、ECSもデータボリュームが使えるようになりました。
「./renderer」は記述不要としました。

とりあえずそのまま載せておきます。

```dockerfile:dockerfile
version: '2'
services:
  postgis:
    build: ./postgis
    image: akikinyan/osm-postgis:latest
    volumes:
      - ./data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=gis
  renderer:
    build: ./renderer
    image: akikinyan/osm-renderer:latest
    volumes:
      - ./renderer/map_data:/map_data
      - ./renderer/scripts:/scripts
    volumes_from:
      - postgis:ro
    ports:
      - "8080:8080"
      - "9090:9090"
    depends_on:
      - postgis
  web:
    image: nginx:1.11-alpine
    volumes:
      - ./web:/usr/share/nginx/html
    ports:
      - "8888:80"
```

## タスク定義

最終的なイメージを載せておきます。
Webコンテナは、別な環境からアクセスするため、作成しませんでした。
最近は、Blue-Green Deploymentの方がいいかなぁと思うので、レンダリング部分とデータベース部分を分離しておいた方が良いかもしれませんが、このOSMコンテナは一緒でも良いような気もします。

ちなみに、Application Load Balancer配下に環境を設置する場合は、ポート番号を適宜変更してください。
試した感じでは、レンダリングサーバが、コンテキストパスを良い感じで処理してくれるようなので、コンテキストパスは任意のもので良さそうです。
(時間の都合でどういう仕組みなのかは把握してないです)

<img width="1050" alt="スクリーンショット 2018-12-07 15.22.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/150734/9d82a760-060d-897b-784a-0dd73ae40549.png">

```json
{
  "ipcMode": null,
  "executionRoleArn": null,
  "containerDefinitions": [
    {
      "dnsSearchDomains": [],
      "logConfiguration": {
        "logDriver": "none",
        "options": null
      },
      "entryPoint": [],
      "portMappings": [],
      "command": [],
      "linuxParameters": {
        "capabilities": {
          "add": null,
          "drop": null
        },
        "sharedMemorySize": null,
        "tmpfs": null,
        "devices": null,
        "initProcessEnabled": null
      },
      "cpu": 0,
      "environment": [
        {
          "name": "POSTGRES_DB",
          "value": "gis"
        }
      ],
      "ulimits": null,
      "dnsServers": [],
      "mountPoints": [
        {
          "readOnly": false,
          "containerPath": "/var/lib/postgresql/data",
          "sourceVolume": "volume-0"
        }
      ],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": [],
      "memory": null,
      "memoryReservation": 256,
      "volumesFrom": [],
      "image": "akikinyan/osm-postgis:latest",
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": [],
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": false,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": false,
      "name": "postgis"
    },
    {
      "dnsSearchDomains": [],
      "logConfiguration": {
        "logDriver": "none",
        "options": null
      },
      "entryPoint": [],
      "portMappings": [
        {
          "hostPort": 8080,
          "protocol": "tcp",
          "containerPort": 8080
        }
      ],
      "command": [],
      "linuxParameters": {
        "capabilities": {
          "add": null,
          "drop": null
        },
        "sharedMemorySize": null,
        "tmpfs": null,
        "devices": null,
        "initProcessEnabled": null
      },
      "cpu": 0,
      "environment": [],
      "ulimits": null,
      "dnsServers": [],
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": [],
      "memory": null,
      "memoryReservation": 256,
      "volumesFrom": [
        {
          "sourceContainer": "postgis",
          "readOnly": true
        }
      ],
      "image": "akikinyan/osm-renderer:latest",
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": [
        "postgis:postgis"
      ],
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": false,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": false,
      "name": "renderer"
    }
  ],
  "placementConstraints": [],
  "memory": null,
  "taskRoleArn": null,
  "compatibilities": [
    "EC2"
  ],
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:************:task-definition/osm-tile-service:1",
  "family": "osm-tile-service",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.17"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.25"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.docker-plugin.local"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.21"
    }
  ],
  "pidMode": null,
  "requiresCompatibilities": [
    "EC2"
  ],
  "networkMode": null,
  "cpu": null,
  "revision": 1,
  "status": "ACTIVE",
  "volumes": [
    {
      "name": "volume-0",
      "host": null,
      "dockerVolumeConfiguration": {
        "autoprovision": true,
        "labels": null,
        "scope": "shared",
        "driver": "local",
        "driverOpts": null
      }
    }
  ]
}
```

# タスク起動

Dockerコンテナを起動します。
うっかりECSのログ設定を忘れてしまったので、docker-compose upコマンドで起動したログを載せておきます。
t2.mediumインスタンスでテストした時のログですが、データロードまで少し時間がかかります。

```
[ec2-user@ip-10-1-*-*** melbourne-map]$ docker-compose up
Creating network "melbourne-map_default" with the default driver
Creating volume "melbourne-map_osm-data" with default driver
Pulling web (nginx:1.11-alpine)...
1.11-alpine: Pulling from library/nginx
709515475419: Pull complete
4b21d71b440a: Pull complete
c92260fe6357: Pull complete
ed383a1b82df: Pull complete
Creating melbourne-map_postgis_1_60c7123978a5 ... done
Creating melbourne-map_web_1_1fadfe7b920b      ... done
Creating melbourne-map_renderer_1_70ada7233ba7 ... done
Attaching to melbourne-map_postgis_1_6a59f2e4f643, melbourne-map_web_1_c83180b5cf64, melbourne-map_renderer_1_6702d8636309
postgis_1_6a59f2e4f643 | The files belonging to this database system will be owned by user "postgres".
postgis_1_6a59f2e4f643 | This user must also own the server process.
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | The database cluster will be initialized with locale "en_US.utf8".
postgis_1_6a59f2e4f643 | The default database encoding has accordingly been set to "UTF8".
postgis_1_6a59f2e4f643 | The default text search configuration will be set to "english".
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Data page checksums are disabled.
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | fixing permissions on existing directory /var/lib/postgresql/data ... ok
postgis_1_6a59f2e4f643 | creating subdirectories ... ok
postgis_1_6a59f2e4f643 | selecting default max_connections ... 100
postgis_1_6a59f2e4f643 | selecting default shared_buffers ... 128MB
postgis_1_6a59f2e4f643 | selecting dynamic shared memory implementation ... posix
postgis_1_6a59f2e4f643 | creating configuration files ... ok
postgis_1_6a59f2e4f643 | creating template1 database in /var/lib/postgresql/data/base/1 ... ok
postgis_1_6a59f2e4f643 | initializing pg_authid ... ok
postgis_1_6a59f2e4f643 | setting password ... ok
postgis_1_6a59f2e4f643 | initializing dependencies ... ok
postgis_1_6a59f2e4f643 | creating system views ... ok
postgis_1_6a59f2e4f643 | loading system objects' descriptions ... ok
postgis_1_6a59f2e4f643 | creating collations ... ok
postgis_1_6a59f2e4f643 | creating conversions ... ok
postgis_1_6a59f2e4f643 | creating dictionaries ... ok
postgis_1_6a59f2e4f643 | setting privileges on built-in objects ... ok
postgis_1_6a59f2e4f643 | creating information schema ... ok
postgis_1_6a59f2e4f643 | loading PL/pgSQL server-side language ... ok
postgis_1_6a59f2e4f643 | vacuuming database template1 ... ok
postgis_1_6a59f2e4f643 | copying template1 to template0 ... ok
postgis_1_6a59f2e4f643 | copying template1 to postgres ... ok
postgis_1_6a59f2e4f643 | syncing data to disk ... 
postgis_1_6a59f2e4f643 | WARNING: enabling "trust" authentication for local connections
postgis_1_6a59f2e4f643 | You can change this by editing pg_hba.conf or using the option -A, or
postgis_1_6a59f2e4f643 | --auth-local and --auth-host, the next time you run initdb.
postgis_1_6a59f2e4f643 | ok
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Success. You can now start the database server using:
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 |     pg_ctl -D /var/lib/postgresql/data -l logfile start
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | ****************************************************
postgis_1_6a59f2e4f643 | WARNING: No password has been set for the database.
postgis_1_6a59f2e4f643 |          This will allow anyone with access to the
postgis_1_6a59f2e4f643 |          Postgres port to access your database. In
postgis_1_6a59f2e4f643 |          Docker's default configuration, this is
postgis_1_6a59f2e4f643 |          effectively any other container on the same
postgis_1_6a59f2e4f643 |          system.
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 |          Use "-e POSTGRES_PASSWORD=password" to set
postgis_1_6a59f2e4f643 |          it in "docker run".
postgis_1_6a59f2e4f643 | ****************************************************
postgis_1_6a59f2e4f643 | waiting for server to start....LOG:  database system was shut down at 2018-12-06 03:29:20 UTC
postgis_1_6a59f2e4f643 | LOG:  MultiXact member wraparound protections are now enabled
postgis_1_6a59f2e4f643 | LOG:  database system is ready to accept connections
postgis_1_6a59f2e4f643 | LOG:  autovacuum launcher started
postgis_1_6a59f2e4f643 |  done
postgis_1_6a59f2e4f643 | server started
postgis_1_6a59f2e4f643 | CREATE DATABASE
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | /usr/local/bin/docker-entrypoint.sh: sourcing /docker-entrypoint-initdb.d/postgis.sh
postgis_1_6a59f2e4f643 | CREATE DATABASE
postgis_1_6a59f2e4f643 | UPDATE 1
postgis_1_6a59f2e4f643 | Loading PostGIS extensions into template_postgis
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | Loading PostGIS extensions into gis
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | CREATE EXTENSION
postgis_1_6a59f2e4f643 | osm2pgsql version 0.92.0 (64 bit id space)
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Using built-in tag processing pipeline
postgis_1_6a59f2e4f643 | Using projection SRS 3857 (Spherical Mercator)
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_point
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_line
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_polygon
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_roads
postgis_1_6a59f2e4f643 | Allocating memory for dense node cache
postgis_1_6a59f2e4f643 | Allocating dense node cache in one big chunk
postgis_1_6a59f2e4f643 | Allocating memory for sparse node cache
postgis_1_6a59f2e4f643 | Sharing dense sparse
postgis_1_6a59f2e4f643 | Node-cache: cache=800MB, maxblocks=12800*65536, allocation method=11
postgis_1_6a59f2e4f643 | Mid: pgsql, scale=100 cache=800
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_nodes
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_ways
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_rels
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Reading in file: /hokkaido-latest.osm.pbf
postgis_1_6a59f2e4f643 | Using PBF parser.
Processing: Node(5880k 294.0k/s) Way(0k 0.00k/s) Relation(0 0.00/s)LOG:  checkpoints are occurring too frequently (25 seconds apart)
postgis_1_6a59f2e4f643 | HINT:  Consider increasing the configuration parameter "max_wal_size".
Processing: Node(12180k 276.8k/s) Way(0k 0.00k/s) Relation(0 0.00/s)LOG:  checkpoints are occurring too frequently (23 seconds apart)
postgis_1_6a59f2e4f643 | HINT:  Consider increasing the configuration parameter "max_wal_size".
Processing: Node(18530k 276.6k/s) Way(0k 0.00k/s) Relation(0 0.00/s)LOG:  checkpoints are occurring too frequently (23 seconds apart)
postgis_1_6a59f2e4f643 | HINT:  Consider increasing the configuration parameter "max_wal_size".
Processing: Node(19702k 281.5k/s) Way(2357k 84.20k/s) Relation(9850 351.79/s)  parse time: 126s
postgis_1_6a59f2e4f643 | Node stats: total(19702672), max(6120021395) in 70s
postgis_1_6a59f2e4f643 | Way stats: total(2357510), max(652949469) in 28s
postgis_1_6a59f2e4f643 | Relation stats: total(9853), max(9077199) in 28s
postgis_1_6a59f2e4f643 | Committing transaction for planet_osm_point
postgis_1_6a59f2e4f643 | Committing transaction for planet_osm_line
postgis_1_6a59f2e4f643 | Committing transaction for planet_osm_polygon
postgis_1_6a59f2e4f643 | Committing transaction for planet_osm_roads
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_nodes
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_ways
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_rels
postgis_1_6a59f2e4f643 | Using built-in tag processing pipeline
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_nodes
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_ways
postgis_1_6a59f2e4f643 | Setting up table: planet_osm_rels
postgis_1_6a59f2e4f643 | Using built-in tag processing pipeline
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Going over pending ways...
postgis_1_6a59f2e4f643 | 	1860907 ways are pending
postgis_1_6a59f2e4f643 | 
postgis_1_6a59f2e4f643 | Using 2 helper-processes

renderer_1_6702d8636309 | Waiting while database is initializing...
renderer_1_6702d8636309 | Waiting while database is initializing...
renderer_1_6702d8636309 | Waiting while database is initializing...
renderer_1_6702d8636309 | Starting renderer
```

# 動作確認

URLにアクセスして確認します。
HTML内のタイルの接続先を変更してから接続してみてください。
ローカルにHTMLファイルを作ってアクセスしても良いです。

http://localhost:8888/


```html:index.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8"/>
	<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" />
	<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script>
	<style type="text/css">
		html, body, #map {
			width: 100%;
			height: 100%;
			margin: 0 !important;
			overflow: hidden;
		}
	</style>
</head>

<body>
<div id="map"></div>
</body>

<script type="text/javascript">
	var map = L.map('map').setView([43.06, 141.34], 12);
	L.tileLayer('http://localhost:8080/{z}/{x}/{y}.png', {
		attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
		maxZoom: 18
	}).addTo(map);
</script>
</html>
```

リポジトリに上がっている緯度経度の設定が、なぜか南半球になっているので、北半球に移動してください（笑）

<img width="1522" alt="スクリーンショット 2018-12-07 15.35.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/150734/ee8ea73a-a4cc-7a30-98b4-ad60bfca2136.png">


# カスタマイズ

タイルのカスタマイズについて記載しておきます。

## リージョンの変更

ダウンロードするファイルは、[Download OpenStreetMap data for this region](http://download.geofabrik.de/asia/japan.html)から検索可能です。
postgisのDockerfileを修正します。
Dockerイメージが大きくなるので、pbfをコンテナ内に入れなくてもと思っているのですが、元々作ってくれた方の意図をそのままにしています。

ちなみに、北海道と東北、関東と関西のようなリージョンが複数必要な場合、2つ指定してもうまく動きませんのでご注意ください。

```Dockerfile
FROM mdillon/postgis:9.5

WORKDIR /

RUN apt-get update && \
    apt-get install -y osm2pgsql git wget && \
    rm -rf /var/lib/apt/lists/*

RUN git clone https://github.com/akikinyan/openstreetmap-carto.git

RUN wget http://download.geofabrik.de/asia/japan/hokkaido-latest.osm.pbf


#Overriding init script to add hstore extension that osm2pgsql requires
COPY ./initdb-postgis.sh /docker-entrypoint-initdb.d/postgis.sh
```

## タイルの細かい変更

Dockerfileファイルを見ると、コンパイルしてからレンダリングエンジンを起動しています。
いろいろ弄りたい方は、別のリポジトリからcloneしている方を弄りましょう。
https://github.com/akikinyan/openstreetmap-carto

Dockerファイルの中を書いておきます。

```renderer/entrypoint.sh
sh /scripts/compile_style.sh

while [ ! -e /var/lib/postgresql/data/DB_INITED ]
do
sleep 5
echo "Waiting while database is initializing..."
done

#Have to wait because once DB created then osm2pgsql restarting postgres.
#TODO: Using pg_isready
echo "DB successfully created, waiting for restart"
sleep 10

echo "Starting renderer"
sh /scripts/run_render.sh
```

```renderer/scripts/compile_style.sh
#!/usr/bin/env bash

# Compiling carto css style and generates OSM xml
# that can be passed to mapnik.

carto /openstreetmap-carto/project.mml > /map_data/stylesheet_.xml

DS='<Parameter name=\"dbname\"><![CDATA[gis]]><\/Parameter>\
    <Parameter name=\"host\"><![CDATA[postgis]]><\/Parameter>\
    <Parameter name=\"port\"><![CDATA[5432]]><\/Parameter>\
    <Parameter name=\"user\"><![CDATA[postgres]]><\/Parameter>\
    <Parameter name=\"password\"><![CDATA[postgres]]><\/Parameter>'
sed "s/<Parameter name=\"dbname\">.*<\/Parameter>/${DS}/" /map_data/stylesheet_.xml > /map_data/stylesheet.xml
rm /map_data/stylesheet_.xml
```

例えば、道路の設定はroads.mssです。
```@moterway-fill```とかの部分は、OSMのWikipediaで調べると何者か分かります。

```openstreetmap-carto/roads.mss
/* For the main linear features, such as roads and railways. */

//road colors for major roads were generated with scripts/generate_road_colors.py
@motorway-fill: #e892a2; // Lch(70,35,10), error 0.5
@trunk-fill: #f9b29c; // Lch(79,33,42), error 0.7
@primary-fill: #fcd6a4; // Lch(88,31,74), error 1.7
@secondary-fill: #f7fabf; // Lch(97,29,106), error 1.7
@tertiary-fill: #ffffff;
@residential-fill: #ffffff;
@service-fill: @residential-fill;
@living-street-fill: #ededed;
@pedestrian-fill: #dddde8;
@raceway-fill: pink;
@road-fill: #ddd;
@footway-fill: salmon;
@steps-fill: @footway-fill;
@cycleway-fill: blue;
@bridleway-fill: green;
@track-fill: #996600;
@aeroway-fill: #bbc;
@runway-fill: @aeroway-fill;
@taxiway-fill: @aeroway-fill;
@helipad-fill: @aeroway-fill;
```

データの取得は```project.mml```で処理をしているので、ズームレベルに応じた描画以外にも、こちらのファイルの修正が必要となる場合があります。```properties```の部分がそれです。

```openstreetmap-carto/project.mml
{
      "name": "roads-text-name",
      "srs-name": "900913",
      "geometry": "linestring",
      "class": "",
      "id": "roads-text-name",
      "srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
      "Datasource": {
        "extent": "-20037508,-20037508,20037508,20037508",
        "table": "(SELECT way,\n    CASE WHEN substr(highway, length(highway)-3, 4) = 'link' THEN substr(highway, 0, length(highway)-4) ELSE highway END,\n    CASE WHEN (tunnel = 'yes' OR tunnel = 'building_passage' OR covered = 'yes') THEN 'yes' ELSE 'no' END AS tunnel,\n    CASE WHEN construction IN ('service', 'footway', 'cycleway', 'bridleway', 'path', 'track') THEN 'yes' ELSE 'no' END AS int_construction_minor,\n    name\n  FROM planet_osm_line\n  WHERE highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link', 'tertiary', \n                    'tertiary_link', 'residential', 'unclassified', 'road', 'service', 'pedestrian', 'raceway', 'living_street', 'construction')\n    AND name IS NOT NULL\n) AS roads_text_name",
        "geometry_field": "way",
        "type": "postgis",
        "key_field": "",
        "dbname": "gis"
      },
      "extent": [
        -180,
        -85.05112877980659,
        180,
        85.05112877980659
      ],
      "properties": {
        "minzoom": 13
      },
      "advanced": {}
    },
```

反映させるため、プロセス(コンテナ)再起動が必要です。
ECS環境で反映する場合は、最新のコンテナイメージにデプロイし直します。

# OSMでルート案内

ルート案内のroutinoを使ってみましたが、時間帯の通行禁止が反映されていないので、地域によっては交通違反になる可能性大です。ご注意ください。
ルート検索を試してみたい方は、私のDockerコンテナイメージをお使いいただけます。
https://hub.docker.com/r/akikinyan/routino-docker


# さいごに

GoogleMapの商用利用が安かったら良かったのだけど、実際に見積もってみたら結構な金額になってしまいました。
OSMは、下図としてはそれなりに使えるのですが、検索やルート案内などの機能が弱いです。
それを補うためのインタフェースがあれば、地域によっては十分じゃないかなと言った印象です。