WIP: GPX Support #4067

Closed
dvdmrtnz wants to merge 6 commits from gpx into master
12 changed files with 504 additions and 3 deletions

View file

@ -588,6 +588,18 @@
<data android:mimeType="application/vnd.google-earth.kmz"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content"/>
<data android:scheme="file"/>
<data android:scheme="data"/>
<data android:host="*"/>
<data android:mimeType="*/*"/>
<data android:pathPattern=".*\\.gpx" />
biodranik commented 2023-01-28 15:27:52 +00:00 (Migrated from github.com)
Review

OSMand uses:

			<intent-filter
				android:label="@string/app_name"
				android:priority="50">
				<action android:name="android.intent.action.VIEW" />
				<category android:name="android.intent.category.DEFAULT" />
				<category android:name="android.intent.category.BROWSABLE" />
				<data android:scheme="file"/>
				<data android:scheme="content"/>
				<data android:host="*"/>
				<data android:mimeType="*/*"/>
				<data android:pathPattern=".*\\.gpx" />
				<data android:pathPattern=".*\\..*\\.gpx" />
				<data android:pathPattern=".*\\..*\\..*\\.gpx" />
				<data android:pathPattern=".*\\..*\\..*\\..*\\.gpx" />
				<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpx" />
				<data android:pathPattern=".*\\.gpx.zip" />
				<data android:pathPattern=".*\\..*\\.gpx.zip" />
				<data android:pathPattern=".*\\..*\\..*\\.gpx.zip" />
				<data android:pathPattern=".*\\..*\\..*\\..*\\.gpx.zip" />
				<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpx.zip" />
			</intent-filter>
OSMand uses: ``` <intent-filter android:label="@string/app_name" android:priority="50"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="file"/> <data android:scheme="content"/> <data android:host="*"/> <data android:mimeType="*/*"/> <data android:pathPattern=".*\\.gpx" /> <data android:pathPattern=".*\\..*\\.gpx" /> <data android:pathPattern=".*\\..*\\..*\\.gpx" /> <data android:pathPattern=".*\\..*\\..*\\..*\\.gpx" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpx" /> <data android:pathPattern=".*\\.gpx.zip" /> <data android:pathPattern=".*\\..*\\.gpx.zip" /> <data android:pathPattern=".*\\..*\\..*\\.gpx.zip" /> <data android:pathPattern=".*\\..*\\..*\\..*\\.gpx.zip" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpx.zip" /> </intent-filter> ```
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>

View file

@ -1083,7 +1083,7 @@ Java_app_organicmaps_Framework_nativeGetMovableFilesExts(JNIEnv * env, jclass)
JNIEXPORT jobjectArray JNICALL
Java_app_organicmaps_Framework_nativeGetBookmarksFilesExts(JNIEnv * env, jclass)
{
const vector<string> exts = { kKmzExtension, kKmlExtension, kKmbExtension };
const vector<string> exts = { kKmzExtension, kKmlExtension, kKmbExtension, kGpxExtension };
return jni::ToJavaStringArray(env, exts);
}

View file

@ -8,6 +8,18 @@
<string>${PRODUCT_NAME}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>GPS Exchange Format (GPX)</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.topografix.gpx</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array>

View file

@ -7,6 +7,8 @@ set(SRC
serdes.hpp
serdes_binary.cpp
serdes_binary.hpp
serdes_gpx.cpp
serdes_gpx.hpp
type_utils.cpp
type_utils.hpp
types.cpp

View file

@ -4,6 +4,7 @@ set(SRC
serdes_tests.cpp
tests_data.hpp
minzoom_quadtree_tests.cpp
gpx_tests.cpp
)
omim_add_test(${PROJECT_NAME} ${SRC})

View file

@ -0,0 +1,43 @@
#include "testing/testing.hpp"
#include "kml/serdes_gpx.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "geometry/mercator.hpp"
char const * kTextGpx =
R"(<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0">
<wpt lat="42.81025" lon="-1.65727">
<time>2022-09-05T08:39:39.3700Z</time>
<name>Waypoint 1</name>
</wpt>
)";
auto const kDefaultLang = StringUtf8Multilang::kDefaultCode;
UNIT_TEST(Gpx_Test)
{
kml::FileData dataFromText;
try
{
kml::DeserializerGpx des(dataFromText);
MemReader reader(kTextGpx, strlen(kTextGpx));
des.Deserialize(reader);
}
catch (kml::DeserializerGpx::DeserializeException const & exc)
{
TEST(false, ("Exception raised", exc.what()));
}
kml::FileData data;
kml::BookmarkData bookmarkData;
bookmarkData.m_name[kDefaultLang] = "Waypoint 1";
bookmarkData.m_point = mercator::FromLatLon(42.81025, -1.65727);
data.m_bookmarksData.emplace_back(std::move(bookmarkData));
TEST_EQUAL(dataFromText, data, ());
}

