Jak stworzyć dynamiczne linie wiodące?

10

Próbuję utworzyć dynamiczne linie odniesienia za pomocą widoku PostGIS oprócz narzędzia QGIS „Move Label”.

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;

Działa to dobrze dla wszystkich etykiet, WHERE ST_X(geom) < xcord_labelale tworzy źle wyglądające linie wiodące dla etykiet WHERE ST_X(geom) > xcord_label.

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Czy ktoś wie, jak uzyskać odpowiednio rozmieszczone linie wiodące dla etykiet WHERE ST_X(geom) > xcord_label? Czy istnieje sposób odwoływania się do współrzędnej xmax etykiet?

wprowadź opis zdjęcia tutaj

Morze Księżycowe
źródło
1
są etykiety w punktach lub map jednostek czy to jednostki map powinno być dość łatwe do odgadnięcia wysokość, a tym samym skrócić linię odniesienia do kompensacji)?
Steven Kay
Rozmiar etykiety jest w jednostkach mapy.
Morze Księżycowe

Odpowiedzi:

9

Możesz użyć specyfikatora umieszczenia kwadrantu QGIS określonego na podstawie azymutu linii, aby umieścić lepszą etykietę. Kwadrant określa 8 pozycji wokół punktu:

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

Oto przykład wokół wyspy Null , tworząc tabelę i dwa widoki.

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;

Następnie w QGIS dodaj:

  • points - geom
  • leader_line- geom- kluczem podstawowym musi byćgid
  • point_labels- geom- kluczem podstawowym musi byćgid

QGIS

Teraz skonfiguruj właściwości warstwy dla point_labels:

  • Zmień styl, aby punkt nie był rysowany, np. Zmień rozmiar na 0.0
  • Oznacz tę warstwę etykietą labeli zmień jej położenie na „Odsunięcie od punktu”, modyfikując „Kwadrant”, aby użyć pola atrybutuquadrant

kwadrant

Bingo!

Bingo

Zauważ, że dla geographytypów wymagane jest nieco inne podejście , ponieważ ST_Azimuth zachowuje się inaczej.


Aktualizacja: Podczas dodawania nowych punktów do pointswarstwy geompole jest aktualizowane jak zwykle, ale label_geomtak nie jest. Aby wypełnić wartość domyślną label_geomnowymi punktami, należy utworzyć wyzwalacz . Ale jeśli używana jest funkcja wyzwalacza, quadrantspecyfikator można zapisać w pointstabeli, a point_labelswidok można zignorować:

Na przykład zacznijmy od nieco innego przykładu z jedną tabelą i jednym widokiem:

-- 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();

W pierwszym przykładzie ponownie wykonaj instrukcje INSERT INTO pointsi CREATE OR REPLACE VIEW leader_line, ponieważ nie wymagają one modyfikacji. Ale zignoruj ​​ten leader_linewidok.

Następnie w QGIS dodaj:

  • points - geom
  • points - label_geom
  • leader_line- geom- kluczem podstawowym musi byćgid

Teraz skonfiguruj właściwości warstwy dla pointsz label_geomtak jak w pierwszym przykładzie point_labels. Specyfikator quadrantzostanie zmodyfikowany automatycznie dla nowych i przeniesionych punktów, ale zauważysz te zmiany za każdym razem, gdy zapiszesz swoje zmiany.

Mike T.
źródło
Świetna robota, ale jak dodać nową funkcję punktową w QGIS mającą dwie kolumny geometrii w jednej tabeli PostGIS?
Lunar Sea
@Lunar Sea - ciekawe, czy dostajesz dwa wpisy do tabeli, po jednym na geometrię, ale qgis nie pozwala ci ustawić pola geometrii z kombinacji? Czy próbowałeś użyć ręcznego zapytania SQL w oknie dialogowym importu (jest to skrajna prawa kolumna i często jest niewidoczna ...)?
Steven Kay,
Mam dwie warstwy punktów w QGIS ( gid | label_geom | labeli gid, geom, label).
Morze Księżycowe
@LunarSea Przerobiłem drugi przykład, który ma jedną tabelę i jeden widok. Tabela ma funkcje wyzwalające do określania wartości domyślnej label_geom, a także aktualizuje quadrantwartość, więc point_labelwarstwa / widok nie jest już potrzebny.
Mike T
Miłe obejście Mike! Po przeniesieniu label_geommuszę zapisać edycję warstwy i odświeżyć płótno, aby zobaczyć rzeczywiste położenie etykiety. Szkoda, że ​​nie ma możliwości użycia specyfikatora kwadrantu z narzędziem QGIS „Move label”.
Morze Księżycowe
1

ok .. ponieważ jest to w jednostkach mapy, powinno to być dość proste, w granicach ograniczeń. Znasz już wysokość etykiety. Gdyby był w punktach, byłby zależny od skali.

Zakłada się stały rozmiar etykiety, więc to, jak to działa, zależy od tego, jak jednolite są twoje etykiety, oraz od tego, czy używasz czcionki proporcjonalnej lub stałej szerokości (stała szerokość jest łatwiejsza - pomnóż długość etykiety przez rozmiar etykiety do pobierz szerokość etykiety).

Niestety nie odpowiada to na pytanie, jak znaleźć granice etykiety jako renderowane .

masz 4 przypadki (NE, NW, SE, SW).

zakładam, że twoja tabela wygląda tak (przepraszam, niektóre nazwy pól są różne)

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;

Następnie dodaj 4 punkty (wszystkie identyczne), ale z etykietami w 4 ćwiartkach, aby przedstawić 4 główne przypadki użycia

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')

Użyłem CRS 27700 (0,0 w lewym dolnym rogu, jednostki mapy w metrach). Przyjąłem szerokość etykiety 50, wysokość 30 jednostek mapy.

-- 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);

Transformacje afiniczne

Inną możliwością jest skrócenie wszystkich wiodących linii, na przykład 80%.

  • Możesz użyć ST_Translate (geom, -ST_X (geom), - ST_Y (geom)), aby przesunąć linię do początku, aby uzyskać geom_o
  • użyj ST_Scale (geom_o, 0.8,0.8), aby uzyskać geom_o_scaled
  • następnie ponownie przetłumacz za pomocą ST_Translate (geom_o_scaled, ST_X (geom), ST_Y (geom)) z powrotem do pierwotnej pozycji.

To może działać lepiej, chociaż tego nie próbowałem.

Steven Kay
źródło
Dziękuję za twoje wysiłki, niestety linie wiodące nie pasują do etykiet bardzo dobrze.
Morze Księżycowe