Geo Functions¶
SemQL has two built-in geo functions for working with coordinates: GEO_DISTANCE() and GEO_WITHIN_RADIUS(). Both take latitude/longitude pairs.
SemQL lowers both functions for every supported database (Snowflake, BigQuery, Amazon Redshift and the others). It compiles them to standard trigonometry (the spherical law of cosines on a 6,371 km sphere).
GEO_DISTANCE¶
Returns the distance in kilometers between two geographic coordinates.
Syntax¶
GEO_DISTANCE(lat1, lng1, lat2, lng2)
| Parameter | Type | Description |
|---|---|---|
lat1 |
number | Latitude of the first point |
lng1 |
number | Longitude of the first point |
lat2 |
number | Latitude of the second point |
lng2 |
number | Longitude of the second point |
Examples¶
Distance from a fixed point to a column value:
SELECT
store_name,
city,
GEO_DISTANCE(48.8566, 2.3522, store_lat, store_lng) AS distance_from_paris_km
FROM retail_stores
ORDER BY distance_from_paris_km ASC
Filter by a calculated distance:
SELECT
store_name,
GEO_DISTANCE(51.5074, -0.1278, store_lat, store_lng) AS distance_km
FROM retail_stores
WHERE GEO_DISTANCE(51.5074, -0.1278, store_lat, store_lng) <= 50
ORDER BY distance_km
Distance between two columns (for example, customer location to warehouse):
SELECT
order_id,
customer_name,
GEO_DISTANCE(customer_lat, customer_lng, warehouse_lat, warehouse_lng) AS delivery_distance_km
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id
JOIN warehouses ON orders.warehouse_id = warehouses.warehouse_id
ORDER BY delivery_distance_km DESC
LIMIT 20
GEO_WITHIN_RADIUS¶
Returns true if a point is within a given radius of another point, false otherwise.
Syntax¶
GEO_WITHIN_RADIUS(lat1, lng1, lat2, lng2, radius_km)
| Parameter | Type | Description |
|---|---|---|
lat1 |
number | Latitude of the reference point |
lng1 |
number | Longitude of the reference point |
lat2 |
number | Latitude of the point to test |
lng2 |
number | Longitude of the point to test |
radius_km |
number | Radius in kilometers |
Examples¶
Filter stores within 25 km of a city:
SELECT store_name, city, store_lat, store_lng
FROM retail_stores
WHERE GEO_WITHIN_RADIUS(52.3676, 4.9041, store_lat, store_lng, 25)
ORDER BY store_name
Categorize records by proximity:
SELECT
customer_name,
city,
CASE
WHEN GEO_WITHIN_RADIUS(48.8566, 2.3522, customer_lat, customer_lng, 50)
THEN 'Within 50km of Paris'
WHEN GEO_WITHIN_RADIUS(48.8566, 2.3522, customer_lat, customer_lng, 200)
THEN 'Within 200km of Paris'
ELSE 'Outside 200km'
END AS proximity_band
FROM customers
ORDER BY proximity_band
Count customers within a radius:
SELECT
COUNT(*) AS customer_count
FROM customers
WHERE GEO_WITHIN_RADIUS(40.7128, -74.0060, customer_lat, customer_lng, 100)
Combining both functions¶
A common pattern is to show the actual distance alongside a proximity flag. Using GEO_WITHIN_RADIUS inside CASE WHEN is the most portable form:
SELECT
store_name,
city,
GEO_DISTANCE(50.8503, 4.3517, store_lat, store_lng) AS distance_from_brussels_km,
CASE
WHEN GEO_WITHIN_RADIUS(50.8503, 4.3517, store_lat, store_lng, 100) THEN 'yes'
ELSE 'no'
END AS within_100km
FROM retail_stores
ORDER BY distance_from_brussels_km
Notes¶
- Coordinates must be numeric (decimal degrees).
- SemQL does not check for missing coordinates. Rows with a missing coordinate can return NULL or an incorrect result (for example, appear to be within the radius), depending on your database. Exclude rows with missing coordinates first, for example
WHERE store_lat IS NOT NULL AND store_lng IS NOT NULL. - The calculation assumes a spherical Earth (radius 6,371 km). It's accurate to within about 0.5% for most practical distances.
- The generated SQL uses
SIN,COS,ACOSandRADIANS(or an equivalent degrees-to-radians conversion). GEO_WITHIN_RADIUSis most portable inWHEREorCASE WHEN. On some databases, a comparison can't be used directly in theSELECTlist.- The number of arguments is checked when the query is compiled. For example,
GEO_DISTANCEwith three arguments is rejected withGEO_DISTANCE takes 4 arguments (lat1, lng1, lat2, lng2), got 3.