I downloaded a shapefile of Boston and wants to plot it out using the code below. However it's giving me an error ValueError: lat_0 must be between -90.000000 and 90.000000 degrees
Turns out coords has values (33869.92130000144, 777617.2998000011, 330800.31099999696, 959741.1853) Why is it so large?
Boston shapefile is obtained here
Code
# Import Boston shapefile
shapefilename = 'ZIPCODES_NT_POLY'
shp = fiona.open(shapefilename + '.shp')
coords = shp.bounds
shp.close()
w, h = coords[2] - coords[0], coords[3] - coords[1]
extra = 0.01
m = Basemap(
projection='tmerc', ellps='WGS84',
lon_0 = np.mean([coords[0], coords[2]]),
lat_0 = np.mean([coords[1], coords[3]]),
llcrnrlon = coords[0] - extra * w,
llcrnrlat = coords[1] - extra * h,
urcrnrlon = coords[2] + extra * w,
urcrnrlat = coords[3] + extra * h,
resolution = 'i', suppress_ticks = True)
Error
ValueError: lat_0 must be between -90.000000 and 90.000000 degrees
You need to reproject from the native projection of Eastings and Northings to another coordinate reference system. If you want degrees of Latitude and Longitude, which is usually WGS84 or EPSG:4326. Here's how to reproject it:
import fiona
import pyproj
from functools import partial
from shapely.geometry import box
from shapely.ops import transform
shp = fiona.open('ZIPCODES_NT_POLY.shp', 'r')
p_in = pyproj.Proj(shp.crs)
bound_box = box(*shp.bounds)
shp.close()
p_out = pyproj.Proj({'init': 'EPSG:4326'}) # aka WGS84
project = partial(pyproj.transform, p_in, p_out)
bound_box_wgs84 = transform(project, bound_box)
print('native box: ' + str(bound_box))
print('WGS84 box: ' + str(bound_box_wgs84))
native box: POLYGON ((330800.310999997 777617.2998000011, 330800.310999997 959741.1853, 33869.92130000144 959741.1853, 33869.92130000144 777617.2998000011, 330800.310999997 777617.2998000011))
WGS84 box: POLYGON ((-69.93980848410942 41.23787282321487, -69.899038698261 42.87724537285449, -73.53324195423785 42.8704709990465, -73.48147096070339 41.2312695091688, -69.93980848410942 41.23787282321487))
Otherwise, most of the parameters that are required by Basemap are in shp.crs (take a look).
You must change the coordinate reference system. I recommend geopandas:
import geopandas as gpd
import matplotlib.pyplot as plt
# load zips with the source projection
shapefilename = 'ZIPCODES_NT_POLY'
zips = gpd.read_file(shapefilename + '.shp')
# convert projection to familiar lat/lon
zips = zips.to_crs('epsg:4326')
zips.plot()
plt.show()
Related
i saw on the internet images that folium generate a elipse form on map, is it possible, i tried creating manually but is inviable point per point, is there any way to do that ?
elipse on folium
Yes, by using folium's Polygon (and some other stuff)
I copied this answer for the polygon construction
import math
from shapely.geometry import Point
from shapely.affinity import scale, rotate
#input parameters
A = Point(-95.5, 41.25)
B = Point(-96.5, 41.25)
R = .25
d = A.distance(B)
#first, rotate B to B' around A so that |AB'| = |AB| and B'.y = A.y
#and then take S as midpoint of AB'
S = Point(A.x + d/2, A.y)
#alpha represents the angle of this rotation
alpha = math.atan2(B.y - A.y, B.x - A.x)
#create a circle with center at S passing through A and B'
C = S.buffer(d/2)
#rescale this circle in y-direction so that the corresponding
#axis is R units long
C = scale(C, 1, R/(d/2))
#rotate the ellipse obtained in previous step around A into the
#original position (positive angles represent counter-clockwise rotation)
C = rotate(C, alpha, origin = A, use_radians = True)
Where C is a shapely polygon
print(type(C))
<class 'shapely.geometry.polygon.Polygon'>
I flip the coordinates from X,Y to Y,X to make it folium friendly
folium_poly = [[y,x] for x,y in C.exterior.coords]
then folium for the rest
import folium
m = folium.Map([C.centroid.y, C.centroid.x])
folium.Polygon(folium_poly).add_to(m)
m
BLUF::
I'm having trouble computing zonal statistics with a rotated array using the rasterstats package. I'm guessing the problem is with my affine matrix, but I'm not completely sure. Below is the affine transform matrix and output:
| 951.79, 0.45, 2999993.57|
| 0.00,-996.15,-1985797.84|
| 0.00, 0.00, 1.00|
Background:
I am creating files for a groundwater flow model and need to compute zonal statistics for each model grid cell using some .csv data from the Puerto Rico Agricultural Water Management web portal. These data are available on a daily timestep for numerous parameters (e.g. ET, tmax, tmin, precip, etc.). These files are not georeferenced, but ancillary files are available that specify the lon/lat for each cell which can then be projected using pyproj:
import pandas as pd
import numpy as np
import pyproj
url_base = 'http://academic.uprm.edu/hdc/GOES-PRWEB_RESULTS'
# Load some data
f = '/'.join([url_base, 'actual_ET', 'actual_ET20090101.csv'])
array = pd.read_csv(f, header=None).values
# Read longitude values
f = '/'.join([url_base, 'NON_TRANSIENT_PARAMETERS', 'LONGITUDE.csv'])
lon = pd.read_csv(f, header=None).values
# Read latitude values
f = '/'.join([url_base, 'NON_TRANSIENT_PARAMETERS', 'LATITUDE.csv'])
lat = np.flipud(pd.read_csv(f, header=None).values)
# Project to x/y coordinates (North America Albers Equal Area Conic)
aea = pyproj.Proj('+init=ESRI:102008')
x, y = aea(lon, lat)
Before I can compute zonal statistics, I need to create the affine transform that relates row/column coordinates to projected x/y coordinates. I specify the 6 parameters to create the Affine object using the affine library:
import math
from affine import Affine
length_of_degree_longitude_at_lat_mean = 105754.71 # 18.25 degrees via http://www.csgnetwork.com/degreelenllavcalc.html
length_of_degree_latitude_at_lat_mean = 110683.25 # 18.25 degrees via http://www.csgnetwork.com/degreelenllavcalc.html
# Find the upper left x, y
xul, yul = aea(lon[0][0], lat[0][0])
xll, yll = aea(lon[-1][0], lat[-1][0])
xur, yur = aea(lon[0][-1], lat[0][-1])
xlr, ylr = aea(lon[-1][-1], lat[-1][-1])
# Compute pixel width
a = abs(lon[0][1] - lon[0][0]) * length_of_degree_longitude_at_lat_mean
# Row rotation
adj = abs(xlr - xll)
opp = abs(ylr - yll)
b = math.atan(opp/adj)
# x-coordinate of the upper left corner
c = xul - a / 2
# Compute pixel height
e = -abs(lat[1][0] - lat[0][0]) * length_of_degree_latitude_at_lat_mean
# Column rotation
d = 0
# y-coordinate of the upper left corner
f = yul - e / 2
affine = Affine(a, b, c, d, e, f)
where:
a = width of a pixel
b = row rotation (typically zero)
c = x-coordinate of the upper-left corner of the upper-left pixel
d = column rotation (typically zero)
e = height of a pixel (typically negative)
f = y-coordinate of the of the upper-left corner of the upper-left pixel
(from https://www.perrygeo.com/python-affine-transforms.html)
The resulting affine matrix looks reasonable and I've tried passing row and column rotation as both radians and degrees with little change in the result. Link to grid features: grid_2km.geojson
import rasterstats
import matplotlib.pyplot as plt
grid_f = 'grid_2km.geojson'
gdf = gpd.read_file(grid_f)
zs = rasterstats.zonal_stats(gdf,
array,
affine=affine,
stats=['mean'])
df = pd.DataFrame(zs).fillna(value=np.nan)
fig = plt.figure(figsize=(14, 6))
ax = fig.add_subplot(131, aspect='equal')
ax.pcolormesh(x, y, np.zeros_like(array))
ax.pcolormesh(x, y, array)
ax.set_title('Projected Data')
ax = fig.add_subplot(132, aspect='equal')
gdf.plot(ax=ax)
ax.set_title('Projected Shapefile')
ax = fig.add_subplot(133, aspect='equal')
ax.imshow(df['mean'].values.reshape((gdf.row.max(), gdf.col.max())))
ax.set_title('Zonal Statistics Output')
plt.tight_layout()
plt.show()
Further, there is a discrepancy between x, y value pairs transformed using the affine object versus those derived from the native lon, lat values using pyproj:
rr = np.array([np.ones(x.shape[1], dtype=np.int) * i for i in range(x.shape[0])])
cc = np.array([np.arange(x.shape[1]) for i in range(x.shape[0])])
x1, y1 = affine * (cc, rr)
fig = plt.figure(figsize=(14, 6))
ax = fig.add_subplot(111, aspect='equal')
ax.scatter(x, y, s=.2)
ax.scatter(x1, y1, s=.2)
plt.show()
I've been working with matplotlib and basemap to show some information about New York City. Up until now, I've been following this guide, but I've hit an issue. I'm trying to show manhattan island within my visualization, but I can't figure out why basemap isn't showing it as an island.
Here's the visualization that basemap is giving me:
Here's a screenshot of the bounding box I'm using:
And here's the code that is generating the image:
wl = -74.04006
sl = 40.683092
el = -73.834067
nl = 40.88378
m = Basemap(resolution='f', # c, l, i, h, f or None
projection='merc',
area_thresh=50,
lat_0=(wl + sl)/2, lon_0=(el + nl)/2,
llcrnrlon= wl, llcrnrlat= sl, urcrnrlon= el, urcrnrlat= nl)
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color='#f2f2f2',lake_color='#46bcec')
m.drawcoastlines()
m.drawrivers()
I thought that it might consider the water in between a river, but m.drawrivers() didn't appear to fix it. Any help is obviously extremely appreciated.
Thanks in advance!
One approach to get a better quality base map for your plots is building one from web map tiles at an appropriate zoom level. Here I demonstrate how to get them from openstreetmap web map servers. In this case, I use zoom level 10, and get 2 map tiles to combined as single image array. One of the drawbacks, the extent of the combined image is always larger than the values we asked for. Here is the working code:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import math
import urllib2
import StringIO
from PIL import Image
# === Begin block1 ===
# Credit: BerndGit, answered Feb 15 '15 at 19:47. And ...
# Source: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
def deg2num(lat_deg, lon_deg, zoom):
'''Lon./lat. to tile numbers'''
lat_rad = math.radians(lat_deg)
n = 2.0 ** zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
return (xtile, ytile)
def num2deg(xtile, ytile, zoom):
'''Tile numbers to lon./lat.'''
n = 2.0 ** zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return (lat_deg, lon_deg) # NW-corner of the tile.
def getImageCluster(lat_deg, lon_deg, delta_lat, delta_long, zoom):
# access map tiles from internet
# no access/key or password is needed
smurl = r"http://a.tile.openstreetmap.org/{0}/{1}/{2}.png"
# useful snippet: smurl.format(zoom, xtile, ytile) -> complete URL
# x increases L-R; y Top-Bottom
xmin, ymax =deg2num(lat_deg, lon_deg, zoom) # get tile numbers (x,y)
xmax, ymin =deg2num(lat_deg+delta_lat, lon_deg+delta_long, zoom)
# PIL is used to build new image from tiles
Cluster = Image.new('RGB',((xmax-xmin+1)*256-1,(ymax-ymin+1)*256-1) )
for xtile in range(xmin, xmax+1):
for ytile in range(ymin, ymax+1):
try:
imgurl = smurl.format(zoom, xtile, ytile)
print("Opening: " + imgurl)
imgstr = urllib2.urlopen(imgurl).read()
# TODO: study, what these do?
tile = Image.open(StringIO.StringIO(imgstr))
Cluster.paste(tile, box=((xtile-xmin)*256 , (ytile-ymin)*255))
except:
print("Couldn't download image")
tile = None
return Cluster
# ===End Block1===
# Credit to myself
def getextents(latmin_deg, lonmin_deg, delta_lat, delta_long, zoom):
'''Return LL and UR, each with (long,lat) of real extent of combined tiles.
latmin_deg: bottom lat of extent
lonmin_deg: left long of extent
delta_lat: extent of lat
delta_long: extent of long, all in degrees
'''
# Tile numbers(x,y): x increases L-R; y Top-Bottom
xtile_LL, ytile_LL = deg2num(latmin_deg, lonmin_deg, zoom) #get tile numbers as specified by (x, y)
xtile_UR, ytile_UR = deg2num(latmin_deg + delta_lat, lonmin_deg + delta_long, zoom)
# from tile numbers, we get NW corners
lat_NW_LL, lon_NW_LL = num2deg(xtile_LL, ytile_LL, zoom)
lat_NW_LLL, lon_NW_LLL = num2deg(xtile_LL, ytile_LL+1, zoom) # next down below
lat_NW_UR, lon_NW_UR = num2deg(xtile_UR, ytile_UR, zoom)
lat_NW_URR, lon_NW_URR = num2deg(xtile_UR+1, ytile_UR, zoom) # next to the right
# get extents
minLat = lat_NW_LLL
minLon = lon_NW_LL
maxLat = lat_NW_UR
maxLon = lon_NW_URR
return (minLon, maxLon, minLat, maxLat) # (left, right, bottom, top) in degrees
# OP's values of extents for target area to plot
# some changes here (with larger zoom level) may lead to better final plot
wl = -74.04006
sl = 40.683092
el = -73.834067
nl = 40.88378
lat_deg = sl
lon_deg = wl
d_lat = nl - sl
d_long = el - wl
zoom = 10 # zoom level
# Acquire images. The combined images will be slightly larger that the extents
timg = getImageCluster(lat_deg, lon_deg, d_lat, d_long, zoom)
# This computes real extents of the combined tile images, and get (left, right, bottom, top)
latmin_deg, lonmin_deg, delta_lat, delta_long = sl, wl, nl-sl, el-wl
(left, right, bottom, top) = getextents(latmin_deg, lonmin_deg, delta_lat, delta_long, zoom) #units: degrees
# Set Basemap with proper parameters
m = Basemap(resolution='h', # h is nice
projection='merc',
area_thresh=50,
lat_0=(bottom + top)/2, lon_0=(left + right)/2,
llcrnrlon=left, llcrnrlat=bottom, urcrnrlon=right, urcrnrlat=top)
fig = plt.figure()
fig.set_size_inches(10, 12)
m.imshow(np.asarray(timg), extent=[left, right, bottom, top], origin='upper' )
m.drawcoastlines(color='gray', linewidth=3.0) # intentionally thick line
#m.fillcontinents(color='#f2f2f2', lake_color='#46bcec', alpha=0.6)
plt.show()
Hope it helps. The resulting plot:
Edit
To crop the image in order to get the exact area to plot is not difficult. The PIL module can handle that. Numpy's array slicing also works.
I'm trying to get the value of a latitude and longitude from a subdataset of a HDF file using gdal. But I'm getting the following error:
IndexError: index -62399 is out of bounds for axis 1 with size 4800
Here's my code:
from osgeo import ogr, osr,gdal
hdf_file = gdal.Open("MOD13Q1.A2017321.h31v10.006.2017337222145.hdf")
subDatasets = hdf_file.GetSubDatasets()
val_dict = {}
#print subDatasets[0]
dataset = gdal.Open(subDatasets[1][0])
transf = dataset.GetGeoTransform()
success,transfInv = gdal.InvGeoTransform(transf)
ds = dataset.ReadAsArray()
#lon,lat = -17.586972, 139.158043
lat = -16.718853
lon = 142.645773
px, py = gdal.ApplyGeoTransform(transfInv, lon, lat)
value = ds[int(px),int(py)]
print value
Can anyone tell me what I'm doing wrong ?
If you look at the geotransform (transf) of the dataset you can see that the coordinates are not in degrees lat/lon (its Sinusoidal).
Therefore you shouldn't provide lat/lon values when applying the geotransform to convert to pixel coordinates. These values should be in the same projection as the dataset.
For example, if you enter the coordinates of the upper-left corner, you will get (0,0) as a result:
mapx = transf[0]
mapy = transf[3]
px, py = gdal.ApplyGeoTransform(transfInv, mapx, mapy)
Or for the lower-right corner:
mapx = transf[0] + transf[1] * ds.shape[1]
mapy = transf[3] + transf[5] * ds.shape[0]
px, py = gdal.ApplyGeoTransform(transfInv, mapx, mapy)
Which results in (4800, 4800).
From a complex 3D shape, I have obtained by tricontourf the equivalent top view of my shape.
I wish now to export this result on a 2D array.
I have tried this :
import numpy as np
from shapely.geometry import Polygon
import skimage.draw as skdraw
import matplotlib.pyplot as plt
x = [...]
y = [...]
z = [...]
levels = [....]
cs = plt.tricontourf(x, y, triangles, z, levels=levels)
image = np.zeros((100,100))
for i in range(len(cs.collections)):
p = cs.collections[i].get_paths()[0]
v = p.vertices
x = v[:,0]
y = v[:,1]
z = cs.levels[i]
# to see polygon at level i
poly = Polygon([(i[0], i[1]) for i in zip(x,y)])
x1, y1 = poly.exterior.xy
plt.plot(x1,y1)
plt.show()
rr, cc = skdraw.polygon(x, y)
image[rr, cc] = z
plt.imshow(image)
plt.show()
but unfortunately, from contours vertices only one polygon is created by level (I think), generated at the end an incorrect projection of my contourf in my 2D array.
Do you have an idea to correctly represent contourf in a 2D array ?
Considering a inner loop with for path in ...get_paths() as suggested by Andreas, things are better ... but not completely fixed.
My code is now :
import numpy as np
import matplotlib.pyplot as plt
import cv2
x = [...]
y = [...]
z = [...]
levels = [....]
...
cs = plt.tricontourf(x, y, triangles, z, levels=levels)
nbpixels = 1024
image = np.zeros((nbpixels,nbpixels))
pixel_size = 0.15 # relation between a pixel and its physical size
for i,collection in enumerate(cs.collections):
z = cs.levels[i]
for path in collection.get_paths():
verts = path.to_polygons()
for v in verts:
v = v/pixel_size+0.5*nbpixels # to centered and convert vertices in physical space to image pixels
poly = np.array([v], dtype=np.int32) # dtype integer is necessary for the next instruction
cv2.fillPoly( image, poly, z )
The final image is not so far from the original one (retunred by plt.contourf).
Unfortunately, some empty little spaces still remains in the final image.(see contourf and final image)
Is path.to_polygons() responsible for that ? (considering only array with size > 2 to build polygons, ignoring 'crossed' polygons and passing through isolated single pixels ??).