Back to catalog
Season 44 15 Episodes 55 min 2026

GeoDjango and PostGIS

v6.0 — 2026 Edition. A comprehensive audio course on building spatial web applications using GeoDjango and PostGIS. Recorded in 2026, covering GeoDjango version 6.0.

Geospatial Analysis Spatial Web Applications Databases
GeoDjango and PostGIS
Now Playing
Click play to start
0:00
0:00
1
The Power of Spatial Web Frameworks
This episode introduces GeoDjango and PostGIS as a powerful combination for building geographic web applications. You will learn why traditional databases struggle with spatial data and how spatial extensions solve this problem.
3m 33s
2
PostGIS Geometry vs Geography
This episode explores PostGIS data types, specifically focusing on the difference between Geometry and Geography types. You will learn when to use flat-plane Cartesian math versus spherical earth calculations.
3m 25s
3
Setting Up Your Spatial Environment
This episode covers the initial setup of a GeoDjango and PostGIS project. You will learn how to enable the PostGIS extension and configure your Django settings to connect to a spatial backend.
3m 53s
4
Coordinate Reference Systems and SRIDs
This episode breaks down Coordinate Reference Systems and SRIDs. You will learn what WGS84 is and why projecting your map data correctly is crucial for accurate distance measurements.
3m 29s
5
Designing Geographic Models
This episode demonstrates how to design geographic models in GeoDjango. You will learn how to define PointField and MultiPolygonField attributes to store spatial data in your Django application.
3m 31s
6
The GDAL and OGR API
This episode introduces the GDAL and OGR API wrapper inside GeoDjango. You will learn how to inspect and read external vector files like Shapefiles natively within Python before importing them.
3m 20s
7
Ingesting Spatial Data with LayerMapping
This episode covers automating spatial data imports. You will learn how to use the LayerMapping utility to map external Shapefile data directly into your GeoDjango models effortlessly.
3m 51s
8
The GEOS API: Pythonic Geometry
This episode focuses on the GEOS API for Pythonic geometry manipulation. You will learn how to perform topological operations like unions and intersections in-memory without hitting the database.
3m 24s
9
Mastering Spatial Lookups
This episode explains geographic query lookups in the Django ORM. You will learn how to use spatial filters to find relationships like which points are contained within specific boundaries.
3m 55s
10
High-Performance Distance Queries
This episode tackles high-performance proximity and distance queries. You will learn how to use distance lookups and the geographic distance object to find nearby locations efficiently.
4m 01s
11
Geographic Database Functions
This episode explores spatial database functions accessible through GeoDjango. You will learn how to compute areas, extract centroids, and generate GeoJSON directly in the database layer.
3m 44s
12
Raster Data in PostGIS
This episode introduces PostGIS rasters and GeoDjango RasterFields. You will learn how to store and query continuous spatial data like elevation models or temperature maps.
3m 31s
13
Geolocation with GeoIP2
This episode covers IP-based geolocation using GeoDjango's GeoIP2 module. You will learn how to map user IP addresses to cities and countries using MaxMind datasets.
3m 49s
14
Testing Spatial Applications
This episode focuses on testing spatial applications in GeoDjango. You will learn how to configure your test suite, handle PostGIS template databases, and set up user privileges.
3m 34s
15
Deploying GeoDjango Applications
This episode wraps up the series by discussing deployment considerations for GeoDjango apps. You will learn about GDAL thread safety and how to configure your WSGI processes to prevent crashes.
4m 04s

Episodes

1

The Power of Spatial Web Frameworks

3m 33s

This episode introduces GeoDjango and PostGIS as a powerful combination for building geographic web applications. You will learn why traditional databases struggle with spatial data and how spatial extensions solve this problem.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 1 of 15. Have you ever tried to calculate the distance between two latitude and longitude points using a regular SQL query? You usually end up writing a massive, fragile block of trigonometry just to figure out which coffee shop is closest. Standard databases simply do not understand physical space. Resolving this data gap is exactly what we cover today in The Power of Spatial Web Frameworks. People often think storing geographic data just means adding two decimal columns to a database table, one for latitude and one for longitude. This is a common misunderstanding. If you are building a food delivery routing system and need to find the closest restaurant to a customer, a standard database treats those coordinates as arbitrary floating-point numbers. It does not know the Earth is a sphere. It cannot efficiently calculate boundaries, intersections, or real-world distances. You are forced to pull thousands of records into your application memory and do the math yourself. This is where a spatial database steps in. PostGIS is a spatial extension for the PostgreSQL database. It changes the fundamental way data is stored. Instead of two separate number columns, you store a single geometry or geography object. A spatial database natively understands the math of the physical world. It knows what a point, a line, and a polygon are. If you want to find all restaurants within a two-kilometer radius of a user, PostGIS uses specialized spatial indexes to find the answer instantly at the database level. But a smart database is only half the solution. You still need to connect that spatial logic to your web application. This brings us to GeoDjango. GeoDjango is a world-class geographic framework built directly into Django. It acts as the bridge between your spatial database and your application logic. GeoDjango extends Django's standard Object-Relational Mapper so it can understand PostGIS geometries. You interact with geographic data exactly like you interact with regular database models. Instead of writing raw spatial SQL queries, you use Python. You can filter database results based on spatial relationships, like checking if a restaurant's delivery zone polygon contains a specific user address point. GeoDjango translates your Python code into the correct PostGIS queries. It even includes map widgets for the Django administration panel, so you can visually create and modify geographic data right out of the box. Together, PostGIS and GeoDjango form an incredibly capable stack. PostGIS handles the heavy mathematical lifting at the storage layer, and GeoDjango provides the clean Python interface to serve that data to your users. You stop fighting complex trigonometry and start building actual location-aware features. Here is the key insight. The true power of a spatial framework is not just drawing lines on a web map. It is shifting complex spatial mathematics completely out of your application code and into a database engine built specifically to understand geometry. If you are finding these deep dives helpful, you can support the show by searching for DevStoriesEU on Patreon. That is it for today. Thanks for listening — go build something cool.
2