273
kml/serdes_gpx.cpp Normal file
View file

@ -0,0 +1,273 @@
#include "kml/serdes_gpx.hpp"
#include "indexer/classificator.hpp"
#include "coding/hex.hpp"
#include "coding/point_coding.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "geometry/mercator.hpp"
#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include "base/timer.hpp"
#include <sstream>
using namespace std::string_literals;
namespace kml
{
namespace gpx
{
auto const kDefaultLang = StringUtf8Multilang::kDefaultCode;
auto const kDefaultTrackWidth = 5.0;
std::string PointToString(m2::PointD const & org)
{
double const lon = mercator::XToLon(org.x);
double const lat = mercator::YToLat(org.y);
std::ostringstream ss;
ss.precision(8);
ss << lon << "," << lat;
return ss.str();
}
}
GpxParser::GpxParser(FileData & data)
: m_data(data)
, m_categoryData(&m_data.m_categoryData)
, m_attrCode(StringUtf8Multilang::kUnsupportedLanguageCode)
{
ResetPoint();
}
void GpxParser::ResetPoint()
{
m_name.clear();
m_description.clear();
m_org = {};
m_predefinedColor = PredefinedColor::None;
m_viewportScale = 0;
m_timestamp = {};
m_color = 0;
m_styleId.clear();
m_mapStyleId.clear();
m_styleUrlKey.clear();
m_featureTypes.clear();
m_customName.clear();
m_boundTracks.clear();
m_visible = true;
m_nearestToponym.clear();
m_nearestToponyms.clear();
m_properties.clear();
m_localId = 0;
m_trackLayers.clear();
m_trackWidth = gpx::kDefaultTrackWidth;
m_icon = BookmarkIcon::None;
m_geometry.Clear();
m_geometryType = GEOMETRY_TYPE_UNKNOWN;
}
bool GpxParser::MakeValid()
{
if (GEOMETRY_TYPE_POINT == m_geometryType)
{
if (mercator::ValidX(m_org.x) && mercator::ValidY(m_org.y))
{
// Set default name.
if (m_name.empty() && m_featureTypes.empty())
m_name[gpx::kDefaultLang] = gpx::PointToString(m_org);
// Set default pin.
if (m_predefinedColor == PredefinedColor::None)
m_predefinedColor = PredefinedColor::Red;
return true;
}
return false;
}
else if (GEOMETRY_TYPE_LINE == m_geometryType)
{
return m_geometry.IsValid();
}
return false;
}
bool GpxParser::Push(std::string const & tag)
{
m_tags.push_back(tag);
if (GetTagFromEnd(0) == "wpt")
{
m_geometryType = GEOMETRY_TYPE_POINT;
}
else if (GetTagFromEnd(0) == "trkpt")
{
m_geometryType = GEOMETRY_TYPE_LINE;
}
return true;
}
void GpxParser::AddAttr(std::string const & attr, std::string const & value)
{
std::string attrInLowerCase = attr;
strings::AsciiToLower(attrInLowerCase);
if (GetTagFromEnd(0) == "wpt")
{
if (attr == "lat")
{
m_lat = stod(value);
}
else if (attr == "lon")
{
m_lon = stod(value);
}
}
else if (GetTagFromEnd(0) == "trkpt" && GetTagFromEnd(1) == "trkseg")
{
if (attr == "lat")
{
m_lat = stod(value);
}
else if (attr == "lon")
{
m_lon = stod(value);
}
}
}
std::string const & GpxParser::GetTagFromEnd(size_t n) const
{
ASSERT_LESS(n, m_tags.size(), ());
return m_tags[m_tags.size() - n - 1];
}
void GpxParser::Pop(std::string const & tag)
{
ASSERT_EQUAL(m_tags.back(), tag, ());
if (tag == "trkpt")
{
m2::Point p = mercator::FromLatLon(m_lat, m_lon);
if (m_line.empty() || !AlmostEqualAbs(m_line.back().GetPoint(), p, kMwmPointAccuracy))
m_line.emplace_back(p);
}
else if (tag == "trkseg")
{
m_geometry.m_lines.push_back(std::move(m_line));
}
else if (tag == "wpt")
{
m_org = mercator::FromLatLon(m_lat, m_lon);
}
if (tag == "trkseg" || tag == "wpt")
{
if (MakeValid())
{
if (GEOMETRY_TYPE_POINT == m_geometryType)
{
BookmarkData data;
data.m_name = std::move(m_name);
data.m_description = std::move(m_description);
data.m_color.m_predefinedColor = m_predefinedColor;
data.m_color.m_rgba = m_color;
data.m_icon = m_icon;
data.m_viewportScale = m_viewportScale;
data.m_timestamp = m_timestamp;
data.m_point = m_org;
data.m_featureTypes = std::move(m_featureTypes);
data.m_customName = std::move(m_customName);
data.m_boundTracks = std::move(m_boundTracks);
data.m_visible = m_visible;
data.m_nearestToponym = std::move(m_nearestToponym);
data.m_minZoom = m_minZoom;
data.m_properties = std::move(m_properties);
data.m_compilations = std::move(m_compilations);
// Here we set custom name from 'name' field for KML-files exported from 3rd-party services.
if (data.m_name.size() == 1 && data.m_name.begin()->first == kDefaultLangCode &&
data.m_customName.empty() && data.m_featureTypes.empty())
{
data.m_customName = data.m_name;
}
m_data.m_bookmarksData.push_back(std::move(data));
}
else if (GEOMETRY_TYPE_LINE == m_geometryType)
{
TrackData data;
data.m_localId = m_localId;
data.m_name = std::move(m_name);
data.m_description = std::move(m_description);
data.m_layers = std::move(m_trackLayers);
data.m_timestamp = m_timestamp;
data.m_geometry = std::move(m_geometry);
data.m_visible = m_visible;
data.m_nearestToponyms = std::move(m_nearestToponyms);
data.m_properties = std::move(m_properties);
m_data.m_tracksData.push_back(std::move(data));
}
}
ResetPoint();
}
m_tags.pop_back();
}
void GpxParser::CharData(std::string value)
{
strings::Trim(value);
size_t const count = m_tags.size();
if (count > 1 && !value.empty())
{
std::string const & currTag = m_tags[count - 1];
std::string const & prevTag = m_tags[count - 2];
std::string const ppTag = count > 2 ? m_tags[count - 3] : std::string();
std::string const pppTag = count > 3 ? m_tags[count - 4] : std::string();
std::string const ppppTag = count > 4 ? m_tags[count - 5] : std::string();
if (prevTag == "wpt")
{
if (currTag == "name")
{
m_name[gpx::kDefaultLang] = value;
}
else if (currTag == "desc")
{
m_description[gpx::kDefaultLang] = value;
}
}
else if (prevTag == "trk")
{
if (currTag == "name")
{
m_categoryData->m_name[gpx::kDefaultLang] = value;
}
else if (currTag == "desc")
{
m_categoryData->m_description[gpx::kDefaultLang] = value;
}
}
}
}
DeserializerGpx::DeserializerGpx(FileData & fileData)
: m_fileData(fileData)
{
m_fileData = {};
}
} // namespace kml

