Skip to content

Add provider for MVT tile generation from Postgres #1979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

ThorodanBrom
Copy link
Contributor

@ThorodanBrom ThorodanBrom commented Mar 27, 2025

Overview

This PR adds MVT tile generation from Postgres/PostGIS tables natively.

Related Issue / discussion

This is related to issue #1978

Additional information

Dependency policy (RFC2)

  • I have ensured that this PR meets RFC2 requirements

Updates to public demo

Contributions and licensing

(as per https://github.com/geopython/pygeoapi/blob/master/CONTRIBUTING.md#contributions-and-licensing)

  • I'd like to contribute this feature to pygeoapi. I confirm that my contributions to pygeoapi will be compatible with the pygeoapi license guidelines at the time of contribution
  • I have already previously agreed to the pygeoapi Contributions and Licensing Guidelines

@ThorodanBrom ThorodanBrom changed the title Add mvt postgres provider Add provider for MVT tile generation from Postgres Mar 27, 2025
@tomkralidis tomkralidis added this to the 0.21.0 milestone Mar 30, 2025
@tomkralidis tomkralidis added the OGC API - Tiles OGC API - Tiles label Mar 30, 2025
Copy link
Contributor

@doublebyte1 doublebyte1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank You @ThorodanBrom ! Would you be able to add a Postgres Provider docker example at pygeoapi-examples? https://github.com/geopython/pygeoapi-examples/tree/main/docker

query = text("""
WITH
bounds AS (
SELECT ST_TileEnvelope(:z, :y, :x, ST_MakeEnvelope(-180, -90, 180, 90, 4326)) AS boundgeom
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the tile envelope here is based on the extent of the crs, instead of the extent of the geometry (like in the case of WebMercator)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ST_TileEnvelope by default is configured to EPSG:3857. In order to use it for other CRSs, the docs mention that you can add an extra parameter of a geometry that has an extent of the 0th zoom level. There's an example of setting it for EPSG:4326, we just used that here. (hope that this answered your question, if not, please let me know)

We can probably use the WKB representation there so that MakeEnvelope isn't calculated for every tile request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think the WKB representation would make sense here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 97ba024. I used EWKT representation instead of WKB as the the WKB hex string was too long

SELECT ST_TileEnvelope(:z, :y, :x) AS boundgeom
),
mvtgeom AS (
SELECT ST_AsMVTGeom(ST_Transform({geom}, 3857), bounds.boundgeom) AS geom {fields}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think converting curves to lines (ST_CurveToLine), could speed up queries in the case of more complex geometries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will try this out, it looks like Martin also uses it.

So by using ST_CurveToLine, will this provider also be able to support CIRCULARSTRING-like data types (that is what I understood from reading the documentation)? Or is it just going to be used for query performance?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think simplification, in general, yields performance; and in the case of tiles, we do not need the geometries to be so detailed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 97ba024

@doublebyte1
Copy link
Contributor

doublebyte1 commented Apr 19, 2025

@ThorodanBrom
Copy link
Contributor Author

Thanks for the review,

  • I'll work on adding an example provider for this in pygeoapi-examples as well as writing test cases
  • I'll check out the errors in the documentation and flake8
  • The PR needs a few updates and also needs to be rebased as well to accomodate the MVT tile fixes, is it OK if I rebase and force-push or will that break any of the CI flows?

@doublebyte1
Copy link
Contributor

Thanks for the review,

  • I'll work on adding an example provider for this in pygeoapi-examples as well as writing test cases
  • I'll check out the errors in the documentation and flake8
  • The PR needs a few updates and also needs to be rebased as well to accomodate the MVT tile fixes, is it OK if I rebase and force-push or will that break any of the CI flows?

Thank You @ThorodanBrom ! I think it is ok to rebase, but I am pinging @tomkralidis here, just in case.

@tomkralidis
Copy link
Member

+1 to rebase, given it will start CI again fresh.

ThorodanBrom and others added 4 commits May 2, 2025 22:32
- `/tiles` and `/tiles/{TMS}` APIs are working
- Supports properties
- Supports both WebMercatorQuad, WorldCRS84Quad

Co-authored-by: PRAJWAL S <praju18299@gmail.com>
Co-authored-by: Tanvi Prasad <tanvi.prasad@datakaveri.org>
- Fixed flake8 issues
- Corrected tile indices in `ST_TileEnvelope`
	- Previous indices worked when tile URL was `/z/x/y`. Current ones work for `/z/y/x`, which is OGC-compliant
- Used `ST_CurveToLine` when querying for features during tile generation to potentially speed up queries
	- Martin also uses it (https://maplibre.org/martin/sources-pg-functions.html#simple-function)
- Used EWKT representation of `ST_MakeEnvelope(-180,-90,180,90,4326)` for WorldCRS84Quad tiles
@ThorodanBrom ThorodanBrom force-pushed the add-mvt-postgres-provider branch from d9c05c6 to 97ba024 Compare May 2, 2025 19:05
table: hotosm_bdi_waterways
geom_field: foo_geom
options:
zoom:
Copy link
Contributor

@doublebyte1 doublebyte1 May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThorodanBrom I think that the zoom should be placed under options

Copy link
Contributor

@doublebyte1 doublebyte1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the format of the example in the documentation, and I think we are good to go.

You can also click on the check-boxes that say "Updates to public demo", as this provider is not used in the configuration file of the demo.

Thank You!

@ThorodanBrom
Copy link
Contributor Author

Thanks for the review @doublebyte1. I'm working on the example in pygeoapi-examples, but I'm not really sure on how to write tests for this provider. There aren't any tests for other MVT providers, so I don't have a reference. I can think of the following conditions to test:

  • if the provider is able to create tiles for the configured table and configured zoom levels for WebMercatorQuad and WorldCRS84Quad
  • if the tiles returned are valid MVT files + a 200 OK
    • I don't know how to test for a 'valid' MVT, is there a library or something that can parse and validate the API output
  • if tiles requested for an invalid index or where data does not exist returns a 204 No Content

Do let me know if this sounds OK.

@doublebyte1
Copy link
Contributor

Thanks for the review @doublebyte1. I'm working on the example in pygeoapi-examples, but I'm not really sure on how to write tests for this provider. There aren't any tests for other MVT providers, so I don't have a reference. I can think of the following conditions to test:

  • if the provider is able to create tiles for the configured table and configured zoom levels for WebMercatorQuad and WorldCRS84Quad

  • if the tiles returned are valid MVT files + a 200 OK

    • I don't know how to test for a 'valid' MVT, is there a library or something that can parse and validate the API output
  • if tiles requested for an invalid index or where data does not exist returns a 204 No Content

Do let me know if this sounds OK.

You are right in that there are no tests for other MVT providers. Let's keep it like this, for now. Thank You for the availability!

Copy link
Member

@tomkralidis tomkralidis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work here! Minor change requests on naming consistency.

@@ -22,6 +22,7 @@ pygeoapi core tile providers are listed below, along with supported features.
`MVT-elastic`_,✅,✅,✅,❌,❌,✅
`MVT-proxy`_,❓,❓,❓,❓,❌,✅
`WMTSFacade`_,✅,❌,✅,✅,✅,❌
`MVT-postgres`_,✅,✅,✅,✅,❌,✅
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to MVT-postgresql

.. note::
Requires Python packages sqlalchemy, geoalchemy2 and psycopg2-binary

Must have PostGIS installed with protobuf-c support.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turn into an additional note

.. note::
Geometry must be using EPSG:4326

This provider gives support to serving tiles generated using `Postgres <https://www.postgresql.org/>`_ with `PostGIS <https://postgis.net/>`_.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Postgres/PostgreSQL/g


providers:
- type: tile
name: MVT-postgres
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MVT-postgresql

@@ -53,6 +53,7 @@
'MVT-tippecanoe': 'pygeoapi.provider.mvt_tippecanoe.MVTTippecanoeProvider', # noqa: E501
'MVT-elastic': 'pygeoapi.provider.mvt_elastic.MVTElasticProvider',
'MVT-proxy': 'pygeoapi.provider.mvt_proxy.MVTProxyProvider',
'MVT-postgres': 'pygeoapi.provider.mvt_postgres.MVTPostgresProvider',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'MVT-postgresql': 'pygeoapi.provider.mvt_postgresql.MVTPostgreSQLProvider',

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to pygeoapi/provider/mvt_postgresql.py

@@ -0,0 +1,246 @@
# =================================================================
#
# Authors:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add yourself as author. Example in https://github.com/geopython/pygeoapi/blob/master/setup.py#L3

#
# Authors:
#
# Copyright (c)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#
# =================================================================

import logging
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order imports by standard library, 3rd party packages, then local imports, alphabetically, separated by blank line.

from copy import deepcopy
import logging

from sqlalchemy.sql import text

from pygeoapi.models.provider.base import (
    TileSetMetadata, TileMatrixSetEnum, LinkType)
from pygeoapi.provider.base import ProviderConnectionError
from pygeoapi.provider.base_mvt import BaseMVTProvider
from pygeoapi.provider.postgresql import PostgreSQLProvider
from pygeoapi.util import url_join

LOGGER = logging.getLogger(__name__)


class MVTPostgresProvider(BaseMVTProvider):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to MVTPostgreSQLProvider

- Updated all necessary files to change postgres -> postgresql related to the plugin
	- Updated documentation
- Added authors to plugin file and organized imports
…S, return ProviderTileNotFound error

- Otherwise error was thrown from PostgreSQL `ST_MakeTileEnvelope` function
@ThorodanBrom ThorodanBrom force-pushed the add-mvt-postgres-provider branch from 19b9fbb to 9f596a1 Compare May 19, 2025 16:50
@ThorodanBrom
Copy link
Contributor Author

ThorodanBrom commented May 19, 2025

Hello @tomkralidis, thanks for the review. I think I've addressed all your comments in 9a320cc. (I added a small bug fix in a034d15 as well). Please let me know if there are any other required changes.

Must have PostGIS installed with protobuf-c support

.. note::
Geometry must be using EPSG:4326

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this note is outdated for the postgresql provider. Is the note valid for this case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OGC API - Tiles OGC API - Tiles
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants