Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Martin

Martin 是一个能够从大型 PostGIS 数据库、PMTiles(本地或远程)以及 MBTiles 文件动态生成和提供矢量瓦片的瓦片服务器,允许将多个瓦片源动态组合为一个。Martin 针对速度和大流量进行了优化,使用 Rust 编写。

另请参阅 Martin 演示站点


Book docs.rs docs GitHub crates.io version Security audit CI build

Martin 快速入门指南

选择您的操作系统以开始使用 Martin 瓦片服务器

在 Linux 上快速开始

mkdir martin
cd martin

# 下载一些示例数据
curl -L -O https://github.com/maplibre/martin/raw/main/tests/fixtures/mbtiles/world_cities.sql

# 检查是否安装了 sqlite
sqlite3 --version

# 初始化数据库
sqlite3 world_cities.mbtiles < world_cities.sql

# 下载最新版本的 Martin 二进制文件,解压并设置为可执行
curl -L -O https://github.com/maplibre/martin/releases/latest/download/martin-x86_64-unknown-linux-gnu.tar.gz
tar -xzf martin-x86_64-unknown-linux-gnu.tar.gz
chmod +x ./martin

# 显示 Martin 帮助屏幕
./martin --help

# 使用示例数据作为唯一的瓦片源运行 Martin
./martin world_cities.mbtiles

查看地图

有关如何查看地图的说明,请参阅使用 QGIS 快速入门。

在 macOS 上快速开始

  1. 下载一些演示瓦片。

  2. 从发布页面下载最新版本的 Martin。 使用关于本机查找您的处理器类型。

  3. 解压两个文件的内容并将它们放在同一个目录中。

  4. 打开命令提示符并导航到 martin 和 world_cities.sql 所在的目录。

  5. 运行以下命令使用演示数据启动 Martin:

# 初始化数据库
sqlite3 world_cities.mbtiles < world_cities.sql

# 显示 Martin 帮助屏幕
./martin --help

# 使用示例数据作为唯一的瓦片源运行 Martin
./martin world_cities.mbtiles

查看地图

有关如何查看地图的说明,请参阅使用 QGIS 快速入门。

在 Windows 上快速开始

  1. 下载一些演示瓦片。

  2. 从发布页面下载最新的 Windows 版本 Martin:martin-x86_64-pc-windows-msvc.zip

  3. 解压两个文件的内容并将它们放在同一个目录中。

  4. 打开命令提示符并导航到 martin 和 world_cities.sql 所在的目录。

  5. 运行以下命令使用演示数据启动 Martin:

# 检查是否安装了 sqlite
sqlite3 --version

# 初始化数据库
sqlite3 world_cities.mbtiles < world_cities.sql

# 显示 Martin 帮助屏幕
martin --help

# 使用示例数据作为唯一的瓦片源运行 Martin
martin world_cities.mbtiles

查看地图

有关如何查看地图的说明,请参阅使用 QGIS 快速入门。

使用 QGIS 查看地图

  1. 为您的平台下载、安装并运行 QGIS

  2. 添加一个新的 矢量瓦片 连接

    alt text

  3. 在 矢量瓦片连接 对话框中,为其命名并输入 Martin 服务器的 URL, 例如 http://localhost:3000/world_cities/{z}/{x}/{y},然后点击 确定。

    alt text

  4. 在 QGIS 浏览器面板(左侧),双击新添加的连接,或右键单击它并点击 将图层添加到项目。

    alt text

  5. 现在地图应该在 QGIS 地图视图中可见。

    alt text

前置要求

如果使用 Martin 连接 PostgreSQL 数据库,您必须安装 PostGIS v3.0+。推荐使用 PostGIS v3.1+。

Docker

Martin 也可作为 Docker 镜像使用。您可以通过 -v 参数将主机上的配置文件共享给容器,或者让 Martin 自动发现所有源,例如通过传递 DATABASE_URL 或指定 .mbtiles/.pmtiles 文件或 .pmtiles 的 URL。

export PGPASSWORD=postgres  # 密码!

docker run -p 3000:3000 \
           -e PGPASSWORD \
           -e DATABASE_URL=postgres://user@host:port/db \
           -v /path/to/config/dir:/config \
           ghcr.io/maplibre/martin:0.20.2 \
           --config /config/config.yaml

手动从二进制发行版安装

您可以从 GitHub 发布页面下载 martin。

平台x64ARM-64
Linux.tar.gz (gnu)
.tar.gz (musl)
.deb
.tar.gz (gnu)
.tar.gz (musl)
macOS.tar.gz.tar.gz
Windows.zip

Rust 用户可以使用 cargo-binstall 和 cargo 安装预构建的 martin 二进制文件。

cargo install cargo-binstall
cargo binstall martin
martin --help

从软件包安装

要使用 apt 源和其他方式安装,我们需要您的帮助来改进各种平台的打包。

Homebrew

如果您使用 macOS 和 Homebrew,可以使用 Homebrew tap 安装 martin。

brew tap maplibre/martin
brew install martin
martin --help

手动安装 Debian 软件包 (x86_64)

curl -O https://github.com/maplibre/martin/releases/latest/download/martin-Debian-x86_64.deb
sudo dpkg -i ./martin-Debian-x86_64.deb
martin --help
rm ./martin-Debian-x86_64.deb

从源码构建

如果您安装了 Rust,可以使用 Cargo 从源码构建 martin:

cargo install martin --locked
martin --help

使用

Martin 至少需要一个 PostgreSQL 连接字符串或一个瓦片源文件作为命令行参数。PG 连接字符串也可以通过 DATABASE_URL 环境变量传递。

martin postgres://postgres@localhost/db

Martin 为数据库中每个启用地理空间的表提供 TileJSON 端点。

命令行界面

您可以使用命令行界面配置 Martin。 有关更多信息,请参阅 martin --help 或 cargo run -- --help:

Blazing fast and lightweight tile server with PostGIS, MBTiles, and PMTiles support

Usage: martin [OPTIONS] [CONNECTION]...

Arguments:
  [CONNECTION]...
          Connection strings, e.g. postgres://... or /path/to/files

Options:
  -c, --config <CONFIG>
          Path to config file. If set, no tile source-related parameters are allowed

      --save-config <SAVE_CONFIG>
          Save resulting config to a file or use "-" to print to stdout. By default, only print if sources are auto-detected

  -C, --cache-size <CACHE_SIZE>
          Main cache size (in MB)

  -s, --sprite <SPRITE>
          Export a directory with SVG files as a sprite source. Can be specified multiple times

  -f, --font <FONT>
          Export a font file or a directory with font files as a font source (recursive). Can be specified multiple times

  -S, --style <STYLE>
          Export a style file or a directory with style files as a style source (recursive). Can be specified multiple times

  -k, --keep-alive <KEEP_ALIVE>
          Connection keep alive timeout. [DEFAULT: 75]

  -l, --listen-addresses <LISTEN_ADDRESSES>
          The socket address to bind. [DEFAULT: 0.0.0.0:3000]

      --base-path <BASE_PATH>
          Set TileJSON URL path prefix.

          This overrides the default of respecting the X-Rewrite-URL header. Only modifies the JSON (TileJSON) returned, martins' API-URLs remain unchanged. If you need to rewrite URLs, please use a reverse proxy. Must begin with a /.

          Examples: /, /tiles

  -W, --workers <WORKERS>
          Number of web server workers

      --preferred-encoding <PREFERRED_ENCODING>
          Martin server preferred tile encoding. [DEFAULT: gzip]

          If the client accepts multiple compression formats, and the tile source is not pre-compressed, which compression should be used. gzip is faster, but brotli is smaller, and may be faster with caching.

          [possible values: brotli, gzip]

  -u, --webui <WEB_UI>
          Control Martin web UI. [DEFAULT: disabled]

          Possible values:
          - disable:        Disable Web UI interface. This is the default, but once implemented, the default will be enabled for localhost.
          - enable-for-all: Enable Web UI interface on all connections

  -b, --auto-bounds <AUTO_BOUNDS>
          Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]

          Possible values:
          - quick: Compute table geometry bounds, but abort if it takes longer than 5 seconds
          - calc:  Compute table geometry bounds. The startup time may be significant. Make sure all GEO columns have indexes
          - skip:  Skip bounds calculation. The bounds will be set to the whole world

      --ca-root-file <CA_ROOT_FILE>
          Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates

  -d, --default-srid <DEFAULT_SRID>
          If a spatial PG table has SRID 0, then this default SRID will be used as a fallback

  -p, --pool-size <POOL_SIZE>
          Maximum Postgres connections pool size [DEFAULT: 20]

  -m, --max-feature-count <MAX_FEATURE_COUNT>
          Limit the number of geo features per tile.

          If the source table has more features than set here, they will not be included in the tile and the result will look "cut off"/incomplete. This feature allows to put a maximum latency bound on tiles with extreme amount of detail at the cost of
          not returning all data. It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at Null Island.

          Can be either a positive integer or unlimited if omitted.

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Use RUST_LOG environment variable to control logging level, e.g. RUST_LOG=debug or RUST_LOG=martin=debug. See https://docs.rs/env_logger/latest/env_logger/index.html#enabling-logging for more information.

环境变量

您可以使用环境变量配置 Martin,但仅限于未使用配置文件时。 配置文件本身可以在需要时使用环境变量。 有关如何在配置文件中使用环境变量的信息,请参阅配置部分。 另请参阅下面的 SSL 配置部分。

环境变量
配置文件键
示例描述
DATABASE_URL
connection_string
postgres://
postgres@localhost/db
Postgres 数据库连接
DEFAULT_SRID
default_srid
4326如果 PostgreSQL 表的几何列 SRID=0,则使用此值
PGSSLCERT
ssl_cert
./postgresql.crt包含客户端 SSL 证书的文件。文档
PGSSLKEY
ssl_key
./postgresql.key包含客户端 SSL 证书密钥的文件。文档
PGSSLROOTCERT
ssl_root_cert
./root.crt包含受信任根证书的文件。该文件应包含一系列 PEM 格式的 CA 证书。文档
AWS_LAMBDA_RUNTIME_API
-
如果已定义,连接到 AWS Lambda 处理请求。不使用常规 HTTP 服务器。请参阅在 AWS Lambda 中运行

托管环境特定指南

为了帮助使用常见的运行时环境,我们编写了以下指南:

使用 Docker 运行

您可以使用官方 Docker 镜像 ghcr.io/maplibre/martin

使用非本地 PostgreSQL

docker run \
  -p 3000:3000 \
  -e DATABASE_URL=postgres://postgres@postgres.example.org/db \
  ghcr.io/maplibre/martin:0.20.2

公开本地文件

您可以使用 -v 标志将本地文件公开给 Docker 容器。

docker run \
  -p 3000:3000 \
  -v /path/to/local/files:/files \
  ghcr.io/maplibre/martin:0.20.2 \
  /files

在 Linux 上访问本地 PostgreSQL

如果您在 localhost 上运行 PostgreSQL 实例,则必须更改网络设置以允许 Docker 容器访问 localhost 网络。

对于 Linux,添加 --net=host 标志以访问 localhost PostgreSQL 服务。您不需要使用 -p 导出端口,因为容器已经在使用主机网络。

docker run \
  --net=host \
  -e DATABASE_URL=postgres://postgres@localhost/db \
  ghcr.io/maplibre/martin:0.20.2

在 macOS 上访问本地 PostgreSQL

对于 macOS,使用 host.docker.internal 作为主机名来访问 localhost PostgreSQL 服务。

docker run \
  -p 3000:3000 \
  -e DATABASE_URL=postgres://postgres@host.docker.internal/db \
  ghcr.io/maplibre/martin:0.20.2

在 Windows 上访问本地 PostgreSQL

对于 Windows,使用 docker.for.win.localhost 作为主机名来访问 localhost PostgreSQL 服务。

docker run \
  -p 3000:3000 \
  -e DATABASE_URL=postgres://postgres@docker.for.win.localhost/db \
  ghcr.io/maplibre/martin:0.20.2

使用 Docker Compose 运行

您可以使用示例 docker-compose.yml 文件作为参考

services:
  martin:
    image: ghcr.io/maplibre/martin:0.20.2
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://postgres:password@db/db
    depends_on:
      - db

  db:
    image: postgis/postgis:17-3.5-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=db
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      # 在 docker 容器外的本地目录中持久化 PostgreSQL 数据
      - ./pg_data:/var/lib/postgresql/data

首先,您需要启动 db 服务

docker compose up -d db

然后,在 db 服务准备好接受连接后,您可以启动 martin

docker compose up -d martin

默认情况下,Martin 将在 localhost:3000 可用

官方 Docker 镜像包含一个 HEALTHCHECK 指令,将被 Docker Compose 使用。请注意,Compose 不会重启不健康的容器。要监控和重启不健康的容器,您可以使用 Docker Autoheal。

使用 AWS Lambda - v0.14+

Martin 可以在 AWS Lambda 中运行。如果您想从无服务器环境提供瓦片,同时从 PostgreSQL 数据库或 S3 中的 PMTiles 文件访问“附近“数据,而不向世界公开原始文件以防止下载滥用并提高性能,这非常有用。

Lambda 有两种部署模型:zip 文件和基于容器。使用 zip 文件部署时,有一个在线代码编辑器可编辑 yaml 配置。使用基于容器的部署时,我们可以在命令行或环境变量中传递配置。

一切都可以通过 AWS CloudShell 执行,或者您可以安装 AWS CLI 和 AWS SAM CLI,并配置身份验证。CloudShell 也在特定的 AWS 区域中运行。

容器部署

Lambda 镜像必须来自公共或私有 ECR 注册表。从 GHCR 拉取镜像并推送到 ECR。

$ docker pull ghcr.io/maplibre/martin:0.20.2 --platform linux/arm64
$ aws ecr create-repository --repository-name martin
[…]
        "repositoryUri": "493749042871.dkr.ecr.us-east-2.amazonaws.com/martin",

