如何正确缩放给定坐标以适应视图的边界而不失真?
How to scale given coordinates correctly to fit into the boundaries of a View without distortion?
我有一个 [latitude/longitude] 对列表,我想将它们描述为连接线的路径绘制到我的自定义视图中,但比例正确。
目前我的解决方案是这样的:
找到纬度和经度的最小值和最大值,将每个值描述为 0 到 1 之间的数字,然后将它们乘以我的观点 height/width。
结果,我的路径总是被拉伸到我的视野范围内。
因此,例如,如果我从北向南步行 10000 米,那时只向西走 1 米,然后向东走 2 米,我的路径看起来像这样(向四面八方伸展):
---------------
| * |
| **** |
|** |
| * |
| *** |
| **** |
| ** |
| **|
---------------
但我希望它看起来像这样:
---------------
| * |
| * |
| * |
| ** |
| * |
| * |
| * |
| * |
---------------
因此,当我主要从北向南行走时,路线应该填满我的整个视野高度,并且应该相应地显示 x 位置。当我主要从西向东走时,它应该占据它的整个宽度。
我不知道如何正确缩放我的值,以便在我的视图中绘制它们。我希望你理解我的问题并能帮助我解决它。提前致谢! :)
我会定义最大纵横比。例如:
min_aspect_ratio = 5; // Minimum aspect ratio of 5:1
x_scale = screen_width/max_x_diff;
y_scale = screen_height/max_y_diff;
if(x_scale>y_scale)
if (x_scale/y_scale < min_aspect_ratio)
y_scale = x_scale/min_aspect_ratio;
else
if (y_scale/x_scale < min_aspect_ratio)
x_scale = y_scale/min_aspect_ratio;
免责声明:我没有测试此代码。
假设您有以下具有明确含义的变量:
screenWidth
、screenHeight
、minLon
、maxLon
、minLat
、maxLat
。
你想要的是 lon
和 lat
到屏幕坐标 x
和 y
的映射,以便填充宽度或高度,但圆仍然是圆(距离的水平和垂直比例相同)。
经纬度差的距离之比几乎正好是纬度的余弦值。 “几乎”只是因为地球不是一个完美的球体。余弦在纬度 0° 处为 1,因此,例如,在赤道,相同的经度和纬度差异对应于相同的距离。对于“足够”小区域的地图,可以使用平均纬度来计算比率。
如果只有水平约束,则必须使 x==0
对应于 minLon
,x==screenWidth-1
对应于 maxLon
。线性变换(即映射)为:
x = lon * lonScale + x0;
和
deltaLon = maxLon - minLon;
lonScale = (screenWidth - 1) / deltaLon;
x0 = - minLon * lonScale;
同样,如果您只有垂直约束:
y = y0 - lat * latScale; // i.e.: lat * (-latScale) + y0
和
deltaLat = maxLat - minLat;
latScale = (screenHeight - 1) / deltaLat;
y0 = maxLat * latScale;
(减号是因为屏幕坐标的原点在左上角。)
现在,如果 deltaLon == 0.0
或 deltaLat == 0.0
,您不想除以 0,只想将有意义的映射应用于您正在处理的直线。如果两者都是!= 0.0
,你要选择latScale
和lonScale
之间较小的一个,记住它们的比值是平均纬度的余弦:
lonScale == latScale * Math.cos((maxLat + minLat) / 2.0)
所以:
avgLat = (maxLat + minLat) / 2.0;
cosFactor = Math.cos(avgLat);
if (deltaLon != 0.0 && deltaLat != 0.0) {
if (lonScale > latScale * cosFactor) {
lonScale = latScale * cosFactor;
x0 = ((screenWidth - 1) - (minLon + maxLon) * lonScale) / 2.0;
} else {
latScale = lonScale / cosFactor;
y0 = ((screenHeight - 1) + (minLat + maxLat) * latScale) / 2.0;
}
}
其中 x0
和 y0
已通过求解将 (maxL* - minL*) / 2.0
置于屏幕中间的方程式进行了调整(*
为 on
或at
).
编辑
根据您的评论,您没有使用 真实 经度和纬度值,而只是使用东西和南北方向的距离。这有点像在赤道上,余弦因子变成1。我继续称输入坐标为lon
和lat
,但它们可以用任何长度单位表示,只要两者的单位相同。
所以计算变成:
deltaLon = maxLon - minLon;
deltaLat = maxLat - minLat;
if (deltaLon != 0.0 && deltaLat != 0.0) {
lonScale = (screenWidth - 1) / deltaLon;
latScale = (screenHeight - 1) / deltaLat;
x0 = - minLon * lonScale;
y0 = maxLat * latScale;
if (lonScale > latScale) {
lonScale = latScale;
x0 = ((screenWidth - 1) - (minLon + maxLon) * lonScale) / 2.0;
} else {
latScale = lonScale;
y0 = ((screenHeight - 1) + (minLat + maxLat) * latScale) / 2.0;
}
} else if (deltaLon != 0.0) {
lonScale = (screenWidth - 1) / deltaLon;
latScale = 0;
x0 = - minLon * lonScale;
y0 = (screenHeight - 1) / 2.0;
} else if (deltaLat != 0.0) {
lonScale = 0;
latScale = (screenHeight - 1) / deltaLat;
x0 = (screenWidth - 1) / 2.0;
y0 = maxLat * latScale;
} else {
lonScale = 0;
latScale = 0;
x0 = (screenHeight - 1) / 2.0;
y0 = (screenWidth - 1) / 2.0;
}
屏幕值的计算与一般情况完全相同,即:
x = x0 + lon * lonScale;
y = y0 - lat * latScale;
我有一个 [latitude/longitude] 对列表,我想将它们描述为连接线的路径绘制到我的自定义视图中,但比例正确。
目前我的解决方案是这样的: 找到纬度和经度的最小值和最大值,将每个值描述为 0 到 1 之间的数字,然后将它们乘以我的观点 height/width。 结果,我的路径总是被拉伸到我的视野范围内。
因此,例如,如果我从北向南步行 10000 米,那时只向西走 1 米,然后向东走 2 米,我的路径看起来像这样(向四面八方伸展):
---------------
| * |
| **** |
|** |
| * |
| *** |
| **** |
| ** |
| **|
---------------
但我希望它看起来像这样:
---------------
| * |
| * |
| * |
| ** |
| * |
| * |
| * |
| * |
---------------
因此,当我主要从北向南行走时,路线应该填满我的整个视野高度,并且应该相应地显示 x 位置。当我主要从西向东走时,它应该占据它的整个宽度。
我不知道如何正确缩放我的值,以便在我的视图中绘制它们。我希望你理解我的问题并能帮助我解决它。提前致谢! :)
我会定义最大纵横比。例如:
min_aspect_ratio = 5; // Minimum aspect ratio of 5:1
x_scale = screen_width/max_x_diff;
y_scale = screen_height/max_y_diff;
if(x_scale>y_scale)
if (x_scale/y_scale < min_aspect_ratio)
y_scale = x_scale/min_aspect_ratio;
else
if (y_scale/x_scale < min_aspect_ratio)
x_scale = y_scale/min_aspect_ratio;
免责声明:我没有测试此代码。
假设您有以下具有明确含义的变量:
screenWidth
、screenHeight
、minLon
、maxLon
、minLat
、maxLat
。
你想要的是 lon
和 lat
到屏幕坐标 x
和 y
的映射,以便填充宽度或高度,但圆仍然是圆(距离的水平和垂直比例相同)。
经纬度差的距离之比几乎正好是纬度的余弦值。 “几乎”只是因为地球不是一个完美的球体。余弦在纬度 0° 处为 1,因此,例如,在赤道,相同的经度和纬度差异对应于相同的距离。对于“足够”小区域的地图,可以使用平均纬度来计算比率。
如果只有水平约束,则必须使 x==0
对应于 minLon
,x==screenWidth-1
对应于 maxLon
。线性变换(即映射)为:
x = lon * lonScale + x0;
和
deltaLon = maxLon - minLon;
lonScale = (screenWidth - 1) / deltaLon;
x0 = - minLon * lonScale;
同样,如果您只有垂直约束:
y = y0 - lat * latScale; // i.e.: lat * (-latScale) + y0
和
deltaLat = maxLat - minLat;
latScale = (screenHeight - 1) / deltaLat;
y0 = maxLat * latScale;
(减号是因为屏幕坐标的原点在左上角。)
现在,如果 deltaLon == 0.0
或 deltaLat == 0.0
,您不想除以 0,只想将有意义的映射应用于您正在处理的直线。如果两者都是!= 0.0
,你要选择latScale
和lonScale
之间较小的一个,记住它们的比值是平均纬度的余弦:
lonScale == latScale * Math.cos((maxLat + minLat) / 2.0)
所以:
avgLat = (maxLat + minLat) / 2.0;
cosFactor = Math.cos(avgLat);
if (deltaLon != 0.0 && deltaLat != 0.0) {
if (lonScale > latScale * cosFactor) {
lonScale = latScale * cosFactor;
x0 = ((screenWidth - 1) - (minLon + maxLon) * lonScale) / 2.0;
} else {
latScale = lonScale / cosFactor;
y0 = ((screenHeight - 1) + (minLat + maxLat) * latScale) / 2.0;
}
}
其中 x0
和 y0
已通过求解将 (maxL* - minL*) / 2.0
置于屏幕中间的方程式进行了调整(*
为 on
或at
).
编辑
根据您的评论,您没有使用 真实 经度和纬度值,而只是使用东西和南北方向的距离。这有点像在赤道上,余弦因子变成1。我继续称输入坐标为lon
和lat
,但它们可以用任何长度单位表示,只要两者的单位相同。
所以计算变成:
deltaLon = maxLon - minLon;
deltaLat = maxLat - minLat;
if (deltaLon != 0.0 && deltaLat != 0.0) {
lonScale = (screenWidth - 1) / deltaLon;
latScale = (screenHeight - 1) / deltaLat;
x0 = - minLon * lonScale;
y0 = maxLat * latScale;
if (lonScale > latScale) {
lonScale = latScale;
x0 = ((screenWidth - 1) - (minLon + maxLon) * lonScale) / 2.0;
} else {
latScale = lonScale;
y0 = ((screenHeight - 1) + (minLat + maxLat) * latScale) / 2.0;
}
} else if (deltaLon != 0.0) {
lonScale = (screenWidth - 1) / deltaLon;
latScale = 0;
x0 = - minLon * lonScale;
y0 = (screenHeight - 1) / 2.0;
} else if (deltaLat != 0.0) {
lonScale = 0;
latScale = (screenHeight - 1) / deltaLat;
x0 = (screenWidth - 1) / 2.0;
y0 = maxLat * latScale;
} else {
lonScale = 0;
latScale = 0;
x0 = (screenHeight - 1) / 2.0;
y0 = (screenWidth - 1) / 2.0;
}
屏幕值的计算与一般情况完全相同,即:
x = x0 + lon * lonScale;
y = y0 - lat * latScale;