Milvus
Zilliz
홈페이지
  • 사용자 가이드
  • Home
  • Docs
  • 사용자 가이드

  • 스키마 및 데이터 필드

  • 지오메트리 필드

기하학 필드Compatible with Milvus 2.6.4+

지리 정보 시스템(GIS), 매핑 도구 또는 위치 기반 서비스와 같은 애플리케이션을 구축할 때 기하학적 데이터를 저장하고 쿼리해야 하는 경우가 많습니다. Milvus의 GEOMETRY 데이터 유형은 유연한 기하학적 데이터를 저장하고 쿼리하는 기본 방법을 제공하여 이 문제를 해결합니다.

예를 들어 벡터 유사성을 공간 제약 조건과 결합해야 할 때는 기하학 필드를 사용하세요:

  • 위치 기반 서비스(LBS): "이 도시 블록 내에서 유사한 POI 찾기"

  • 다중 모드 검색: "이 지점에서 1km 이내에 있는 유사한 사진 검색"

  • 지도 및 물류: "한 지역 내의 자산" 또는 "경로와 교차하는 경로"

지오메트리 필드를 사용하려면 SDK를 최신 버전으로 업그레이드하세요.

지오메트리 필드란 무엇인가요?

지오메트리 필드는 기하학적 데이터를 저장하는 Milvus의 스키마 정의 데이터 유형(DataType.GEOMETRY)입니다. 기하학 필드로 작업할 때는 데이터 삽입과 쿼리 모두에 사용되는 사람이 읽을 수 있는 표현인 잘 알려진 텍스트(WKT) 형식을 사용하여 데이터와 상호 작용합니다. 내부적으로 Milvus는 효율적인 저장 및 처리를 위해 WKT를 잘 알려진 바이너리(WKB) 로 변환하지만, 사용자가 직접 WKB를 처리할 필요는 없습니다.

GEOMETRY 데이터 유형은 다음과 같은 기하학적 객체를 지원합니다:

  • 포인트: POINT (x y); 예: POINT (13.403683 52.520711), 여기서 x = 경도, y = 위도

  • 선형: 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 ((x1 y1), (x2 y2), …), 예시, MULTIPOINT ((10 40), (40 30), (20 20), (30 10))

  • 다중 문자열 MULTILINESTRING ((x1 y1, …), (xk yk, …)) 예를 들어 MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))

  • 다중다각형: 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 필드를 사용하려면 컬렉션을 만들 때 컬렉션 스키마에 명시적으로 정의합니다. 다음 예는 DataType.GEOMETRY 유형의 geo 필드가 있는 컬렉션을 만드는 방법을 보여줍니다.

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 값을 허용합니다. 자세한 내용은 Null 가능 및 기본값을 참조하세요.

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)

Where:

  • operator 는 지원되는 기하 도형 연산자 중 하나입니다(예: ST_CONTAINS, ST_INTERSECTS). 연산자 이름은 모두 대문자 또는 모두 소문자여야 합니다. 지원되는 연산자 목록은 지원되는 지오메트리 연산자를 참조하십시오.

  • geo_fieldGEOMETRY 필드의 이름입니다.

  • '{wkt}' 는 쿼리할 지오메트리의 WKT 표현입니다.

  • distanceST_DWITHIN 에 대한 임계값입니다.

다음 예에서는 필터 표현식에서 다양한 도형 관련 연산자를 사용하는 방법을 보여 줍니다:

예제 1: 예 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: 중심점으로부터 1km 이내의 엔티티 찾기

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를 참조하세요.

FAQ

컬렉션에 동적 필드 기능을 활성화한 경우, 동적 필드 키에 기하 도형 데이터를 삽입할 수 있나요?

아니요, 도형 데이터는 동적 필드에 삽입할 수 없습니다. 기하 도형 데이터를 삽입하기 전에 컬렉션 스키마에 GEOMETRY 필드가 명시적으로 정의되어 있는지 확인하세요.

도형 필드가 mmap 기능을 지원하나요?

예. GEOMETRY 필드는 mmap을 지원합니다. 자세한 내용은 mmap 사용을 참조하세요.

GEOMETRY 필드를 null 가능으로 정의하거나 기본값을 설정할 수 있나요?

예, GEOMETRY 필드는 nullable 속성과 WKT 형식의 기본값을 지원합니다. 자세한 내용은 Null 가능 및 기본값을 참조하세요.