DockerでMapServerを動かしてみる

:::note info 
これは MIERUNE AdventCalendar 2024 1日目の記事です! 
:::

# MapServerとは？
![MapServer.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/c2b70d7d-2fdc-5fdd-6e20-3a3e0d4436f8.jpeg)


提供元：University of Minnesota、MIT、Wikimedia Commons（https://commons.wikimedia.org/w/index.php?curid=10007423）


https://mapserver.org/

MapServerは、オープンソースの地図配信ソフトウェアで、地理空間データを処理・可視化するために使用されます。1990年代後半に、アメリカのミネソタ大学ツインシティー校によって開発され、GIS（地理情報システム）の分野で広く利用されてきました。

MapServerの主な特徴の一つは、さまざまなフォーマットの地理空間データを動的に処理し、ラスタータイルとして動的に配信できる能力です。この配信能力は、OGC（Open Geospatial Consortium）が定めた国際標準規格であるWMS（Web Map Service）に対応している点で特に注目されてました。

WMSは、地理空間データをインターネット上で共有するための標準規格であり、リクエストに応じて地図画像や地理情報データを提供する仕組みを提供します。MapServerは、このWMS規格に準拠して動作することで、さまざまなクライアントソフトウェアやシステムと連携し、地理空間データの効率的な配信を実現します。

https://www.ogc.org/publications/standard/wms/



# 背景の変化とMapServerの現状

近年はブラウザの処理能力の向上により、地図データの描画や処理をクライアント側で行うことが可能になり、地図配信のアーキテクチャが大きく変化してきました。特に、WebGLを活用したライブラリや、ベクトルタイル形式の普及により、サーバーはデータ配信に特化し、地図のスタイリングや描画はクライアント側で行うケースが増えています。

また、MapServerのようにサーバー側でラスタータイルを動的に生成して配信するモデルの利用は減少傾向にあります。現在では、ラスタータイルは動的に生成されるのではなく、事前に生成された静的なタイルとしてキャッシュやCDNを通じて配信される傾向が強まっています。このアプローチは、パフォーマンスやスケーラビリティの観点で優れているためです。

しかし、古いWebGISシステムやレガシーな環境では、依然としてMapServerが利用されているケースが多く見られます。ちなみに、現在でもMapServerは継続的にアップデートされています。

# MapServerを動かしてみる

PCを汚したくないので、今回はドッカーでMapServerを動かしてみます。ディレクトリ構成は以下になります。docker-compose.ymlとMapfile（`sample.map`）を用意します。
```sh
.
├── mapfile # マップファイルを格納するディレクトリ
│   ├── data
│   │   ├──（ここにデータを入れる）
│   └── sample.map
└── docker-compose.yml
```

ドッカーイメージはこちらになります。

https://hub.docker.com/r/mapserver/mapserver


しかし、私のM1 Mac 環境だと動かず、代わりにこちらのイメージを使用しました。

https://hub.docker.com/layers/camptocamp/mapserver/8.0-gdal3.7-arm64/images/sha256-7f34387a679d5d2e3bc6f37fc2963dae8e23e04bf0be6b3f059933ba493dec45


docker-compose.ymlに書き込みます。

```yml
services:
  mapserver:
    # M1 Macの場合を想定してるので、もし動かない場合は`mapserver/mapserver`のimageを試してください
    image: camptocamp/mapserver:8.0-gdal3.7-arm64
    container_name: mapserver
    ports:
      - "8080:80"
    volumes:
      - ./mapfile:/etc/mapserver
    environment:
      - MS_MAPFILE=/etc/mapserver/sample.map
      - MS_DEBUGLEVEL=5
      - MS_MAP_NO_PATH=true
```




# 使用するデータ


https://www.naturalearthdata.com/

| データ名| データタイプ |　データ形式 |
|:-----------|:------------:|:------------:|
|Airports    | ポイント       | Shape         |
| Roads    | ライン     | Shape      |
| Urban Areas       | ポリゴン        | Shape         |
|Ocean Bottom       | ラスター        | GeoTiff           |


