# Import modules
import numpy as np
import matplotlib.pyplot as plt
import geopandas as gpd
86 Weather network coverage
In this exercise we will examine the spatial coverage and representativeness of a weather network by applying a circular buffer to each station, and then applying intersection, difference, and dissolve operations to identify the network’s current reach but also to pinpoint critical gaps in coverage, which can be used by network managers to guide network expansion or optimization to ensure more uniform spatial representation.
Define geographic and projected coordinate reference systems
A Coordinate Reference System (CRS) is a framework that defines how the two-dimensional, flat surface of a map relates to the real places on the three-dimensional surface of the Earth. CRSs ensure that locations are accurately represented in spatial analyses and visualizations. There are two primary types of CRSs: Geographic Coordinate Systems and Projected Coordinate Systems.
Geographic Coordinate Systems use a three-dimensional spherical surface to define locations on the Earth. They express locations as latitude and longitude, which are angles measured from the Earth’s center to a point on the Earth’s surface. This system is great for global and regional mapping because it represents the Earth’s curvature.
On the other hand, Projected Coordinate Systems transform geographic coordinates into Cartesian coordinates (x and y values) on a flat surface. This projection process introduces some distortions, but it allows for more practical measurements and calculations in units like meters, making projected coordinates ideal for detailed local maps where accurate distances and areas are essential. Each PCS is designed for specific regions or purposes, minimizing distortions within its intended area of use. You will notice that computations of area and length, and even the determinatino of buffers in GeoPandas require changing the CRS from geographic to projected coordinates.
# Define projected and geographic coordinate reference systems
= 32614 # UTM Zone 14
utm14 = 4326 # WGS84 wgs84
Read and inspect datasets
# Read stations
= gpd.read_file('../datasets/KS_mesonet_geoinfo.csv')
stations 'lon','lat']] = stations[['lon','lat']].astype('float')
stations[['geometry'] = gpd.points_from_xy(x=stations['lon'], y=stations['lat'], crs=wgs84) stations[
# Read counties map (only for visuals, not used in any core computation) (already in WGS84)
= gpd.read_file('../datasets/spatial/us_county_5m.geojson') #.to_crs(epsg_wgs)
counties = counties['STATE_NAME'] == 'Kansas'
idx_state = counties.loc[idx_state].reset_index(drop=True) counties
# Creaet figure using object-based syntax
= plt.subplots(1, 1, figsize=(6,4))
fig, ax =ax, facecolor='None', edgecolor='gray', linewidth=0.25)
counties.plot(ax=ax, facecolor='tomato', edgecolor='k', markersize=5)
stations.plot(ax'Longitude')
plt.xlabel('Latitude')
plt.ylabel( plt.show()
Create buffer
We will assume that each weather station is representative of an area with a 50 km radius. While on a daily basis locations within this buffer area can exhibit weather differenes, overall climatic trends should be fairly consistent within this radius.
# Create a buffer
'buffer'] = stations.to_crs(utm14).buffer(50_000).to_crs(wgs84)
stations[
# Set buffer as geometry for easy plotting and analysis in posterior steps
'buffer', inplace=True) stations.set_geometry(
# Creaet figure using object-based syntax
= plt.subplots(1, 1, figsize=(6,4))
fig, ax =ax, facecolor='None', edgecolor='gray', linewidth=0.5)
counties.plot(ax=ax, facecolor='None', edgecolor='k')
stations.plot(ax'Longitude')
plt.xlabel('Latitude')
plt.ylabel( plt.show()
Find area represented by the stations
# Find represented areas of the state
= counties.overlay(stations, how='intersection')
represented_area =0.5, edgecolor='k', cmap='tab10');
represented_area.plot(alpha'Longitude')
plt.xlabel('Latitude')
plt.xlabel( plt.show()
87 Find area not represented by the stations
# Find non-represented county areas
= counties.overlay(stations, how='difference')
non_represented_area =0.5, edgecolor='k', cmap='tab10');
non_represented_area.plot(alpha'Longitude')
plt.xlabel('Latitude')
plt.ylabel( plt.show()
Dissolve polygons
Note that the previous polygons still retain the county boundaries. If we want to compute the area of each polygon, we first need to dissolve the internal boundaries. This operation will create a single MultiPolygon
feature with all the polygons inside. To separate each polygon, we can use the explode()
method.
# Dissolve polygons
= non_represented_area.dissolve()
non_represented_area = non_represented_area.explode(index_parts=True).reset_index(drop=True)
non_represented_area =0.5, edgecolor='k', cmap='tab10');
non_represented_area.plot(alpha'Longitude')
plt.xlabel('Latitude')
plt.ylabel( plt.show()
Find largest polygon not covered by the network
# Find largest non-represented area
'area'] = non_represented_area.to_crs(utm14).area
non_represented_area['perimeter'] = non_represented_area.to_crs(utm14).length
non_represented_area[= non_represented_area['area'].argmax()
idx_largest_area 3) non_represented_area.head(
STATEFP | COUNTYFP | COUNTYNS | AFFGEOID | GEOID | NAME | NAMELSAD | STUSPS | STATE_NAME | LSAD | ALAND | AWATER | area | perimeter | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20 | 137 | 00485032 | 0500000US20137 | 20137 | Norton | Norton County | KS | Kansas | 06 | 2274122718 | 8421464 | 3.187139e+09 | 300298.876438 | POLYGON ((-99.37539 37.00018, -99.40702 36.999... |
1 | 20 | 137 | 00485032 | 0500000US20137 | 20137 | Norton | Norton County | KS | Kansas | 06 | 2274122718 | 8421464 | 1.542991e+07 | 21123.638265 | POLYGON ((-98.30280 37.53727, -98.29914 37.535... |
2 | 20 | 137 | 00485032 | 0500000US20137 | 20137 | Norton | Norton County | KS | Kansas | 06 | 2274122718 | 8421464 | 1.996688e+07 | 24035.816406 | POLYGON ((-99.30669 38.04489, -99.31468 38.001... |
Display the largest area not represented by the network
# Find non-represented county areas
# We need to restore geometry to points (not buffers)
'geometry',inplace=True)
stations.set_geometry(
# Creaet figure showing largest non-represented area
= plt.subplots(1, 1, figsize=(6,4))
fig, ax =ax, facecolor='None', edgecolor='gray', linewidth=0.25)
counties.plot(ax=ax, alpha=0.5, edgecolor='k', cmap='tab10');
non_represented_area.plot(ax=ax, facecolor='tomato', edgecolor='k', markersize=5)
stations.plot(ax'geometry'].plot(ax=ax, facecolor='None',edgecolor='red')
non_represented_area.loc[[idx_largest_area],'Longitude')
plt.xlabel('Latitude')
plt.ylabel(
plt.show()
Practice
How do the non-represented areas change in shape and size when changing the buffer radius from 50 km to 10 km?
Using the
area
andperimeter
of the polygons representing areas without station coverage, compute additional metrics to identify larger polygons with different shape properties. One option is to use the Shape Index, SI = \frac{Perimeter}{\sqrt{Area}}, which decreases as the area increases, for a given perimeter. This makes it a suitable metric for identifying large polygons without being overly penalized for having a larger perimeter.