¿Cómo crear líneas líderes dinámicas?

10

Estoy tratando de crear líneas líderes dinámicas mediante el uso de una vista PostGIS además de la herramienta QGIS "Mover etiqueta".

CREATE VIEW leader_line AS
SELECT
gid,
ST_MakeLine(geom, ST_SetSRID(ST_MakePoint(xcord_label, ycord_label), SRID))::geometry(linestring, SRID) AS geom
FROM point
WHERE xcord_label IS NOT NULL;

Esto funciona bien para todas las etiquetas, WHERE ST_X(geom) < xcord_labelpero crea líneas de guía de aspecto incorrecto para las etiquetas WHERE ST_X(geom) > xcord_label.

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

¿Alguien sabe cómo obtener líneas de guía correctamente colocadas para las etiquetas WHERE ST_X(geom) > xcord_label? ¿Hay alguna forma de referirse a la coordenada xmax de las etiquetas?

ingrese la descripción de la imagen aquí

Mar lunar
fuente
1
¿Están sus etiquetas en puntos o unidades de mapa? Si se trata de unidades de mapa, debería ser bastante fácil adivinar la altura y, por lo tanto, acortar su línea de líder para compensar)
Steven Kay
El tamaño de la etiqueta está en unidades de mapa.
Mar Lunar

Respuestas:

9

Puede utilizar el especificador de colocación de cuadrantes de QGIS determinado a partir del acimut de la línea para colocar una mejor etiqueta. El cuadrante especifica 8 posiciones alrededor de un punto:

[ 0=Above Left | 1=Above | 2=Above Right |
  3=Left       | 4=Over  | 5=Right       |
  6=Below Left | 7=Below | 8=Below Right ]

Aquí hay un ejemplo de Null Island , creando una tabla y dos vistas.

CREATE TABLE points (
  gid serial PRIMARY KEY,
  geom geometry(Point, 4326),
  label_geom geometry(Point, 4326),
  label text
);

INSERT INTO points(geom, label_geom, label)
SELECT origin, pt, round(degrees(ST_Azimuth(origin, pt))) || ' degrees'
FROM (
  SELECT
    ST_SetSRID(ST_MakePoint(0, 0), 4326) AS origin,
    ST_SetSRID(ST_MakePoint(cos(radians(x)), sin(radians(x))), 4326) AS pt
  FROM generate_series(0, 350, 15) AS x
) AS f;

CREATE OR REPLACE VIEW point_labels AS
  SELECT gid, label_geom AS geom,
  CASE
    WHEN ST_Azimuth(geom, label_geom) ISNULL THEN 2 -- default if azimuth cannot be determined
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 22.5 THEN 1 -- Above
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 67.5 THEN 2 -- Above Right
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 112.5 THEN 5 -- Right
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 157.5 THEN 8 -- Below Right
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 202.5 THEN 7 -- Below
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 247.5 THEN 6 -- Below Left
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 292.5 THEN 3 -- Left
    WHEN degrees(ST_Azimuth(geom, label_geom)) < 337.5 THEN 0 -- Above Left
    ELSE 1 -- >= 337.5 Above
  END AS quadrant, label
  FROM points;

CREATE OR REPLACE VIEW leader_line AS
  SELECT gid, ST_MakeLine(geom, label_geom)::geometry(LineString, 4326) AS geom, label
  FROM points;

Luego, en QGIS, agregue:

  • points - geom
  • leader_line- geom- la clave principal debe sergid
  • point_labels- geom- la clave principal debe sergid

QGIS

Ahora configure las propiedades de capa para point_labels:

  • Cambie el estilo para que no se dibuje el punto, por ejemplo, cambie el tamaño a 0.0
  • Etiquete esta capa con label, y cambie la ubicación a "Desplazamiento desde el punto", modificando el "Cuadrante" para usar el campo de atributoquadrant

cuadrante

¡Bingo!

Bingo

Tenga en cuenta que se requiere un enfoque ligeramente diferente para los geographytipos, ya que ST_Azimuth se comporta de manera diferente.


