Geospatial data with PostGIS

Maria Garcia Feb 2026
2 tabs
-- Install PostGIS extension
CREATE EXTENSION IF NOT EXISTS postgis;

-- Create table with geometry column
CREATE TABLE locations (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  location GEOMETRY(Point, 4326),  -- 4326 = WGS 84 (GPS coordinates)
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Create spatial index
CREATE INDEX idx_locations_geom ON locations USING GIST(location);

-- Insert point (longitude, latitude)
INSERT INTO locations (name, location) VALUES
  ('Statue of Liberty', ST_SetSRID(ST_MakePoint(-74.0445, 40.6892), 4326)),
  ('Empire State Building', ST_SetSRID(ST_MakePoint(-73.9857, 40.7484), 4326)),
  ('Central Park', ST_SetSRID(ST_MakePoint(-73.9654, 40.7829), 4326));

-- Insert from lat/lon columns
INSERT INTO locations (name, location)
SELECT
  name,
  ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)
FROM raw_locations;

-- Find distance between two points (meters)
SELECT
  l1.name AS from_location,
  l2.name AS to_location,
  ST_Distance(
    l1.location::geography,
    l2.location::geography
  ) AS distance_meters
FROM locations l1
CROSS JOIN locations l2
WHERE l1.id != l2.id;

-- Find locations within radius (5km)
SELECT
  name,
  ST_Distance(
    location::geography,
    ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography
  ) AS distance_meters
FROM locations
WHERE ST_DWithin(
  location::geography,
  ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography,
  5000  -- 5000 meters = 5km
)
ORDER BY distance_meters;

-- Nearest neighbors (5 closest locations)
SELECT
  name,
  ST_Distance(
    location::geography,
    ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography
  ) AS distance_meters
FROM locations
ORDER BY location <-> ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)
LIMIT 5;

-- Extract coordinates
SELECT
  name,
  ST_X(location) AS longitude,
  ST_Y(location) AS latitude
FROM locations;

-- Polygon (service area, delivery zone)
CREATE TABLE delivery_zones (
  id SERIAL PRIMARY KEY,
  zone_name VARCHAR(100),
  boundary GEOMETRY(Polygon, 4326)
);

-- Create polygon
INSERT INTO delivery_zones (zone_name, boundary) VALUES (
  'Downtown',
  ST_SetSRID(
    ST_MakePolygon(
      ST_GeomFromText('LINESTRING(
        -74.02 40.70,
        -74.00 40.70,
        -74.00 40.72,
        -74.02 40.72,
        -74.02 40.70
      )')
    ),
    4326
  )
);

-- Check if point is within polygon
SELECT
  l.name,
  dz.zone_name
FROM locations l
JOIN delivery_zones dz ON ST_Within(l.location, dz.boundary);

-- Find zone for a specific point
SELECT zone_name
FROM delivery_zones
WHERE ST_Contains(
  boundary,
  ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)
);

-- Bounding box query (faster preliminary filter)
SELECT name
FROM locations
WHERE location && ST_MakeEnvelope(
  -74.05, 40.70,  -- min lon, min lat
  -73.95, 40.80,  -- max lon, max lat
  4326
);
2 files · sql Explain with highlit

Geospatial data represents geographic locations and shapes. I use PostGIS for spatial queries. Point data stores coordinates—latitude, longitude. LineString represents paths. Polygon defines areas. Spatial indexes (GIST) enable fast proximity queries. Distance calculations find nearby points. Intersection queries detect overlaps. Containment checks if point is within polygon. Understanding spatial reference systems ensures accuracy. Bounding box queries filter before precise calculations. Geospatial aggregations compute centroids, unions. Essential for mapping applications, location services, delivery routing, geofencing. PostGIS rivals specialized GIS systems for many use cases.