幾何領域Compatible with Milvus 2.6.4+
在建立地理資訊系統 (GIS)、繪圖工具或定位服務等應用程式時,您經常需要儲存和查詢幾何資料。Milvus 中的GEOMETRY 資料類型提供了儲存和查詢彈性幾何資料的原生方式,解決了這個難題。
例如,當您需要結合向量相似性與空間限制時,請使用 GEOMETRY 欄位:
位置基礎服務 (LBS):「尋找此城市區塊內相似的 POI」。
多模式搜尋:"檢索此點1 公里範圍內的相似照片
地圖與物流:「區域內的資產 」或「與路徑相交的路線」
若要使用 GEOMETRY 欄位,請將您的 SDK 升級至最新版本。
什麼是 GEOMETRY 欄位?
在 Milvus 中,GEOMETRY 欄位是一種模式定義的資料類型 (DataType.GEOMETRY) ,用來儲存幾何資料。當處理幾何欄位時,您使用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:
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 的 null 值。詳情請參閱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 欄位進行查詢時,會對所有行執行完整掃描,這在大型資料集上可能會很慢。若要加速幾何查詢,請在 GEOMETRY 欄位上建立RTREE 索引。
如需詳細資訊,請參閱RTREE。
常見問題
如果我啟用了資料集的動態欄位功能,我可以將幾何資料插入動態欄位鍵嗎?
不可以,幾何資料不能插入動態欄位。在插入幾何資料之前,請確定GEOMETRY 欄位已在您的資料庫模式中明確定義。
GEOMETRY 欄位是否支援 mmap 功能?
是的,GEOMETRY 欄位支援 mmap。如需詳細資訊,請參閱使用 mmap。
我是否可以將 GEOMETRY 欄位定義為可為空或設定預設值?
可以,GEOMETRY 欄位支援nullable 屬性和 WKT 格式的預設值。如需詳細資訊,請參閱Nullable & Default。