PostGis. Como encontro um erro em uma consulta espacial?

imagem


Boa tarde! Eu sou Victor, um desenvolvedor de desenvolvimento de Gems. Todos os dias, nossa equipe trabalha com dados espaciais de complexidade e qualidade variadas. Ao realizar uma operação de interseção espacial com Postgis no Postgresql, encontramos o seguinte erro:



XX000: GEOSIntersects: TopologyException: side location conflict at 10398.659 3844.9200000000001



A solicitação que levou ao erro tem a seguinte aparência:



select q1.key,st_asGeoJson(geoloc)
    from usahalinsk.V_GEO_OOPT q1 
        where ST_Intersects(geoloc,
                ST_GeomFromGeoJSON('{"type":"Polygon","coordinates":
                    [[[11165.15,2087.5],[11112,2066.6],[11127.6,2022.5],
                    [11122.6,2020.7],
                    [11122.25,2021.2],[11107.07,2015.7],
                    [11121,1947],[11123.48,1922.99],[11128.42,1874.4],
                    [11131.5,1875],[11140.96,1876.81],[11160.73,1880.59],
                    [11201.04,1888.3],[11194.2,1908],[11221.93,1916.57],
                    [11223.3,1917],[11165.15,2087.5]]]}'))



A solução para esse problema bloqueia o trabalho dos usuários, pois não permite a construção de relatórios sobre os dados e retarda o trabalho de prestação de serviços. Muitas ações no sistema que estamos desenvolvendo, tais como: preparar o layout de um terreno, preparar um plano de urbanismo de um terreno, entre outras, utilizam operações espaciais como esta.



Vamos supor que o problema seja a geometria incorreta. Freqüentemente, esse erro é gerado pela operação de interseção se os objetos que participam da consulta tiverem autossecções ou pontos duplicados. Um exemplo desses erros de geometria pode ser visto abaixo. (A borda do polígono se cruza e há duas coordenadas idênticas na linha)





Conduzimos nossa própria investigação para encontrar as causas do erro e queremos informá-lo sobre isso.

No momento, estamos usando Postgis 2.4 e Postgresql 9.6. Vamos direto para a prática. Vamos verificar a validade da geometria constante e descobrir que tudo funciona corretamente.





Podemos supor que o assunto está na tabela (ver) usahalinsk.V_GEO_OOPT na qual estamos procurando cruzamentos. Para confirmar a hipótese, também verificaremos esses dados.





Mas também não encontramos erros aqui. Além disso, os dados não foram incluídos na amostra. Se fossem, a tarefa seria resolvida corrigindo as entradas encontradas por meio da função st_makeValid do Postgis.



Mas não há erros na visualização e a solicitação não é executada. Sugerimos examinar seu plano.





Nota: no modelo real usamos três colunas para geometria (para polígonos, linhas e pontos), mas por brevidade vamos chamar isso de campo de geolocalização - ele armazena a geometria e a exibe na vista.



Nossa visão usahalinsk.V_GEO_OOPT foi construída como uma seleção da tabela com dados espaciais usahalinsk.d_geometry e um índice espacial foi criado no campo com geometria.



Isso significa que quando a consulta é executada, o índice está sendo lido, e em algum lugar da tabela, não entrando em nossa seleção, há dados espaciais inválidos que foram incluídos no índice. ele é construído em toda a mesa.



Vamos tentar excluir o índice:



DROP INDEX usahalinsk.d_geometry_cs1_all_sx;


E vamos tentar atender à solicitação problemática.





Funcionou sem erros. Confirmamos que o problema está no índice. Você pode retornar o índice, mas com a condição para a geometria correta:



CREATE INDEX d_geometry_cs1_all_sx
  ON usahalinsk.d_geometry
  USING gist(geoloc)
  where st_isvalid(geoloc)=true;


Vamos verificar a implementação e ver o plano.





A solicitação foi executada sem erros e o índice no plano também é usado. A desvantagem de tal solução pode ser a lentidão do insert / update, tk. além disso, a condição será verificada ao reconstruir o índice.



Vamos retornar essa alteração de volta e ainda tentar encontrar quais objetos no índice estão causando a falha de nossa consulta.



DROP INDEX usahalinsk.d_geometry_cs1_all_sx;
 
CREATE INDEX d_geometry_cs1_all_sx
  ON usahalinsk.d_geometry
  USING gist
  (geoloc);


Lembrarei que temos as coordenadas do local do erro:



XX000: GEOSIntersects: TopologyException: side location conflict at 10398.659 3844.9200000000001



Mas se buscarmos nos dados ou como resultado da função IsValidReason, que retorna o motivo do erro, não encontraremos nada semelhante.



select key,ST_IsValidReason(geoloc)
from usahalinsk.d_geometry 
    where st_isvalid(geoloc)!=true
        and ST_AsText(geoloc) like '%3844.9200000000001%';
        
select key,ST_IsValidReason(geoloc)
from usahalinsk.d_geometry 
    where st_isvalid(geoloc)!=true
        and ST_IsValidReason(geoloc) like '%3844.9200000000001%';


Você pode usar o seguinte script para localizar objetos que afetam a consulta. Iremos verificar cada objeto da tabela e cruzá-lo com a constante desejada. Durante a execução, pegamos exceções e verificamos seu conteúdo. Se o erro contém as coordenadas de que precisamos, esta é a geometria do nosso problema.



do
$$
declare
    tKey bigint;
    rec record;
    error_text text;
    -- 
    error_info text:='GEOSIntersects: TopologyException: side location conflict at 10398.659 3844.9200000000001';
begin
    --    
    for rec in(select key from usahalinsk.d_geometry)
    loop
        begin
            select key into tKey
            from (select * from usahalinsk.d_geometry q1 
                                --   
                        where q1.key=rec.key
                            and ST_Intersects(geoloc,
                                    -- 
                                    ST_GeomFromGeoJSON('{"type":"Polygon","coordinates":[[[11165.15,2087.5],
                                    [11112,2066.6],[11127.6,2022.5],[11122.6,2020.7],
                                    [11122.25,2021.2],[11107.07,2015.7],[11121,1947],                                                    [11123.48,1922.99],[11128.42,1874.4],
[11131.5,1875],[11140.96,1876.81],                                    [11160.73,1880.59],[11201.04,1888.3],
[11194.2,1908],[11221.93,1916.57],[11223.3,1917],
                                    [11165.15,2087.5]]]}'))) geoQ;        
        exception when others then
                --    
              GET STACKED DIAGNOSTICS error_text = MESSAGE_TEXT;
            --    ,     
            if error_text=error_info then
                raise info '%',rec.key;    
            end if;                  
        end;
    end loop;
end$$;


Como resultado, obtemos três chaves geométricas fáceis de corrigir:



update usahalinsk.d_geometry 
set cs1_geometry_polygone=st_collectionextract(st_makevalid(geoloc),3)
where key in(
1000010001988961,
1000010001989399,
1000010004293508);


Vou responder à pergunta que se coloca: "por que é impossível corrigir todas as geometrias errôneas da tabela, para não buscar seletivamente os motivos?" ...



O fato é que os dados espaciais chegam ao nosso sistema de várias fontes (inclusive do Rosreestr) e não podemos realizar a correção (via de regra, vem acompanhada de distorção) de todos os dados. Depois de receber as chaves necessárias, analisamos quais dados eles representam e se é possível corrigi-los.



A tarefa trivial de encontrar a causa do erro pode se transformar em uma investigação completa com um script de correção no final.



Uma versão mais complexa do problema: e se a interseção não for realizada com uma constante, mas com outra tabela? Alternativamente, cruze cada um dos objetos participantes na primeira tabela com todos os objetos na segunda. E pegue exceções.



Com que frequência você se depara com problemas de geometria e como garante a qualidade de seus dados espaciais?



All Articles