You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python PIL经纬度转地图像素点绘制图标问题求助

经纬度转像素坐标定位偏移问题排查与修复

问题背景

将经纬度坐标转换为像素坐标,使用PIL库在地图图片上粘贴自定义图标,采用地图中心点相对绘制法,但图标与预期标记偏差过大。

现有条件

  • 地图图片:宽度2800,高度2600
  • 缩放级别:16.045383428216734
  • 地图中心点:[149.7985724, -35.4420469]
  • 待绘制点:[(149.79889, -35.44060), (149.79772, -35.44349)]

当前问题

生成的图片中橙色框图标与预期圆形标记偏差过大(已知滚动条会导致少量偏移,但当前偏差远超预期)。

原始代码

import math
from math import *
from math import pi, atan, sinh, log, tan, cos

import pandas as pd
import geopandas as gpd

from PIL import Image, ImageOps, ImageChops, ImageDraw

def getDistance(y,x,lat,lng):
    p1 = (float(lat), float(lng))
    p2 = (float(y),float(x))
    distance = round(geodesic(p1, p2).meters,0)
    return distance

mapboxZoom = 16.045383428216734
snagDpi = 4
mapWidth = 700
mapHeight = 650
centroid = [149.7985724, -35.4420469]

schDfDict = {'lng': {1: 149.79889, 2: 149.7977248}, 'lat': {1: -35.4406, 2: -35.4434938}}
schDf = pd.DataFrame(schDfDict)
schDf = gpd.GeoDataFrame(schDf, geometry=[Point(xy) for xy in zip(schDf['lng'].astype(float), schDf['lat'].astype(float))])

im = Image.open(io.BytesIO(base64.b64decode(img)))

metersPerPx = 156543.03392 * cos(centroid[1] * pi / 180) / 2.0 ** mapboxZoom
mapWidthKm = metersPerPx * mapWidth
mapHeightKm = metersPerPx * mapHeight

## multiply distances by snagDpi
snagMetersPerPx = metersPerPx / snagDpi

centroidPx = ((mapWidth * snagDpi) / 2, (mapHeight * snagDpi) / 2)

foreground = Image.open("C:\\Users\\ronan\\Downloads\\testIcon.png")

for index, row in schDf.reset_index().iterrows():

    prop = schDf.iloc[[index]]
    propX = prop.geometry.x.values[0]
    propY = prop.geometry.y.values[0]

    angle = degrees(atan((propX - centroid[0]) / (propY - centroid[1])))

    mDist = getDistance(centroid[1],centroid[0],propY,propX)

    pxDist = mDist / snagMetersPerPx

    ## radian
    alpha = angle * pi / 180

    xx = centroidPx[0] + (pxDist * cos(alpha))
    yy = centroidPx[1] + (pxDist * sin(alpha))

    im.paste(foreground, (round(xx),round(yy)), foreground)

问题排查与修复方案

1. 角度计算逻辑错误

当前用atan((propX - centroid[0]) / (propY - centroid[1]))计算角度,无法区分四个象限的方向,且未处理分母为0的极端情况,导致方向判断错误。

修复: 替换为atan2函数,自动处理象限问题:

# 替换原角度计算代码
dx = propX - centroid[0]
dy = propY - centroid[1]
angle = degrees(atan2(dy, dx))

2. 像素坐标系方向不匹配

PIL的图片坐标系Y轴向下递增,而地理纬度Y轴向上递增(纬度越大越靠北),导致Y方向偏移完全反向。

修复: 计算Y轴像素坐标时取反偏移量:

yy = centroidPx[1] - (pxDist * sin(alpha))

3. 地图尺寸变量与实际不符

代码中mapWidth=700、mapHeight=650与实际地图尺寸2800x2600不匹配,即使通过snagDpi缩放,也会引入不必要的计算误差。

修复: 直接使用实际地图尺寸:

mapWidth = 2800
mapHeight = 2600
snagDpi = 1  # 无需额外缩放

4. 缺失关键依赖导入

geodesic函数未导入,代码运行时会报错,同时Point类也未明确导入。

修复: 添加必要导入:

from geopy.distance import geodesic
from shapely.geometry import Point
import io
import base64

5. 图标锚点未对齐中心

im.paste默认以图标左上角为定位点,但预期标记通常是图标中心对齐目标位置,导致视觉偏移。

修复: 计算图标中心点偏移:

# 获取图标尺寸
icon_w, icon_h = foreground.size
# 调整坐标为图标中心位置
paste_x = round(xx - icon_w / 2)
paste_y = round(yy - icon_h / 2)

修复后的完整代码

import math
from math import pi, degrees, atan2, cos
from geopy.distance import geodesic
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
import io
import base64
from PIL import Image

def getDistance(y,x,lat,lng):
    p1 = (float(lat), float(lng))
    p2 = (float(y),float(x))
    distance = round(geodesic(p1, p2).meters, 0)
    return distance

mapboxZoom = 16.045383428216734
mapWidth = 2800
mapHeight = 2600
centroid = [149.7985724, -35.4420469]

schDfDict = {'lng': {1: 149.79889, 2: 149.7977248}, 'lat': {1: -35.4406, 2: -35.4434938}}
schDf = pd.DataFrame(schDfDict)
schDf = gpd.GeoDataFrame(schDf, geometry=[Point(xy) for xy in zip(schDf['lng'].astype(float), schDf['lat'].astype(float))])

im = Image.open(io.BytesIO(base64.b64decode(img)))

metersPerPx = 156543.03392 * cos(centroid[1] * pi / 180) / (2.0 ** mapboxZoom)
centroidPx = (mapWidth / 2, mapHeight / 2)

foreground = Image.open("C:\\Users\\ronan\\Downloads\\testIcon.png")
icon_w, icon_h = foreground.size

for _, row in schDf.iterrows():
    propX = row.geometry.x
    propY = row.geometry.y

    dx = propX - centroid[0]
    dy = propY - centroid[1]
    angle = degrees(atan2(dy, dx))
    alpha = math.radians(angle)

    mDist = getDistance(centroid[1], centroid[0], propY, propX)
    pxDist = mDist / metersPerPx

    xx = centroidPx[0] + (pxDist * cos(alpha))
    yy = centroidPx[1] - (pxDist * sin(alpha))

    paste_x = round(xx - icon_w / 2)
    paste_y = round(yy - icon_h / 2)

    im.paste(foreground, (paste_x, paste_y), foreground)

im.save("result_map.png")

内容的提问来源于stack exchange,提问作者ronan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 14:18:16