Pregunta

I would like a solution to automatically center a basemap plot on my coordinate data.

I've got things to automatically center, but the resulting area is much larger than the area actually used by my data. I would like the plot to be bounded by the plot coordinates, rather than an area drawn from the lat/lon boundaries.

I am using John Cook's code for calculating the distance between two points on (an assumed perfect) sphere.

First Try

Here is the script I started with. This was causing the width and height to bee small too small for the data area, and the center latitude (lat0) too far south.

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import sys
import csv
import spheredistance as sd


print '\n'
if len(sys.argv) < 3:
    print >>sys.stderr,'Usage:',sys.argv[0],'<datafile> <#rows to skip>'
    sys.exit(1)
print '\n'

dataFile = sys.argv[1]
dataStream = open(dataFile, 'rb')
dataReader = csv.reader(dataStream,delimiter='\t')
numRows = sys.argv[2]

dataValues = []
dataLat = []
dataLon = []

print 'Plotting Data From: '+dataFile

dataReader.next()
for row in dataReader:
    dataValues.append(row[0])
    dataLat.append(float(row[1]))
    dataLon.append(float(row[2]))

# center and set extent of map
earthRadius = 6378100 #meters
factor = 1.00

lat0new = ((max(dataLat)-min(dataLat))/2)+min(dataLat)
lon0new = ((max(dataLon)-min(dataLon))/2)+min(dataLon)

mapH = sd.distance_on_unit_sphere(max(dataLat),lon0new,
            min(dataLat),lon0new)*earthRadius*factor

mapW = sd.distance_on_unit_sphere(lat0new,max(dataLon),
            lat0new,min(dataLon))*earthRadius*factor

# setup stereographic basemap.
# lat_ts is latitude of true scale.
# lon_0,lat_0 is central point.
m = Basemap(width=mapW,height=mapH,
            resolution='l',projection='stere',\
            lat_0=lat0new,lon_0=lon0new)

#m.shadedrelief()
m.drawcoastlines(linewidth=0.2)
m.fillcontinents(color='white', lake_color='aqua')

#plot data points (omitted due to ownership)
#x, y = m(dataLon,dataLat)
#m.scatter(x,y,2,marker='o',color='k')

# draw parallels and meridians.
m.drawparallels(np.arange(-80.,81.,20.), labels=[1,0,0,0], fontsize=10)
m.drawmeridians(np.arange(-180.,181.,20.), labels=[0,0,0,1], fontsize=10)
m.drawmapboundary(fill_color='aqua')

plt.title("Example")
plt.show()

enter image description here

¿Fue útil?

Solución

After generating some random data, it was obvious that the bounds that I chose did not work with this projection (red lines). Using map.drawgreatcircle(), I first visualized where I wanted the bounds while zoomed over the projection of random data.

Red lines are old calculated widths and height

I corrected the longitude by using the longitudinal difference at the southern most latitude (blue horizontal line).

I determined the latitudinal range using the Pythagorean theorem to solve for the vertical distance, knowing the distance between the northern most longitudinal bounds, and the central southernmost point (blue triangle).

def centerMap(lats,lons,scale):
    #Assumes -90 < Lat < 90 and -180 < Lon < 180, and
    # latitude and logitude are in decimal degrees
    earthRadius = 6378100.0 #earth's radius in meters

    northLat = max(lats)
    southLat = min(lats)
    westLon = max(lons)
    eastLon = min(lons)

    # average between max and min longitude 
    lon0 = ((westLon-eastLon)/2.0)+eastLon

    # a = the height of the map
    b = sd.spheredist(northLat,westLon,northLat,eastLon)*earthRadius/2
    c = sd.spheredist(northLat,westLon,southLat,lon0)*earthRadius

    # use pythagorean theorom to determine height of plot
    mapH = pow(pow(c,2)-pow(b,2),1./2)
    arcCenter = (mapH/2)/earthRadius

    lat0 = sd.secondlat(southLat,arcCenter)

    # distance between max E and W longitude at most souther latitude
    mapW = sd.spheredist(southLat,westLon,southLat,eastLon)*earthRadius

    return lat0,lon0,mapW*scale,mapH*scale

lat0center,lon0center,mapWidth,mapHeight = centerMap(dataLat,dataLon,1.1)

The lat0 (or latitudinal center) in this case is therefore the point half-way up the height of this triangle, which I solved using John Cooks method, but for solving for an unknown coordinate while knowing the first coordinate (the median longitude at the southern boundary) and the arc length (half that of the total height).

def secondlat(lat1, arc):
    degrees_to_radians = math.pi/180.0
    lat2 = (arc-((90-lat1)*degrees_to_radians))*(1./degrees_to_radians)+90
    return lat2

Update: The above function, as well as the distance between two coordinates can be achieved with higher accuracy using the pyproj Geod class methods geod.fwd() and geod.inv(). I found this in Erik Westra's Python for Geospatial Development, which is an excellent resource.

Update: I have now verified that this also works for Lambert Conformal Conic (lcc) projections.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top