PostGIS Geometry vs Geography

3m 25s

This episode explores PostGIS data types, specifically focusing on the difference between Geometry and Geography types. You will learn when to use flat-plane Cartesian math versus spherical earth calculations.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 2 of 15. The shortest path between two points on Earth is not a straight line, it is a curve. If your database treats longitude and latitude like an ordinary X and Y grid, your distance queries are going to be completely wrong. Fixing this requires understanding the difference between the PostGIS Geometry and Geography data types. By default, spatial databases operate on a flat, two-dimensional plane. This is the Geometry type. It uses standard Cartesian mathematics. If you want to find the distance between two points, the database essentially draws a straight line and applies the Pythagorean theorem. This approach is incredibly fast and highly precise for local data. If you are mapping out a city block, plotting the walls of a building, or tracking the exact boundary of a small park, the curvature of the Earth is negligible. A flat map works perfectly. Developers often assume they can use the Geometry type for everything. They store global GPS coordinates, which are degrees of longitude and latitude, inside a flat Geometry column. When they ask the database to calculate a distance, it returns a meaningless number in degrees, rather than meters or miles. To get real-world distances with the Geometry type across large areas, you have to continually translate your data into specific, localized map projections. This is exactly why PostGIS provides the Geography type. The Geography type models the Earth as a sphere. Specifically, it natively uses the WGS84 spatial reference system. When you store coordinates in a Geography column, the database understands that the surface is curved. When you ask it for a distance, it does not draw a flat line. It calculates a great circle route. Think about measuring a flight path from New York to London. If you use the Geometry type on a standard unprojected flat map, the database calculates a straight line cutting directly across the grid. If you use the Geography type, the database calculates the true shortest distance around the curve of the Earth, tracing the great circle over the Atlantic. Better yet, the Geography type returns this distance in meters automatically. You completely bypass the need to manage complex map projections yourself. Here is the key insight. You might conclude that Geography is the superior type and should replace Geometry entirely. That is false. Calculating distances on a sphere requires heavy trigonometry. It demands significantly more processing power than flat-plane math, making operations on Geography types slower. Furthermore, PostGIS supports far fewer spatial functions for Geography than it does for Geometry. Many advanced geometric operations simply do not translate to spherical mathematics. Your choice entirely depends on the physical span of your data. Use the Geography type when your application stores points globally and needs accurate distances without projection management overhead. Use the Geometry type when your data is confined to a specific local region, or when you need absolute maximum performance and the full suite of spatial functions. The type you pick defines the shape of the world your database lives in. Thanks for spending a few minutes with me. Until next time, take it easy.
3

Setting Up Your Spatial Environment

3m 53s

This episode covers the initial setup of a GeoDjango and PostGIS project. You will learn how to enable the PostGIS extension and configure your Django settings to connect to a spatial backend.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 3 of 15. Turning your standard database into a spatial engine takes exactly three words of SQL, but if you execute them in the wrong context, your Django application will fail on its very first migration. Today, we are focusing entirely on Setting Up Your Spatial Environment. We start at the database layer. You already have PostgreSQL running and you have created an empty database for your new project. At this stage, that database is completely standard. It only understands text, integers, dates, and standard relational types. It has no concept of what a coordinate or a geographic boundary is. To change that, you need to enable PostGIS. People often trip up here by assuming that installing the PostGIS packages on their server automatically makes every database spatially aware. That is not the case. The spatial extension must be created inside the specific database your project will use. You must connect directly to that new database using a database client or the command line. Once connected, you execute the command to create the extension named postgis. Those three SQL words instantly transform the environment. PostgreSQL executes the command, loading hundreds of spatial functions and specialized data types into your schema. It also generates a crucial system table that stores the spatial reference systems, which provide the mathematical formulas required to project global coordinates onto a flat screen. With the database prepared, you shift over to your Django project. You bootstrap a standard project and create a new application. Let us call this app world. Now, open your settings file. There are two distinct modifications required to bridge Django and PostGIS. First, locate your installed apps array. You need to add the module named django dot contrib dot gis. This module is the core of GeoDjango. It loads the Python wrappers for the underlying geographic libraries, providing the spatial fields and geographic database lookups you will use to write queries later. You also append your new world app to this list so Django knows to track its models. Here is the key insight. The second modification happens in your database configuration dictionary. Django routes all database traffic through an engine backend. If you leave the engine set to the default PostgreSQL backend, Django will treat your spatial database like a standard relational database. It will not know how to serialize a geographic shape or safely parse a bounding box query. You must explicitly point the engine to the GeoDjango PostGIS backend. You change the engine value to django dot contrib dot gis dot db dot backends dot postgis. The rest of the connection dictionary remains identical. You still provide the database name, user, password, host, and port exactly as you would for a standard setup. When you run your initial migrations, this new backend takes over. It connects to PostgreSQL and immediately checks for the PostGIS extension. If you forgot to create the extension in the database, the backend will catch the missing spatial types and throw an error. If the extension is present, the connection succeeds. You now have a complete spatial pipeline. The true power of this specific configuration is that once the postgis backend is wired up, the massive complexity of spatial database queries is entirely hidden behind the familiar Django object relational mapper. That is all for this one. Thanks for listening, and keep building!
4