129
kml/serdes_gpx.hpp Normal file
View file

@ -0,0 +1,129 @@
#pragma once
#include "kml/types.hpp"
#include "coding/parse_xml.hpp"
#include "coding/reader.hpp"
#include "coding/writer.hpp"
#include "geometry/point2d.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/exception.hpp"
#include "base/stl_helpers.hpp"
#include <chrono>
#include <string>
namespace kml
{
class GpxParser
{
public:
explicit GpxParser(FileData & data);
bool Push(std::string const & name);
void AddAttr(std::string const & attr, std::string const & value);
bool IsValidAttribute(std::string const & type, std::string const & value,
std::string const & attrInLowerCase) const;
std::string const & GetTagFromEnd(size_t n) const;
void Pop(std::string const & tag);
void CharData(std::string value);
static kml::TrackLayer GetDefaultTrackLayer();
private:
enum GeometryType
{
GEOMETRY_TYPE_UNKNOWN,
GEOMETRY_TYPE_POINT,
GEOMETRY_TYPE_LINE
};
void ResetPoint();
void SetOrigin(std::string const & s);
void ParseLineCoordinates(std::string const & s, char const * blockSeparator,
char const * coordSeparator);
bool MakeValid();
void ParseColor(std::string const &value);
bool GetColorForStyle(std::string const & styleUrl, uint32_t & color) const;
double GetTrackWidthForStyle(std::string const & styleUrl) const;
FileData & m_data;
CategoryData m_compilationData;
CategoryData * m_categoryData; // never null
std::vector<std::string> m_tags;
GeometryType m_geometryType;
MultiGeometry m_geometry;
uint32_t m_color;
std::string m_styleId;
std::string m_mapStyleId;
std::string m_styleUrlKey;
std::map<std::string, uint32_t> m_styleUrl2Color;
std::map<std::string, double> m_styleUrl2Width;
std::map<std::string, std::string> m_mapStyle2Style;
int8_t m_attrCode;
std::string m_attrId;
std::string m_attrKey;
LocalizableString m_name;
LocalizableString m_description;
PredefinedColor m_predefinedColor;
Timestamp m_timestamp;
m2::PointD m_org;
double m_lat;
double m_lon;
MultiGeometry::LineT m_line;
uint8_t m_viewportScale;
std::vector<uint32_t> m_featureTypes;
LocalizableString m_customName;
std::vector<LocalId> m_boundTracks;
LocalId m_localId;
BookmarkIcon m_icon;
std::vector<TrackLayer> m_trackLayers;
bool m_visible;
std::string m_nearestToponym;
std::vector<std::string> m_nearestToponyms;
int m_minZoom = 1;
kml::Properties m_properties;
std::vector<CompilationId> m_compilations;
double m_trackWidth;
};
class DeserializerGpx
{
public:
DECLARE_EXCEPTION(DeserializeException, RootException);
explicit DeserializerGpx(FileData & fileData);
template <typename ReaderType>
void Deserialize(ReaderType const & reader)
{
NonOwningReaderSource src(reader);
GpxParser parser(m_fileData);
if (!ParseXML(src, parser, true))
{
// Print corrupted KML file for debug and restore purposes.
std::string kmlText;
reader.ReadAsString(kmlText);
if (!kmlText.empty() && kmlText[0] == '<')
LOG(LWARNING, (kmlText));
MYTHROW(DeserializeException, ("Could not parse KML."));
}
}
private:
FileData & m_fileData;
};
} // namespace kml

