当前位置: 首页 > news >正文

基于PostGIS(Postgres)+Node.js实现的xyz瓦片地图服务器

背景介绍

前两天研究GeoServer发布存储在PostGIS中栅格数据,最终目的是想在PostGIS中存储金字塔瓦片,用GeoServer发布,但是最后经过研究不改GeoServer源码的情况下,好像只支持将大图tif存在PostGIS数据库中进行发布,金字塔存入数据库后由于PostGIS的raster类型只存了瓦片的scale没有存类似层级的东西,导致发布后所有的金字塔层级一起显示了(没有层级控制),导致了很多影像叠加到一起了,后来又想到在数据库新增一个level字段,然后使用sld来进行控制显示,最后发现sld读不到字段表,这个路径也就放弃了(也许是我没弄对,有大佬点拨一下的话万分感谢)。

今天灵感来了(自己弄着玩),想到直接把瓦片的编号以及原始数据存到数据库,然后写个网络接口按照xyz数据源的格式请求,接口里查询数据库返回一张image给客户端,是不是就相当于一个xyz的瓦片服务器了?最终经过测试是可行的,先上两张效果图。

QGIS加载效果:

c432545dfa224e2983d1b205f59a75ab.png

水经微图加载效果:

8e7123471c8a47f580e4696bcdefe3cf.png

下面就说一下实现的具体流程

数据准备

数据是wgs84的瓦片,xyz都是从0开始,从左上角开始逆时针编号,以下是瓦片本地存储示例:

2dc62ce18d9d4c5d8877e1f74e3bc5a7.png

组织结构最外层为z值,第二层为x值,图片名称为y值。

数据库表示例:

b9a129b65fe84e6190d35f0748fb899c.png

然后使用Qt将瓦片读入并写入数据库,相关代码如下:

#pragma once#include <QString>
#include <QSqlDatabase>class CImageUploader
{
public:CImageUploader();~CImageUploader();void Init();void CreateTable();void UploadTileImage(const QString& strTileDir);private:QSqlDatabase m_db;
};
#include "ImageUploader.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QFileInfo>
#include <QSqlQuery>
#include <QSqlError>
#include <QByteArray>
#include <QBuffer>QString strHostName = "192.168.1.7";
QString strDatabaseName = "Tile";
QString strUserName = "postgres";
QString strPassword = "root";
QString strPort = "4321";CImageUploader::CImageUploader()
{Init();CreateTable();
}CImageUploader::~CImageUploader()
{}void CImageUploader::Init()
{m_db = QSqlDatabase::addDatabase("QPSQL");m_db.setHostName(strHostName);m_db.setDatabaseName(strDatabaseName);m_db.setUserName(strUserName);m_db.setPassword(strPassword);m_db.setPort(strPort.toInt());if (!m_db.open()){qDebug() << "Failed to open database connection!" << m_db.lastError().text();}
}void CImageUploader::CreateTable()
{// 使用 IF NOT EXISTS 判断表是否存在QString strCreateTableQuery = QString(R"(CREATE TABLE IF NOT EXISTS Tile (id SERIAL PRIMARY KEY,x BIGINT NOT NULL,y BIGINT NOT NULL,z INT NOT NULL,data BYTEA NOT NULL);)");QSqlQuery query(m_db);if (!query.exec(strCreateTableQuery)){qDebug() << "Exec failed:" << query.lastError().text().toLocal8Bit();}
}void CImageUploader::UploadTileImage(const QString& strTileDir)
{QDir zDir(strTileDir);if (!zDir.exists()) {qDebug() << "Directory does not exist:" << strTileDir;return;}// 遍历 z 值文件夹QStringList zFolders = zDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);foreach(const QString &zFolder, zFolders) {bool zOk;int z = zFolder.toInt(&zOk);if (!zOk) {qDebug() << "Invalid z folder:" << zFolder;continue;}QDir xDir(zDir.filePath(zFolder));// 遍历 x 值文件夹QStringList xFolders = xDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);foreach(const QString &xFolder, xFolders) {bool xOk;qint64 x = xFolder.toLongLong(&xOk);if (!xOk) {qDebug() << "Invalid x folder:" << xFolder;continue;}QDir yDir(xDir.filePath(xFolder));// 遍历 y 值的图片文件QStringList imageFiles = yDir.entryList(QDir::Files);foreach(const QString &imageFile, imageFiles) {QString yValueStr = QFileInfo(imageFile).baseName();bool yOk;qint64 y = yValueStr.toLongLong(&yOk);if (!yOk) {qDebug() << "Invalid y file name:" << imageFile;continue;}QString imagePath = yDir.filePath(imageFile);QFile file(imagePath);if (!file.open(QIODevice::ReadOnly)) {qDebug() << "Failed to open image file:" << imagePath;continue;}QByteArray imageData = file.readAll();file.close();// 上传图片数据到数据库QSqlQuery query(m_db);query.prepare("INSERT INTO Tile (x, y, z, data) VALUES (:x, :y, :z, :data)");query.bindValue(":x", x);query.bindValue(":y", y);query.bindValue(":z", z);query.bindValue(":data", imageData);if (!query.exec()) {qDebug() << "Failed to upload tile image:" << query.lastError().text();}else {qDebug() << "Successfully uploaded tile image:"<< "z=" << z << ", x=" << x << ", y=" << y;}}}}
}
#include <QCoreApplication>
#include "ImageUploader.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);CImageUploader imageUploader;imageUploader.UploadTileImage(QString::fromLocal8Bit(R"(H:\data)"));return a.exec();
}