先ほどのディレクトリに以下のようにデータを格納します。
```
.
└── mapfile
    ├── data
    │   ├── ne_10m_admin_0_countries
    │   │   ├── ne_10m_admin_0_countries.cpg
    │   │   ├── ne_10m_admin_0_countries.dbf
    │   │   ├── ne_10m_admin_0_countries.prj
    │   │   ├── ne_10m_admin_0_countries.shp
    │   │   └── ne_10m_admin_0_countries.shx
    │   ├── ne_10m_airports
    │   │   ├── ne_10m_airports.cpg
    │   │   ├── ne_10m_airports.dbf
    │   │   ├── ne_10m_airports.prj
    │   │   ├── ne_10m_airports.shp
    │   │   └── ne_10m_airports.shx
    │   ├── ne_10m_roads
    │   │   ├── ne_10m_roads.cpg
    │   │   ├── ne_10m_roads.dbf
    │   │   ├── ne_10m_roads.prj
    │   │   ├── ne_10m_roads.shp
    │   │   └── ne_10m_roads.shx
    │   ├── OB_LR
    │   │   ├── OB_LR.prj
    │   │   ├── OB_LR.tfw
    │   │   └── OB_LR.tif
    └── sample.map
```
# Mapfileを書いてみる

MapServerはMapfile(`.map`)を使用して、各レイヤーの描画スタイル（色、ラベル、シンボルなど）を細かく設定できます。


また、VSCodeの場合、以下の拡張機能を使うとMapfileのテキストが見やすくなります

https://marketplace.visualstudio.com/items?itemName=chicoff.mapfile

```sh:sample.map
MAP
    NAME "TestMapServer"
    UNITS DD
    EXTENT -180 -90 180 90 
    PROJECTION
        "init=EPSG:4326"
    END

    WEB
        METADATA
            "wms_title" "Test WMS Server"
            "wms_onlineresource" "http://localhost:8080/cgi-bin?/mapserv?map=/map/m1.map"
            "wms_srs" "EPSG:3857 EPSG:4326"
            "wms_enable_request" "*"
        END
    END

    OUTPUTFORMAT
        NAME "png"
        DRIVER AGG/PNG
        MIMETYPE "image/png"
        IMAGEMODE RGBA
        EXTENSION "png"
        TRANSPARENT ON
    END

    # シンボルの形状の定義
    SYMBOL
        NAME "circle"
        TYPE ELLIPSE
        POINTS 1 1 END
        FILLED TRUE
    END


    # Point Layer
    LAYER
        NAME "ne_10m_airports"
        TYPE POINT
        STATUS ON
        DATA "data/ne_10m_airports/ne_10m_airports.shp"
        METADATA
            "wms_title" "ne_10m_airports"
        END
        CLASS
            NAME "ne_10m_airports"
            STYLE
                COLOR 255 0 0
                SYMBOL "circle" # シンボルの形状を指定（デフォルト設定が必要）
                SIZE 12
            END
        END
    END

    # LineLayer
    LAYER
        NAME "ne_10m_roads"
        TYPE LINE
        STATUS ON
        DATA "data/ne_10m_roads/ne_10m_roads.shp"
        METADATA
            "wms_title" "ne_10m_roads"
        END
        CLASS
            NAME "ne_10m_roads"
            STYLE
                OUTLINECOLOR 0 255 0
                WIDTH 2
            END
        END
    END

    # Polygon Layer
    LAYER
        NAME "ne_10m_admin_0_countries"
        TYPE POLYGON
        STATUS ON
        DATA "data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp"
        METADATA
            "wms_title" "ne_10m_admin_0_countries"
        END
        CLASS
            NAME "ne_10m_admin_0_countries"
            STYLE
                COLOR 255 175 0
                OUTLINECOLOR 0 0 0
            END
        END
    END

    # GeoTIFF Layer
    LAYER
        NAME "OB_LR"
        TYPE RASTER
        STATUS ON
        DATA "data/OB_LR/OB_LR.tif"  # GeoTIFFファイルのパス
        METADATA
            "wms_title" "OB_LR"
        END
        PROJECTION
            "init=EPSG:4326"  # GeoTIFFが使用する座標系を指定
        END
        CLASS
            NAME "OB_LR"
        END
    END

END

```