# 读取包含您帐户号码的 repositoryUri
$ docker tag ghcr.io/maplibre/martin:0.20.2 493749042871.dkr.ecr.us-east-2.amazonaws.com/martin:latest
$ aws ecr get-login-password --region us-east-2 \
  | docker login --username AWS --password-stdin 493749042871.dkr.ecr.us-east-2.amazonaws.com
$ docker push 493749042871.dkr.ecr.us-east-2.amazonaws.com/martin:latest

打开 Lambda 控制台并创建您的函数:

  1. 点击 “Create function”。
  2. 选择 “Container image”。
  3. 在 “Function name” 中输入名称。
    • 注意:这是一个内部标识符,不会在函数 URL 中公开。
  4. 点击 “Browse images”,然后选择您的仓库和标签。
    • 如果找不到,请查看您是否在同一区域?
  5. 展开 “Container image overrides”,在 CMD 下放置 .pmtiles 文件的 URL。
  6. 将 “Architecture” 设置为 arm64 以匹配我们拉取的平台。Lambda 的 ARM CPU 比 x86 更好。
  7. 点击 “Create function”。
  8. 找到 “Configuration” 选项卡,选择 “Function URL”,“Create function URL”。
  9. 将 “Auth type” 设置为 NONE
    • 不要启用 CORS。Martin 已经有 CORS 支持,因此它会创建不正确的重复标头。
  10. 点击 “Function URL”。
  11. 要调试问题,请打开 “Monitor” 选项卡,“View CloudWatch logs”,找到最近的日志流。

Zip 部署

可以从 AWS 控制台部署整个代码库,但我们将使用无服务器应用程序模型。我们的函数将由一个 “Layer”(包含 Martin 二进制文件)组成,我们的函数本身将以 yaml 格式包含配置。

Layer

下载二进制文件并将其放置在您的暂存目录中。Layer 的 bin 目录将被添加到 PATH。

mkdir -p martin_layer/src/bin/
cd martin_layer
curl -OL https://github.com/maplibre/martin/releases/latest/download/martin-aarch64-unknown-linux-musl.tar.gz
tar -C src/bin/ -xzf martin-aarch64-unknown-linux-musl.tar.gz martin

每个基于 zip 的 Lambda 函数都运行一个名为 bootstrap 的文件。

cat <<EOF >src/bootstrap
#!/bin/sh
set -eu
exec martin --config \${_HANDLER}.yaml
EOF

编写 SAM 模板。

cat <<EOF >template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  MartinLayer:
    Type: 'AWS::Serverless::LayerVersion'
    DeletionPolicy: Delete
    Properties:
      ContentUri: src
      CompatibleRuntimes:
      - provided.al2023
      CompatibleArchitectures:
      - arm64
Outputs:
  LayerArn:
    Value: !Ref MartinLayer
    Export:
      Name: !Sub "${AWS::StackName}-LayerArn"
EOF

运行 sam deploy --guided。

  1. Stack Name:为您的 CloudFormation 堆栈命名,例如 martin-layer。
  2. 其他所有选项按 Enter
  3. 设置保存到 samconfig.toml,因此您以后可以执行 sam deploy 来更新版本,或执行 sam delete。

现在,如果您访问 Lambda 控制台并选择 “Layers”,您应该会看到您的 layer。

函数

  1. 选择 “Functions”,“Create function”。
  2. 在 “Function name” 中输入名称。
  3. 将 “Runtime” 设置为 “Amazon Linux 2023”。
  4. 将 “Architecture” 设置为 “arm64”。
  5. 在 “Advanced settings” 下,选择 “Enable function URL”,“Auth type” 为 “NONE”。
  6. 点击 “Create function”。

添加您的 layer:

  1. 点击 “add a layer”(顶部的绿色横幅,或最底部)。
  2. 选择 “Custom layers”,然后选择您的 layer 及其版本。
  3. 点击 “Add”。

在函数源代码中添加您的配置文件:

  1. Code 选项卡,File,New File:hello.handler.yaml。

    pmtiles:
      sources:
        demotiles: <url to a pmtiles file>
    
  2. 点击 Deploy,等待成功横幅,然后访问您的函数 URL。

TODO

AWS Lambda 支持是初步的;有功能要添加到 Martin,配置要调整,文档要改进。欢迎您的帮助。

  • Lambda 的默认超时为 3 秒,内存为 128 MB,这可能不是最优的。
  • 记录如何连接到 RDS 上的 PostgreSQL 数据库。
  • 设置 CloudFront CDN,这是一件大事,但解释动机和基础知识。
  • 授予执行角色从 S3 存储桶读取对象的权限,并教 Martin 如何向 S3 发出经过身份验证的请求。
  • 教 Martin 如何从 S3 存储桶提供所有 PMTiles 文件,而不必在启动时列出它们。
  • 教 Martin 如何设置 Cache-Control 和 Etag 标头以获得更好的默认值。

反向代理

Martin 可以在没有反向代理的情况下运行。

这样做有一些缺点:

  • Martin 不支持 HTTPS 连接(TLS 终止)。
  • 我们不检查 HOST 头 - 我们只是在端口上提供服务。 这意味着任何人都可以将他们的 DNS 记录指向您的服务器,并为所有指向 Martin 运行端口的请求提供服务。 使用反向代理可以使这种滥用变得明显。
  • Martin 仅支持简单的内存缓存。 如果您需要更高级的缓存选项,可以使用反向代理,如 Nginx、Varnish 或带有自定义规则的 Apache。 例如,您可以选择仅缓存缩放级别 0..10。
  • 您可能需要在单个域名下托管的不仅仅是瓦片。
  • Martin 具有固定的公共 API,但您的站点可能需要不同的结构,例如从子路径(如 /tiles/source/z/x/y)提供瓦片。

与 NGINX 一起使用

您可以在 NGINX 代理后面运行 Martin,这样您可以使用自定义逻辑缓存经常访问的瓦片。 这是一个运行 Martin 与 NGINX 和 PostgreSQL 的示例 docker-compose.yml 文件。

version: '3'

services:
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./cache:/var/cache/nginx
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - martin

  martin:
    image: maplibre/martin:v0.7.0
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgres://postgres:password@db/db
    depends_on:
      - db

  db:
    image: postgis/postgis:14-3.3-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=db
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - ./pg_data:/var/lib/postgresql/data

您可以在此处找到示例 NGINX 配置文件。

重写 URL

如果您在 NGINX 代理后面运行 Martin,您可能希望重写请求 URL 以正确处理 TileJSON 中的瓦片 URL。

location ~ /tiles/(?<fwd_path>.*) {
    proxy_set_header  X-Rewrite-URL $uri;
    proxy_set_header  X-Forwarded-Host $host:$server_port;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_redirect    off;

    proxy_pass        http://martin:3000/$fwd_path$is_args$args;
}

缓存瓦片

您还可以使用 NGINX 缓存瓦片。在示例中,最大缓存大小设置为 10GB,代码为 200、204 和 302 的响应的缓存时间设置为 1 小时,代码为 404 的响应的缓存时间设置为 1 分钟。

http {
  ...
  proxy_cache_path  /var/cache/nginx/
                    levels=1:2
                    max_size=10g
                    use_temp_path=off
                    keys_zone=tiles_cache:10m;

  server {
    ...
    location ~ /tiles/(?<fwd_path>.*) {
        proxy_set_header        X-Rewrite-URL $uri;
        proxy_set_header        X-Forwarded-Host $host:$server_port;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_redirect          off;

        proxy_cache             tiles_cache;
        proxy_cache_lock        on;
        proxy_cache_revalidate  on;

        # 设置响应的缓存时间
        proxy_cache_valid       200 204 302 1h;
        proxy_cache_valid       404 1m;

        proxy_cache_use_stale   error timeout http_500 http_502 http_503 http_504;
        add_header              X-Cache-Status $upstream_cache_status;

        proxy_pass              http://martin:3000/$fwd_path$is_args$args;
    }
  }
}

您可以在此处找到示例 NGINX 配置文件。

与 Apache 一起使用

您可以在 Apache “代理“后面运行 Martin,这样您就可以使用 HTTPs。以下是使用 Apache 运行 Martin 的配置文件示例。

首先,您必须设置一个在端口 443 上工作的虚拟主机。

启用必要的模块

确保所需的模块已启用:


sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod headers
sudo a2enmod rewrite

修改您的 VHOST 配置

打开您正在使用的域名的 VHOST 配置文件,mydomain.tld:


sudo nano /etc/apache2/sites-available/mydomain.tld.conf

更新配置


<VirtualHost *:443>
    ServerName mydomain.tld
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/mydomain
    ProxyPreserveHost On

    RewriteEngine on
    RewriteCond %{REQUEST_URI} ^/tiles/(.*)$
    RewriteRule ^/tiles/(.*)$ http://localhost:3000/tiles/$1 [P,L]

    <IfModule mod_headers.c>
        RequestHeader set X-Forwarded-Proto "https"
    </IfModule>

    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
</VirtualHost>

检查配置:验证 Apache 配置的语法错误


sudo apache2ctl configtest

重启 Apache:如果配置正确,重启 Apache 以应用更改


sudo systemctl restart apache2

故障排除

日志级别是按模块控制的,默认情况下,除错误外,所有日志记录都被禁用。日志记录通过 RUST_LOG 环境变量控制。此环境变量的值是以逗号分隔的日志记录指令列表。

这将为所有模块启用调试日志记录:

export RUST_LOG=debug
martin postgres://postgres@localhost/db

而这只会为 actix_web 模块启用详细日志记录,并为 martin 和 tokio_postgres 模块启用调试日志记录:

export RUST_LOG=actix_web=info,martin=debug,tokio_postgres=debug
martin postgres://postgres@localhost/db

配置文件

