Indizieren und Abfragen von GeoJSON-Standortdaten in Azure Cosmos DB for NoSQL

GILT FÜR: NoSQL

Geodaten in Azure Cosmos DB for NoSQL ermöglichen es Ihnen, Standortinformationen zu speichern und allgemeine Abfragen durchzuführen, einschließlich, aber nicht beschränkt auf:

  • Ermitteln, ob sich ein Standort innerhalb eines definierten Bereichs befindet
  • Messen des Abstands zwischen zwei Standorten
  • Bestimmen, ob ein Pfad eine Position oder einen Bereich schneidet

Dieser Leitfaden führt Sie durch den Prozess zum Erstellen und Indizieren von Geodaten sowie zum anschließenden Abfragen dieser Daten in einem Container.

Voraussetzungen

Erstellen eines Containers und einer Indizierungsrichtlinie

Alle Container enthalten eine standardmäßige Indizierungsrichtlinie, die Geodaten erfolgreich indiziert. Um eine benutzerdefinierte Indizierungsrichtlinie zu erstellen, erstellen Sie ein Konto, und geben Sie dann eine JSON-Datei mit der Konfiguration der Richtlinie an. In diesem Abschnitt wird ein benutzerdefinierter räumlicher Index für einen neu erstellten Container verwendet.

  1. Öffnen Sie ein Terminal.

  2. Erstellen Sie eine Shellvariable für den Namen Ihres Azure Cosmos DB for NoSQL-Kontos und ihre Ressourcengruppe.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  3. Erstellen Sie mithilfe von „az cosmosdb sql database create“ eine neue Datenbank namens „cosmicworks“.

    az cosmosdb sql database create \
        --resource-group $resourceGroupName \
        --account-name $accountName \
        --name "cosmicworks" \
        --throughput 400
    
  4. Erstellen Sie eine neue JSON-Datei namens index-policy.json, und fügen Sie der Datei das folgende JSON-Objekt hinzu.

    {
      "indexingMode": "consistent",
      "automatic": true,
      "includedPaths": [
        {
          "path": "/*"
        }
      ],
      "excludedPaths": [
        {
          "path": "/\"_etag\"/?"
        }
      ],
      "spatialIndexes": [
        {
          "path": "/location/*",
          "types": [
            "Point",
            "Polygon"
          ]
        }
      ]
    }
    
  5. Verwenden Sie „az cosmosdb sql container create“, um einen neuen Container mit dem Namen „locations“ und dem Partitionsschlüsselpfad „/region“ zu erstellen.

    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. Rufen Sie mithilfe von „az cosmosdb keys list“ die primäre Verbindungszeichenfolge für das Konto ab.

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

    Tipp

    Verwenden Sie „az cosmosdb keys list --resource-group $resourceGroupName --name $accountName --type "connection-strings"“, um alle möglichen Verbindungszeichenfolgen für ein Konto anzuzeigen.

  7. Zeichnen Sie die Verbindungszeichenfolge auf. Sie werden diese Anmeldeinformationen weiter unten in diesem Leitfaden verwenden.

Erstellen einer .NET-SDK-Konsolenanwendung

