本文字数:9099字

预计阅读时间:25分钟

这里,首先我们从概念出发,搞清楚瓦片地图服务以及地图瓦片的原理,读起来似乎有点拗口,但是从字面上看得出它们必定拥有着区别与联系,前者是WebGIS中的一个服务,后者则是关于‘地图瓦片’的底层原理,那么什么是瓦片地图服务呢?

一、瓦片地图服务

根据官方定义,瓦片地图服务(TMS,Tile Map Service)指的是OSGeo的瓦片地图服务规范,提供的操作允许用户按需访问瓦片地图。在OGC标准化服务中,也有一个类似的服务,叫做WMTS(Web Map Tile Service,网络地图瓦片服务),它是OGC提出的缓存技术标准。两者名字虽然不一样,但是都是地图瓦片服务,在本质上非常类似,基本上遵循的是同一种切片规则。所以,这里对这两者不再区分,下述均以WMTS为代表说明。WMTS的产生是为了更高效快速的加载渲染地图数据。如果海量的地图数据以矢量的形式传输到客户端、在客户端渲染,那么我们可以确定的是,首先需要消耗大量的网络流量,其次,这对客户端的CPU/GPU也必定会有较大的负荷。基于这些情况,WMTS提出预渲染图块的模式,在服务端将地图渲染好,并根据比例尺分割不同的栅格图块,根据客户端的请求,传输这些图块,提供给客户端进行展示。所以,也可以简单的理解为,WMTS是WMS“能将切片保存到本地”的版本。目前,大部分PC端、移动端的地图底图使用的基本上都是这种栅格瓦片。

二、地图瓦片原理

那么,到底什么是地图瓦片呢?

2.1 瓦片

将一定范围内的地图按照一定的尺寸和格式,按缩放级别或者比例尺,切成若干行和列的正方形栅格图片,切片后的正方形栅格图片就如同屋顶错落有致的瓦片一样,故被形象的称为瓦片(Tile)。

图1:瓦片(左),地图瓦片(右)

2.2 金字塔模型

瓦片地图的金字塔模型是一种多分辨率的层次模型,如图二所示,zoom level(瓦片等级)越大,组成地图的瓦片数越多,可以展示的地图内容也就越详细。

图2:地图瓦片层次关系

在某一zoom level下的地图瓦片,是由它的上一层级的各瓦片切割成的4个瓦片组成,如果把这种层级关系自顶向下依次排列,就如同金字塔一样,如图三所示。可以看出,从瓦片金字塔的底层到顶层,分辨率越来越低,但表示的地理范围不变,地图的最小zoom level为0,此时整张地图是一个瓦片。

图3:地图瓦片金字塔模型

根据WMTS官方标准,规定的瓦片地图设定Web-Mercator投影坐标系原点为东经180°、南纬85.05°,x轴向右,y轴向上,zoom level最小为0,最大为24。瓦片尺寸通常为256*256像素(也可以是其它尺寸)。

图4:WMTS瓦片地图规范

但是在这个标准出台以前,Google的瓦片地图规范已经施行多年,深受行业认可,国内外图商,如OSM、高德、腾讯等都使用Google的瓦片地图规范,其定义略有不同,坐标原点为东经180°、北纬85.05°,x轴向右,y轴向下,zoom level最小为0、最大为21。

图5:Google瓦片地图规范

三、瓦片地图的生产与分发

上述对地图瓦片及瓦片地图服务有了一个概念性的了解,那么接下来,我们一起看一下瓦片地图的整个生产流程是怎样的。在已有数据的前提下,整个流程大概经过三个环节:对地理数据进行投影,并进行配图 ;分层级渲染数据,对渲染后栅格图像进行切片处理;分发地图瓦片,并进行前端展示。

3.1 地图投影与配图

3.1.1 数据投影

在数据库中,地理数据是以地理坐标系(如WGS84)存储的,为了更好地进行展示,那么我们首先需要将这种以经纬度形式存储的三维数据映射到二维平面上。这便是地图投影,它是利用一定数学法则把地球表面的经纬度转换到平面上的理论和方法。

(1)地图坐标系