如果您不想公开所有表和函数,可以在配置文件中列出您的源。要使用配置文件启动 Martin,需要通过 --config 参数传递文件路径。配置文件可能包含环境变量,这些变量将在解析前展开。例如,要在配置文件中使用 MY_DATABASE_URL:connection_string: ${MY_DATABASE_URL},或使用默认值 connection_string: ${MY_DATABASE_URL:-postgres://postgres@localhost/db}

martin --config config.yaml

您可能希望使用 --save-config 参数自动生成配置文件。这将生成一个包含所有配置的 yaml 文件,您可以编辑它以删除任何不想公开的源。

martin  ... ... ...  --save-config config.yaml

配置示例

# Connection keep alive timeout [default: 75]
keep_alive: 75

# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'

# Set TileJSON URL path prefix.
# This overrides the default of respecting the X-Rewrite-URL header.
# Only modifies the JSON (TileJSON) returned; Martin's API-URLs remain unchanged.
# If you need to rewrite URLs, please use a reverse proxy.
# Must begin with a `/`.
# Examples: `/`, `/tiles`
base_path: /tiles

# Number of web server workers
worker_processes: 8

# Amount of memory (in MB) to use for caching [default: 512, 0 to disable]
#
# This is the total amount of cache we use.
# By default, this is split up between:
# - Tiles 50% -> 256 MB
# - Pmtiles' directories 25% -> 128 MB
# - Fonts 12.5% -> 64 MB
# - Sprites 12.5% -> 64 MB
#
# How the cache works internally is unstable and may change to improve performance/efficiency.
# For example, we may change the split between sources to improve efficiency.
#
# Specify each cache size individually for finer cache size control:
# - Tiles: `tile_cache_size_mb`
# - Pmtiles: `pmtiles.directory_cache_size_mb`
# - Fonts: `fonts.cache_size_mb`
# - Sprites: `sprites.cache_size_mb`
cache_size_mb: 512

# Allows overriding the size of the tile cache.
# Defaults to `cache_size_mb` / 2
tile_cache_size_mb: 256

# Which compression should be used if the
# - client accepts multiple compression formats, and
# - tile source is not pre-compressed.
#
# `gzip` is faster, but `brotli` is smaller, and may be faster with caching.
# Default could be different depending on Martin version.
preferred_encoding: gzip

# Enable or disable Martin web UI. [default: disable]
#
# At the moment, only allows `enable-for-all`, which enables the web UI for all connections.
# This may be undesirable in a production environment
web_ui: disable

# Advanced monitoring options
observability:
  # Configure metrics reported under `/_/metrics`
  metrics:
    # Add these labels to every metric
    # Example: `{ env: prod, server: martin }`
    add_labels: {}

# CORS Configuration
#
# Defaults to `cors: true`, which allows all origins.
# Sending/Acting on CORS headers can be completely disabled via `cors: false`
cors:
  # Sets the `Access-Control-Allow-Origin` header [default: *]
  # '*' will use the requests `ORIGIN` header
  origin:
    - https://example.org
  # Sets `Access-Control-Max-Age` Header. [default: null]
  # null means not setting the header for preflight requests
  max_age: 3600

# Database configuration. This can also be a list of PG configs.
postgres:
  # Database connection string.
  #
  # You can use environment variables too, for example:
  # connection_string: $DATABASE_URL
  # connection_string: ${DATABASE_URL:-postgres://postgres@localhost/db}
  connection_string: 'postgres://postgres@localhost:5432/db'

  # Same as PGSSLCERT for psql
  ssl_cert: './postgresql.crt'
  # Same as PGSSLKEY for psql
  ssl_key: './postgresql.key'
  # Same as PGSSLROOTCERT for psql
  ssl_root_cert: './root.crt'

  # If a spatial table has SRID 0, then this SRID will be used as a fallback
  default_srid: 4326

  # Maximum Postgres connections pool size [default: 20]
  pool_size: 20

  # Limit the number of geo features per tile.
  #
  # If the source table has more features than set here, they will not be
  # included in the tile and the result will look "cut off"/incomplete.
  # This feature allows you to put a maximum latency bound on tiles with an
  # extreme amount of detail at the cost of not returning all data.
  # It is sensible to set this limit if you have user generated/untrusted
  # geodata, e.g. a lot of data points at [Null Island]
  # (https://en.wikipedia.org/wiki/Null_Island).
  max_feature_count: null # either a positive integer, or null=unlimited (default)

  # Specify how bounds should be computed for the spatial PG tables [default: quick]
  #
  # Options:
  # - `calc` compute table geometry bounds on startup.
  # - `quick` same as 'calc', but the calculation will be aborted after 5 seconds.
  # - `skip` does not compute table geometry bounds on startup.
  auto_bounds: quick

  # Enable automatic discovery of tables and functions.
  # You may set this to `false` to disable.
  auto_publish:
    # Optionally limit to just these schemas
    from_schemas:
      - public
      - my_schema
    # Here we enable both tables and functions auto discovery.
    # You can also enable just one of them by not mentioning the other, or
    # setting it to false. Setting one to true disables the other one as well.
    # E.g. `tables: false` enables just the functions auto-discovery.
    tables:
      # Optionally set how source ID should be generated based on the table's name,
      # schema, and geometry column
      source_id_format: 'table.{schema}.{table}.{column}'
      # Add more schemas to the ones listed above
      from_schemas: my_other_schema
      # A table column to use as the feature ID
      # If a table has no column with this name, `id_column` will not be set for
      # that table.
      # If a list of strings is given, the first found column will be treated as a
      # feature ID.
      id_columns: feature_id
      # Controls if geometries should be clipped or encoded as is [default: true]
      clip_geom: true
      # Buffer distance in tile coordinate space to optionally clip geometries,
      # optional, default to 64
      buffer: 64
      # Tile extent in tile coordinate space, optional, default to 4096
      extent: 4096
    functions:
      # Optionally limit to just these schemas
      from_schemas:
        - public
        - my_schema
      # Optionally set how source ID should be generated based on the function's
      # name and schema
      source_id_format: '{schema}.{function}'

  # Associative arrays of table sources
  tables:
    table_source_id:
      # ID of the MVT layer (optional, defaults to table name)
      layer_id: table_source

      # Table schema (required)
      schema: public

      # Table name (required)
      table: table_source

      # Geometry SRID (required)
      srid: 4326

      # Geometry column name (required)
      geometry_column: geom

      # Feature id column name
      id_column: ~

      # An integer specifying the minimum zoom level
      minzoom: 0

      # An integer specifying the maximum zoom level. MUST be >= minzoom
      maxzoom: 30

      # The maximum extent of available map tiles. Bounds MUST define an area
      # covered by all zoom levels. The bounds are represented in WGS:84 latitude
      # and longitude values, in the order left, bottom, right, top. Values may
      # be integers or floating point numbers.
      bounds: [ -180.0, -90.0, 180.0, 90.0 ]

      # Tile extent in tile coordinate space
      extent: 4096

      # Buffer distance in tile coordinate space to optionally clip geometries
      buffer: 64

      # Boolean to control if geometries should be clipped or encoded as is
      clip_geom: true

      # Geometry type
      geometry_type: GEOMETRY

      # List of columns, that should be encoded as tile properties (required)
      #
      # Keys and values are the names and descriptions of attributes available in this layer.
      # Each value (description) must be a string that describes the underlying data.
      # If no fields (=just the geometry) should be encoded, an empty object is allowed.
      properties:
        gid: int4

  # Associative arrays of function sources
  functions:
    function_source_id:
      # Schema name (required)
      schema: public

      # Function name (required)
      function: function_zxy_query

      # An integer specifying the minimum zoom level
      minzoom: 0

      # An integer specifying the maximum zoom level. MUST be >= minzoom
      maxzoom: 30

      # The maximum extent of available map tiles. Bounds MUST define an area
      # covered by all zoom levels. The bounds are represented in WGS:84
      # latitude and longitude values, in the order left, bottom, right, top.
      # Values may be integers or floating point numbers.
      bounds: [ -180.0, -90.0, 180.0, 90.0 ]

# Publish PMTiles files from local disk or proxy to a web server
pmtiles:
  # Size of the directory cache (in MB).
  # Defaults to cache_size_mb / 4
  #
  # Note:
  # Tile and directory caching are complementary.
  # For good performance, you want
  # - directory caching (to not resolve the directory on each request) and
  # - tile caching (for high access tiles)
  directory_cache_size_mb: 128

  # You can pass options for pmtiles files located on remote storages here.
  #
  # The avaliable options are documented here:
  # - local file sources don't have options
  # - Http(s) Source: https://docs.rs/object_store/latest/object_store/http/struct.HttpBuilder.html
  # - Amazon S3: https://docs.rs/object_store/latest/object_store/aws/struct.AmazonS3Builder.html
  # - Google Cloud Storage: https://docs.rs/object_store/latest/object_store/gcp/struct.GoogleCloudStorageBuilder.html
  # - Microsoft Azure: https://docs.rs/object_store/latest/object_store/azure/struct.MicrosoftAzureBuilder.html
  #
  # Example for configuring a source to allow http
  allow_http: true

  paths:
    # scan this whole dir, matching all *.pmtiles files
    - /dir-path
    # specific pmtiles file will be published as a pmt source (filename without extension)
    - /path/to/pmt.pmtiles
    # A web server with a PMTiles file that supports range requests
    - https://example.org/path/tiles.pmtiles
  sources:
    # named source matching source name to a single file
    pm-src1: /path/to/pmt.pmtiles
    # A named source to a web server with a PMTiles file that supports range requests
    pm-web2: https://example.org/path/tiles.pmtiles

# Publish MBTiles files
mbtiles:
  paths:
    # scan this whole dir, matching all *.mbtiles files
    - /dir-path
    # specific mbtiles file will be published as mbtiles2 source
    - /path/to/mbtiles.mbtiles
  sources:
    # named source matching source name to a single file
    mb-src1: /path/to/mbtiles1.mbtiles

# Sprite configuration
sprites:
  # Size of the sprite cache (in MB).
  # Defaults to cache_size_mb / 8
  cache_size_mb: 64

  paths:
    # all SVG files in this dir will be published as a "my_images" sprite source
    - /path/to/my_images
  sources:
    # SVG images in this directory will be published as a "my_sprites" sprite source
    my_sprites: /path/to/some_dir

# Font configuration
fonts:
  # Size of the sprite cache (in MB).
  # Defaults to cache_size_mb / 4
  cache_size_mb: 64

  # A list of *.otf, *.ttf, and *.ttc font files and dirs to search recursively.
  paths:
    - /path/to/font/file.ttf
    - /path/to/font_dir

# Publish MapLibre style files
# In the future, the style files will be used for the server-side rendering as well
styles:
   paths:
     # publish all *.json files in this directory
     # The name of the file will be used as the style name
     - /path/to/styles_dir
     # publish a single file - here `maplibre_style` will be the style name
     - /path/to/maplibre_style.json
   sources:
     # publish a JSON file found at this path as `some_style_name`
     #
     # Contrairy to paths, if directories are specified, Martin will print a
     # warning and ignore them.
     # To serve a style-directory, use the `paths` section above or name each
     # style individually. This prevents footguns with names being unclear.
     some_style_name: /path/to/this/style.json
     #  Publish specific file as `other_style_name`
     other_style_name: /path/to/other_style.json

# If set, the version of the tileset (as specified in the MBTiles or PMTiles metadata)
# will be embedded in the TileJSON `tiles` URL, with the set identifier.
# This is useful to give clients a better way to cache-bust a CDN:
# 1. maplibre requests tilejson, tilejson contains the tiles URL. This is always up-to-date.
# 2. maplibre requests each tile it requires, with the tiles URL in the tilejson.
# 3. Add `Control: public, max-age=..., immutable` on the tile responses
#    optimize browser/CDN cache hit rates, while also making sure that
#    old tiles aren't served when a new tileset is deployed.
#
# The CDN must handle query parameters for caching to work correctly.
# Many CDNs ignore them by default.
#
# For example, if
# - the setting here is `version`, and
# - the PMTiles tileset version is `1.0.0`, the
# TileJSON will be:
# { ..., "tiles": [".../{z}/{x}/{y}?version=1.0.0"], ... }
tilejson_url_version_param: null # a string, such as `version` or `v`

瓦片源

Martin 支持多种瓦片源

瓦片归档(MBTiles/PMTiles)和数据库(PG-Table/PG-Function)之间的区别在于

  • 数据库更加灵活,可能(取决于您如何填充它)实时更新。
  • 瓦片归档则可能(取决于数据)更紧凑、内存效率更高,并在瓦片服务方面表现出更好的性能。

=> 对于大多数用例,您可能需要两者的混合。我们通过组合源支持这一点 => 对于某些用例,您希望拥有数据库的灵活性,但又不想承担运行时成本。我们提供 martin-cp 实用程序将所有瓦片渲染到瓦片归档中。这也可用于通过差异和同步 mbtiles 提供离线地图

MBTiles 和 PMTiles 之间的区别在于:

  • MBTiles 要求整个归档位于同一台机器上。PMTiles 可以利用支持远程 HTTP-Range 请求的服务器或本地文件。
  • 性能方面,MBTiles 略快于 PMTiles,但通过缓存可以忽略不计。
  • 磁盘大小方面,MBTiles 略高于(10-15%)PMTiles。
  • PMTiles 在极端情况下需要更少的内存,因为 sqlite 有一个小的内存缓存。

选择取决于您的具体用例和要求。

MBTiles 和 PMTiles 文件源

Martin 可以从 PMTile 和 MBTile 文件提供任何类型的瓦片。要从 CLI 提供文件,只需将文件路径或包含 *.mbtiles 或 *.pmtiles 文件的目录放置即可。PMTiles 文件的路径可以是 URL。例如:

martin  /path/to/mbtiles/file.mbtiles  /path/to/directory   https://example.org/path/tiles.pmtiles

您可能还想使用 --save-config my-config.yaml 生成配置文件,然后编辑它并使用 --config my-config.yaml 选项使用它。

tip

有关可用数据源之间差异的更详细说明,请参阅我们的瓦片源说明。

自动发现

对于 mbtiles 或本地 pmtiles 文件,我们支持启动时自动发现。 这意味着以下命令将发现目录中的所有 mbtiles 和 pmtiles 文件:

martin  /path/to/directory

warning

对于远程 PMTiles,我们目前不支持自动发现。 如果您想实现此功能,请参阅 https://github.com/maplibre/martin/issues/2180

我们目前也不支持在运行时刷新目录。 如果您想实现此功能,请参阅 https://github.com/maplibre/martin/issues/288。

从本地文件系统、http 或对象存储提供 PMTiles

PMTiles 源的可用设置取决于后端:

对于本地源,您需要提供路径或 URL。 例如:

martin  path/to/tiles.pmtiles

可用的方案是:

  • file:///path/to/my/file.pmtiles
  • path/to/my/file.pmtiles

您也可以通过配置文件进行配置:

pmtiles:
  sources:
    tiles: file:///path/to/my/file.pmtiles

PostgreSQL 连接

Martin 支持标准的 PostgreSQL 连接字符串设置,包括 host、port、user、password、dbname、sslmode、connect_timeout、keepalives、keepalives_idle 等。 有关更多详细信息,请参阅 PostgreSQL 文档。

SSL 连接

Martin 支持 PostgreSQL sslmode 设置:disable、prefer、require、verify-ca 和 verify-full。 有关模式说明,请参阅 PostgreSQL 文档。 证书可以在配置文件中提供,也可以通过环境变量提供(与 psql 相同)。 环境变量适用于所有 PostgreSQL 连接。 有关详细信息,请参阅环境变量。

默认情况下,sslmode 是 prefer - 如果服务器支持则加密(不检查证书),但如果不支持则在不使用 SSL 的情况下继续连接。 这与 psql 的默认行为相匹配。

如果您需要关于窃听或 MITM 保护的保证,您需要不同的选项。 使用 sslmode 参数指定不同的模式:

martin postgres://user:password@host/db?sslmode=verify-full

有关 SSL 证书设置的实用演练——包括创建、配置和故障排除——请参阅我们的 PostgreSQL SSL 证书指南。

PostgreSQL 表源

表源是可用于查询矢量瓦片的数据库表。如果提供了 PostgreSQL 连接字符串,Martin 将发布所有至少有一个几何列的表作为数据源。如果几何列的 SRID 为 0,则必须设置默认 SRID,否则该几何列/表将被忽略。所有非几何表列将作为矢量瓦片要素标签(属性)发布。

修改 TileJSON

Martin 将自动为每个表源生成 TileJSON 清单。它将包含 name、description、minzoom、maxzoom、bounds 和 vector_layer 信息。 例如,如果有一个表 public.table_source: 默认的 TileJSON 可能如下所示(注意 URL 将自动调整以匹配请求主机):

表:

CREATE TABLE "public"."table_source" ( "gid" int4 NOT NULL, "geom" "public"."geometry" );

TileJSON:

{
    "tilejson": "3.0.0",
    "tiles": [
        "http://localhost:3000/table_source/{z}/{x}/{y}"
    ],
    "vector_layers": [
        {
            "id": "table_source",
            "fields": {
                "gid": "int4"
            }
        }
    ],
    "bounds": [
        -2.0,
        -1.0,
        142.84131509869133,
        45.0
    ],
    "description": "public.table_source.geom",
    "name": "table_source"
}

默认情况下,description 和 name 是关于此表的数据库标识符,边界从数据库查询。您可以通过调整配置文件中的 auto_publish 部分来微调这些。

SQL 注释中的 TileJSON

除了在配置文件中调整 auto_publish 部分外,您还可以直接在数据库端微调 TileJSON:在表上添加有效的 JSON 作为 SQL 注释。

Martin 将使用 JSON 合并补丁将表注释合并到生成的 TileJSON 中。以下示例更新描述并向 TileJSON 添加 attribution、version、foo(甚至嵌套的 DIY 字段)字段。

DO $do$ BEGIN
    EXECUTE 'COMMENT ON TABLE table_source IS $tj$' || $$
    {
        "version": "1.2.3",
        "attribution": "osm",
        "description": "a description from table comment",
        "foo": {"bar": "foo"}
    }
    $$::json || '$tj$';
END $do$;

PostgreSQL 函数源

函数源是一个数据库函数,可用于查询矢量瓦片。启动时,Martin 将查找具有合适签名的函数。

如果函数返回 bytea 值,或返回包含 bytea 和 text 值的记录,则该函数可用作函数源。text 值应该是用户定义的哈希值,例如 MD5 值,最终将用作 ETag。

有效的函数还必须具有以下参数:

参数类型描述
z(或 zoom)integer瓦片缩放参数
xinteger瓦片 x 参数
yinteger瓦片 y 参数
query(可选,任意名称)json查询字符串参数

带坐标投影的简单函数

例如,如果您有一个在 WGS84(4326 SRID)中具有任意几何的表 table_source。 如果我们需要表的行字段 field_color 和几何 geom 作为函数源,则可以编写为:

CREATE OR REPLACE
    FUNCTION function_zxy(z integer, x integer, y integer)
    RETURNS bytea AS $$
DECLARE
  mvt bytea;
BEGIN
  SELECT INTO mvt ST_AsMVT(tile, 'function_zxy', 4096, 'geom') FROM (
    SELECT
      ST_AsMVTGeom(
          ST_Transform(ST_CurveToLine(geom), 3857),
          ST_TileEnvelope(z, x, y),
          4096, 64, true) AS geom,
        field_color AS color
    FROM table_source
    WHERE geom && ST_Transform(ST_TileEnvelope(z, x, y), 4326)
  ) as tile WHERE geom IS NOT NULL;

  RETURN mvt;
END
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;

tip

默认情况下,ST_TileEnvelope 生成 3857 SRID,ST_AsMVTGeom 使用 3857 SRID。 因此,许多工具(例如 osm2pgsql)直接将其数据存储在 3857 SRID 中以降低处理开销。 如果您的数据在 3857 SRID 中,您可以删除两个 ST_Transform 调用。

让我们解释函数的几个方面:

ST_Transform(ST_CurveToLine(geom), 3857)

  • 由于示例中的表可以包含任意几何,我们需要转换 CIRCULARSTRING 几何类型。 具体来说,我们使用 ST_CurveToLine 将
    • CIRCULAR STRING 转换为常规 LINESTRING,
    • CURVEPOLYGON 转换为 POLYGON 或
    • MULTISURFACE 转换为 MULTIPOLYGON。
  • ST_Transform 是必需的,因为 ST_CurveToLine 返回 4326 SRID 中的几何,这是示例中存储几何的 SRID。

WHERE geom && ST_Transform(ST_TileEnvelope(z, x, y), 4326)

  • && 是空间交集运算符。因此它检查几何是否与瓦片包络相交并使用空间索引。
  • ST_Transform 用于将瓦片包络从 3857 SRID 转换为 4326 SRID,因为我们示例中的 geom 在 4326 SRID 中。

note

规划模式 IMMUTABLE STRICT PARALLEL SAFE 允许 postgres 进一步自由优化我们的函数。 您的函数可能与示例属于同一类别,但要小心不要导致意外行为。

  • IMMUTABLE 该函数没有副作用。

    表示该函数不能修改数据库,并且在给定相同的参数值时始终返回相同的结果; 也就是说,它不执行数据库查找或以其他方式使用不直接存在于其参数列表中的信息。 如果给出此选项,则具有全常量参数的函数的任何调用都可以立即替换为函数值。

  • STRICT:如果任何参数为 NULL,则不会调用我们的函数。

  • PARALLEL SAFE: 我们的函数可以安全地并行调用,因为它不修改数据库,也不使用随机性或临时表。

    如果函数满足以下条件,则应标记为并行不安全

    • 修改任何数据库状态,
    • 更改事务状态(除了使用子事务进行错误恢复),
    • 访问序列(例如,通过调用 currval)或
    • 对设置进行持久更改。

    如果函数满足以下条件,则应标记为并行受限

    • 访问临时表,
    • 客户端连接状态,
    • 游标,
    • 准备好的语句,或
    • 系统无法在并行模式下同步的各种后端本地状态 (例如,setseed 只能由组长执行,因为其他进程所做的更改 不会反映在领导者中)。

    一般来说,如果函数被标记为安全而实际上是受限或不安全的,或者被标记为受限而实际上是不安全的, 则在并行查询中使用时可能会抛出错误或产生错误答案。 理论上,如果标记错误,C 语言函数可能表现出完全未定义的行为,因为系统无法保护自己免受任意 C 代码的影响, 但在最可能的情况下,结果不会比任何其他函数差。 如有疑问,函数应标记为 UNSAFE,这是默认值。

带查询参数的函数

用户可以添加 query 参数以将附加参数传递给函数。

query_params 参数是瓦片请求查询参数的 JSON 表示。查询参数可以作为简单的查询值传递,例如

curl localhost:3000/function_zxy_query/0/0/0?answer=42

您还可以使用 urlencoded 参数来编码复杂值:

curl \
  --data-urlencode 'arrayParam=[1, 2, 3]' \
  --data-urlencode 'numberParam=42' \
  --data-urlencode 'stringParam=value' \
  --data-urlencode 'booleanParam=true' \
  --data-urlencode 'objectParam={"answer" : 42}' \
  --get localhost:3000/function_zxy_query/0/0/0

然后 query_params 将被解析为:

{
  "arrayParam": [1, 2, 3],
  "numberParam": 42,
  "stringParam": "value",
  "booleanParam": true,
  "objectParam": { "answer": 42 }
}

您可以使用 json 运算符访问这些参数:

...WHERE answer = (query_params->'objectParam'->>'answer')::int;

作为示例,我们在 WGS84(4326 SRID)中的 table_source 有一个 integer 类型的列 answer。 函数 function_zxy_query 将返回一个 MVT 瓦片,其中 answer 列作为属性。

CREATE OR REPLACE
    FUNCTION function_zxy_query(z integer, x integer, y integer, query_params json)
    RETURNS bytea AS $$
DECLARE
  mvt bytea;
BEGIN
  SELECT INTO mvt ST_AsMVT(tile, 'function_zxy_query', 4096, 'geom') FROM (
    SELECT
      ST_AsMVTGeom(
          ST_Transform(ST_CurveToLine(geom), 3857),
          ST_TileEnvelope(z, x, y),
          4096, 64, true) AS geom
    FROM table_source
    WHERE geom && ST_Transform(ST_TileEnvelope(z, x, y), 4326) AND
          answer = (query_params->>'answer')::int
  ) as tile WHERE geom IS NOT NULL;

  RETURN mvt;
END
$$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE;

修改 TileJSON

Martin 将自动为每个函数源生成基本的 TileJSON 清单。 这将包含函数的 name 和 description,以及可选的 minzoom、maxzoom 和 bounds(如果通过配置方法之一指定)。

例如,如果有一个函数 public.function_zxy_query_jsonb,默认的 TileJSON 可能如下所示:

{
  "tilejson": "3.0.0",
  "tiles": [
    "http://localhost:3111/function_zxy_query_jsonb/{z}/{x}/{y}"
  ],
  "name": "function_zxy_query_jsonb",
  "description": "public.function_zxy_query_jsonb"
}

note

URL 将自动调整以匹配请求主机

SQL 注释中的 TileJSON

要修改自动生成的 TileJSON,您可以在函数上添加有效的 JSON 作为 SQL 注释。 Martin 将使用 JSON Merge patch 将函数注释合并到生成的 TileJSON 中。 以下示例将 attribution 和 version 字段添加到 TileJSON。

note

此示例使用 EXECUTE 确保注释是有效的 JSON (否则 PostgreSQL 将抛出错误)。 您可以使用其他创建 SQL 注释的方法。

DO $do$ BEGIN
    EXECUTE 'COMMENT ON FUNCTION my_function_name IS $tj$' || $$
    {
        "description": "my new description",
        "attribution": "my attribution",
        "vector_layers": [
            {
                "id": "my_layer_id",
                "fields": {
                    "field1": "String",
                    "field2": "Number"
                }
            }
        ]
    }
    $$::json || '$tj$';
END $do$;

云优化 GeoTIFF 文件源

warning

此功能目前不稳定,因此未包含在默认构建中。 其行为可能会在补丁版本中发生变化。

要尝试它,安装 Rust,并运行此命令下载、编译和安装带有不稳定功能的 martin:

cargo install martin --features=unstable-cog

由于当前实现的限制,它不稳定:

我们欢迎贡献以帮助稳定此功能!

Martin 支持提供本地 COG(云优化 GeoTIFF) 文件等栅格源。

note

对于 S3 等远程存储上的 cog 和其他改进,您可以在 issue 875 上跟踪它们。 我们欢迎任何帮助。

支持的颜色类型和每样本位数

颜色类型每样本位数支持状态
rgb/rgba8✅
rgb/rgba16/32…🛠️开发中
gray8/16/32…🛠️开发中

支持的压缩

  • None
  • LZW
  • Deflate
  • PackBits

使用 CLI 运行 Martin 提供 cog 文件

# 配置包含 `*.tif` 或 `*.tiff` TIFF 文件的目录。
martin /with/tiff/dir1 /with/tiff/dir2
# 配置专用 TIFF 文件
martin /path/to/target1.tif /path/to/target2.tiff
# 配置目录和专用 TIFF 文件的组合。
martin /with/tiff/files /path/to/target1.tif /path/to/target2.tiff

使用配置文件运行 Martin

要在 martin 中添加 COG,只需添加

# 云优化 GeoTIFF 文件源
cog:
  paths:
    # 扫描整个目录,匹配所有 *.tif 和 *.tiff 文件
    - /dir-path
    # 特定 TIFF 文件将作为 cog 源发布
    - /path/to/cog_file1.tif
    - /path/to/cog_file2.tiff
  sources:
    # 将源名称匹配到单个文件的命名源
     cog-src1: /path/to/cog1.tif
     cog-src2: /path/to/cog2.tif

关于 COG

COG 只是云优化 GeoTIFF 文件。

TIFF 是一种图像文件格式。TIFF 标签类似于内部的键值对,用于描述有关 TIFF 文件的元数据,如 ImageWidth、ImageLength 等。

GeoTIFF 是一个有效的 TIFF 文件,带有一组 TIFF 标签来描述与之关联的“制图“信息。

COG 是一个有效的 GeoTIFF 文件,具有一些高效读取的要求。也就是说,所有 COG 文件都是有效的 GeoTIFF 文件,但并非所有 GeoTIFF 文件都是有效的 COG 文件。为了快速访问 TIFF 文件中的瓦片,Martin 依赖于要求/建议(如关于缩小分辨率子文件的要求和内容划分策略),因此我们在文档和配置文件中使用术语 COG 而不是 GeoTIFF。

您可能想访问这些规范:

使用 GDAL 生成 COG

您可以使用 gdal_translate 或 gdalwarp 生成 cog。有关更多详细信息,请参阅 gdal 文档。

# gdal-bin 安装
# sudo apt update
# sudo apt install gdal-bin

# gdalwarp
gdalwarp src1.tif src2.tif out.tif -of COG

# 或 gdal_translate
gdal_translate input.tif output_cog.tif -of COG

ZXY 到 tiff 块的映射

  • 单个 TIFF 文件可以包含有关相同空间区域的许多子文件,每个子文件具有不同的分辨率
  • 子文件由许多瓦片组织

因此,基本上存在从 zxy 到 TIFF 子文件的瓦片的映射。

zxy映射到
缩放级别TIFF 文件中的哪个子文件
X 和 Y子文件中的哪个瓦片

客户端只需读取 COG 的头部即可找出从 zxy 到块编号和子文件编号的映射。Martin 通过此映射向前端获取瓦片。

组合源

组合源允许将多个源组合为一个。组合源由多个用逗号分隔的源组成:{source1},...,{sourceN}

组合源中的每个源都可以使用其 {source_name} 作为 source-layer 属性进行访问。

组合源 TileJSON 端点可在 /{source1},...,{sourceN} 获取,瓦片可在 /{source1},...,{sourceN}/{z}/{x}/{y} 获取。

例如,组合 points 和 lines 源的组合源将在 /points,lines/{z}/{x}/{y} 可用

# TileJSON
curl localhost:3000/points,lines

# 整个世界作为单个瓦片
curl localhost:3000/points,lines/0/0/0

支持资源

仅有数据不足以显示交互式地图。 地图需要数据,但也需要字体、精灵图(有时称为字形)以及(取决于渲染引擎)样式。

精灵图源

给定一个包含 SVG 图像的目录,Martin 将生成一个精灵图——一个 JSON 索引和一个 PNG 图像,用于低分辨率和高分辨率显示。 不带扩展名的 SVG 文件名将用作精灵图的图像 ID(请记住,一个精灵图,即 sprite_id 包含多个图像)。 在给定目录中递归搜索图像,因此子目录名称将用作图像 ID 的前缀。 例如,icons/bicycle.svg 将作为 icons/bicycle 精灵图图像可用。

精灵图表生成会被缓存,此缓存的大小可以通过配置文件中的 sprites.cache_size_mb 进行配置。

API

Martin 使用 MapLibre 精灵图 API 规范通过多个端点提供精灵图。精灵图图像和索引是即时生成的,因此如果更新了精灵图目录,更改将立即反映。

您可以使用 /catalog api 查看所有 <sprite_id> 及其包含的精灵图。

精灵图 PNG

sprite

GET /sprite/<sprite_id>.png 端点包含一个组合所有源图像的单个 PNG 精灵图图像。 此外,在 GET /sprite/<sprite_id>@2x.png 有一个高 DPI 版本可用。

精灵图索引

/sprite/<sprite_id>.json 元数据索引描述精灵图内每个图像的位置和大小。就像 PNG 一样,在 /sprite/<sprite_id>@2x.json 有一个高 DPI 版本可用。

{
  "bicycle": {
    "height": 15,
    "pixelRatio": 1,
    "width": 15,
    "x": 20,
    "y": 16
  },
  ...
}
通过有符号距离场 (SDF) 在运行时着色

如果您想在运行时设置精灵图的颜色,您需要使用有符号距离场 (SDF) 端点。 例如,如果使用 SDF,maplibre 确实支持通过 icon-color 和 icon-halo-color 属性修改图像。

SDF 有一个显著的缺点,即只允许一种颜色。 如果您想要多种颜色,则需要将图标分层。

以下 API 可用:

  • /sdf_sprite/<sprite_id>.json 用于获取 SDF 精灵图索引
  • /sdf_sprite/<sprite_id>.png 用于获取 SDF 精灵图 PNG

组合多个精灵图

可以使用与瓦片连接相同的模式将多个 sprite_id 值组合到一个精灵图中:/sprite/<sprite_id1>,<sprite_id2>,...,<sprite_idN>。不进行 ID 重命名,因此相同的精灵图名称将相互覆盖。

从 CLI 配置

可以使用 --sprite 标志从 CLI 配置精灵图目录。该标志可以多次使用以配置多个精灵图目录。精灵图的 sprite_id 将是目录的名称——在下面的示例中,精灵图将在 /sprite/sprite_a 和 /sprite/sprite_b 可用。使用 --save-config 将配置保存到配置文件。

martin --sprite /path/to/sprite_a --sprite /path/to/other/sprite_b

使用配置文件配置

可以使用 sprite 键从配置文件配置精灵图目录,类似于 MBTiles 和 PMTiles 的配置方式。

# 精灵图配置
sprites:
  paths:
    # 此目录中的所有 SVG 文件将在 sprite_id "my_images" 下发布
    - /path/to/my_images
  sources:
    # 此目录中的 SVG 图像将在 sprite_id "my_sprites" 下发布
    my_sprites: /path/to/some_dir

精灵图现在在 /sprite/my_images,some_dir.png/ … 可用

样式源

Martin 将根据 MapLibre 渲染库的需要提供您的样式。

要编辑这些样式,我们建议使用 https://maputnik.github.io/editor/。

API

Martin 可以提供 MapLibre 样式规范。 目前,Martin 将使用任何有效的 JSON 文件作为样式, 但将来,我们可能会优化 Martin,这可能会导致额外的限制。

使用 /catalog API 查看所有 <style_id>。

地图样式

使用 /style/<style_id> API 获取 <style_id> 的 JSON 内容。

样式的更改或删除会立即反映,但添加不会。 需要重启 Martin 才能看到新样式。

服务器端栅格瓦片渲染

warning

此功能目前不稳定,因此未包含在默认构建中。 其行为可能会在补丁版本中发生变化。

要尝试它,安装 Rust 并运行 just install-dependencies。 安装完这些后,运行以下命令以安装带有不稳定功能的 martin:

cargo build --features=unstable-rendering

由于当前实现的限制,它不稳定:

我们欢迎贡献以帮助稳定此功能!

我们支持为给定样式的 XYZ 瓦片生成栅格化图像。

为此,您需要在配置文件中启用该功能:

styles:
    rendering: true

完成后,您可以使用 /style/<style_id>/{z}/{x}/{y}.{filetype} API 获取 <style_id> 的渲染 png/jpeg 内容。

静态图像准备

我们目前没有与 Tileserver-GL 相同的能力来布局图像。 我们正在努力添加此功能,并非常欢迎贡献。

字体源

Martin 可以根据 MapLibre 文本渲染的需要从 otf、ttf 和 ttc 字体提供字形范围。Martin 会动态即时生成它们。 字形范围生成会被缓存,此缓存的大小可以通过配置文件中的 fonts.cache_size_mb 进行配置。

API

字体范围可用于单个字体或多个字体的组合。字体名称区分大小写,应与目录中发布的字体文件中的字体名称匹配。确保对字体名称进行 URL 转义,因为它们通常包含空格。

字体请求
模式/font/{name}/{start}-{end}
示例/font/Overpass%20Mono%20Bold/0-255

组合字体请求

组合多个字体时,如果第一个列出的字体中有可用的字形,则字形范围将包含该字形,如果第一个字体中没有该字形,则回退到下一个字体,依此类推。如果所有字体都不包含该字形,则字形范围将为空。

带回退的组合字体请求
模式/font/{name1},…,{nameN}/{start}-{end}
示例/font/Overpass%20Mono%20Bold,Overpass%20Mono%20Light/0-255

目录

Martin 将在 /catalog 端点显示所有可用字体。

curl http://127.0.0.1:3000/catalog
{
  "fonts": {
    "Overpass Mono Bold": {
      "family": "Overpass Mono",
      "style": "Bold",
      "glyphs": 931,
      "start": 0,
      "end": 64258
    },
    "Overpass Mono Light": {
      "family": "Overpass Mono",
      "style": "Light",
      "glyphs": 931,
      "start": 0,
      "end": 64258
    },
    "Overpass Mono SemiBold": {
      "family": "Overpass Mono",
      "style": "SemiBold",
      "glyphs": 931,
      "start": 0,
      "end": 64258
    }
  }
}

从 CLI 使用

可以使用一个或多个 --font 参数从 CLI 配置字体文件或目录。

martin --font /path/to/font/file.ttf --font /path/to/font_dir

从配置文件配置

可以使用 fonts 键从配置文件配置字体目录。

# 字体配置
fonts:
  # *.otf、*.ttf 和 *.ttc 字体文件和目录的列表,递归搜索。
  - /path/to/font/file.ttf
  - /path/to/font_dir

Martin 端点

Martin 数据可通过 HTTP GET 端点获得:

URL描述
/Web UI
/catalog所有源列表
/{sourceID}源 TileJSON
/{sourceID}/{z}/{x}/{y}地图瓦片
/{source1},…,{sourceN}组合源 TileJSON
/{source1},…,{sourceN}/{z}/{x}/{y}组合源瓦片
/sprite/{spriteID}[@2x].{json,png}精灵图源
/sdf_sprite/{spriteID}[@2x].{json,png}SDF 精灵图源
/font/{font}/{start}-{end}字体源
/font/{font1},…,{fontN}/{start}-{end}组合字体源
/style/{style}样式源
/healthMartin 服务器健康检查:返回 200 OK
/_/metricsMartin 服务器 Prometheus 指标

重复源 ID

如果有多个同名源,例如 PG 函数在两个模式/连接中可用,或表有多个几何列,源将被分配唯一 ID,如 /points、/points.1 等。

保留源 ID

某些源 ID 保留供内部使用。如果您尝试使用它们,它们将自动重命名为唯一 ID,与处理重复源 ID 的方式相同,例如 catalog 源将变为 catalog.1。

一些保留的 ID:_、catalog、config、font、health、help、index、manifest、metrics、refresh、reload、sprite、status。

目录

所有可用源的列表可通过目录端点获得:

curl localhost:3000/catalog | jq
{
  "tiles" {
    "function_zxy_query": {
      "name": "public.function_zxy_query",
      "content_type": "application/x-protobuf"
    },
    "points1": {
      "name": "public.points1.geom",
      "content_type": "image/webp"
    },
    ...
  },
  "sprites": {
    "cool_icons": {
      "images": [
        "bicycle",
        "bear",
      ]
    },
    ...
  },
  "fonts": {
    "Noto Mono Regular": {
      "family": "Noto Mono",
      "style": "Regular",
      "glyphs": 875,
      "start": 0,
      "end": 65533
    },
    ...
  },
  "styles": {
    "maplibre_demo": {
      "path": "path/to/maplibre_demo.json",
    },
  },
}

源 TileJSON

所有瓦片源在 /{SourceID} 处都有一个 TileJSON 端点。

例如,points 函数或表将在 /points 可用。组合 points 和 lines 源的组合源将在 /points,lines 端点可用。

curl localhost:3000/points | jq
curl localhost:3000/points,lines | jq

指南

本文档站点的此部分专门提供一些示例和教程。 如果您有想要与社区分享的内容,这些内容不太直观但可能对其他人有益,这里就是最佳位置!

添加示例或指南的过程非常简单,有关更多信息,请参阅 Martin 仓库中的 docs 文件夹。

地图渲染器特定

Martin 主要是一个通用的瓦片服务器。 因此它可以与不是 maplibre 的渲染器一起使用。

warning

样式服务是 maplibre 特有的。

与 MapLibre 一起使用

MapLibre 是一个用于在网站上显示地图的开源 JavaScript 库。MapLibre 可以接受 Martin 生成的 MVT 矢量瓦片,并对其应用样式以使用 Web GL 绘制地图。

您可以向地图添加图层并将 Martin TileJSON 端点指定为矢量源 URL。您还应该指定 source-layer 属性。对于表源,默认情况下为 {table_name}。

map.addLayer({
    id: 'points',
    type: 'circle',
    source: {
        type: 'vector',
        url: 'http://localhost:3000/points'
    },
    'source-layer': 'points',
    paint: {
        'circle-color': 'red'
    },
});
map.addSource('rpc', {
    type: 'vector',
    url: `http://localhost:3000/function_zxy_query`
});
map.addLayer({
    id: 'points',
    type: 'circle',
    source: 'rpc',
    'source-layer': 'function_zxy_query',
    paint: {
        'circle-color': 'blue'
    },
});

您还可以使用组合源将多个源组合为一个源。组合源中的每个源都可以使用其 {source_name} 作为 source-layer 属性进行访问。

map.addSource('points', {
    type: 'vector',
    url: `http://0.0.0.0:3000/points1,points2`
});

map.addLayer({
    id: 'red_points',
    type: 'circle',
    source: 'points',
    'source-layer': 'points1',
    paint: {
        'circle-color': 'red'
    }
});

map.addLayer({
    id: 'blue_points',
    type: 'circle',
    source: 'points',
    'source-layer': 'points2',
    paint: {
        'circle-color': 'blue'
    }
});

与 Leaflet 一起使用

Leaflet 是适用于移动友好交互式地图的领先开源 JavaScript 库。

您可以使用 Leaflet.VectorGrid 插件添加矢量瓦片。您必须使用 URL 模板初始化 VectorGrid.Protobuf,就像在 L.TileLayers 中一样。区别在于您应该为所有要素定义样式。

L.vectorGrid
  .protobuf('http://localhost:3000/points/{z}/{x}/{y}', {
    vectorTileLayerStyles: {
      'points': {
        color: 'red',
        fill: true
      }
    }
  })
  .addTo(map);

与 deck.gl 一起使用

deck.gl 是一个用于大型数据集视觉探索性数据分析的 WebGL 驱动框架。

您可以使用 MVTLayer 添加矢量瓦片。MVTLayer data 属性定义 MVT 图层的远程数据。它可以是

  • String:URL 模板或 TileJSON URL。
  • Array:URL 模板数组。它允许跨不同瓦片端点平衡请求。例如,如果您定义一个包含 4 个 url 的数组并且需要加载 16 个瓦片,则每个端点负责提供 16/4 个瓦片。
  • JSON:有效的 TileJSON 对象。
const pointsLayer = new MVTLayer({
  data: 'http://localhost:3000/points',
  pointRadiusUnits: 'pixels',
  getRadius: 5,
  getFillColor: [230, 0, 0]
});

const deckgl = new DeckGL({
  container: 'map',
  mapStyle: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
  initialViewState: {
    latitude: 0,
    longitude: 0,
    zoom: 1
  },
  layers: [pointsLayer]
});

与 Mapbox 一起使用

Mapbox GL JS 是一个用于在 Web 上绘制交互式、可自定义矢量地图的 JavaScript 库。 Mapbox GL JS v1.x 是开源的,后来被分叉为 MapLibre,因此与 Mapbox 一起使用 Martin 类似于 MapLibre。 Mapbox GL JS 可以接受 Martin 生成的 MVT 矢量瓦片,并对其应用样式以使用 Web GL 绘制地图。

您可以向地图添加图层并将 Martin TileJSON 端点指定为矢量源 URL。 您还应该指定 source-layer 属性。 对于表源,默认情况下为 {table_name}。

map.addLayer({
    id: 'points',
    type: 'circle',
    source: {
        type: 'vector',
        url: 'http://localhost:3000/points'
    },
    'source-layer': 'points',
    paint: {
        'circle-color': 'red'
    }
});

与 OpenLayers 一起使用

OpenLayers 是一个用于在 Web 上创建交互式地图的开源库。与 MapLibre GL JS 类似,它也可以显示 Martin 瓦片服务器提供的图像和矢量地图瓦片。

您可以使用 VectorTileLayer 将 martin 和 OpenLayers 的瓦片服务集成。以下是将 MixPoints 矢量瓦片源添加到 OpenLayers 地图的示例。

const layer = new VectorTileLayer({
    source: new VectorTileSource({
        format: new MVT(),
        url: 'http://0.0.0.0:3000/MixPoints/{z}/{x}/{y}',
        maxZoom: 14,
    }),
});
map.addLayer(layer);

瓦片源特定

某些瓦片源比其他源更难设置。 下面,我们为一些常见数据源提供设置说明:

托管的 PostgreSQL

某些 PostgreSQL 数据库提供商需要额外配置才能与 Martin 配合使用。 我们尝试为每个提供商提供指南,但可能无法涵盖所有提供商。

与 DigitalOcean PostgreSQL 一起使用

您可以将 Martin 与 DigitalOcean 的托管 PostgreSQL 一起使用,并启用 PostGIS 扩展

首先,您需要从仪表板下载 CA 证书并获取集群连接字符串。 之后,您可以使用连接字符串和 CA 证书连接到数据库

martin --ca-root-file ./ca-certificate.crt \
       postgres://user:password@host:port/db?sslmode=require

与 Heroku PostgreSQL 一起使用

您可以将 Martin 与 Heroku 的托管 PostgreSQL 一起使用,并启用 PostGIS 扩展

heroku pg:psql -a APP_NAME -c 'create extension postgis'

使用与 Heroku 为 psql 建议的相同环境变量。

export DATABASE_URL=$(heroku config:get DATABASE_URL -a APP_NAME)
export PGSSLCERT=DIRECTORY/PREFIXpostgresql.crt
export PGSSLKEY=DIRECTORY/PREFIXpostgresql.key
export PGSSLROOTCERT=DIRECTORY/PREFIXroot.crt

martin

您也可以使用显式 sslmode 验证 SSL 证书,例如

export DATABASE_URL="$(heroku config:get DATABASE_URL -a APP_NAME)?sslmode=verify-ca"

设置底图并从 PostGIS 叠加点

您通常有一些半专有数据源,您想将其叠加到另一个数据源上。 本指南展示如何使用 Planetiler 从 OSM 生成底图,并从 PostGIS 数据库叠加自定义点。 有关此/替代数据源的优缺点讨论,请参见此处。

先决条件

我们期望您已经安装以下内容:

使用 Planetiler 生成 MBTiles 底图

有多种方法可以生成瓦片存档。 对于半静态瓦片存档,我们认为使用 Planetiler 通过 OpenMapTiles 构建 MBtiles 存档是一个很好的起点。

🤔 为什么我首先需要一个工具来将 OSM 转换为 mbtiles?(点击展开)

您需要一个工具从 OpenStreetMap 构建矢量瓦片集的原因是 OpenStreetMap 中的数据

  • 不遵循特定架构,
  • 也没有预先分块为 x/y/z 块。
🤔 MBtiles 和 OpenMapTiles 是什么?(点击展开)

好问题。

MBtiles 是存档格式。可以将其视为一个 sqlite 数据库,存储您需要的世界块(x/y/z)的数据。 有关此/替代格式的优缺点讨论,请参阅我们的比较 pmtiles vs. mbtiles。

但存档中的数据看起来如何? 这就是矢量瓦片架构的作用: OpenMapTiles 定义了在提供的数据中包含哪些层以及它们如何聚合。 OpenMapTiles 确实有归属要求。您需要在地图底部有 © OpenMapTiles。

如果您想了解更多,请参阅 Shortbread,这是一个更新但不太成熟的替代方案。

以下命令为摩纳哥下载一个瓦片存档到 data/monaco.mbtiles。

有关不同下载选项,请参阅 Planetiler 文档。

mkdir --parents data
docker run \
  --user=$UID \
  -e JAVA_TOOL_OPTIONS="-Xmx1g" \
  -v "$(pwd)/data":/data \
  --rm \
  ghcr.io/onthegomap/planetiler:latest \
  --download \
  --minzoom=0 \
  --maxzoom=14 \
  --tile_compression=none \
  --area=monaco \
  --output /data/monaco.mbtiles

将数据加载到 PostGIS 数据库

运行 PostGIS

首先,您需要一个正在运行的 postGIS 实例。

docker run \
  --name some-postgis \
  --env POSTGRES_PASSWORD=mypass \
  --publish 5432:5432 \
  --detach \
  postgis/postgis

将点导入 PostGIS

然后您需要将几何添加到 PostGIS 数据库。 这可以通过多种方式实现,例如 osm2pgsql 用于添加特定的、可更新的 OSM 数据,或通过 postgres 的各种数据库连接器在您选择的编程语言中实现。

docker exec some-postgis psql --dbname postgres --username postgres --command \
  "CREATE TABLE where_yachts_can_be_looked_at ("\
  "  title TEXT NOT NULL, "\
  "  subtitle TEXT NOT NULL, "\
  "  location GEOMETRY(Point, 4326) NOT NULL);"

docker exec some-postgis psql --dbname postgres --username postgres --command \
  "INSERT INTO where_yachts_can_be_looked_at (title, subtitle, location) VALUES ( "\
  "  'Port Hercules', "\
  "  'Great view of superyachts docked in the iconic harbor.', "\
  "  ST_SetSRID(ST_MakePoint(7.424789, 43.735217), 4326));"

使用 Martin 提供瓦片

现在我们将提供 mbtiles 和 postgis 数据库的内容。

如果您想要更精确的选项来控制发布内容的方式,请参阅配置文件或 cli 文档。 默认情况下,我们将共享每个可服务的 postgres 表、视图和函数。

martin data/monaco.mbtiles postgres://postgres:mypass@localhost:5432/postgres

您现在可以在此处查看可用内容的目录:http://localhost:3000/catalog 两个瓦片集的 tilejson 端点是 http://localhost:3000/monaco,where_yachts_can_be_looked_at

在 Maputnik 中使用以设置地图样式

由于 CORS,我们无法在没有进一步设置的情况下使用网站 https://maplibre.org/maputnik/。 一旦瓦片部署在 https 后面,就可以使用它。

要获取 maputnik 的本地版本,请运行

docker run -it --rm -p 8888:80 ghcr.io/maplibre/maputnik:main

Maputnik 现在在线,所以让我们将 martin 的瓦片加载到其中。

  1. 访问 http://localhost:8888
  2. 您首先需要一个样式:
    • 点击 Open UI 中 “Open” 按钮的位置
    • 选择您喜欢的样式(我们将选择 Maptiler Basic) 如何选择样式
  3. 您现在有一个使用 Maptilers’(不是 martin)数据的样式。您需要更改其数据源以使用我们刚刚发布的瓦片:
    • 点击 Data Sources 如何更改数据源
    • 并添加上面的 tilejson http://localhost:3000/monaco,where_yachts_can_be_looked_at: 如何添加 tilejson
  4. 现在,让我们放大到摩纳哥: 摩纳哥在地图上的位置
  5. 最后,让我们为我们的游艇添加一个圆圈层:
    • 点击 Add Layer 添加圆圈层

    • 按如下方式配置层: 添加圆圈层

    • 并将其样式设置为: 添加圆圈层

      json 配置(点击展开)
      {
        "id": "where_yachts_can_be_looked_at",
        "type": "circle",
        "source": "openmaptiles",
        "source-layer": "where_yachts_can_be_looked_at",
        "paint": {
          "circle-color": "rgba(255, 7, 103, 1)",
          "circle-blur": 0.2,
          "circle-radius": {
            "stops": [
              [13, 1],
              [15, 300],
              [20, 2000]
            ]
          },
          "circle-opacity": {
            "stops": [[13.5, 0], [14, 0.4]]
          }
        },
        "minzoom": 13
      }
      

PostgreSQL SSL 证书

Martin 支持 PostgreSQL 连接的 SSL 证书身份验证。本指南涵盖证书生成、PostgreSQL 配置和 Martin 设置。

何时使用 SSL 证书

在以下情况使用 SSL 证书:

  • martin 和 Postgis 在不同机器上部署
  • 合规要求(PCI DSS、HIPAA 等)
  • 云 PostgreSQL 部署
  • 需要基于证书身份验证的高安全性环境

SSL 模式

sslmode窃听
保护
中间人
攻击保护
说明
disable⛔⛔我不关心安全性,也不想承担加密的开销。
allow🤷⛔我不关心安全性,但如果服务器坚持,我愿意承担加密的开销。
prefer🤷⛔我不关心加密,但如果服务器支持,我愿意承担加密的开销。
require✅⛔我希望我的数据被加密,我接受开销。我相信网络将确保我始终连接到我想要的服务器。
verify-ca✅取决于
CA 策略
我希望我的数据被加密,我接受开销。我想确保我连接到一个我信任的服务器。
verify-full✅✅我希望我的数据被加密,我接受开销。我想确保我连接到一个我信任的服务器,并且它是我指定的那个。

我们的建议:verify-full 或 allow。 在这两者之间的情况并不多。

特别是,默认模式(prefer)没有太大意义。 来自 postgres 文档:

如表所示,从安全角度来看这没有意义,它只是在可能的情况下承诺性能开销。 它仅作为默认值提供以实现向后兼容,不建议在安全部署中使用。

有关不同权衡的更完整解释,请参阅 PostgreSQL SSL 证书文档。

生成证书

对于基本的 SSL 加密,您需要:

  • server-cert.pem - PostgreSQL 服务器证书
  • server-key.pem - PostgreSQL 服务器私钥
  • ca-cert.pem - 证书颁发机构证书
┌─────────────────┐    SSL/TLS     ┌─────────────────┐
│     Martin      │◄─────────────►│   PostgreSQL    │
└─────────────────┘   verify-full  └─────────────────┘
         │                                   │
    ┌─────────┐                        ┌─────────────┐
    │ CA 证书 │                        │ 服务器证书  │
    │         │                        │ 服务器密钥  │
    └─────────┘                        └─────────────┘

自签名证书

要作为 CA 生成证书,您需要一个私钥。 要验证证书,您需要 CA 证书。

# 生成 CA 私钥
openssl genrsa -out ca-key.pem 3072

# 生成 CA 证书
openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=Test CA"

然后您可以生成服务器证书:

# 生成服务器私钥
openssl genrsa -out server-key.pem 3072

# 使用 SAN 扩展生成服务器证书签名请求
openssl req -new -key server-key.pem -out server-csr.pem \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" \
    -addext "subjectAltName = DNS:localhost"

# 使用 SAN 扩展生成由 CA 签名的服务器证书
openssl x509 -req -days 365 -in server-csr.pem -CA ca-cert.pem -CAkey ca-key.pem \
    -CAcreateserial -out server-cert.pem -extensions v3_req \
    -extfile <(printf "[v3_req]\nsubjectAltName = DNS:localhost")

# 设置权限
chmod 400 *-key.pem
chmod 444 *-cert.pem ca-cert.pem

生产证书

对于生产环境,使用来自以下的证书:

  • 常规证书颁发机构(Let’s Encrypt、DigiCert、GlobalSign)
  • 云提供商管理的证书颁发机构
  • 组织内部证书颁发机构

PostgreSQL 配置

services:
  db:
    image: postgis/postgis:17-3.5
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - ./server-cert.pem:/var/lib/postgresql/server.crt:ro
      - ./server-key.pem:/var/lib/postgresql/server.key:ro
    command: -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
docker compose up

tip

Postgres 需要 SSL 证书的特定文件权限和所有权。 在 docker 中这可能有点棘手:

alpine 镜像的默认 user:group 为 70:70 debian 镜像的默认 user:group 为 999:999

您可以通过运行以下命令来更改:

chown 999:999 *.pem
chmod 400 *.pem

使用 psql 测试

通过以下方式测试 SSL 连接

PGSSLROOTCERT=ca-cert.pem psql "postgres://postgres:password@localhost:5432/postgres?sslmode=verify-full"

tip

如果您遇到文件权限错误,请确保当前用户可以访问这些文件。 前面的步骤可能将它们设置为当前用户不可读。

然后,通过以下方式验证 SSL 状态

-- 启用 SSL 信息扩展(ssl_is_used 函数所需)
CREATE EXTENSION IF NOT EXISTS sslinfo;

-- 检查 SSL 状态
SELECT ssl_is_used();

-- SSL 连接详细信息
SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid();

Martin 配置

Martin 可以使用环境变量、CLI 或配置文件进行配置。 您选择哪一个取决于您自己。 您不需要配置两次。

  • 环境变量(点击展开)
    export PGSSLROOTCERT=./ca-cert.pem
    export DATABASE_URL="postgres://postgres:password@localhost:5432/postgres?sslmode=verify-full"
    martin
    
  • 配置文件(点击展开)
    postgres:
      ssl_root_cert: './ca-cert.pem'
      connection_string: 'postgres://postgres:password@localhost:5432/postgres?sslmode=verify-full'
    
  • 命令行(点击展开)
    martin --ca-root-file ./ca-cert.pem \
          "postgres://postgres:password@localhost:5432/postgres?sslmode=verify-full"
    

故障排除

您可以通过以下命令获取更多上下文:

export PGSSLMODE=verify-full
export PGSSLROOTCERT=./ca-cert.pem
# 详细 psql
psql -h localhost -U postgres -d postgres -v

# 调试 Martin
RUST_LOG=debug martin postgres://...

可能发生以下错误:

  • 证书验证失败(点击展开)
    • 检查服务器证书是否由 CA 签名
    • 验证 PGSSLROOTCERT 中的 CA 证书路径
    • 确保证书文件可读
  • 主机名验证失败(点击展开)
    • 服务器证书 CN/SAN 必须与主机名匹配
    • 如果主机名不匹配,请使用 verify-ca 而不是 verify-full
  • 权限被拒绝(点击展开)
    • 检查证书文件权限
    • 私钥应该是 chmod 400 并且运行应用程序的用户可读
  • 连接被拒绝(点击展开)
    • 验证 PostgreSQL 接受 SSL 连接
    • 检查 pg_hba.conf 是否允许来自您 IP 的 SSL

通过 SSL 使用 postgres 的安全最佳实践

  • 使用至少 3072 位 RSA 密钥
  • 使用受限权限(chmod 400)保护私钥
  • 在过期前轮换证书
  • 在生产环境中使用 verify-full
  • 监控证书过期
  • 安全存储 ca-key.pem(仅证书管理需要)
  • 对生产证书使用安全的密钥管理

批量生成瓦片

我们提供 martin-cp 工具,用于从 Martin 支持的任何源批量生成瓦片,并将检索到的瓦片保存到新的或现有的 MBTiles 文件中。

martin-cp 可用于为大型区域或多个区域(边界框)生成瓦片。 如果多个区域重叠,它将确保每个瓦片仅生成一次 martin-cp 支持与 Martin 服务器相同的配置文件和 CLI 参数,因此它可以支持所有源甚至组合源。

复制后,martin-cp 将更新 agg_tiles_hash 元数据值,除非指定了 --skip-agg-tiles-hash。 这允许使用 mbtiles validate 命令验证 MBTiles 文件。

使用

这将使用规范化模式将 PostGIS 表 my_table 中的瓦片复制到 MBTiles 文件 tileset.mbtiles 中,缩放级别从 0 到 10,边界为整个世界。

martin-cp  --output-file tileset.mbtiles \
           --mbtiles-type normalized     \
           "--bbox=-180,-90,180,90"      \
           --min-zoom 0                  \
           --max-zoom 10                 \
           --source source_name          \
           postgres://postgres@localhost:5432/db

tip

除了常规源外,--source <SOURCE> 确实支持组合源。 这意味着 martin-cp 可用于将两个不同的源合并到一个 mbtiles 归档中。

如果关注性能,您还应该考虑

[!TIP] --concurrency <CONCURRENCY> 和 --pool-size <POOL_SIZE> 可分别用于控制并发请求数和 postgres 源的池大小。

最佳设置取决于:

  • 源的性能特征
  • 允许多少负载,例如在多租户环境中
  • 如何压缩存储在输出文件中的瓦片

您还应该考虑

tip

--encoding <ENCODING> 可用于减小 MBTiles 文件的最终大小或减少 martin-cp 的处理量。

默认的 gzip 对于大多数用例应该是一个合理的选择,但如果您更喜欢不同的编码,可以在此处指定。 如果设置为多个值,如 'gzip,br',martin-cp 将使用第一个编码,或者如果瓦片已编码且该编码未列出,则重新编码。 使用 identity 禁用压缩。 对于不可编码的瓦片(如 PNG 和 JPEG)将被忽略。

参数

使用 martin-cp --help 查看可用选项列表:

A tool to bulk copy tiles from any Martin-supported sources into an mbtiles file

Usage: martin-cp [OPTIONS] --output-file <OUTPUT_FILE> [CONNECTION]...

Arguments:
  [CONNECTION]...
          Connection strings, e.g. postgres://... or /path/to/files

Options:
  -s, --source <SOURCE>
          Name of the source to copy from. Not required if there is only one source

  -o, --output-file <OUTPUT_FILE>
          Path to the mbtiles file to copy to

      --mbtiles-type <SCHEMA>
          Output format of the new destination file. Ignored if the file exists. [DEFAULT: normalized]

          [possible values: flat, flat-with-hash, normalized]

      --url-query <URL_QUERY>
          Optional query parameter (in URL query format) for the sources that support it (e.g. Postgres functions)

      --encoding <ENCODING>
          Optional accepted encoding parameter as if the browser sent it in the HTTP request.

          If set to multiple values like gzip,br, martin-cp will use the first encoding, or re-encode if the tile is already encoded and that encoding is not listed. Use identity to disable compression. Ignored for non-encodable tiles like PNG and JPEG.

          [default: gzip]

      --on-duplicate <ON_DUPLICATE>
          Allow copying to existing files, and indicate what to do if a tile with the same Z/X/Y already exists

          [possible values: override, ignore, abort]

      --concurrency <CONCURRENCY>
          Number of concurrent connections to use

          [default: 1]

      --bbox <BBOX>
          Bounds to copy, in the format min_lon,min_lat,max_lon,max_lat. Can be specified multiple times. Overlapping regions will be handled correctly

      --min-zoom <MIN_ZOOM>
          Minimum zoom level to copy

      --max-zoom <MAX_ZOOM>
          Maximum zoom level to copy

  -z, --zoom-levels <ZOOM_LEVELS>
          List of zoom levels to copy

      --skip-agg-tiles-hash
          Skip generating a global hash for mbtiles validation. By default, martin-cp will compute and update agg_tiles_hash metadata value

      --set-meta <KEY=VALUE>
          Set additional metadata values. Must be set as "key=value" pairs. Can be specified multiple times

  -c, --config <CONFIG>
          Path to config file. If set, no tile source-related parameters are allowed

      --save-config <SAVE_CONFIG>
          Save resulting config to a file or use "-" to print to stdout. By default, only print if sources are auto-detected

  -b, --auto-bounds <AUTO_BOUNDS>
          Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]

          Possible values:
          - quick: Compute table geometry bounds, but abort if it takes longer than 5 seconds
          - calc:  Compute table geometry bounds. The startup time may be significant. Make sure all GEO columns have indexes
          - skip:  Skip bounds calculation. The bounds will be set to the whole world

      --ca-root-file <CA_ROOT_FILE>
          Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates

  -d, --default-srid <DEFAULT_SRID>
          If a spatial PG table has SRID 0, then this default SRID will be used as a fallback

  -p, --pool-size <POOL_SIZE>
          Maximum Postgres connections pool size [DEFAULT: 20]

  -m, --max-feature-count <MAX_FEATURE_COUNT>
          Limit the number of geo features per tile.

          If the source table has more features than set here, they will not be included in the tile and the result will look "cut off"/incomplete. This feature allows to put a maximum latency bound on tiles with extreme amount of detail at the cost of
          not returning all data. It is sensible to set this limit if you have user generated/untrusted geodata, e.g. a lot of data points at Null Island.

          Can be either a positive integer or unlimited if omitted.

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Use RUST_LOG environment variable to control logging level, e.g. RUST_LOG=debug or RUST_LOG=martin_cp=debug. See https://docs.rs/env_logger/latest/env_logger/index.html#enabling-logging for more information.

