Calcul de la « bouding box » appropriée pour l’indexation spatiale

SQL Server dispose d’index spatiaux qui nécessitent l’utilisation d’une « bounding box » pour le type geometry. Une « bounding box » est en fait un rectangle qui délimite la surface à indexer. Mais comment déterminer les limites de cette « boite » ? Cet article vous propose une procédure stockée capable de calculer les meilleurs paramètres de ces « bounding box » et vous donne le code de création des index spatiaux à ajouter à votre base.

Bounding box pour l'indexation géométrique de la france

Bounding box pour l’indexation géométrique de la france

Dans le principe, tous les points de toutes vos géométries dans une même colonne de table devraient être situés dans la bounding box de l’index pour que certaines recherches bénéficient de l’indexation. Pour ce faire il faut déterminer le point le plus bas, le plus haut (en y) le plus à gauche et le plus à droite (en x) de l’ensemble des points formant toutes vos géométries.
Pour effectuer ce calcul il existe une astuce qui consiste à calculer l’enveloppe d’une géométrie, comme le montre l’image suivante :

Calcul de l'enveloppe de Paris à l'aide de la fonction STEnvelope

Calcul de l’enveloppe de Paris à l’aide de la fonction STEnvelope


Notez que le calcul des limites de Paris a été effectué par concaténation des polygones des différentes arrondissements dans une premier temps (geometry::UnionAggregate(GEOM)).

À partir de là on peut obtenir les valeurs min et max de X et Y, car, comme le rectangle est formé de 4 points, il suffit de prendre les coordonnées de X et Y du point 1 et du point 3, les points étant spécifiées par chainage !

Enveloppe de Paris et ses points

Enveloppe de Paris et ses points

