GNSS: Places, kept as GPX waypoints

A Place is a named position the user marked (issue #16). The list is
read from and written to a GPX file, so a map program opens it and
waypoints made by one are read here. Host-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-11 17:01:57 +02:00
co-authored by Claude Opus 5.5
parent da311a11b6
commit a87c3016cc
3 changed files with 201 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
#include "places.h"
#include <cstdio>
#include <cstdlib>
namespace roro::gnss {
namespace {
struct Entity {
const char* text;
char ch;
};
constexpr Entity kEntities[] = {{"&amp;", '&'}, {"&lt;", '<'}, {"&gt;", '>'}, {"&quot;", '"'}, {"&apos;", '\''}};
std::string escaped(const std::string& s) {
std::string out;
for (char c : s) {
const char* entity = nullptr;
for (auto& e : kEntities)
if (e.ch == c) entity = e.text;
if (entity) out += entity;
else out += c;
}
return out;
}
std::string unescaped(const std::string& s) {
std::string out;
for (size_t i = 0; i < s.size();) {
bool found = false;
if (s[i] == '&')
for (auto& e : kEntities) {
std::string text = e.text;
if (s.compare(i, text.size(), text) == 0) {
out += e.ch;
i += text.size();
found = true;
break;
}
}
if (!found) out += s[i++];
}
return out;
}
// The value of ` name="..."` in a tag; false if it isn't there or isn't a number.
bool attribute(const std::string& tag, const char* name, double& value) {
std::string key = std::string(" ") + name + "=";
size_t at = tag.find(key);
if (at == std::string::npos || at + key.size() >= tag.size()) return false;
at += key.size();
char quote = tag[at];
if (quote != '"' && quote != '\'') return false;
size_t end = tag.find(quote, at + 1);
if (end == std::string::npos) return false;
std::string text = tag.substr(at + 1, end - at - 1);
char* rest = nullptr;
value = std::strtod(text.c_str(), &rest);
return !text.empty() && rest && *rest == 0;
}
} // namespace
std::vector<Place> parsePlaces(const std::string& gpx) {
std::vector<Place> places;
size_t at = 0;
while (places.size() < kMaxPlaces && (at = gpx.find("<wpt", at)) != std::string::npos) {
size_t tagEnd = gpx.find('>', at);
if (tagEnd == std::string::npos) break;
std::string tag = gpx.substr(at, tagEnd - at);
bool alone = !tag.empty() && tag.back() == '/'; // <wpt .../>: no name
size_t close = alone ? tagEnd : gpx.find("</wpt>", tagEnd);
if (close == std::string::npos) break;
Place p;
if (attribute(tag, "lat", p.latitude) && attribute(tag, "lon", p.longitude) && p.latitude >= -90 &&
p.latitude <= 90 && p.longitude >= -180 && p.longitude <= 180) {
size_t name = alone ? std::string::npos : gpx.find("<name>", tagEnd);
if (name != std::string::npos && name < close) {
size_t nameEnd = gpx.find("</name>", name);
if (nameEnd != std::string::npos && nameEnd < close) p.name = unescaped(gpx.substr(name + 6, nameEnd - name - 6));
}
if (p.name.empty()) p.name = "Place " + std::to_string(places.size() + 1);
places.push_back(p);
}
at = close + 1;
}
return places;
}
std::string placesGpx(const std::vector<Place>& places) {
std::string out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<gpx version=\"1.1\" creator=\"roro9stack\" xmlns=\"http://www.topografix.com/GPX/1/1\">\n";
for (auto& p : places) {
char buf[80];
std::snprintf(buf, sizeof buf, "<wpt lat=\"%.7f\" lon=\"%.7f\"><name>", p.latitude, p.longitude);
out += buf + escaped(p.name) + "</name></wpt>\n";
}
return out + "</gpx>\n";
}
} // namespace roro::gnss
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstddef>
#include <string>
#include <vector>
namespace roro::gnss {
// A Place (issue #16): somewhere the user marked, to be led back to by the Compass.
struct Place {
std::string name;
double latitude = 0, longitude = 0;
};
constexpr const char* kPlacesPath = "/gnss/places.gpx";
constexpr size_t kMaxPlaces = 50;
constexpr size_t kPlaceNameBytes = 24;
// The Places are kept as GPX waypoints, so a map program opens the file, and a file made by one
// is read here: every <wpt> with a latitude and a longitude, named by its <name>.
std::vector<Place> parsePlaces(const std::string& gpx);
std::string placesGpx(const std::vector<Place>& places);
} // namespace roro::gnss