Поле геометрииCompatible with Milvus 2.6.4+
При создании таких приложений, как географические информационные системы (ГИС), картографические инструменты или сервисы, основанные на определении местоположения, часто возникает необходимость хранить и запрашивать геометрические данные. Тип данных GEOMETRY в Milvus решает эту задачу, предоставляя собственный способ хранения и запроса гибких геометрических данных.
Используйте поле GEOMETRY, например, когда вам нужно объединить векторное сходство с пространственными ограничениями:
Location-Base Service (LBS): "найти похожие POI в пределах этого городского квартала".
Мультимодальный поиск: "найти похожие фотографии в радиусе 1 км от этой точки".
Карты и логистика: "активы внутри региона" или "маршруты , пересекающиеся с путем".
Чтобы использовать поле GEOMETRY, обновите SDK до последней версии.
Что такое поле ГЕОМЕТРИЯ?
Поле GEOMETRY - это тип данных, определяемый схемой (DataType.GEOMETRY) в Milvus, который хранит геометрические данные. При работе с геометрическими полями вы взаимодействуете с данными, используя формат Well-Known Text (WKT), человекочитаемое представление, используемое как для вставки данных, так и для запросов. Внутри Milvus преобразует WKT в Well-Known Binary (WKB) для эффективного хранения и обработки, но вам не нужно работать с WKB напрямую.
Тип данных GEOMETRY поддерживает следующие геометрические объекты:
POINT:
POINT (x y); например,POINT (13.403683 52.520711), гдеx= долгота иy= широта.LINESTRING:
LINESTRING (x1 y1, x2 y2, …); например,LINESTRING (13.40 52.52, 13.41 52.51)ПОЛИГОН:
POLYGON ((x1 y1, x2 y2, x3 y3, x1 y1)); например,POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))MULTIPOINT:
MULTIPOINT ((x1 y1), (x2 y2), …), например,MULTIPOINT ((10 40), (40 30), (20 20), (30 10))MULTILINESTRING:
MULTILINESTRING ((x1 y1, …), (xk yk, …)), например,MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))MULTIPOLYGON:
MULTIPOLYGON (((outer ring ...)), ((outer ring ...))), например,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), ...), например,GEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 40), POLYGON ((40 40, 20 45, 45 30, 40 40)))
Основные операции
Рабочий процесс использования поля GEOMETRY включает его определение в схеме коллекции, вставку геометрических данных, а затем запрос данных с использованием специальных выражений фильтрации.
Шаг 1: Определите поле GEOMETRY
Чтобы использовать поле GEOMETRY, явно определите его в схеме коллекции при ее создании. В следующем примере показано, как создать коллекцию с полем geo типа 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
В этом примере поле GEOMETRY, определенное в схеме коллекции, допускает нулевые значения с помощью nullable=True. Подробнее см. в разделе Nullable & Default.
Шаг 2: Вставка данных
Вставьте сущности с геометрическими данными в формате WKT. Вот пример с несколькими геоточками:
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
Шаг 3: Операции фильтрации
Прежде чем выполнять операции фильтрации по полям GEOMETRY, убедитесь, что:
Вы создали индекс для каждого векторного поля.
Коллекция загружена в память.
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
После выполнения этих требований вы можете использовать выражения со специальными геометрическими операторами для фильтрации коллекции на основе геометрических значений.
Определение выражений фильтрации
Для фильтрации по полю GEOMETRY используйте геометрический оператор в выражении:
Общие:
{operator}(geo_field, '{wkt}')На основе расстояния:
ST_DWITHIN(geo_field, '{wkt}', distance)
Где:
operatorодин из поддерживаемых геометрических операторов (например,ST_CONTAINS,ST_INTERSECTS). Имена операторов должны быть полностью прописными или полностью строчными. Список поддерживаемых операторов см. в разделе Поддерживаемые операторы геометрии.geo_fieldэто имя вашего поляGEOMETRY.'{wkt}'WKT-представление геометрии для запроса.distanceпорог, специально предназначенный дляST_DWITHIN.
В следующих примерах показано, как использовать различные операторы геометрии в выражении фильтра:
Пример 1: Поиск сущностей в пределах прямоугольной области
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
Пример 2: Поиск объектов в пределах 1 км от центральной точки
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
Пример 3: Комбинируйте векторное сходство с пространственным фильтром
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
Далее: Ускорение запросов
По умолчанию запросы к полям GEOMETRY без индекса выполняют полное сканирование всех строк, что может быть медленным для больших наборов данных. Чтобы ускорить геометрические запросы, создайте индекс RTREE для поля GEOMETRY.
Подробности см. в разделе RTREE.
ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ
Если я включил функцию динамических полей для своей коллекции, могу ли я вставить геометрические данные в ключ динамического поля?
Нет, геометрические данные не могут быть вставлены в динамическое поле. Перед вставкой геометрических данных убедитесь, что поле GEOMETRY явно определено в схеме вашей коллекции.
Поддерживает ли поле GEOMETRY функцию mmap?
Да, поле GEOMETRY поддерживает функцию mmap. Дополнительные сведения см. в разделе Использование mmap.
Можно ли определить поле GEOMETRY как nullable или установить значение по умолчанию?
Да, поле GEOMETRY поддерживает атрибут nullable и значение по умолчанию в формате WKT. Дополнительные сведения см. в разделе Nullable & Default.