这里需要理解几个地图中使用的坐标系,以帮助我们理解投影的作用。

  • 地理坐标系,也叫经纬度坐标系。像素坐标系,也可以称为屏幕坐标系。像素坐标系和地理坐标系存在对应关系,屏幕上的每一个像素都对应一个经纬度位置。在瓦片地图服务中,不同的zoom level下,像素坐标系与地理坐标系之间的对应关系是不同的。

  • 投影坐标系,其定义在上面已经给出,既然经纬度能够在二维平面进行表示,那么,就可以在二维平面上对像素坐标系与投影坐标系之间进行映射,这就引出了投影坐标系的一个重要作用:将地理坐标系和屏幕坐标系关联起来。所以,理解投影坐标系是GIS研究中的一个重要环节。

(2)投影方法与分类

由于地球是一个赤道略宽两级略扁的不规则的梨形球体,故其表面是一个不可展平的曲面,所以运用任何数学方法进行这种转换都会产生误差和变形,为了在不同需求下减小误差,就产生了各种投影方法。

图6:地图投影示意及分类

如上图所示,根据投影方法的不同,就形成了不同的投影类型,常见的投影类型有:高斯-克吕格投影、斜轴等面积方位投影、双标准纬线等角圆锥投影、等差分纬线等角圆锥投影、等差分纬线多圆锥投影、正轴方位投影、墨卡托投影等。

(3)墨卡托投影

在这诸多地图投影方法中,值得强调的是墨卡托投影。墨卡托投影,是一种正轴等角圆柱投影。由荷兰地图学家墨卡托(G.Mercator)于1569年创立。假想一个与地轴方向一致的圆柱切或割于地球,按等角条件,将经纬网投影到圆柱面上,将圆柱面展为平面后,即为墨卡托投影。墨卡托投影没有角度变形,由每一点向各方向的长度比相等,它的经纬线都是平行直线,且相交成直角,经线间隔相等,纬线间隔从标准纬线向两极逐渐增大。墨卡托投影的地图上长度和面积变形明显,但标准纬线无变形,从标准纬线向两极变形逐渐增大,但因为它具有各个方向均等扩大的特性,保持了方向和相互位置关系的正确。在地图上保持方向和角度的正确是墨卡托投影的优点,墨卡托投影地图常用作航海图和航空图,如果循着墨卡托投影图上两点间的直线航行,方向不变可以一直到达目的地,因此它对船舰在航行中定位、确定航向都具有有利条件,给航海者带来很大方便。

图7:墨卡托投影示意图

目前国内做数字城市方面的GIS项目、产品和公众应用,常涉及的投影方式主要有:面向局部区域的二维平面高斯投影(横轴墨卡托,横轴圆柱投影)、面向大范围(如全省、全国)的兰伯特投影(圆锥投影)、面向大范围的经纬度等间隔直投,而互联网上的大部分地图网站(百度地图、Google Maps)则是另外一种——“Web墨卡托”。Web墨卡托是墨卡托投影的变体,较接近于最原始的墨卡托,即正轴墨卡托(投影圆柱的轴心与地球自转轴重合)。由于 Web 墨卡托投影指定了在 WGS 84 椭球面模型上给出的测量坐标,但在投影的时候定义在球面上的,因此会有偏离,但在小比例尺民用场景精度可忽略不计,它主要的优点还是计算简单,数学运算复杂度低。

图8:Web墨卡托投影示意图

因为将极点投影在无穷远处,所以使用 Web墨卡托投影的地图无法显示极点,数据覆盖范围在经度 (-180°~180°),纬度 (-85.051129°~85.051129°)之间,投影的地图也是正方形。如果在移动端展示一张Web墨卡托投影的地图,通常是将地图中心点固定在移动设备屏幕的中心点,那么效果如下:

图9:手机与地图的位置关系

3.1.2 地图配图

对数据进行投影后,还需要根据数据类型、比例尺级别对数据进行配置、分层级配图。不同的数据类型配置不同的样式,比如不同区域填充不同颜色,铁路显示为黑白相间的线段等;在不同的比例尺级别下,显示的内容详略也会不同,比如在zoom level小于10的时候仅显示省道、国道,大于15的时候显示城市道路等。数据的配图相对比较简单,但是需要关注的细节较多,诸如ArcGIS、QGIS等专业的桌面软件提供了丰富的工具。

3.2 地图切片及瓦片定位

