I have a dataframe that contains thousands of points with geolocation (longitude, latitude) for Washington D.C. The following is a snippet of it:
import pandas as pd
df = pd.DataFrame({'lat': [ 38.897221,38.888100,38.915390,38.895100,38.895100,38.901005,38.960491,38.996342,38.915310,38.936820], 'lng': [-77.031048,-76.898480,-77.021380,-77.036700,-77.036700 ,-76.990784,-76.862907,-77.028131,-77.010403,-77.184930]})
If you plot the points in the map you can see that some of them are clearly within some buildings:
import folium
wash_map = folium.Map(location=[38.8977, -77.0365], zoom_start=10)
for index,location_info in df.iterrows():
folium.CircleMarker(
location=[location_info["lat"], location_info["lng"]], radius=5,
fill=True, fill_color='red',).add_to(wash_map)
wash_map.save('example_stack.html')
import webbrowser
import os
webbrowser.open('file://'+os.path.realpath('example_stack.html'), new=2)
My goal is to exclude all the points that are within buildings. For that, I first download bounding boxes for the city buildings and then try to exclude points within those polygons as follows:
import osmnx as ox
#brew install spatialindex this solves problems in mac
%matplotlib inline
ox.config(log_console=True)
ox.__version__
tags = {"building": True}
gdf = ox.geometries.geometries_from_point([38.8977, -77.0365], tags, dist=1000)
gdf.shape
For computational simplicity I have requested the shapes of all buildings around the White house with a radius of 1 km. On my own code I have tried with bigger radiuses to make sure all the buildings are included.
In order to exclude points within the polygons I developed the following function (which includes the shape obtention):
def buildings(df,center_point,dist):
import osmnx as ox
#brew install spatialindex this solves problems in mac
%matplotlib inline
ox.config(log_console=True)
ox.__version__
tags = {"building": True}
gdf = ox.geometries.geometries_from_point(center_point, tags,dist)
from shapely.geometry import Point,Polygon
# Next step is to put our coordinates in the correct shapely format: remember to run the map funciton before
#df['within_building']=[]
for point in range(len(df)):
if gdf.geometry.contains(Point(df.lat[point],df.lng[point])).all()==False:
df['within_building']=False
else :
df['within_building']=True
buildings(df,[38.8977, -77.0365],1000)
df['within_building'].all()==False
The function always returns that points are outside building shapes although you can clearly see in the map that some of them are within. I don't know how to plot the shapes over my map so I am not sure if my polygons are correct but for the coordinates they appear to be so. Any ideas?
The example points you provided don't seem to fall within those buildings' footprints. I don't know what your points' coordinate reference system is, so I guessed EPSG4326. But to answer your question, here's how you would exclude them, resulting in gdf_points_not_in_bldgs:
import geopandas as gpd
import matplotlib.pyplot as plt
import osmnx as ox
import pandas as pd
# the coordinates you provided
df = pd.DataFrame({'lat': [38.897221,38.888100,38.915390,38.895100,38.895100,38.901005,38.960491,38.996342,38.915310,38.936820],
'lng': [-77.031048,-76.898480,-77.021380,-77.036700,-77.036700 ,-76.990784,-76.862907,-77.028131,-77.010403,-77.184930]})
# create GeoDataFrame of point geometries
geom = gpd.points_from_xy(df['lng'], df['lat'])
gdf_points = gpd.GeoDataFrame(geometry=geom, crs='epsg:4326')
# get building footprints
tags = {"building": True}
gdf_bldgs = ox.geometries_from_point([38.8977, -77.0365], tags, dist=1000)
gdf_bldgs.shape
# get all points that are not within a building footprint
mask = gdf_points.within(gdf_bldgs.unary_union)
gdf_points_not_in_bldgs = gdf_points[~mask]
print(gdf_points_not_in_bldgs.shape) # (10, 1)
# plot buildings and points
ax = gdf_bldgs.plot()
ax = gdf_points.plot(ax=ax, c='r')
plt.show()
# zoom in to see better
ax = gdf_bldgs.plot()
ax = gdf_points.plot(ax=ax, c='r')
ax.set_xlim(-77.04, -77.03)
ax.set_ylim(38.89, 38.90)
plt.show()
Related
To create a Voronoi polygon with geovoronoi lib i use:
polyShapes, puntos = voronoi_regions_from_coords(coords, milagroShape)
coords is a geoDataFrame object that it contains map´s locations and milagroShape is a polygon.shp. Now, to plot the Voronoi use the code:
fig, ax = subplot_for_map(figsize=[14, 8])
plot_voronoi_polys_with_points_in_area(ax, milagroShape, polyShapes, coords, puntos)
ax.set_title('Diagrama de Voronoi')
plt.tight_layout()
plt.show()
Now it works, the graph is showed on screen, but it´s only a mathplotlib plot.
I guess that I have to convert it into a geodataframe object (to that, I use geopandas library).
This is the map where I need to put the Voronoi graph:
Only the polygon of the city´s area is set, but I want to set the Voronoi too.
To add the city´s area I used the code below:
for _, r in milagro.iterrows(): #milagro is a geodataframe object
#sim_geo = gpd.GeoSeries(r['geometry'])
sim_geo = gpd.GeoSeries(r['geometry']).simplify(tolerance=0.0001)
geo_j = sim_geo.to_json()
geo_j = folium.GeoJson(data=geo_j,
style_function=lambda x: {'fillColor': 'orange'})
#folium.Popup(r['Name']).add_to(geo_j)
geo_j.add_to(mapaMilagro) #mapaMilagro is a folium map object
Libraries that i use for my proyect are:
import folium #map library
import pandas as pd #Data Frame
import matplotlib.pyplot as plt #to plot graphs
import condacolab #To install some libraries
import geopandas as gpd #Geo Data Frame library
from shapely.ops import cascaded_union #I don´t know what is this xd
from geovoronoi.plotting import subplot_for_map, plot_voronoi_polys_with_points_in_area
from geovoronoi import voronoi_regions_from_coords, points_to_coords
polyShapes, puntos = voronoi_regions_from_coords(coords, milagroShape)
polyShapes is a dict where the keys are meaningless (?) numbers and the values are shapely polygons. You can load those into a new gpd dataframe.
in geopandas I use this code to create centroid parameter from geometric parameter.
df["center"]=df.centroid
I want to force the calculation of the centroids to be within the polygon.
here i found somthing in R. can I do it in python?
Calculate Centroid WITHIN / INSIDE a SpatialPolygon
To get the representative points that always fall within their corresponding polygons can be done in geopandas with the function called representative_point(). Here is a demo code that creates and plots the polygons and their rep. points.
import pandas as pd
import geopandas as gpd
from shapely import wkt
from shapely.geometry import Point, Polygon
from shapely.wkt import loads
#Create some test data
d = {'col1': [1,2],
'wkt': [
'POLYGON ((700000 5500000, 700000 5600000, 800000 5600000, 800000 5500000, 700000 5500000))',
"""POLYGON ((1441727.5096940901130438 6550163.0046194596216083,
1150685.2609429201111197 6669225.7427449300885201,
975398.4520359700545669 6603079.7771196700632572,
866257.6087542800232768 6401334.5819626096636057,
836491.9242229099618271 6106985.0349301798269153,
972091.1537546999752522 5835786.5758665995672345,
1547561.0546945100650191 5782869.8033663900569081,
1408654.5268814601004124 5600968.3978968998417258,
720736.4843787000281736 5663807.0652409195899963,
598366.4479719599476084 6001151.4899297598749399,
654590.5187534400029108 6341803.2128998702391982,
869564.9070355399744585 6784981.1825891500338912,
1451649.4045378800947219 6788288.4808704098686576,
1441727.5096940901130438 6550163.0046194596216083))"""
]
}
df = pd.DataFrame( data=d )
gdf = gpd.GeoDataFrame(df, \
crs={'init': 'epsg:3857'}, \
geometry=[loads(pgon) for pgon in df.wkt])
gdf4326 = gdf.to_crs(4326) #coordinates in plain degrees
# create 2nd geometry column, for representative points
gdf4326["geometry2"] = gdf4326.representative_point()
# plot all layers of geometries
ax1 = gdf4326.plot(color="gray", alpha=0.5) # the polygons
gdf4326.set_geometry('geometry2').plot(zorder=10, color='red', ax=ax1) # the rep_points
I am using OSMNX to generate building footprints from a specific point. However, OSMNx doesn't display any footprints. I verified the same place from GoogleMaps where it shows there are many buildings around set point as shown in the below figure:
OSMNx Script
import osmnx
import pandas as pd
import networkx as nx
import osmnx as ox
from IPython.display import Image
import matplotlib.colors as colors
import pprint
ox.config(use_cache=True, log_console=True)
# get a graph for some city
G = ox.graph_from_place('Colac, Victoria, Australia', network_type='drive')
img_folder = 'images'
extension = 'png'
size = 640
dpi = 20
# #-----------------------------------------------------------------------------------------------------
def make_plot(place, point, network_type='drive', bldg_color='orange', dpi=400,
dist=500, default_width=4, street_widths=None):
gdf = ox.footprints.footprints_from_point(point=point, distance=dist)
fig, ax = ox.plot_figure_ground(point=point, dist=dist, network_type=network_type, default_width=default_width,
street_widths=street_widths, save=False, show=False, close=True)
fig, ax = ox.footprints.plot_footprints(gdf, fig=fig, ax=ax, color=bldg_color, set_bounds=False,
save=True, show=False, close=True, filename=place, dpi=dpi)
# #-----------------------------------------------------------------------------------------------------
place = 'Colac_buildings'
#Colac Lat,Lon
point = (-38.338850, 143.599013)
make_plot(place, point)
Image('{}/{}.{}'.format(img_folder, place, extension), height=size, width=size)
Does it mean the information is missing in OSMNx? What is the best way to get the building information or footprints?
Actually I am trying to extract addresses of all buildings in a specific area. Is there any other way to achieve this?
OSMNx uses data from OpenStreetMap, not from Google Maps. For your location -38.338850, 143.599013 there are no buildings in OSM yet (but there are several nearby, e.g. to the west).
To improve this you can create an OSM account and start to improve the map. Besides building footprints you can also add missing addresses to OSM (if you have access to a compatible license). Afterwards you can retrieve them with a geocoder, for example Nominatim.
Starting with a shapefile I obtained from https://s3.amazonaws.com/nyc-tlc/misc/taxi_zones.zip, I'd like to plot the borough of Manhattan, and have outlines for each taxi-zone.
This code rotates each individual taxi zone individually instead of all at once.
import geopandas as gpd
from matplotlib import pyplot as plt
fname = "path_to_shapefile.shp"
df = gpd.read_file(fname)
df = df[df['borough'] == "Manhattan"]
glist = gpd.GeoSeries([g for g in df['geometry']])
glist = glist.rotate(90)
glist.plot()
[EDIT]
I have further refined this to be able to rotate the image programmatically. However, if I add a legend, then that is also rotated, which is not desirable. Still looking for a better solution.
Note, there is also this stackoverflow post (How can I rotate a matplotlib plot through 90 degrees?), however, the solutions that rotate the plot, and not the image, only work with 90 degree rotations.
import geopandas as gpd
from matplotlib import pyplot as plt
import numpy as np
from scipy import ndimage
from matplotlib import transforms
fname = "path_to_shapefile.shp"
df = gpd.read_file(fname)
df = df[df['borough'] == "Manhattan"]
df.plot()
plt.axis("off")
plt.savefig("test.png")
img = plt.imread('test.png')
rotated_img = ndimage.rotate(img, -65)
plt.imshow(rotated_img, cmap=plt.cm.gray)
plt.axis('off')
plt.show()
[EDIT2]
A simple modification to the answer given below by #PMende solved it.
df = gpd.read_file(fname)
df = df[df['borough'] == "Manhattan"]
glist = gpd.GeoSeries([g for g in df['geometry']])
glist = glist.rotate(-65, origin=(0,0))
glist.plot()
The key was rotating all of the objects around a single point, instead of around their individual origins.
[EDIT 3] If anyone is trying to do this, and needs to save the resulting rotated geoseries to a dataframe (say for instance, to color the geometry based on an additional column), you need to create a new one, simply writing
df['geometry'] = glist
does not work. I'm not sure why at the moment. However, the following code worked for me.
new_dataframe = gpd.GeoDataFrame(glist)
new_dataframe = new_dataframe.rename(columns={0:'geometry'}).set_geometry('geometry')
new_dataframe.plot()
If I'm understanding GeoPandas' documentation correctly, you can specify the origin of the rotation of each of your geometries (which by default is the center of each geometry). To get your desired behavior, you can rotate each shape about the same origin.
For example:
import geopandas as gpd
from matplotlib import pyplot as plt
fname = "path_to_shapefile.shp"
df = gpd.read_file(fname)
df = df[df['borough'] == "Manhattan"]
center = df["geometry"].iloc[0].centroid()
glist = gpd.GeoSeries([g for g in df['geometry']])
glist = glist.rotate(90, origin=center)
glist.plot()
I can't test this myself, but it should hopefully get you started in the right direction.
(Though I also agree with #martinfeleis' point about not necessarily wanting to rotate the geometry, but rather the plot.)
I'm trying to plot data around the Antarctica while masking the continent. While I'm using basemap and it has an option to easily mask continents using map.fillcontinents(), the continent considered by basemap includes the ice shelves, which I do not want to mask.
I tried using geopandas from a code I found on the Internet. This works, except the coastline produces an undesired line in what I assume is the beginning/end of the polygon for the Antarctica:
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import geopandas as gpd
import shapely
from descartes import PolygonPatch
lats = np.arange(-90,-59,1)
lons = np.arange(0,361,1)
X, Y = np.meshgrid(lons, lats)
data = np.random.rand(len(lats),len(lons))
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
fig=plt.figure(dpi=150)
ax = fig.add_subplot(111)
m = Basemap(projection='spstere',boundinglat=-60,lon_0=180,resolution='i',round=True)
xi, yi = m(X,Y)
cf = m.contourf(xi,yi,data)
patches = []
selection = world[world.name == 'Antarctica']
for poly in selection.geometry:
if poly.geom_type == 'Polygon':
mpoly = shapely.ops.transform(m, poly)
patches.append(PolygonPatch(mpoly))
elif poly.geom_type == 'MultiPolygon':
for subpoly in poly:
mpoly = shapely.ops.transform(m, poly)
patches.append(PolygonPatch(mpoly))
else:
print(poly, 'blah')
ax.add_collection(PatchCollection(patches, match_original=True,color='w',edgecolor='k'))
The same line appears when I try to use other shapefiles, such as the land one that is available to download for free from Natural Earth Data. So I edited this shapefile in QGIS to remove the borders of the Antarctica. The problem now is that I don't know how to mask everything that's inside the shapefile (and couldn't find how to do it either). I also tried combining the previous code with geopandas by setting the linewidth=0, and adding on top the shapefile I created. The problem is that they are not exactly the same:
Any suggestion on how to mask using a shapefile, or with geopandas but without the line?
Edit: Using Thomas Khün's previous answer with my edited shapefile produces a well masked Antarctica/continents, but the coastline goes outside the round edges of the map:
I uploaded here the edited shapefile I used, but it's the Natural Earth Data 50m land shapefile without the line.
Here an example of how to achieve what you want. I basically followed the Basemap example how to deal with shapefiles and added a bit of shapely magic to restrict the outlines to the map boundaries. Note that I first tried to extract the map outline from ax.patches, but that somehow didn't work, so I defined a circle which has a radius of boundinglat and transformed it using the Basemap coordinate transformation functionality.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
import shapely
from shapely.geometry import Polygon as sPolygon
boundinglat = -40
lats = np.arange(-90,boundinglat+1,1)
lons = np.arange(0,361,1)
X, Y = np.meshgrid(lons, lats)
data = np.random.rand(len(lats),len(lons))
fig, ax = plt.subplots(nrows=1, ncols=1, dpi=150)
m = Basemap(
ax = ax,
projection='spstere',boundinglat=boundinglat,lon_0=180,
resolution='i',round=True
)
xi, yi = m(X,Y)
cf = m.contourf(xi,yi,data)
#adjust the path to the shapefile here:
result = m.readshapefile(
'shapefiles/AntarcticaWGS84_contorno', 'antarctica',
zorder = 10, color = 'k', drawbounds = False)
#defining the outline of the map as shapely Polygon:
rim = [np.linspace(0,360,100),np.ones(100)*boundinglat,]
outline = sPolygon(np.asarray(m(rim[0],rim[1])).T)
#following Basemap tutorial for shapefiles
patches = []
for info, shape in zip(m.antarctica_info, m.antarctica):
#instead of a matplotlib Polygon, create first a shapely Polygon
poly = sPolygon(shape)
#check if the Polygon, or parts of it are inside the map:
if poly.intersects(outline):
#if yes, cut and insert
intersect = poly.intersection(outline)
verts = np.array(intersect.exterior.coords.xy)
patches.append(Polygon(verts.T, True))
ax.add_collection(PatchCollection(
patches, facecolor= 'w', edgecolor='k', linewidths=1., zorder=2
))
plt.show()
The result looks like this:
Hope this helps.
For anyone still trying to figure out a simple way to mask a grid from a shapefile, here is a gallery example from the python package Antarctic-Plots which makes this simple.
from antarctic_plots import maps, fetch, utils
import pyogrio
# fetch a grid and shapefile
grid = fetch.bedmachine(layer='surface')
shape = fetch.groundingline()
# subset the grounding line from the coastline
gdf = pyogrio.read_dataframe(shape)
groundingline = gdf[gdf.Id_text == "Grounded ice or land"]
# plot the grid
fig = maps.plot_grd(grid)
# plot the shapefile
fig.plot(groundingline, pen='1p,red')
fig.show()
# mask the inside region
masked_inside = utils.mask_from_shp(
shapefile=groundingline, xr_grid=grid, masked=True)
masked_inside.plot()
# mask the outside region
masked_outside = utils.mask_from_shp(
shapefile=groundingline, xr_grid=grid, masked=True, invert=False)
masked_outside.plot()