Coordinate Reference Systems and SRIDs

3m 29s

This episode breaks down Coordinate Reference Systems and SRIDs. You will learn what WGS84 is and why projecting your map data correctly is crucial for accurate distance measurements.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 4 of 15. Try measuring the footprint of a building using latitude and longitude, and your numbers will be completely useless. That is because latitude and longitude are just angles from the center of the Earth, not physical distances. To turn those angles into usable measurements like meters or feet, you need a Coordinate Reference System and an SRID. A coordinate like 48 degrees north and 2 degrees east is just a pair of raw numbers. Without context, your database does not know how to map those numbers to the physical world. That context comes from the Spatial Reference System Identifier, commonly called the SRID. An SRID is simply an integer that links your raw coordinate data to a specific mathematical model of the Earth. By default, GeoDjango geometry fields use an SRID of 4326. This integer represents the WGS84 coordinate system. It is the standard model used by GPS satellites and most web mapping libraries. It stores data using degrees of latitude and longitude. WGS84 is excellent for pinpointing locations anywhere on the globe. However, it is a geographic system, meaning it models the Earth as a three-dimensional shape. Here is the key insight. You cannot accurately calculate flat distances or areas using degrees. The physical distance between two lines of longitude shrinks as you move from the equator toward the poles. If you try to find all coffee shops within 500 meters of a point using SRID 4326, the database must perform heavy spherical math on the fly, which is slow and often imprecise. To solve this, you use projected coordinate systems. A projected system mathematically flattens a specific portion of the curved Earth onto a two-dimensional grid. Once the area is flattened, the coordinates change from angles to standard units of length, such as meters or feet. This makes distance calculations fast and accurate using basic geometry. Your choice of SRID depends entirely on the scope of your application. If you are building a global flight tracking system, you store your data using WGS84 and SRID 4326. Your points span the whole world, so you need a global system, even if distance calculations are slightly more complex. If you are building a local city planning application to measure exact property boundaries, using WGS84 is a mistake. Instead, you assign a local projected coordinate system to your spatial field. For instance, you might use a specific State Plane coordinate system with its own unique SRID. These local systems are highly tailored. They minimize the distortion that happens when you flatten a sphere, but only for that specific geographic zone. When setting up your models in GeoDjango, you define the SRID directly on the spatial field. If you do nothing, it defaults to 4326. If you want a localized projection in meters, you pass that specific integer to the field definition. GeoDjango then treats all geometry in that column according to that specific mathematical model. The right SRID ensures your distance calculations do not warp as your data moves further from the equator. Thanks for tuning in. Until next time!
5

Designing Geographic Models

3m 31s

This episode demonstrates how to design geographic models in GeoDjango. You will learn how to define PointField and MultiPolygonField attributes to store spatial data in your Django application.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 5 of 15. You want to add location data to your application, so you might think about adding two simple float fields for latitude and longitude to your database. But doing that completely locks you out of advanced spatial queries, distance calculations, and geometric intersections. To unlock those capabilities, you need proper spatial types, which brings us to Designing Geographic Models. Moving from standard Django to GeoDjango models requires only a slight shift in your imports. Instead of pulling from the standard Django database models, you import models from the geographic contrib module. This custom module provides a drop-in replacement. It gives you access to every standard Django field type, like characters and integers, while seamlessly introducing spatial types into the exact same class definition. Take a scenario where you are building a WorldBorder model to store country data. You define the model class just like any other. You assign a character field for the country name, an integer field for the population, and a float field for the total land area. To handle the actual map geometry, you add a geographic field directly alongside the others. For a country, you would use a MultiPolygonField. While a PointField is perfect for storing a single pair of coordinates like a capital city, a MultiPolygonField can represent complex national boundaries, including nations with multiple disconnected landmasses or offshore islands. When you attach this spatial field to your model, you are defining a coordinate system behind the scenes. Every geographic field relies on a Spatial Reference System Identifier, known as the SRID. The SRID tells the database how to project the raw coordinate numbers onto the actual shape of the earth. If you do not explicitly set this value, GeoDjango automatically defaults the SRID to 4326. That specific integer refers to the WGS84 standard, which is the exact same coordinate system used by standard GPS hardware. If your source data relies on a different local projection, you simply pass the target integer to the SRID parameter right inside the field definition. This is where it gets interesting. Developers familiar with the Django ORM know that to make a column fast to query, you must explicitly pass a database index flag to the field definition. Because spatial queries are inherently heavy, you might assume you need to manually configure specialized indexes for your geometry columns. You do not. Geographic fields in GeoDjango operate differently. They include a specific parameter for spatial indexing, and it defaults to true. When you run your migrations, GeoDjango automatically tells the underlying database to build a specialized spatial index. You get high-performance bounding box queries and geographic intersection lookups straight out of the box, with zero extra configuration. The true strength of designing models this way is that it completely demystifies spatial data. A massive, complex polygon representing a continent becomes just another property on your Python object, ready to be saved and queried alongside a simple population count. That is it for today. Thanks for listening — go build something cool.
6