Mapfileを書いたところでドッカーを起動します。
```sh
docker compose up -d
```

起動後、以下のURLにブラウザでリクエストしてみます。

```
http://localhost:8080/cgi-bin?/mapserv?map=/map/sample.map&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities
```

するとMapServerがWMS（Web Map Service）のGetCapabilitiesリクエストを処理し、サービスの詳細情報を返します。この情報には、提供される地図データのレイヤー構成やサポートされる投影法、リクエスト可能な形式などが含まれます。

返されるXML形式のレスポンスは、クライアントソフトウェアによって解釈され、地図データの利用が可能になります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/1bd9d718-6e67-43bf-6b36-46261dc4ea02.png)




# クライアントで描画してみる

## QGIS

https://docs.qgis.org/3.34/ja/docs/server_manual/services/wms.html

ブラウザパネルの「WMS/WMTS」の項目を右クリックして「新規接続」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/50bde6af-8aec-8831-7b61-2d0bcee9bcb7.png)

名称は任意の文字列を入れ、URLに以下のリクエストURLを記入して「OK」を選択します
```
http://localhost:8080/cgi-bin?/mapserv?map=/map/sample.map
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/49869143-9471-f335-3bd5-e5f5dcc559ef.png)


ブラウザパネルの「WMS/WMTS」のトグルを展開すると先ほど登録したものが表示されるので、さらにトグルを展開すると、Mapfileに記入したレイヤー群が表示されるので、ダブルクリックすることでレイヤーパネルに追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/1935fe52-f5c5-414a-12c4-52c189baf055.png)

地図に描画された様子です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/1eb71d78-9969-c62b-46bb-ce35e4f89fce.png)


## MapLibre GL JS


公式のこちらのexamplesを参考にして描画してみます。

https://maplibre.org/maplibre-gl-js/docs/examples/wms/

`&LAYERS=`の部分にMapfileで定義したレイヤー名をカンマ区切りで指定することとで、タイル画像として描画されます。ちなみに、後に書いたレイヤーの方が上に重なるようになっています。


```ts
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

// 地図の表示
const map = new maplibregl.Map({
    container: 'map',
    style: {
        version: 8,
        glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',
        sources: {
            'wms-source': {
                type: 'raster',
                tiles: [
                    'http://localhost:8080/cgi-bin?/mapserv?map=/map/sample.map&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=OB_LR,ne_10m_admin_0_countries,ne_10m_roads,ne_10m_airports&STYLES=&CRS=EPSG:3857&BBOX={bbox-epsg-3857}&WIDTH=256&HEIGHT=256&FORMAT=image/png',
                ],
                tileSize: 256,
            },
        },
        layers: [
            {
                id: 'wms-layer',
                type: 'raster',
                source: 'wms-source',
                paint: {
                    'raster-opacity': 1.0,
                },
            },
        ],
    },
    center: [135.41533470153811, 34.736675273476976],
    zoom: 4,
});

// NavigationControl
map.addControl(new maplibregl.NavigationControl(), 'top-right');

```

MapLibreで描画した様子です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/a4527342-0fbd-0d25-32f3-cd5582983194.png)


Chromeのコンソール画面でタイルリクエストを確認すると、複数のレイヤーが一枚のタイル画像として返してくれてるのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1575765/adfa555d-b26d-180f-5e3c-fb0a1a1d5168.png)






## おわりに
今回MapServerを触るにあたり、ネット上に公開されている情報が少なく、環境を整えるのに苦戦しました。冒頭でも書きましたが、サーバーサイドで動的にラスタータイルを生成するMapServerのアプローチが時代のニーズに合わなくなってきているためであると考えられます。

:::note info 
明日は@nbayashiさんによる記事です！お楽しみに！！ 
:::