Das .NET-SDK für Azure Cosmos DB for NoSQL stellt Klassen für gängige GeoJSON-Objekte bereit. Verwenden Sie dieses SDK, um den Prozess des Hinzufügens geografischer Objekte zu Ihrem Container zu optimieren.

  1. Öffnen Sie ein Terminal in einem leeren Verzeichnis.

  2. Erstellen Sie eine neue .NET-Anwendung mithilfe des Befehls mit der dotnet new Vorlage Konsole.

    dotnet new console
    
  3. Importieren Sie das Microsoft.Azure.Cosmos NuGet Paket mithilfe des dotnet add package-Befehls.

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

    Warnung

    Entity Framework unterstützt derzeit keine räumlichen Daten in Azure Cosmos DB for NoSQL. Verwenden Sie eines der Azure Cosmos DB for NoSQL-SDKs für die Unterstützung von stark typisiertem GeoJSON.

  4. Erstellen Sie das Projekt mit dem dotnet build-Befehl.

    dotnet build
    
  5. Öffnen Sie die integrierte Entwicklerumgebung (Integrated Developer Environment, IDE) Ihrer Wahl im selben Verzeichnis wie Ihre .NET-Konsolenanwendung.

  6. Öffnen Sie die neu erstellte Datei Program.cs, und löschen Sie den gesamten vorhandenen Code. Fügen Sie in den Namespaces „“Microsoft.Azure.Cosmos, „Microsoft.Azure.Cosmos.Linq“ und „Microsoft.Azure.Cosmos.SpatialUsing-Direktiven hinzu.

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    using Microsoft.Azure.Cosmos.Spatial;
    
  7. Fügen Sie eine Zeichenfolgenvariable namens *connectionString mit der Verbindungszeichenfolge hinzu, die Sie weiter oben in dieser Anleitung aufgezeichnet haben.

    string connectionString = "<your-account-connection-string>"
    
  8. Erstellen Sie eine neue Instanz der Klasse „CosmosClient“, die an „connectionString“ übergeben und von einer Using-Anweisung umschlossen wird.

    using CosmosClient client = new (connectionString);
    
  9. Rufen Sie einen Verweis auf den zuvor erstellten Container (cosmicworks/locations) im Azure Cosmos DB for NoSQL-Konto ab, indem Sie erst „CosmosClient.GetDatabase“ und dann „Database.GetContainer“ verwenden. Speichern Sie das Ergebnis in einer Variable namens container.

    var container = client.GetDatabase("cosmicworks").GetContainer("locations");
    
  10. Speichern Sie die Datei Program.cs.

Hinzufügen von Geodaten

