简介:SGGS-Google Earth拼图工具是一款面向GIS从业者、地理学者及城市规划人员的卫星图像拼接应用,主要用于解决Google Earth分块卫星图不便连续查看的问题,适合需要对大范围地表进行无缝制图与精细分析的场景。资源包整体仅3.4MB,共20个文件,其中13个DLL动态库承担图像处理与核心运行支持,2个EXE程序负责工具执行,INI与TDB文件保存配置和任务信息,并附有使用说明网页,构成完整工具链。已有1269人学习下载。借助该工具可自动抓取指定区域卫星图块、对齐融合并消除接缝,导出为单张大图以便在GIS软件中继续使用;包内还附有详细说明与帮助链接,方便上手设置分辨率与存储参数。对需要高精度、连续区域地图的用户来说,这是一份轻量实用的GIS辅助工具包。
1. 大范围地图导出不是截图:切片-拼接才是正路
半年前接到一个需求:给某工业园区三平方公里范围出一张 1 米/像素的卫星底图。当时手里的方案有两个,要么在线地图,要么本机已经装好的 Google Earth;一圈试下来,还是得自己拼。真正上手才发现,Google Earth 单次保存图像的大小和清晰度都有上限,想覆盖一个大范围,你得先按经纬度把区域切成几十块,再把每块按地理位置“拼回去”。这也是 SGGS 这套拼图脚本的核心逻辑:规划网格、批量导出、坐标对齐、羽化拼接。适合整天对着 GIS 底图发愁的规划、测绘、工程前期人员,以及想知道“拼图软件背后到底在算什么”的开发者。
2. 原理与选型:为什么谷歌地球要切片再拼而不是截图硬拼
2.1 直接截图的三个翻车现场
很多第一次用 Google Earth 做底图的人会想:用屏幕截图软件,把地图滚动一遍截下来,然后交给拼图工具自动拼接。我见过 A 同学这么干,结果连续翻车。
翻车现场一:道路断头。滚动地图时,屏幕边缘的画面随着视点移动而变形,两帧之间共享的区域并不是严格的平移关系。拼图软件按“像素重合”搜索两张图,找到的是近似匹配,接缝处道路错开、房屋出现双影。这种像素级拼接对地图这类强纹理图像特别不友好。
翻车现场二:比例失衡。Google Earth 默认是带透视的三维视角,视点稍微倾斜,远处的房子比近处的小。截屏得到的画面边缘有梯形变形,两端的比例尺都不一样,直接拼出来是扭曲的,且没有地理坐标信息用来纠正。
翻车现场三:注记错位。地图上的文字和标记叠加在影像上,滚动刷新后注记重排,同一栋楼在不同块里的文字位置完全不同,拼接后像打了补丁。
SGGS 的做法本质上规避了这三类问题:它的每一块切片都是在固定的正上方视角、固定的视口大小、固定的比例尺下导出的;相邻块之间必须有精确的地理边界,块与块通过经纬度对齐,而不是通过图像特征对齐。所以,理解选型的前提是理解 Google Earth 的视口机制。
2.2 视口、分辨率和单次导出的天花板
Google Earth 视图里,你看到的矩形区域由视口像素和视角决定。默认水平视场角约 60°,在正北、垂直视角下,视口中心的地面宽度和“视点高度”近似成正比。我在多次标定后总结出一个经验公式:
地面宽度(m) ≈ 2 * 视点高度(m) * tan(水平视场角 / 2)当视场角为 60° 时,tan(30°) 约 0.577,所以地面宽度大约是视点高度的 1.15 倍。这个公式不用背,但在标定“每像素对应多少米”时,比瞎调省时间。
再看单次导出上限。通过保存图像功能导出的最大分辨率不是由屏幕决定,而是由当前视口对应的图层清晰度决定;实际操作中,保存对话框里的最高挡位会把图片放大到视口宽度的 4 倍左右。以一块 1080P 的视口来说,最大约 3840 像素宽;超过这个值,导出的图片会变得模糊,因为原生瓦片已经放大到头了。
所以我在 SGGS 工具包里定了一条不成文规则:单块切片宽度控制在 3000~3600 像素,按目标地面分辨率算下来一块覆盖 3~4 公里左右。又清晰,又不会把后续拼接的内存撑爆。视点高度这块有点玄学,不同版本对参数的解释不完全一样,我一般会先飞到一个空旷场地,按公式算一次,再手动对比边界,标定好再跑批量。
2.3 切片-拼接的总流程
既然单次导出有限制,大区域就一定得“化整为零,再化零为整”。SGGS 的整体流程分为五步:
- 确定目标范围(最小/最大经纬度)、目标分辨率、重叠率;
- 按分辨率反推单块地面尺寸,生成经纬度网格;
- 在 Google Earth 里逐个定位到网格中心,把视口摆正(北向上、垂直向下),导出图片;
- 记录每张图片对应的四个角经纬度,存成清单;
- 用清单里的角点坐标把所有切片统一投影到同一坐标系下,按位置合成。
后面三章就按这三步展开。这个流程有一个好处:每个环节都是可独立验证的。第 3 步导出后,你可以先只导出左上角一块,在 Google Earth 里放大看看有没有变形,再决定要不要铺全量。
3. 切片清单生成:经纬度网格与重叠率计算
3.1 网格参数计算:目标分辨率怎么变成步距
先定义输入参数。假设目标范围经度跨度 lon_span(度)、纬度跨度 lat_span(度),目标分辨率 gsd(米/像素),重叠率 overlap(0~0.3)。单块导出的像素宽 block_pix 取 3000~3600。
关键在于把“米”换算成“度”。在地球表面,经度方向每度对应的地面距离随纬度变化:赤道约 111320 米/度,到北纬 60° 会缩短一半。纬度方向相对稳定,约 110540 米/度。所以在某个纬度上:
m_per_deg_lon = 111320 * cos(lat_rad) m_per_deg_lat = 110540单块经度跨度 = 单块宽度(米) / m_per_deg_lon;单块纬度跨度 = 单块高度(米) / m_per_deg_lat。再乘上 (1 + overlap),保证块与块之间至少有重叠,防止边缘影像抖动时出现缝隙。
下面这段 Python 直接算出行数和列数:
import math def calc_grid(lon_min, lat_min, lon_max, lat_max, gsd=1.0, overlap=0.2, block_pix=3200): # 以区域中心纬度为准计算经度方向的米/度,避免边角误差过大 lat_c = (lat_min + lat_max) / 2.0 m_per_deg_lon = 111320.0 * math.cos(math.radians(lat_c)) m_per_deg_lat = 110540.0 block_w_m = block_pix * gsd # 单块对应的地面宽 block_h_m = block_pix * gsd # 单块对应的地面高 step_lon = block_w_m / m_per_deg_lon * (1 + overlap) step_lat = block_h_m / m_per_deg_lat * (1 + overlap) cols = math.ceil((lon_max - lon_min) / step_lon) rows = math.ceil((lat_max - lat_min) / step_lat) return rows, cols, step_lon, step_lat逻辑说明:先把经纬度范围转成了行列数。中心纬度上算经度步距,是因为整个区域可能横跨 0.1° 以上,用边缘纬度算,步距偏大或偏小会造成边界不齐。
参数说明:block_pix 我建议取 3000~3600。取 4000 以上时,Google Earth 导出图片时间变长,内存占用也明显增加,而且瓦片清晰度往往支撑不住,不划算。overlap=0.2 指的是“每块向外扩 20%”,所以实际单块覆盖的地面比网格步距大,后续拼接时重叠区就有回旋余地。
3.2 生成切片清单:CSV 与 KML 双输出
光有行列数还不够,每块的中心经纬度和文件名要有标准格式。SGGS 的脚本会生成一张 CSV 清单,每行记录序号、左上经纬度、右下经纬度、中心经纬度和文件名。文件名用行号_列号补齐三位,避免 10 号排到 2 号前面。
def gen_tile_list(lon_min, lat_min, lon_max, lat_max, gsd=1.0, overlap=0.2, block_pix=3200): rows, cols, step_lon, step_lat = calc_grid( lon_min, lat_min, lon_max, lat_max, gsd, overlap, block_pix) tiles = [] for r in range(rows): lat_top = lat_min + r * step_lat # 本块上边缘纬度 lat_bottom = lat_top - (block_pix * gsd) / 110540.0 for c in range(cols): lon_left = lon_min + c * step_lon lon_right = lon_left + (block_pix * gsd) / \ (111320.0 * math.cos(math.radians(lat_top))) name = f"{r:03d}_{c:03d}" tiles.append({ "name": name, "lon_min": lon_left, "lat_min": lat_bottom, "lon_max": lon_right, "lat_max": lat_top, "lon_c": (lon_left + lon_right) / 2, "lat_c": (lat_top + lat_bottom) / 2, }) return tiles逻辑说明:注意这里没有直接用 step_lon 作为这块的右边界,而是重新算了一遍单块的经度跨度。网格步距带重叠率,是为了让相邻两块的边缘重叠;而单块的导出范围就是它自己的清晰边界,不能用带重叠的步距值,否则整张图会被拉宽。
参数说明:lat_top 取了本块上边缘纬度用于计算经度跨度,这是为了保证同一行内每块经度方向的地面宽度一致;代价是同一行内各块纬度跨度略有差异,但对 3000 像素级别的影响可以忽略。
这些 tiles 除了写 CSV,还可以生成 KML 地标文件。在 Google Earth 里导入 KML 后,每个地标就是一个多边形,双击就能飞到对应位置,导出图片时省去手输经纬度。
def write_kml(tiles, path): with open(path, "w", encoding="utf-8") as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write('<kml xmlns="http://www.opengis.net/kml/2.2">\n') f.write('<Document>\n') for t in tiles: f.write(f'<Placemark><name>{t["name"]}</name>\n') f.write('<Polygon><outerBoundaryIs><LinearRing><coordinates>\n') f.write(f'{t["lon_min"]},{t["lat_min"]},0\n') f.write(f'{t["lon_max"]},{t["lat_min"]},0\n') f.write(f'{t["lon_max"]},{t["lat_max"]},0\n') f.write(f'{t["lon_min"]},{t["lat_max"]},0\n') f.write(f'{t["lon_min"]},{t["lat_min"]},0\n') f.write('</coordinates></LinearRing></outerBoundaryIs></Polygon>\n') f.write('</Placemark>\n') f.write('</Document></kml>\n')逻辑说明:KML 的坐标顺序是经度,纬度,高度,注意别写反了,否则多边形会变成一条条乱线。
参数说明:这里每块输出的是一个闭合多边形上的 4 个点,没有做加密细化。如果你要在地图界面里看得更准,可以在每边中间再加几个内插点,但导出边界其实只需 4 个角点。
3.3 批量导出:自动化脚本与手工路线的取舍
切片清单生成后,最枯燥的是逐块导出。全自动化路线是 Windows 下通过 COM 调用 Google Earth 的接口,核心代码很短:
import time import win32com.client def export_tile(ge, lon_c, lat_c, height_m, out_path): # 定位到该块中心,视点高度按目标地面宽度换算 ge.SetCamera(latitude=lat_c, longitude=lon_c, altitude=height_m, altMode=1, azimuth=0, pitch=0.0, range=0.0) time.sleep(6) # 让影像瓦片和周边贴图加载完 ge.SaveImage(out_path, 0, 3200) # 0 表示保存当前视口,3200 为输出宽度逻辑说明:SetCamera 里的 pitch 必须传 0,表示垂直向下;azimuth 为 0 表示正北在上。很多教程里漏了 azimuth,导出后图片就不是正北朝上,后续拼接会歪。
参数说明:altMode=1 表示绝对海拔模式,altitude 传的是视点高度,单位是米;把视点高度设为单块地面宽度除以 1.15 左右(结合 60° 视场角),就能覆盖目标地面范围。time.sleep(6) 不能省,影像流式加载需要时间,保存早了图会花。
注意:COM 接口在不同版本的 Google Earth 里偶尔对参数名称不敏感,有的用 range,有的用 altitude。我一般会先打印一下版本号再跑全量脚本,宁可慢一点,不冒批量导出的风险。
如果 COM 不稳定或者机器上没装对应组件,就走半自动路线:把上一步生成的 KML 导入 Google Earth,在侧边栏按顺序双击地标找到位置,再按 Ctrl+Shift+E 手动保存。速度慢一半,但胜在稳。批量任务前,我会先用第一块和最后一块做试验,确认导出的文件名、位置和清晰度都符合预期。
4. 坐标对齐与拼接:从经纬度到像素的落地实现
4.1 为什么不能按文件名顺序直接拼图
切片导出完成后,目录里往往长这样:
000_000.jpg 000_001.jpg 000_002.jpg ...一眼看过去,按文件名组合起来就像是一张完整图。但直接拼一定错,原因有两点。第一,每一块是独立导出的,地图服务的瓦片加载时间不同,影像内容可能来自不同的拍摄日期,同一地块边缘的亮度、色调会有差异。第二,更重要的是,每一张 jpg 自身没有携带经纬度信息,只有清单里记录的角点坐标才是“地理锚”。如果图块在导出时因为视场内边距而稍微多出了一点边界,按文件名硬拼就会重叠或断裂。
所以拼接的第一步不是读图片,而是先把每张图的四个角经纬度换算到同一个平面坐标。工具包里统一用 Web 墨卡托(EPSG:3857),换算简洁,和 Google Earth 底图也保持同源。核心换算函数如下:
import math def lonlat_to_meters(lon, lat): # 球面墨卡托:经度线性,纬度用等角投影公式 x = lon * 20037508.34 / 180.0 y = math.log(math.tan((90.0 + lat) * math.pi / 360.0)) y = y * 20037508.34 / math.pi return x, y逻辑说明:第一行把经度范围线性映射到全球宽度;第二行里 tan((90+lat)π/360) 是标准墨卡托的纬度变换,它把高纬度的变形控制在一个可计算的范围内。
参数说明:20037508.34 是 EPSG:3857 定义下的半球宽度,不必深究,直接用即可。如果你要对接的坐标系不是墨卡托,换成当地的 UTM 投影函数也一样,关键是全流程统一。
4.2 用地理包围盒把切片放到画布上
拿到投影坐标后,拼接就变成了一个“把很多人图块放进一张大地图”的仿射变换问题。具体做法是先统计所有块在墨卡托投影下的最小/最大范围,然后建立整张画布(像素网格),再逐块把像素映射到画布的对应区域。
用 numpy 预分配一个“全图空画布”,再用 Pillow 逐块读取 jpg 并写入对应位置。核心代码如下:
import numpy as np from PIL import Image def stitch_all(tiles, gsd=1.0, block_pix=3200): # 1) 计算全局范围 xs_min, ys_min = lonlat_to_meters(tiles[0]["lon_min"], tiles[0]["lat_min"]) xs_max, ys_max = lonlat_to_meters(tiles[0]["lon_max"], tiles[0]["lat_max"]) for t in tiles: x0, y0 = lonlat_to_meters(t["lon_min"], t["lat_min"]) x1, y1 = lonlat_to_meters(t["lon_max"], t["lat_max"]) xs_min, ys_min = min(xs_min, x0), min(ys_min, y0) xs_max, ys_max = max(xs_max, x1), max(ys_max, y1) # 2) 根据目标分辨率计算画布尺寸 width_px = int((xs_max - xs_min) / gsd) + 1 height_px = int((ys_max - ys_min) / gsd) + 1 canvas = np.zeros((height_px, width_px, 3), dtype=np.uint8) mask = np.zeros((height_px, width_px), dtype=np.uint8) for t in tiles: x0, y0 = lonlat_to_meters(t["lon_min"], t["lat_min"]) x1, y1 = lonlat_to_meters(t["lon_max"], t["lat_max"]) # 地理坐标转画布像素坐标(注意 y 方向要翻转) px0 = int((x0 - xs_min) / gsd) py1 = int((ys_max - y0) / gsd) px1 = int((x1 - xs_min) / gsd) py0 = int((ys_max - y1) / gsd) img = Image.open(f'tiles/{t["name"]}.jpg') img = img.resize((px1 - px0, py1 - py0)) arr = np.array(img) canvas[py0:py1, px0:px1] = arr mask[py0:py1, px0:px1] = 255 return canvas, mask逻辑说明:第 1)步统计全局边界时,一定要遍历所有块,直接拿第 1 块当全局边界会导致画布右下大片空白。第 2)步里 y 方向用的翻转公式,是因为投影坐标 y 轴向北增大,而图像数组的行号向下增大,所以 y 取ys_max - y再换算。
参数说明:步骤里用简单 resize 把每块贴到目标区域;当导出时每块的像素尺寸已经被固定为 block_pix,而这里重新按像素宽度缩放一次,是为了消除视口微调带来的毫米级误差,代价是预先拉伸一次。对 1 米/像素这种精度,拉伸造成的损失远小于人工对位误差。
注意:如果目标范围很大,canvas 直接以 uint8 三通道存会导致内存爆炸。经常遇到 8 亿像素以上的画布,我一般会分段拼接:按行号分批合成,每批输出一个临时大图,最后再纵向拼合。
4.3 重叠区羽化:让接缝从“可见”变“不可见”
简单覆盖拼接在重叠区域会出现一条明显的“接缝线”,因为两块影像在同一边缘色调不一致,即使坐标对得很准,看起来也是断裂的。SGGS 在写画布时采用的是“后写中心,先写边缘”策略,再加上重叠区线性权重融合。
羽化权重函数不复杂,就是在每块图片的边缘生成一个从 0 到 1 渐变的 alpha:
def feather_mask(shape, feather_px=200): h, w = shape[:2] x = np.ones(w, dtype=np.float32) y = np.ones(h, dtype=np.float32) # 左/右边缘渐变 ramp = np.linspace(0, 1, feather_px, dtype=np.float32) x[:feather_px] = ramp x[-feather_px:] = ramp[::-1] # 上/下边缘渐变 ramp_y = np.linspace(0, 1, feather_px, dtype=np.float32) y[:feather_px] = ramp_y y[-feather_px:] = ramp_y[::-1] # 二维权重 = 横向 * 纵向 wgt = np.outer(y, x) return wgt[:, :, None]逻辑说明:这张权重图在中心区域接近 1,在边缘接近 0。合成时按“已存在像素=旧像素×权重+新像素×(1-权重)”的方式更新,这样重叠区自然过渡。
参数说明:feather_px=200 是经验值,对应 3000 像素宽切片,约合 0.2 公里的重叠宽度。如果你的重叠率是 0.2、切片宽度是 3000,正好边缘 600 像素都属于重叠区,羽化带取 200 既能盖住接缝,又不至于把中心区域拖糊。
如果两块的色调差异特别大,单纯羽化会看到一条“渐变的色带”。我的经验是先做全局亮度匹配:把每块与相邻块重叠区的平均亮度做比值,再整体乘上一个增益系数。这个校正可以放在 4.2 写画布之前做,比拼完后在整幅图里调色更干净。
5. 避坑与排查:五个高频问题与定位方法
5.1 缝隙:某一行整体缺了一条细条状黑缝
现象:拼接完成后,每隔 N 列就有一条黑线,位置刚好在行与行之间。放大看不是内容缺失,而是一整条像素没有被写入。
原因:网格步距计算时用了重叠率 0.2,但在导出时把整块的角点边界直接当成了“该地块范围”,导致相邻两个导出块之间的地面实际间隔大于 0。等于说网格规划了块 A 和块 B 有重叠,可导出时两块都只覆盖了各自的核心区,重叠区根本没被导出。
解决:检查 CSV 清单里的 lon_max 与下一块的 lon_min 差值。正常情况下一块的右边界应大于下一块的左边界,即存在正的重叠。若出现负值,把导出边界改为“中心经纬度 ± 单块地面跨度的一半”,而不是从网格步距推导。脚本里我用的是单独重算的 lon_right,如果你发现黑缝,优先看这一步是不是漏了。
5.2 重影:道路出现双线,房屋出现两个身影
现象:地图拼完后,重叠区域并没有出现“自然的融合”,反而道路和建筑的边缘出现了重影。
原因:两块影像来自不同的拍摄时间或不同的贴图等级。例如某块导出早,影像瓦片用的是上一年度某季度数据;另一块导出晚,瓦片已经更新到今年。两套影像在同一位置错开几米,属于地理真实现象,不是拼接错误。
解决:在 Google Earth 里把历史影像图层打开,统一到某个固定日期后再导出全部块;或者在导出前先手动选中一个已知路口,检查两块影像在同一点的坐标是否一致。如果差异只有 2 米以内,用羽化融合能盖住;超过 5 米就只能以其中一块为基准重新导出。
5.3 整幅图北向偏斜:所有块单体正常,合体后越往上越偏
现象:导出时每个块都设为 pitch=0,但拼完发现整块区域被“拉伸”成平行四边形,北侧地图向东偏移几十米。
原因:视点高度和地面覆盖比例都被标定为“中心视角”,但默认视场角不是严格的 60°,或者屏幕宽高比与 1:1 有差异,导致每块导出的地面范围不是代码里假设的正方形。KML 的边界画的是理想的经纬度矩形,而实际导出的图片覆盖范围是个梯形。
解决:不要完全信任视点高度换算的覆盖范围,而是在导出后的第一块图片里,找两个已知地物(比如两个路口),量一量它们的像素间距和经纬度差,倒推出该块真实的分辨率。用这个实测值去回填拼接代码里的 gsd 参数,再重新生成网格和拼接画布。我一般会在正式任务前专门做一次标定。
5.4 画布内存溢出或保存时卡死
现象:拼接脚本在运行到第 10 块时内存快速上涨,最后进程被系统杀掉。或者 Google Earth 导出一块 3000 像素 jpg 时程序无响应。
原因:全图画布以整型数组全量预分配,对 8 亿像素的 uint8 三通道,直接就占 2.4GB 内存,还不算 Pillow 读图的临时对象。Google Earth 无响应通常是因为等待影像加载时间太短,保存时贴图未到位,程序陷入重试。
解决:拼接侧把画布改为“按行分块”或“用磁盘缓存”,例如把每 100 行结果先写到一个临时 TIFF,最后再合并。导出侧把等待时间从 6 秒提到 15 秒,并限制同时运行的导出线程数不要超过 2 个。8GB 内存的机器跑 2 亿像素以上区域时,这两条缺一不可。
5.5 自动化导出中偶发“定位失败、图片重复”
现象:COM 批量导出脚本跑到某一块时,生成的 jpg 内容与前一块完全相同。程序没有报错,下一个块也正常。
原因:SetCamera 调用后,Google Earth 的地图载入是异步的。如果影像没有及时刷新,或上一次移动动画没有结束,执行保存时拿到的是上一帧画面。这类问题最容易出现在跨越较大高度变化的地块切换时。
解决:在 SetCamera 之后,显式读取当前位置的相机参数并确认与目标坐标一致,等待确认后再保存。代码里加一个简单校验:
cam = ge.GetCamera() if abs(cam.latitude - lat_c) > 0.0001 or abs(cam.longitude - lon_c) > 0.0001: time.sleep(3) cam = ge.GetCamera()如果重试三次仍不一致,把这一块记录到失败清单,不要继续往下跑。全量跑完后再统一补漏,比中途反复尝试省时间。
6. 进阶校验与技巧:把拼图结果做成一幅可用底图
6.1 特征点坐标校验
拼图完成后,先别急着交付。我会选三个明显的地物点(通常是路口中线或独立建筑角点),在 Google Earth 里读取它们的经纬度,然后换算到画布上,再与拼图里手动点选的位置比对。如果三个点的像素误差都控制在 5 个像素以内,这一版拼图就可以放心交给下游用。这个流程在 SGGS 里是一段校验脚本,输入三个经纬度,自动在画布上画出理论位置并计算误差。
6.2 输出 GeoTIFF 与坐标伴随文件
最后一步是把画布、分辨率、投影参数打包。能写 GeoTIFF 的环境用 GDAL 直接完成:
from osgeo import gdal, osr srs = osr.SpatialReference() srs.ImportFromEPSG(3857) ds = gdal.GetDriverByName("GTiff").Create("output.tif", width_px, height_px, 3, gdal.GDT_Byte) ds.SetGeoTransform([xs_min, gsd, 0, ys_max, 0, -gsd]) ds.SetProjection(srs.ExportToWkt()) for band_idx in range(3): ds.GetRasterBand(band_idx + 1).WriteArray(canvas[:, :, band_idx]) ds.FlushCache()参数说明:SetGeoTransform 里六个数字分别是左上角 x、x 方向像素宽、旋转参数 0、左上角 y、旋转参数 0、y 方向像素高(这里要取负值)。任何一项写错,后续 GIS 软件里图形都会翻转或偏移。如果没有 GDAL,至少把投影、四个角经纬度和分辨率写进一个同名 txt,方便后续配准。
某次交付前,我图省事没跑校验,结果甲方在 CAD 里叠图时发现整幅图整体向东偏了 15 米。查下来只是中心纬度 cos 值写错,经度跨度整体算小了。从那以后,我每次拼图都强制先做一小块试拼,再跑三个特征点校验,最后才铺全范围。希望帮到你。
本文还有配套的精品资源,点击获取