[MaaStep]OpenStreetMapのセットアップ[Ubuntu18.04でできたので完成版]
2019-06-19
azblob://2022/11/11/eyecatch/2019-06-17-maastep-openstreetmap-setup-nginx-000-e1560754782649.jpg

はじめに

Azureを用いてMaaSに関するアプリの作成をしているときに作ったもの・触ったものを「MaaStep」としてタグをつけて紹介していく企画第4弾です。 前回はOpenStreetMapのタイルサーバーのセットアップを試みましたが、Apacheの設定で詰みました。
前回言っていたApacheの設定の話ですが、調べて出てきた解決方法を全部試してみたものの、うまくいきませんでした。
ということで、気分を一新して今回はUbuntu18.04でやってみます[参考]。
ちなみに同期の優秀な石川君はUbuntu16.04でうまくいってます。すごい流石、Sushiプロ……。

実行環境

リンクはインストール方法へのリンクです。

目次

1. VMのセットアップ

この記事を参考にVMのセットアップをしていきます。
今回はUbuntu18.04なので、以下のtfファイルで設定します。

# Azureプロバイダーの設定

provider "azurerm" {
}
# Azure上のリソースグループの作成
resource "azurerm_resource_group" "terraformgroup" {
  name     = "re_osm2Group"
  location = "japaneast"

  tags = {
    environment = "OSM"
  }
}

# 仮想ネットワークの作成
resource "azurerm_virtual_network" "terraformnetwork" {
  name                = "re_osmVnet"
  address_space       = ["10.0.0.0/16"]
  location            = "japaneast"
  resource_group_name = "${azurerm_resource_group.terraformgroup.name}"

  tags = {
    environment = "OSM"
  }
}
## 仮想ネットワークにサブネットを作成
resource "azurerm_subnet" "terraformsubnet" {
  name                 = "re_osmSubnet"
  resource_group_name  = "${azurerm_resource_group.terraformgroup.name}"
  virtual_network_name = "${azurerm_virtual_network.terraformnetwork.name}"
  address_prefix       = "10.0.2.0/24"
}

# パブリックIPアドレスの作成
resource "azurerm_public_ip" "terraformpublicip" {
  name                = "re_osmPublicIP"
  location            = "japaneast"
  resource_group_name = "${azurerm_resource_group.terraformgroup.name}"
  allocation_method   = "Dynamic"

  tags = {
    environment = "OSM"
  }
}

# ネットワーク セキュリティ グループの作成
resource "azurerm_network_security_group" "terraformnsg" {
  name                = "re_osmSecurityGroup"
  location            = "japaneast"
  resource_group_name = "${azurerm_resource_group.terraformgroup.name}"

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  tags = {
    environment = "OSM"
  }
}

# 仮想ネットワーク インターフェイス カードの作成
resource "azurerm_network_interface" "terraformnic" {
  name                      = "re_osmNIC"
  location                  = "japaneast"
  resource_group_name       = "${azurerm_resource_group.terraformgroup.name}"
  network_security_group_id = "${azurerm_network_security_group.terraformnsg.id}"

  ip_configuration {
    name                          = "re_osmNicConfiguration"
    subnet_id                     = "${azurerm_subnet.terraformsubnet.id}"
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = "${azurerm_public_ip.terraformpublicip.id}"
  }

  tags = {
    environment = "OSM"
  }
}

# ストレージアカウントの作成
## 一意なストレージアカウント名の生成
resource "random_id" "randomId" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group = "${azurerm_resource_group.terraformgroup.name}"
  }

  byte_length = 8
}
## ストレージアカウントの作成
resource "azurerm_storage_account" "storageaccount" {
  name                     = "diag${random_id.randomId.hex}"
  resource_group_name      = "${azurerm_resource_group.terraformgroup.name}"
  location                 = "japaneast"
  account_replication_type = "LRS"
  account_tier             = "Standard"

  tags = {
    environment = "OSM"
  }
}

# 仮想マシンの作成
resource "azurerm_virtual_machine" "terraformvm" {
  name                  = "re_osmVM"
  location              = "japaneast"
  resource_group_name   = "${azurerm_resource_group.terraformgroup.name}"
  network_interface_ids = ["${azurerm_network_interface.terraformnic.id}"]
  vm_size               = "Standard_B2s"

  storage_os_disk {
    name              = "myOsDisk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  os_profile {
    computer_name  = "myvm"
    admin_username = "azureuser"
    admin_password = "hogehoge"
  }


  boot_diagnostics {
    enabled     = "true"
    storage_uri = "${azurerm_storage_account.storageaccount.primary_blob_endpoint}"
  }

  tags = {
    environment = "OSM"
  }
}