使用 MBTiles 归档

Martin 包含 mbtiles 实用程序,用于从命令行与 *.mbtiles 文件进行交互。 它允许用户检查、复制、验证或比较并应用它们之间的差异。

此工具可以通过使用 cargo install mbtiles --locked 编译最新发布版本来安装,或者从发布页面下载预构建的二进制文件。

使用 mbtiles --help 查看可用命令列表:

A utility to work with .mbtiles file content

Usage: mbtiles <COMMAND>

Commands:
  summary      Show MBTiles file summary statistics
  meta-all     Prints all values in the metadata table in a free-style, unstable YAML format
  meta-get     Gets a single value from the MBTiles metadata table
  meta-set     Sets a single value in the MBTiles metadata table or deletes it if no value
  diff         Compare two files A and B, and generate a new diff file. If the diff file is applied to A, it will produce B
  copy         Copy tiles from one mbtiles file to another
  apply-patch  Apply diff file generated from 'copy' command
  meta-update  Update metadata to match the content of the file
  validate     Validate tile data if hash of tile data exists in file
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

Use RUST_LOG environment variable to control logging level, e.g. RUST_LOG=debug or RUST_LOG=mbtiles=debug. See https://docs.rs/env_logger/latest/env_logger/index.html#enabling-logging for more information.

