pydantic-geojson 🌍
A type-safe, Pydantic-based library for validating and working with GeoJSON data according to RFC 7946 specification.
Supported GeoJSON Objects
| 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 |
Installation
pydantic-geojson is compatible with Python 3.9 and up.
The recommended way to install is via poetry:
poetry add pydantic_geojson
Using pip to install is also possible:
pip install pydantic_geojson
Quick Start
from 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)
GeoJSON Types with Visualizations
| Type | Visualization | Usage |
|---|---|---|
| Point A single geographic coordinate |
from pydantic_geojson import PointModel
data = {
"type": "Point",
"coordinates": [-105.01621, 39.57422]
}
point = PointModel(**data)
|
|
| MultiPoint Multiple points |
from pydantic_geojson import MultiPointModel
data = {
"type": "MultiPoint",
"coordinates": [
[-105.01621, 39.57422],
[-80.666513, 35.053994]
]
}
multi_point = MultiPointModel(**data)
|
|
| LineString A sequence of connected points forming a line |
from pydantic_geojson import LineStringModel
data = {
"type": "LineString",
"coordinates": [
[-99.113159, 38.869651],
[-99.0802, 38.85682],
[-98.822021, 38.85682],
[-98.448486, 38.848264]
]
}
line_string = LineStringModel(**data)
|
|
| MultiLineString Multiple line strings |
from pydantic_geojson import MultiLineStringModel
data = {
"type": "MultiLineString",
"coordinates": [
[[-105.019898, 39.574997],
[-105.019598, 39.574898],
[-105.019061, 39.574782]],
[[-105.017173, 39.574402],
[-105.01698, 39.574385],
[-105.016636, 39.574385]]
]
}
multi_line_string = MultiLineStringModel(**data)
|
|
| Polygon A closed area, optionally with holes |
from pydantic_geojson import PolygonModel
data = {
"type": "Polygon",
"coordinates": [
[[100, 0],
[101, 0],
[101, 1],
[100, 1],
[100, 0]]
]
}
polygon = PolygonModel(**data)
|
|
| MultiPolygon Multiple polygons |
from pydantic_geojson import MultiPolygonModel
data = {
"type": "MultiPolygon",
"coordinates": [
[[[107, 7], [108, 7],
[108, 8], [107, 8],
[107, 7]]],
[[[100, 0], [101, 0],
[101, 1], [100, 1],
[100, 0]]]
]
}
multi_polygon = MultiPolygonModel(**data)
|
|
| GeometryCollection Collection of different geometry types |
from pydantic_geojson import GeometryCollectionModel
data = {
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [-80.660805, 35.049392]
},
{
"type": "Polygon",
"coordinates": [[[-80.664582, 35.044965],
[-80.663874, 35.04428],
[-80.662586, 35.04558],
[-80.663444, 35.046036],
[-80.664582, 35.044965]]]
},
{
"type": "LineString",
"coordinates": [[-80.662372, 35.059509],
[-80.662693, 35.059263],
[-80.662844, 35.05893]]
}
]
}
geometry_collection = GeometryCollectionModel(**data)
|
Features and FeatureCollections
Feature
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)
FeatureCollection
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)
Custom Properties Models
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) # 8336817
Validation
pydantic-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
Example: Invalid Coordinates
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]
FastAPI Integration
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.
Basic Example
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]
}
)
]
)
Working with Different Geometry Types
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 linestring
Custom Properties with FastAPI
Define 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]
}
)
]
)
OpenAPI Schema Generation
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
Example API with Full CRUD Operations
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)
Compatibility with Other Libraries
pydantic-geojson is designed to work well with popular Python geospatial libraries:
Pydantic
Built on Pydantic, so you get all the benefits:
- Type validation
- JSON serialization/deserialization
- Model configuration
- Field validation
FastAPI
Perfect integration for building GeoJSON APIs with automatic OpenAPI documentation.
GeoPandas
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")
]
Shapely
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]
)
Testing
Run the test suite:
poetry run pytest
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Links
Metadata
Release files for pydantic-geojson 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pydantic_geojson-0.3.2.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_geojson-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.2 kB
Release files / pydantic_geojson-0.3.2.tar.gz
| Download URL | pydantic_geojson-0.3.2.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b6079978430199b6883539c0ccbc45fcbf38c6b885bd0e60781da599efbec202
|
|
BLAKE2b-256 checksum How to use checksums |
2cc0f445b00c0f664a6dd5a60cbe91077801f1cc70946fe38e289c349dd904e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 1, 2026.
Transparency logRelease files / pydantic_geojson-0.3.2-py3-none-any.whl
| Download URL | pydantic_geojson-0.3.2-py3-none-any.whl |
|---|---|
| Size | 19.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
714b74eafe4737809e979b7eb8806926ece3675c9f783ffdd4d93f2499ae0270
|
|
BLAKE2b-256 checksum How to use checksums |
7cfe184cf565b3c6b63c4647660f51fca562d89691220651c920c0402ba8ed65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 1, 2026.
Transparency log