以下のコマンドでVMを作成していきます。

terraform init
terraform plan
terraform apply

無事作成されたら、以下のようにログインします。

# VMのパブリックIPアドレスを取得
az vm show --resource-group osmgroup --name osmVM -d --query [publicIps] --o tsv
# パブリックIPアドレスを用いて、VMにSSH接続
ssh azureuser@xx.xxx.xxx.xxx

2. OpenStreetMap Tile Serverのセットアップ

2.1 PostgreSQLとPostGISのセットアップ

PostgreSQLとPostGISは以下のようなものでした12

  • PostgreSQL:オープンソースのリレーショナルデータベース管理システム
  • PostGIS:PostgreSQLの地理情報システムのための拡張モジュール。PostGISにより、地理情報のオブジェクトやそれを操作するための関数などが利用できるようになる

これらをインストールしていきます。

# ソフトウェアのアップグレード
sudo apt update
sudo apt upgrade
# PostgreSQLとPostGISのインストール
sudo apt install postgresql postgresql-contrib postgis postgresql-10-postgis-2.4

PostgreSQLサーバーのスーパーユーザー(パスワードはなし)はインストール中に自動で作成されます。
そのスーパーユーザーでPostgreSQLサーバーにログインし、OpenStreetMap用のデータベースのオーナーを以下のコマンドで作成します。

# サーバーにログイン
sudo -u postgres -i
# オーナーの作成
createuser osm

それでは、データベースを作成し、拡張機能をつけていきます。 拡張機能は先ほどのPostGISとPostgreSQLの型の1種で、キーとバリューの集合を1つの列に管理できるhstoreを利用します。

# データベースを作成
createdb -E UTF8 -O osm gis
# 拡張機能の追加
## 基本構文は「psql -c "SQL文" -d 対象データベース」
psql -c "CREATE EXTENSION postgis;" -d gis
psql -c "CREATE EXTENSION hstore;" -d gis
# テーブルのオーナーを変更
psql -c "ALTER TABLE spatial_ref_sys OWNER TO osm;" -d gis
# ログアウト
eixt

以上でPostgreSQLとPostGISのセットアップが終了しました。

2.2 マップのスタイルシートとマップデータのインストール

まず、OpenStreetMap用のユーザーをVM側に作成し、ユーザーを切り替えます。

# 以下のコマンドを実行した後、new UNIX passwordだけ入力する必要がある
sudo adduser osm
# 先ほど入力したパスワードを用いてログイン
su - osm

以下のコマンドで、マップのスタイルシートとマップデータのインストールをしていきます。
今回は関西地方にしてみました。

# スタイルシート
wget https://github.com/gravitystorm/openstreetmap-carto/archive/v4.20.0.tar.gz
tar xvf v4.20.0.tar.gz
# マップのインストール
wget http://download.geofabrik.de/asia/japan/kansai-latest.osm.pbf
# ログアウト
exit

2.3 PostgreSQLへのマップデータの格納

OpenStreetMapデータをPostgreSQLデータベースで使えるように変換するosm2pgsqlパッケージを使って、格納していきます。

# osm2pgsqlのインストール
sudo apt install osm2pgsql
# osmユーザーへの切り替え
su - osm
# マップデータの格納
 osm2pgsql --slim -d gis --hstore --multi-geometry --number-processes 8 --tag-transform-script /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.lua --style /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.style kansai-latest.osm.pbf

マップデータの格納には以下のように約30分くらいかかりました。

2.4 mod_tileとrenderdのインストール

osmユーザーからログアウトして、以下の2つをインストールしていきます。

  • mod_tile:地図タイルを提供するために必要なApacheモジュール
  • renderd:OpenStreetMapタイルをレンダリングするためのレンダリングデーモン
# ログアウト
exit
# Ubuntuレポジトリに追加
sudo add-apt-repository ppa:osmadmins/ppa
# インストール
sudo apt install libapache2-mod-tile renderd

2.5 Mapnikスタイルシートの作成

Mapnikと必要なパッケージを以下でインストールしていきます。

sudo apt install curl unzip gdal-bin mapnik-utils libmapnik-dev nodejs npm 
sudo npm install -g carto

パッケージのインストールが終わったら、openstreetmap-cartoをインストールしているosmユーザに切り替えます。