使用 mbtiles <command> --help 查看特定命令的帮助。 mbtiles validate --help 示例:

Validate tile data if hash of tile data exists in file

Usage: mbtiles validate [OPTIONS] <FILE>

Arguments:
  <FILE>
          MBTiles file to validate

Options:
      --integrity-check <INTEGRITY_CHECK>
          Value to specify the extent of the SQLite integrity check performed

          [default: quick]
          [possible values: quick, full, off]

      --agg-hash <AGG_HASH>
          How should the aggregate tiles hash be checked or updated

          Possible values:
          - verify: Verify that the aggregate tiles hash value in the metadata table matches the computed value. Used by default
          - update: Update the aggregate tiles hash value in the metadata table
          - off:    Do not check the aggregate tiles hash value

  -h, --help
          Print help (see a summary with '-h')

MBTiles 模式

mbtiles 工具在原始 MBTiles 规范的基础上构建,为 tiles 数据指定了三种不同类型的模式:flat、flat-with-hash 和 normalized。mbtiles 工具可以在这些模式之间进行转换,还可以生成任何模式的两个文件之间的差异,以及将多个模式文件合并为一个文件。

flat

Flat 模式最接近原始 MBTiles 规范。它将所有瓦片存储在单个表中。当瓦片集不包含重复瓦片时,此模式最有效。

