heymoon/vector-tile-data-provider

提供、写入和读取 Mapbox Vector Tile 2.1

1.0.8 2024-01-18 20:18 UTC

This package is auto-updated.

Last update: 2024-09-18 21:50:40 UTC


README

Version PHP Version Require Test Maintainability Test Coverage

Symfony + Redis 示例: https://map.heymoon.cc

Screenshot

基于 Leaflet 的基本地图示例,具有实时 Symfony 后端性能预览。当“使用缓存”复选框未激活时,在每个请求上调用 getTileMVT 函数,而不采用任何额外的静态缓存策略。启用“使用缓存”选项时,后端只渲染每个瓦片一次,并将结果存储在 Redis 中。分别请求两个图层进行基准测试。

  • 基于包含 7 个字符串属性的 976K GeoJSON 的多边形。
  • 基于 2.8M GeoJSON 的线条,包含 139 个可选属性。

它只有 1 个 CPU 和低内存可用,所以请温柔对待。

摘要

OpenGIS 数据通过 brick/geo 直接转换为 Mapbox Vector Tile 2.1 格式。专注于以尽可能低的延迟交付频繁变更的源数据。通过 C/C++ 库 GEOS 和 PHP 集成以及自定义更新触发器,快速处理数据以满足您的需求。比以往更快地进行 SRID 转换和 Douglas-Peucker 简化。

附加功能:将 MVT 瓦片转换为 SVG(仅用于调试目的,不适合生产)。安装 meyfa/php-svg 以使用此功能。

动机

如果您想在地图上显示当前天气状况或某些移动对象,那么能够在接收到数据提供者的更新时直接生成您的瓦片集将很有用,例如,在从 HTTP API 响应中读取 GeoJSON 时。瓶颈通常出现在以下方面

  • 需要使用与某些第三方工具兼容的中间存储来准备 MVT
  • 生成大比例尺瓦片
  • 在服务器节点之间共享更新,尤其是如果它存储在单个 MBTiles 文件中。当然,您可以从 PostGIS 数据库 生成瓦片,这将允许频繁更新源数据,但您可能会在每次索引更新后遇到性能下降的问题。因此,使用标准工具集,您将被迫在低更新延迟和低响应时间之间做出选择。但情况不应该是这样。这正是这个库可以提供帮助的地方。

如何更快

很可能是您的数据并非在每一个瓦片中都发生变化。即使发生变化,如果您查看按缩放级别划分的请求分布,您可能会发现,生成的瓦片大多数在小于例如18级缩放的范围内,很少在数据变化时被请求,但占用了更新的大部分时间,因此您可能希望优先处理某些瓦片而不是其他瓦片。在足够的灵活性下,您可以使之前的结果部分失效,并仅提前处理频繁请求的缩放级别,其余部分留待按需处理,并仅在HTTP请求时更新MVT缓存。使用这个库,您将能够实现与特定更新场景的紧密集成,并通过自定义更新流水线最小化冗余计算。此外,由于可以直接调用GEOS函数,因此更容易进行扩展。

安装

composer require heymoon/vector-tile-data-provider

您必须显式地从项目根目录生成protobuf类

protoc --proto_path=./vendor/heymoon/vector-tile-data-provider/proto --php_out=./vendor/heymoon/vector-tile-data-provider/proto/gen ./vendor/heymoon/vector-tile-data-provider/proto/vector_tile.proto

为获得最佳性能,请安装php-geosphp-protobuf扩展。以下是用于Alpine 3.16和PHP 8.1的示例Dockerfile

FROM php:8.1-alpine3.16
RUN apk add --no-cache --virtual .build-deps \
    $PHPIZE_DEPS \
    geos-dev \
    git
RUN apk add --no-cache protoc geos
RUN pecl install protobuf \
    && docker-php-ext-enable protobuf \
	&& git clone https://git.osgeo.org/gitea/geos/php-geos.git /usr/src/php/ext/geos && cd /usr/src/php/ext/geos && \
	./autogen.sh && ./configure && make && \
	echo "extension=/usr/src/php/ext/geos/modules/geos.so" > /usr/local/etc/php/conf.d/docker-php-ext-geos.ini