su - osm

openstreetmap-cartoのディレクトリに移動し、石川君と同様にget-shapefiles.pyを置き換えます。
おそらく、そのまま実行しようとすると、Not Foundとかエラーが出ると思います。

cd /home/osm/openstreetmap-carto-4.20.0/
vim scripts/get-shapefiles.py
#!/usr/bin/env python

# This script generates and populates the 'data' directory with all needed
# shapefiles.

from __future__ import (
    division,
    absolute_import,
    print_function,
    unicode_literals)
import os
import errno
import tarfile
import zipfile
import subprocess
import distutils.spawn
import argparse
import sys
import tempfile
import logging
import time
import email.utils
import atexit
import time

if sys.version_info >= (3,):
    import urllib.request as urllib2
    import urllib.parse as urlparse
else:
    import urllib2
    import urlparse

start = time.time()
data_dir = 'data'
settings = {
    # Keys 1, 2, 3, ... set the arg short-options and the related process
    # ordering. Use > 0 to allow processing.
    1: {
        'directory': 'world_boundaries',
        'url': 'https://planet.openstreetmap.org/historical-shapefiles/world_boundaries-spherical.tgz',  # noqa
        'type': 'tgz',
        'shp_basename': [
            'world_bnd_m',
            'places',
            'world_boundaries_m'],
        'long_opt': '--world-boundaries'
    },

    2: {
        'directory': 'simplified-land-polygons-complete-3857',
        'url': 'https://osmdata.openstreetmap.de/download/simplified-land-polygons-complete-3857.zip',  # noqa
        'type': 'zip',
        'shp_basename': ['simplified_land_polygons'],
        'long_opt': '--simplified-land'
    },

    3: {
        'directory': 'ne_110m_admin_0_boundary_lines_land',
        'url': 'http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/110m/cultural/ne_110m_admin_0_boundary_lines_land.zip',  # noqa
        'type': 'zip_dir',
        'shp_basename': ['ne_110m_admin_0_boundary_lines_land'],
        'long_opt': '--ne-admin'
    },

    4: {
        'directory': 'land-polygons-split-3857',
        'url': 'https://osmdata.openstreetmap.de/download/land-polygons-split-3857.zip',  # noqa
        'type': 'zip',
        'shp_basename': ['land_polygons'],
        'long_opt': '--land-polygons'
    },

    5: {
        'directory': 'antarctica-icesheet-polygons-3857',
        'url': 'https://osmdata.openstreetmap.de/download/antarctica-icesheet-polygons-3857.zip',  # noqa
        'type': 'zip',
        'shp_basename': ['icesheet_polygons'],
        'long_opt': '--icesheet-polygons'
    },

    6: {
        'directory': 'antarctica-icesheet-outlines-3857',
        'url': 'https://osmdata.openstreetmap.de/download/antarctica-icesheet-outlines-3857.zip',  # noqa
        'type': 'zip',
        'shp_basename': ['icesheet_outlines'],
        'long_opt': '--icesheet-outlines'
    }
}

u_prompt = True


def exit_handler(dir_path):
    # Removing empty directory
    try:
        os.rmdir(dir_path)
    except Exception:
        pass