CREATE TABLE tiles (
    zoom_level INTEGER,
    tile_column INTEGER,
    tile_row INTEGER,
    tile_data BLOB
);

CREATE UNIQUE INDEX tile_index ON tiles (
    zoom_level, tile_column, tile_row
);

flat-with-hash

与 flat 模式类似,但还包括一个 tile_hash 列,其中包含 tile_data 列的哈希值。当瓦片集没有重复瓦片,但您仍希望能够单独验证每个瓦片的内容时,请使用此模式。

CREATE TABLE tiles_with_hash (
    zoom_level INTEGER NOT NULL,
    tile_column INTEGER NOT NULL,
    tile_row INTEGER NOT NULL,
    tile_data BLOB,
    tile_hash TEXT
);

CREATE UNIQUE INDEX tiles_with_hash_index ON tiles_with_hash (
    zoom_level, tile_column, tile_row
);

CREATE VIEW tiles AS
SELECT
    zoom_level,
    tile_column,
    tile_row,
    tile_data
FROM tiles_with_hash;

normalized

Normalized 模式在瓦片集包含重复瓦片时最有效。它将所有瓦片 blob 存储在 images 表中,并将瓦片 Z、X、Y 坐标存储在 map 表中。map 表包含一个 tile_id 列,它是 images 表的外键。tile_id 列是 tile_data 列的哈希值,这使得可以像在 flat-with-hash 模式中一样验证每个单独的瓦片,同时通过仅存储每个唯一瓦片一次来优化存储。

