Get US State for given location coordinates - pandas

I want to figure out which state a lat long belongs to. And for this I am using the shape files provided by US Census and shapely library. This is what I tried so far:
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
df_poly = gpd.read_file("data/tl_2019_us_state.shp")
df_poly = df_poly[['GEOID', 'geometry']].set_index('GEOID')
display(df_poly.head(5))
geometry
GEOID
54 POLYGON ((-81.74725 39.09538, -81.74635 39.096...
12 MULTIPOLYGON (((-86.38865 30.99418, -86.38385 ...
17 POLYGON ((-91.18529 40.63780, -91.17510 40.643...
27 POLYGON ((-96.78438 46.63050, -96.78434 46.630...
24 POLYGON ((-77.45881 39.22027, -77.45866 39.220...
p1 = Point(map(float, (29.65, -95.17)))
any(df_poly['geometry'].contains(p1))
False
But it is somehow returning False for any coordinate that I try. For example the above coordinate is from Texas but still its returning False, so what am I missing here?

Here are a few things you should check:
Did you use the correct order for the point? Shapely points use (x, y) coordinates, which are in the opposite order of (lat, lon) coordinates. I'd try flipping the coordinates and seeing if that works.
For example, I see one of your coordinates is this: "-81.74725 39.09538" If you interpret that in (lat, lon) order, it's in Antartica. If you interpret it in (x, y) order, it's in Ohio.
Are you using the correct SRID? The census data usually uses NAD83, but this is a good thing to check:
print(df_poly.crs)
Another good sanity check is to look at the centroid of each polygon, and verify that it's reasonable:
df.geometry.centroid
In the past, I've seen people who had data which was in the wrong SRID, and had to convert it.

Related

Contour Map Failure

I am trying to make contour maps with a Latitude, Longitude, and a value that is not elevation for my Master's project. I really like the contour plot of irregularly spaced data in matplotlib, but just can't make it work for my data! I get how it generally works. I have over 600 coordinates.
SEE:
https://matplotlib.org/stable/gallery/images_contours_and_fields/irregulardatagrid.html
I have a data frame and just need it to plot some points and those nice contour lines. Please save me! I am a noob. I bet this is super easy for one of you matplotlib pros. Here is what I have:
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
df = pd.read_csv('BI_LT_475_South_Carolina.csv')
Lon = df.iloc[:,0]
Lat = df.iloc[:,1]
CSR = df.iloc[:,2] # What I want to plot instead of elevation.
The csv. looks like this:
Longitude, Latitude, and CSR

osmnx: project point to street segments

I have a point given by lat and lon and I want to find the nearest edge to the point by minimum Euclidean distance. For example
import osmnx as ox
track = [(40.7052, -74.0069)]
fig, ax = ox.plot_graph(G, show=False, close=False)
for pairs in track:
ax.scatter(pairs[1], pairs[0], c='red')
plt.show()
ox.distance.get_nearest_edge(G, track, return_geom=True, return_dist=True)
and I get
(2350521192,2350521202,0,
<shapely.geometry.linestring.LineString at 0x16569aa30>,
162.22242578930698)
It outputs the vertices of the edge and its geometry. The distance between the point and nearest edge is 162. But how do I find the the projection of my point onto this nearest edge?
Here's a complete minimal working example:
import osmnx as ox
from shapely.geometry import Point
ox.config(use_cache=True, log_console=True)
# create point tuple as (lat, lng)
point = (40.7052, -74.0069)
G = ox.graph_from_point(point, network_type='drive')
u, v, k, edge_geom, dist = ox.distance.get_nearest_edge(G, point, return_geom=True, return_dist=True)
# create shapely point geometry object as (x, y), that is (lng, lat)
point_geom = Point(reversed(point))
# use shapely to find the point along the edge that is closest to the reference point
nearest_point_on_edge = edge_geom.interpolate(edge_geom.project(point_geom))
nearest_point_on_edge.coords[0]

Cartopy plot high/low sea level pressure on map

I'm migrating from basemap to cartopy. One thing I would like to do is plot high/low pressure on a map, such as in basemap. There is a good example on this page of how to do this: https://matplotlib.org/basemap/users/examples.html ("Plot sea-level pressure weather map with labelled highs and lows"). I'm not going to copy and paste the code from this site, but would like to know how to do the same in cartopy. The main thing I can't get my head around is how to do m.xmax and x > m.xmin and y < m.ymax and y > m.ymin in cartopy (some kind of vector transform I'd imagine.
I've had a good look and can't see this particular example translated into something compatible with cartopy. Any help would be welcome!
In order to write an equivalent program using cartopy you need to be able to translate two concepts. The first is finding the extent of a projection, this can be done with the get_extent() method of a GeoAxes:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
my_proj = ccrs.Miller(central_longitude=180)
ax = plt.axes(projection=my_proj)
xmin, xmax, ymin, ymax = ax.get_extent()
You also need to transform coordinate points from geographic to projection coordinates, which is the function of the transform_points() method of a coordinate reference system instance:
import numpy as np
lons2d, lats2d = np.meshgrid(lons, lats) # lons lats are in degrees
transformed = my_proj.transform_points(ccrs.Geodetic(), lons2d, lats2d)
x = transformed[..., 0] # lons in projection coordinates
y = transformed[..., 1] # lats in projection coordinates
Now you can use the same technique as in the basemap example to filter and plot points, where instead of m.xmin you use xmin etc.
There are of course alternate ways of doing this which have pros and cons relative to the basemap example. If you come up with something nice you can contribute it to the Cartopy gallery.

Mask cube with features

I want to plot data from a global cube, but only for a list of countries. So I select a subcube according to the countries' "bounding box".
So far so good. What I'm looking for is an easy way to mask out all points of a cube which do not fall in any of my countries (which are represented as features), so that only those points of the cube which lie within any of my features are plotted.
Any idea is greatly appreciated =)
You can achieve this directly at the plotting stage rather than masking the cube within iris. I've approached this by setting the clip path of the artist returned by pcolor. The method is to create a list of geometries from features (in this case countries from Natural Earth, they could be from a shapefile) then transform these geometries into a matplotlib path which the image can be clipped to. I'll detail this method, and hopefully this will be enough to get you started:
I first defined a function to retrieve the Shapely geometries corresponding to given country names, the geometries come from the Natural Earth 110m administrative boundaries shapefile, access through the cartopy interface.
I then defined a second function which is a wrapper around the iris.plot.pcolor function which makes the plot and clips it to the given geometries.
Now all I need to do is set up the plot as normal, but use the plotting wrapper instead of directly calling the iris.plot.pcolor function.
Here is a complete example:
import cartopy.crs as ccrs
from cartopy.io.shapereader import natural_earth, Reader
from cartopy.mpl.patch import geos_to_path
import iris
import iris.plot as iplt
import matplotlib.pyplot as plt
from matplotlib.path import Path
def get_geometries(country_names):
"""
Get an iterable of Shapely geometries corrresponding to given countries.
"""
# Using the Natural Earth feature interface provided by cartopy.
# You could use a different source, all you need is the geometries.
shape_records = Reader(natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')).records()
geoms = []
for country in shape_records:
if country.attributes['name_long'] in country_names:
try:
geoms += country.geometry
except TypeError:
geoms.append(country.geometry)
return geoms, ccrs.PlateCarree()._as_mpl_transform
def pcolor_mask_geoms(cube, geoms, transform):
path = Path.make_compound_path(*geos_to_path(geoms))
im = iplt.pcolor(cube)
im.set_clip_path(path, transform=transform)
# First plot the full map:
cube = iris.load_cube(iris.sample_data_path('air_temp.pp'))
plt.figure(figsize=(12, 6))
ax1 = plt.axes(projection=ccrs.PlateCarree())
ax1.coastlines()
iplt.pcolor(cube)
# Now plot just the required countries:
plt.figure(figsize=(12, 6))
ax2 = plt.axes(projection=ccrs.PlateCarree())
ax2.coastlines()
countries = [
'United States',
'United Kingdom',
'Saudi Arabia',
'South Africa',
'Nigeria']
geoms, transform = get_geometries(countries)
pcolor_mask_geoms(cube, geoms, transform(ax2))
plt.show()
The results of which look like this:
If you want to use iris.plot.pcolormesh instead you will need to modify the plotting function a little bit. This is dues to a workaround for a matplotlib issue that is currently included in cartopy. The modified version would look like this:
def pcolor_mask_geoms(cube, geoms, transform):
path = Path.make_compound_path(*geos_to_path(geoms))
im = iplt.pcolormesh(cube)
im.set_clip_path(path, transform=transform)
try:
im._wrapped_collection_fix.set_clip_path(path, transform)
except AttributeError:
pass

Why the annotate worked unexpected here in cartopy?

Code first:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Mercator())
ax.set_extent([72, 135, 18, 53])
ax.annotate('hello', xy=(100, 49), xycoords='data',
transform=ccrs.PlateCarree(), zorder=12)
plt.show()
The result is not the expected one, and I have other doubts about my approach. So my questions are:
If I want to plot a map looks like the web map (eg. google map). The map area maybe is as large as China, mostly not global. After googling around, those sites use a "web mercator" projection mostly. So I suppost I should use plt.axes(projection=ccrs.Mercator() here, am I right? Or what should I use if I'm wrong?
The coords data I want to plot is like 121°E, 49°N(converted the degree to decimal before plotting of course), unprojected, WGS84 coords system, probably from a GPS. So am I right to use transform=ccrs.PlateCarree()? Or what should I use if I'm wrong?
The annotate above shows nothing. After comment the ax.set_extent line, the "hello" text is plotted at zero(0, 0) point. What I want is at point (100°E, 49°N)How to correct this?
First - thanks for the code - it makes it a lot easier to get going with the question.
To be honest, I don't think annotate has been used in earnest with Cartopy before, so that is probably why you're hitting this problem - you're trail blazing ;)
It looks like matplotlib's Axes.annotate method is to blame here - it nukes the transform passed through around https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L651. This is mostly because annotate has special keywords for defining the transform of both the coordinate and the text position independently (see xycoords and textcoords in http://matplotlib.org/users/annotations_intro.html#annotating-text).
When we dig down into the Annotate class, we will find that Annotate's _get_xy_transform (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/text.py#L1446) can handle various (some undocumented) forms as values to textcoords, including transform instances.
Ok, so far, so good. It would seem you can just put through a coordinate system to xycoords and everything should be hunky-dory. Sadly though, annotate does not know how to convert a Cartopy coordinate system to a matplotlib transform in the way that most of the rest of matplotlib does, so we are going to have to do that for the annotate function up-front.
To create a matplotlib transform from any cartopy coordinate system, for any axes, we can simply do:
ax = plt.axes(projection=ccrs.Mercator())
crs = ccrs.PlateCarree()
transform = crs._as_mpl_transform(ax)
We can now pass this transform through to the annotate method, and we should end up with text and an arrow in the expected location. I've taken a few liberties to highlight some of the functionality of annotate while I'm at it:
import cartopy.feature
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Mercator())
ax.set_extent([65, 125, 5, 40])
ax.add_feature(cartopy.feature.OCEAN)
ax.add_feature(cartopy.feature.LAND)
ax.add_feature(cartopy.feature.BORDERS, linestyle=':', edgecolor='gray')
ax.coastlines()
ax.plot(116.4, 39.95, 'ob', transform=ccrs.PlateCarree())
transform = ccrs.PlateCarree()._as_mpl_transform(ax)
ax.annotate('Beijing', xy=(116.4, 39.9), xycoords=transform,
ha='right', va='top')
ax.annotate('Delhi', xy=(113, 40.5), xytext=(77.23, 28.61),
arrowprops=dict(facecolor='gray',
arrowstyle="simple",
connectionstyle="arc3,rad=-0.2",
alpha=0.5),
xycoords=transform,
ha='right', va='top')
plt.show()
In answer to your other questions:
If I want to plot a map looks like the web map (eg. google map)
There is a new constant in cartopy.crs which defined the Google Mercator exactly (cartopy.crs.GOOGLE_MERCATOR). This is just an instance of a Mercator projection with a few tweaks to make it exactly like the Google Mercator (https://github.com/SciTools/cartopy/blob/master/lib/cartopy/crs.py#L889).
The coords data I want to plot is like 121°E, 49°N(converted the
degree to decimal before plotting of course), unprojected, WGS84
coords system, probably from a GPS. So am I right to use
transform=ccrs.PlateCarree()? Or what should I use if I'm wrong?
I would suggest you would be better placed using the Geodetic coordinate system - this coordinate system defaults to using a WGS84 datum which will give you the most accurate representation of your WGS84 latitudes and longitudes. Though, at the scale you are currently drawing them, I imagine you would struggle to notice the difference (maximum difference is about ~22Km in mid-latitudes).
HTH,