Сохранить начальные точки в UnionAggregate - PullRequest
0 голосов
/ 18 февраля 2019

Вариант использования 1:

DECLARE @Geom TABLE 
( 
   shape geometry, 
   shapeType nvarchar(50) 
); 

INSERT INTO @Geom(shape,shapeType) 
VALUES('LINESTRING(1 2, 3 4)', 'A'), 
('LINESTRING(3.2 4, 7 8)', 'B'); 

SELECT *
FROM @Geom

SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;

WKT для вывода:

MULTILINESTRING ((7 8, 3.2 4), (3 4, 1 2))

, когда я хочу

MULTILINESTRING ((1 2, 3 4), (3.2 4, 7 8))

Где начало строк "A" и "B" должно быть (1 2) и (3.2 4) с уважением.

Такое поведение UnionAggregate, похоже, не имеет значения "Направление "геометрии, чтобы поддерживать, что объединение B и объединение B является одним и тем же результатом.Тем не менее, я хочу сохранить начальные / конечные точки, так как объединяю геометрию улиц и хочу, чтобы все ЛИСТРИНГИ шли в своем первоначальном направлении.

Эта проблема обсуждается здесь: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/89e95366-3649-4294-a0bc-f3921598157f/union-of-linestrings-and-reversing-direction?forum=sqlspatial

Онипохоже, предлагают возможное решение о проверке конечного результата, но мне не ясно, как это сделать.В связанном потоке намекают, что

MultiLineString всегда представляет график из точки, наиболее удаленной от исходной точки.

Мне не совсем понятно, что это значит, но я не думаю, что могу просто предположить, что результат UnionAggregate всегда противоположен тому, что я хочу

Еслитрудно определить направленное намерение, тогда я могу добавить M мер, где направление должно следовать за увеличивающимися значениями M.

Предполагая, что у меня есть метод для изменения точек на линии, как бы я решил для этого решение?

Я обнаружил функцию, которая имитирует STUnion для дополнительной поддержки мер Z и M: http://www.spatialdbadvisor.com/files/SQLServer.html#robo48, однако отмечается, что «их направление может измениться (например, отношение Start / Start Point).", это то, чего я хочу избежать.

Редактировать:

Функциональность, которая мне также нужна, заключается в том, что, когда к LINESTRING используется общая конечная точка, результатом является соединение LINESTRING

Вариант использования 2:

DECLARE @Geom TABLE 
( 
   shape geometry, 
   shapeType nvarchar(50) 
); 

INSERT INTO @Geom(shape,shapeType) 
VALUES('LINESTRING(1 2, 3 4)', 'A'), 
('LINESTRING(3 4, 7 8)', 'B'); 

SELECT *
FROM @Geom

SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;

Это приводит к WKT LINESTRING (7 8, 3 4, 1 2)

Когда я бы хотел

LINESTRING (1 2, 3 4, 7 8)


Попытка решения

geometry::CollectionAggregate(shape).Reduce(0), как предложено Клэем, решает вариант использования 1. Я попытался использовать STUnion для результата с пустой строкой, и пока он работает, он возвращается к неправильному порядку.

Я подозреваю, что решением будет функция масштабирования, подобная ST_LineMerge , которая берет результат CollectionAggregate (MULTILINESTRING) и затем объединяет точки, когда это возможно, в одну LINESTRING, и когдане может вернуть тГеометрия назад неизменна

Ответы [ 3 ]

0 голосов
/ 17 марта 2019

Первоначально я предложил ...

DECLARE @Geom TABLE 
( 
   shape geometry, 
   shapeType nvarchar(50) 
); 

INSERT @Geom(shape,shapeType) VALUES
  ('LINESTRING(1 2, 3 4)', 'A'), 
  ('LINESTRING(3.2 4, 7 8)', 'B'); 

SELECT * FROM @Geom

SELECT 
  geometry::CollectionAggregate(shape).Reduce(0).ToString(), 
  geometry::CollectionAggregate(shape).Reduce(0)
FROM @Geom

Вы получаете:

enter image description here

... однако, это сталоМне ясно, что ответ, который я дал, не достаточно хорош.Например, довольно сложно удержать Reduce() от упрощения части ваших строк,

Мне все еще нравится CollectionAggregate для получения вашего исходного массива строк в одну вещь, но потом я решил, что просто нужнобыть способом построения требуемой геометрической структуры.

Я играл с этим несколько раз, и эта итерация превратится в LineString или MultiLineString в зависимости от того, есть ли непересекающиеся LineString элементы ввходные данные:

create function dbo.SimplifyToLine( @geo geometry ) returns geometry as
begin
  declare 
    @numSubGeos int = @geo.STNumGeometries(),
    @subGeoIdx int = 1,
    @sql nvarchar( max ) = N'',
    @subGeo geometry,
    @oldEndX float = -1.0e26,
    @oldEndY float = -1.0e26,
    @startX float,
    @startY float,
    @endX float,
    @endY float,
    @idx int,
    @numPoints int,
    @point geometry,
    @segment int = 1,
    @continue bit,
    @result geometry,
    @started bit = 0

  declare
    @geos table
    ( 
      Idx int primary key, 
      SubGeo geometry, 
      StartX decimal, 
      EndX decimal, 
      StartY decimal, 
      EndY decimal, 
      NumPoints int, 
      ContinueFromPrevious bit 
    ) 

  declare
    @multiLines table
    (
      Idx int primary key,
      Segment nvarchar(max)
    )

  --> collect geometries and extents...
  while ( @subGeoIdx <= @numSubGeos )  
  begin

    select @subGeo = @geo.STGeometryN( @subGeoIdx )

    select 
      @startX = @subGeo.STPointN( 1 ).STX,
      @startY = @subGeo.STPointN( 1 ).STY,
      @endX = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STX,
      @endY = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STY

    insert @geos values
    ( 
      @subGeoIdx,
      @subGeo, 
      @startX, 
      @endX, 
      @startY, 
      @endY, 
      @subGeo.STNumPoints() ,
      case when @subGeoIdx = 1 then 1 when @oldEndX = @startX and @oldEndY = @startY then 1 else 0 end
    )   

    select 
      @oldEndX = @endX, 
      @oldEndY = @endY, 
      @subGeoIdx = @subGeoIdx + 1
  end


  if not exists ( select * from @geos where ContinueFromPrevious = 0 ) --> then all LineStrings are connected 
  begin
    --> build a single LINESTRING( )...
    select @sql = ''
    declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos order by Idx  
    open c
    while ( 1 = 1 )
    begin
      fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
      if @@fetch_status != 0 break;
      select @idx = case when @started = 0 then 1 else 2 end, @started = 1  --> accrue all points, de-duplicating line ends...
      while ( @idx <= @numPoints )
      begin
        select @point = @subGeo.STPointN( @idx )
        select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
        select @idx = @idx + 1
      end
    end
    close c
    deallocate c
    select @sql = substring( @sql, 1, len( @sql ) -1 )
    select @result =  geometry::STGeomFromText(N'LINESTRING(' + @sql + N')', 0 ) 
  end
  else  --> we have disjoint lines in the inputs...
  begin
    select @sql = N'', @started = 0
    --> build a MULTILINESTRING((),()...) with line segements terminated at disjoint points..
    declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos  order by Idx
    open c
      while ( 1=1 )
      begin
        fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
        if @@fetch_status != 0 break;
        if @continue = 1
        begin
          select @idx = case when @started = 0 then 1 else 2 end, @started = 1
          while ( @idx <= @numPoints )
          begin
            select @point = @subGeo.STPointN( @idx )
            select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
            select @idx = @idx + 1
          end
        end
        else
        begin
          insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) ) --> collect the segment
          select @idx = 1, @sql = N'', @segment = @segment + 1
          while ( @idx <= @numPoints )
          begin
            select @point = @subGeo.STPointN( @idx )
            select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
            select @idx = @idx + 1
          end
        end
      end
    close c
    deallocate c
    insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) )
    select @sql = N''
    select @sql += N'(' + Segment + N'),' from @multiLines order by Idx --> appends all segments
    select @sql = substring( @sql, 1, len( @sql ) -1 )
    select @result = geometry::STGeomFromText( 'MULTILINESTRING('+ @sql + N')', 1 )
  end

... и, наконец, дано:

DECLARE @Geom TABLE 
( 
   shape geometry, 
   shapeType nvarchar(50) 
); 

INSERT @Geom(shape,shapeType) VALUES
  ('LINESTRING(1 2, 3 4)', 'A'), 
  ('LINESTRING(3 4, 9 9)', 'B'),  --> disjoint from here to the next LINESTRING
  ('LINESTRING(9 8, 3 4)', 'C'),
  ('LINESTRING(3 4, 1 2)', 'D'); 

select 
  dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
  dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from 
  @Geom

delete @Geom

INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'), 
('LINESTRING(3 4, 9 8)', 'B'),
('LINESTRING(9 8, 3 4)', 'C'),
('LINESTRING(3 4, 1 2)', 'D'); 

select 
  dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
  dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from
  @Geom

... вы получаете:

this

0 голосов
/ 21 марта 2019