The GDAL and OGR API

3m 20s

This episode introduces the GDAL and OGR API wrapper inside GeoDjango. You will learn how to inspect and read external vector files like Shapefiles natively within Python before importing them.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 6 of 15. You are handed a massive spatial file — an ESRI Shapefile or a GeoJSON — and you need to know what attributes it contains. Your first instinct might be to install a heavy desktop GIS program just to peek inside. You do not have to. GeoDjango can read these files natively right in the Python shell using the GDAL and OGR API. If geographic information systems are a toolbox, the Geospatial Data Abstraction Library is the universal translator. Technically, GDAL handles raster data like satellite imagery, while its partner OGR handles vector data like points, lines, and polygons. GeoDjango bundles a Pythonic wrapper around the OGR library, giving you a direct way to inspect almost any vector file format on the planet. Say you downloaded a shapefile containing the borders of every country in the world. Before you write a script to ingest this data, you need to know exactly how it is structured. You start by importing the DataSource object from the GeoDjango GDAL module. You create a new data source by passing it the file path of your shapefile. This object now represents your entire downloaded spatial dataset. A data source organizes its spatial information into layers. You can ask the data source for a count of its layers. An ESRI Shapefile traditionally only contains one layer, but formats like GeoPackages might contain dozens. You retrieve that first layer using its index, exactly like pulling an item from a standard Python list. Here is the key insight. The layer object is where the actual introspection happens. It acts as a container for individual spatial records, which the API calls features. You can ask the layer to return its total feature count. For our world borders file, the count should roughly match the number of countries on earth. You can also ask the layer for its geometry type. This confirms whether the data consists of points, lines, or polygons. For country borders, the type will typically report back as MultiPolygon. Next, you need to know what metadata is attached to those shapes. You can ask the layer for its fields. This returns a list of strings representing the attribute column names. You might see names corresponding to an internal country code, a common name, and a population count. You can even drill down one level deeper. By accessing an individual feature from the layer by its index, you can inspect a single country. You can ask that specific feature for the value of its common name field. You can also access that feature's specific geometry object and ask it to output its coordinates as well-known text or GeoJSON. You have now mapped out exactly what shapes the file contains, the names of its attributes, and the structure of its records. You did all of this purely through Python, without writing a database import script or relying on external visualization software. The GDAL wrapper turns your standard Python shell into an exploratory lens for spatial data, allowing you to validate foreign files before a single record touches your database. That is all for this one. Thanks for listening, and keep building!
7

Ingesting Spatial Data with LayerMapping

3m 51s

This episode covers automating spatial data imports. You will learn how to use the LayerMapping utility to map external Shapefile data directly into your GeoDjango models effortlessly.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 7 of 15. Importing thousands of geographic polygons and their metadata manually could take days. Your source files have cryptic column names and mismatched types, but your database demands strict structure. The solution is GeoDjango's LayerMapping utility, which handles this entire ingestion process with exactly one dictionary. When dealing with spatial data, you often receive vector files like ESRI Shapefiles. These files contain both the geometric shapes of locations and attribute data attached to those shapes. However, external files rarely match your database schema. A Shapefile might have a population column abbreviated as POP2005 and a country code listed as ISO2. Your pristine Django model, let us call it WorldBorder, has clearly named, strictly typed fields for those exact attributes. You might be tempted to use manual SQL imports or database tools like pgAdmin to push this data into PostGIS. LayerMapping is a completely different approach. It is a programmatic Python script utility. It lives directly inside your Django project, tying external spatial files straight into your Object Relational Mapper. Here is the key insight. The core of LayerMapping is a simple Python dictionary. The keys of this dictionary are the names of your clean Django model fields. The values are the raw string names of the attributes in your source file. For example, your dictionary maps the Django model field name to the Shapefile attribute NAME. It maps your model's population field to POP2005, and the iso code field to ISO2. You also map the geometry field on your model to the geometry type of the shapefile, which is usually represented by a string like MULTIPOLYGON. To automate this, you write a short Python file, typically named a load script. Inside this script, you import your WorldBorder model and the LayerMapping tool. You define your mapping dictionary. Then, you create a new LayerMapping instance. You pass this instance three mandatory pieces of information. First, your Django model. Second, the file path to your source Shapefile. Third, the mapping dictionary you just created. You can also provide optional arguments to control the ingestion behavior. If your source data is already in the exact spatial reference system your database expects, you can tell the utility not to perform coordinate transformations. This skips unnecessary calculations and speeds up the import. Finally, you call the save method on your LayerMapping instance. When you execute this script, the utility takes over entirely. It opens the Shapefile, iterates through every single geographic feature, and uses your dictionary to align the messy file attributes with your strict model fields. It automatically translates the raw geometry into a format PostGIS understands, and saves the records into your database. You can instruct the save method to raise an exception on the first error it encounters, or you can let it run and simply log any problem features to your terminal. The single most important takeaway is that LayerMapping completely decouples the rigid structure of external spatial files from the clean design of your database, letting you ingest massive datasets with just a few lines of Python. Before we wrap up, if you enjoy the show and want to help keep it going, you can support us by searching for DevStoriesEU on Patreon. That is all for this one. Thanks for listening, and keep building!
8