def download_file(
    url,
    desc=None,
    option_force_update=False,
        option_no_curl=False):
    global u_prompt
    try:
        scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
        file_name = os.path.basename(path)
        if not file_name:
            file_name = 'downloaded.file'
        if desc:
            file_name = os.path.join(desc, file_name)

        org_file_modified = None
        org_file_size = None
        if os.path.exists(file_name):
            org_file_modified = time.localtime((os.path.getmtime(file_name)))
            org_file_size = int(os.path.getsize(file_name))

        curl_used = 0
        if not option_no_curl and distutils.spawn.find_executable("curl"):
            curl_used = 1
            sys.stdout.flush()
            if os.path.exists(file_name) and not option_force_update:
                if subprocess.call(
                    ["curl", "-R", "-z", file_name, "-L", "-o", file_name, url],
                        stderr=subprocess.STDOUT) != 0:
                    sys.exit("\n\n   'curl' error: download failed.\n")
                curl_used = 2
            else:
                if subprocess.call(
                    ["curl", "-R", "-L", "-o", file_name, url],
                        stderr=subprocess.STDOUT) != 0:
                    sys.exit("\n\n   'curl' error: download failed.\n")
            sys.stdout.flush()

        u = urllib2.urlopen(url)
        meta = u.info()

        # Compare dates and sizes
        local_file_modified = None
        local_file_size = None
        if os.path.exists(file_name):
            local_file_modified = time.localtime((os.path.getmtime(file_name)))
            local_file_size = int(os.path.getsize(file_name))
        meta_func = meta.getheaders if hasattr(
            meta, 'getheaders') else meta.get_all
        host_file_modified = email.utils.parsedate(
            meta_func("last-modified")[0])
        meta_length = meta_func("Content-Length")
        host_file_size = None
        if meta_length:
            host_file_size = int(meta_length[0])

        # Do a file check control after using curl (which looks like not
        # including it internally)
        if curl_used == 2 and (host_file_size != local_file_size):
            print(
                "     Warning: file size differs. Downloading the file again.")
            curl_used = 0
        if curl_used > 0:
            u.close()
            if (not option_force_update and local_file_size is not None and
                    (org_file_modified == local_file_modified) and
                    (org_file_size == local_file_size)):
                print("     No newer file to download.")
                return file_name, 0
            else:
                return file_name, 1

        if (not option_force_update and os.path.exists(file_name) and
                (host_file_modified <= local_file_modified) and
                (host_file_size == local_file_size)):
            print("     No newer file to download.", end="")
            if u_prompt:
                print(" (Use -u to force downloading file)", end="")
                u_prompt = False
            print()
            u.close()
            return file_name, 0

        with open(file_name, 'wb') as f:
            print(" Bytes: {0:10}".format(host_file_size))

            file_size_dl = 0
            block_sz = 65536
            while True:
                buffer = u.read(block_sz)
                if not buffer:
                    if file_size_dl != host_file_size:
                        sys.exit("\n\n   Error: download with invalid size.\n")
                    break

                file_size_dl += len(buffer)
                f.write(buffer)

                status = "{0:18}".format(file_size_dl)
                if host_file_size:
                    status += "   [{0:3.0f}%]".format(
                        file_size_dl *
                        100 /
                        host_file_size)
                status += chr(13)
                print(status, end="")
            f.close()
            u.close()
            os.utime(
                file_name,
                (time.mktime(host_file_modified),
                    time.mktime(host_file_modified)))
            print()

        return file_name, 2
    except urllib2.HTTPError as e:
        sys.exit(
            "\n\n   Error: download failed. (error code: " +
            str(e.code) +
            ", error reason: " + e.reason + ")\n")
    except Exception as e:
        sys.exit("\n\n   Error: download failed.\n" + str(e) + "\n")


