Geofencing is often used tool in Geographic Data Science, especially in marketing, security and zoning applications. The example in the above GIF shows an app that alerts vehicles based on their location and London’s Congestion Charge Zone (CCZ). The application calculates the congestion charge and tracks the number of vehicles inside the congestion area at a given time.
The concept of geofencing is straightforward, yet it is a powerful technique that enhances location applications. Simply put, a geofence is a defining virtual boundary around geographic objects or an area, so that every time a user enters or leaves the boundary perimeters, actions or notifications can be triggered. With the increased use of smartphones, GPS, and location services, geofencing becomes an indispensable tool in location data analytics and intelligence.
In this tutorial, we use a GPS Trajectory dataset. It contains GPS points (latitude and longitude) with timestamps. It also provides unique track_id for each trajectory. Let us read the data with the Pandas library.
import pandas as pd
import geopandas as gpd
import plotly_express as px
import matplotlib.pyplot as plt
!wget https://www.dropbox.com/s/ejev7z29lzirbo5/GPSTrajectory.zip
!unzip GPSTrajectory.zip
df = pd.read_csv('GPSTrajectory/go_track_trackspoints.csv')
df.head()
The first five rows of the dataset are shown below. We have latitude and longitude as well as track_id and time.

We convert the Dataframe into Geodataframe, which allows us to perform geofencing. Converting the data frame to Geodataframe with Geopandas is straightforward.
gdf = gpd.GeoDataFrame( df, geometry=gpd.points_from_xy(df.longitude, df.latitude))
gdf.head()
Let us plot a map of the dataset. We use Plotly Express here as offers an easy interface and high-level API for plotting with Plotly. We need to set Mapbox token here.
px.set_mapbox_access_token("pk.eyJ1Ijoic2hha2Fzb20iLCJhIjoiY2plMWg1NGFpMXZ5NjJxbjhlM2ttN3AwbiJ9.RtGYHmreKiyBfHuElgYq_w")
px.scatter_mapbox(gdf, lat="latitude", lon="longitude" ,size_max=6, zoom=8, width=1200, height=800)
The plot below shows all the points in the dataset. As you can see, these trajectories fall in 3 different cities.

Let us filter out other cities and focus on the central city(Aracuja) since most of the data fall here.
gdf = df[(gdf['latitude']<-10.80) & (gdf['longitude']>-37.5)]
px.scatter_mapbox(gdf, lat="latitude", lon="longitude" ,size_max=6, zoom=8, width=1200, height=800)
Now we have only points that we are interested in Aracuja city, Brazil.

Now that we have cleaned the data let us do geofencing.
Geofencing
First, we need to have an area that marks the geofence. Let us download an area that I have created which is in the city centre.
# Get the data
!wget https://www.dropbox.com/s/e9g5n7e7iwnue4x/CENTERAREA.zip
!unzip CENTERAREA.zip
polygon = gpd.read_file("CENTERAREA.geojson")
Let us plot the polygon with Geopandas and overlay the points to see both data.
# Plot track_id 1 points over the Buffer Polygon
fig, ax = plt.subplots(figsize=(10,10))
gdf.plot(ax=ax, color='black')
polygon.plot(ax=ax)
#plt.tight_layout()
#plt.axis('off')
plt.show()
Here is the map overlaying both the polygon and points data, shown below.

Now, let us perform what we call Point in Polygon (PIP). We can use "within" operation in Geopandas to check whether the points are inside the polygon or not.
mask = (polygon.loc[0, 'geometry'])
pip_mask_geofence = gdf.within(mask)
The above code will return a series of False and True values depending on whether the point is inside the polygon or not. Let us add a column to our data that marks False and True values. We call this "geofence".
#Create PIP mask
gdf.loc[:,'geofence'] = pip_mask_geofence
gdf.sample(5)
Now, our data has a "geofence" column. Let us look at a sample of the data.

As you can see, there is addition column in the data: geofence. The first four rows show that they are not inside the geofence, while the last row indicates that it is inside the geofence. Let us replace False and True values with In and Out values, respectively.
# Replace True with In and False with Out
gdf['geofence'] = gdf['geofence'].replace({True: 'In', False: 'Out'})
We can see now if we plot a map with Plotly Express and use geofence as a colour, which points are inside or outside the geofencing area.
px.scatter_mapbox(gdf, lat="latitude", lon="longitude", color="geofence", size='track_id' ,
size_max=6, zoom=12, width=1200, height=800)

Finally, we can animate the points to visualize with track movements. The annimation can be accomplished easily with Plotly Express. However, it can not visualize all our points at one time due to some limitations. Let us visualize one Track ID now.
px.scatter_mapbox(gdf[gdf["track_id"]== 23], lat="latitude", lon="longitude", color="geofence", size='track_id', animation_frame='time', size_max=10, zoom=12, width=1200, height=800)
Here is GIF animation of our simple Geofencing example. Once the track gets inside the geofence area, the points become red, while being blue outside of the marked area.

Conclusion
In this tutorial, we have seen how to do a simple Geofencing example with Python using Pandas, Geopandas and Plotly Express. If you want to experiment with the code, here is the link to Google Colab Notebook.