The GEOS API: Pythonic Geometry

3m 24s

This episode focuses on the GEOS API for Pythonic geometry manipulation. You will learn how to perform topological operations like unions and intersections in-memory without hitting the database.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 8 of 15. You might think every time you need to merge two polygons or find a spatial intersection, you have to write a query and wait for the database to return an answer. The reality is, you can do complex spatial math right in your Python shell without ever touching a database connection. That is exactly what we cover today: The GEOS API and Pythonic Geometry. A common misconception with GeoDjango is that PostGIS handles all the spatial heavy lifting. While PostGIS is incredibly powerful, the GEOS API allows you to perform geometric operations entirely in local memory. GEOS stands for Geometry Engine Open Source. It is a highly optimized C++ library, but GeoDjango gives you a powerful wrapper called the GEOSGeometry object. This object lets you create, manipulate, and analyze geometries purely in Python. This is where it gets interesting. A GEOSGeometry object does not feel like a clunky wrapper around a C library. It acts exactly like a native Python container. If you have a LineString with ten points, you can run the standard Python length function on it to see that it has ten coordinates. You can iterate over a Polygon with a standard for-loop to extract its individual rings, or use an index to grab a specific point out of a MultiPoint geometry. They behave just like familiar lists or tuples of coordinates. The real power unlocks when you apply topological operations. Say you are building a logistics application and you have two overlapping delivery zones defined as GEOS Polygons. You need to find the exact area where these two zones overlap. You do not need to save these zones to a model and run a database query. Instead, you can generate the overlap instantly in memory. First, you instantiate two GEOSGeometry objects representing your delivery zones. Then, you can simply call the intersection method on the first polygon, passing the second polygon as the argument. Because the API is deeply Pythonic, you can also just use the bitwise AND operator. You literally type polygon one, an ampersand, and polygon two. This returns a brand new GEOSGeometry object representing the exact overlapping shape. You have access to a full suite of these spatial operations. If you need to find the area covered by the first delivery zone but strictly outside the second, you use the difference method, or the regular Python minus operator. If you want to merge them into one massive zone, you use the union method, or the bitwise OR pipe operator. You can even generate new shapes out of thin air. Calling the buffer method on a point geometry with a width argument instantly calculates and returns a polygon representing a perfect circle drawn around that point. Because everything relies on the underlying C++ engine, these calculations happen extremely fast. The key takeaway here is that the GEOS API gives you an independent, in-memory spatial engine, allowing you to validate geometries, calculate buffers, and find intersections before a single byte of data ever reaches your database. That is your lot for this one. Catch you next time!
9

Mastering Spatial Lookups

3m 55s

This episode explains geographic query lookups in the Django ORM. You will learn how to use spatial filters to find relationships like which points are contained within specific boundaries.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 9 of 15. Forget writing massive coordinate comparisons. You have a user location and a delivery zone, and you need to know if they overlap. Doing this mathematically means checking hundreds of polygon edges in your application logic. Instead, GeoDjango reduces this entirely to a database operation. Today, we are mastering spatial lookups. If you have used Django, you know the double-underscore syntax for filtering querysets. You append something like double-underscore exact or double-underscore in to a field name. GeoDjango extends this exact same syntax to execute complex PostGIS topological relationships. Topological relationships are just rules about how geometries share space. Are they touching? Are they overlapping? Is one entirely inside the other? GeoDjango gives you dedicated lookup types to ask these questions directly in your ORM queries. The most common and forgiving spatial lookup is intersects. If you filter a geometry field with double-underscore intersects, the database returns records where the two geometries share any portion of space. They can cross, they can touch at a boundary, or one can completely envelop the other. As long as there is any shared space, intersects evaluates to true. Often, you need more precision than a simple intersection. You need strict boundaries. This is where contains and within come into play. Developers mix these two up constantly. Here is the key insight. The difference is purely directional. If geometry A contains geometry B, then geometry B is within geometry A. Think of a box and a marble. The box contains the marble. The marble is within the box. You choose the lookup based on which geometry is in your database column and which geometry you are passing as the filter argument. Consider a concrete scenario. You have a database table of houses, and each house has a point geometry representing its location. The city just released a new polygon defining a designated flood zone. You need to find all the houses that sit perfectly inside that risk area. You do not query the flood zone and see what it contains. You query the houses. You call filter on the house model, reference the point geometry field, add double-underscore within, and pass the flood zone polygon as the value. When you execute that query, Django translates your double-underscore lookup into a native PostGIS function call, specifically ST_Within. The database engine evaluates the geometries using its optimized spatial indexes. It checks if every single coordinate of the house point lies inside the interior of the flood zone polygon. If the house sits exactly on the boundary edge of the polygon, it is not considered within. Strict containment means the interior of the first geometry must be completely inside the interior of the second geometry. If you reverse the scenario, say you are querying a database table of neighborhoods to find the one that surrounds a specific user location, you use double-underscore contains. You filter the neighborhood polygon field, add double-underscore contains, and pass the user point. The logic is identical under the hood, but the order of the spatial arguments passed to PostGIS is swapped. Understanding this directional logic prevents silent failures and empty querysets. By mapping PostGIS operations directly to Django keyword arguments, you filter massive spatial datasets efficiently without writing raw SQL. The geometry you are filtering against always dictates the direction of your spatial lookup. Thanks for tuning in. Until next time!
10

