在UnionAggregate中保留起点

时间:2019-02-18 04:38:53

标签: sql-server tsql geometry spatial sql-server-2017

用例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的这种行为似乎并不关心几何的“方向”,以便维持A联合B和B联合A是相同的结果。但是,我在合并街道几何图形时希望保留起点/终点,并且希望所有LINESTRING沿其原始方向移动。

这里讨论了这个问题: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,但是请注意,“它们的方向可能会改变(例如,起点/起点关系)”。这是我要避免的事情。

编辑:

我还需要的功能是,当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)


尝试解决方案

Clay建议的geometry::CollectionAggregate(shape).Reduce(0)解决了用例1。我尝试对结果使用STUnion并带有空的线串,并且在工作时它会退回到不正确的顺序。

我怀疑解决方案将是类似于ST_LineMerge的缩放器函数,该函数使用CollectionAggregate(MULTILINESTRING)的结果,然后在可以合并为一个LINESTRING且无法返回时返回这些点。几何形状保持不变

3 个答案:

答案 0 :(得分:7)

The geometry types don't record/encode directionality. The lines that you give it may be considered "undirected" or "bi-directional". This returns 1:

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

So what you're looking for isn't available using these types. You consider the "start points" to be special. I suggest you separately record those as individual POINTs.

This does make all of the resulting code uglier now though - you have to keep these data pairs processed together:

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;

At this point you may decide to stop using the geography type directly - you can create a CLR UDT that references SqlGeography (a CLR surfacing of the same type) and uses that internally but also tracks it's "directionality" too, all wrapped up together, and start using that instead.

You're unlikely to want to surface all of the geography methods in that wrapper though - you'll have to pick and choose your battles. And, of course, since it's not really the SQL Server geography turning up in your results, you won't get the benefit of the "Spatial Results" tab in Management Studio.


The only place I can think of where some "directionality" does exist in these types is the left-hand rule for disambiguating geography shapes.

答案 1 :(得分:5)

最初,我建议...

DatabaseReference mDatabaseReference = FirebaseDatabase.getInstance().getReference().child("Users").child("uid").child("username");
    mDatabaseReference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            if (dataSnapshot.exists()) {
                String username = Objects.requireNonNull(dataSnapshot.getValue()).toString();
                mName.setText(("username" + username ));
            } else {
                mName.setText(null);
            }
        }
        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

您得到:

enter image description here

...但是,对我来说,很明显我给出的答案还不够好。例如,很难阻止dicc['first'] = l1简化行的一部分,

我仍然喜欢CollectionAggregate,它可以将原始的线阵列整合为一体,但是后来我发现,必须有一种构建必要的几何结构的方法。

我玩过几次,根据输入中是否存在不相交的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 元素,该迭代将评估为Reduce()LineString

MultiLineString

...最后,给出:

LineString

...您得到:

this

答案 2 :(得分:1)

摆脱Clay传入GeometryCollection的想法,我实现了一个健壮的版本,该版本将采用POINT,MULTIPOINT,LINESTRING,MULTILINESTRING的任意组合,并删除@Tolerance中的任何触摸端点,并创建POINT,LINESTRING,MULTILINESTRING

以下是其工作原理的说明(请注意,0和0.1的公差如何影响第二和第三输出):

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
)

然后,我创建了一个函数,将fnGetPoints交叉应用于@GeometryCollection中的每个几何图形以获得点矩阵。使用窗口函数(LAG)查找端点在@Tolerance内的位置,然后删除这些点。然后,我做了data smear来合并它们共享端点的几何。

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