A type-safe, Pydantic-based library for validating and working with GeoJSON data according to RFC 7946 specification.
| GeoJSON Objects | Status | Description |
|---|---|---|
| Point | âś… | A single geographic coordinate |
| MultiPoint | âś… | Multiple points |
| LineString | âś… | A sequence of connected points forming a line |
| MultiLineString | âś… | Multiple line strings |
| Polygon | âś… | A closed area, optionally with holes |
| MultiPolygon | âś… | Multiple polygons |
| GeometryCollection | âś… | Collection of different geometry types |
| Feature | âś… | Geometry with properties |
| FeatureCollection | âś… | Collection of features |
pydantic-geojson is compatible with Python 3.9 and up.
The recommended way to install is via poetry:
poetry add pydantic_geojsonUsing pip to install is also possible:
pip install pydantic_geojsonfrom pydantic_geojson import PointModel
# Create a point
point = PointModel(
type="Point",
coordinates=[-105.01621, 39.57422]
)
print(point)
# type='Point' coordinates=Coordinates(lon=-105.01621, lat=39.57422)A geometry with properties.
from pydantic_geojson import FeatureModel
data = {
"type": "Feature",
"properties": {
"name": "Dinagat Islands",
"population": 10000
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-80.724878, 35.265454],
[-80.722646, 35.260338],
[-80.720329, 35.260618],
[-80.71681, 35.255361],
[-80.704793, 35.268397],
[-80.724878, 35.265454]
]
]
}
}
feature = FeatureModel(**data)A collection of features.
from pydantic_geojson import FeatureCollectionModel
data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-80.870885, 35.215151]
},
"properties": {
"name": "Location 1"
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-80.724878, 35.265454],
[-80.722646, 35.260338],
[-80.720329, 35.260618],
[-80.704793, 35.268397],
[-80.724878, 35.265454]
]
]
},
"properties": {
"name": "Location 2"
}
}
]
}
feature_collection = FeatureCollectionModel(**data)You can define typed properties models for type-safe feature properties:
from pydantic import BaseModel
from pydantic_geojson import FeatureModel
class CityProperties(BaseModel):
name: str
population: int
country: str
class CityFeature(FeatureModel):
properties: CityProperties
data = {
"type": "Feature",
"properties": {
"name": "New York",
"population": 8336817,
"country": "USA"
},
"geometry": {
"type": "Point",
"coordinates": [-74.006, 40.7128]
}
}
city = CityFeature(**data)
print(city.properties.name) # "New York"
print(city.properties.population) # 8336817pydantic-geojson automatically validates:
- Coordinate ranges: Longitude must be between -180 and 180, latitude between -90 and 90
- Geometry types: Ensures correct type strings according to RFC 7946
- Structure: Validates GeoJSON object structure and required fields
- Type safety: Full type checking with Pydantic
from pydantic_geojson import PointModel
from pydantic import ValidationError
try:
point = PointModel(
type="Point",
coordinates=[200, 50] # Invalid longitude (> 180)
)
except ValidationError as e:
print(e)
# 1 validation error for PointModel
# coordinates.0
# Input should be less than or equal to 180 [type=less_than_equal, input_value=200, input_type=int]pydantic-geojson works seamlessly with FastAPI for automatic API documentation and OpenAPI schema generation. FastAPI automatically generates interactive API documentation (Swagger UI) with proper GeoJSON schemas.
from fastapi import FastAPI
from pydantic_geojson import FeatureCollectionModel, FeatureModel
app = FastAPI()
@app.post("/features", response_model=FeatureModel)
async def create_feature(feature: FeatureModel):
"""Create a new GeoJSON feature."""
# Your business logic here
return feature
@app.get("/features", response_model=FeatureCollectionModel)
async def get_features():
"""Get all features as a FeatureCollection."""
return FeatureCollectionModel(
type="FeatureCollection",
features=[
FeatureModel(
type="Feature",
geometry={
"type": "Point",
"coordinates": [-105.01621, 39.57422]
}
)
]
)You can use any GeoJSON geometry type in your FastAPI endpoints:
from fastapi import FastAPI
from pydantic_geojson import (
PointModel,
PolygonModel,
LineStringModel,
FeatureModel,
FeatureCollectionModel
)
app = FastAPI()
@app.post("/points", response_model=PointModel)
async def create_point(point: PointModel):
"""Create a Point geometry."""
return point
@app.post("/polygons", response_model=PolygonModel)
async def create_polygon(polygon: PolygonModel):
"""Create a Polygon geometry."""
return polygon
@app.post("/linestrings", response_model=LineStringModel)
async def create_linestring(linestring: LineStringModel):
"""Create a LineString geometry."""
return linestringDefine typed properties models for type-safe feature properties in your API:
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic_geojson import FeatureModel, FeatureCollectionModel
app = FastAPI()
class CityProperties(BaseModel):
name: str
population: int
country: str
area_km2: float
class CityFeature(FeatureModel):
properties: CityProperties
@app.post("/cities", response_model=CityFeature)
async def create_city(city: CityFeature):
"""Create a city feature with typed properties."""
# Access typed properties
print(f"City: {city.properties.name}")
print(f"Population: {city.properties.population}")
return city
@app.get("/cities", response_model=FeatureCollectionModel)
async def get_cities():
"""Get all cities as a FeatureCollection."""
return FeatureCollectionModel(
type="FeatureCollection",
features=[
CityFeature(
type="Feature",
properties=CityProperties(
name="New York",
population=8336817,
country="USA",
area_km2=783.8
),
geometry={
"type": "Point",
"coordinates": [-74.006, 40.7128]
}
)
]
)FastAPI automatically generates OpenAPI schemas for all pydantic-geojson models. The generated schemas include:
- Proper GeoJSON structure - All geometry types, Features, and FeatureCollections
- Coordinate validation - Longitude (-180 to 180) and latitude (-90 to 90) constraints
- Type definitions - Complete type information for all fields
- Example values - Sample data in the documentation
Access the interactive API documentation at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
from fastapi import FastAPI, HTTPException
from pydantic_geojson import FeatureModel, FeatureCollectionModel
from typing import List
app = FastAPI()
# In-memory storage (use a database in production)
features_db: List[FeatureModel] = []
@app.post("/features", response_model=FeatureModel, status_code=201)
async def create_feature(feature: FeatureModel):
"""Create a new GeoJSON feature."""
features_db.append(feature)
return feature
@app.get("/features", response_model=FeatureCollectionModel)
async def list_features():
"""List all features as a FeatureCollection."""
return FeatureCollectionModel(
type="FeatureCollection",
features=features_db
)
@app.get("/features/{feature_id}", response_model=FeatureModel)
async def get_feature(feature_id: int):
"""Get a specific feature by ID."""
if feature_id >= len(features_db):
raise HTTPException(status_code=404, detail="Feature not found")
return features_db[feature_id]
@app.put("/features/{feature_id}", response_model=FeatureModel)
async def update_feature(feature_id: int, feature: FeatureModel):
"""Update a specific feature."""
if feature_id >= len(features_db):
raise HTTPException(status_code=404, detail="Feature not found")
features_db[feature_id] = feature
return feature
@app.delete("/features/{feature_id}", status_code=204)
async def delete_feature(feature_id: int):
"""Delete a specific feature."""
if feature_id >= len(features_db):
raise HTTPException(status_code=404, detail="Feature not found")
features_db.pop(feature_id)pydantic-geojson is designed to work well with popular Python geospatial libraries:
Built on Pydantic, so you get all the benefits:
- Type validation
- JSON serialization/deserialization
- Model configuration
- Field validation
Perfect integration for building GeoJSON APIs with automatic OpenAPI documentation.
You can convert between pydantic-geojson and GeoPandas:
import geopandas as gpd
from pydantic_geojson import FeatureCollectionModel
# Convert FeatureCollection to GeoDataFrame
feature_collection = FeatureCollectionModel(...)
geojson_dict = feature_collection.model_dump()
gdf = gpd.GeoDataFrame.from_features(geojson_dict["features"])
# Convert GeoDataFrame to FeatureCollection
features = [
FeatureModel(**feature)
for feature in gdf.to_dict("records")
]Convert to/from Shapely geometries:
from shapely.geometry import Point as ShapelyPoint
from pydantic_geojson import PointModel
# Pydantic GeoJSON to Shapely
pydantic_point = PointModel(type="Point", coordinates=[-105.01621, 39.57422])
shapely_point = ShapelyPoint(
pydantic_point.coordinates.lon,
pydantic_point.coordinates.lat
)
# Shapely to Pydantic GeoJSON
shapely_geom = ShapelyPoint(-105.01621, 39.57422)
pydantic_point = PointModel(
type="Point",
coordinates=[shapely_geom.x, shapely_geom.y]
)Run the test suite:
poetry run pytestContributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.