High-Performance Distance Queries

4m 01s

This episode tackles high-performance proximity and distance queries. You will learn how to use distance lookups and the geographic distance object to find nearby locations efficiently.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 10 of 15. Radius queries are the bread and butter of location-based applications. If you write them wrong, your database calculates the exact distance to every single row in your table, forcing a massive full scan. If you write them right, it takes milliseconds. High-Performance Distance Queries are the difference between an app that snaps and one that crashes under load. A common mistake developers make is thinking about spatial queries the same way they think about basic math. The naive approach is to calculate the precise distance from your target point to every other point in the database, and then filter out anything larger than your maximum radius. You should never do this. Calculating true distances on a sphere is computationally heavy. If your query filters by calculating the distance first, the database cannot use its spatial indexes. It is forced to check every single row. To keep queries fast, you must rely on index-backed lookups. Before looking at the database lookups, you need a way to express distance clearly in your Python code. GeoDjango provides the Distance object, commonly imported simply as the capital letter D. You initialize it with a unit and a value. For example, passing the argument m i equals five to this object creates a measurement of exactly five miles. You can use kilometers, meters, or even degrees. This normalizes the measurement in Python before the query ever hits the database. Consider a scenario where you are implementing a feature that finds all coffee shops within a five-mile radius of a user's current GPS ping. You have the user location point, and you want to query your coffee shop model. You could use the distance less-than-or-equal lookup. You write a query filter where your coffee shop location field is followed by a double underscore and the word distance, an underscore, and the letters l t e. You pass it a tuple containing the user point and your distance object set to five miles. This works. It translates to a database function that checks if the distance is less than or equal to five miles. But depending on your spatial backend and how your columns are set up, this lookup might still do more mathematical work than strictly necessary by calculating exact distances before doing the final comparison. Here is the key insight. You rarely need to know the exact distance to filter a radius. You just need to know if the object is inside the circle. This is where the dwithin lookup becomes essential. Instead of distance less-than-or-equal, you append a double underscore and the word dwithin to your location field. You pass it the exact same user point and five-mile distance object. Under the hood, this routes directly to the PostGIS function ST DWithin. This function is highly optimized. It does a fast bounding-box check first, which perfectly utilizes your spatial indexes. It draws a rough square around your five-mile radius and immediately discards any points outside that square without doing any complex spherical math. It only performs precise, expensive calculations for the few points that fall near the boundary of the circle inside that square. If your database table uses geography columns instead of basic geometry columns, PostGIS handles the curvature of the earth automatically while still keeping that index usage lightning fast. The single most valuable habit in spatial programming is realizing that checking if a point lives inside a shape is always cheaper than pulling out a tape measure to calculate the exact distance between them. Thanks for spending a few minutes with me. Until next time, take it easy.
11

Geographic Database Functions

3m 44s

This episode explores spatial database functions accessible through GeoDjango. You will learn how to compute areas, extract centroids, and generate GeoJSON directly in the database layer.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 11 of 15. You have a database full of massive, complex polygons, and your frontend just needs to display a pin at the center of each one. If you fetch those raw polygons into Python to calculate the center, you are dragging megabytes of useless coordinate data across the network. The solution is pushing that math down to the database using Geographic Database Functions. There are two ways to get spatial information like area or a center point in GeoDjango. The first is to query the database record, pull the entire geometry into a Python GEOS object, and access a property like dot area on it. Do not do this unless you actually need the full geometry in Python. Fetching a national park boundary might mean loading tens of thousands of vertices into memory just to calculate a single number. The second, much more efficient way is to tell PostGIS to calculate the answer and only send back the final result. This is where geographic database functions, combined with the Django ORM annotate method, become essential. Let us look at a practical scenario. You are building an API that lists national parks. For the list view, the client application only needs the park name, its total area, and a single center coordinate to drop a map pin. It does not need the complex boundary polygon. In your Django queryset, you use the annotate method. You tell it to create a new attribute, maybe called park area, and set it equal to the Area function acting on your geometry column. Next to it, you create another attribute called center point, set equal to the Centroid function on that same geometry column. Finally, you restrict the query output to just the name and your two new annotations. When this query executes, PostGIS reads the raw geometry data on disk. It runs highly optimized internal routines to calculate the spatial area and find the mathematical center point. It then returns exactly three things over the network to your Django application: a text string for the name, a number for the area, and a single point geometry for the center. You completely bypass the bandwidth and memory costs of transferring the large polygon. This strategy applies to many spatial operations. You can use the Transform function inside an annotation to convert coordinates from your database storage projection directly into the projection your web map needs, like Web Mercator. The database handles the math before Python even sees the data. You can also use the AsGeoJSON function. This instructs PostGIS to format the spatial data into a standard JSON text string directly in the database. When Django receives it, it is just text ready to pass directly to your frontend, avoiding Python serialization overhead entirely. You can also apply aggregate functions across entire querysets. The Extent function looks at a group of geometries and returns a single bounding box that covers all of them. If a user searches for all parks in a specific region, returning the Extent gives you the exact coordinates needed to automatically zoom the frontend map to fit all the results perfectly. Here is the key insight. Your spatial database is a highly specialized computation engine, not just a storage container. Keep your heavy spatial computations inside PostGIS, and only move the calculated answers across the network to Python. That is your lot for this one. Catch you next time!
12

