Accessing MapStand WFS Data with GeoPandas
This tutorial walks you through:- Connecting to a Web Feature Service (WFS) endpoint
- Downloading GeoJSON data with requests
- Loading it into GeoPandas
- Performing a spatial join to find the nearest features
Prerequisite:
Before starting, make sure you have the following libraries installed:
pip install geopandas requests geojson python-dotenv
You’ll also need a MapStand API Access Token.
Create a
.env
file in your working directory and add your key:MPS_API_KEY=your_api_key_here
Details on how to find your API access token can be found here.
Step 1: Import Libraries and Load Environment Variables
import geopandas as gpd
import requests
import geojson
import os
from dotenv import load_dotenv
# Load API keys and environment variables
load_dotenv()
Step 2: Define a Helper Function to Read WFS Data
Before fetching multiple WFS layers, it’s helpful to create a helper function. This avoids repeating the same request and parsing steps, making your code cleaner and easier to reuse.
def read_wfs(url, params, typename, crs='EPSG:4326', cql_filter=""):
"""
Fetch a WFS layer and return it as a GeoDataFrame.
Args:
url (str): WFS base URL
params (dict): Common WFS parameters
typename (str): Name of the layer to request
crs (str): Coordinate Reference System (default: 'EPSG:4326')
cql_filter (str): Optional CQL filter for querying features
Returns:
geopandas.GeoDataFrame: The requested layer as a GeoDataFrame
"""
params = params.copy()
params.update({
"typename": typename,
"srsName": crs,
"CQL_FILTER": cql_filter
})
response = requests.get(url, params=params)
response.raise_for_status()
data = geojson.loads(response.content)
gdf = gpd.GeoDataFrame.from_features(data, crs=crs)
print(f"Loaded {len(gdf)} features from {typename}")
return gdf
Step 3: Define WFS Connection Parameters
Details of connection parameters and how to find typenames can be found here.
url = "https://hub.mapstand.com/gs/ows?"
params = {
"version": "1.3.0",
"service": "WFS",
"request": "GetFeature",
"outputFormat": "application/json",
"apikey": os.getenv('MPS_API_KEY')
}
crs = 'EPSG:3857' # Web Mercator projection used for distance calculation in meters
cql_filter = "glbl_country = 'United Kingdom'"
Step 4: Fetch Geospatial Layers
We’ll pull two layers:
- Industrial facilities
- Hydrogen plants
industrial_facility_gdf = read_wfs(
url, params, "phoenix:industrial_facility", crs, cql_filter
)
hydrogen_plant_gdf = read_wfs(
url, params, "phoenix:hydrogen_plant", crs, cql_filter
)
Step 5: Perform Spatial Calculations
For this example, we're going to perform a spatial join to find the nearest hydrogen plant to each industrial facility.
GeoPandas provides the function:
gpd.sjoin_nearest()
which matches each feature from the left GeoDataFrame with its nearest neighbor from the right.nearest = gpd.sjoin_nearest(
industrial_facility_gdf,
hydrogen_plant_gdf,
how="left",
max_distance=10_000, # limit to 10 km
distance_col="distance_m",
lsuffix="industrial_facility",
rsuffix="hydrogen_plant"
)
nearest.head()
Step 6: Save the Result (Optional)
You can save your joined dataset to a GeoPackage or Shapefile:
nearest.to_file("nearest_hydrogen_facilities.gpkg", layer="uk_facilities", driver="GPKG")
Step 7: Visualise (Optional)
Quick map preview using GeoPandas and matplotlib tools:
import matplotlib.pyplot as plt
# Only keep industrial facilities that successfully matched a hydrogen plant
joined_facilities = nearest.dropna(subset=['distance_m'])
# Scale marker size based on distance (closer → larger)
max_size = 200
min_size = 20
distances = joined_facilities['distance_m']
marker_sizes = min_size + (distances.max() - distances) / distances.max() * (max_size - min_size)
fig, ax = plt.subplots(figsize=(10, 10))
# Plot hydrogen plants
hydrogen_plant_gdf.plot(
ax=ax, color='blue', markersize=50, label='Hydrogen Plants', alpha=0.7
)
# Plot industrial facilities (matched) with size scaled by distance
joined_facilities.plot(
ax=ax, color='red', markersize=marker_sizes, label='Industrial Facilities', alpha=0.7
)
plt.title("Industrial Facilities and Nearest Hydrogen Plants")
plt.legend()
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.show()