同前文一样,这里以Google地图的TMS规范为例(若无特别说明,本文举例均使用Google规范),梳理地图切片的流程及原理。上一节中,我们对地图投影做了一个大概的了解,根据投影的作用,可以知道想要将经纬度坐标转换为像素坐标,大致过程是:经纬度=>米(英尺)=>像素坐标。在TMS中,我们需要将经纬度坐标转换成瓦片坐标,Web墨卡托投影的转换流程如下图所示。

图10:手机与地图的位置关系

下面我们以Google瓦片规范简单描述一下Web墨卡托投影的算法原理。当缩放等级zoom level等于0时,Web墨卡托投影就是球面投影到一个正方形的平面上,这个平面就是将世界坐标调整为左上角为(0,0),右下角为(256,256)的正方形,假设基本单位为像素,那么就等于把地图投影在一个256pixel*256pixel的图幅上:

x = \lfloor \frac{256}{2\pi} * 2^{zoom\_level}(lng + \pi) \rfloor * pixels
\\
y = \lfloor \frac{256}{2\pi} * 2^{zoom\_level}(\pi - \ln[\tan(\frac{\pi}{4} + \frac{lat}{2})]) * pixels

其中x、y是像素坐标,lng、lat是经纬度,pixels是像素,zoom_level是地图瓦片比例尺层级。那么,经纬度坐标转换为瓦片坐标其推导过程为 经纬度 => 米 => 像素坐标 => 瓦片坐标。

