Как создать динамические лидерские линии?

10

Я пытаюсь создать динамические выносные линии, используя представление PostGIS в дополнение к инструменту 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;

Это прекрасно работает для всех этикеток, WHERE ST_X(geom) < xcord_labelно создает неправильно выглядящие лидерные линии для этикеток WHERE ST_X(geom) > xcord_label.

введите описание изображения здесь введите описание изображения здесь

Кто-нибудь знает, как правильно расположить лидерские линии для ярлыков WHERE ST_X(geom) > xcord_label? Есть ли способ ссылаться на координату xmax меток?

введите описание изображения здесь

Лунное море
источник
1
Ваши метки в точках или единицах карты? Если это единицы карты, достаточно легко угадать высоту и тем самым сократить линию лидера, чтобы компенсировать это)
Стивен Кей
Размер метки указан в единицах карты.
Лунное море

Ответы:

9

Вы можете использовать спецификатор размещения квадранта QGIS, определенный по азимуту линии, чтобы разместить метку получше. Квадрант определяет 8 позиций вокруг точки:

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

Вот пример вокруг Нулевого острова , создание таблицы и двух представлений.

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;

Затем в QGIS добавьте:

  • points - geom
  • leader_line- geom- первичный ключ должен бытьgid
  • point_labels- geom- первичный ключ должен бытьgid

QGIS

Теперь настройте свойства слоя для point_labels:

  • Измените стиль, чтобы точка не отображалась, например, измените размер на 0.0
  • Пометьте этот слой labelи измените размещение на «Смещение от точки», изменив «Квадрант» для использования поля атрибута.quadrant

квадрант

Бинго!

Бинго

Обратите внимание, что для geographyтипов требуется немного другой подход , поскольку ST_Azimuth ведет себя по-разному.


Обновление: при добавлении новых точек в pointsслой, geomполе обновляется как обычно, но label_geomэто не так. Чтобы заполнить значение по умолчанию label_geomновыми точками, необходимо создать триггер . Но если используется триггерная функция, quadrantспецификатор может быть сохранен в pointsтаблице, а point_labelsпредставление может быть проигнорировано:

Например, давайте начнем снова с немного другого примера с одной таблицей и одним представлением:

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

Из первого примера, повторно сделать INSERT INTO pointsи CREATE OR REPLACE VIEW leader_lineзаявление, поскольку они не требуют модификации. Но игнорируйте leader_lineмнение.

Затем в QGIS добавьте:

  • points - geom
  • points - label_geom
  • leader_line- geom- первичный ключ должен бытьgid

Теперь настройте свойства слоя для pointsс label_geomкак первый пример сделал для point_labels. Спецификатор quadrantбудет изменен автоматически для новых и перемещенных точек, но вы будете замечать эти изменения только каждый раз, когда сохраняете свои изменения.

Майк Т
источник
Отличная работа, но как добавить новый точечный объект в QGIS, имеющий два геометрических столбца в одной таблице PostGIS?
Лунное море
@Lunar Sea - интересно, вы получаете две записи для таблицы, по одной на геометрию, но qgis не позволяет вам задать поле геометрии из комбо? Вы пытались использовать SQL-запрос вручную в диалоговом окне импорта (это самый правый столбец, и часто скрытый из поля зрения ...)?
Стивен Кей,
У меня есть два «точечных» слоя в QGIS ( gid | label_geom | labelи gid, geom, label).
Лунное море
@LunarSea Я переделал второй пример, который имеет одну таблицу и одно представление. Таблица имеет триггерные функции для определения значения по умолчанию для label_geom, а также обновляет quadrantзначение, поэтому point_labelслой / представление больше не требуется.
Майк Т
Отличный обходной путь, Майк! После перемещения label_geomя должен сохранить слой, отредактировать и обновить холст, чтобы увидеть фактическое положение метки. Жаль, что невозможно использовать спецификатор квадранта с инструментом QGIS «Метка перемещения».
Лунное море
1

хорошо ... так как это в единицах карты, это должно быть довольно просто, в пределах ограничений. Вы уже знаете высоту этикетки. Если бы это было в пунктах, это будет зависеть от масштаба.

Это предполагает фиксированный размер метки, поэтому насколько хорошо это работает, зависит от того, насколько однородны ваши метки, и от того, используете ли вы шрифт пропорциональной или фиксированной ширины (фиксированная ширина проще - умножьте длину метки на размер метки, чтобы получить ширину метки).

К сожалению, это не ответит на ваш вопрос о том, как на самом деле найти границы метки как предоставленные .

у вас есть 4 случая (NE, NW, SE, SW).

я предполагаю, что ваша таблица выглядит следующим образом (извинения, некоторые имена полей отличаются)

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;

Затем добавьте 4 балла (все идентичные), но с метками в 4 квадрантах, чтобы представить 4 основных варианта использования

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

Я использовал CRS 27700 (0,0 слева внизу, единицы измерения карты в м). Я принял ширину метки 50, высоту 30 единиц карты.

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

Аффинные преобразования

Другая возможность - сократить все лидирующие позиции, скажем, на 80%.

  • Вы можете использовать ST_Translate (geom, -ST_X (geom), - ST_Y (geom)), чтобы переместить линию в начало координат, чтобы получить geom_o
  • используйте ST_Scale (geom_o, 0.8,0.8), чтобы получить geom_o_scaled
  • затем повторите перевод с помощью ST_Translate (geom_o_scaled, ST_X (geom), ST_Y (geom)) обратно в исходное положение.

Это может работать лучше, хотя я не пробовал.

Стивен Кей
источник
Спасибо за ваши усилия, к сожалению, лидерские линии не очень хорошо соответствуют лейблам.
Лунное море