CREATE TABLE map (
    zoom_level INTEGER,
    tile_column INTEGER,
    tile_row INTEGER,
    tile_id TEXT
);

CREATE TABLE images (
    tile_id TEXT,
    tile_data BLOB
);

CREATE UNIQUE INDEX map_index ON map (
    zoom_level, tile_column, tile_row
);
CREATE UNIQUE INDEX images_id ON images (
    tile_id
);

CREATE VIEW tiles AS
SELECT
    map.zoom_level,
    map.tile_column,
    map.tile_row,
    images.tile_data
FROM
    map INNER JOIN images
    ON map.tile_id = images.tile_id;

CREATE VIEW tiles_with_hash AS
SELECT
    map.zoom_level,
    map.tile_column,
    map.tile_row,
    images.tile_data,
    images.tile_id AS tile_hash
FROM
    map INNER JOIN images
    ON map.tile_id = images.tile_id;

可选地,具有 normalized 模式的 .mbtiles 文件可以包含 tiles_with_hash 视图。由 mbtiles 工具创建的所有 normalized 文件都将包含此视图。

FROM
    map INNER JOIN images
    ON map.tile_id = images.tile_id;

CREATE VIEW tiles_with_hash AS
SELECT
    map.zoom_level,
    map.tile_column,
    map.tile_row,
    images.tile_data,
    images.tile_id AS tile_hash
FROM
    map INNER JOIN images
    ON map.tile_id = images.tile_id;

MBTiles 信息和元数据

summary

使用 mbtiles summary 获取 MBTiles 文件内容的摘要。该命令将打印一个表格,显示每个缩放级别的瓦片数量、最小和最大瓦片的大小以及每个缩放级别的瓦片平均大小。该命令还将打印每个缩放级别覆盖区域的边界框。

# 在某个目录中创建一个示例 .mbtiles 文件
sqlite3 target/world_cities.mbtiles < tests/fixtures/mbtiles/world_cities.sql
# 获取 mbtiles 摘要
mbtiles summary target/world_cities.mbtiles

MBTiles file summary for tests/fixtures/mbtiles/world_cities.mbtiles
Schema: flat
File size: 48.00kB
Page size: 4.00kB
Page count: 12

 Zoom |   Count   | Smallest  |  Largest  |  Average  | Bounding Box
    0 |         1 |     1.0kB |     1.0kB |    1.0kB | -180,-85,180,85
    1 |         4 |      160B |      650B |      366B | -180,-85,180,85
    2 |         7 |      137B |      495B |      239B | -180,-67,180,67
    3 |        17 |       67B |      246B |      134B | -135,-41,180,67
    4 |        38 |       64B |      175B |       86B | -135,-41,180,67
    5 |        57 |       64B |      107B |       72B | -124,-41,180,62
    6 |        72 |       64B |       97B |       68B | -124,-41,180,62
  all |       196 |       64B |     1.0kB |       96B | -180,-85,180,85

meta-all

将所有元数据值以及瓦片检测结果打印到 stdout。打印值的格式不稳定,仅应用于目视检查。

mbtiles meta-all my_file.mbtiles

meta-get

通过名称检索原始元数据值。该值将打印到 stdout 而不进行任何修改。例如,要从 mbtiles 文件获取 description 值:

mbtiles meta-get my_file.mbtiles description

meta-set

通过名称设置元数据值,或者如果未提供值则删除键。例如,要将 description 值设置为 A vector tile dataset:

mbtiles meta-set my_file.mbtiles description "A vector tile dataset"

复制、差异和修补 MBTiles

mbtiles copy

复制命令复制一个 mbtiles 文件,可选择按缩放级别过滤其内容。

mbtiles copy src_file.mbtiles dst_file.mbtiles \
        --min-zoom 0 --max-zoom 10

此命令还可用于生成不同支持的模式的文件。

mbtiles copy normalized.mbtiles dst.mbtiles \
         --dst-type flat-with-hash

mbtiles copy --diff-with-file

此选项与使用 mbtiles diff ... 相同。以下两个命令是等效的:

mbtiles diff file1.mbtiles file2.mbtiles diff.mbtiles

mbtiles copy file1.mbtiles diff.mbtiles \
        --diff-with-file file2.mbtiles

mbtiles copy --apply-patch

将源文件复制到目标文件,同时将上述 copy --diff-with-file 命令生成的差异文件应用到目标 mbtiles 文件。这允许更安全地应用差异文件,因为源文件不会被修改。

mbtiles copy src_file.mbtiles dst_file.mbtiles \
        --apply-patch diff.mbtiles

MBTiles 差异

mbtiles diff

复制命令也可用于比较两个 mbtiles 文件并生成增量(diff)文件。差异文件可以在其他地方应用到 src_file.mbtiles,以避免复制/传输整个修改后的数据集。增量文件将包含两个文件之间不同的所有瓦片(修改、插入和删除为 NULL 值),包括瓦片和元数据表。

有一个例外:agg_tiles_hash 元数据值将被重命名为 agg_tiles_hash_after_apply,并为差异文件本身生成新的 agg_tiles_hash。这样做是为了避免在将差异文件应用于原始文件时产生混淆,因为应用差异后 agg_tiles_hash 值将不同。apply-patch 命令将在应用差异时自动将 agg_tiles_hash_after_apply 值重命名为 agg_tiles_hash。

# 此命令将比较 `file1.mbtiles` 和 `file2.mbtiles`,
# 并生成一个新的差异文件 `diff.mbtiles`。
mbtiles diff file1.mbtiles file2.mbtiles diff.mbtiles