View file

@ -4,6 +4,7 @@
#include "kml/serdes.hpp"
#include "kml/serdes_binary.hpp"
#include "kml/serdes_gpx.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_data.hpp"
@ -269,6 +270,7 @@ std::string GenerateValidAndUniqueFilePathForKML(std::string const & fileName)
std::string const kKmzExtension = ".kmz";
std::string const kKmlExtension = ".kml";
std::string const kKmbExtension = ".kmb";
std::string const kGpxExtension = ".gpx";
std::string const kDefaultBookmarksFileName = "Bookmarks";
std::unique_ptr<kml::FileData> LoadKmlFile(std::string const & file, KmlFileType fileType)
@ -292,7 +294,7 @@ std::string GetKMLPath(std::string const & filePath)
{
std::string const fileExt = GetFileExt(filePath);
std::string fileSavePath;
if (fileExt == kKmlExtension)
if (fileExt == kKmlExtension || fileExt == kGpxExtension)
{
fileSavePath = GenerateValidAndUniqueFilePathForKML(base::FileNameFromFullPath(filePath));
if (!base::CopyFileX(filePath, fileSavePath))
@ -360,6 +362,11 @@ std::unique_ptr<kml::FileData> LoadKmlData(Reader const & reader, KmlFileType fi
kml::DeserializerKml des(*data);
des.Deserialize(reader);
}
else if (fileType == KmlFileType::Gpx)
{
kml::DeserializerGpx des(*data);
des.Deserialize(reader);
}
else
{
CHECK(false, ("Not supported KmlFileType"));

View file

@ -68,11 +68,13 @@ enum class BookmarkBaseType : uint16_t
extern std::string const kKmzExtension;
extern std::string const kKmlExtension;
extern std::string const kKmbExtension;
extern std::string const kGpxExtension;
extern std::string const kDefaultBookmarksFileName;
enum class KmlFileType
{
Text,
Gpx,
biodranik commented 2023-01-28 15:50:30 +00:00 (Migrated from github.com)
Review

Don't change the enum order, please. Add Gpx at the end.

Don't change the enum order, please. Add Gpx at the end.
Binary
};

View file

@ -1869,7 +1869,17 @@ void BookmarkManager::LoadBookmarkRoutine(std::string const & filePath, bool isT
std::string fileSavePath = GetKMLPath(filePath);
if (!fileSavePath.empty())
{
auto kmlData = LoadKmlFile(fileSavePath, KmlFileType::Text);
auto const ext = base::GetFileExtension(filePath);
std::unique_ptr<kml::FileData> kmlData;
if (ext == ".gpx")
{
kmlData = LoadKmlFile(fileSavePath, KmlFileType::Gpx);
biodranik commented 2023-01-28 15:51:28 +00:00 (Migrated from github.com)
Review

Please check docs/CPP_STYLE.md and use it everywhere, to save the reviewer's and your time later.

Please check docs/CPP_STYLE.md and use it everywhere, to save the reviewer's and your time later.
}
else
{
kmlData = LoadKmlFile(fileSavePath, KmlFileType::Text);
}
if (m_needTeardown)
return;

View file

@ -13,6 +13,8 @@
45E4560120584DF200D9F45E /* serdes_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45E455FF20584DEF00D9F45E /* serdes_tests.cpp */; };
45E4560320584E1C00D9F45E /* libkml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 45E4557D205849A600D9F45E /* libkml.a */; };
45E456142058509200D9F45E /* testingmain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45E456122058508C00D9F45E /* testingmain.cpp */; };
464344F3294F952700984CB7 /* gpx_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 464344F2294F952700984CB7 /* gpx_tests.cpp */; };
46AA9E60294549B000ECED73 /* serdes_gpx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 464BD0FB294546B20011955A /* serdes_gpx.cpp */; };
E2AA225E25275C6B002589E2 /* minzoom_quadtree_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E2AA225C25275C6B002589E2 /* minzoom_quadtree_tests.cpp */; };
E2DC9C9125264E3E0098174E /* types.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E2DC9C9025264E3E0098174E /* types.cpp */; };
FA67C84B26BB365600B33DCA /* libplatform.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA67C84A26BB365600B33DCA /* libplatform.a */; };
@ -55,6 +57,9 @@
45E455ED20584DCB00D9F45E /* kml_tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kml_tests.app; sourceTree = BUILT_PRODUCTS_DIR; };
45E455FF20584DEF00D9F45E /* serdes_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serdes_tests.cpp; sourceTree = "<group>"; };
45E456122058508C00D9F45E /* testingmain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = testingmain.cpp; path = ../../testing/testingmain.cpp; sourceTree = "<group>"; };
464344F2294F952700984CB7 /* gpx_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpx_tests.cpp; sourceTree = "<group>"; };
464BD0FB294546B20011955A /* serdes_gpx.cpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.cpp.cpp; path = serdes_gpx.cpp; sourceTree = "<group>"; };
464BD0FC294546B20011955A /* serdes_gpx.hpp */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.cpp.h; path = serdes_gpx.hpp; sourceTree = "<group>"; };
E2AA225925275C1D002589E2 /* minzoom_quadtree.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = minzoom_quadtree.hpp; sourceTree = "<group>"; };
E2AA225C25275C6B002589E2 /* minzoom_quadtree_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minzoom_quadtree_tests.cpp; sourceTree = "<group>"; };
E2AA225D25275C6B002589E2 /* tests_data.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tests_data.hpp; sourceTree = "<group>"; };
@ -145,6 +150,8 @@
E2DC9C9025264E3E0098174E /* types.cpp */,
45E4559420584ABA00D9F45E /* types.hpp */,
45E4559120584ABA00D9F45E /* visitors.hpp */,
464BD0FB294546B20011955A /* serdes_gpx.cpp */,
464BD0FC294546B20011955A /* serdes_gpx.hpp */,
);
name = kml;
path = ../../kml;
@ -157,6 +164,7 @@
45E455FF20584DEF00D9F45E /* serdes_tests.cpp */,
45E456122058508C00D9F45E /* testingmain.cpp */,
E2AA225D25275C6B002589E2 /* tests_data.hpp */,
464344F2294F952700984CB7 /* gpx_tests.cpp */,
);
name = kml_tests;
path = ../../kml/kml_tests;
@ -268,7 +276,9 @@
buildActionMask = 2147483647;
files = (
45E4559620584ABA00D9F45E /* serdes_binary.cpp in Sources */,
464344F3294F952700984CB7 /* gpx_tests.cpp in Sources */,
4568C86420BD455700E2192B /* type_utils.cpp in Sources */,
46AA9E60294549B000ECED73 /* serdes_gpx.cpp in Sources */,
45E4559520584ABA00D9F45E /* serdes.cpp in Sources */,
E2DC9C9125264E3E0098174E /* types.cpp in Sources */,
);