Indexación y consulta de datos de ubicación de GeoJSON en Azure Cosmos DB for NoSQL

SE APLICA A: NoSQL

Los datos geoespaciales de Azure Cosmos DB for NoSQL permiten almacenar información de la ubicación y realizar consultas comunes, entre las que se incluyen, entre otras:

  • Averiguar si una ubicación está dentro de un área definida
  • Medir la distancia entre dos ubicaciones
  • Determinar si una ruta de acceso se interseca con una ubicación o un área

En esta guía se explica el proceso de creación de datos geoespaciales, la indexación de los datos y la consulta de los datos en un contenedor.

Requisitos previos

Creación de una directiva de contenedores e indexación

Todos los contenedores incluyen una directiva de indexación predeterminada que indexará correctamente los datos geoespaciales. Para crear una directiva de indexación personalizada, cree una cuenta y especifique un archivo JSON con la configuración de la directiva. En esta sección, se usa un índice espacial personalizado para un contenedor recién creado.

  1. Abra un terminal.

  2. Cree una variable de shell para el nombre de la cuenta y el grupo de recursos de Azure Cosmos DB for NoSQL.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  3. Cree una base de datos denominada cosmicworks mediante az cosmosdb sql database create.

    az cosmosdb sql database create \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks" \
        --throughput 400
    
  4. Cree un nuevo archivo JSON denominado index-policy.json y agregue el siguiente objeto JSON al archivo.

    {
      "indexingMode": "consistent",
      "automatic": true,
      "includedPaths": [
        {
          "path": "/*"
        }
      ],
      "excludedPaths": [
        {
          "path": "/\"_etag\"/?"
        }
      ],
      "spatialIndexes": [
        {
          "path": "/location/*",
          "types": [
            "Point",
            "Polygon"
          ]
        }
      ]
    }
    
  5. Use az cosmosdb sql container create para crear un contenedor denominado locations con una ruta de acceso de clave de partición de /region.

    az cosmosdb sql container create \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --database-name "cosmicworks" \
        --name "locations" \
        --partition-key-path "/category" \
        --idx @index-policy.json
    
  6. Recupere la cadena de conexión principal de la cuenta mediante az cosmosdb keys list.

    az cosmosdb keys list \
        --resource-group $resourceGroupName \
        --name $accountName \
        --type "connection-strings" \
        --query "connectionStrings[?keyKind == \`Primary\`].connectionString" \
        --output tsv
    

    Sugerencia

    Para ver todas las cadenas de conexión posibles para una cuenta, use az cosmosdb keys list --resource-group $resourceGroupName --name $accountName --type "connection-strings".

  7. Registre la cadena de conexión. Esta credencial se usa más adelante en esta guía.

Creación del SDK de una aplicación de consola de .NET

El SDK de .NET para Azure Cosmos DB for NoSQL proporciona clases para objetos GeoJSON comunes. Use este SDK para simplificar el proceso de agregar objetos geográficos al contenedor.

  1. Abra un terminal en un directorio vacío.

  2. Cree una nueva aplicación .NET mediante el comando dotnet new con la plantilla console.

    dotnet new console
    
  3. Importe el paquete de NuGet Microsoft.Azure.Cosmos mediante el comando dotnet add package.

    dotnet add package Microsoft.Azure.Cosmos --version 3.*
    

    Advertencia

    Entity Framework no tiene actualmente datos espaciales en Azure Cosmos DB for NoSQL. Use uno de los SDK de Azure Cosmos DB for NoSQL para admitir GeoJSON fuertemente tipado.

  4. Compile el proyecto con el comando dotnet build.

    dotnet build
    
  5. Abra el entorno de desarrollador integrado (IDE) que prefiera en el mismo directorio que la aplicación de consola de .NET.

  6. Abra el archivo Program.cs recién creado y elimine cualquier código existente. Agregue mediante directivas para los espacios de nombres Microsoft.Azure.Cosmos, Microsoft.Azure.Cosmos.Linq y Microsoft.Azure.Cosmos.Spatial.

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    using Microsoft.Azure.Cosmos.Spatial;
    
  7. Agregue una variable de cadena denominada *connectionString con la cadena de conexión que registró anteriormente en esta guía.

    string connectionString = "<your-account-connection-string>"
    
  8. Cree una nueva instancia de la clase CosmosClient que pasa en connectionString y encapsúlela en una instrucción using.

    using CosmosClient client = new (connectionString);
    
  9. Recupere una referencia al contenedor creado anteriormente (cosmicworks/locations) en la cuenta de Azure Cosmos DB for NoSQL mediante CosmosClient.GetDatabase y, a continuación, Database.GetContainer. Almacene el resultado en una variable denominada container.

    var container = client.GetDatabase("cosmicworks").GetContainer("locations");
    
  10. Guarde el archivo Program.cs.

Agregar datos geoespaciales

