Using the new MVT function in PostGIS使用PostGIS中的MVT函數ST_AsMVT()

We ❤️ vector tiles. They’re a key part of our modern open source spatial stack, and we’ve played around with several ways to generate them over the y̵e̵a̵r̵s̵
months. We’ve pulled them out of Carto’s Maps API, even before they were a documented feature. We’ve built simple tools cut them from geojson, and used tilestrata to create them from shapefiles. We host our own openmaptiles server to serve up vector tiles with openstreetmap data.node

咱們❤️ 矢量平鋪。它們是咱們現代開源空間堆棧的關鍵部分,咱們過去幾個月已經研究了幾種生成它們的方法。咱們已經把它們從Carto的maps api中拉了出來,甚至在它們成爲一個文檔化的特性以前。咱們構建了從geojson中剪切它們的簡單工具,並使用tilestrata從shapefile中建立它們。咱們託管咱們本身的openmappiles服務器來提供帶有openstreetmap數據的向量塊。git

We recently found ourselves setting up a new PostGIS-based data service, and trying to figure out the best way to serve vector tiles from it. In the past, vector tiles have involved some other layer of code to process and compress the raw spatial data that comes from the database.github

咱們最近發現本身正在創建一個新的基於PostGIS的數據服務,並試圖找出從中提供矢量切片服務的最佳方法。在過去,矢量圖塊涉及到一些其餘的代碼層來處理和壓縮來自數據庫的原始空間數據。web

 

 Prior to ST_AsMVT(), you needed middleware or some other package to clip, build, and compress a vector tile from raw data數據庫

在ST_AsMVT()函數出現以前,你須要中間件或者其它的包來裁剪、生成和壓縮從原始數據來製做矢量切片express

As of PostGIS 2.4.0, ST_AsMVT() is a thing! ???? Now you can get a ready-to-consume vector tile right from the database, without another layer on top. What’s also great is that this works seamlessly with express, our technology of choice for building web APIs. This means we can build out a custom vector tile endpoint with just a few lines of JavaScript! (We found several great tutorials on using the new features, but none that specifically paired them with express and pg-promise, so here’s our contribution for others who may be using this stack). The new PostGIS feature cuts out the middle man!json

而從PostGIS2.4.0開始,ST_AsMVT()出現了!如今你能夠直接從數據庫中獲取一個矢量切片,而無需在上面再包裹一層。還有一點很好,那就是它能夠與express無縫結合,express是咱們構建webapi的首選技術。這意味着咱們能夠用幾行JavaScript構建一個定製的矢量切片端點(咱們找到了一些關於使用新特性的很好的教程,可是沒有一個專門將它們與express和pg-promise結合使用,所以下面是咱們對其餘可能使用此堆棧的人的貢獻)。新的PostGIS功能去掉了中間人!api

 

 With ST_AsMVT(), you can get ready-to-consume vector tiles right out of PostGIS!promise

使用ST_AsMVT(),你能夠從PostGIS中直接獲取矢量切片!服務器

Here’s a dirt-simple vector tile route. You hit the endpoint with your z/x/y tile ids, and get back a tile.

這是一個很是簡單的矢量切片route。您使用z/x/y切片ID命中端點,而後獲得一個切片。

/* GET /tiles/:z/:x/:y.mvt */
/* Retreive a vector tile by tileid 經過切片id獲取一個矢量切片*/
router.get('/tiles/:z/:x/:y.mvt', async (req, res) => {
  const { z, x, y } = req.params;

  // calculate the bounding polygon for this tile 
  const bbox = mercator.bbox(x, y, z, false);

  // Query the database, using ST_AsMVTGeom() to clip the geometries
  // Wrap the whole query with ST_AsMVT(), which will create a protocol buffer
  const SQL = `
    SELECT ST_AsMVT(q, 'internal-layer-name', 4096, 'geom')
    FROM (
      SELECT
          somecolumn,
          ST_AsMVTGeom(
              geom,
              ST_MakeEnvelope(${bbox[0]}, ${bbox[1]}, ${bbox[2]}, ${bbox[3]}, 4326),
              4096,
              256,
              false
          ) geom
      FROM sometable c
    ) q
  `;

  try {
    const tile = await db.one(SQL);

    // set the response header content type
    res.setHeader('Content-Type', 'application/x-protobuf');

    // trigger catch if the vector tile has no data, (return a 204)
    if (tile.st_asmvt.length === 0) {
      res.status(204);
    }    
    
    // send the tile!
    res.send(tile.st_asmvt);
  } catch (e) {
    res.status(404).send({
      error: e.toString(),
    });
  }
});

A few things to note:

  • ST_AsMVT() works hand-in-hand with ST_AsMVTGeom(), which clips the geometries at the tile edge—plus a tile buffer in the same units as the extent (see below).
  • The subquery above gets us multiple rows of tile-ready geometries and their properties (or attributes for the GIS-minded), the the wrapping query uses ST_AsMVT(), which bundles it all up in a nice compressed tile in protocol buffer format.
  • We must get the corners of the tile before we can call ST_AsMVTGeom(); this is done in node using the @mapbox/sphericalmercator package. The resulting coordinates are added to the SQL query as a bounding polygon using ST_MakeEnvelope();
  • The 4096 you see in both ST_AsMVT() and ST_AsMVTGeom() is the tile’s extent, or the internal coordinate system of tile. For more on why 4096 is the default for this, here’s a github issue thread about it.
  • We’re using pg-promise and async-await to run the query. If all goes well, we get a nice vector tile blob back, and can send it right out the door with res.send() All that’s necessary is to set the response Content-Type header to application/x-protobuf
  • If the query yields no results because there are no geometries within the bounds of the requested tile, we return an HTTP 204 (no data). This prevents console warnings/errors in the client that’s consuming the vector tiles.

We were surprised at how quickly this approach 「just worked」, and that the data returned from the database could just be sent back in the express response without any additional work. We had mapboxGL consuming our new tile endpoint in minutes!

Some things to keep tinkering with:

  • So far we’ve only used this method to produce vector tiles with a single internal layer. Our next step will be to pack several internal layers in to the same tile.

          到目前爲止,咱們只使用這種方法來生成具備單個內層的矢量圖塊。咱們的下一步將是把幾個內部層打包到同一個瓷磚中。

  • There may be some efficiency gained if we can pipe/stream the data from the database into the response, especially for larger multi-layer tiles.

         若是咱們可以將數據庫中的數據經過管道/流傳輸到響應中,可能會得到一些效率,特別是對於更大的多層圖塊。

Thanks for reading! Have you used ST_AsMVT()? If you have pointers, pitfalls, or general comments, let us know on twitter at @nycplanninglabs.

Happy mapping!

https://medium.com/nyc-planning-digital/using-the-new-mvt-function-in-postgis-75f8addc1d68

相關文章
相關標籤/搜索