Dès lors la méthode pour obtenir les coordonnées de la bounding box est d’obtenir les valeurs de X et Y comme ceci :

  • MaGeometrie.STPointN(1).STX => Xmin
  • MaGeometrie.STPointN(1).STY => Ymin
  • MaGeometrie.STPointN(3).STX => Xmax
  • MaGeometrie.STPointN(3).STY => Ymax
  • Mais il faut concaténer toutes les entrées de toutes les géométries de la colonnes à l’aide de la fonction EnvelopeAggregate.

    la requête finale ressembles à celle-ci (calcul de la bounding box pour toutes les communes de la France métropolitaine) :

    WITH T AS
    (
    SELECT geometry::EnvelopeAggregate(GEOM) AS GEO
    FROM   [S_GEO].[COMMUNE]
    )
    SELECT geometry::EnvelopeAggregate(GEO).STPointN(1).STX AS MIN_X,
           geometry::EnvelopeAggregate(GEO).STPointN(3).STX AS MAX_X,
           geometry::EnvelopeAggregate(GEO).STPointN(1).STY AS MIN_Y,
           geometry::EnvelopeAggregate(GEO).STPointN(3).STY AS MAX_Y
    FROM   T

    Au final nous pouvons automatiser ce calcul pour toutes les colonnes geometry de toute la base. Ceci conduit à la procédures suivantes :

    CREATE PROCEDURE P_AUTOBOUND_SPATIAL_INDEX @DELTA FLOAT
    AS

    DECLARE @T TABLE (SCH sysname, TBL sysname, COL sysname, XMIN FLOAT, XMAX FLOAT, YMIN FLOAT, YMAX FLOAT);
    -- le delate doit être 1 (bornes strictes) ou supérieur à 1
    IF @DELTA < 1 OR @DELTA IS NULL
       SET @DELTA = 1;

    -- recherches des colonnes spatiales non indexées de type GEOMETRY
    INSERT INTO @T (SCH, TBL, COL)
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
    FROM   INFORMATION_SCHEMA.COLUMNS
    WHERE  DATA_TYPE = 'geometry'
    EXCEPT
    SELECT s.name, o.name, c.name
    FROM   sys.objects AS o
           JOIN sys.indexes AS i
                ON o.object_id = i.object_id
           JOIN sys.schemas AS s
                ON o.schema_id = s.schema_id
           JOIN sys.spatial_indexes AS si
                ON i.object_id = si.object_id
                AND i.index_id = si.index_id
           JOIN sys.index_columns AS ic
                ON i.object_id = ic.object_id
                AND i.index_id = ic.index_id
           JOIN sys.COLUMNS AS c
                ON ic.object_id = c.object_id
                AND ic.column_id = c.column_id
           JOIN sys.systypes AS t
                ON c.system_type_id = t.TYPE
    WHERE  t.name = 'geometry';

    DECLARE @TXY TABLE (XMIN FLOAT, XMAX FLOAT, YMIN FLOAT, YMAX FLOAT);
    DECLARE @TBL sysname, @SCH sysname, @COL sysname,
            @XMIN FLOAT, @XMAX FLOAT, @YMIN FLOAT, @YMAX FLOAT,
            @SQL NVARCHAR(MAX);

    DECLARE C CURSOR
       LOCAL
       FORWARD_ONLY
       STATIC
       READ_ONLY
    FOR
       SELECT SCH, TBL, COL
       FROM   @T;

    OPEN C;

    FETCH C INTO @SCH, @TBL, @COL;

    WHILE @@FETCH_STATUS = 0
    BEGIN
       DELETE FROM @TXY;
       SET @SQL = N'WITH T AS (SELECT geometry::EnvelopeAggregate([' + @COL + N']) AS GEO FROM [' + @SCH  + '].[' + @TBL + ']) '
                + N'SELECT geometry::EnvelopeAggregate(GEO).STPointN(1).STX / ' + CAST(@DELTA AS VARCHAR(16)) + N' AS MIN_X, '
                + N'geometry::EnvelopeAggregate(GEO).STPointN(3).STX * ' + CAST(@DELTA AS VARCHAR(16)) + N' AS MAX_X, '
                + N'geometry::EnvelopeAggregate(GEO).STPointN(1).STY / ' + CAST(@DELTA AS VARCHAR(16)) + N' AS MIN_Y, '
                + N'geometry::EnvelopeAggregate(GEO).STPointN(3).STY * ' + CAST(@DELTA AS VARCHAR(16)) + N' AS MAX_Y '
                + N'FROM T;'
       INSERT INTO @TXY
       EXEC (@SQL);
       UPDATE T
       SET    XMIN = TT.XMIN, XMAX = TT.XMAX, YMIN = TT.YMIN, YMAX = TT.YMAX
       FROM   @T AS T
              CROSS JOIN @TXY AS TT
       WHERE  SCH = @SCH
         AND  TBL = @TBL
         AND  COL = @COL;
       FETCH C INTO @SCH, @TBL, @COL;
    END

    CLOSE C;

    DEALLOCATE C;

    SELECT N'CREATE SPATIAL INDEX XS_' + LEFT(SCH, 16) + N'_' + LEFT(TBL, 30) + N'_' + LEFT(COL, 30)  + N'_'
           + REPLACE(CAST(NEWID() AS NVARCHAR(38)), N'-', N'_') + '_' + CONVERT(CHAR(8), GETDATE(), 112)
           + NCHAR(13) + NCHAR(10)
           + N'   ON [' + SCH + N'].[' + TBL + '] ([' + COL + N']) '
           + NCHAR(13) + NCHAR(10)
           + N'   WITH ( BOUNDING_BOX = ('
           + CAST( XMIN AS NVARCHAR(32)) + N', '
           + CAST( YMIN AS NVARCHAR(32)) + N', '
           + CAST( XMAX AS NVARCHAR(32)) + N', '
           + CAST( YMAX AS NVARCHAR(32)) + N') );'
    FROM   @T;  

    GO

    Le paramètre @DELTA sert à rajouter un peu de "mou" sur la périphérie de la bounding box et doit être égale à 1 (pas de "mou") ou supérieur.
    par exemple avec 1.1, on ajoute 10% de distance en périphérie de la bounding box.

    LE CODE * LE CODE * LE CODE * LE CODE * LE CODE * LE CODE * LE CODE * LE CODE * LE CODE
    Le code !

    Frédéric Brouard, alias SQLpro, ARCHITECTE DE DONNÉES
    Expert  S.G.B.D  relationnelles   et   langage  S.Q.L
    Moste  Valuable  Professionnal  Microsoft  SQL Server
    Société SQLspot  :  modélisation, conseil, formation,
    optimisation,  audit,  tuning,  administration  SGBDR
    Enseignant: CNAM PACA, ISEN Toulon, CESI Aix en Prov.

    L’entreprise SQL Spot
    Le site web sur le SQL et les SGBDR

    MVP Microsoft SQL
Server

    Développez et administrez pour la performance avec SQL Server 2014

    Développez et administrez pour la performance avec SQL Server 2014

    Laisser un commentaire