Das .NET-SDK enthält mehrere Typen im Namespace „Microsoft.Azure.Cosmos.Spatial“, um gängige GeoJSON-Objekte darzustellen. Diese Typen optimieren den Prozess zum Hinzufügen neuer Standortinformationen zu Elementen in einem Container.

  1. Erstellen Sie eine neue Datei mit dem Namen Office.cs. Fügen Sie in der Datei eine Using-Direktive zu „Microsoft.Azure.Cosmos.Spatial“ hinzu, und erstellen Sie dann einen DatensatztypOffice“ mit den folgenden Eigenschaften:

    type BESCHREIBUNG Standardwert
    id string Eindeutiger Bezeichner
    name string Name des Büros
    location Point Geografischer GeoJSON-Punkt
    category string Partitionsschlüsselwert business-office
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Office(
        string id,
        string name,
        Point location,
        string category = "business-office"
    );
    

    Hinweis

    Dieser Datensatz enthält eine „Point“-Eigenschaft, die eine bestimmte Position in GeoJSON darstellt. Weitere Informationen finden Sie unter GeoJSON-Punkt.

  2. Erstellen Sie eine weitere neue Datei namens Region.cs. Fügen Sie einen weiteren Datensatztyp namens „Region“ mit den folgenden Eigenschaften hinzu:

    type BESCHREIBUNG Standardwert
    id string Eindeutiger Bezeichner
    name string Name des Büros
    location Polygon Geografische GeoJSON-Form
    category string Partitionsschlüsselwert business-region
    using Microsoft.Azure.Cosmos.Spatial;
    
    public record Region(
        string id,
        string name,
        Polygon location,
        string category = "business-region"
    );
    

    Hinweis

    Dieser Datensatz enthält eine „Polygon“-Eigenschaft, die eine aus Linien bestehende Form darstellt, die zwischen mehreren Positionen in GeoJSON gezeichnet wird. Weitere Informationen finden Sie unter GeoJSON-Polygon.

  3. Erstellen Sie eine weitere neue Datei namens Result.cs. Fügen Sie einen Datensatztyp namens „Result“ mit den folgenden beiden Eigenschaften hinzu:

    type BESCHREIBUNG
    name string Name des übereinstimmenden Ergebnisses
    distanceKilometers decimal Entfernung in Kilometern
    public record Result(
        string name,
        decimal distanceKilometers
    );
    
  4. Speichern Sie die Dateien Office.cs, Region.cs und Result.cs ab.

  5. Öffnen Sie die Datei Program.cs erneut.

  6. Erstellen Sie eine neue „Polygon“ in einer Variablen mit dem Namen „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. Erstellen Sie mithilfe des Polygons, des eindeutigen Bezeichners „1000“ und des Namens „Main Campus“ eine neue „Region“-Variable namens „mainCampusRegion“.

    Region mainCampusRegion = new ("1000", "Main Campus", mainCampusPolygon);
    
  8. Verwenden Sie „Container.UpsertItemAsync“, um die Region zum Container hinzuzufügen. Schreiben Sie die Informationen der Region in die Konsole.

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

    Tipp

    In diesem Leitfaden wird upsert anstelle von insert verwendet, damit Sie das Skript mehrmals ausführen können, ohne einen Konflikt zwischen eindeutigen Bezeichnern zu verursachen. Weitere Informationen zu Upsert-Vorgängen finden Sie unter Erstellen von Elementen.

  9. Erstellen Sie eine neue „Point“-Variable namens „headquartersPoint“. Verwenden Sie diese Variable, um mithilfe des Punkts, des eindeutigen Bezeichners „0001“ und des Namens „Headquarters“ eine neue „Office“-Variable namens „headquartersOffice“ zu erstellen.

    Point headquartersPoint = new (-122.12827, 47.63980);
    Office headquartersOffice = new ("0001", "Headquarters", headquartersPoint);
    
  10. Erstellen Sie eine weitere „Point“-Variable mit dem Namen „researchPoint“. Verwenden Sie diese Variable, um mithilfe des entsprechenden Punkts, des eindeutigen Bezeichners „0002“ und des Namens „Research and Development“ eine weitere „Office“-Variable namens „researchOffice“ zu erstellen.

    Point researchPoint = new (-96.84369, 46.81298);
    Office researchOffice = new ("0002", "Research and Development", researchPoint);
    
  11. Erstellen Sie ein „TransactionalBatch“, um für beide „Office“-Variablen ein Upsert als einzelne Transaktion auszuführen. Schreiben Sie dann die Informationen beider Büros in die Konsole.

    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}");
    

    Hinweis

    Weitere Informationen zu Transaktionen finden Sie unter Transaktionale Batchvorgänge.

  12. Speichern Sie die Datei Program.cs.

  13. Führen Sie die Anwendung mithilfe von „dotnet run“ in einem Terminal. Beachten Sie, dass die Ausgabe der Anwendungsausführung Informationen zu den drei neu erstellten Elementen enthält.

    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 }
    

Abfragen von Geodaten mithilfe einer NoSQL-Abfrage

Die Typen im Namespace „Microsoft.Azure.Cosmos.Spatial“ können als Eingaben für eine parametrisierte NoSQL-Abfrage verwendet werden, um integrierte Funktionen wie „ST_DISTANCE“ zu verwenden.

  1. Öffnen Sie die Datei Program.cs.

  2. Erstellen Sie eine neue „string“-Variable namens „nosql“ mit der in diesem Abschnitt verwendeten Abfrage, um den Abstand zwischen Punkten zu messen.

    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
    ";
    

    Tipp

    Diese Abfrage platziert die Geofunktion in einer Unterabfrage, um den Prozess der mehrfachen Wiederverwendung des bereits berechneten Werts in den Klauseln „SELECT“ und „WHERE“ zu vereinfachen.

  3. Erstellen Sie eine neue „QueryDefinition“-Variable namens „query“, indem Sie die Variable „nosqlString“ als Parameter verwenden. Verwenden Sie dann die Fluent-Methode „QueryDefinition.WithParameter“ mehrmals, um der Abfrage folgende Parameter hinzuzufügen:

    Wert
    @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. Erstellen Sie mit „Container.GetItemQueryIterator<>“, dem generischen Typ „Result“ und der Variablen „query“ einen neuen Iterator. Verwenden Sie dann eine Kombination aus einer while- und einer foreach-Schleife, um alle Ergebnisse auf jeder Ergebnisseite zu durchlaufen. Geben Sie jedes Ergebnis an die Konsole aus.

    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}");
        }
    }
    

    Hinweis

    Weitere Informationen zum Auflisten von Abfrageergebnissen finden Sie unter Abfrageelemente.

  5. Speichern Sie die Datei Program.cs.

  6. Führen Sie die Anwendung mithilfe von „dotnet run“ erneut in einem Terminal. Beachten Sie, dass die Ausgabe jetzt die Ergebnisse der Abfrage enthält.

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

