带有Express和Postgis的极简主义栅格瓷砖服务器
#javascript #教程 #database #gis

如果人们开始阅读有关渲染地图的阅读,那么在涉及的许多不同技术中很容易迷失。我已经阅读了有关如何仅使用PostGis实现矢量瓷砖服务器的几个博客文章,但如何实现栅格瓷砖服务器。

该项目的目的是创建尽可能简约的栅格瓷砖。它不会很浮华,也不会很快,但是我学习新技能和技术方面的精神是尝试尽可能独立,尽可能地实施新技能。这增加了对可用解决方案的理解,这也有助于了解每个涉及的技术试图解决哪些问题。

该项目的代码可以在this github repository中找到,该帖子的代码在分支机构下。

让我们从一些基本定义开始,因此我们在同一页面上。 TileServer是一台服务的服务器,可为构成地图的图块提供服务。瓷砖通常遵循z,x,y命名标准,其中z是缩放水平,x和y是该变焦级别的坐标。缩放级别0包含一个整个地球上的一个瓷砖,每个随后的变焦级别都包含x和y轴上的瓷砖量的两倍,因此每个变焦级别都具有zâ'tiles(zâ² *zâ²)。

Image description

有两种类型的瓷砖:栅格和矢量瓷砖,它们是自我解释的。在此项目中,我将创建一个栅格瓷砖服务器。在地图应用程序中,每个栅格图块具有相同的分辨率,共同的分辨率为256x256像素。获取瓷砖的正常标准是Servername/x/y/z.png。

通常在创建瓷砖时需要以下构建块:

  1. 数据
  2. 渲染地图的一种方法
  3. 处理请求和响应的服务器

要创建一个映射服务器,我们将需要数据。我将使用OpenStreetMaps(OSM)数据和PostgreSQL扩展Postgis来存储瓷砖。

我使用来自https://osmdata.openstreetmap.de/data/land-polygons.html的数据下载数据。 OSM数据以OSM数据(.OSM)或ShapeFiles(.shp)下载。我个人使用QGIS可以使用大多数GIS软件打开ShapeFiles和OSM数据。用于此项目的多边形在.shp。

为了使ShapeFile使用Postgres可读,我们需要更改数据的格式。我使用SHP2PSQL,这是一个非常简单的CLI程序,在下载PostGIS时,SHP2PSQL包含在PostGIS捆绑包中。对于转换OSM数据,可以使用osm2psql

在此示例中,我不会使用单独的渲染,但是我将用Postgis毫不贴花。

核心问题现在是生成一个SQL查询,以在Z,X,Y位置呈现PNG瓷砖。幸运的是,后GIS扩展包含许多使此功能的功能。我在项目中使用了以下查询:

SELECT ST_AsPNG(
  ST_AsRaster(
      ST_collect(Array(
          SELECT ST_Intersection(geom,ST_TileEnvelope($1,$2,$3)) FROM ${TABLE} UNION
          SELECT ST_boundary(ST_TileEnvelope($1,$2,$3))
      )
  ), 256, 256, ARRAY['8BUI', '8BUI', '8BUI'], ARRAY[100,100,100], ARRAY[0,0,0])
);

让我们通过查询。对于那些不太熟悉后的人来说,以ST开头的功能是后GIS功能。查询假设要渲染的所有几何形状都在列呼叫着地理上。

从内而外移动

  • st_intersection(几何a,几何b)返回两种几何之间共享的几何a和几何b的一部分。
  • st_tileenvelope(z,x,y):创建一个矩形多边形,给出了XYZ系统中瓷砖的范围。
  • st_collect(几何[])将几何形状收集到几何集合
  • ST_ASRASTER()将后gis几何形状转换为后gis栅格
  • st_aspng()将栅格的选定频带返回单个png

有关后智能功能的更多信息,请参阅出色的postGIS documentation。特别是对于Asraster和Aspng函数,由于有几种可能性,因此,为简单起见,我没有在此处列出它们

后端很简单。我将用于HTTP服务器的Express和PG库的PostgreSQL API。

这是后端的整体代码,如您所见,它确实是紧凑的:

require("dotenv").config();
const { Client } = require("pg");
const express = require("express");
const path = require("path");

const PORT = process.env.PORT || 8080;
const HOSTNAME = process.env.HOSTNAME || "127.0.0.1";
const PUBLICPATH = path.join(__dirname, "./public");
const PGUSERNAME = process.env.PGUSERNAME;
const PASSWORD = process.env.PASSWORD;
const DATABASE = process.env.DATABASE;
const TABLE = process.env.TABLE;

const query = `
SELECT ST_AsPNG(
  ST_AsRaster(
      ST_collect(Array(
          SELECT ST_Intersection(geom,ST_TileEnvelope($1,$2,$3)) FROM ${TABLE} UNION
          SELECT ST_boundary(ST_TileEnvelope($1,$2,$3))
      )
  ), 256, 256, ARRAY['8BUI', '8BUI', '8BUI'], ARRAY[100,100,100], ARRAY[0,0,0])
);
`;

const pathMakesSense = (z, x, y) => {
  const maxCoord = 2 ** z;
  return z >= 0 && z <= 20 && x >= 0 && x < maxCoord && y >= 0 && y < maxCoord;
}

const client = new Client({
  user: PGUSERNAME,
  database: DATABASE,
  password: PASSWORD,
});

client.connect();

let app = express();

app.get("/tiles/:z/:x/:y", async function (req, res) {
  const { z, x, y } = req.params;
  if (pathMakesSense(parseInt(z), parseInt(x), parseInt(y))) {
    try {
        let response = await client.query(query, [z, x, y]);
        img = response.rows[0].st_aspng;
        res.writeHead(200, {
          "Content-Type": "image/png",
          "Content-Length": img.length,
        });
        res.end(img);
    } catch (error) {
      console.log(error);
    }
  } else {
    res.writeHead(400);
    res.end("Incorrect path");
  }
});

app.use(express.static(PUBLICPATH));

app.listen(PORT, HOSTNAME, () => {
  console.log(`Listening on ${HOSTNAME}, port ${PORT}`);
});

代码的其余部分可以在GitHub存储库中找到。前端地图功能是用传单实现的,也确实很简单。

让我们看看应用程序的工作原理。以下是我的笔记本电脑呈现瓷砖的实时GIF。

Image description

非常令人沮丧,对吗?服务器渲染每个瓷砖,每个瓷砖大约需要一秒钟。它确实具有自己的军事外观,但是没有许多功能。

很单调。

通过Servername/Tiles/Z/X/Y提供单个瓷砖。因此,例如http://localhost:8080/tiles/10/582/296返回以下瓷砖。

Image description

感谢您的阅读。欢迎任何评论。在接下来的帖子中,我将首先使用Redis添加一个简单的缓存,然后我将开始使用Mapnik作为渲染器来制作更好的图像。