服务实现

数据写入数据库后只需要弄一个http接口了,我这里使用的是Node.js直连PostGIS数据库,然后根据请求去查数据库对应的image值,然后返回给客户端,Node代码如下:

const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 3000;// 配置PostgreSQL连接池
const pool = new Pool({user: 'postgres',host: '192.168.1.7',database: 'Tile',password: 'root',port: 4321,
});// XYZ瓦片接口
app.get('/tiles/:z/:x/:y', async (req, res) => {let { z, x, y } = req.params;// 提取数字部分的 y 值(去除文件扩展名)y = y.split('.')[0];try {// 确保 z, x, y 是有效的数字const zInt = parseInt(z, 10);const xInt = parseInt(x, 10);const yInt = parseInt(y, 10);if (isNaN(zInt) || isNaN(xInt) || isNaN(yInt)) {return res.status(400).send('Invalid tile coordinates');}// 查询数据库获取瓦片数据const queryText = `SELECT data FROM Tile WHERE z = $1 AND x = $2 AND y = $3`;const result = await pool.query(queryText, [zInt -1, xInt, yInt]);if (result.rows.length > 0) {const tileData = result.rows[0].data;res.setHeader('Content-Type', 'image/jpeg'); // 确保设置正确的图片格式res.send(tileData);} else {res.status(404).send('Tile not found');}} catch (err) {console.error('Error fetching tile:', err);res.status(500).send('Internal Server Error');}
});// 启动服务器
app.listen(port, () => {console.log(`Tile server is running at http://localhost:${port}`);
});

 安装依赖:

npm init -y
npm install express pg

 启动:

node server.js

然后就可以在浏览器请求测试(图片格式后缀其实没影响加不加都可):

http://localhost:3000/tiles/{z}/{x}/{y}.jpg

例如:

localhost:3000/tiles/1/0/0.jpg

 效果如下:

bc78f7cd1cdc4923bae70fd8ee18d0eb.png

然后就是QGIS加载测试,在XYZ Tiles新建链接,输入名称和网址,网址直接输入http://localhost:3000/tiles/{z}/{x}/{y}.jpg即可,Node做了z-1处理),图块分辨率可以不管也可以设为256*256,如下:

8d7dc05ab57c47ed881115df9f901acf.png

在水经微图里点击在线地图(自定义),在弹出的页面中,输入前面所说的网址即可,如下:

3eb740a032664fb3a502a122cafa8b35.png

最后给两张放大的效果图(数据只有6级):

QGIS:

92f813507a384ff38ce3dc2a3d063603.png

水经微图:

7deb52c1be7e4c9abd5e412391980ee1.png

分享到此结束。

 

相关文章:

基于PostGIS(Postgres)+Node.js实现的xyz瓦片地图服务器

背景介绍 前两天研究GeoServer发布存储在PostGIS中栅格数据&#xff0c;最终目的是想在PostGIS中存储金字塔瓦片&#xff0c;用GeoServer发布&#xff0c;但是最后经过研究不改GeoServer源码的情况下&#xff0c;好像只支持将大图tif存在PostGIS数据库中进行发布&#xff0c;金…...

浙大数据结构慕课课后题(06-图3 六度空间)

题目要求&#xff1a; 输入格式: 输入第1行给出两个正整数&#xff0c;分别表示社交网络图的结点数N&#xff08;1<N≤103&#xff0c;表示人数&#xff09;、边数M&#xff08;≤33N&#xff0c;表示社交关系数&#xff09;。随后的M行对应M条边&#xff0c;每行给出一对正…...

Windows File Recovery卡在99%怎么解决?实用指南!

为什么会出现“Windows File Recovery卡在99%”的问题&#xff1f; Windows File Recovery&#xff08;Windows文件恢复&#xff09;是微软设计的命令行应用程序。它可以帮助用户从健康/损坏/格式化的存储设备中恢复已删除/丢失的文件。 通过输入相关命令&#xff0c;设置源/…...

数据结构之数组

写在前面 看下数组。 1&#xff1a;巴拉巴拉 数组是一种线性数据结构&#xff0c;使用连续的内存空间来存储数据&#xff0c;存储的数据要求有相同的数据类型&#xff0c;并且每个元素占用的内存空间相同。获取元素速度非常快&#xff0c;为O(1)常量时间复杂度&#xff0c;所…...

springboot集成sensitive-word实现敏感词过滤

文章目录 敏感词过滤方案一&#xff1a;正则表达式方案二&#xff1a;基于DFA算法的敏感词过滤工具框架-sensitive-wordspringboot集成sensitive-word步骤一&#xff1a;引入pom步骤二&#xff1a;自定义配置步骤三&#xff1a;自定义敏感词白名单步骤四&#xff1a;核心方法测…...

