How to Work with Features in FastFuels SDK
This guide shows you how to work with geographic features (roads and water bodies) in your domains using the FastFuels SDK.
Get Features for a Domain
Features are always associated with a domain. To access features:
from fastfuels_sdk import Features
# Get Feature data from an existing domain ID.
features = Features.from_domain_id("your_domain_id")
Check what features exist
if features.road: print("Domain has road features") if features.water: print("Domain has water features")
## Create Road Features
### Basic Road Feature Creation
The simplest way to create road features is using OpenStreetMap (OSM) data:
```python
# Using the convenience method
road = features.create_road_feature_from_osm()
# Or the general method
road = features.create_road_feature(sources="OSM")
Update Features Instance
To automatically update the Features object when creating road features:
# Create features and update the Features instance
road = features.create_road_feature_from_osm(in_place=True)
# Now features.road points to the new road features
assert features.road is road
Create Road Features from GeoJSON/GeoDataFrame
You can also create road features from your own GeoJSON data or GeoPandas GeoDataFrame. This is particularly useful for: - Custom road data not available in OSM - Local coordinate system domains (OSM is not supported for local CRS) - Integration with existing geospatial workflows
Using GeoJSON
# Define your road areas as GeoJSON
geojson_data = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-120.5, 39.5],
[-120.5, 39.6],
[-120.4, 39.6],
[-120.4, 39.5],
[-120.5, 39.5]
]]
},
"properties": {}
}]
}
# Create road features from GeoJSON
road = features.create_road_feature(sources="geojson", geojson=geojson_data)
Using GeoPandas GeoDataFrame
import geopandas as gpd
from shapely.geometry import Polygon
# Create a GeoDataFrame with road polygons
polygon = Polygon([
(-120.5, 39.5),
(-120.5, 39.6),
(-120.4, 39.6),
(-120.4, 39.5),
(-120.5, 39.5)
])
gdf = gpd.GeoDataFrame({
'geometry': [polygon],
'name': ['highway_1']
}, crs="EPSG:4326")
# Create road features from GeoDataFrame
road = features.create_road_feature_from_geodataframe(gdf)
# Or with in_place=True
road = features.create_road_feature_from_geodataframe(gdf, in_place=True)
!!! note "GeoJSON Requirements" - Must contain Polygon or MultiPolygon geometries (area-based features) - Geometries will be automatically clipped to the domain boundary - For non-local CRS, geometries are transformed to match the domain's CRS
Wait for Processing
Road feature creation happens asynchronously. Wait for processing to complete:
# Wait with status updates
road.wait_until_completed(
step=5, # Check every 5 seconds
timeout=300, # Wait up to 5 minutes
verbose=True # Print status updates
)
print(f"Status: {road.status}") # Should print 'completed'
Create Water Features
Basic Water Feature Creation
Similar to roads, create water features from OSM data:
# Using the convenience method
water = features.create_water_feature_from_osm()
# Or the general method
water = features.create_water_feature(sources="OSM")
# Wait for processing
water.wait_until_completed(in_place=True)
Update Features Instance
To automatically update the Features object:
Retrieve Feature Data (GeoJSON)
Once features are processed (status "completed"), you can retrieve the actual geometry and attribute data as GeoJSON.
Get Road Feature Data
# Wait for processing to complete
road.wait_until_completed()
# Get first page of road data (default: 50 features per page)
data = road.get_data()
print(f"Page {data.current_page}, {len(data.features)} of {data.total_items} features")
# Get specific page with custom size
data = road.get_data(page=1, size=100)
# Get ALL road features at once (handles pagination automatically)
all_data = road.get_all_data()
print(f"Total features: {len(all_data.features)}")
Get Water Feature Data
# Wait for processing to complete
water.wait_until_completed()
# Get first page of water data
data = water.get_data()
print(f"Page {data.current_page}, {len(data.features)} of {data.total_items} features")
# Get ALL water features at once
all_data = water.get_all_data()
print(f"Total features: {len(all_data.features)}")
Convert to GeoDataFrame
The feature data can be easily converted to a GeoPandas GeoDataFrame for analysis:
import geopandas as gpd
# Get all road features
all_roads = road.get_all_data()
# Convert to GeoDataFrame
gdf = gpd.GeoDataFrame.from_features(
[f.to_dict() for f in all_roads.features],
crs="EPSG:4326"
)
# Now you can use all GeoPandas functionality
print(gdf.head())
gdf.plot()
Pagination Details
page: Zero-indexed page number (default: 0)size: Number of features per page (1-1000, default: 50)get_all_data(): Automatically iterates through all pages and returns combined results
The response includes pagination metadata:
- current_page: Current page number
- page_size: Features per page
- total_items: Total number of features available
- crs: Coordinate reference system information
Get Updated Feature Data
Refresh All Features
To get the latest data for all features:
# Get new instance with fresh data
updated_features = features.get()
# Or update in place
features.get(in_place=True)
Refresh Specific Features
To update just road or water features:
if features.road:
# Get new instance with fresh data
updated_road = features.road.get()
# Or update in place
features.road.get(in_place=True)
if features.water:
# Get new instance with fresh data
updated_water = features.water.get()
# Or update in place
features.water.get(in_place=True)
Delete Features
Delete Road Features
To remove road features from a domain:
if features.road:
# Delete the road features
features.road.delete()
# Refresh features to reflect deletion
features.get(in_place=True)
assert features.road is None
Delete Water Features
Similarly for water features:
if features.water:
# Delete the water features
features.water.delete()
# Refresh features to reflect deletion
features.get(in_place=True)
assert features.water is None
Complete Example
Here's a complete example showing how to work with both road and water features:
from fastfuels_sdk import Features
# Get domain and features
features = Features.from_domain_id("your_domain_id")
# Create road features and wait for completion
road = features.create_road_feature_from_osm(in_place=True)
road.wait_until_completed(verbose=True)
# Create water features and wait for completion
water = features.create_water_feature_from_osm(in_place=True)
water.wait_until_completed(verbose=True)
# Retrieve the actual feature data as GeoJSON
if features.road and features.road.status == "completed":
road_data = features.road.get_all_data()
print(f"Retrieved {len(road_data.features)} road features")
if features.water and features.water.status == "completed":
water_data = features.water.get_all_data()
print(f"Retrieved {len(water_data.features)} water features")
# Convert to GeoDataFrame for analysis (optional)
import geopandas as gpd
if road_data.features:
roads_gdf = gpd.GeoDataFrame.from_features(
[f.to_dict() for f in road_data.features],
crs="EPSG:4326"
)
print(f"Roads GeoDataFrame shape: {roads_gdf.shape}")
# Clean up when done
if features.road:
features.road.delete()
if features.water:
features.water.delete()