<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Introduction
In this tutorial, you'll explore several techniques for proximity analysis.  In particular, you'll learn how to do such things as
Step1:  You'll work with a dataset from the US Environmental Protection Agency (EPA) that tracks releases of toxic chemicals in Philadelphia, Pennsylvania, USA.
Step2:  You'll also work with a dataset that contains readings from air quality monitoring stations in the same city.
Step3:  Measuring distance
To measure distances between points from two different GeoDataFrames, we first have to make sure that they use the same coordinate reference system (CRS).  Thankfully, this is the case here, where both use EPSG 2272.
Step4:  We also check the CRS to see which units it uses (meters, feet, or something else).  In this case, EPSG 2272 has units of feet.  (If you like, you can check this here.)
It's relatively straightforward to compute distances in GeoPandas.  The code cell below calculates the distance (in feet) between a relatively recent release incident in recent_release and every station in the stations GeoDataFrame.
Step5:  Using the calculated distances, we can obtain statistics like the mean distance to each station.
Step6:  Or, we can get the closest monitoring station.
Step7:  Creating a buffer
If we want to understand all points on a map that are some radius away from a point, the simplest way is to create a buffer.
The code cell below creates a GeoSeries two_mile_buffer containing 12 different Polygon objects.  Each polygon is a buffer of 2 miles (or, 2*5280 feet) around a different air monitoring station.
Step8:  We use folium.GeoJson() to plot each polygon on a map.  Note that since folium requires coordinates in latitude and longitude, we have to convert the CRS to EPSG 4326 before plotting.
Step9:  Now, to test if a toxic release occurred within 2 miles of any monitoring station, we could run 12 different tests for each polygon (to check individually if it contains the point).
But a more efficient way is to first collapse all of the polygons into a MultiPolygon object.  We do this with the unary_union attribute.
Step10:  We use the contains() method to check if the multipolygon contains a point.  We'll use the release incident from earlier in the tutorial, which we know is roughly 3781 feet to the closest monitoring station.
Step11:  But not all releases occured within two miles of an air monitoring station!
<ASSISTANT_TASK:>
Python Code:
#$HIDE_INPUT$
import folium
from folium import Marker, GeoJson
from folium.plugins import HeatMap
import pandas as pd
import geopandas as gpd
Explanation: Introduction
In this tutorial, you'll explore several techniques for proximity analysis.  In particular, you'll learn how to do such things as:
- measure the distance between points on a map, and
- select all points within some radius of a feature.
End of explanation
releases = gpd.read_file("../input/geospatial-learn-course-data/toxic_release_pennsylvania/toxic_release_pennsylvania/toxic_release_pennsylvania.shp") 
releases.head()
Explanation: You'll work with a dataset from the US Environmental Protection Agency (EPA) that tracks releases of toxic chemicals in Philadelphia, Pennsylvania, USA.
End of explanation
stations = gpd.read_file("../input/geospatial-learn-course-data/PhillyHealth_Air_Monitoring_Stations/PhillyHealth_Air_Monitoring_Stations/PhillyHealth_Air_Monitoring_Stations.shp")
stations.head()
Explanation: You'll also work with a dataset that contains readings from air quality monitoring stations in the same city.
End of explanation
print(stations.crs)
print(releases.crs)
Explanation: Measuring distance
To measure distances between points from two different GeoDataFrames, we first have to make sure that they use the same coordinate reference system (CRS).  Thankfully, this is the case here, where both use EPSG 2272.
End of explanation
# Select one release incident in particular
recent_release = releases.iloc[360]
# Measure distance from release to each station
distances = stations.geometry.distance(recent_release.geometry)
distances
Explanation: We also check the CRS to see which units it uses (meters, feet, or something else).  In this case, EPSG 2272 has units of feet.  (If you like, you can check this here.)
It's relatively straightforward to compute distances in GeoPandas.  The code cell below calculates the distance (in feet) between a relatively recent release incident in recent_release and every station in the stations GeoDataFrame.
End of explanation
print('Mean distance to monitoring stations: {} feet'.format(distances.mean()))
Explanation: Using the calculated distances, we can obtain statistics like the mean distance to each station.
End of explanation
print('Closest monitoring station ({} feet):'.format(distances.min()))
print(stations.iloc[distances.idxmin()][["ADDRESS", "LATITUDE", "LONGITUDE"]])
Explanation: Or, we can get the closest monitoring station.
End of explanation
two_mile_buffer = stations.geometry.buffer(2*5280)
two_mile_buffer.head()
Explanation: Creating a buffer
If we want to understand all points on a map that are some radius away from a point, the simplest way is to create a buffer.
The code cell below creates a GeoSeries two_mile_buffer containing 12 different Polygon objects.  Each polygon is a buffer of 2 miles (or, 2*5280 feet) around a different air monitoring station.
End of explanation
# Create map with release incidents and monitoring stations
m = folium.Map(location=[39.9526,-75.1652], zoom_start=11)
HeatMap(data=releases[['LATITUDE', 'LONGITUDE']], radius=15).add_to(m)
for idx, row in stations.iterrows():
    Marker([row['LATITUDE'], row['LONGITUDE']]).add_to(m)
    
# Plot each polygon on the map
GeoJson(two_mile_buffer.to_crs(epsg=4326)).add_to(m)
# Show the map
m
Explanation: We use folium.GeoJson() to plot each polygon on a map.  Note that since folium requires coordinates in latitude and longitude, we have to convert the CRS to EPSG 4326 before plotting.
End of explanation
# Turn group of polygons into single multipolygon
my_union = two_mile_buffer.geometry.unary_union
print('Type:', type(my_union))
# Show the MultiPolygon object
my_union
Explanation: Now, to test if a toxic release occurred within 2 miles of any monitoring station, we could run 12 different tests for each polygon (to check individually if it contains the point).
But a more efficient way is to first collapse all of the polygons into a MultiPolygon object.  We do this with the unary_union attribute.
End of explanation
# The closest station is less than two miles away
my_union.contains(releases.iloc[360].geometry)
Explanation: We use the contains() method to check if the multipolygon contains a point.  We'll use the release incident from earlier in the tutorial, which we know is roughly 3781 feet to the closest monitoring station.
End of explanation
# The closest station is more than two miles away
my_union.contains(releases.iloc[358].geometry)
Explanation: But not all releases occured within two miles of an air monitoring station!
End of explanation
<END_TASK>