Champ géométriqueCompatible with Milvus 2.6.4+
Lors de la création d'applications telles que les systèmes d'information géographique (SIG), les outils de cartographie ou les services basés sur la localisation, il est souvent nécessaire de stocker et d'interroger des données géométriques. Le type de données GEOMETRY de Milvus résout ce problème en fournissant un moyen natif de stocker et d'interroger des données géométriques flexibles.
Utilisez un champ GEOMETRY lorsque vous devez combiner la similarité vectorielle avec des contraintes spatiales, par exemple :
Service de localisation (LBS) : "trouver des points d'intérêt similaires à l'intérieur de ce pâté de maisons".
Recherche multimodale : recherche multimodale : "trouver des photos similaires dans un rayon de 1 km autour de ce point".
Cartes et logistique : "actifs à l'intérieur d' une région" ou "itinéraires croisant un chemin".
Pour utiliser le champ GEOMETRY, mettez à jour votre SDK avec la dernière version.
Qu'est-ce qu'un champ GEOMETRIE ?
Un champ GEOMETRIE est un type de données défini par le schéma (DataType.GEOMETRY) dans Milvus qui stocke des données géométriques. Lorsque vous travaillez avec des champs géométriques, vous interagissez avec les données à l'aide du format Well-Known Text (WKT), une représentation lisible par l'homme utilisée à la fois pour l'insertion de données et l'interrogation. En interne, Milvus convertit WKT en Well-Known Binary (WKB ) pour un stockage et un traitement efficaces, mais vous n'avez pas besoin de gérer WKB directement.
Le type de données GEOMETRY prend en charge les objets géométriques suivants :
POINT:
POINT (x y); par exemple,POINT (13.403683 52.520711)oùx= longitude ety= latitudeLINESTRING:
LINESTRING (x1 y1, x2 y2, …); par exemple,LINESTRING (13.40 52.52, 13.41 52.51)POLYGONE:
POLYGON ((x1 y1, x2 y2, x3 y3, x1 y1)); par exemple,POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))MULTIPOINT:
MULTIPOINT ((x1 y1), (x2 y2), …), par exemple,MULTIPOINT ((10 40), (40 30), (20 20), (30 10))MULTILINESTRING:
MULTILINESTRING ((x1 y1, …), (xk yk, …)), par exemple,MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))MULTIPOLYGON:
MULTIPOLYGON (((outer ring ...)), ((outer ring ...))), par exemple,MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))GEOMETRYCOLLECTION:
GEOMETRYCOLLECTION(POINT(x y), LINESTRING(x1 y1, x2 y2), ...), par exemple,GEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 40), POLYGON ((40 40, 20 45, 45 30, 40 40)))
Opérations de base
Pour utiliser un champ GEOMETRY, il faut le définir dans le schéma de la collection, insérer des données géométriques, puis interroger les données à l'aide d'expressions de filtrage spécifiques.
Étape 1 : Définition d'un champ GEOMETRY
Pour utiliser un champ GEOMETRY, définissez-le explicitement dans votre schéma de collection lors de la création de la collection. L'exemple suivant montre comment créer une collection avec un champ geo de type DataType.GEOMETRY.
from pymilvus import MilvusClient, DataType
import numpy as np
dim = 8
collection_name = "geo_collection"
milvus_client = MilvusClient("http://localhost:19530")
# Create schema with a GEOMETRY field
schema = milvus_client.create_schema(enable_dynamic_field=True)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("embeddings", DataType.FLOAT_VECTOR, dim=dim)
schema.add_field("geo", DataType.GEOMETRY, nullable=True)
schema.add_field("name", DataType.VARCHAR, max_length=128)
milvus_client.create_collection(collection_name, schema=schema, consistency_level="Strong")
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
private static final String COLLECTION_NAME = "geo_collection";
private static final Integer DIM = 128;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.token("root:Milvus")
.build());
CreateCollectionReq.CollectionSchema collectionSchema = CreateCollectionReq.CollectionSchema.builder()
.enableDynamicField(true)
.build();
collectionSchema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.build());
collectionSchema.addField(AddFieldReq.builder()
.fieldName("embeddings")
.dataType(DataType.FloatVector)
.dimension(DIM)
.build());
collectionSchema.addField(AddFieldReq.builder()
.fieldName("geo")
.dataType(DataType.Geometry)
.isNullable(true)
.build());
collectionSchema.addField(AddFieldReq.builder()
.fieldName("name")
.dataType(DataType.VarChar)
.maxLength(128)
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName(COLLECTION_NAME)
.collectionSchema(collectionSchema)
.build();
client.createCollection(requestCreate);
import { MilvusClient, DataType } from '@zilliz/milvus2-sdk-node';
const milvusClient = new MilvusClient('http://localhost:19530');
const schema = [
{ name: 'id', data_type: DataType.Int64, is_primary_key: true },
{ name: 'embeddings', data_type: DataType.FloatVector, dim: 8 },
{ name: 'geo', data_type: DataType.Geometry, is_nullable: true },
{ name: 'name', data_type: DataType.VarChar, max_length: 128 },
];
await milvusClient.createCollection({
collection_name: 'geo_collection',
fields: schema,
consistency_level: 'Strong',
});
// go
# restful
Dans cet exemple, le champ GEOMETRY défini dans le schéma de la collection autorise les valeurs nulles avec nullable=True. Pour plus de détails, voir Nullable & Default.
Étape 2 : Insérer des données
Insérez des entités avec des données géométriques au format WKT. Voici un exemple avec plusieurs points géo :
rng = np.random.default_rng(seed=19530)
geo_points = [
'POINT(13.399710 52.518010)',
'POINT(13.403934 52.522877)',
'POINT(13.405088 52.521124)',
'POINT(13.408223 52.516876)',
'POINT(13.400092 52.521507)',
'POINT(13.408529 52.519274)',
]
rows = [
{"id": 1, "name": "Shop A", "embeddings": rng.random((1, dim))[0], "geo": geo_points[0]},
{"id": 2, "name": "Shop B", "embeddings": rng.random((1, dim))[0], "geo": geo_points[1]},
{"id": 3, "name": "Shop C", "embeddings": rng.random((1, dim))[0], "geo": geo_points[2]},
{"id": 4, "name": "Shop D", "embeddings": rng.random((1, dim))[0], "geo": geo_points[3]},
{"id": 5, "name": "Shop E", "embeddings": rng.random((1, dim))[0], "geo": geo_points[4]},
{"id": 6, "name": "Shop F", "embeddings": rng.random((1, dim))[0], "geo": geo_points[5]},
]
insert_result = milvus_client.insert(collection_name, rows)
print(insert_result)
# Expected output:
# {'insert_count': 6, 'ids': [1, 2, 3, 4, 5, 6]}
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
List<String> geoPoints = Arrays.asList(
"POINT(13.399710 52.518010)",
"POINT(13.403934 52.522877)",
"POINT(13.405088 52.521124)",
"POINT(13.408223 52.516876)",
"POINT(13.400092 52.521507)",
"POINT(13.408529 52.519274)"
);
List<String> names = Arrays.asList("Shop A", "Shop B", "Shop C", "Shop D", "Shop E", "Shop F");
Random ran = new Random();
Gson gson = new Gson();
List<JsonObject> rows = new ArrayList<>();
for (int i = 0; i < geoPoints.size(); i++) {
JsonObject row = new JsonObject();
row.addProperty("id", i);
row.addProperty("geo", geoPoints.get(i));
row.addProperty("name", names.get(i));
List<Float> vector = new ArrayList<>();
for (int d = 0; d < DIM; ++d) {
vector.add(ran.nextFloat());
}
row.add("embeddings", gson.toJsonTree(vector));
rows.add(row);
}
client.insert(InsertReq.builder()
.collectionName(COLLECTION_NAME)
.data(rows)
.build());
const geo_points = [
'POINT(13.399710 52.518010)',
'POINT(13.403934 52.522877)',
'POINT(13.405088 52.521124)',
'POINT(13.408223 52.516876)',
'POINT(13.400092 52.521507)',
'POINT(13.408529 52.519274)',
];
const rows = [
{"id": 1, "name": "Shop A", "embeddings": [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8], "geo": geo_points[0]},
{"id": 2, "name": "Shop B", "embeddings": [0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9], "geo": geo_points[1]},
{"id": 3, "name": "Shop C", "embeddings": [0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0], "geo": geo_points[2]},
{"id": 4, "name": "Shop D", "embeddings": [0.4,0.5,0.6,0.7,0.8,0.9,1.0,0.1], "geo": geo_points[3]},
{"id": 5, "name": "Shop E", "embeddings": [0.5,0.6,0.7,0.8,0.9,1.0,0.1,0.2], "geo": geo_points[4]},
{"id": 6, "name": "Shop F", "embeddings": [0.6,0.7,0.8,0.9,1.0,0.1,0.2,0.3], "geo": geo_points[5]},
];
const insert_result = await milvusClient.insert({
collection_name: 'geo_collection',
data: rows,
});
console.log(insert_result);
// go
# restful
Etape 3 : Opérations de filtrage
Avant de pouvoir effectuer des opérations de filtrage sur les champs GEOMETRY, il faut s'assurer que
Vous avez créé un index sur chaque champ vectoriel.
La collection est chargée en mémoire.
index_params = milvus_client.prepare_index_params()
index_params.add_index(field_name="embeddings", metric_type="L2")
milvus_client.create_index(collection_name, index_params)
milvus_client.load_collection(collection_name)
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.index.request.CreateIndexReq;
List<IndexParam> indexParams = new ArrayList<>();
indexParams.add(IndexParam.builder()
.fieldName("embeddings")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.L2)
.build());
client.createIndex(CreateIndexReq.builder()
.collectionName(COLLECTION_NAME)
.indexParams(indexParams)
.build());
const index_params = {
field_name: "embeddings",
index_type: "IVF_FLAT",
metric_type: "L2",
params: { nlist: 128 },
};
await milvusClient.createIndex({
collection_name: 'geo_collection',
index_name: 'embeddings_index',
index_params: index_params,
});
await milvusClient.loadCollection({
collection_name: 'geo_collection',
});
// go
# restful
Une fois ces conditions remplies, vous pouvez utiliser des expressions avec des opérateurs géométriques dédiés pour filtrer votre collection sur la base des valeurs géométriques.
Définir les expressions de filtrage
Pour filtrer sur un champ GEOMETRY, utilisez un opérateur géométrique dans une expression :
Général :
{operator}(geo_field, '{wkt}')Basé sur la distance :
ST_DWITHIN(geo_field, '{wkt}', distance)
Où :
operatorest l'un des opérateurs géométriques pris en charge (par exemple,ST_CONTAINS,ST_INTERSECTS). Les noms des opérateurs doivent être en majuscules ou en minuscules. Pour obtenir la liste des opérateurs pris en charge, reportez-vous à la section Opérateurs géométriques pris en charge.geo_fieldest le nom de votre champGEOMETRY.'{wkt}'est la représentation WKT de la géométrie à interroger.distanceest le seuil spécifique àST_DWITHIN.
Les exemples suivants montrent comment utiliser différents opérateurs spécifiques à la géométrie dans une expression de filtre :
Exemple 1 : Trouver des entités à l'intérieur d'une zone rectangulaire
top_left_lon, top_left_lat = 13.403683, 52.520711
bottom_right_lon, bottom_right_lat = 13.455868, 52.495862
bounding_box_wkt = f"POLYGON(({top_left_lon} {top_left_lat}, {bottom_right_lon} {top_left_lat}, {bottom_right_lon} {bottom_right_lat}, {top_left_lon} {bottom_right_lat}, {top_left_lon} {top_left_lat}))"
query_results = milvus_client.query(
collection_name,
filter=f"st_within(geo, '{bounding_box_wkt}')",
output_fields=["name", "geo"]
)
for ret in query_results:
print(ret)
# Expected output:
# {'name': 'Shop D', 'geo': 'POINT (13.408223 52.516876)', 'id': 4}
# {'name': 'Shop F', 'geo': 'POINT (13.408529 52.519274)', 'id': 6}
# {'name': 'Shop A', 'geo': 'POINT (13.39971 52.51801)', 'id': 1}
# {'name': 'Shop B', 'geo': 'POINT (13.403934 52.522877)', 'id': 2}
# {'name': 'Shop C', 'geo': 'POINT (13.405088 52.521124)', 'id': 3}
# {'name': 'Shop D', 'geo': 'POINT (13.408223 52.516876)', 'id': 4}
# {'name': 'Shop E', 'geo': 'POINT (13.400092 52.521507)', 'id': 5}
# {'name': 'Shop F', 'geo': 'POINT (13.408529 52.519274)', 'id': 6}
import io.milvus.v2.service.vector.request.QueryReq;
import io.milvus.v2.service.vector.response.QueryResp;
float topLeftLon = 13.403683f;
float topLeftLat = 52.520711f;
float bottomRightLon = 13.455868f;
float bottomRightLat = 52.495862f;
String boundingBoxWkt = String.format("POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))",
topLeftLon, topLeftLat, bottomRightLon, topLeftLat, bottomRightLon, bottomRightLat,
topLeftLon, bottomRightLat, topLeftLon, topLeftLat);
String filter = String.format("st_within(geo, '%s')", boundingBoxWkt);
QueryResp queryResp = client.query(QueryReq.builder()
.collectionName(COLLECTION_NAME)
.filter(filter)
.outputFields(Arrays.asList("name", "geo"))
.build());
List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
System.out.println("Query results:");
for (QueryResp.QueryResult result : queryResults) {
System.out.println(result.getEntity());
}
const top_left_lon = 13.403683;
const top_left_lat = 52.520711;
const bottom_right_lon = 13.455868;
const bottom_right_lat = 52.495862;
const bounding_box_wkt = `POLYGON((${top_left_lon} ${top_left_lat}, ${bottom_right_lon} ${top_left_lat}, ${bottom_right_lon} ${bottom_right_lat}, ${top_left_lon} ${bottom_right_lat}, ${top_left_lon} ${top_left_lat}))`;
const query_results = await milvusClient.query({
collection_name: 'geo_collection',
filter: `st_within(geo, '${bounding_box_wkt}')`,
output_fields: ['name', 'geo'],
});
for (const ret of query_results.data) {
console.log(ret);
}
// go
# restful
Exemple 2 : Trouver des entités dans un rayon de 1 km autour d'un point central
center_point_lon, center_point_lat = 13.403683, 52.520711
radius_meters = 1000.0
central_point_wkt = f"POINT({center_point_lon} {center_point_lat})"
query_results = milvus_client.query(
collection_name,
filter=f"st_dwithin(geo, '{central_point_wkt}', {radius_meters})",
output_fields=["name", "geo"]
)
for ret in query_results:
print(ret)
# Expected output:
# hit: {'id': 4, 'distance': 0.9823770523071289, 'entity': {'name': 'Shop D', 'geo': 'POINT (13.408223 52.516876)'}}
import io.milvus.v2.service.vector.request.QueryReq;
import io.milvus.v2.service.vector.response.QueryResp;
float centerPointLon = 13.403683f;
float centerPointLat = 52.520711f;
float radiusMeters = 1000.0f;
String centralPointWkt = String.format("POINT(%f %f)", centerPointLon, centerPointLat);
String filter=String.format("st_dwithin(geo, '%s', %f)", centralPointWkt, radiusMeters);
QueryResp queryResp = client.query(QueryReq.builder()
.collectionName(COLLECTION_NAME)
.filter(filter)
.outputFields(Arrays.asList("name", "geo"))
.build());
List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
System.out.println("Query results:");
for (QueryResp.QueryResult result : queryResults) {
System.out.println(result.getEntity());
}
const center_point_lon = 13.403683;
const center_point_lat = 52.520711;
const radius_meters = 1000.0;
const central_point_wkt = `POINT(${center_point_lon} ${center_point_lat})`;
const query_results_dwithin = await milvusClient.query({
collection_name: 'geo_collection',
filter: `st_dwithin(geo, '${central_point_wkt}', ${radius_meters})`,
output_fields: ['name', 'geo'],
});
for (const ret of query_results_dwithin.data) {
console.log(ret);
}
// go
# restful
Exemple 3 : Combiner la similarité vectorielle avec un filtre spatial
vectors_to_search = rng.random((1, dim))
result = milvus_client.search(
collection_name,
vectors_to_search,
limit=3,
output_fields=["name", "geo"],
filter=f"st_within(geo, '{bounding_box_wkt}')"
)
for hits in result:
for hit in hits:
print(f"hit: {hit}")
# Expected output:
# hit: {'id': 6, 'distance': 1.3406795263290405, 'entity': {'name': 'Shop F', 'geo': 'POINT (13.408529 52.519274)'}}
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
Random ran = new Random();
List<Float> vector = new ArrayList<>();
for (int d = 0; d < DIM; ++d) {
vector.add(ran.nextFloat());
}
String filter=String.format("st_within(geo, '%s')", boundingBoxWkt);
SearchReq request = SearchReq.builder()
.collectionName(COLLECTION_NAME)
.data(Collections.singletonList(new FloatVec(vector)))
.limit(3)
.filter(filter)
.outputFields(Arrays.asList("name", "geo"))
.build();
SearchResp statusR = client.search(request);
List<List<SearchResp.SearchResult>> searchResults = statusR.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
for (SearchResp.SearchResult result : results) {
System.out.printf("ID: %d, Score: %f, %s\n", (long)result.getId(), result.getScore(), result.getEntity().toString());
}
}
const vectors_to_search = [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]];
const search_results = await milvusClient.search({
collection_name: "geo_collection",
vectors: vectors_to_search,
limit: 3,
output_fields: ["name", "geo"],
filter: `st_within(geo, '${bounding_box_wkt}')`,
});
for (const hits of search_results.results) {
for (const hit of hits) {
console.log(`hit: ${JSON.stringify(hit)}`);
}
}
// go
# restful
Suivant : Accélérer les requêtes
Par défaut, les requêtes sur les champs GEOMETRY sans index effectueront un balayage complet de toutes les lignes, ce qui peut être lent sur les grands ensembles de données. Pour accélérer les requêtes géométriques, créez un index RTREE sur votre champ GEOMETRY.
Pour plus d'informations, reportez-vous à RTREE.
FAQ
Si j'ai activé la fonctionnalité de champ dynamique pour ma collection, puis-je insérer des données géométriques dans une clé de champ dynamique ?
Non, les données géométriques ne peuvent pas être insérées dans un champ dynamique. Avant d'insérer des données géométriques, assurez-vous que le champ GEOMETRY a été explicitement défini dans le schéma de votre collection.
Le champ GEOMETRY prend-il en charge la fonction mmap ?
Oui, le champ GEOMETRY prend en charge la fonction mmap. Pour plus d'informations, reportez-vous à la section Utiliser mmap.
Puis-je définir la rubrique GEOMETRY comme nullable ou lui attribuer une valeur par défaut ?
Oui, le champ GEOMETRY prend en charge l'attribut nullable et une valeur par défaut au format WKT. Pour plus d'informations, reportez-vous à la section Nullable & Default.