El SDK de .NET incluye varios tipos en el espacio de nombres Microsoft.Azure.Cosmos.Spatial para representar objetos GeoJSON comunes. Estos tipos simplifican el proceso de agregar nueva información de ubicación a los elementos de un contenedor.

  1. Cree un nuevo archivo llamado Office.cs. En el archivo, agregue una directiva using a Microsoft.Azure.Cosmos.Spatial y, a continuación, cree un Officetipo de registro con estas propiedades:

    Tipo Descripción Valor predeterminado
    id string Identificador único
    name string Nombre de la oficina
    ubicación Point Punto geográfico GeoJSON
    category string Valor de la clave de partición business-office
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Office(
        string id,
        string name,
        Point location,
        string category = "business-office"
    );
    

    Nota

    Este registro incluye una propiedad Point que representa una posición específica en GeoJSON. Para obtener más información, consulte Punto GeoJSON.

  2. Cree otro archivo denominado Region.cs. Agregue otro tipo de registro denominado Region con estas propiedades:

    Tipo Descripción Valor predeterminado
    id string Identificador único
    name string Nombre de la oficina
    ubicación Polygon Forma geográfica de GeoJSON
    category string Valor de la clave de partición business-region
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Region(
        string id,
        string name,
        Polygon location,
        string category = "business-region"
    );
    

    Nota

    Este registro incluye una propiedad Polygon que representa una forma compuesta de líneas dibujadas entre varias ubicaciones en GeoJSON. Para obtener más información, consulte Polígono GeoJSON.

  3. Cree otro archivo denominado Result.cs. Agregue un tipo de registro denominado Result con estas dos propiedades:

    Tipo Descripción
    name string Nombre del resultado coincidente
    distanceKilometers decimal Distancia en kilómetros
    public record Result(
        string name,
        decimal distanceKilometers
    );
    
  4. Guarde los archivos Office.cs, Region.cs y Result.cs.

  5. Abra de nuevo el archivo Program.cs.

  6. Cree un nuevo Polygon en una variable denominada mainCampusPolygon.

    Polygon mainCampusPolygon = new (
        new []
        {
            new LinearRing(new [] {
                new Position(-122.13237, 47.64606),
                new Position(-122.13222, 47.63376),
                new Position(-122.11841, 47.64175),
                new Position(-122.12061, 47.64589),
                new Position(-122.13237, 47.64606),
            })
        }
    );
    
  7. Cree una variable Region denominada mainCampusRegion con el polígono, el identificador único 1000 y el nombre Main Campus.

    Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
    
  8. Use Container.UpsertItemAsync para agregar la región al contenedor. Escriba la información de la región en la consola.

    await container.UpsertItemAsync<Region>(mainCampusRegion);
    Console.WriteLine($"[UPSERT ITEM]\t{mainCampusRegion}");
    

    Sugerencia

    En esta guía se usa upsert en lugar de insertar para que pueda ejecutar el script varias veces sin causar un conflicto entre los identificadores únicos. Para obtener más información sobre las operaciones upsert, consulte creación de elementos.

  9. Crear una nueva variable Point llamada headquartersPoint. Use esa variable para crear una variable Office denominada headquartersOffice con el punto, el identificador único 0001 y el nombre Headquarters.

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. Cree otra variable Point denominada researchPoint. Use esa variable para crear otra variable Office denominada researchOffice con su correspondiente punto, el identificador único 0002 y el nombre Research and Development.

    Point researchPoint = new (-96.84369, 46.81298);
    Office researchOffice = new ("0002", "Research and Development", researchPoint);
    
  11. Cree un objeto TransactionalBatch para upsert ambas variables Office como una sola transacción. A continuación, escriba la información de ambas oficinas en la consola.

    TransactionalBatch officeBatch = container.CreateTransactionalBatch(new PartitionKey("business-office"));
    officeBatch.UpsertItem<Office>(headquartersOffice);
    officeBatch.UpsertItem<Office>(researchOffice);
    await officeBatch.ExecuteAsync();
    
    Console.WriteLine($"[UPSERT ITEM]\t{headquartersOffice}");
    Console.WriteLine($"[UPSERT ITEM]\t{researchOffice}");
    

    Nota

    Para obtener más información sobre las transacciones, consulte Operaciones por lotes transaccionales.

  12. Guarde el archivo Program.cs.

  13. Ejecute la aplicación en un terminal mediante dotnet run. Observe que la salida de la ejecución de la aplicación incluye información sobre los tres elementos recién creados.

    dotnet run
    
    [UPSERT ITEM]   Region { id = 1000, name = Main Campus, location = Microsoft.Azure.Cosmos.Spatial.Polygon, category = business-region }
    [UPSERT ITEM]   Office { id = 0001, name = Headquarters, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    [UPSERT ITEM]   Office { id = 0002, name = Research and Development, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    

Consulta de datos geoespaciales mediante una consulta NoSQL

Los tipos del espacio de nombres Microsoft.Azure.Cosmos.Spatial se pueden usar como entradas para que una consulta con parámetros NoSQL use funciones integradas como ST_DISTANCE.

  1. Abra el archivo Program.cs.

  2. Cree una variable string denominada nosql con la consulta para medir la distancia entre puntos.

    string nosqlString = @"
        SELECT
            o.name,
            NumberBin(distanceMeters / 1000, 0.01) AS distanceKilometers
        FROM
            offices o
        JOIN
            (SELECT VALUE ROUND(ST_DISTANCE(o.location, @compareLocation))) AS distanceMeters
        WHERE
            o.category = @partitionKey AND
            distanceMeters > @maxDistance
    ";
    

    Sugerencia

    Esta consulta coloca la función geoespacial dentro de una subconsulta para simplificar el proceso de reutilización del valor ya calculado varias veces en las cláusulas SELECT y WHERE.

  3. Cree una variable QueryDefinition denominada query con la variable nosqlString como parámetro. A continuación, use el método fluent QueryDefinition.WithParameter varias veces para agregar estos parámetros a la consulta:

    Value
    @maxDistance 2000
    @partitionKey "business-office"
    @compareLocation new Point(-122.11758, 47.66901)
    var query = new QueryDefinition(nosqlString)
        .WithParameter("@maxDistance", 2000)
        .WithParameter("@partitionKey", "business-office")
        .WithParameter("@compareLocation", new Point(-122.11758, 47.66901));
    
  4. Cree un nuevo iterador mediante Container.GetItemQueryIterator<>, el tipo genérico Result y la variable query. A continuación, use una combinación de un bucle while y foreach para recorrer en iteración todos los resultados en cada página de resultados. Genere cada resultado en la consola.

    var distanceIterator = container.GetItemQueryIterator<Result>(query);
    while (distanceIterator.HasMoreResults)
    {
        var response = await distanceIterator.ReadNextAsync();
        foreach (var result in response)
        {
            Console.WriteLine($"[DISTANCE KM]\t{result}");
        }
    }
    

    Nota

    Para obtener más información sobre cómo enumerar los resultados de la consulta, consulte elementos de consulta.

  5. Guarde el archivo Program.cs.

  6. Vuelva a ejecutar la aplicación en un terminal mediante dotnet run. Observe que la salida ahora incluye los resultados de la consulta.

    dotnet run
    
    [DISTANCE KM]   Result { name = Headquarters, distanceKilometers = 3.34 }
    [DISTANCE KM]   Result { name = Research and Development, distanceKilometers = 1907.43 }
    

Consulta de datos geoespaciales mediante LINQ

La funcionalidad LINQ a NoSQL del SDK de .NET admite la inclusión de tipos geoespaciales en las expresiones de consulta. Además, el SDK incluye métodos de extensión que se asignan a funciones integradas equivalentes:

Método de extensión Función integrada
Distance() ST_DISTANCE
Intersects() ST_INTERSECTS
IsValid() ST_ISVALID
IsValidDetailed() ST_ISVALIDDETAILED
Within() ST_WITHIN
  1. Abra el archivo Program.cs.

  2. Recupere el elemento Region del contenedor con un identificador único de 1000 y almacénelo en una variable denominada region.

    Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));
    
  3. Use el método Container.GetItemLinqQueryable<> para obtener una consulta LINQ y la compilación fluida de la consulta LINQ mediante la realización de estas tres acciones:

    1. Use el método de extensión Queryable.Where<> para filtrar solo los elementos con un category equivalente a "business-office".

    2. Use Queryable.Where<> de nuevo para filtrar solo las ubicaciones dentro de la propiedad region de la variable location mediante Geometry.Within().

    3. Traduzca la expresión LINQ a un iterador de fuente mediante CosmosLinqExtensions.ToFeedIterator<>.

    var regionIterator = container.GetItemLinqQueryable<Office>()
        .Where(o => o.category == "business-office")
        .Where(o => o.location.Within(region.location))
        .ToFeedIterator<Office>();
    

    Importante

    En este ejemplo, la propiedad de ubicación de la oficina tiene un punto y la propiedad de ubicación de la región tiene un polígono. ST_WITHIN determina si el punto de la oficina está dentro del polígono de la región.

  4. A continuación, use una combinación de un bucle while y foreach para recorrer en iteración todos los resultados en cada página de resultados. Genere cada resultado en la consola.

    while (regionIterator.HasMoreResults)
    {
        var response = await regionIterator.ReadNextAsync();
        foreach (var office in response)
        {
            Console.WriteLine($"[IN REGION]\t{office}");
        }
    }
    
  5. Guarde el archivo Program.cs.

  6. Ejecute la aplicación una última vez en un terminal mediante dotnet run. Observe que la salida ahora incluye los resultados de la segunda consulta basada en LINQ.

    dotnet run
    
    [IN REGION]     Office { id = 0001, name = Headquarters, location = Microsoft.Azure.Cosmos.Spatial.Point, category = business-office }
    

Limpieza de recursos

Quite la base de datos después de completar esta guía.

  1. Abra un terminal y cree una variable de shell para el nombre de la cuenta y el grupo de recursos.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  2. Use az cosmosdb sql database delete para eliminar la base de datos.

    az cosmosdb sql database delete \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks"
    

Pasos siguientes