matplotlib and apect ratio of geographical-data pl

2019-05-30 07:40发布

I process geographical information and present the results using matplotlib. All input is lattitude/longitude [degree]. I convert into x/y [meter] for my calculations. And I present my results in lattitude/longitude. The problem is to get the graphs aspect-ratio right: All graphs are too wide. Is there a standard procedure to set the correct aspect-ratio so I can simply draw my scatter and other diagrams
using lat/lon and the result has the correct shape? On screen and on paper (png)?

[added this part later] This is a bare-bone stripped version of my problem. I need actual lat/lon values around the axes and an accurate shape (square). Right now it appears wide (2x).

import math
import matplotlib.pyplot as plt
import numpy as np
from pylab import *

w=1/math.cos(math.radians(60.0))
plt_area=[0,w,59.5,60.5] #60deg North, adjacent to the prime meridian

a=np.zeros(shape=(300,300))

matshow(a, extent=plt_area)

plt.grid(False)
plt.axis(plt_area)
fig   = plt.gcf()
fig.set_size_inches(8,8)
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)

plt.show()

3条回答
疯言疯语
2楼-- · 2019-05-30 08:08

Don't try to fix this by fiddling fig.set_size_inches() or fig.subplots_adjust() or by changing your data; instead use a Mercator projection.

You can get a quick and dirty Mercator projection by using an aspect ratio of the reciprocal of the cosine of the mean latitude of your data. This is "pretty good" for data contained in about 1 degree of latitude, which is about 100 km. (You have to decide if, for your application, this is "good enough". If it isn't, you really have to consider some serious geographical projection libraries...)

Example:

from math import cos, radians
import matplotlib.pyplot as plt
import numpy as np

# Helsinki 60.1708 N, 24.9375 E
# Helsinki (lng, lat)
hels = [24.9375, 60.1708]
# a point 100 km directly north of Helsinki
pt_N = [24.9375, 61.0701]
# a point 100 km east of Helsinki along its parallel
pt_E = [26.7455, 60.1708]

coords = np.array([pt_N, hels, pt_E])

plt.figure()
plt.plot(coords[:,0], coords[:,1])

# either of these will estimate the "central latitude" of your data
# 1) do the plot, then average the limits of the y-axis    
central_latitude = sum(plt.axes().get_ylim())/2.
# 2) actually average the latitudes in your data
central_latitude = np.average(coords, 0)[1]

# calculate the aspect ratio that will approximate a 
# Mercator projection at this central latitude 
mercator_aspect_ratio = 1/cos(radians(central_latitude))

# set the aspect ratio of the axes to that
plt.axes().set_aspect(mercator_aspect_ratio)

plt.show()

I picked Helsinki for the example since at that latitude the aspect ratio is almost 2... because two degrees of longitude is the about same distance as one degree of latitude.

To really see this work: a) run the above, b) resize the window. Then comment out the call to set_aspect() and do the same. In the first case, the correct aspect ratio is maintained, in the latter you get nonsensical stretching.

The points 100km north and east of Helsinki were calculated/confirmed by the EXCELLENT page calculating distances between lat/lng points at Movable Type Scripts

查看更多
小情绪 Triste *
3楼-- · 2019-05-30 08:10

It seems I found the solution. And I found it here: How can I set the aspect ratio in matplotlib?

import math
import matplotlib.pyplot as plt
import numpy as np

w=1/math.cos(math.radians(60.0))
plt_area=[0,w,59.5,60.5] #square area

a=np.zeros(shape=(300,300))

fig = plt.figure()
ax = fig.add_subplot(111)

ax.imshow(a)

plt.grid(False)
ax.axis(plt_area)
fig   = plt.gcf()
fig.set_size_inches(8,8)
ax.set_aspect(w)
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)

plt.show()
查看更多
萌系小妹纸
4楼-- · 2019-05-30 08:18

In matplotlib I usually change the figure size like this:

import matplotlib.pyplot as plt

plt.clf()
fig     = plt.figure()
fig_p   = plt.gcf()
fig_p.set_size_inches(8, 8)    # x, y

However this sets the dimensions for the figure outer dimensions, not the plot area. You can change the plot area relative to the figure size given in ratios of the total figure size lengths of x and y respectively:

fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)

As long as the the relative ratios stay symmetrically the aspect ratio should be the same for the plot are.

Example 1:

plt.clf()
fig     = plt.figure()
fig_p   = plt.gcf()
fig_p.set_size_inches(5, 5)    # x, y for figure canvas

# Relative distance ratio between origin of the figure and max extend of canvas
fig.subplots_adjust(left=0.2, right=0.8, bottom=0.2, top=0.8)

ax1   = fig.add_subplot(111)
xdata = [rand()*10 for i in xrange(100)]
ydata = [rand()*1  for i in xrange(100)]
ax1.plot(xdata, ydata, '.b', )
ax1.set_xlabel('Very Large X-Label', size=30)
plt.savefig('squareplot.png', dpi=96)

$.subplot_adjust$ overwrites the default space between figure size and plot extend

Example 2:

fig.subplots_adjust(left=0.0, right=1.0, bottom=0.0, top=1.0)

Plot area fills the figure size completely:

No spacing between figure size and plot area

查看更多
登录 后发表回答