def main():

    # Option handling

    parser = argparse.ArgumentParser(
        epilog="This script generates and populates the '" + data_dir +
        "' directory with all needed shapefiles, including indexing " +
        " them through shapeindex.")
    parser.add_argument(
        '-c', "--check", dest='option_check_mode', action='store_true',
        help="check whether the '" + data_dir + "' directory already exists")
    parser.add_argument(
        "-d", "--directory", dest="data_dir",
        help="set the name of the data directory (default: '" +
        data_dir + "')",
        default=data_dir, metavar="<directory name>")
    parser.add_argument(
        '-e', "--no-extract", dest='option_no_extract', action='store_true',
        help="do not populate target directories with the expansion " +
        "of downloaded data")
    parser.add_argument(
        '-f', "--force", dest='option_force', action='store_true',
        help="force continuing even if project.mml does not exist")
    parser.add_argument(
        '-l', "--no-curl", dest='option_no_curl', action='store_true',
        help="do not use 'curl' even if available")
    parser.add_argument(
        '-n', "--no-download", dest='option_no_download', action='store_true',
        help="do not download archive if already existing locally")
    parser.add_argument(
        '-p', "--pause", dest='option_pause_mode', action='store_true',
        help="pause before starting")
    parser.add_argument(
        '-r', "--remove", dest='option_remove', action='store_true',
        help="remove each downloaded archive after its expansion")
    parser.add_argument(
        '-s', "--no-shape", dest='option_no_shape', action='store_true',
        help="do not run shapeindex")
    parser.add_argument(
        '-u', "--update", dest='option_force_update', action='store_true',
        help="force performing an update operation even if not needed " +
        "(e.g., downloading, expanding, indexing)")
    for element in sorted(settings):
        parser.add_argument(
            settings[element]['long_opt'],
            dest='option_filter', action='append_const', const=element,
            help="only process " + settings[element]['directory'])

    args = parser.parse_args()

    # Initial checks

    if not args.option_no_shape and (
            not distutils.spawn.find_executable("shapeindex")):
        sys.exit(
            """\n   Error: you need shapeindex (or shapeindex is not in the
   PATH). Otherwise, use '-s' option to skip shapeindex
   (indexing shapes is suggested for performance improvement).\n""")

    if args.option_force:
        os.chdir(os.path.join(os.path.dirname(__file__)))
    else:
        os.chdir(os.path.join(os.path.dirname(__file__), '..'))
        if not os.path.isfile("project.mml"):
            sys.exit(
                """\n   Error: project.mml not found.
   Are you sure you are in the correct folder?
   Otherwise, use '-f' option to go on creating or updating the '""" +
                args.data_dir + """' directory
   placed in the same path of this script.\n""")

    if os.path.isfile(args.data_dir):
        sys.exit(
            """\n   Error: existing file named '""" +
            args.data_dir +
            """'\n""")
    if args.option_check_mode:
        if os.path.isdir(args.data_dir):
            sys.exit(
                """\n   A directory named '""" + args.data_dir +
                """' already exists.
   Please consider renaming it.
   Otherwise, remove '-c' option to allow updating.\n""")

    if args.option_pause_mode:
        print(
            "\nThis script generates and populates the '" + args.data_dir +
            "' directory with all needed shapefiles.\n")
        try:
            input(
                "Press Enter to continue " +
                "(remove '-p' option to avoid this message)...")
        except Exception:
            pass

    print("\nStarting " + os.path.basename(__file__) + "...")

    # Processing

    for element in sorted(settings):

        if (not args.option_filter or
                (args.option_filter and
                 element in args.option_filter)) and element > 0:

            dir_name = settings[element]['directory']
            dir_path = os.path.join(args.data_dir, dir_name)
            path_name = os.path.join(
                args.data_dir,
                settings[element]['url'].rsplit('/', 1)[-1])

            # Creating directory
            try:
                os.makedirs(dir_path)
                atexit.register(exit_handler, dir_path)
            except Exception:
                pass

            # Downloading
            download_type = -1
            if not args.option_no_download or not os.path.isfile(path_name):
                print(str(element) + "-1. Downloading '" + dir_name + "'...")
                archive_file_name, download_type = download_file(
                    settings[element]['url'], args.data_dir,
                    args.option_force_update, args.option_no_curl)

            # Expanding
            if ((not args.option_no_extract and download_type > 0) or
                    args.option_force_update):
                sys.stdout.flush()
                print()
                print(
                    str(element) + "-2. Expanding '" + dir_name + "'...",
                    end="")
                sys.stdout.flush()
                if settings[element]['type'] == 'tgz':
                    tar = tarfile.open(path_name)
                    try:
                        tar.extractall(args.data_dir)
                    except Exception:
                        sys.exit(" Failed (try with -u option).\n")
                    tar.close()
                elif settings[element]['type'] == 'zip':
                    zip = zipfile.ZipFile(path_name)
                    try:
                        zip.extractall(args.data_dir)
                    except Exception:
                        sys.exit(" Failed (try with -u option).\n")
                    zip.close()
                elif settings[element]['type'] == 'zip_dir':
                    zip = zipfile.ZipFile(path_name)
                    try:
                        zip.extractall(dir_path)
                    except Exception:
                        sys.exit(" Failed (try with -u option).\n")
                    zip.close()
                else:
                    sys.exit(
                        "\n\nInternal error: unmanaged 'type'='" +
                        settings[element]['type'] + "'.\n")
                sys.stdout.flush()
                print(" Done.\n")

            # Removing archive
            if args.option_remove:
                try:
                    os.remove(path_name)
                except OSError:
                    sys.exit("\n\n\nCannot remove '" + path_name + "'\n")

            # Indexing
            for item, shp_basename in enumerate(
                    settings[element]['shp_basename']):
                shp_file_name = os.path.join(dir_path, shp_basename + ".shp")
                index_file_name = os.path.join(
                    dir_path, shp_basename + ".index")
                shp_file_modified = None
                if os.path.exists(shp_file_name):
                    shp_file_modified = time.localtime(
                        (os.path.getmtime(shp_file_name)))
                index_file_modified = None
                if os.path.exists(index_file_name):
                    index_file_modified = time.localtime(
                        (os.path.getmtime(index_file_name)))
                if (not args.option_no_shape and shp_file_modified is None
                        and index_file_modified is not None):
                    try:
                        os.remove(index_file_name)
                    except OSError:
                        sys.exit(
                            "\n\n\nCannot remove '" +
                            index_file_name +
                            "'\n")
                if shp_file_modified is None:
                    sys.exit("\n\n\nMissing '" + shp_file_name + "'\n")
                if (args.option_force_update or index_file_modified is None or
                        (shp_file_modified is not None and index_file_modified is not None and
                         (shp_file_modified > index_file_modified))):
                    if args.option_no_shape and index_file_modified is not None:
                        if len(settings[element]['shp_basename']) == 1:
                            print(
                                str(element) +
                                "-3" +
                                ". Removing old index '" +
                                index_file_name +
                                "'...")
                        else:
                            print(str(element) + "-3-" + str(item + 1) +
                                  ". Removing old index '" + index_file_name + "'...")
                        sys.stdout.flush()
                        try:
                            os.remove(index_file_name)
                        except OSError:
                            sys.exit(
                                "\n\n\nCannot remove old index '" +
                                index_file_name +
                                "'\n")
                            pass
                        print()
                    if not args.option_no_shape:
                        if len(settings[element]['shp_basename']) == 1:
                            print(str(element) + "-3" + ". Indexing '" +
                                  shp_file_name + "'...")
                        else:
                            print(str(element) + "-3-" + str(item + 1) +
                                  ". Indexing '" + shp_file_name + "'...")
                        sys.stdout.flush()
                        if (subprocess.call(["shapeindex", "--shape_files",
                                             shp_file_name],
                                            stderr=subprocess.STDOUT) != 0):
                            sys.exit(
                                "\n   Indexing error: shapeindex failed.\n")
                        sys.stdout.flush()
                        print()

    # Finishing
    if time.time()-start < 2:
        print ("...script completed.\n")
    else:
        print ("...script completed in %.1f seconds.\n" % (time.time()-start))

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        sys.exit("\n\n\nInterrupted: you pressed Ctrl+C!\n")
    except Exception as e:
        sys.exit("\n   Error. " + str(e) + "\n")

