Skip to content

Commit

Permalink
Merge branch '129-add-new-distance-type-geo' into 'main'
Browse files Browse the repository at this point in the history
Add new distance-type 'Geo' #129

See merge request objectbox/objectbox-dart!98
  • Loading branch information
greenrobot-team committed Feb 4, 2025
2 parents 0d98007 + 8b6df59 commit 282510d
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 4 deletions.
2 changes: 2 additions & 0 deletions objectbox/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## latest

* Vector Search: You can now use the new `VectorDistanceType.GEO` distance-type to perform vector searches on
geographical coordinates. This is particularly useful for location-based applications.
* Flutter for Linux/Windows, Dart Native: update to [objectbox-c 4.1.0](https://github.com/objectbox/objectbox-c/releases/tag/v4.1.0).
* Flutter for Android: update to [objectbox-android 4.1.0](https://github.com/objectbox/objectbox-java/releases/tag/V4.1.0).
If your project is [using Admin](https://docs.objectbox.io/data-browser#admin-for-android), make sure to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class City {

String? name;

@HnswIndex(dimensions: 2)
@HnswIndex(dimensions: 2, distanceType: VectorDistanceType.geo)
@Property(type: PropertyType.floatVector)
List<double>? location;

Expand Down
13 changes: 12 additions & 1 deletion objectbox/lib/src/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,18 @@ enum VectorDistanceType {
/// The more negative the dot product, the higher the distance is (the farther the vectors are).
///
/// Value range: 0.0 - 2.0 (nonlinear; 0.0: nearest, 1.0: orthogonal, 2.0: farthest)
dotProductNonNormalized
dotProductNonNormalized,

/// For geospatial coordinates aka latitude/longitude pairs.
///
/// Note, that the vector dimension must be 2, with the latitude being the first element and longitude the second.
/// If the vector has more than 2 dimensions, the first 2 dimensions are used.
/// If the vector has fewer than 2 dimensions, the distance is zero.
///
/// Internally, this uses haversine distance.
///
/// Value range: 0 km - 6371 * π km (approx. 20015.09 km; half the Earth's circumference)
geo
}

/// Flags as a part of the [HnswIndex] configuration.
Expand Down
2 changes: 2 additions & 0 deletions objectbox/lib/src/modelinfo/model_hnsw_params.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ extension ModelVectorDistanceType on VectorDistanceType {
return OBXVectorDistanceType.DotProduct;
} else if (this == VectorDistanceType.dotProductNonNormalized) {
return OBXVectorDistanceType.DotProductNonNormalized;
} else if (this == VectorDistanceType.geo) {
return OBXVectorDistanceType.Geo;
} else {
throw ArgumentError.value(this, "distanceType");
}
Expand Down
1 change: 1 addition & 0 deletions objectbox_test/test/annotations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void main() {
OBXVectorDistanceType.DotProduct);
expect(VectorDistanceType.dotProductNonNormalized.toConstant(),
OBXVectorDistanceType.DotProductNonNormalized);
expect(VectorDistanceType.geo.toConstant(), OBXVectorDistanceType.Geo);
});

test("ModelHnswParams maps values", () {
Expand Down
4 changes: 4 additions & 0 deletions objectbox_test/test/entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ class HnswObject {
@HnswIndex(dimensions: 2)
List<double>? floatVector;

@Property(type: PropertyType.floatVector)
@HnswIndex(dimensions: 2, distanceType: VectorDistanceType.geo)
List<double>? floatVectorGeoCoordinates;

final rel = ToOne<RelatedNamedEntity>();
}

Expand Down
35 changes: 35 additions & 0 deletions objectbox_test/test/hnsw_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,41 @@ void main() {
expect(closest2.name, "node8");
});

test('vectorSearchCitiesGeo', () {
// capital cities across Europe
List<String> cities = ["Berlin", "Paris", "Rome", "Madrid", "London"];
List<List<double>> coordinates = [
[52.5200, 13.4050],
[48.8566, 2.3522],
[41.9028, 12.4964],
[40.4168, -3.7038],
[51.5074, -0.1278]
];

box.putMany(List.generate(cities.length, (i) {
return HnswObject()
..name = cities[i]
..floatVectorGeoCoordinates = coordinates[i];
}));

// lat/lng for Munich
final List<double> searchVector = [48.1371, 11.5754];

final query = box
.query(HnswObject_.floatVectorGeoCoordinates
.nearestNeighborsF32(searchVector, 5))
.build();
addTearDown(() => query.close());

final nearestCities = query.find();
expect(nearestCities.length, 5);
expect(nearestCities[0].name, "Berlin");
expect(nearestCities[1].name, "Paris");
expect(nearestCities[2].name, "Rome");
expect(nearestCities[3].name, "Madrid");
expect(nearestCities[4].name, "London");
});

test('find offset limit', () {
box.putMany(List.generate(15, (index) {
final i = index + 1; // start at 1
Expand Down
11 changes: 9 additions & 2 deletions objectbox_test/test/objectbox-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@
},
{
"id": "14:880388751413233760",
"lastPropertyId": "4:2308158275756661586",
"lastPropertyId": "5:1476994841170736323",
"name": "HnswObject",
"properties": [
{
Expand Down Expand Up @@ -695,6 +695,13 @@
"flags": 520,
"indexId": "22:6527315700526716999",
"relationTarget": "RelatedNamedEntity"
},
{
"id": "5:1476994841170736323",
"name": "floatVectorGeoCoordinates",
"type": 28,
"flags": 8,
"indexId": "23:6649884639373473085"
}
],
"relations": []
Expand All @@ -720,7 +727,7 @@
}
],
"lastEntityId": "15:4803284427984871569",
"lastIndexId": "22:6527315700526716999",
"lastIndexId": "23:6649884639373473085",
"lastRelationId": "1:2155747579134420981",
"lastSequenceId": "0:0",
"modelVersion": 5,
Expand Down

0 comments on commit 282510d

Please sign in to comment.