Исходя из идеи Клея передать GeometryCollection, я реализовал надежную версию, которая будет принимать любую комбинацию POINT, MULTIPOINT, LINESTRING, MULTILINESTRING и удалять любые касающиеся конечные точки внутри @Tolerance и создавать POINT, LINESTRING, MULTILINESTRING

Вот пример того, как он работает (обратите внимание, как допуск 0 и 0,1 имеет значение для 2-го и 3-го выхода):

DECLARE @GeometryCollection GEOMETRY = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), LINESTRING (3 4, 100 100), LINESTRING (9 8, 3 4), LINESTRING (3 4, 1 2), POINT(1 2), POINT(1 2), POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString();
--Output: MULTILINESTRING ((1 2, 3 4, 100 100), (9 8, 3 4, 1 2))

SET @GeometryCollection  = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: MULTILINESTRING ((1 2, 3 4.1), (3 4, 9 9, 6 1))

SET @GeometryCollection  = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0.1).ToString()
--Output: LINESTRING (1 2, 3 4.1, 9 9, 6 1)

SET @GeometryCollection  = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: POINT (1 2)

SET @GeometryCollection  = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (MULTIPOINT((1 2), (2 3)))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output:  (1 2, 2 3)

Сначала я должен был создать рекурсивную функцию CTE, котораяберет геометрию и извлекает все точки.

CREATE FUNCTION [dbo].[fnGetPoints]
(   
    @Geometry GEOMETRY
)
RETURNS TABLE 
AS
RETURN 
(

    WITH GeometryPoints(N, Point) AS ( 
        SELECT 
            CAST(1 AS DECIMAL(9,2)) as N
            ,@Geometry.STPointN(1) as Point
        UNION ALL
        SELECT 
            CAST(N + 1.0 AS DECIMAL(9,2)) as N
            ,@Geometry.STPointN(N + 1) as Point
        FROM GeometryPoints GP
        WHERE N < @Geometry.STNumPoints()  
    )

    SELECT *
    FROM GeometryPoints
)

Затем я создал функцию CROSS APPLY fnGetPoints для каждой геометрии в @GeometryCollection, чтобы получить матрицу точек.Использование оконной функции (LAG) для поиска мест, где конечные точки находятся в пределах @Tolerance, и удаления этих точек.Затем я сделал мазок данных , чтобы объединить геометрии, в которых они разделяли конечные точки.

