Extrapolando uma linha no PostGIS

19

Estou tentando extrapolar de um segmento de linha para encontrar um ponto na linha, mas um terceiro do caminho 'voltar', ou seja, tentando encontrar um ponto new, dados os pontos Ae Babaixo:

insira a descrição da imagem aqui

Dada uma linha, eu posso interpolar para obter uma posição em qualquer porcentagem específica:

=# select st_line_interpolate_point(
   st_makeline('0101000020E6100000300DC347C49418C03EE8D9ACFAA44A40', 
               '0101000020E6100000FB743C66A03218C0CDCCCCCCCC7C4A40'), 
   0.333);
0101000020E6100000ED45B41D537718C069C6A2E9EC984A40

Tentei inserir um número negativo para encontrar um ponto ao longo da linha na direção oposta, mas isso falha porque o argumento de interpolação deve estar no intervalo [0, 1]

Pensei em escalar a linha primeiro, mas isso não usa o centro da linha como origem, por isso é inútil para meus propósitos.

EoghanM
fonte

Respostas:

21

Outra maneira de resolver um problema semelhante anteriormente é decompô-lo nas etapas a seguir.

-- get the points A and B given a line L
A := ST_STARTPOINT(L);
B := ST_ENDPOINT(L);

-- get the bearing from point B --> A
azimuth := ST_AZIMUTH(B,A);

-- get the length of the line A --> B
length := ST_DISTANCE(A,B);
newlength := length + (length * (1/3));   -- increase the line length by 1/3

-- create a new point 1/3 as far away from A as B is from A
newpoint := ST_TRANSLATE(A, sin(azimuth) * newlength, cos(azimuth) * newlength);

EDIT: corrigiu a atribuição de newlength para que ele fosse 1 1/3 do comprimento, em vez de 1/3, e alternou A & B para corresponder ao diagrama.

Jayden
fonte
Temos um vencedor! Muito mais compreensível.
EoghanM
Isso é muito legal
Nathan W
Obrigado. Originalmente, eu tinha esse trecho de algum trabalho que eu estava fazendo interpolando manualmente entre linhas de contorno - acabou sendo uma perda de tempo o que eu estava tentando fazer com os contornos, mas feliz por esse trecho ter ajudado outra pessoa! :)
Jayden
6

Resolva-o com:

F = 1.3333
st_affine(A, F, 0, 
             0, F, 
            (F-1)*-st_x(st_line_interpolate_point(st_makeline(A, B), 0.5)), 
            (F-1)*-st_y(st_line_interpolate_point(st_makeline(A, B), 0.5))
          )

Explicação:

(2-d) Escale o ponto inicial por um fator de 1,3333, tomando o ponto médio do segmento de linha como a origem do dimensionamento.

Saia do papel milimetrado!

http://en.wikipedia.org/wiki/Affine_transformation

EoghanM
fonte
2

Eu escrevi uma função para isso:

CREATE OR REPLACE FUNCTION st_extend (
    geom geometry,
    head_rate double precision,
    head_constant double precision,
    tail_rate double precision,
    tail_constant double precision)
  RETURNS geometry AS
$BODY$
-- Extends a linestring.
-- First segment get extended by length * head_rate + head_constant.
-- Last segment get extended by length * tail_rate + tail_constant.
--
-- References:
-- http://blog.cleverelephant.ca/2015/02/breaking-linestring-into-segments.html
-- /gis//a/104451/44921
-- /gis//a/16701/44921
WITH segment_parts AS (
SELECT
(pt).path[1]-1 as segment_num
,
CASE
WHEN
  (nth_value((pt).path, 2) OVER ()) = (pt).path
AND
  (last_value((pt).path) OVER ()) = (pt).path
THEN
  3
WHEN
  (nth_value((pt).path, 2) OVER ()) = (pt).path
THEN
  1
WHEN
  (last_value((pt).path) OVER ()) = (pt).path
THEN
  2
ELSE
  0
END AS segment_flag
,
(pt).geom AS a
,
lag((pt).geom, 1, NULL) OVER () AS b
FROM ST_DumpPoints($1) pt
)
,
extended_segment_parts
AS
(
SELECT
  *
  ,
  ST_Azimuth(a,b) AS az1
  ,
  ST_Azimuth(b,a) AS az2
  ,
  ST_Distance(a,b) AS len
FROM
segment_parts
where b IS NOT NULL
)
,
expanded_segment_parts
AS
(
SELECT
  segment_num
  ,
  CASE
  WHEN
    bool(segment_flag & 2)
  THEN
    ST_Translate(b, sin(az2) * (len*tail_rate+tail_constant), cos(az2) * (len*tail_rate+tail_constant))
  ELSE
    a
  END
  AS a
  ,
  CASE
  WHEN
    bool(segment_flag & 1)
  THEN
    ST_Translate(a, sin(az1) * (len*head_rate+head_constant), cos(az1) * (len*head_rate+head_constant))
  ELSE
    b
  END
  AS b
FROM extended_segment_parts
)
,
expanded_segment_lines
AS
(
SELECT
  segment_num
  ,
  ST_MakeLine(a, b) as geom
FROM
expanded_segment_parts
)
SELECT
  ST_LineMerge(ST_Collect(geom ORDER BY segment_num)) AS geom
FROM expanded_segment_lines
;
$BODY$
LANGUAGE sql;

Uso:

SELECT st_extend(
st_makeline(
  '0101000020E6100000300DC347C49418C03EE8D9ACFAA44A40', 
  '0101000020E6100000FB743C66A03218C0CDCCCCCCCC7C4A40'
),
1.333::double precision,
0::double precision,
1::double precision,
0::double precision
);

Observe que isso gera a cadeia de linhas mais longa, mas não o terminal.

Código no GitHub Gist (se você votar aqui, eu também apreciaria uma estrela lá)

Descrição dos parâmetros (caso você os tenha perdido no comentário da função sql):

  • O comprimento do primeiro segmento será original_length * head_rate + head_constant.
  • Se você deseja duplicar, a taxa de juros é 2, a constante é 0.
  • Na Hungria, normalmente usamos projeção EOV que é baseada em medidores. Portanto, se eu quiser adicionar 2 metros ao final da linha, defino o tail: rate como 1 e o tail_constant como 2.
SzieberthAdam
fonte
Isso funciona muito bem. Você pode adicionar algumas informações sobre head_rate, head_constant, tail_rate e tail_constant? Eles não são explicados aqui ou no seu GitHub. Estou assumindo que a taxa inicial = fator de escala para extensão de linha após o ponto final e a taxa final = fator de escala para extensão de linha antes do ponto inicial. Como as constantes funcionam? Que efeito eles têm?
jbalk 16/11
Está no comentário. O comprimento do primeiro segmento será original_length * head_rate + head_constant. Se você deseja duplicar, a taxa de cabeça é 2, a constante é 0. Na Hungria, normalmente usamos projeção EOV, que é baseada em medidores. Portanto, se eu quiser adicionar 2 metros ao final da linha, defino a cauda: rate como 1 e tail_constant como 2.
SzieberthAdam 17/11
Obrigado! E obrigado por compartilhar esta função. Funciona perfeitamente e roda rapidamente.
jbalk 18/11