Raster Data in PostGIS

3m 31s

This episode introduces PostGIS rasters and GeoDjango RasterFields. You will learn how to store and query continuous spatial data like elevation models or temperature maps.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 12 of 15. Vector shapes are great when you want to draw a clean line around a city border or pinpoint a user location. But what happens when you need to map something that does not have hard boundaries, like the shifting temperature across a continent, or the elevation of an entire mountain range? You cannot draw a polygon for every single degree of temperature. This is where Raster Data in PostGIS comes in. If you are used to working with points, lines, and polygons, raster data requires a mental shift. Raster data is not made of discrete shapes. It is a continuous grid of pixels, exactly like a digital photograph. But instead of red, green, and blue color values, each pixel holds a specific numeric data point. That data could be rainfall, soil type, or altitude. The entire grid is mathematically tied to the real world through georeferencing. This means the database knows exactly where every single pixel sits on the map. In GeoDjango, handling this data primarily involves two components: the RasterField and the GDALRaster object. RasterField is the database column type. You add it to your Django model just like you would a geometry field. This tells PostGIS to allocate space for grid data internally. But you do not interact with the raw database bytes directly. When you pull a raster out of the database, or before you put one in, you use GDALRaster. This is a Python wrapper around the underlying geospatial libraries. It allows you to inspect the raster properties, like its scale, its coordinate reference system, and the actual values inside its bands. A band is just a single layer of data in the grid. A standard image has three bands for color, but an elevation map typically just has one band representing height. Consider a concrete scenario. You are building an application for hikers, and you have a Digital Elevation Model of a mountain range. This elevation model is usually provided as a GeoTIFF file. First, you define a Django model for your mountain range and give it a RasterField. Then, in your Python code, you instantiate a GDALRaster by passing it the file path to your GeoTIFF. Django loads the file into memory as a GDALRaster object. You assign that object to the RasterField on your mountain model and call save. PostGIS takes that grid and stores it. Now, when a hiker uploads their GPS route, you can query the database to find out exactly which pixel their coordinates fall into. You extract the elevation value stored in that specific pixel. When you read that field back out of the database, Django gives you a GDALRaster object again. You can ask this object for its width, its height, or its spatial extent. You can access the raw pixel data arrays directly in Python to perform calculations before sending the data to your application front end. Here is the key insight. You use vectors for things that exist at a specific place, and you use rasters for things that exist everywhere across an area. By combining both in PostGIS, your application can relate discrete objects, like a hiking trail, to continuous environments, like the actual altitude of the ground underfoot. If you want to help keep the show going, you can search for DevStoriesEU on Patreon — your support means a lot. That is all for this one. Thanks for listening, and keep building!
13

Geolocation with GeoIP2

3m 49s

This episode covers IP-based geolocation using GeoDjango's GeoIP2 module. You will learn how to map user IP addresses to cities and countries using MaxMind datasets.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 13 of 15. What if you need to know where a user is to serve them local content, but they have not granted you GPS permissions? Their incoming IP address holds the answer. This is Geolocation with GeoIP2. To be clear upfront, IP geolocation is not a precise coordinate lookup like a browser geolocation API. It is an approximation. When you look up an IP address, you are querying a static dataset that maps assigned IP blocks to geographical areas. It gets you the right country almost always, and the right city often, but it will not find the user's actual street address. Consider a scenario where a visitor lands on your e-commerce site. You want to automatically redirect them to their country's localized storefront or default a map's center to their region purely based on their incoming IPv4 or IPv6 address. GeoDjango provides the GeoIP2 object specifically to handle this offline lookup. Before you can write any logic, you need the right pieces in place. First, you install the geoip2 Python library in your environment. Second, you need the actual data. GeoIP2 relies on MaxMind datasets, typically the free GeoLite2 City or Country databases. You download these files, which have an mmdb extension, and place them on your server. Finally, in your Django settings, you define a variable called GEOIP_PATH and point it to the directory containing those files. Django expects to find specifically named files like GeoLite2-City dot mmdb in that folder. With the setup complete, using the tool requires just a few steps. You import the GeoIP2 object from the django contrib gis geoip2 module and instantiate it. You extract the incoming IP address string from your request headers, and you pass it to one of the lookup methods. If you only need broad routing logic, you call the country method. You pass the IP address in, and it returns a dictionary containing the country code and the country name. If you need more granularity to center a map, you call the city method instead. This returns a richer dictionary containing the city name, the region, and the approximate longitude and latitude values. The city method includes the country data as well, so you only ever need to make one call. If you simply want the coordinates to feed into another function, there are dedicated methods like coords that return a basic tuple of longitude and latitude. You also need to handle failures gracefully. If you pass an invalid IP address, or an address that is simply not present in the MaxMind database, GeoIP2 does not return an empty dictionary. It raises a GeoIP2Exception. You must wrap your lookup calls in a try block and provide a default fallback location for your storefront when the lookup fails. Here is the key insight. The code itself is essentially just an optimized file reader. Your geolocation accuracy entirely depends on the freshness of the database sitting in your GEOIP_PATH directory. IP allocations change constantly. If you build a routing system around GeoIP2, you must automate the downloading of new database files regularly, or your localized redirects will slowly and silently become incorrect. That is your lot for this one. Catch you next time!
14

