如果人们开始阅读有关渲染地图的阅读,那么在涉及的许多不同技术中很容易迷失。我已经阅读了有关如何仅使用PostGis实现矢量瓷砖服务器的几个博客文章,但如何实现栅格瓷砖服务器。
该项目的目的是创建尽可能简约的栅格瓷砖。它不会很浮华,也不会很快,但是我学习新技能和技术方面的精神是尝试尽可能独立,尽可能地实施新技能。这增加了对可用解决方案的理解,这也有助于了解每个涉及的技术试图解决哪些问题。
该项目的代码可以在this github repository中找到,该帖子的代码在分支机构下。
让我们从一些基本定义开始,因此我们在同一页面上。 TileServer是一台服务的服务器,可为构成地图的图块提供服务。瓷砖通常遵循z,x,y命名标准,其中z是缩放水平,x和y是该变焦级别的坐标。缩放级别0包含一个整个地球上的一个瓷砖,每个随后的变焦级别都包含x和y轴上的瓷砖量的两倍,因此每个变焦级别都具有zâ'tiles(zâ² *zâ²)。
。有两种类型的瓷砖:栅格和矢量瓷砖,它们是自我解释的。在此项目中,我将创建一个栅格瓷砖服务器。在地图应用程序中,每个栅格图块具有相同的分辨率,共同的分辨率为256x256像素。获取瓷砖的正常标准是Servername/x/y/z.png。
通常在创建瓷砖时需要以下构建块:
- 数据
- 渲染地图的一种方法
- 处理请求和响应的服务器
要创建一个映射服务器,我们将需要数据。我将使用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。
非常令人沮丧,对吗?服务器渲染每个瓷砖,每个瓷砖大约需要一秒钟。它确实具有自己的军事外观,但是没有许多功能。
很单调。通过Servername/Tiles/Z/X/Y提供单个瓷砖。因此,例如http://localhost:8080/tiles/10/582/296返回以下瓷砖。
感谢您的阅读。欢迎任何评论。在接下来的帖子中,我将首先使用Redis添加一个简单的缓存,然后我将开始使用Mapnik作为渲染器来制作更好的图像。