Abfragen von Geodaten mithilfe von LINQ

Die LINQ to NoSQL-Funktionalität im .NET-SDK unterstützt das Einschließen von räumlichen Typen in Abfrageausdrücken. Darüber hinaus enthält das SDK Erweiterungsmethoden, die gleichwertigen integrierten Funktionen zugeordnet sind:

Erweiterungsmethode Integrierte Funktion
Distance() ST_DISTANCE
Intersects() ST_INTERSECTS
IsValid() ST_ISVALID
IsValidDetailed() ST_ISVALIDDETAILED
Within() ST_WITHIN
  1. Öffnen Sie die Datei Program.cs.

  2. Rufen Sie das Element „Region“ aus dem Container mit dem eindeutigen Bezeichner „1000“ ab, und speichern Sie es in einer Variablen mit dem Namen „region“.

    Region region = await container.ReadItemAsync<Region>("1000", new PartitionKey("business-region"));
    
  3. Verwenden Sie die „Container.GetItemLinqQueryable<>“-Methode, um eine LINQ-Abfrage abzurufen, und erstellen Sie die LINQ-Abfrage, indem Sie die folgenden drei Aktionen ausführen:

    1. Verwenden Sie die Erweiterungsmethode „Queryable.Where<>“, um nur nach Elementen mit einem „category“, das äquivalent zu „"business-office"“ ist, zu filtern.

    2. Verwenden Sie „Queryable.Where<>“ erneut, um mithilfe von „Geometry.Within()“ nur Orte innerhalb von „region“ der Variable „location“ zu filtern.

    3. Übersetzen Sie den LINQ-Ausdruck mithilfe von „CosmosLinqExtensions.ToFeedIterator<>“ in einen Feediterator.

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

    Wichtig

    In diesem Beispiel weist die „Location“-Eigenschaft des Büros einen Punkt und die „Location“-Eigenschaft der Region ein Polygon auf. „ST_WITHIN“ bestimmt, ob sich der Punkt des Büros innerhalb des Polygons der Region befindet.

  4. Verwenden Sie eine Kombination aus einer while- und einer foreach-Schleife, um alle Ergebnisse auf jeder Ergebnisseite zu durchlaufen. Geben Sie jedes Ergebnis an die Konsole aus.

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

  6. Führen Sie die Anwendung mithilfe von „dotnet run“ ein letztes Mal in einem Terminal aus. Beachten Sie, dass die Ausgabe jetzt die Ergebnisse der zweiten auf LINQ basierenden Abfrage enthält.

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

Bereinigen von Ressourcen

Entfernen Sie Ihre Datenbank, nachdem Sie diesen Leitfaden abgeschlossen haben.

  1. Öffnen Sie ein Terminal, und erstellen Sie eine Shellvariable für den Namen Ihres Kontos und Ihrer Ressourcengruppe.

    # Variable for resource group name
    resourceGroupName="<name-of-your-resource-group>"
    
    # Variable for account name
    accountName="<name-of-your-account>"
    
  2. Verwenden Sie „az cosmosdb sql database delete“, um die Datenbank zu entfernen.

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

Nächste Schritte