Bases de Datos AvanzadasCuarto añoLicenciatura en Sistemas de Información
Material de estudio
Guia BD Geograficas postgis
Otro
Subido porChipawer
Guía práctica de PostGIS
Vamos a trabajar en un plano cartesiano artificial, con coordenadas simples. No son coordenadas reales, no son latitud/longitud y no vamos a usar SRID 4326. La idea es dibujar lo que creemos que va a pasar, ejecutar SQL, mirar el resultado y, cuando convenga, verlo en QGIS.
Paso 1. Crear la base y activar PostGIS
Crear una base para la práctica:
CREATE DATABASE bdg_practica;
Conectarse a esa base y activar PostGIS:
CREATE EXTENSION postgis;
Comprobar que está disponible:
SELECT PostGIS_Version();
PostGIS agrega tipos de datos espaciales y funciones para trabajar con geometrías dentro de PostgreSQL.
Paso 2. Crear la primera tabla
CREATE TABLE puntos (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(Point)
);
La columna geom guarda geometrías. En esta tabla sólo vamos a guardar puntos.
Paso 3. Insertar el primer punto
Insertamos el origen:
INSERT INTO puntos (descripcion, geom)
VALUES (
'Origen',
ST_GeomFromText('POINT(0 0)')
);
ST_GeomFromText crea una geometría a partir de WKT.
Bases de Datos AvanzadasCuarto añoLicenciatura en Sistemas de Información
Material de estudio
Guia BD Geograficas postgis
Otro
Subido porChipawer
Guía práctica de PostGIS
Vamos a trabajar en un plano cartesiano artificial, con coordenadas simples. No son coordenadas reales, no son latitud/longitud y no vamos a usar SRID 4326. La idea es dibujar lo que creemos que va a pasar, ejecutar SQL, mirar el resultado y, cuando convenga, verlo en QGIS.
Paso 1. Crear la base y activar PostGIS
Crear una base para la práctica:
CREATE DATABASE bdg_practica;
Conectarse a esa base y activar PostGIS:
CREATE EXTENSION postgis;
Comprobar que está disponible:
SELECT PostGIS_Version();
PostGIS agrega tipos de datos espaciales y funciones para trabajar con geometrías dentro de PostgreSQL.
Paso 2. Crear la primera tabla
CREATE TABLE puntos (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(Point)
);
La columna geom guarda geometrías. En esta tabla sólo vamos a guardar puntos.
Paso 3. Insertar el primer punto
Insertamos el origen:
INSERT INTO puntos (descripcion, geom)
VALUES (
'Origen',
ST_GeomFromText('POINT(0 0)')
);
ST_GeomFromText crea una geometría a partir de WKT.
Consultar la tabla:
SELECT *
FROM puntos;
Ver la geometría en formato legible:
SELECT
id,
descripcion,
ST_AsText(geom) AS wkt
FROM puntos;
Resultado esperado:
1 | Origen | POINT(0 0)
ST_AsText muestra la geometría como texto WKT.
Paso 4. Inspeccionar el punto
Antes de ejecutar:
¿Qué dimensión tiene un punto?
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_Dimension(geom) AS dimension,
ST_IsEmpty(geom) AS esta_vacia,
ST_X(geom) AS x,
ST_Y(geom) AS y
FROM puntos
WHERE descripcion = 'Origen';
Resultado esperado:
Origen | ST_Point | 0 | false | 0 | 0
Un punto tiene dimensión 0. ST_X y ST_Y devuelven sus coordenadas.
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM puntos
ORDER BY id;
Abrir QGIS, cargar la tabla puntos, activar etiquetas con descripcion y hacer zoom a la capa.
Paso 6. Medir distancias
Antes de ejecutar:
¿Cuánto vale la distancia entre (0,0) y (5,0)?
SELECT ST_Distance(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(5 0)')
) AS distancia;
Resultado esperado:
5
Ahora una diagonal:
SELECT ST_Distance(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(5 5)')
) AS distancia;
Resultado esperado aproximado:
7.0710678118654755
En este plano las unidades son unidades abstractas. Todavía no son metros ni grados.
Medir la distancia desde cada punto hasta (15,15):
SELECT
descripcion,
ST_AsText(geom) AS punto,
ST_Distance(
geom,
ST_GeomFromText('POINT(15 15)')
) AS distancia
FROM puntos
ORDER BY distancia;
Antes de mirar el resultado, pensar cuál debería quedar más cerca.
Ahora medir la distancia de cada punto con cada uno de los otros puntos:
SELECT
p1.descripcion AS punto_1,
ST_AsText(p1.geom) AS geom_1,
p2.descripcion AS punto_2,
ST_AsText(p2.geom) AS geom_2,
ST_Distance(p1.geom, p2.geom) AS distancia
FROM puntos p1
JOIN puntos p2
ON p1.id < p2.id
ORDER BY p1.id, p2.id;
La condición p1.id < p2.id evita dos cosas:
comparar un punto consigo mismo;
repetir la misma comparación al revés.
Por ejemplo, si ya aparece Origen contra X+5, no necesitamos también X+5 contra Origen.
Paso 7. Crear los ejes del plano
Creamos una tabla para guardar líneas.
CREATE TABLE lineas (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(LineString)
);
Los ejes se dibujan más largos que los puntos de referencia para que funcionen visualmente como ejes del plano, no como segmentos que terminan justo en -10 y 10.
Ver las líneas:
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM lineas
ORDER BY id;
Cargar lineas en QGIS.
Crear una vista con el plano base:
CREATE OR REPLACE VIEW vista_plano_base AS
SELECT
'Punto: ' || descripcion AS descripcion,
geom::geometry AS geom
FROM puntos
WHERE descripcion IN (
'Origen',
'X-5', 'X-10', 'X+5', 'X+10',
'Y-5', 'Y-10', 'Y+5', 'Y+10'
)
UNION ALL
SELECT
descripcion,
geom::geometry AS geom
FROM lineas
WHERE descripcion IN ('Eje X', 'Eje Y');
Esta vista junta los puntos de referencia iniciales y los dos ejes. Sirve para abrir el resultado en el visor geométrico o en QGIS y tener contexto visual.
SELECT
descripcion,
geom
FROM vista_plano_base;
Paso 8. Agregar una línea oblicua
Ahora agregamos una línea oblicua, como si fuera la gráfica de una función lineal sobre nuestro plano.
Antes de ejecutar:
Dibujá aproximadamente una recta que pase por (-3,-10) y (8,10). ¿Corta al eje X? ¿Corta al eje Y?
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM lineas
ORDER BY id;
Actualizar la capa lineas en QGIS o volver a cargarla para ver la nueva línea.
Paso 9. Inspeccionar líneas
SELECT
descripcion,
ST_Length(geom) AS longitud,
ST_NumPoints(geom) AS cantidad_puntos,
ST_AsText(ST_StartPoint(geom)) AS inicio,
ST_AsText(ST_EndPoint(geom)) AS fin
FROM lineas
ORDER BY id;
ST_Length mide la línea. ST_StartPoint y ST_EndPoint devuelven geometrías.
Ver los vértices:
SELECT
descripcion,
ST_AsText(ST_PointN(geom, 1)) AS primer_punto,
ST_AsText(ST_PointN(geom, 2)) AS segundo_punto
FROM lineas
ORDER BY id;
ST_PointN recupera el punto número N de una línea.
Paso 10. Preguntar relaciones entre líneas
Antes de ejecutar:
¿La línea oblicua corta al eje X?
SELECT ST_Intersects(l1.geom, l2.geom) AS se_intersectan
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Resultado esperado:
true
Probar todas las líneas contra todas:
SELECT
l1.descripcion AS linea_1,
l2.descripcion AS linea_2,
ST_Intersects(l1.geom, l2.geom) AS intersects,
ST_Disjoint(l1.geom, l2.geom) AS disjoint,
ST_Touches(l1.geom, l2.geom) AS touches,
ST_Crosses(l1.geom, l2.geom) AS crosses,
ST_Equals(l1.geom, l2.geom) AS equals
FROM lineas l1
JOIN lineas l2
ON l1.id < l2.id
ORDER BY l1.id, l2.id;
Estas funciones devuelven true o false.
Mini machete:
Función
Pregunta que responde
ST_Intersects
¿Tienen algún punto en común?
ST_Disjoint
¿No tienen ningún punto en común?
ST_Touches
¿Se tocan en el borde, sin cruzarse por dentro?
ST_Crosses
¿Una geometría cruza a la otra?
ST_Equals
¿Representan exactamente la misma geometría?
Ojo: ST_Disjoint es lo contrario de ST_Intersects. Si una da true, la otra debería dar false.
Paso 11. Intersects no es Intersection
Primero preguntamos si se intersectan:
SELECT ST_Intersects(l1.geom, l2.geom) AS se_intersectan
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Eso devuelve un booleano.
Ahora preguntamos dónde se intersectan:
SELECT
ST_AsText(ST_Intersection(l1.geom, l2.geom)) AS interseccion
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Resultado esperado:
POINT(2.5 0)
Para verlo con contexto, consultar el plano base junto con el punto de intersección:
SELECT
descripcion,
geom
FROM vista_plano_base
UNION ALL
SELECT
'Intersección oblicua / eje X' AS descripcion,
ST_Intersection(l1.geom, l2.geom)::geometry AS geom
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X'
UNION ALL
SELECT
descripcion,
geom
FROM lineas
WHERE descripcion = 'Oblicua';
En el visor geométrico ahora deberían verse:
los puntos de referencia;
los ejes X e Y;
la línea oblicua;
el punto de intersección POINT(2.5 0).
Ese punto debería aparecer exactamente sobre el eje X y también sobre la línea oblicua. La consulta usa UNION ALL para mostrar todas esas geometrías juntas sin perderlas como objetos separados.
Función
Devuelve
ST_Intersects
true / false
ST_Intersection
una geometría
Esta diferencia es clave.
Paso 12. Crear buffers
Antes de ejecutar:
¿Qué forma esperás al hacer un buffer de 2 unidades alrededor del origen?
CREATE TABLE buffers (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(80),
geom geometry
);
INSERT INTO buffers (descripcion, geom)
SELECT
'Buffer de 2 unidades alrededor del origen',
ST_Buffer(geom, 2)
FROM puntos
WHERE descripcion = 'Origen';
Ver el tipo de geometría generado:
SELECT
descripcion,
ST_GeometryType(geom) AS tipo
FROM buffers;
ST_Buffer genera una nueva geometría.
Ahora un buffer alrededor del eje X:
INSERT INTO buffers (descripcion, geom)
SELECT
'Buffer de 1 unidad alrededor del eje X',
ST_Buffer(geom, 1)
FROM lineas
WHERE descripcion = 'Eje X';
Para visualizar las geometrías originales junto con sus buffers:
SELECT
'Original: ' || descripcion AS descripcion,
geom
FROM puntos
WHERE descripcion = 'Origen'
UNION ALL
SELECT
'Original: ' || descripcion AS descripcion,
geom
FROM lineas
WHERE descripcion = 'Eje X'
UNION ALL
SELECT
descripcion,
geom
FROM buffers
ORDER BY descripcion;
En el visor geométrico deberían verse el punto Origen, el Eje X y los dos buffers generados. La idea es comparar cada geometría original con la zona que produce ST_Buffer.
Cargar buffers en QGIS y usar transparencia para ver las capas anteriores.
Paso 13. Crear polígonos
CREATE TABLE poligonos (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(Polygon)
);
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM poligonos
ORDER BY descripcion;
Cargar poligonos en QGIS con relleno transparente.
Paso 14. Relaciones entre polígonos
Antes de ejecutar:
¿A contiene a C?
SELECT ST_Contains(a.geom, c.geom) AS a_contiene_c
FROM poligonos a
JOIN poligonos c
ON a.descripcion = 'A'
AND c.descripcion = 'C';
Resultado esperado:
true
Ahora la relación inversa:
SELECT ST_Within(c.geom, a.geom) AS c_dentro_de_a
FROM poligonos c
JOIN poligonos a
ON c.descripcion = 'C'
AND a.descripcion = 'A';
Resultado esperado:
true
Ahora comparamos A contra B y A contra C en una sola consulta:
SELECT
ST_Overlaps(a.geom, b.geom) AS a_b_se_superponen,
ST_Intersects(a.geom, b.geom) AS a_b_se_intersectan,
ST_Disjoint(a.geom, b.geom) AS a_b_son_disjuntos,
ST_Overlaps(a.geom, c.geom) AS a_c_se_superponen,
ST_Intersects(a.geom, c.geom) AS a_c_se_intersectan,
ST_Contains(a.geom, c.geom) AS a_contiene_c
FROM poligonos a
JOIN poligonos b
ON b.descripcion = 'B'
JOIN poligonos c
ON c.descripcion = 'C'
WHERE a.descripcion = 'A';
Resultado esperado:
true | true | false | false | true | true
A y B se superponen parcialmente. A y C no se superponen parcialmente: C está completamente dentro de A.
Paso 15. Un caso para Touches
Agregamos un polígono D que toca a A por un borde.
SELECT
ST_Touches(a.geom, d.geom) AS toca,
ST_Intersects(a.geom, d.geom) AS intersecta,
ST_Overlaps(a.geom, d.geom) AS se_superpone
FROM poligonos a
JOIN poligonos d
ON a.descripcion = 'A'
AND d.descripcion = 'D';
Resultado esperado:
true | true | false
Ahora probamos D contra B:
SELECT
ST_Touches(d.geom, b.geom) AS toca,
ST_Intersects(d.geom, b.geom) AS intersecta,
ST_Overlaps(d.geom, b.geom) AS se_superpone
FROM poligonos d
JOIN poligonos b
ON d.descripcion = 'D'
AND b.descripcion = 'B';
Resultado esperado:
false | true | true
D y B no sólo se tocan: comparten una región. Por eso ST_Overlaps da true.
Si dos geometrías se tocan, también se intersectan. Pero tocar no es lo mismo que superponerse.
Paso 16. Álgebra espacial
CREATE TABLE resultados_poligonales (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(80),
geom geometry
);
Intersección entre A y B
Antes de ejecutar, sombrear la zona común entre A y B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Intersección entre A y B',
ST_Intersection(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Intersección entre A y B';
Unión entre A y B
Antes de ejecutar, sombrear toda la región cubierta por A o por B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Unión entre A y B',
ST_Union(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Unión entre A y B';
Diferencia A menos B
Antes de ejecutar, sombrear la parte de A que no está cubierta por B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Diferencia A menos B',
ST_Difference(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Diferencia A menos B';
Cargar resultados_poligonales en QGIS y comparar con A y B.
Unión espacial del plano base
Antes usamos UNION ALL para mostrar varias geometrías juntas sin fusionarlas. Ahora usamos ST_Union para unir espacialmente todas las geometrías de vista_plano_base en una sola geometría.
SELECT
'Plano base unido' AS descripcion,
ST_Union(geom) AS geom
FROM vista_plano_base;
Ahora intersectamos la línea oblicua contra ese plano base unido y lo mostramos todo junto:
SELECT
descripcion,
geom
FROM vista_plano_base
UNION ALL
SELECT
descripcion,
geom
FROM lineas
WHERE descripcion = 'Oblicua'
UNION ALL
SELECT
'Intersección oblicua / plano base unido' AS descripcion,
ST_Intersection(l.geom, plano.geom)::geometry AS geom
FROM lineas l
CROSS JOIN (
SELECT ST_Union(geom) AS geom
FROM vista_plano_base
) plano
WHERE l.descripcion = 'Oblicua';
Resultado esperado: deberían verse el plano base, la oblicua y dos puntos de intersección, porque la oblicua corta al eje X y también al eje Y.
Para leerlos como texto:
SELECT
ST_AsText(
ST_Intersection(l.geom, plano.geom)
) AS interseccion
FROM lineas l
CROSS JOIN (
SELECT ST_Union(geom) AS geom
FROM vista_plano_base
) plano
WHERE l.descripcion = 'Oblicua';
La diferencia importante:
Operación
Qué hace
UNION ALL
Junta filas en una consulta
ST_Union
Une geometrías espacialmente
Paso 17. Envolventes: Convex Hull y Envelope
Volvemos a los puntos.
Convex Hull
Antes de ejecutar:
¿Cuál sería el polígono convexo más chico que contiene todos los puntos?
SELECT
ST_AsText(
ST_ConvexHull(
ST_Collect(geom)
)
) AS convex_hull
FROM puntos;
ST_Collect agrupa los puntos. ST_ConvexHull calcula el polígono convexo que los envuelve.
Guardar el resultado:
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Convex hull de los puntos',
ST_ConvexHull(ST_Collect(geom))
FROM puntos;
Verlo en QGIS.
Envelope
ST_Envelope devuelve el rectángulo mínimo alineado a los ejes que contiene una geometría.
Antes de ejecutar:
Si los puntos llegan hasta -10 y 10 en los ejes, ¿qué rectángulo esperás obtener?
SELECT
ST_AsText(
ST_Envelope(
ST_Collect(geom)
)
) AS envelope
FROM puntos;
Guardar el resultado:
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Envelope de los puntos',
ST_Envelope(ST_Collect(geom))
FROM puntos;
Ver juntos Convex hull y Envelope:
SELECT
descripcion,
geom
FROM resultados_poligonales
WHERE descripcion IN (
'Convex hull de los puntos',
'Envelope de los puntos'
);
El Convex Hull se ajusta a la forma convexa más chica. El Envelope siempre devuelve un rectángulo alineado a los ejes.
Paso 18. LINESTRING y MULTILINESTRING
Una línea puede tener muchos vértices y seguir siendo una sola LINESTRING.
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_NumPoints(geom) AS vertices,
ST_AsText(geom) AS wkt
FROM lineas
WHERE descripcion = 'Línea con varios vértices';
Aunque tenga muchos puntos, sigue siendo una única línea.
Ahora creamos una geometría múltiple:
CREATE TABLE multilines (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(MultiLineString)
);
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_AsText(geom) AS wkt
FROM multilines;
MULTILINESTRING contiene varias líneas independientes.
Fin
Ver la geometría en formato legible:
SELECT
id,
descripcion,
ST_AsText(geom) AS wkt
FROM puntos;
Resultado esperado:
1 | Origen | POINT(0 0)
ST_AsText muestra la geometría como texto WKT.
Paso 4. Inspeccionar el punto
Antes de ejecutar:
¿Qué dimensión tiene un punto?
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_Dimension(geom) AS dimension,
ST_IsEmpty(geom) AS esta_vacia,
ST_X(geom) AS x,
ST_Y(geom) AS y
FROM puntos
WHERE descripcion = 'Origen';
Resultado esperado:
Origen | ST_Point | 0 | false | 0 | 0
Un punto tiene dimensión 0. ST_X y ST_Y devuelven sus coordenadas.
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM puntos
ORDER BY id;
Abrir QGIS, cargar la tabla puntos, activar etiquetas con descripcion y hacer zoom a la capa.
Paso 6. Medir distancias
Antes de ejecutar:
¿Cuánto vale la distancia entre (0,0) y (5,0)?
SELECT ST_Distance(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(5 0)')
) AS distancia;
Resultado esperado:
5
Ahora una diagonal:
SELECT ST_Distance(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(5 5)')
) AS distancia;
Resultado esperado aproximado:
7.0710678118654755
En este plano las unidades son unidades abstractas. Todavía no son metros ni grados.
Medir la distancia desde cada punto hasta (15,15):
SELECT
descripcion,
ST_AsText(geom) AS punto,
ST_Distance(
geom,
ST_GeomFromText('POINT(15 15)')
) AS distancia
FROM puntos
ORDER BY distancia;
Antes de mirar el resultado, pensar cuál debería quedar más cerca.
Ahora medir la distancia de cada punto con cada uno de los otros puntos:
SELECT
p1.descripcion AS punto_1,
ST_AsText(p1.geom) AS geom_1,
p2.descripcion AS punto_2,
ST_AsText(p2.geom) AS geom_2,
ST_Distance(p1.geom, p2.geom) AS distancia
FROM puntos p1
JOIN puntos p2
ON p1.id < p2.id
ORDER BY p1.id, p2.id;
La condición p1.id < p2.id evita dos cosas:
comparar un punto consigo mismo;
repetir la misma comparación al revés.
Por ejemplo, si ya aparece Origen contra X+5, no necesitamos también X+5 contra Origen.
Paso 7. Crear los ejes del plano
Creamos una tabla para guardar líneas.
CREATE TABLE lineas (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(LineString)
);
Los ejes se dibujan más largos que los puntos de referencia para que funcionen visualmente como ejes del plano, no como segmentos que terminan justo en -10 y 10.
Ver las líneas:
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM lineas
ORDER BY id;
Cargar lineas en QGIS.
Crear una vista con el plano base:
CREATE OR REPLACE VIEW vista_plano_base AS
SELECT
'Punto: ' || descripcion AS descripcion,
geom::geometry AS geom
FROM puntos
WHERE descripcion IN (
'Origen',
'X-5', 'X-10', 'X+5', 'X+10',
'Y-5', 'Y-10', 'Y+5', 'Y+10'
)
UNION ALL
SELECT
descripcion,
geom::geometry AS geom
FROM lineas
WHERE descripcion IN ('Eje X', 'Eje Y');
Esta vista junta los puntos de referencia iniciales y los dos ejes. Sirve para abrir el resultado en el visor geométrico o en QGIS y tener contexto visual.
SELECT
descripcion,
geom
FROM vista_plano_base;
Paso 8. Agregar una línea oblicua
Ahora agregamos una línea oblicua, como si fuera la gráfica de una función lineal sobre nuestro plano.
Antes de ejecutar:
Dibujá aproximadamente una recta que pase por (-3,-10) y (8,10). ¿Corta al eje X? ¿Corta al eje Y?
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM lineas
ORDER BY id;
Actualizar la capa lineas en QGIS o volver a cargarla para ver la nueva línea.
Paso 9. Inspeccionar líneas
SELECT
descripcion,
ST_Length(geom) AS longitud,
ST_NumPoints(geom) AS cantidad_puntos,
ST_AsText(ST_StartPoint(geom)) AS inicio,
ST_AsText(ST_EndPoint(geom)) AS fin
FROM lineas
ORDER BY id;
ST_Length mide la línea. ST_StartPoint y ST_EndPoint devuelven geometrías.
Ver los vértices:
SELECT
descripcion,
ST_AsText(ST_PointN(geom, 1)) AS primer_punto,
ST_AsText(ST_PointN(geom, 2)) AS segundo_punto
FROM lineas
ORDER BY id;
ST_PointN recupera el punto número N de una línea.
Paso 10. Preguntar relaciones entre líneas
Antes de ejecutar:
¿La línea oblicua corta al eje X?
SELECT ST_Intersects(l1.geom, l2.geom) AS se_intersectan
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Resultado esperado:
true
Probar todas las líneas contra todas:
SELECT
l1.descripcion AS linea_1,
l2.descripcion AS linea_2,
ST_Intersects(l1.geom, l2.geom) AS intersects,
ST_Disjoint(l1.geom, l2.geom) AS disjoint,
ST_Touches(l1.geom, l2.geom) AS touches,
ST_Crosses(l1.geom, l2.geom) AS crosses,
ST_Equals(l1.geom, l2.geom) AS equals
FROM lineas l1
JOIN lineas l2
ON l1.id < l2.id
ORDER BY l1.id, l2.id;
Estas funciones devuelven true o false.
Mini machete:
Función
Pregunta que responde
ST_Intersects
¿Tienen algún punto en común?
ST_Disjoint
¿No tienen ningún punto en común?
ST_Touches
¿Se tocan en el borde, sin cruzarse por dentro?
ST_Crosses
¿Una geometría cruza a la otra?
ST_Equals
¿Representan exactamente la misma geometría?
Ojo: ST_Disjoint es lo contrario de ST_Intersects. Si una da true, la otra debería dar false.
Paso 11. Intersects no es Intersection
Primero preguntamos si se intersectan:
SELECT ST_Intersects(l1.geom, l2.geom) AS se_intersectan
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Eso devuelve un booleano.
Ahora preguntamos dónde se intersectan:
SELECT
ST_AsText(ST_Intersection(l1.geom, l2.geom)) AS interseccion
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X';
Resultado esperado:
POINT(2.5 0)
Para verlo con contexto, consultar el plano base junto con el punto de intersección:
SELECT
descripcion,
geom
FROM vista_plano_base
UNION ALL
SELECT
'Intersección oblicua / eje X' AS descripcion,
ST_Intersection(l1.geom, l2.geom)::geometry AS geom
FROM lineas l1
JOIN lineas l2
ON l1.descripcion = 'Oblicua'
AND l2.descripcion = 'Eje X'
UNION ALL
SELECT
descripcion,
geom
FROM lineas
WHERE descripcion = 'Oblicua';
En el visor geométrico ahora deberían verse:
los puntos de referencia;
los ejes X e Y;
la línea oblicua;
el punto de intersección POINT(2.5 0).
Ese punto debería aparecer exactamente sobre el eje X y también sobre la línea oblicua. La consulta usa UNION ALL para mostrar todas esas geometrías juntas sin perderlas como objetos separados.
Función
Devuelve
ST_Intersects
true / false
ST_Intersection
una geometría
Esta diferencia es clave.
Paso 12. Crear buffers
Antes de ejecutar:
¿Qué forma esperás al hacer un buffer de 2 unidades alrededor del origen?
CREATE TABLE buffers (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(80),
geom geometry
);
INSERT INTO buffers (descripcion, geom)
SELECT
'Buffer de 2 unidades alrededor del origen',
ST_Buffer(geom, 2)
FROM puntos
WHERE descripcion = 'Origen';
Ver el tipo de geometría generado:
SELECT
descripcion,
ST_GeometryType(geom) AS tipo
FROM buffers;
ST_Buffer genera una nueva geometría.
Ahora un buffer alrededor del eje X:
INSERT INTO buffers (descripcion, geom)
SELECT
'Buffer de 1 unidad alrededor del eje X',
ST_Buffer(geom, 1)
FROM lineas
WHERE descripcion = 'Eje X';
Para visualizar las geometrías originales junto con sus buffers:
SELECT
'Original: ' || descripcion AS descripcion,
geom
FROM puntos
WHERE descripcion = 'Origen'
UNION ALL
SELECT
'Original: ' || descripcion AS descripcion,
geom
FROM lineas
WHERE descripcion = 'Eje X'
UNION ALL
SELECT
descripcion,
geom
FROM buffers
ORDER BY descripcion;
En el visor geométrico deberían verse el punto Origen, el Eje X y los dos buffers generados. La idea es comparar cada geometría original con la zona que produce ST_Buffer.
Cargar buffers en QGIS y usar transparencia para ver las capas anteriores.
Paso 13. Crear polígonos
CREATE TABLE poligonos (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(Polygon)
);
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM poligonos
ORDER BY descripcion;
Cargar poligonos en QGIS con relleno transparente.
Paso 14. Relaciones entre polígonos
Antes de ejecutar:
¿A contiene a C?
SELECT ST_Contains(a.geom, c.geom) AS a_contiene_c
FROM poligonos a
JOIN poligonos c
ON a.descripcion = 'A'
AND c.descripcion = 'C';
Resultado esperado:
true
Ahora la relación inversa:
SELECT ST_Within(c.geom, a.geom) AS c_dentro_de_a
FROM poligonos c
JOIN poligonos a
ON c.descripcion = 'C'
AND a.descripcion = 'A';
Resultado esperado:
true
Ahora comparamos A contra B y A contra C en una sola consulta:
SELECT
ST_Overlaps(a.geom, b.geom) AS a_b_se_superponen,
ST_Intersects(a.geom, b.geom) AS a_b_se_intersectan,
ST_Disjoint(a.geom, b.geom) AS a_b_son_disjuntos,
ST_Overlaps(a.geom, c.geom) AS a_c_se_superponen,
ST_Intersects(a.geom, c.geom) AS a_c_se_intersectan,
ST_Contains(a.geom, c.geom) AS a_contiene_c
FROM poligonos a
JOIN poligonos b
ON b.descripcion = 'B'
JOIN poligonos c
ON c.descripcion = 'C'
WHERE a.descripcion = 'A';
Resultado esperado:
true | true | false | false | true | true
A y B se superponen parcialmente. A y C no se superponen parcialmente: C está completamente dentro de A.
Paso 15. Un caso para Touches
Agregamos un polígono D que toca a A por un borde.
SELECT
ST_Touches(a.geom, d.geom) AS toca,
ST_Intersects(a.geom, d.geom) AS intersecta,
ST_Overlaps(a.geom, d.geom) AS se_superpone
FROM poligonos a
JOIN poligonos d
ON a.descripcion = 'A'
AND d.descripcion = 'D';
Resultado esperado:
true | true | false
Ahora probamos D contra B:
SELECT
ST_Touches(d.geom, b.geom) AS toca,
ST_Intersects(d.geom, b.geom) AS intersecta,
ST_Overlaps(d.geom, b.geom) AS se_superpone
FROM poligonos d
JOIN poligonos b
ON d.descripcion = 'D'
AND b.descripcion = 'B';
Resultado esperado:
false | true | true
D y B no sólo se tocan: comparten una región. Por eso ST_Overlaps da true.
Si dos geometrías se tocan, también se intersectan. Pero tocar no es lo mismo que superponerse.
Paso 16. Álgebra espacial
CREATE TABLE resultados_poligonales (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(80),
geom geometry
);
Intersección entre A y B
Antes de ejecutar, sombrear la zona común entre A y B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Intersección entre A y B',
ST_Intersection(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Intersección entre A y B';
Unión entre A y B
Antes de ejecutar, sombrear toda la región cubierta por A o por B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Unión entre A y B',
ST_Union(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Unión entre A y B';
Diferencia A menos B
Antes de ejecutar, sombrear la parte de A que no está cubierta por B.
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Diferencia A menos B',
ST_Difference(a.geom, b.geom)
FROM poligonos a
JOIN poligonos b
ON a.descripcion = 'A'
AND b.descripcion = 'B';
SELECT
descripcion,
ST_AsText(geom) AS wkt
FROM resultados_poligonales
WHERE descripcion = 'Diferencia A menos B';
Cargar resultados_poligonales en QGIS y comparar con A y B.
Unión espacial del plano base
Antes usamos UNION ALL para mostrar varias geometrías juntas sin fusionarlas. Ahora usamos ST_Union para unir espacialmente todas las geometrías de vista_plano_base en una sola geometría.
SELECT
'Plano base unido' AS descripcion,
ST_Union(geom) AS geom
FROM vista_plano_base;
Ahora intersectamos la línea oblicua contra ese plano base unido y lo mostramos todo junto:
SELECT
descripcion,
geom
FROM vista_plano_base
UNION ALL
SELECT
descripcion,
geom
FROM lineas
WHERE descripcion = 'Oblicua'
UNION ALL
SELECT
'Intersección oblicua / plano base unido' AS descripcion,
ST_Intersection(l.geom, plano.geom)::geometry AS geom
FROM lineas l
CROSS JOIN (
SELECT ST_Union(geom) AS geom
FROM vista_plano_base
) plano
WHERE l.descripcion = 'Oblicua';
Resultado esperado: deberían verse el plano base, la oblicua y dos puntos de intersección, porque la oblicua corta al eje X y también al eje Y.
Para leerlos como texto:
SELECT
ST_AsText(
ST_Intersection(l.geom, plano.geom)
) AS interseccion
FROM lineas l
CROSS JOIN (
SELECT ST_Union(geom) AS geom
FROM vista_plano_base
) plano
WHERE l.descripcion = 'Oblicua';
La diferencia importante:
Operación
Qué hace
UNION ALL
Junta filas en una consulta
ST_Union
Une geometrías espacialmente
Paso 17. Envolventes: Convex Hull y Envelope
Volvemos a los puntos.
Convex Hull
Antes de ejecutar:
¿Cuál sería el polígono convexo más chico que contiene todos los puntos?
SELECT
ST_AsText(
ST_ConvexHull(
ST_Collect(geom)
)
) AS convex_hull
FROM puntos;
ST_Collect agrupa los puntos. ST_ConvexHull calcula el polígono convexo que los envuelve.
Guardar el resultado:
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Convex hull de los puntos',
ST_ConvexHull(ST_Collect(geom))
FROM puntos;
Verlo en QGIS.
Envelope
ST_Envelope devuelve el rectángulo mínimo alineado a los ejes que contiene una geometría.
Antes de ejecutar:
Si los puntos llegan hasta -10 y 10 en los ejes, ¿qué rectángulo esperás obtener?
SELECT
ST_AsText(
ST_Envelope(
ST_Collect(geom)
)
) AS envelope
FROM puntos;
Guardar el resultado:
INSERT INTO resultados_poligonales (descripcion, geom)
SELECT
'Envelope de los puntos',
ST_Envelope(ST_Collect(geom))
FROM puntos;
Ver juntos Convex hull y Envelope:
SELECT
descripcion,
geom
FROM resultados_poligonales
WHERE descripcion IN (
'Convex hull de los puntos',
'Envelope de los puntos'
);
El Convex Hull se ajusta a la forma convexa más chica. El Envelope siempre devuelve un rectángulo alineado a los ejes.
Paso 18. LINESTRING y MULTILINESTRING
Una línea puede tener muchos vértices y seguir siendo una sola LINESTRING.
SELECT
descripcion,
ST_GeometryType(geom) AS tipo,
ST_NumPoints(geom) AS vertices,
ST_AsText(geom) AS wkt
FROM lineas
WHERE descripcion = 'Línea con varios vértices';
Aunque tenga muchos puntos, sigue siendo una única línea.
Ahora creamos una geometría múltiple:
CREATE TABLE multilines (
id SERIAL PRIMARY KEY,
descripcion VARCHAR(50),
geom geometry(MultiLineString)
);