# 如果将 diff.mbtiles 应用到 file1.mbtiles,将产生 file2.mbtiles
mbtiles apply-patch file1.mbtiles diff.mbtiles file2a.mbtiles

# file2.mbtiles 和 file2a.mbtiles 现在应该相同
# 验证两个文件并查看它们的哈希值是否相同
mbtiles validate file2.mbtiles
[INFO ] The agg_tiles_hashes=E95C1081447FB25674DCC1EB97F60C26 has been verified for file2.mbtiles

mbtiles validate file2a.mbtiles
[INFO ] The agg_tiles_hashes=E95C1081447FB25674DCC1EB97F60C26 has been verified for file2a.mbtiles

mbtiles apply-patch

将使用上述 mbtiles diff 命令生成的差异文件应用到 MBTiles 文件。差异文件可以应用到先前下载的 src_file.mbtiles,以避免再次复制/传输整个修改后的数据集。src_file.mbtiles 将被就地修改。也可以在将源文件复制到新目标文件时应用差异文件,使用 mbtiles copy --apply-patch 命令。

请注意,应用差异时,agg_tiles_hash_after_apply 元数据值将被重命名为 agg_tiles_hash。 这样做是为了避免在将差异文件应用于原始文件时产生混淆,因为应用差异后 agg_tiles_hash 值将不同。

mbtiles apply-patch src_file.mbtiles diff_file.mbtiles

使用 SQLite 应用差异

应用差异的另一种方法是直接使用 sqlite3 命令行工具。此 SQL 将从 src_file.mbtiles 中删除在 diff_file.mbtiles 中设置为 NULL 的所有瓦片,然后将 diff_file.mbtiles 中的所有新瓦片插入或更新到 src_file.mbtiles 中,其中两个文件都是 flat 类型。差异文件的名称作为查询参数传递给 sqlite3 命令行工具,然后在 SQL 语句中使用。请注意,这不会更新 agg_tiles_hash 元数据值,因此应用差异后它将不正确。

sqlite3 src_file.mbtiles \
  -bail \
  -cmd ".parameter set @diffDbFilename diff_file.mbtiles "\
  "ATTACH DATABASE @diffDbFilename AS diffDb; "\
  "DELETE FROM tiles "\
  "  WHERE (zoom_level, tile_column, tile_row) IN ( "\
  "    SELECT zoom_level, tile_column, tile_row "\
  "    FROM diffDb.tiles "\
  "    WHERE tile_data ISNULL); "\
  "INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) "\
  "  SELECT * FROM diffDb.tiles WHERE tile_data NOTNULL;"

MBTiles 验证

原始 MBTiles 规范不对 MBTiles 中瓦片数据的内容提供任何保证。mbtiles validate 假设了一些额外的约定并使用它们来确保瓦片数据的内容有效,执行多个验证步骤。如果文件无效,该命令将打印错误消息并以非零退出代码退出。

mbtiles validate src_file.mbtiles

SQLite 完整性检查

validate 命令将在文件上运行 PRAGMA integrity_check,如果结果不是 ok 将失败。 --integrity-check 标志可用于禁用此检查,或使用 full 值使其更彻底。默认为 quick。

模式检查

validate 命令将验证 tiles 表/视图是否存在,以及它是否具有预期的列和索引。 它还将验证 metadata 表/视图是否存在,以及它是否具有预期的列和索引。

按瓦片验证

如果 .mbtiles 文件使用 flat_with_hash 或 normalized 模式,validate 命令将验证 tile_data 列的 MD5 哈希是否与 tile_hash 或 tile_id 列匹配(取决于模式)。

由 tilelive-copy 等工具生成的典型规范化模式在 tile_id 列中使用 MD5 哈希。Martin 的 mbtiles 工具可以使用此哈希来验证每个瓦片的内容。 我们还定义了一个新的 flat-with-hash 模式,它将哈希和瓦片数据存储在同一个表中,允许在没有多表布局的情况下进行按瓦片验证。

按瓦片验证不适用于 flat 模式,将被跳过。

聚合内容验证

按瓦片验证将捕获单个瓦片损坏,但不会检测整体数据存储损坏,例如缺少瓦片、不应存在的瓦片或具有不正确 z/x/y 值的瓦片。为此,mbtiles 工具定义了一个名为 agg_tiles_hash 的新元数据值。

该值通过对 tiles 表/视图中所有行的组合值进行哈希计算,按 z、x、y 排序。该值使用以下 SQL 表达式计算,该表达式使用来自 sqlite-hashes crate 的自定义 md5_concat_hex 函数:

md5_concat_hex(
    CAST(zoom_level  AS TEXT),
    CAST(tile_column AS TEXT),
    CAST(tile_row    AS TEXT),
    tile_data)

如果没有行或全部为 NULL,则使用空字符串的哈希值。请注意,SQLite 允许在任何列中存储任何值类型,因此如果 tile_data 意外包含非 blob/text/null 值,验证将失败。

mbtiles 工具将在复制或验证 mbtiles 文件时计算 agg_tiles_hash 值。使用 --agg-hash update 强制更新该值,即使它不正确或不存在。

开发

docker

安装 docker 和 docker-compose

just

安装 Just:

cargo install just --locked
just validate-tools  # 验证设置

其他必需工具

我们提供一个简单的命令来检查所有要求是否已设置

just validate-tools

Git 设置

建议核心贡献者和临时贡献者始终在自己的帐户下创建主仓库的 fork。本地仓库应该有两个远程:upstream 指向主 maplibre/martin 仓库,origin 指向用户自己的 fork。main 分支应跟踪 upstream/main,但所有新工作将推送到 origin,并从那里创建 PR。

此设置的理由(点击展开)

此理由从 Yuri 的一篇文章复制而来

开源贡献既是技术现象,也是社会现象。 任何 FOSS 项目自然都有一个“等级制度“ - 一组 拥有广泛权利的贡献者与其他所有人。其中一些分离 是必要的 - 核心贡献者对代码有更深入的了解,共享愿景, 并相互信任。

核心贡献者拥有其他人没有的一项权利 - 他们可以创建仓库分支。 因此,他们可以“本地“贡献 - 通过将建议的更改推送到主仓库的工作分支, 并在同一仓库内创建“本地“拉取请求。这与其他人不同, 其他人只能从自己的 fork 贡献。

从自己的 fork 创建拉取请求与从主仓库创建拉取请求之间几乎没有区别, 核心贡献者永远不应该从主仓库创建拉取请求有几个原因:

  • 它确保临时贡献者始终运行与核心贡献者相同的 CI。如果贡献过程中断,它将影响所有人,并将更快修复。
  • 它让每个人处于同一水平的竞争环境中,减少“等级制度“效应,让项目对新贡献者感觉更友好
  • 它确保主仓库只有维护的分支(例如 main 和 v1.x), 而不是一堆所有权和工作状态对每个人都不清楚的 PR 分支

在 martin 仓库中,我们遵循这一点,并有一个分支保护规则,防止核心贡献者从主仓库创建拉取请求。

# 将主 fork 克隆到本地机器,将远程命名为"upstream"
# 确保将 URL 替换为正确的 URL
git clone -o upstream https://github.com/maplibre/martin.git
cd martin

# 将您自己的 fork 添加为远程,将其命名为"origin"
git remote add origin https://github.com/nyurik/martin.git

有关 IDE 的进一步设置说明,请在安装以下必要工具后查看参与贡献步骤。

如果您已经在本地克隆了仓库,请使用此指南更新您的设置(点击展开)

如果您已经在本地克隆了仓库,您可以更新它以使用新设置。这假设您有仓库的本地克隆,远程名称为 origin,并且您已经在 GitHub 上 fork 了仓库。

# 快速查看您的远程:git remote -v
git remote -v
# 将现有远程重命名为"upstream"。您的"main"分支现在将跟踪"upstream/main"
git remote rename origin upstream

# 将您自己的 fork 添加为远程,将其命名为"origin"(调整 URL)
git remote add origin https://github.com/nyurik/martin.git

贡献新代码

# 切换到 main 分支(跟踪 upstream/main),并拉取最新更改
git switch main
git fetch upstream

# 为您的工作创建一个新分支
git switch -c my-new-feature

# 编辑文件并提交更改
# '-a' 将添加所有修改的文件
# `-m` 允许您添加简短的提交消息
git commit -a -m "My new feature"

# 将更改推送到您自己的 fork
# '-u' 将使用远程跟踪您的本地分支
git push -u origin my-new-feature

# 点击终端中 `git push` 显示的链接以使用 GitHub Web 界面从您的 fork 创建拉取请求

tip

开发 MBTiles SQL 代码时,只要修改了 SQL 查询,您可能需要使用 just prepare-sqlite。

快速开始

# 安装工具
cargo install just --locked
just validate-tools

# 开始开发
just start  # 测试数据库
just run    # Martin 服务器
just test   # 验证设置

使用 Just

just help        # 常用命令
just --list      # 所有命令
just validate-tools  # 检查设置

开发工作流程

just start       # 启动测试数据库
just run         # 启动 Martin 服务器
just test        # 运行所有测试
just fmt         # 格式化代码
just clippy      # lint 代码
just book        # 构建文档
just stop        # 停止测试数据库

向命令传递参数

just test-cargo -- --test integration_test
just run --config /path/to/config.yaml

参与贡献

一旦您拥有 fork 和所有必需的软件,就可以开始参与了。 本指南涵盖 IDE 设置和调试。 虽然我们以 Visual Studio Code 为例,但 Martin 可以使用任何支持 Rust 的编辑器进行开发。

特定编辑器指南(点击展开)

Visual Studio Code

安装这些必要的扩展:

Vim/Neovim

使用 rustaceanvim

Emacs

使用以下任一:

RustRover

RustRover 开箱即用支持 rust

Zed

Zed 开箱即用支持 rust

快速开发设置

在深入 IDE 配置之前,请确保您的开发环境已准备就绪:

# 验证所有必需的工具已安装
just validate-tools

# 启动开发环境
just start  # 启动测试数据库
just help   # 显示常用命令

使用 launch.json 调试

通常,您需要使用特定参数或配置文件调试 martin 以修复问题或添加功能。

最方便的方法是生成 launch.json 并修改它。

生成

在键盘上按 F1,然后输入 “Generate Launch Configurations from Cargo.toml”。执行它并将其保存到您的 .vscode 目录。

修改

假设您想使用以下命令调试 Martin:

martin postgres://postgres:postgres@localhost:5411/db

您可以在 launch.json 中找到 Debug executable 'martin',如下所示:

{
    "type": "lldb",
    "request": "launch",
    "name": "Debug executable 'martin'",
    "cargo": {
        "args": [
            "build",
            "--bin=martin",
            "--package=martin"
        ],
        "filter": {
            "name": "martin",
            "kind": "bin"
        }
    },
    "args": [],
    "cwd": "${workspaceFolder}"
},

只需在其后复制并粘贴,然后像这样修改您粘贴的内容:

{
    "type": "lldb",
    "request": "launch",
    "name": "my first debug", // 随意命名
    "cargo": {
        "args": [
            "build",
            "--bin=martin",
            "--package=martin"
        ],
        "filter": {
            "name": "martin",
            "kind": "bin"
        }
    },
    "args": ["postgres://postgres:postgres@localhost:5411/db"], // 在此处添加您的参数
     "env": {
         "DEFAULT_SRID": 4490, // 在此处添加您的环境变量
     },
    "cwd": "${workspaceFolder}"
},

添加断点

转到 martin 代码中您感兴趣的任何部分并添加断点。

我们在 martin 的开始处添加一个断点。

use clap::Parser;
use log::{error, info, log_enabled};
use martin::args::{Args, OsEnv};
use martin::srv::new_server;
use martin::{read_config, Config, MartinResult};

const VERSION: &str = env!("CARGO_PKG_VERSION");

async fn start(args: Args) -> MartinResult<()> {
    info!("Starting Martin v{VERSION}");

调试

单击 Visual Studio Code 左侧面板上的 Run and Debug。选择 my first debug 并在键盘上按 F5。

等待断点被命中。

提供的工具

除了 martin 瓦片服务器外,我们还提供一套工具来帮助构建和管理地图。这些工具旨在与 martin 服务器无缝协作,可用于生成瓦片、管理数据以及对地图执行各种操作。

CLI 工具

Martin 项目包含额外的工具,用于帮助管理可通过 Martin 瓦片服务器提供服务的数据。

martin-cp

martin-cp 是一个用于批量生成瓦片并将检索到的瓦片保存到新的或现有的 MBTiles 文件中的工具。它可用于为大型区域或多个区域生成瓦片。 如果多个区域重叠,它只会生成一次瓦片。 martin-cp 支持与 Martin 服务器相同的配置文件和 CLI 参数,因此它可以支持所有源甚至组合源。

有关更多信息,请参阅这篇文章。

mbtiles

mbtiles 是一个用于从命令行与 *.mbtiles 文件交互的小型实用程序。 它允许用户检查、复制、验证或比较并应用它们之间的差异。

有关更多信息,请参阅这篇文章。

支持的 crate

除了这些工具外,我们还有一套支持 martin 服务器及其生态系统的 crate。 例如 martin-tile-utils,它在 martin、mbtiles 和 martin-cp 中使用。

Martin 作为库

Martin 可以用作独立服务器,也可以用作您自己的 Rust 应用程序中的库。当用作库时,您可以使用以下功能:

  • webui - 启用 Web UI
  • 瓦片源
    • mbtiles - 启用 MBTile 瓦片源
    • pmtiles - 启用 PMTile 瓦片源
    • postgres - 启用 PostgreSQL/PostGIS 瓦片源
  • 支持资源
    • fonts - 启用字体源
    • sprites - 启用精灵图源
    • styles - 启用样式源
  • lambda - 添加对在无服务器函数中运行的专门支持

如果您在公共 martin API 中缺少 Martin 功能的某些部分,我们很乐意听取您的意见。 请在我们的 GitHub 仓库上开启一个 issue,或直接提交 pull request。