Actualización: al agregar nuevos puntos a la pointscapa, el geomcampo se actualiza como de costumbre, pero label_geomno lo es. Para completar un valor predeterminado de label_geomcon nuevos puntos, se debe crear un activador . Pero si se utiliza una función de activación, el quadrantespecificador se puede almacenar en la pointstabla y point_labelsse puede ignorar la vista:

Por ejemplo, comencemos nuevamente con un ejemplo ligeramente diferente con una tabla y una vista:

-- DROP TABLE points CASCADE;
CREATE TABLE points (
  gid serial PRIMARY KEY,
  geom geometry(Point, 4326),
  label_geom geometry(Point, 4326),
  quadrant integer,
  label text
);

CREATE FUNCTION label_geom_tg_fn() RETURNS trigger AS
$BODY$
DECLARE
  azimuth float8;
BEGIN
  -- Set a default label_geom
  IF NEW.label_geom ISNULL THEN
    NEW.label_geom := NEW.geom;
  END IF;
  -- Determine quadrant
  azimuth := degrees(ST_Azimuth(NEW.geom, NEW.label_geom));
  NEW.quadrant := CASE
    WHEN azimuth ISNULL THEN 2 -- azimuth cannot be determined, so put Above Right
    WHEN azimuth < 22.5 THEN 1 -- Above
    WHEN azimuth < 67.5 THEN 2 -- Above Right
    WHEN azimuth < 112.5 THEN 5 -- Right
    WHEN azimuth < 157.5 THEN 8 -- Below Right
    WHEN azimuth < 202.5 THEN 7 -- Below
    WHEN azimuth < 247.5 THEN 6 -- Below Left
    WHEN azimuth < 292.5 THEN 3 -- Left
    WHEN azimuth < 337.5 THEN 0 -- Above Left
    ELSE 1 END;-- >= 337.5 Above
  RETURN NEW;
END;$BODY$ LANGUAGE plpgsql;

CREATE TRIGGER label_geom_tg BEFORE INSERT OR UPDATE
   ON points FOR EACH ROW
   EXECUTE PROCEDURE label_geom_tg_fn();

Desde el primer ejemplo, vuelva a hacer las declaraciones INSERT INTO pointsy CREATE OR REPLACE VIEW leader_line, ya que no requieren modificación. Pero ignora la leader_linevista.

Luego, en QGIS, agregue:

  • points - geom
  • points - label_geom
  • leader_line- geom- la clave principal debe sergid

Ahora configure las propiedades de la capa pointscon label_geomcomo lo hizo el primer ejemplo point_labels. El quadrantespecificador se modificará automáticamente para los puntos nuevos y movidos, pero solo notará estos cambios cada vez que guarde sus ediciones.

Mike T
fuente
Gran trabajo, pero ¿cómo agregar una nueva característica de punto en QGIS que tiene dos columnas de geometría en una tabla PostGIS?
Mar lunar
@Lunar Sea: interesante, ¿obtienes dos entradas para la tabla, una por geometría, pero qgis no te permite establecer el campo de geometría desde el combo? ¿Has intentado usar una consulta SQL manual en el cuadro de diálogo de importación (es la columna más a la derecha, y a menudo está oculta fuera de la vista ...)?
Steven Kay
Tengo dos capas de 'puntos' en QGIS ( gid | label_geom | labely gid, geom, label).
Mar Lunar
@LunarSea Reformé un segundo ejemplo que tiene una tabla y una vista. La tabla tiene funciones de activación para determinar un valor predeterminado para el label_geom, y también actualiza el quadrantvalor también, por lo que la point_labelcapa / vista ya no es necesaria.
Mike T
Buena solución Mike! Después de mover un label_geomarchivo, tengo que guardar la edición de la capa y actualizar el lienzo para ver la posición real de la etiqueta. Es una pena que no haya forma de utilizar un especificador de cuadrante con la herramienta QGIS "Mover etiqueta".
Mar Lunar
1

bien ... como está en unidades de mapa, esto debería ser bastante sencillo, dentro de las limitaciones. Ya sabes la altura de la etiqueta. Si estuviera en puntos, dependería de la escala.