3.2.1 地理坐标=>投影坐标(经纬度=>米

X_{proj} = lng * \frac{2 * \pi * R}{2.0}
\\
Y_{proj} = R * log(tan(\frac{(90+lat)*\pi}{360}))
# 地理坐标投影
def LatLongToMeterXY(lng, lat) {# 地球的周长的一半 20037508.342789244 单位米circumferenceHalf = math.pi * 2 * 6378137 / 2.0meterX = lng * circumferenceHalf / 180temp = math.log(math.tan((90 + lat) * (math.pi / 360.0))) / (math.pi / 180.0)meterY = circumferenceHalf * temp / 180return meterX,meterY
# [-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]

3.2.2 投影坐标=>像素坐标(米=>像素)

X_{pixel} = \frac{(X_{proj}+\pi*R)*tile_size*z^{zoom}}{2*\pi*R}
\\
Y_{pixel} = \frac{(Y_{proj}+\pi*R)*tile_size*z^{zoom}}{2*\pi*R}
# 投影坐标转像素坐标
def metersToPixelXY(meterX, meterY, zoom, tileSize=256) {# 地球的周长的一半 20037508.342789244 单位米circumferenceHalf = math.pi * 2 * 6378137 / 2.0# 米/每像素resolution = math.pi * 2 * 6378137 / (tileSize * math.pow(2, zoom))pixelX = (meterX + circumferenceHalf) / resolutionpixelY = (meterY + circumferenceHalf) / resolutionreturn pixelX,pixelY

3.2.3 地理坐标=>像素坐标(经纬度=>像素)

X_{pixel} = \frac{lng+180}{360}*2^z*256 \mod 256
\\
Y_{pixel} = (1-\frac{\ln(\tan(\frac{lat*\pi}{180}) + \sec(\frac{lat * \pi}{180}))}{2* \pi} *2^z*256\mod256
# 经纬度转像素
def lnglatToPixel(lng,lat,zoom):pixelX=round((lng+180)/360*math.pow(2,zoom)*256%256)pixelY=round((1-math.log(math.tan(math.radians(lat))+1/math.cos(math.radians(lat)))/(2*math.pi))*math.pow(2,zoom)*256%256)return pixelX,pixelY

3.2.4 像素坐标 =>瓦片坐标(像素=>瓦片)

X_{tile} = \lfloor \frac{X_{pixel}}{tile\_size} \rfloor
\\
Y_{tile} = \lfloor \frac{Y_{pixel}}{tile\_size} \rfloor
def pixelXYToTileXY(pixelX, pixelY, tileSize=256) {tileX = Math.floor(pixelX / tileSize)tileY = Math.floor(pixelY / tileSize)return tileX,tileY

3.2.5 地理坐标<=>瓦片坐标(经纬度<=>瓦片)

(1)地理坐标转瓦片坐标
X_{pixel} = \frac{lng+180}{360}*2^z*256 \mod 256
\\
Y_{pixel} = (1-\frac{\ln(\tan(\frac{lat*\pi}{180}) + \sec(\frac{lat * \pi}{180}))}{2* \pi} *2^z*256\mod256
# 经纬度转瓦片
def lnglatToTile(lng,lat,zoom):tileX=int((lng+180)/360*math.pow(2,zoom))tileY=int((1-math.asinh(math.tan(math.radians(lat)))/math.pi)*math.pow(2,zoom-1))return tileX,tileY
(2)瓦片坐标转地理坐标
lng = \frac{X_{tile}}{2^z}*360-180
\\
lat = arctan(sinh(\pi*(1-\frac{2*Y_{tile}}{2^z})))*\frac{180}{\pi}
# 瓦片转经纬度
def tileToLnglat(tileX,tileY,zoom):lng=tileX/math.pow(2,zoom)*360-180lat=math.degrees(math.atan(math.sinh(math.pi*(1-2*tileY/math.pow(2,zoom)))))return lng,lat
(3)瓦片坐标的像素坐标转地理坐标
lng = \frac{X_{tile}+\frac{X_{pixel}}{256}}{2^z}*360-180
\\
lat = arctan(sinh(\pi*(1-\frac{2*(Y_{tile}+{\frac{Y_{pixel}}{256}})}{2^z})))*\frac{180}{\pi}
# 瓦片坐标的像素坐标转经纬度
def pixelToLnglat(tileX,tileY,pixelX,pixelY,level):lng=(tileX+pixelX/256)/math.pow(2,level)*360-180lat=math.degrees(math.atan(math.sinh(math.pi-2*math.pi*(tileY+pixelY/256)/math.pow(2,level))))return lng,lat

3.3 瓦片分发

瓦片是为了优化数据传输和提升渲染性能。切片后得到的瓦片以金字塔索引形式保存,一般是每个缩放等级为一个独立的目录,其中每列为一个子目录,该子目录中存放该列所有的瓦片文件,故每个瓦片的路径可表示为:缩放等级+列号+行号,例如z/x/y.png的形式。另外,鉴于服务并发考虑,瓦片服务器会提供多个子域以应对大流量问题。比如OSM地图服务:

  • A 子域名:https://a.tile.openstreetmap.org/12/3371/1551.png

  • B 子域名:https://b.tile.openstreetmap.org/12/3371/1551.png

  • C 子域名:https://c.tile.openstreetmap.org/12/3371/1551.png

图11:OSM瓦片示例

通过上面的URL以及瓦片路径结构,可以看出上图中的瓦片来自于缩放等级为12,第3771列、第1551行。可以概括的看这个URL结构为:

https://{sub_domain}.tile.openstreetmap.org/{z}/{x}/{y}.png

那么,瓦片的分发其实就是把这些瓦片文件通过静态文件服务器或者CDN以诸如这种形式发布出去,客户端就可以获取这些瓦片,并通过相应的目录结构拼接渲染完整的地图。

3.4 瓦片地图服务解析

瓦片地图的展示,简单的讲,就是网格平铺,并根据瓦片序号装载对应瓦片的过程。整个流程可以简单概括为:获取目标地图的地理范围和缩放等级,计算瓦片坐标;通过服务地址、瓦片坐标,获取瓦片数据;拼接渲染;

3.4.1 瓦片地图服务解析流程

为了更好的理解瓦片地图服务的解析流程,下面以L7栅格与矢量瓦片服务的解析设计流程为例,理解客户端是如何获取瓦片并渲染成全部地图的。

图12:L7栅格与矢量瓦片服务的解析流程

3.4.2 leaflet中实现瓦片的渲染

下面在leaflet中对实现上述逻辑,以便更清晰的理解整个渲染流程。第一步,我们需要一个DIV容器来装载地图,设定中心坐标和缩放等级,并根据缩放等级、中心坐标、DIV容器的尺寸计算地理空间覆盖范围,进而计算出需要加载多少瓦片以及加载哪些瓦片。然后,在这个DIV中创建一些与瓦片尺寸一致的DOM,根据像素坐标与地理坐标的对应关系,计算出DOM与瓦片的关系,拼接完整的URL获取瓦片,并将瓦片图像加载到DOM中。最后,通过DOM将瓦片拼接进行展示。

<html>
<head><title>瓦片原理-OSM</title><meta charset="utf-8" /><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" /><script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script><style>body {padding: 0;margin: 0;}html, body, #map {height: 100%;width: 100%;}</style>
</head>
<body><div id="map"></div>
</body>
<script>var map = new L.Map('map', {  center: [40, 116], zoom: 17});var tiles = new L.GridLayer();tiles.createTile = function(coords) {console.log('coords:');console.log(coords);// 创建DOM装载瓦片var tile = L.DomUtil.create('canvas', 'leaflet-tile');var ctx = tile.getContext('2d');// 获取瓦片尺寸var size = this.getTileSize();console.log('size:');console.log(size);tile.width = size.x;tile.height = size.y;// 计算瓦片左上角的像素坐标var nwPoint = coords.scaleBy(size);// 计算瓦片左上角的地理坐标var nw = map.unproject(nwPoint, coords.z);// 计算瓦片右下角的像素坐标seCoords = new L.Point(coords.x+1,coords.y+1);seCoords.z = coords.z;console.log('seCoords:');console.log(seCoords);// 计算瓦片右下角的地理坐标var sePoint = seCoords.scaleBy(size);var se = map.unproject(sePoint, coords.z);// 加载瓦片图像var img = new Image();var subdomains = ['a', 'b', 'c'];var index = Math.abs(coords.x + coords.y) % subdomains.length;img.src = 'http://'+subdomains[index]+'.tile.openstreetmap.org/'+coords.z+'/'+coords.x+'/'+coords.y+'.png';console.log(img);ctx.drawImage(img,0,0,size.x,size.y);console.log(ctx); // 标识对应的瓦片信息// 背景颜色ctx.fillStyle = 'white';// 背景尺寸ctx.fillRect(0, 0, size.x, 90);// 字体颜色ctx.fillStyle = 'black';ctx.fillText('x: ' + coords.x + ', y: ' + coords.y + ', zoom level: ' + coords.z, 20, 20);ctx.fillText('minlng: ' + nw.lng + ', minlat: ' + se.lat, 20, 40);ctx.fillText('maxlng: ' + se.lng + ', matlat: ' + nw.lat, 20, 60);var dlng = se.lng - nw.lng;var dlat = nw.lat - se.lat;ctx.fillText('dlng: ' + dlng + ', dlat: ' + dlat, 20, 80);ctx.strokeStyle = 'red';ctx.beginPath();ctx.moveTo(0, 0);ctx.lineTo(size.x-1, 0);ctx.lineTo(size.x-1, size.y-1);ctx.lineTo(0, size.y-1);ctx.closePath();ctx.stroke();return tile;};// 加载OSM瓦片代码// L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {//    attribution: 'Map data &copy; <a href="http://www.osm.org">OpenStreetMap</a>'// }).addTo(map);tiles.addTo(map);
</script>
</html>

四、总结

通过对瓦片服务以及瓦片的原理有了一个大致的理解,在实际应用中就更加得心应手,碰到疑难杂症或者某些需要优化的点的时候,也就有了切入口,甚至是会有更深的见解,知其然,也知其所以然。当然,纸上得来终觉浅,绝知此事要躬行,理论必须付诸实践才能发挥出其深层次的意义。

参考文献

[1]https://baike.baidu.com/item/%E5%A2%A8%E5%8D%A1%E8%A8%97%E6%8A%95%E5%BD%B1/5477927
[2]https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
[3]https://www.yuque.com/antv/l7/up4re5
[4]http://webgis.cn/standard-wmts.html
[5]https://github.com/antvis/L7/tree/master/packages/utils/src/tileset-manager

瓦片地图服务与地图瓦片原理相关推荐

  1. 《水经注地图服务》地图服务与通用设置说明

    (本文首发于"水经注GIS"公号,关注公号免费领取地图数据) <水经注地图服务>(WeServer)是一款可快速发布全国乃至全球海量卫星影像的地图发布服务产品,该产品完 ...

  2. 一站式地图服务平台“地图易”——平台架构解析

    地图易是一站式地图服务平台,作为整体解决方案,具有分析插件丰富.大数据分析和展示性能好.实施快速.开发简单等特点. 图 '地图易'平台架构 '地图易'平台分为用户.平台.数据和基础设施等部分: '地图 ...

  3. 一站式地图服务平台“地图易”数据——基础地理矢量数据

    地图易是一站式地图服务平台,'地图易'平台分为用户.平台.数据和基础设施等部分,其中数据是其中非常重要的组成部分. 基础地理信息库主要包含基础地理适量数据.遥感影像数据和三维数据,是地图可视化展示.构 ...

  4. iOS LBS地图服务--高德地图使用大全

    项目github 地址 效果图 天气预报 仿摩拜首页 关键字检索 自定义小蓝点 自定义标记点 持续定位 绘制折线面 海量图层 1.项目github 地址 高德地图服务集成开发 2.效果图 天气预报 仿 ...

  5. nginx代理地图服务--离线部署地图服务(地图数据篇.4)

    听老人家说:多看美女会长寿 地图之家总目录(建议先查看该内容) 推荐使用Nginx进行发布,也可用tomcat进行发布. 经过之前写过的文章,介绍了如何对天地图,百度地图,高德地图,google地图的 ...

  6. Web地图服务规范(WMS、WMTS、TMS)简析

    Web地图服务规范(WMS.WMTS.TMS) 1.概况 Web地图服务规范包括WMS(网络地图服务).WMTS(网络地图瓦片服务).TMS(瓦片地图服务)等.WMTS服务和WMS服务都是由开发地理信 ...

  7. iclient加载标准规范的矢量瓦片mvt服务

    作者:yyy 前言 MVT全称是Mapbox Vector Tile,是Mapbox标准的矢量切片.SuperMap iClient产品很好的支持了MVT矢量瓦片在Web端的加载,但在平常的使用中基本 ...

  8. 使用超图桌面版制作点图层并发布为地图服务

    现在要用超图桌面版本制作点图层:并发布为地图服务: 进入超图桌面版本:新建文件型数据源: 保存文件型数据源,后缀名如图: 在数据源下新建数据集: 新建数据集对话框:选择创建类型为点:输入数据集名称: ...

  9. WGS 1984 Web Mercator 对于在线地图服务的意义

       WGS 1984是3S应用的常用大地坐标系之一,和我们的生活息息相关,最典型的应用莫过于手持设备的GPS模块,无论是美国GPS全球卫星定位系统.俄罗斯 GLONASS.欧盟GALILEO,还是中 ...

最新文章

  1. c++ lambda函数_C++ Lambda表达式
  2. 软件开发管理规范流程图
  3. 数学的记号(notation)
  4. ubuntu linux桌面快捷方式,Ubuntu下生成桌面快捷方式
  5. RAW socket使用
  6. JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
  7. 操作系统面试常问问题
  8. vc如何画圆,并填充颜色
  9. 高程(DEM) ASCII数据获取
  10. 热传导方程的差分解法c语言,九热传导方程的差分解法.PPT
  11. c语言挖地雷游戏,c扫雷小游戏
  12. 移动APP开发的三种常见模式
  13. Linux OBS虚拟摄像头 解决方案
  14. 无人机编队飞行技术 pdf_无人机教师李刚:无人机飞行中最重要的六个要点
  15. 自媒体新手经常犯,自媒体平台发布作品没收益?分析了这5个原因
  16. 测试用例设计——WEB通用测试用例(转)
  17. 思考“手绘家谱”的算法
  18. 在未提供官方驱动的Windows平板上安装Win10且完美驱动的解决方案
  19. MinGW-w64 C/C++编译器各版本说明
  20. BOOL和Boolean的区别

热门文章

  1. “一起吧”低调上线,百度社交还有机会吗?
  2. 将群晖NAS变为本地盘
  3. Win10+Debian11双系统的配置小记
  4. 微信小程序人工智能之添加学生信息
  5. 清华申请退学博士作品:完全用Linux工作
  6. 从游戏智能到疾病诊断,腾讯「绝悟」AI 从虚拟走向现实
  7. 售价五万,4.6升/百公里,它们是国内油耗最低的车
  8. 架设网页游戏服务器,如何架设网页游戏服务器
  9. STM32串口通信中的USART_RecieveData函数分析
  10. 7-1 顺序表的建立及遍历