مجال الهندسةCompatible with Milvus 2.6.4+

عند إنشاء تطبيقات مثل نظم المعلومات الجغرافية (GIS) أو أدوات رسم الخرائط أو الخدمات المستندة إلى الموقع، غالبًا ما تحتاج إلى تخزين البيانات الهندسية والاستعلام عنها. يحل نوع البيانات GEOMETRY في Milvus هذا التحدي من خلال توفير طريقة أصلية لتخزين البيانات الهندسية المرنة والاستعلام عنها.

استخدم حقل GEOMETRY عندما تحتاج إلى الجمع بين التشابه المتجه والقيود المكانية، على سبيل المثال:

  • خدمة قاعدة الموقع الجغرافي (LBS): "العثور على نقاط مهمة متشابهة داخل هذا المربع السكني في المدينة"

  • بحث متعدد الوسائط: "استرداد الصور المتشابهة في نطاق 1 كم من هذه النقطة"

  • الخرائط واللوجستيات: "الأصول داخل منطقة ما" أو "الطرق التي تتقاطع مع مسار ما"

لاستخدام حقل GEOMETRY، قم بترقية SDK إلى أحدث إصدار.

ما هو حقل GEOMETRY؟

حقل GEOMETRY هو نوع بيانات معرّف من قبل المخطط (DataType.GEOMETRY) في ميلفوس يخزن البيانات الهندسية. عند العمل مع حقول الهندسة، فإنك تتفاعل مع البيانات باستخدام تنسيق النص المعروف (WKT) ، وهو تمثيل مقروء بشري يستخدم لإدراج البيانات والاستعلام عنها. داخليًا، تقوم Milvus بتحويل WKT إلى ثنائي معروف (WKB) للتخزين والمعالجة الفعالة، ولكنك لا تحتاج إلى التعامل مع WKB مباشرة.

يدعم نوع البيانات GEOMETRY الكائنات الهندسية التالية:

  • نقطة: 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 ((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 على أنه قابل للإلغاء أو تعيين قيمة افتراضية؟

نعم، يدعم حقل GEOMETRY السمة nullable والقيمة الافتراضية بتنسيق WKT. لمزيد من المعلومات، ارجع إلى Nullable & Default.