Testing Spatial Applications

3m 34s

This episode focuses on testing spatial applications in GeoDjango. You will learn how to configure your test suite, handle PostGIS template databases, and set up user privileges.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 14 of 15. Testing spatial applications has a unique catch: your automated test database needs to install complex C-level spatial extensions before the very first test even runs. If your automated pipeline is crashing instantly with permission errors, this is likely why. Today, we are looking at Testing Spatial Applications. The first requirement is strictly configuration. When you run tests, Django creates a parallel test database to keep your production data safe. For GeoDjango, this test database must be spatial. This means your database settings must explicitly use a spatial engine, such as the PostGIS backend provided by GeoDjango. You cannot use the standard Postgres backend and just add spatial fields later. The spatial backend is what tells the Django test runner to look for and initialize the required spatial features. Here is the key insight. The standard Django test runner creates a fresh database, runs your migrations, and starts testing. But for a GeoDjango application, creating a fresh database is not enough. The database must also load the PostGIS extension. This is not a standard SQL table. It is a system-level extension. By default, PostgreSQL restricts who can create extensions for security reasons. It usually requires a database user with superuser privileges. This is exactly where continuous integration pipelines often fail. You spin up a Postgres container, pass in a standard database user, and tell Django to run tests. Django connects, tries to build the test database, attempts to run the command to create the PostGIS extension, and hits a permission denied error. To fix this in an automated pipeline, the database user executing the tests must have the necessary privileges. The most straightforward approach in an isolated, throwaway environment is to grant the test user superuser roles temporarily. If your security policies forbid superuser access even in a temporary container, you must configure PostgreSQL so that the specific test user is a trusted owner of the test database and is explicitly allowed to create the PostGIS extension. Once the database is built, GeoDjango needs to know what it is working with. Different versions of PostGIS support different spatial functions. During normal operations, GeoDjango queries the database to determine the installed PostGIS version. However, in a test environment, you can bypass this query by defining a specific setting called PostGIS Version. You provide this as a tuple of three numbers, representing the major, minor, and patch versions. Setting this explicitly can speed up test initialization and acts as a fail-safe if the database connection struggles during the initial version check. Running spatial tests is not about writing different assertions. It is primarily an infrastructure challenge. The most critical takeaway is that if your test database user cannot create extensions on the fly, your tests will never even start. Fix your test user privileges to bootstrap PostGIS, and the rest of your pipeline will behave exactly like a standard Django test suite. Thanks for spending a few minutes with me. Until next time, take it easy.
15

Deploying GeoDjango Applications

4m 04s

This episode wraps up the series by discussing deployment considerations for GeoDjango apps. You will learn about GDAL thread safety and how to configure your WSGI processes to prevent crashes.

Download
Hi, this is Alex from DEV STORIES DOT EU. GeoDjango and PostGIS, episode 15 of 15. Your spatial app runs perfectly on your laptop. You push it to a production server, traffic spikes, and suddenly the application silently crashes. No standard Python traceback, just dead processes. The problem is a threading clash deep inside a C library. Deploying GeoDjango Applications requires avoiding this exact trap. Deploying a spatial web application is almost identical to deploying a standard Django project. The routing, the database connections, and the server architecture follow the exact same rules. But GeoDjango carries a hidden variable. That variable is the Geospatial Data Abstraction Library, commonly known as GDAL. GDAL is a massive, highly optimized C library that performs the heavy lifting for geographic data formats and coordinate transformations. Here is the key insight. GDAL is not thread-safe. This explains why your code works locally but crashes on the server. The local Django development server handles requests sequentially. It rarely triggers concurrent access to the C library. Production WSGI servers are built differently. To handle high traffic efficiently, servers like Apache with mod_wsgi typically spin up multiple threads within a single process. Python usually relies on the Global Interpreter Lock to keep threads from colliding. However, C libraries manage their own memory entirely outside of Python's control. If two WSGI threads attempt to execute a GDAL function simultaneously, they access the same memory space without safety checks. One thread corrupts the data the other thread is using. Because this happens at the C level, Python cannot throw a standard exception. The result is a segmentation fault. The server process drops dead instantly, taking any active user requests down with it. To resolve this, you must change how your WSGI server handles concurrency. You need to rely on processes instead of threads. Operating systems isolate processes from one another, giving each process its own dedicated memory space. If you are pushing a highly trafficked geographic app to production using Apache and mod_wsgi, you handle this in the daemon configuration. Instead of configuring a small number of processes with a large pool of threads, you restrict the threads. You open your Apache configuration file and locate the WSGI daemon process directive. Alongside the application name, you add a parameter explicitly setting threads to exactly one. You then scale the application by increasing the number of processes to handle the expected load. The same logic applies if you use a server like Gunicorn. You must bind it to standard worker processes. You avoid any worker class that introduces threading or asynchronous event loops, because they will inevitably trigger the same GDAL collisions. Forcing your WSGI server into a multi-process, single-threaded model guarantees that GDAL never collides with itself. The single most crucial step in making a GeoDjango app production-ready is isolating your C libraries from threaded concurrency. This concludes our entire series on GeoDjango and PostGIS. The best way to solidify these concepts is to read through the official Django documentation and build something hands-on. If you have ideas for what technology we should cover next, visit devstories dot eu and suggest a topic. Thanks for listening. Take care, everyone.