CREATE FUNCTION [dbo].[fnSimplifyToLine] (@GeometryCollection GEOMETRY, @Tolerance DECIMAL(19,10))
RETURNS GEOMETRY
AS
BEGIN
    DECLARE @PointMatrix TABLE (
        PointId INT,
        LinestringId INT,
        GeometryIndex INT,
        GeometryType varchar(100),
        PointIndex INT,
        Point GEOMETRY,
        Duplicate BIT
    );

    DECLARE @Linestrings TABLE (
        LinestringId INT,
        PointArrayStr varchar(max)
    );

    WITH CollectionGeometries(N, Geom) AS ( 
        SELECT 
            CAST(1 AS DECIMAL(9,2)) as N
            ,@GeometryCollection.STGeometryN(1) as Geom
        UNION ALL
        SELECT 
            CAST(N + 1.0 AS DECIMAL(9,2)) as N
            , @GeometryCollection.STGeometryN(N + 1) as Geom
        FROM CollectionGeometries CG
        WHERE N < @GeometryCollection.STNumGeometries()
    ), PointMatrix AS (
        SELECT 
            ROW_NUMBER() OVER(ORDER BY G.N, P.N) as PointId
            ,G.N as GeometryIndex
            ,G.Geom.STGeometryType() as GeometryType
            ,P.N as PointIndex
            ,P.Point
        FROM CollectionGeometries G
        CROSS APPLY dbo.fnGetPoints(Geom) P
    )

    INSERT INTO @PointMatrix
    SELECT 
        PointId
        ,GeometryIndex as LinestringId
        ,GeometryIndex
        ,GeometryType
        ,PointIndex
        ,Point
        ,CASE 
            WHEN 
                GeometryIndex != LAG(GeometryIndex) OVER(ORDER BY PointId)
                AND ABS(Point.STX - LAG(Point.STX) OVER(ORDER BY PointId)) <= @Tolerance
                AND ABS(Point.STY - LAG(Point.STY) OVER(ORDER BY PointId)) <= @Tolerance
            THEN 1
            ELSE 0
        END as Duplicate
    FROM PointMatrix
    OPTION (MAXRECURSION 10000)



    -- POLYGON, MULTIPOLYGON, GEOMETRYCOLLECTION, CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON not supported
    IF EXISTS ( SELECT * FROM @PointMatrix WHERE GeometryType NOT IN ('POINT', 'MULTIPOINT', 'LINESTRING', 'MULTILINESTRING'))
        RETURN CAST('Geometries in @GeometryCollection must all be IN (''POINT'',''MULTIPOINT'', ''LINESTRING'', ''MULTILINESTRING'')' as GEOMETRY);

    DECLARE @SRID INT = (SELECT DISTINCT Point.STSrid FROM @PointMatrix)

    UPDATE @PointMatrix
    SET LinestringId = NULL
    WHERE GeometryIndex IN (
        SELECT GeometryIndex FROM @PointMatrix WHERE Duplicate = 1
    )

    DELETE @PointMatrix
    WHERE Duplicate = 1;

    -- Data smear
    WITH Cnt AS (
        SELECT PointId, Point, LinestringId,c=COUNT(LinestringId) OVER (ORDER BY PointId)
        FROM @PointMatrix
    ), SmearedLineStringId AS (
        SELECT PointId, Point, LinestringId=MAX(LinestringId) OVER (PARTITION BY c)
        FROM Cnt
    )

    INSERT @Linestrings
    SELECT 
        LinestringId
        ,'(' + 
                STUFF((
                    SELECT ',' + CAST(Point.STX as varchar(100)) + ' ' + CAST(Point.STY as varchar(100))
                    FROM SmearedLineStringId t2
                    WHERE t1.LinestringId = t2.LinestringId 
                    ORDER BY PointId
                    FOR XML PATH ('')
                ), 1, 1, '')
        + ')' as PointArray
    FROM SmearedLineStringId t1
    GROUP BY LinestringId

    DECLARE @Type varchar(100) = CASE 
        WHEN 1 =(SELECT COUNT(*) FROM @PointMatrix) THEN
            'POINT'
        WHEN 1 =(SELECT COUNT(*) FROM @Linestrings) THEN
            'LINESTRING'
        ELSE
            'MULTILINESTRING'
    END

    DECLARE @BeginParens char(1) = '(';
    DECLARE @EndParens char(1) = ')'

    IF @Type != 'MULTILINESTRING'
    BEGIN
        SET @BeginParens = '';
        SET @EndParens = '';
    END

    DECLARE @Wkt varchar(max) = @Type + @BeginParens + 
                STUFF((
                    SELECT ',' + PointArrayStr
                    FROM @Linestrings t2
                    ORDER BY LinestringId
                    FOR XML PATH ('')
                ), 1, 1, '')
            + @EndParens

    RETURN Geometry::STGeomFromText(@Wkt, @SRID)

END
GO
0 голосов
/ 21 февраля 2019

Типы геометрии не записывают / не кодируют направленность.Линии, которые вы задаете, могут рассматриваться как «ненаправленные» или «двунаправленные».Это возвращает 1:

select geometry::STGeomFromText('LINESTRING(1 2, 3 4)',0).STEquals(
       geometry::STGeomFromText('LINESTRING(3 4, 1 2)',0))

Так что то, что вы ищете, не доступно, используя эти типы.Вы считаете «начальные точки» особенными.Я предлагаю вам отдельно записать их как отдельные POINT s.

Хотя теперь это делает весь полученный код более уродливым - вы должны сохранять эти пары данных обработанными вместе:

DECLARE @Geom TABLE 
(
   start geometry, 
   shape geometry, 
   shapeType nvarchar(50) 
); 

INSERT INTO @Geom(start,shape,shapeType) 
VALUES('POINT(1 2)','LINESTRING(1 2, 3 4)', 'A'), 
('POINT(3.2 4)','LINESTRING(3.2 4, 7 8)', 'B'); 

SELECT *
FROM @Geom

SELECT
    geometry::UnionAggregate(start).ToString(), geometry::UnionAggregate(shape).ToString(),
    geometry::UnionAggregate(start), geometry::UnionAggregate(shape)
FROM @Geom;

На этом этапе вы можете принять решение прекратить использование типа географии напрямую - вы можете создать UDT CLR, который ссылается на SqlGeography (интерфейс CLR того же типа) и использует его внутренне, но также отслеживает его "направленность""также, все обернуто вместе, и начните использовать это вместо этого.

Вы вряд ли захотите вспомнить все geography методы в этой оболочке - вам придется выбирать и выбирать сражения,И, конечно, поскольку на самом деле это не SQL Server geography, который появляется в ваших результатах, вы не сможете воспользоваться вкладкой «Пространственные результаты» в Management Studio.


Единственноеместо, где я могу думать о том, где существует некоторая «направленность» в этих типах, является правилом левой руки для устранения неоднозначности geography форм.

...