--- title: The earthdistance extension subtitle: Calculate great-circle distances between points on Earth in Postgres enableTableOfContents: true updatedOn: '2025-08-02T10:33:29.232Z' --- The `earthdistance` extension for Postgres provides functions to calculate great-circle distances between points on the Earth's surface. This is essential for applications requiring geospatial distance calculations, such as location-based services, mapping applications, logistics, and any system that needs to find nearby points or calculate travel distances. The `earthdistance` extension primarily assumes a spherical Earth model for its calculations, which provides good approximations for many use cases. It relies on the [`cube`](/docs/extensions/cube) extension for some of its underlying operations. You may consider using the [`postgis` extension](/docs/extensions/postgis) if accurate geospatial calculations are critical for your application. ## Enable the `earthdistance` extension To use `earthdistance`, you first need to enable it and its dependency, the [`cube` extension](/docs/extensions/cube). You can do this by running the following `CREATE EXTENSION` statements in the [Neon SQL Editor](/docs/get-started/query-with-neon-sql-editor) or from a client like [psql](/docs/connect/query-with-psql-editor): ```sql CREATE EXTENSION IF NOT EXISTS cube; CREATE EXTENSION IF NOT EXISTS earthdistance; ``` **Version availability:** Please refer to the [list of all extensions](/docs/extensions/pg-extensions) available in Neon for up-to-date extension version information. ## Core concepts The `earthdistance` extension offers two main ways to represent geographic points and calculate distances: 1. **Using the `earth` type:** This approach involves converting latitude and longitude coordinates into a special `earth` data type (which is a domain over `cube`, representing a point in 3D Cartesian coordinates based on a spherical earth model). Distances are calculated in meters. 2. **Using the native `point` type:** This approach uses the built-in `point` type in Postgres, where the first component is longitude and the second is latitude. It provides a specific operator for distance calculation, which returns results in statute miles. ### The `earth` data type and associated functions - `earth` data type Represents a point on the Earth's surface. It's internally a `cube` point representing a 3D Cartesian coordinate. You don't usually interact with its internal representation directly but use helper functions. - `ll_to_earth(latitude double precision, longitude double precision)` returns `earth` Converts latitude and longitude (in degrees) to an `earth` data type value. - `earth_distance(p1 earth, p2 earth)` returns double precision Calculates the great-circle distance in **meters** between two `earth` points. ```sql -- Distance between London and Paris SELECT earth_distance( ll_to_earth(51.5074, -0.1278), -- London ll_to_earth(48.8566, 2.3522) -- Paris ) AS distance_meters; -- Output: 343942.5946120387 ``` - `earth_box(location earth, radius_meters double precision)` returns `cube` Computes a bounding box (as a `cube` type) that encloses all points within the specified `radius_meters` from the given `location`. This is primarily used for optimizing radius searches with [GiST indexes](/postgresql/postgresql-indexes/postgresql-index-types#gist-indexes). ```sql -- Create a bounding box for a 10km radius around London SELECT earth_box(ll_to_earth(51.5074, -0.1278), 10000) AS search_box; ``` When used in queries, you typically use the `<@` operator from the `cube` extension. The `<@` operator means "is contained by". The expression `ll_to_earth(lat, lon) <@ earth_box(center_point_earth, search_radius_meters)` checks if the specific geographic point (represented as an `earth` type, which is a `cube` point) is contained within the square bounding `earth_box` (also a `cube`). For instance, if `point_A` is `ll_to_earth(51.5, -0.1)` (a point in London) and `london_box` is `earth_box(ll_to_earth(51.5074, -0.1278), 10000)`, then `point_A <@ london_box` would be `true`. ```sql SELECT ll_to_earth(51.5, -0.1) <@ earth_box(ll_to_earth(51.5074, -0.1278), 10000) AS is_within_box; -- Output: true ``` ### Using the `point` data type - `point` data type A built-in Postgres type representing a 2D point in Cartesian coordinates. In the context of `earthdistance`, the first component is longitude and the second is latitude. - `point1 <@> point2` returns double precision Calculates the great-circle distance in **statute miles** between two points. ```sql -- Distance between San Francisco (-122.4194 lon, 37.7749 lat) -- and New York (-74.0060 lon, 40.7128 lat) SELECT point '(-122.4194, 37.7749)' <@> point '(-74.0060, 40.7128)' AS distance_miles; -- Output: 2565.6899113306895 ``` ## Example usage Now that we've seen the core functions, let's create and populate a sample table to demonstrate practical usage scenarios. This table will store location data with latitude and longitude. ```sql CREATE TABLE locations ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, latitude DOUBLE PRECISION NOT NULL, longitude DOUBLE PRECISION NOT NULL ); INSERT INTO locations (name, latitude, longitude) VALUES ('San Francisco', 37.7749, -122.4194), ('New York', 40.7128, -74.0060), ('Los Angeles', 34.0522, -118.2437), ('Chicago', 41.8781, -87.6298), ('London', 51.5074, -0.1278), ('Tokyo', 35.6895, 139.6917), ('Sydney', -33.8688, 151.2093); ``` ## Practical usage scenarios With our sample `locations` table, we can now explore common geospatial queries. ### Calculating distance between two specific points Using `ll_to_earth()` and `earth_distance()`: ```sql SELECT a.name AS location_a, b.name AS location_b, earth_distance( ll_to_earth(a.latitude, a.longitude), ll_to_earth(b.latitude, b.longitude) ) AS distance_meters FROM locations a, locations b WHERE a.name = 'San Francisco' AND b.name = 'New York'; ``` Output: ```text | location_a | location_b | distance_meters | |---------------|------------|---------------------| | San Francisco | New York | 4133731.792059527 | ``` ### Finding locations within a given radius Find all locations within 8000 kilometers of London using the `earth` type functions. ```sql SELECT name, earth_distance( ll_to_earth(latitude, longitude), ll_to_earth(51.5074, -0.1278) -- London's coordinates ) / 1000.0 AS distance_from_london_km -- Convert meters to km FROM locations WHERE earth_distance( ll_to_earth(latitude, longitude), ll_to_earth(51.5074, -0.1278) ) < 8000 * 1000 -- Radius in meters ORDER BY distance_from_london_km; ``` Output: ```text | name | distance_from_london_km | |---------------|-------------------------| | London | 0.0 | | New York | 5576.4892261332425 | | Chicago | 6360.125481207209 | ``` ## Indexing for performance For applications with many locations that require frequent radius searches or nearest-neighbor queries, indexing is crucial. GiST indexes are used with the `earth` type functions (`ll_to_earth`, `earth_box`). 1. **Create a GiST index on the `earth` representation of your coordinates:** This index will be on the result of the `ll_to_earth()` function applied to your latitude and longitude columns. ```sql CREATE INDEX locations_earth_coords_idx ON locations USING GIST (ll_to_earth(latitude, longitude)); ``` 2. **Perform an indexed radius search:** Let's find locations within 1000 km of San Francisco `(37.7749° N, -122.4194° W)`. ```sql SELECT name, earth_distance( ll_to_earth(latitude, longitude), ll_to_earth(37.7749, -122.4194) ) / 1000.0 AS distance_from_sf_km FROM locations WHERE -- This part uses the GiST index for a fast coarse filter ll_to_earth(latitude, longitude) <@ earth_box(ll_to_earth(37.7749, -122.4194), 1000 * 1000) -- Radius in meters -- This part is the exact distance check for refinement (necessary as earth_box is a square) AND earth_distance( ll_to_earth(latitude, longitude), ll_to_earth(37.7749, -122.4194) ) < 1000 * 1000 -- Radius in meters ORDER BY distance_from_sf_km; ``` **Explanation of the indexed query:** - The `ll_to_earth(latitude, longitude) <@ earth_box(...)` condition uses the GiST index. The `earth_box` function creates a square bounding box. The index quickly finds points whose `earth` representation falls within this box. - The second condition, `earth_distance(...) < radius`, is crucial. It performs the precise great-circle distance calculation for the candidate rows selected by the index, filtering them to the exact circular radius. This is because the `earth_box` provides a rough filter, and the `earth_distance` provides the exact filter. ## Conclusion The `earthdistance` extension is a powerful and convenient tool in Postgres for applications dealing with geographic locations. It simplifies the calculation of great-circle distances, enabling features like location-based searching and distance filtering directly within your database. By understanding its core functions, data representations, and how to leverage GiST indexing, you can build efficient and effective geospatial queries. ## Resources - PostgreSQL official documentation: - [earthdistance](https://www.postgresql.org/docs/current/earthdistance.html) - [cube](https://www.postgresql.org/docs/current/cube.html) - [point](https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-POINTS) - [Cube extension](/docs/extensions/cube) - [Greater-circle distance](https://en.wikipedia.org/wiki/Great-circle_distance)