RUN apk del -f .build-deps && rm -rf /tmp/* /var/cache/apk/*
# run "composer install" and then...
RUN protoc --proto_path=./vendor/heymoon/vector-tile-data-provider/proto --php_out=./vendor/heymoon/vector-tile-data-provider/proto/gen ./vendor/heymoon/vector-tile-data-provider/proto/vector_tile.proto

提供

  • HeyMoon\VectorTileDataProvider\Entity\SourceEntity\SourceProxy实例(在评估前将几何形状存储为WKB),由HeyMoon\VectorTileDataProvider\Factory\SourceFactory初始化,以便轻松地从Brick\Geo\IO\GeoJSON\FeatureCollection或手动填充的Brick\Geo\Geometry对象加载数据。
  • HeyMoon\VectorTileDataProvider\Service\SpatialService用于低成本的空问系统转换。
  • HeyMoon\VectorTileDataProvider\Service\GridService和生成的HeyMoon\VectorTileDataProvider\Entity\Grid实例,用于过滤特定瓦片和特定线程中可见的几何形状(可以通过在GridService::getGrid中提供过滤回调函数以基于位置跳过瓦片来实现线程化)。此操作对RAM的需求最大,但可能比完整结果生成更快完成。
  • HeyMoon\VectorTileDataProvider\Service\TileService用于生成Mapbox Vector Tile 2.1,可能在Grid::iterate回调或HTTP请求中,从预存的GridService组中读取所需的几何形状。几何形状简化仅在TileService::getTileMVT上执行。
  • HeyMoon\VectorTileDataProvider\Service\ExportService用于基本导出到.mvt,通过NGINX提供瓦片集作为静态文件,或用于结果预览的.svg
  • HeyMoon\VectorTileDataProvider\Factory\TileFactory用于解析和合并准备好的矢量瓦片。

空间系统

默认情况下,网格预期与Web Mercator投影对齐,这很可能与您原始数据源的空间参考系统不同。为了处理具有任意SRID的输入,库在HeyMoon\VectorTileDataProvider\Service\SpatialService中包含自定义的空间转换引擎实现(因为php-geos不支持SRID转换,并且在PostGIS中存在性能问题)。目前以下几何形状支持默认安装

  • SRID 3857(即前面提到的Web Mercator)。
  • SRID 4326(即WGS 84,最常用的,GeoJSON的默认值,根据RFC 7946确定)。

额外的投影可以被描述为SpatialProjectionInterface的子类,并传递给由AbstractProjectionRegistry扩展的新类的supports函数。投影类应该实现从WGS 84到所需空间系统的点坐标在二维表面上的转换,以及反向转换函数。

Symfony中的示例用法

依赖注入配置

services:
  Brick\Geo\IO\GeoJSONReader: ~
  Brick\Geo\Engine\GeometryEngine:
    class: 'Brick\Geo\Engine\GEOSEngine'
  HeyMoon\VectorTileDataProvider\Factory\GeometryCollectionFactory: ~
  HeyMoon\VectorTileDataProvider\Factory\SourceFactory: ~
  HeyMoon\VectorTileDataProvider\Registry\AbstractProjectionRegistry:
    class: 'HeyMoon\VectorTileDataProvider\Registry\BasicProjectionRegistry'
  HeyMoon\VectorTileDataProvider\Registry\AbstractExportFormatRegistry:
    class: 'HeyMoon\VectorTileDataProvider\Registry\ExportFormatRegistry'
  HeyMoon\VectorTileDataProvider\Service\SpatialService: ~
  HeyMoon\VectorTileDataProvider\Service\GridService: ~
  HeyMoon\VectorTileDataProvider\Service\TileService: ~
  HeyMoon\VectorTileDataProvider\Service\ExportService: ~

操作

use Brick\Geo\IO\GeoJSONReader;
use Brick\Geo\Exception\GeometryException;
use HeyMoon\VectorTileDataProvider\Entity\TilePosition;
use HeyMoon\VectorTileDataProvider\Factory\SourceFactory;
use HeyMoon\VectorTileDataProvider\Service\GridService;
use HeyMoon\VectorTileDataProvider\Service\TileService;
use HeyMoon\VectorTileDataProvider\Service\ExportService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:export')]
class ExportCommand extends Command
{
    public function __construct(
        private readonly GeoJSONReader $geoJSONReader,
        private readonly SourceFactory $sourceFactory,
        private readonly GridService $gridService,
        private readonly TileService $tileService,
        private readonly ExportService $exportService
    )
    {
        parent::__construct();
    }

    public function configure()
    {
        $this->addArgument('in', InputArgument::REQUIRED);
        $this->addArgument('out', InputArgument::REQUIRED);
        $this->addOption('zoom', 'z', InputOption::VALUE_OPTIONAL);
        $this->addOption('type', 't', InputOption::VALUE_OPTIONAL,
            'mvt for .mvt or svg for .svg');
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $source = $this->sourceFactory->create();
        try {
            $source->addCollection('export', $this->geoJSONReader->read(file_get_contents($input->getArgument('in'))));
            $grid = $this->gridService->getGrid($source, $input->getOption('zoom') ?? 0);
            $path = $input->getArgument('out');
            $type = $input->getOption('type') ?? 'mvt';
            $grid->iterate(fn (TilePosition $position, array $data) =>
                $this->exportService->dump(
                    $this->tileService->getTileMVT($data, $position), "$path/$position.$type")
            );
        } catch (GeometryException $e) {
            $output->writeln("Data error: {$e->getMessage()}");
            return 1;
        }
        return 0;
    }
}

已在Symfony 6.1上测试。

导出结果

在实际场景中,您不会导出SVG文件,而是将数据写入您选择的数据库。例如,您可以创建一个可由MBTiles文件读取,该文件可由tileserver-gl读取。这可以通过包含来自最新规范描述的模式的SQLite数据库来实现。或者,您可以存储仅网格分区结果,在PHP中稍后在自己的矢量数据源控制器中按需生成(如果需要更快的数据更新,则很有用)。