LocationNameResolver.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Niklas Arlt ( niklas dot arlt at kit dot edu )
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
23
24#include <map>
25#include <sstream>
26#include <vector>
27
28#include <SimoxUtility/algorithm/string/string_tools.h>
29
33
37
39
40#include <range/v3/algorithm/contains.hpp>
41#include <range/v3/range/conversion.hpp>
42#include <range/v3/view/concat.hpp>
43#include <range/v3/view/filter.hpp>
44#include <range/v3/view/map.hpp>
45#include <range/v3/view/transform.hpp>
46
48{
49 namespace
50 {
51
52 // Try to match the requested name to an object. If there is a (perfect) match, we check
53 // whether there is a location associated with the object (`inFrontOf`).
54 std::optional<ResolvedLocation>
55 resolveFromObject(const std::string& locationName,
56 const LocationNameResolverServices& services)
57 {
58 // query object poses from memory
59 const auto objectPoseStorage = services.objectPoseClient.getObjectPoseStorage();
60 const auto objectPosesIce = objectPoseStorage->getObjectPoses();
61
62 const std::map<armarx::ObjectID, armem::clazz::ObjectClass> objectClasses =
63 services.objectClassReader->getAllObjectClasses();
64
65 objpose::ObjectPoseSeq objectPoses;
66 objpose::fromIce(objectPosesIce, objectPoses);
67
68 // search for matching objects (helper lambda)
69 const auto isMatchingObject = [&](const objpose::ObjectPose& pose) -> bool
70 {
71 // make sure that the classes exist.
72 if (not ranges::contains(ranges::views::keys(objectClasses),
73 pose.objectID.getClassID(),
74 [](const armarx::ObjectID& oid)
75 { return oid.getClassID(); }))
76 {
77 ARMARX_VERBOSE << "Requested to match object but there is no match";
78 return false;
79 }
80
81 const auto& objectClass = objectClasses.at(pose.objectID.getClassID());
82 const auto& recognizedNames = objectClass.names.recognized;
83
84 // perform case-insensitive comparison
85 return ranges::contains(recognizedNames,
86 simox::alg::to_lower(locationName),
87 simox::alg::to_lower);
88 };
89
90 // find all matching objects
91 const std::vector<objpose::ObjectPose> matchingObjects =
92 objectPoses | ranges::views::filter(isMatchingObject) | ranges::to<std::vector>();
93
94 ARMARX_INFO << "Found " << matchingObjects.size() << " objects matching location name '"
95 << locationName << "'.";
96
97 const auto locations = services.locationReader->locations();
98
99 const auto getMatchingLocation =
100 [&](const objpose::ObjectPose& pose) -> std::vector<core::Location>
101 {
102 const std::string inFrontOfName = pose.objectID.getClassID().str() + "/inFrontOf";
103 const std::string inFrontOfNameWithInstance =
104 pose.objectID.getClassID().str() + "/inFrontOf:" + pose.objectID.instanceName();
105
106 const auto matchingLocations =
107 core::util::findMatchingLocations(locations, inFrontOfName, std::nullopt);
108 ARMARX_INFO << "Found " << matchingLocations.size() << " locations matching name "
109 << QUOTED(inFrontOfName) << ".";
110
111 const auto matchingLocationsWithInstance = core::util::findMatchingLocations(
112 locations, inFrontOfNameWithInstance, std::nullopt);
113 ARMARX_INFO << "Found " << matchingLocationsWithInstance.size()
114 << " locations matching name " << QUOTED(inFrontOfNameWithInstance)
115 << ".";
116
117 const auto appendLocationInstanceName = [&pose](core::Location loc) -> core::Location
118 {
119 loc.name += ":" + pose.objectID.instanceName();
120 return loc;
121 };
122
123 // For those matching locations on class level, append the instance name
124 const auto matchingLocationsWithAppendedInstance =
125 matchingLocations | ranges::views::transform(appendLocationInstanceName) |
126 ranges::to<std::vector>();
127
128 return ranges::views::concat(matchingLocationsWithAppendedInstance,
129 matchingLocationsWithInstance) |
130 ranges::to<std::vector>();
131 };
132
133 // obtain all locations associated with matching objects
134 std::vector<core::Location> associatedLocations;
135 for (const auto& matchingObject : matchingObjects)
136 {
137 const auto locationsForObject = getMatchingLocation(matchingObject);
138 for (const core::Location& loc : locationsForObject)
139 {
140 ARMARX_INFO << "Found associated location " << QUOTED(loc.name)
141 << " for matching object " << QUOTED(matchingObject.objectID.str())
142 << ".";
143 associatedLocations.push_back(loc);
144 }
145 }
146
147 ARMARX_INFO << "Found total of " << associatedLocations.size()
148 << " associated locations for matching objects.";
149
150 for (const auto& loc : associatedLocations)
151 {
152 ARMARX_INFO << "- " << loc.name;
153 }
154
155 if (associatedLocations.empty())
156 {
157 return std::nullopt;
158 }
159
160 if (associatedLocations.size() > 1)
161 {
162 ARMARX_WARNING << "Multiple associated locations found for matching objects. Using "
163 "the first one.";
164 }
165
166 const auto& firstLocation = *associatedLocations.begin();
167
168 return ResolvedLocation{.name = firstLocation.name, .provider = firstLocation.provider};
169 }
170
171 // Try to match the requested name to a named location in the location graph.
172 std::optional<ResolvedLocation>
173 resolveFromKnownLocations(const std::string& locationName,
174 const LocationNameResolverServices& services,
175 double minMatchRatio)
176 {
177 std::stringstream log;
178 auto matchResult =
179 services.locationReader->resolveLocationName(locationName, minMatchRatio, &log);
180
181 ARMARX_INFO << log.str();
182 ARMARX_CHECK(matchResult.resolved.has_value() xor matchResult.errorMessage.has_value());
183
184 if (matchResult.errorMessage.has_value())
185 {
186 ARMARX_INFO << "Failed to resolve location name '" << locationName
187 << "'. Reason: " << matchResult.errorMessage.value();
188 return std::nullopt;
189 }
190
191 ARMARX_CHECK(matchResult.resolved.has_value());
192 return ResolvedLocation{.name = matchResult.resolved->locationId.entityName,
193 .provider =
194 matchResult.resolved->locationId.providerSegmentName};
195 }
196
197 } // namespace
198
199 std::optional<ResolvedLocation>
200 resolveLocationName(const std::string& locationName,
201 const LocationNameResolverServices& services,
202 const double minMatchRatio,
203 const bool useObjectLocations)
204 {
207
208 if (useObjectLocations)
209 {
210 if (const auto resolved = resolveFromObject(locationName, services))
211 {
212 ARMARX_INFO << "Resolved location name '" << locationName
213 << "' to location associated with matching object.";
214 return resolved;
215 }
216 }
217
218 if (const auto resolved =
219 resolveFromKnownLocations(locationName, services, minMatchRatio))
220 {
221 ARMARX_INFO << "Resolved location name '" << locationName << "' to known location: "
222 << QUOTED(resolved->provider + "::" + resolved->name) << ".";
223 return resolved;
224 }
225
226 return std::nullopt;
227 }
228
229} // namespace armarx::navigation::skills
#define QUOTED(x)
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
std::vector< core::Location > findMatchingLocations(const std::vector< core::Location > &locations, const std::string &locationName, const std::optional< std::string > &provider)
Definition location.cpp:46
std::optional< ResolvedLocation > resolveLocationName(const std::string &locationName, const LocationNameResolverServices &services, const double minMatchRatio, const bool useObjectLocations)
Resolve a human-readable place onto a location, the way NavigateToNamedLocation does.
std::vector< ObjectPose > ObjectPoseSeq
void fromIce(const Box &box, simox::OrientedBox< float > &oobb)
Everything needed to turn a spoken place into a location.
armarx::armem::obj::clazz::ClassReader * objectClassReader
A location in the navigation memory, identified the way the memory identifies it.