C++ 之动手写 Reactor 服务器模型(一):网络编程基础复习总结

基础 IP 地址可以在网络环境中唯一标识一台主机。 端口号可以在主机中唯一标识一个进程。 所以在网络环境中唯一标识一个进程可以使用 IP 地址与端口号 Port 。 字节序 TCP/IP协议规定&#xff0c;网络数据流应采用大端字节序。 大端&#xff1a;低地址存高位&#xff0c…...

qt 在vs2022 报错记录

1&#xff0c;qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed 需要把SSL 相关的库加入进去&#xff0c;如ssleay32.dll&#xff0c;libeay32.dll。 2&#xff0c;在一个文件中已定义&#xff0c;编译器在链接时&#xff0c;在多处报 已在.*…...

【人工智能】TensorFlow和机器学习概述

一、TensorFlow概述 TensorFlow是由Google Brain团队开发的开源机器学习库&#xff0c;用于各种复杂的数学计算&#xff0c;特别是在深度学习领域。以下是对TensorFlow的详细概述&#xff1a; 1. 核心概念 张量&#xff08;Tensor&#xff09;&#xff1a;TensorFlow中的基本…...

SQLALchemy 的介绍

SQLALchemy 的介绍 基本概述主要特点使用场景安装与配置安装 SQLAlchemy配置 SQLAlchemy示例&#xff1a;使用 SQLite 数据库连接到其他数据库 结论 总结 SQLAlchemy是Python编程语言下的一款开源软件&#xff0c;它提供了SQL工具包及对象关系映射&#xff08;ORM&#xff09;工…...

Java虚拟机:运行时内存结构

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 035 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…...

微信小程序子组件调用父组件的方法

来源&#xff1a;通义千文2.5 步骤 1: 定义父组件中的方法 首先&#xff0c;在父组件中定义一个方法&#xff08;如 handleClick&#xff09;&#xff0c;并准备一个用于接收子组件传来的数据的方法。 父组件&#xff08;Parent.wxml&#xff09; html<!-- parent.wxml …...

【数据结构】TreeMap和TreeSet

目录 前言TreeMap实现的接口内部类常用方法 TreeSet实现的接口常用方法 前言 Map和set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关。 一般把搜索的数据称为关键字&#xff08;Key&#xff09;&#xff0c; 和关键字对应的称为…...

前端react集成OIDC

文章目录 OpenID Connect (OIDC)3种 授权模式 【服务端】express 集成OIDC【前端】react 集成OIDCoidc-client-js库 原生集成react-oidc-context 库非组件获取user信息 OAuth 2.0 协议主要用于资源授权。 OpenID Connect (OIDC) https://openid.net/specs/openid-connect-core…...

JavaWeb—XML_Tomcat10_HTTP

一、XML XML是EXtensible MarkupLanguage的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签。 可扩展:三个字表面上的意思是XML允许自定义格式。但这不代表你可以随便写; 在XML基…...

中介者模式在Java中的实现:设计模式精解

中介者模式在Java中的实现&#xff1a;设计模式精解 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于定义一个中介者对象&#xff0c;以封装一系列对象之间的交互&#xff0c;从而使对象之间的交互不再直接发生&#xff0c;减少了系…...

PyQt编程快速上手

Python GUI安装 GUI就是图形用户界面的意思&#xff0c;在Python中使用PyQt可以快速搭建自己的应用&#xff0c;使得自己的程序看上去更加高大上&#xff0c;学会GUI编程可以使得自己的软件有可视化的结果。 如果你想用Python快速制作界面&#xff0c;可以安装PyQt&#xff1a…...

Docker Swarm管理

Docker Swarm管理 前置知识点 Docker Swarm 是 Docker 公司 2014年出品的基于 Docker 的集群管理调度工具&#xff0c;能够将多台主机构建成一个Docker集群&#xff0c;并结合Overlay网络实现容器调度的互访 用户可以只通过 Swarm API 来管理多个主机上的 Docker Swarm 群集包…...

Python | Leetcode Python题解之第335题路径交叉

题目&#xff1a; 题解&#xff1a; class Solution:def isSelfCrossing(self, distance: List[int]) -> bool:n len(distance)# 处理第 1 种情况i 0while i < n and (i < 2 or distance[i] > distance[i - 2]):i 1if i n:return False# 处理第 j 次移动的情况…...

Ubuntu视频工具

1. VLC VLC Media Player&#xff08;VLC多媒体播放器&#xff09;&#xff0c;最初命名为VideoLAN客户端&#xff0c;是VideoLAN品牌产品&#xff0c;是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式&#xff0c;并支持DVD影音光盘&#xff0c;VCD影音光…...

HBase snapshot+replication 测试

一、背景 画像标签服务&#xff08;CDP&#xff09;是核心服务&#xff0c;被公司其他系统如现金、电商、风控等核心业务调用。异常的话&#xff0c;影响范围大。 二、目标 存量数据测试通过 snapshot 迁移。增量数据测试通过 replication 同步。 三、测试 方案二测试&#x…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...