pythonファイルを以下のコマンドで実行します。

scripts/get-shapefiles.py

エラーなく終了したら、以下のコマンドでMapnik xmlスタイルシートを作成します。

carto project.mml > style.xml
#osm userからのログアウト
exit

2.6 日本語フォントのインストール

デフォルトだと日本語が表示できないのでフォントをインストールしていきます。

sudo apt install fonts-noto-cjk fonts-noto-hinted fonts-noto-unhinted ttf-unifont

2.7 renderdの設定

OpenStreetMapタイルをレンダリングするためのレンダリングデーモン、renderdを設定していきます。

まず、プラグインがどこにインストールされているかを確認します。

mapnik-config --input-plugins

renderd.configをvimで開いて、以下のように変更します。

sudo vim /etc/renderd.conf

# renderd.confの以下変更点
## [default]
XML=/home/osm/openstreetmap-carto-4.20.0/style.xml
## [mapnik]:先ほど調べたプラグインの場所に変更(以下は例)
plugins_dir=/usr/lib/mapnik/3.0/input/
## 日本語フォントが適応されるように変更
font_dir=/usr/share/fonts/truetype
font_dir_recurse=true

次にinitスクリプトファイルも書き換えます。

sudo vim /etc/init.d/renderd

# 以下renderdファイルの変更点
## 実行ユーザーを変更
RUNASUSER=osm

描画したタイルを保存するディレクトリのオーナーを以下のコマンドで変更します。

sudo chown osm:osm /var/lib/mod_tile/ -R

renderdを再起動します。

sudo systemctl daemon-reload
sudo systemctl restart renderd

2.8 Apacheの設定

ドメインネームを変更していない場合は特に設定を変更する必要はなさそうです。
念のため、再起動しておきます。

sudo systemctl restart apache2

ブラウザでxx.xxx.xxx.xxx/osm/0/0/0.png(xx.xxx.xxx.xxxはVMのパブリックIPアドレス)にアクセスすると、このように表示されるはずです。

また、この記事などを参考にしてReactアプリからこのOpenstreetMapサーバーにアクセスするときはhttp://xx.xxx.xxx.xxx/osm/{z}/{x}/{y}.png(xx.xxx.xxx.xxxはVMのパブリックIPアドレス)を用います。

まとめ

OpenStreetMapのタイルサーバーのセットアップをUbuntu18.04で行いました。
サーバーサイドの知識不足で長い戦いとなったので、精進したいです。