Esto supone un tamaño de etiqueta fijo, por lo que su funcionamiento depende de cuán uniformes sean sus etiquetas y de si usa o no una fuente proporcional o de ancho fijo (el ancho fijo es más fácil: multiplique la longitud de la etiqueta por el tamaño de la etiqueta para obtener el ancho de la etiqueta).

Lamentablemente, esto no responde a su pregunta sobre cómo encontrar realmente los límites de la etiqueta tal como se representa .

Tiene 4 casos (NE, NW, SE, SW).

Supongo que su tabla se ve así (disculpas, algunos nombres de campo son diferentes)

CREATE TABLE points
(
  uniq int PRIMARY KEY,
  geom geometry(Point,27700),
  label_x int,
  label_y int,
  labeltext character varying(100)
);
ALTER TABLE points
  OWNER TO user;
GRANT ALL ON TABLE points TO user;
GRANT SELECT ON TABLE points TO public;

Luego, agregue 4 puntos (todos idénticos) pero con etiquetas en los 4 cuadrantes para representar los 4 casos de uso principales

insert into points values 
(1,ST_SetSRID(ST_Point(1000,1000),27700),750,750,'123');

insert into points values(2,ST_SetSRID(ST_Point(1000,1000),27700),1250,1250,'456')

insert into points values 
(3,ST_SetSRID(ST_Point(1000,1000),27700),750,1250,'456')

insert into points values 
(4,ST_SetSRID(ST_Point(1000,1000),27700),1250,750,'789')

Utilicé CRS 27700 (0,0 en la parte inferior izquierda, unidades de mapa en m). Asumí un ancho de etiqueta 50, altura 30 unidades de mapa.

-- SW use case
CREATE OR REPLACE VIEW leader_line_sw AS
SELECT
uniq,
ST_MakeLine(geom, ST_SetSRID(ST_MakePoint(label_x+50, label_y+30), 27700))::geometry(linestring, 27700) AS geom
FROM points
WHERE label_x IS NOT NULL AND 
label_y<=ST_Y(geom) and label_x<=ST_X(geom);

-- SE use case
CREATE OR REPLACE VIEW leader_line_se AS
SELECT
uniq,
ST_MakeLine(geom, ST_SetSRID(ST_MakePoint(label_x, label_y-30), 27700))::geometry(linestring, 27700) AS geom
FROM points
WHERE label_x IS NOT NULL AND 
label_y<=ST_Y(geom) and label_x>ST_X(geom);


-- NE use case
CREATE OR REPLACE VIEW leader_line_ne AS
SELECT
uniq,
ST_MakeLine(geom, ST_SetSRID(ST_MakePoint(label_x, label_y), 27700))::geometry(linestring, 27700) AS geom
FROM points
WHERE label_x IS NOT NULL AND 
label_y>ST_Y(geom) and label_x>ST_X(geom);

-- NW use case
CREATE OR REPLACE VIEW leader_line_nw2 AS
SELECT
uniq,
ST_MakeLine(geom, ST_SetSRID(ST_MakePoint(label_x+50, label_y), 27700))::geometry(linestring, 27700) AS geom
FROM points
WHERE label_x IS NOT NULL AND 
label_y>ST_Y(geom) and label_x<=ST_X(geom);

Transformaciones afines

Otra posibilidad es acortar todas las líneas principales, por ejemplo, el 80%.

  • Puede usar ST_Translate (geom, -ST_X (geom), - ST_Y (geom)) para mover la línea al origen para obtener geom_o
  • use ST_Scale (geom_o, 0.8,0.8) para obtener geom_o_scaled
  • luego vuelva a traducir usando ST_Translate (geom_o_scaled, ST_X (geom), ST_Y (geom)) de vuelta a la posición original.

Esto podría funcionar mejor, aunque no lo he probado.

Steven Kay
fuente
Gracias por sus esfuerzos, desafortunadamente las líneas líderes no coinciden muy bien con las etiquetas.
Mar Lunar