Repository: owncloud/ocis
Pull request #7886 (2023-12-05 17:06:50)

Location Metadata
<!--
Thanks for submitting a change to ownCloud!

This is the bug tracker for the oCIS component. Find other components at https://github.com/owncloud/core/blob/master/.github/CONTRIBUTING.md#guidelines

For fixing potential security issues please see https://owncloud.org/security/

To make it possible for us to get your change reviewed and merged please carefully fill out the requested information below.

Please note that any kind of change needs first be submitted to the master branch which holds the next version of oCIS.

Please set the following labels:

- Set label "Status:Needs-Review" for review or "Status:In-Progress" if the PR still has open tasks.
- Assignment: assign to self
- Reviewers: pick at least one
-->

## Description
<!--- Describe your changes in detail -->
![Screenshot_20231205_180928](https://github.com/owncloud/ocis/assets/448487/fe464bfd-245d-4085-b224-4d974c220896)

## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
- Fixes <issue_link>

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- test environment:
- test case 1:
- test case 2:
- ...

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [ ] Code changes
- [ ] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


update-docs[bot]: Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a [changelog](https://github.com/owncloud/ocis/blob/master/changelog/README.md) item based on your changes.

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7886)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [2 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7886)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL)

[![89.1%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '89.1%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list) [89.1% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7886)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7886&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL) [3 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7886&resolved=false&types=CODE_SMELL)

[![89.1%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '89.1%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list) [89.1% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7886&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/changelog/unreleased/enhancement-store-and-index-location-metadata.md b/changelog/unreleased/enhancement-store-and-index-location-metadata.md
new file mode 100644
index 00000000000..ffc804b55d4
--- /dev/null
+++ b/changelog/unreleased/enhancement-store-and-index-location-metadata.md
@@ -0,0 +1,6 @@
+Enhancement: Store and index metadata
+
+location metadata is now extracted and stored by the search service.
+It is available for driveItems in a folder listing using the Graph API.
+
+https://github.com/owncloud/ocis/pull/7886
diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.go b/protogen/gen/ocis/messages/search/v0/search.pb.go
index 267fcf0e65f..0349d3e1772 100644
--- a/protogen/gen/ocis/messages/search/v0/search.pb.go
+++ b/protogen/gen/ocis/messages/search/v0/search.pb.go
@@ -306,6 +306,69 @@ func (x *Audio) GetYear() int32 {
 	return 0
 }
 
+type GeoCoordinates struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Altitude  *float64 `protobuf:"fixed64,1,opt,name=altitude,proto3,oneof" json:"altitude,omitempty"`
+	Latitude  *float64 `protobuf:"fixed64,2,opt,name=latitude,proto3,oneof" json:"latitude,omitempty"`
+	Longitude *float64 `protobuf:"fixed64,3,opt,name=longitude,proto3,oneof" json:"longitude,omitempty"`
+}
+
+func (x *GeoCoordinates) Reset() {
+	*x = GeoCoordinates{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoCoordinates) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoCoordinates) ProtoMessage() {}
+
+func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoCoordinates.ProtoReflect.Descriptor instead.
+func (*GeoCoordinates) Descriptor() ([]byte, []int) {
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GeoCoordinates) GetAltitude() float64 {
+	if x != nil && x.Altitude != nil {
+		return *x.Altitude
+	}
+	return 0
+}
+
+func (x *GeoCoordinates) GetLatitude() float64 {
+	if x != nil && x.Latitude != nil {
+		return *x.Latitude
+	}
+	return 0
+}
+
+func (x *GeoCoordinates) GetLongitude() float64 {
+	if x != nil && x.Longitude != nil {
+		return *x.Longitude
+	}
+	return 0
+}
+
 type Entity struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -326,12 +389,13 @@ type Entity struct {
 	Tags             []string               `protobuf:"bytes,13,rep,name=tags,proto3" json:"tags,omitempty"`
 	Highlights       string                 `protobuf:"bytes,14,opt,name=highlights,proto3" json:"highlights,omitempty"`
 	Audio            *Audio                 `protobuf:"bytes,15,opt,name=audio,proto3" json:"audio,omitempty"`
+	Location         *GeoCoordinates        `protobuf:"bytes,16,opt,name=location,proto3" json:"location,omitempty"`
 }
 
 func (x *Entity) Reset() {
 	*x = Entity{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -344,7 +408,7 @@ func (x *Entity) String() string {
 func (*Entity) ProtoMessage() {}
 
 func (x *Entity) ProtoReflect() protoreflect.Message {
-	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -357,7 +421,7 @@ func (x *Entity) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Entity.ProtoReflect.Descriptor instead.
 func (*Entity) Descriptor() ([]byte, []int) {
-	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{3}
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *Entity) GetRef() *Reference {
@@ -465,6 +529,13 @@ func (x *Entity) GetAudio() *Audio {
 	return nil
 }
 
+func (x *Entity) GetLocation() *GeoCoordinates {
+	if x != nil {
+		return x.Location
+	}
+	return nil
+}
+
 type Match struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -479,7 +550,7 @@ type Match struct {
 func (x *Match) Reset() {
 	*x = Match{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[5]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -492,7 +563,7 @@ func (x *Match) String() string {
 func (*Match) ProtoMessage() {}
 
 func (x *Match) ProtoReflect() protoreflect.Message {
-	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[5]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -505,7 +576,7 @@ func (x *Match) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Match.ProtoReflect.Descriptor instead.
 func (*Match) Descriptor() ([]byte, []int) {
-	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{4}
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *Match) GetEntity() *Entity {
@@ -588,7 +659,17 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{
 	0x5f, 0x69, 0x73, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x69, 0x74, 0x72, 0x61,
 	0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x42, 0x08, 0x0a, 0x06,
 	0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b,
-	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x22, 0xb8,
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x22, 0x9d,
+	0x01, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65,
+	0x73, 0x12, 0x1f, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x08, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x88,
+	0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x01, 0x48, 0x01, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x48, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74,
+	0x75, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6c, 0x74, 0x69, 0x74,
+	0x75, 0x64, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0xfd,
 	0x04, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x66,
 	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65,
 	0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30,
@@ -624,17 +705,22 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{
 	0x68, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x0f, 0x20, 0x01,
 	0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
 	0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x75, 0x64,
-	0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x56, 0x0a, 0x05, 0x4d, 0x61, 0x74,
-	0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x45, 0x6e, 0x74,
-	0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73,
-	0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72,
-	0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
-	0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x76, 0x32,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63,
-	0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72,
-	0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x12, 0x43, 0x0a, 0x08, 0x6c, 0x6f, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x63,
+	0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72,
+	0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e,
+	0x61, 0x74, 0x65, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56,
+	0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76,
+	0x30, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+	0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52,
+	0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63,
+	0x69, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67,
+	0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73,
+	0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
@@ -649,28 +735,30 @@ func file_ocis_messages_search_v0_search_proto_rawDescGZIP() []byte {
 	return file_ocis_messages_search_v0_search_proto_rawDescData
 }
 
-var file_ocis_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_ocis_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_ocis_messages_search_v0_search_proto_goTypes = []interface{}{
 	(*ResourceID)(nil),            // 0: ocis.messages.search.v0.ResourceID
 	(*Reference)(nil),             // 1: ocis.messages.search.v0.Reference
 	(*Audio)(nil),                 // 2: ocis.messages.search.v0.Audio
-	(*Entity)(nil),                // 3: ocis.messages.search.v0.Entity
-	(*Match)(nil),                 // 4: ocis.messages.search.v0.Match
-	(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
+	(*GeoCoordinates)(nil),        // 3: ocis.messages.search.v0.GeoCoordinates
+	(*Entity)(nil),                // 4: ocis.messages.search.v0.Entity
+	(*Match)(nil),                 // 5: ocis.messages.search.v0.Match
+	(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
 }
 var file_ocis_messages_search_v0_search_proto_depIdxs = []int32{
 	0, // 0: ocis.messages.search.v0.Reference.resource_id:type_name -> ocis.messages.search.v0.ResourceID
 	1, // 1: ocis.messages.search.v0.Entity.ref:type_name -> ocis.messages.search.v0.Reference
 	0, // 2: ocis.messages.search.v0.Entity.id:type_name -> ocis.messages.search.v0.ResourceID
-	5, // 3: ocis.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
+	6, // 3: ocis.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
 	0, // 4: ocis.messages.search.v0.Entity.parent_id:type_name -> ocis.messages.search.v0.ResourceID
 	2, // 5: ocis.messages.search.v0.Entity.audio:type_name -> ocis.messages.search.v0.Audio
-	3, // 6: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity
-	7, // [7:7] is the sub-list for method output_type
-	7, // [7:7] is the sub-list for method input_type
-	7, // [7:7] is the sub-list for extension type_name
-	7, // [7:7] is the sub-list for extension extendee
-	0, // [0:7] is the sub-list for field type_name
+	3, // 6: ocis.messages.search.v0.Entity.location:type_name -> ocis.messages.search.v0.GeoCoordinates
+	4, // 7: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity
+	8, // [8:8] is the sub-list for method output_type
+	8, // [8:8] is the sub-list for method input_type
+	8, // [8:8] is the sub-list for extension type_name
+	8, // [8:8] is the sub-list for extension extendee
+	0, // [0:8] is the sub-list for field type_name
 }
 
 func init() { file_ocis_messages_search_v0_search_proto_init() }
@@ -716,7 +804,7 @@ func file_ocis_messages_search_v0_search_proto_init() {
 			}
 		}
 		file_ocis_messages_search_v0_search_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Entity); i {
+			switch v := v.(*GeoCoordinates); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -728,6 +816,18 @@ func file_ocis_messages_search_v0_search_proto_init() {
 			}
 		}
 		file_ocis_messages_search_v0_search_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Entity); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_ocis_messages_search_v0_search_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Match); i {
 			case 0:
 				return &v.state
@@ -741,13 +841,14 @@ func file_ocis_messages_search_v0_search_proto_init() {
 		}
 	}
 	file_ocis_messages_search_v0_search_proto_msgTypes[2].OneofWrappers = []interface{}{}
+	file_ocis_messages_search_v0_search_proto_msgTypes[3].OneofWrappers = []interface{}{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_ocis_messages_search_v0_search_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   5,
+			NumMessages:   6,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.web.go b/protogen/gen/ocis/messages/search/v0/search.pb.web.go
index 1ed7a0bf377..cbcd502c50f 100644
--- a/protogen/gen/ocis/messages/search/v0/search.pb.web.go
+++ b/protogen/gen/ocis/messages/search/v0/search.pb.web.go
@@ -118,6 +118,42 @@ func (m *Audio) UnmarshalJSON(b []byte) error {
 
 var _ json.Unmarshaler = (*Audio)(nil)
 
+// GeoCoordinatesJSONMarshaler describes the default jsonpb.Marshaler used by all
+// instances of GeoCoordinates. This struct is safe to replace or modify but
+// should not be done so concurrently.
+var GeoCoordinatesJSONMarshaler = new(jsonpb.Marshaler)
+
+// MarshalJSON satisfies the encoding/json Marshaler interface. This method
+// uses the more correct jsonpb package to correctly marshal the message.
+func (m *GeoCoordinates) MarshalJSON() ([]byte, error) {
+	if m == nil {
+		return json.Marshal(nil)
+	}
+
+	buf := &bytes.Buffer{}
+
+	if err := GeoCoordinatesJSONMarshaler.Marshal(buf, m); err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+var _ json.Marshaler = (*GeoCoordinates)(nil)
+
+// GeoCoordinatesJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
+// instances of GeoCoordinates. This struct is safe to replace or modify but
+// should not be done so concurrently.
+var GeoCoordinatesJSONUnmarshaler = new(jsonpb.Unmarshaler)
+
+// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
+// uses the more correct jsonpb package to correctly unmarshal the message.
+func (m *GeoCoordinates) UnmarshalJSON(b []byte) error {
+	return GeoCoordinatesJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
+}
+
+var _ json.Unmarshaler = (*GeoCoordinates)(nil)
+
 // EntityJSONMarshaler describes the default jsonpb.Marshaler used by all
 // instances of Entity. This struct is safe to replace or modify but
 // should not be done so concurrently.
diff --git a/protogen/gen/ocis/messages/settings/v0/settings.pb.go b/protogen/gen/ocis/messages/settings/v0/settings.pb.go
index 9f0f3d92c64..bae3a9c8710 100644
--- a/protogen/gen/ocis/messages/settings/v0/settings.pb.go
+++ b/protogen/gen/ocis/messages/settings/v0/settings.pb.go
@@ -489,13 +489,13 @@ type Bundle struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Id          string      `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                                                 // @gotags: yaml:"id"
-	Name        string      `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`                                             // @gotags: yaml:"name"
-	Type        Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty"` // @gotags: yaml:"type"
-	Extension   string      `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty"`                                   // @gotags: yaml:"extension"
-	DisplayName string      `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`            // @gotags: yaml:"display_name"
-	Settings    []*Setting  `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty"`                                     // @gotags: yaml:"settings"
-	Resource    *Resource   `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty"`                                     // @gotags: yaml:"resource"
+	Id          string      `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                                                 // @gotags: yaml:"id"
+	Name        string      `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"`                                             // @gotags: yaml:"name"
+	Type        Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty" yaml:"type"` // @gotags: yaml:"type"
+	Extension   string      `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty" yaml:"extension"`                                   // @gotags: yaml:"extension"
+	DisplayName string      `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"`            // @gotags: yaml:"display_name"
+	Settings    []*Setting  `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty" yaml:"settings"`                                     // @gotags: yaml:"settings"
+	Resource    *Resource   `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"`                                     // @gotags: yaml:"resource"
 }
 
 func (x *Bundle) Reset() {
@@ -584,10 +584,10 @@ type Setting struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                                      // @gotags: yaml:"id"
-	Name        string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`                                  // @gotags: yaml:"name"
-	DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name"
-	Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`                    // @gotags: yaml:"description"
+	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                                      // @gotags: yaml:"id"
+	Name        string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"`                                  // @gotags: yaml:"name"
+	DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name"
+	Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty" yaml:"description"`                    // @gotags: yaml:"description"
 	// Types that are assignable to Value:
 	//
 	//	*Setting_IntValue
@@ -597,7 +597,7 @@ type Setting struct {
 	//	*Setting_MultiChoiceValue
 	//	*Setting_PermissionValue
 	Value    isSetting_Value `protobuf_oneof:"value"`
-	Resource *Resource       `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource"
+	Resource *Resource       `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource"
 }
 
 func (x *Setting) Reset() {
@@ -721,27 +721,27 @@ type isSetting_Value interface {
 }
 
 type Setting_IntValue struct {
-	IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 type Setting_StringValue struct {
-	StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type Setting_BoolValue struct {
-	BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
+	BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
 }
 
 type Setting_SingleChoiceValue struct {
-	SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof"` // @gotags: yaml:"single_choice_value"
+	SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof" yaml:"single_choice_value"` // @gotags: yaml:"single_choice_value"
 }
 
 type Setting_MultiChoiceValue struct {
-	MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof"` // @gotags: yaml:"multi_choice_value"
+	MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof" yaml:"multi_choice_value"` // @gotags: yaml:"multi_choice_value"
 }
 
 type Setting_PermissionValue struct {
-	PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof"` // @gotags: yaml:"permission_value"
+	PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof" yaml:"permission_value"` // @gotags: yaml:"permission_value"
 }
 
 func (*Setting_IntValue) isSetting_Value() {}
@@ -761,11 +761,11 @@ type Int struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default     int64  `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"`        // @gotags: yaml:"default"
-	Min         int64  `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty"`                // @gotags: yaml:"min"
-	Max         int64  `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty"`                // @gotags: yaml:"max"
-	Step        int64  `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty"`              // @gotags: yaml:"step"
-	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder"
+	Default     int64  `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`        // @gotags: yaml:"default"
+	Min         int64  `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty" yaml:"min"`                // @gotags: yaml:"min"
+	Max         int64  `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty" yaml:"max"`                // @gotags: yaml:"max"
+	Step        int64  `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty" yaml:"step"`              // @gotags: yaml:"step"
+	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder"
 }
 
 func (x *Int) Reset() {
@@ -840,11 +840,11 @@ type String struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default     string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"`                       // @gotags: yaml:"default"
-	Required    bool   `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty"`                    // @gotags: yaml:"required"
-	MinLength   int32  `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"` // @gotags: yaml:"min_length"
-	MaxLength   int32  `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"` // @gotags: yaml:"max_length"
-	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"`               // @gotags: yaml:"placeholder"
+	Default     string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`                       // @gotags: yaml:"default"
+	Required    bool   `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty" yaml:"required"`                    // @gotags: yaml:"required"
+	MinLength   int32  `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty" yaml:"min_length"` // @gotags: yaml:"min_length"
+	MaxLength   int32  `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty" yaml:"max_length"` // @gotags: yaml:"max_length"
+	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"`               // @gotags: yaml:"placeholder"
 }
 
 func (x *String) Reset() {
@@ -919,8 +919,8 @@ type Bool struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default bool   `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
-	Label   string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`      // @gotags: yaml:"label"
+	Default bool   `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
+	Label   string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty" yaml:"label"`      // @gotags: yaml:"label"
 }
 
 func (x *Bool) Reset() {
@@ -974,7 +974,7 @@ type SingleChoiceList struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
+	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
 }
 
 func (x *SingleChoiceList) Reset() {
@@ -1021,7 +1021,7 @@ type MultiChoiceList struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
+	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
 }
 
 func (x *MultiChoiceList) Reset() {
@@ -1068,9 +1068,9 @@ type ListOption struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Value        *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`                                   // @gotags: yaml:"value"
-	Default      bool             `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty"`                              // @gotags: yaml:"default"
-	DisplayValue string           `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value"
+	Value        *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`                                   // @gotags: yaml:"value"
+	Default      bool             `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`                              // @gotags: yaml:"default"
+	DisplayValue string           `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value"
 }
 
 func (x *ListOption) Reset() {
@@ -1131,8 +1131,8 @@ type Permission struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Operation  Permission_Operation  `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty"`    // @gotags: yaml:"operation"
-	Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty"` // @gotags: yaml:"constraint"
+	Operation  Permission_Operation  `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty" yaml:"operation"`    // @gotags: yaml:"operation"
+	Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty" yaml:"constraint"` // @gotags: yaml:"constraint"
 }
 
 func (x *Permission) Reset() {
@@ -1187,12 +1187,12 @@ type Value struct {
 	unknownFields protoimpl.UnknownFields
 
 	// id is the id of the Value. It is generated on saving it.
-	Id       string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                             // @gotags: yaml:"id"
-	BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` // @gotags: yaml:"bundle_id"
+	Id       string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                             // @gotags: yaml:"id"
+	BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty" yaml:"bundle_id"` // @gotags: yaml:"bundle_id"
 	// setting_id is the id of the setting from within its bundle.
-	SettingId   string    `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty"`       // @gotags: yaml:"setting_id"
-	AccountUuid string    `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty"` // @gotags: yaml:"account_uuid"
-	Resource    *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"`                          // @gotags: yaml:"resource"
+	SettingId   string    `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty" yaml:"setting_id"`       // @gotags: yaml:"setting_id"
+	AccountUuid string    `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty" yaml:"account_uuid"` // @gotags: yaml:"account_uuid"
+	Resource    *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"`                          // @gotags: yaml:"resource"
 	// Types that are assignable to Value:
 	//
 	//	*Value_BoolValue
@@ -1309,19 +1309,19 @@ type isValue_Value interface {
 }
 
 type Value_BoolValue struct {
-	BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
+	BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
 }
 
 type Value_IntValue struct {
-	IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 type Value_StringValue struct {
-	StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type Value_ListValue struct {
-	ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof"` // @gotags: yaml:"list_value"
+	ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof" yaml:"list_value"` // @gotags: yaml:"list_value"
 }
 
 func (*Value_BoolValue) isValue_Value() {}
@@ -1337,7 +1337,7 @@ type ListValue struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values"
+	Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values"
 }
 
 func (x *ListValue) Reset() {
@@ -1449,11 +1449,11 @@ type isListOptionValue_Option interface {
 }
 
 type ListOptionValue_StringValue struct {
-	StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type ListOptionValue_IntValue struct {
-	IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 func (*ListOptionValue_StringValue) isListOptionValue_Option() {}
diff --git a/protogen/gen/ocis/services/search/v0/search.swagger.json b/protogen/gen/ocis/services/search/v0/search.swagger.json
index 6fce96faa1f..d84ac28f330 100644
--- a/protogen/gen/ocis/services/search/v0/search.swagger.json
+++ b/protogen/gen/ocis/services/search/v0/search.swagger.json
@@ -270,6 +270,26 @@
         },
         "audio": {
           "$ref": "#/definitions/v0Audio"
+        },
+        "location": {
+          "$ref": "#/definitions/v0GeoCoordinates"
+        }
+      }
+    },
+    "v0GeoCoordinates": {
+      "type": "object",
+      "properties": {
+        "altitude": {
+          "type": "number",
+          "format": "double"
+        },
+        "latitude": {
+          "type": "number",
+          "format": "double"
+        },
+        "longitude": {
+          "type": "number",
+          "format": "double"
         }
       }
     },
diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
index 1b0aa92c6d3..5d03a85fad6 100644
--- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
+++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
@@ -36,7 +36,7 @@ type GetThumbnailRequest struct {
 	Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"`
 	// The height of the thumbnail
 	Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"`
-	// Indicates which processor should be used
+	// Indicates which image processor to use
 	Processor string `protobuf:"bytes,5,opt,name=processor,proto3" json:"processor,omitempty"`
 	// Types that are assignable to Source:
 	//
diff --git a/protogen/proto/ocis/messages/search/v0/search.proto b/protogen/proto/ocis/messages/search/v0/search.proto
index 034d7f85e4b..84b2b9c2504 100644
--- a/protogen/proto/ocis/messages/search/v0/search.proto
+++ b/protogen/proto/ocis/messages/search/v0/search.proto
@@ -36,6 +36,12 @@ message Audio {
 	optional int32 year = 16;
 }
 
+message GeoCoordinates {
+	optional double altitude  = 1;
+	optional double latitude = 2;
+	optional double longitude = 3;
+}
+
 message Entity {
 	Reference ref = 1;
 	ResourceID id = 2;
@@ -52,6 +58,7 @@ message Entity {
 	repeated string tags = 13;
 	string highlights = 14;
 	Audio audio = 15;
+	GeoCoordinates location = 16;
 }
 
 message Match {
diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go
index 78243d9802a..38e960204ea 100644
--- a/services/graph/pkg/service/v0/driveitems.go
+++ b/services/graph/pkg/service/v0/driveitems.go
@@ -870,7 +870,10 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf
 		driveItem.Folder = &libregraph.Folder{}
 	}
 
-	driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
+	if res.ArbitraryMetadata != nil {
+		driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
+		driveItem.Location = cs3ResourceToDriveItemLocationFacet(logger, res)
+	}
 
 	return driveItem, nil
 }
@@ -893,6 +896,20 @@ func cs3ResourceToDriveItemAudioFacet(logger *log.Logger, res *storageprovider.R
 	return nil
 }
 
+func cs3ResourceToDriveItemLocationFacet(logger *log.Logger, res *storageprovider.ResourceInfo) *libregraph.GeoCoordinates {
+	k := res.ArbitraryMetadata.Metadata
+	if k == nil {
+		return nil
+	}
+
+	var location = &libregraph.GeoCoordinates{}
+	if ok := unmarshalStringMap(logger, location, k, "libre.graph.location."); ok {
+		return location
+	}
+
+	return nil
+}
+
 func getFieldName(structField reflect.StructField) string {
 	tag := structField.Tag.Get("json")
 	if tag == "" {
@@ -922,6 +939,10 @@ func unmarshalStringMap(logger *log.Logger, out any, flatMap map[string]string,
 					tmp, err = strconv.ParseInt(value, 10, 32)
 				case reflect.Int64:
 					tmp, err = strconv.ParseInt(value, 10, 64)
+				case reflect.Float32:
+					tmp, err = strconv.ParseFloat(value, 32)
+				case reflect.Float64:
+					tmp, err = strconv.ParseFloat(value, 64)
 				case reflect.Bool:
 					tmp, err = strconv.ParseBool(value)
 				default:
diff --git a/services/search/pkg/content/content.go b/services/search/pkg/content/content.go
index fccd8062896..ba61befaf40 100644
--- a/services/search/pkg/content/content.go
+++ b/services/search/pkg/content/content.go
@@ -21,7 +21,8 @@ type Document struct {
 	Mtime    string
 	MimeType string
 	Tags     []string
-	Audio    *libregraph.Audio `json:"audio,omitempty"`
+	Audio    *libregraph.Audio          `json:"audio,omitempty"`
+	Location *libregraph.GeoCoordinates `json:"location,omitempty"`
 }
 
 func CleanString(content, langCode string) string {
diff --git a/services/search/pkg/content/tika.go b/services/search/pkg/content/tika.go
index b169de8fd88..d4d0b53f447 100644
--- a/services/search/pkg/content/tika.go
+++ b/services/search/pkg/content/tika.go
@@ -89,72 +89,121 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document,
 			doc.Content = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Content, content))
 		}
 
+		doc.Location = t.getLocation(meta)
+
 		if contentType, err := getFirstValue(meta, "Content-Type"); err == nil && strings.HasPrefix(contentType, "audio/") {
-			audio := libregraph.Audio{}
+			doc.Audio = t.getAudio(meta)
+		}
+	}
+
+	if langCode, _ := t.tika.LanguageString(ctx, doc.Content); langCode != "" && t.CleanStopWords {
+		doc.Content = CleanString(doc.Content, langCode)
+	}
+
+	return doc, nil
+}
+
+func (t Tika) getLocation(meta map[string][]string) *libregraph.GeoCoordinates {
+	var location *libregraph.GeoCoordinates
+	initLocation := func() {
+		if location == nil {
+			location = libregraph.NewGeoCoordinates()
+		}
+	}
+
+	// TODO: location.Altitute: transform the following data to … feet above sea level.
+	// "GPS:GPS Altitude":                          []string{"227.4 metres"},
+	// "GPS:GPS Altitude Ref":                      []string{"Sea level"},
 
-			if v, err := getFirstValue(meta, "xmpDM:album"); err == nil {
-				audio.SetAlbum(v)
-			}
+	if v, err := getFirstValue(meta, "geo:lat"); err == nil {
+		if i, err := strconv.ParseFloat(v, 64); err == nil {
+			initLocation()
+			location.SetLatitude(i)
+		}
+	}
+
+	if v, err := getFirstValue(meta, "geo:long"); err == nil {
+		if i, err := strconv.ParseFloat(v, 64); err == nil {
+			initLocation()
+			location.SetLongitude(i)
+		}
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:albumArtist"); err == nil {
-				audio.SetAlbumArtist(v)
-			}
+	return location
+}
 
-			if v, err := getFirstValue(meta, "xmpDM:artist"); err == nil {
-				audio.SetArtist(v)
-			}
+func (t Tika) getAudio(meta map[string][]string) *libregraph.Audio {
+	var audio *libregraph.Audio
+	initAudio := func() {
+		if audio == nil {
+			audio = libregraph.NewAudio()
+		}
+	}
 
-			// TODO: audio.Bitrate: not provided by tika
-			// TODO: audio.Composers: not provided by tika
-			// TODO: audio.Copyright: not provided by tika for audio files?
+	if v, err := getFirstValue(meta, "xmpDM:album"); err == nil {
+		initAudio()
+		audio.SetAlbum(v)
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:discNumber"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetDisc(int32(i))
-				}
+	if v, err := getFirstValue(meta, "xmpDM:albumArtist"); err == nil {
+		initAudio()
+		audio.SetAlbumArtist(v)
+	}
 
-			}
+	if v, err := getFirstValue(meta, "xmpDM:artist"); err == nil {
+		initAudio()
+		audio.SetArtist(v)
+	}
 
-			//  TODO: audio.DiscCount: not provided by tika
+	// TODO: audio.Bitrate: not provided by tika
+	// TODO: audio.Composers: not provided by tika
+	// TODO: audio.Copyright: not provided by tika for audio files?
 
-			if v, err := getFirstValue(meta, "xmpDM:duration"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 64); err == nil {
-					audio.SetDuration(i * 1000)
-				}
-			}
+	if v, err := getFirstValue(meta, "xmpDM:discNumber"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetDisc(int32(i))
+		}
 
-			if v, err := getFirstValue(meta, "xmpDM:genre"); err == nil {
-				audio.SetGenre(v)
-			}
+	}
 
-			// TODO: audio.HasDrm: not provided by tika
-			// TODO: audio.IsVariableBitrate: not provided by tika
+	//  TODO: audio.DiscCount: not provided by tika
 
-			if v, err := getFirstValue(meta, "dc:title"); err == nil {
-				audio.SetTitle(v)
-			}
+	if v, err := getFirstValue(meta, "xmpDM:duration"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 64); err == nil {
+			initAudio()
+			audio.SetDuration(i * 1000)
+		}
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:trackNumber"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetTrack(int32(i))
-				}
-			}
+	if v, err := getFirstValue(meta, "xmpDM:genre"); err == nil {
+		initAudio()
+		audio.SetGenre(v)
+	}
 
-			// TODO: audio.TrackCount: not provided by tika
+	// TODO: audio.HasDrm: not provided by tika
+	// TODO: audio.IsVariableBitrate: not provided by tika
 
-			if v, err := getFirstValue(meta, "xmpDM:releaseDate"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetYear(int32(i))
-				}
-			}
+	if v, err := getFirstValue(meta, "dc:title"); err == nil {
+		initAudio()
+		audio.SetTitle(v)
+	}
 
-			doc.Audio = &audio
+	if v, err := getFirstValue(meta, "xmpDM:trackNumber"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetTrack(int32(i))
 		}
 	}
 
-	if langCode, _ := t.tika.LanguageString(ctx, doc.Content); langCode != "" && t.CleanStopWords {
-		doc.Content = CleanString(doc.Content, langCode)
+	// TODO: audio.TrackCount: not provided by tika
+
+	if v, err := getFirstValue(meta, "xmpDM:releaseDate"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetYear(int32(i))
+		}
 	}
 
-	return doc, nil
+	return audio
 }
diff --git a/services/search/pkg/engine/bleve.go b/services/search/pkg/engine/bleve.go
index 8e1b6efedbb..a0b3f26f30c 100644
--- a/services/search/pkg/engine/bleve.go
+++ b/services/search/pkg/engine/bleve.go
@@ -211,6 +211,7 @@ func (b *Bleve) Search(ctx context.Context, sir *searchService.SearchIndexReques
 				Tags:       getFieldSliceValue[string](hit.Fields, "Tags"),
 				Highlights: getFragmentValue(hit.Fragments, "Content", 0),
 				Audio:      getAudioValue[searchMessage.Audio](hit.Fields),
+				Location:   getLocationValue[searchMessage.GeoCoordinates](hit.Fields),
 			},
 		}
 
@@ -329,6 +330,7 @@ func (b *Bleve) getResource(id string) (*Resource, error) {
 			Content:  getFieldValue[string](fields, "Content"),
 			Tags:     getFieldSliceValue[string](fields, "Tags"),
 			Audio:    getAudioValue[libregraph.Audio](fields),
+			Location: getLocationValue[libregraph.GeoCoordinates](fields),
 		},
 	}, nil
 }
@@ -382,6 +384,15 @@ func getAudioValue[T any](fields map[string]interface{}) *T {
 	return nil
 }
 
+func getLocationValue[T any](fields map[string]interface{}) *T {
+	var location = newPointerOfType[T]()
+	if ok := unmarshalInterfaceMap(location, fields, "location."); ok {
+		return location
+	}
+
+	return nil
+}
+
 func (b *Bleve) updateEntity(id string, mutateFunc func(r *Resource)) (*Resource, error) {
 	it, err := b.getResource(id)
 	if err != nil {
diff --git a/services/search/pkg/search/service.go b/services/search/pkg/search/service.go
index 199b3df31e3..7561e880fe0 100644
--- a/services/search/pkg/search/service.go
+++ b/services/search/pkg/search/service.go
@@ -494,6 +494,7 @@ func (s *Service) UpsertItem(ref *provider.Reference, uID *user.UserId) {
 	// determine if metadata needs to be stored in storage as well
 	metadata := map[string]string{}
 	addAudioMetadata(metadata, doc.Audio)
+	addLocationMetadata(metadata, doc.Location)
 	if len(metadata) == 0 {
 		return
 	}
@@ -525,6 +526,13 @@ func addAudioMetadata(metadata map[string]string, audio *libregraph.Audio) {
 	marshalToStringMap(audio, metadata, "libre.graph.audio.")
 }
 
+func addLocationMetadata(metadata map[string]string, location *libregraph.GeoCoordinates) {
+	if location == nil {
+		return
+	}
+	marshalToStringMap(location, metadata, "libre.graph.location.")
+}
+
 func marshalToStringMap[T libregraph.MappedNullable](source T, target map[string]string, prefix string) {
 	// ToMap never returns a non-nil error ...
 	m, _ := source.ToMap()
@@ -549,6 +557,10 @@ func valueToString(value interface{}) string {
 		return strconv.FormatInt(int64(*v), 10)
 	case *int64:
 		return strconv.FormatInt(*v, 10)
+	case *float32:
+		return strconv.FormatFloat(float64(*v), 'f', -1, 32)
+	case *float64:
+		return strconv.FormatFloat(*v, 'f', -1, 64)
 	case *bool:
 		return strconv.FormatBool(*v)
 	default:


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 0eab263318f..e3eb2cb2562 100644
--- a/services/graph/pkg/service/v0/driveitems_test.go
+++ b/services/graph/pkg/service/v0/driveitems_test.go
@@ -759,6 +759,7 @@ var _ = Describe("Driveitems", func() {
 
 				res := assertItemsList(1)
 				Expect(res.Value[0].Audio).To(BeNil())
+				Expect(res.Value[0].Location).To(BeNil())
 			})
 
 			It("returns the audio facet if metadata is available", func() {
@@ -816,6 +817,36 @@ var _ = Describe("Driveitems", func() {
 				Expect(audio.TrackCount).To(Equal(libregraph.PtrInt32(9)))
 				Expect(audio.Year).To(Equal(libregraph.PtrInt32(1994)))
 			})
+
+			It("returns the location facet if metadata is available", func() {
+				gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+					Status: status.NewOK(ctx),
+					Infos: []*provider.ResourceInfo{
+						{
+							Type:     provider.ResourceType_RESOURCE_TYPE_FILE,
+							Id:       &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
+							Etag:     "etag",
+							Mtime:    utils.TimeToTS(mtime),
+							MimeType: "image/jpeg",
+							ArbitraryMetadata: &provider.ArbitraryMetadata{
+								Metadata: map[string]string{
+									"libre.graph.location.altitude":  "1047.7",
+									"libre.graph.location.latitude":  "49.48675890884328",
+									"libre.graph.location.longitude": "11.103870357204285",
+								},
+							},
+						},
+					},
+				}, nil)
+
+				res := assertItemsList(1)
+				location := res.Value[0].Location
+
+				Expect(location).ToNot(BeNil())
+				Expect(location.Altitude).To(Equal(libregraph.PtrFloat64(1047.7)))
+				Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+				Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+			})
 		})
 	})
 })
diff --git a/services/search/pkg/content/tika_test.go b/services/search/pkg/content/tika_test.go
index 497247cac8d..8a1c7699453 100644
--- a/services/search/pkg/content/tika_test.go
+++ b/services/search/pkg/content/tika_test.go
@@ -140,6 +140,28 @@ var _ = Describe("Tika", func() {
 
 		})
 
+		It("adds location content", func() {
+			fullResponse = `[
+				{
+					"geo:lat": "49.48675890884328",
+					"geo:long": "11.103870357204285"
+				}
+			]`
+			doc, err := tika.Extract(context.TODO(), &provider.ResourceInfo{
+				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
+				Size: 1,
+			})
+			Expect(err).ToNot(HaveOccurred())
+
+			location := doc.Location
+			Expect(location).ToNot(BeNil())
+
+			// TODO: Altitude is not supported right now
+			Expect(location.Altitude).To(BeNil())
+			Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+			Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+		})
+
 		It("removes stop words", func() {
 			body = "body to test stop words!!! against almost everyone"
 			language = "en"
diff --git a/services/search/pkg/engine/bleve_test.go b/services/search/pkg/engine/bleve_test.go
index 8b9668faf47..1023ee8da0c 100644
--- a/services/search/pkg/engine/bleve_test.go
+++ b/services/search/pkg/engine/bleve_test.go
@@ -548,5 +548,38 @@ var _ = Describe("Bleve", func() {
 			})
 		})
 
+		Context("with location metadata", func() {
+			BeforeEach(func() {
+				resource := engine.Resource{
+					ID:       "1$2!7",
+					ParentID: rootResource.ID,
+					RootID:   rootResource.ID,
+					Path:     "./team.jpg",
+					Type:     uint64(sprovider.ResourceType_RESOURCE_TYPE_FILE),
+					Document: content.Document{
+						Name:     "team.jpg",
+						MimeType: "image/jpeg",
+						Location: &libregraph.GeoCoordinates{
+							Altitude:  libregraph.PtrFloat64(1047.7),
+							Latitude:  libregraph.PtrFloat64(49.48675890884328),
+							Longitude: libregraph.PtrFloat64(11.103870357204285),
+						},
+					},
+				}
+				err := eng.Upsert(resource.ID, resource)
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+			It("returns audio metadata for search", func() {
+				matches := assertDocCount(rootResource.ID, `*team*`, 1)
+				location := matches[0].Entity.Location
+
+				Expect(location).ToNot(BeNil())
+
+				Expect(location.Altitude).To(Equal(libregraph.PtrFloat64(1047.7)))
+				Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+				Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+			})
+		})
 	})
 })