feature:support save lvx file for lidar;
This commit is contained in:
+13
-7
@@ -59,16 +59,16 @@ char broadcast_code_list[BROADCAST_CODE_LIST_SIZE][kBroadcastCodeSize] = {
|
||||
"00000000000001"
|
||||
};*/
|
||||
|
||||
void GetLidarData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num) {
|
||||
void GetLidarData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num, void *client_data) {
|
||||
static uint32_t receive_packet_count = 0;
|
||||
if (data) {
|
||||
++receive_packet_count;
|
||||
if (0 == (receive_packet_count % 10000)) {
|
||||
printf("receive packet count %d %d\n", data->id, receive_packet_count);
|
||||
|
||||
/** Parsing the timestamp and the point cloud data. */
|
||||
uint64_t cur_timestamp = *((uint64_t *)(data->timestamp));
|
||||
LivoxRawPoint *p_point_data = (LivoxRawPoint *)data->data;
|
||||
/** Parsing the timestamp and the point cloud data. */
|
||||
uint64_t cur_timestamp = *((uint64_t *)(data->timestamp));
|
||||
LivoxRawPoint *p_point_data = (LivoxRawPoint *)data->data;
|
||||
}
|
||||
printf("receive packet from %d \n", (uint16_t) HubGetLidarHandle(data->slot, data->id));
|
||||
}
|
||||
@@ -198,7 +198,7 @@ void OnDeviceBroadcast(const BroadcastDeviceInfo *info) {
|
||||
uint8_t handle = 0;
|
||||
result = AddHubToConnect(info->broadcast_code, &handle);
|
||||
if (result == kStatusSuccess && handle < kMaxLidarCount) {
|
||||
SetDataCallback(handle, GetLidarData);
|
||||
SetDataCallback(handle, GetLidarData, NULL);
|
||||
devices[handle].handle = handle;
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
}
|
||||
@@ -221,8 +221,14 @@ int SetProgramOption(int argc, const char *argv[]) {
|
||||
int optch = 0;
|
||||
const char *optarg = NULL;
|
||||
|
||||
apr_initialize();
|
||||
apr_pool_create(&mp, NULL);
|
||||
if (apr_initialize() != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (apr_pool_create(&mp, NULL) != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = apr_getopt_init(&opt, mp, argc, argv);
|
||||
if (rv != APR_SUCCESS) {
|
||||
printf("Program options initialization failed.\n");
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
set(DEMO_NAME hub_lvx_sample)
|
||||
add_executable(${DEMO_NAME} main.cpp lvx_file.cpp)
|
||||
target_link_libraries(${DEMO_NAME}
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_static
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include "lvx_file.h"
|
||||
|
||||
#define WRITE_BUFFER_LEN 1024 * 1024
|
||||
#define MAGIC_CODE (0xac0ea767)
|
||||
#define PACK_POINT_NUM 100
|
||||
#define M_PI 3.14159265358979323846
|
||||
|
||||
LvxFileHandle::LvxFileHandle() : cur_offset_(0), cur_frame_index_(0) {
|
||||
}
|
||||
|
||||
bool LvxFileHandle::InitLvxFile() {
|
||||
time_t curtime = time(nullptr);
|
||||
char filename[30] = { 0 };
|
||||
|
||||
tm* local_time = localtime(&curtime);
|
||||
sprintf(filename, "%d-%02d-%02d_%02d-%02d-%02d.lvx", local_time->tm_year + 1900,
|
||||
local_time->tm_mon + 1,
|
||||
local_time->tm_mday,
|
||||
local_time->tm_hour,
|
||||
local_time->tm_min,
|
||||
local_time->tm_sec);
|
||||
|
||||
lvx_file_.open(filename, std::ios::out | std::ios::binary);
|
||||
|
||||
if (!lvx_file_.is_open()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LvxFileHandle::InitLvxFileHeader() {
|
||||
LvxFileHeader lvx_file_header = { 0 };
|
||||
std::unique_ptr<char[]> write_buffer(new char[WRITE_BUFFER_LEN]);
|
||||
std::string signature = "livox_tech";
|
||||
memcpy(lvx_file_header.signature, signature.c_str(), signature.size());
|
||||
|
||||
lvx_file_header.version[0] = 1;
|
||||
lvx_file_header.version[1] = 0;
|
||||
lvx_file_header.version[2] = 0;
|
||||
lvx_file_header.version[3] = 0;
|
||||
|
||||
lvx_file_header.magic_code = MAGIC_CODE;
|
||||
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&lvx_file_header, sizeof(LvxFileHeader));
|
||||
cur_offset_ += sizeof(LvxFileHeader);
|
||||
|
||||
uint8_t device_count = static_cast<uint8_t>(device_info_list_.size());
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&device_count, sizeof(uint8_t));
|
||||
cur_offset_ += sizeof(uint8_t);
|
||||
|
||||
for (int i = 0; i < device_count; i++) {
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&device_info_list_[i], sizeof(LvxDeviceInfo));
|
||||
cur_offset_ += sizeof(LvxDeviceInfo);
|
||||
}
|
||||
|
||||
lvx_file_.write((char *)write_buffer.get(), cur_offset_);
|
||||
}
|
||||
|
||||
void LvxFileHandle::SaveFrameToLvxFile(std::list<LvxBasePackDetail> &point_packet_list_temp) {
|
||||
uint64_t cur_pos = 0;
|
||||
FrameHeader frame_header = { 0 };
|
||||
std::unique_ptr<char[]> write_buffer(new char[WRITE_BUFFER_LEN]);
|
||||
|
||||
int pack_num = point_packet_list_temp.size();
|
||||
frame_header.current_offset = cur_offset_;
|
||||
frame_header.next_offset = cur_offset_ + (int64_t)pack_num * sizeof(LvxBasePackDetail) + sizeof(FrameHeader);
|
||||
frame_header.package_count = pack_num;
|
||||
frame_header.frame_index = cur_frame_index_;
|
||||
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&frame_header, sizeof(FrameHeader));
|
||||
cur_pos += sizeof(FrameHeader);
|
||||
|
||||
auto iter = point_packet_list_temp.begin();
|
||||
for (; iter != point_packet_list_temp.end(); iter++) {
|
||||
if (cur_pos + sizeof(LvxBasePackDetail) >= WRITE_BUFFER_LEN) {
|
||||
lvx_file_.write((char*)write_buffer.get(), cur_pos);
|
||||
cur_pos = 0;
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&(*iter), sizeof(LvxBasePackDetail));
|
||||
cur_pos += sizeof(LvxBasePackDetail);
|
||||
}
|
||||
else {
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&(*iter), sizeof(LvxBasePackDetail));
|
||||
cur_pos += sizeof(LvxBasePackDetail);
|
||||
}
|
||||
}
|
||||
lvx_file_.write((char*)write_buffer.get(), cur_pos);
|
||||
|
||||
cur_offset_ = frame_header.next_offset;
|
||||
cur_frame_index_++;
|
||||
}
|
||||
|
||||
void LvxFileHandle::CloseLvxFile() {
|
||||
if (lvx_file_.is_open())
|
||||
lvx_file_.close();
|
||||
}
|
||||
|
||||
void LvxFileHandle::BasePointsHandle(LivoxEthPacket *data, LvxBasePackDetail &packet) {
|
||||
packet.version = data->version;
|
||||
packet.port_id = data->slot;
|
||||
packet.lidar_index = data->id;
|
||||
packet.rsvd = data->rsvd;
|
||||
packet.error_code = data->err_code;
|
||||
packet.timestamp_type = data->timestamp_type;
|
||||
packet.data_type = data->data_type;
|
||||
memcpy(packet.timestamp, data->timestamp, 8 * sizeof(uint8_t));
|
||||
|
||||
if (packet.data_type == 0) {
|
||||
LivoxRawPoint tmp[PACK_POINT_NUM];
|
||||
memcpy(tmp, (void *)data->data, PACK_POINT_NUM * sizeof(LivoxRawPoint));
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
packet.point[i].x = static_cast<float>(tmp[i].x / 1000.0);
|
||||
packet.point[i].y = static_cast<float>(tmp[i].y / 1000.0);
|
||||
packet.point[i].z = static_cast<float>(tmp[i].z / 1000.0);
|
||||
packet.point[i].reflectivity = tmp[i].reflectivity;
|
||||
}
|
||||
}
|
||||
else if (packet.data_type == 1) {
|
||||
LivoxSpherPoint tmp[PACK_POINT_NUM];
|
||||
memcpy(tmp, (void *)data->data, PACK_POINT_NUM * sizeof(LivoxSpherPoint));
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
packet.point[i].x = static_cast<float>(tmp[i].depth / 1000.0);
|
||||
packet.point[i].y = static_cast<float>(tmp[i].theta);
|
||||
packet.point[i].z = static_cast<float>(tmp[i].phi);
|
||||
packet.point[i].reflectivity = tmp[i].reflectivity;
|
||||
LivoxPoint temp = { packet.point[i].x * sin(packet.point[i].y) * cos(packet.point[i].z), packet.point[i].x * sin(packet.point[i].y) * sin(packet.point[i].z), packet.point[i].x * cos(packet.point[i].y) };;
|
||||
packet.point[i] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#include <apr_thread_cond.h>
|
||||
#include <apr_thread_mutex.h>
|
||||
#include <apr_thread_proc.h>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include "livox_sdk.h"
|
||||
|
||||
typedef enum {
|
||||
kDeviceStateDisconnect = 0,
|
||||
kDeviceStateConnect = 1,
|
||||
kDeviceStateSampling = 2,
|
||||
} DeviceState;
|
||||
|
||||
typedef struct {
|
||||
uint8_t handle;
|
||||
DeviceState device_state;
|
||||
DeviceInfo info;
|
||||
} DeviceItem;
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
typedef struct {
|
||||
uint8_t signature[16];
|
||||
uint8_t version[4];
|
||||
uint32_t magic_code;
|
||||
}LvxFileHeader;
|
||||
|
||||
typedef struct {
|
||||
uint8_t lidar_broadcast_code[16];
|
||||
uint8_t hub_broadcast_code[16];
|
||||
uint8_t device_index;
|
||||
uint8_t device_type;
|
||||
float roll;
|
||||
float pitch;
|
||||
float yaw;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} LvxDeviceInfo;
|
||||
|
||||
typedef struct {
|
||||
uint8_t device_index;
|
||||
uint8_t version;
|
||||
uint8_t port_id;
|
||||
uint8_t lidar_index;
|
||||
uint8_t rsvd;
|
||||
uint32_t error_code;
|
||||
uint8_t timestamp_type;
|
||||
uint8_t data_type;
|
||||
uint8_t timestamp[8];
|
||||
LivoxPoint point[100];
|
||||
}LvxBasePackDetail;
|
||||
|
||||
typedef struct {
|
||||
uint64_t current_offset;
|
||||
uint64_t next_offset;
|
||||
uint64_t frame_index;
|
||||
uint64_t package_count;
|
||||
}FrameHeader;
|
||||
|
||||
#pragma pack()
|
||||
|
||||
class LvxFileHandle {
|
||||
public:
|
||||
LvxFileHandle();
|
||||
|
||||
bool InitLvxFile();
|
||||
void InitLvxFileHeader();
|
||||
void SaveFrameToLvxFile(std::list<LvxBasePackDetail> &point_packet_list_temp);
|
||||
void CloseLvxFile();
|
||||
|
||||
void AddDeviceInfo(LvxDeviceInfo &info) { device_info_list_.push_back(info); };
|
||||
int GetDeviceInfoListSize() { return device_info_list_.size(); }
|
||||
|
||||
void BasePointsHandle(LivoxEthPacket *data, LvxBasePackDetail &packet);
|
||||
|
||||
private:
|
||||
std::ofstream lvx_file_;
|
||||
std::vector<LvxDeviceInfo> device_info_list_;
|
||||
uint32_t cur_frame_index_;
|
||||
uint64_t cur_offset_;
|
||||
};
|
||||
@@ -0,0 +1,354 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <apr_general.h>
|
||||
#include <apr_getopt.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include "lvx_file.h"
|
||||
|
||||
DeviceItem devices[kMaxLidarCount];
|
||||
LvxFileHandle lvx_file_handler;
|
||||
std::list<LvxBasePackDetail> point_packet_list;
|
||||
std::condition_variable condition_variable;
|
||||
std::mutex mtx;
|
||||
int lidar_units_index[32];
|
||||
int lvx_file_save_time = 10;
|
||||
bool is_finish_extrinsic_parameter = false;
|
||||
|
||||
#define FRAME_RATE 20
|
||||
|
||||
/** Connect the first broadcast hub in default and connect specific device when use program options or broadcast_code_list is not empty. */
|
||||
std::vector<std::string> broadcast_code_list = {
|
||||
//"000000000000001"
|
||||
};
|
||||
|
||||
/** Receiving point cloud data from Livox Hub. */
|
||||
void GetHubData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num, void *client_data) {
|
||||
if (data) {
|
||||
if (is_finish_extrinsic_parameter) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
LvxBasePackDetail packet;
|
||||
packet.device_index = lidar_units_index[HubGetLidarHandle(data->slot, data->id)];
|
||||
lvx_file_handler.BasePointsHandle(data, packet);
|
||||
point_packet_list.push_back(packet);
|
||||
|
||||
if (point_packet_list.size() % (50 * broadcast_code_list.size()) == 0) {
|
||||
condition_variable.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of starting sampling. */
|
||||
void OnSampleCallback(uint8_t status, uint8_t handle, uint8_t response, void *data) {
|
||||
printf("OnSampleCallback statue %d handle %d response %d \n", status, handle, response);
|
||||
if (status == kStatusSuccess) {
|
||||
if (response != 0) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
}
|
||||
} else if (status == kStatusTimeout) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of stopping sampling. */
|
||||
void OnStopSampleCallback(uint8_t status, uint8_t handle, uint8_t response, void *data) {
|
||||
}
|
||||
|
||||
/** Callback function of get LiDAR units' extrinsic parameter. */
|
||||
void OnGetLidarUnitsExtrinsicParameter(uint8_t status, uint8_t handle, HubGetExtrinsicParameterResponse *response, void *data) {
|
||||
if (status == kStatusSuccess) {
|
||||
if (response != 0) {
|
||||
printf("OnGetLidarUnitsExtrinsicParameter statue %d handle %d response %d \n", status, handle, response->ret_code);
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
LvxDeviceInfo lidar_info;
|
||||
for (int i = 0; i < response->count; i++) {
|
||||
ExtrinsicParameterResponseItem temp;
|
||||
memcpy(&temp, (void *)(response->parameter_list + i), sizeof(ExtrinsicParameterResponseItem));
|
||||
strncpy((char *)lidar_info.lidar_broadcast_code, temp.broadcast_code, kBroadcastCodeSize);
|
||||
strncpy((char *)lidar_info.hub_broadcast_code, broadcast_code_list[0].c_str(), kBroadcastCodeSize);
|
||||
|
||||
std::unique_ptr<DeviceInfo[]> device_list(new DeviceInfo[kMaxLidarCount]);
|
||||
std::unique_ptr<uint8_t> size(new uint8_t);
|
||||
GetConnectedDevices(device_list.get(), size.get());
|
||||
for (int j = 0; j < kMaxLidarCount; j++) {
|
||||
if (strncmp(device_list[j].broadcast_code, temp.broadcast_code, kBroadcastCodeSize) == 0) {
|
||||
lidar_units_index[device_list[j].handle] = i;
|
||||
lidar_info.device_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lidar_info.device_type = devices[handle].info.type;
|
||||
lidar_info.pitch = temp.pitch;
|
||||
lidar_info.roll = temp.roll;
|
||||
lidar_info.yaw = temp.yaw;
|
||||
lidar_info.x = static_cast<float>(temp.x / 1000.0);
|
||||
lidar_info.y = static_cast<float>(temp.y / 1000.0);
|
||||
lidar_info.z = static_cast<float>(temp.z / 1000.0);
|
||||
lvx_file_handler.AddDeviceInfo(lidar_info);
|
||||
}
|
||||
is_finish_extrinsic_parameter = true;
|
||||
condition_variable.notify_one();
|
||||
}
|
||||
}
|
||||
else if (status == kStatusTimeout) {
|
||||
printf("GetLidarUnitsExtrinsicParameter timeout! \n");
|
||||
}
|
||||
}
|
||||
|
||||
void OnHubLidarInfo(uint8_t status, uint8_t handle, HubQueryLidarInformationResponse *response, void *client_data) {
|
||||
if (status != kStatusSuccess) {
|
||||
printf("Device Query Informations Failed %d\n", status);
|
||||
}
|
||||
if (response) {
|
||||
int i = 0;
|
||||
for (i = 0; i < response->count; ++i) {
|
||||
printf("Hub Lidar Info broadcast code %s id %d slot %d \n ",
|
||||
response->device_info_list[i].broadcast_code,
|
||||
response->device_info_list[i].id,
|
||||
response->device_info_list[i].slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of changing of device state. */
|
||||
void OnDeviceChange(const DeviceInfo *info, DeviceEvent type) {
|
||||
if (info == nullptr) {
|
||||
return;
|
||||
}
|
||||
printf("OnDeviceChange broadcast code %s update type %d\n", info->broadcast_code, type);
|
||||
uint8_t handle = info->handle;
|
||||
if (handle >= kMaxLidarCount) {
|
||||
return;
|
||||
}
|
||||
if (type == kEventConnect) {
|
||||
HubQueryLidarInformation(OnHubLidarInfo, nullptr);
|
||||
if (devices[handle].device_state == kDeviceStateDisconnect) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
devices[handle].info = *info;
|
||||
}
|
||||
} else if (type == kEventDisconnect) {
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
} else if (type == kEventStateChange) {
|
||||
devices[handle].info = *info;
|
||||
}
|
||||
|
||||
if (devices[handle].device_state == kDeviceStateConnect) {
|
||||
printf("Device State error_code %d\n", devices[handle].info.status.status_code);
|
||||
printf("Device State working state %d\n", devices[handle].info.state);
|
||||
printf("Device feature %d\n", devices[handle].info.feature);
|
||||
if (devices[handle].info.state == kLidarStateNormal) {
|
||||
if (devices[handle].info.type == kDeviceTypeHub) {
|
||||
HubGetExtrinsicParameter(OnGetLidarUnitsExtrinsicParameter, nullptr);
|
||||
HubStartSampling(OnSampleCallback, nullptr);
|
||||
devices[handle].device_state = kDeviceStateSampling;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function when broadcast message received.
|
||||
* You need to add listening device broadcast code and set the point cloud data callback in this function.
|
||||
*/
|
||||
void OnDeviceBroadcast(const BroadcastDeviceInfo *info) {
|
||||
if (info == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Receive Broadcast Code %s\n", info->broadcast_code);
|
||||
|
||||
if (broadcast_code_list.size() > 0) {
|
||||
bool found = false;
|
||||
uint8_t i = 0;
|
||||
for (i = 0; i < broadcast_code_list.size(); ++i) {
|
||||
if (strncmp(info->broadcast_code, broadcast_code_list[i].c_str(), kBroadcastCodeSize) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
broadcast_code_list.push_back(info->broadcast_code);
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
uint8_t handle = 0;
|
||||
result = AddHubToConnect(info->broadcast_code, &handle);
|
||||
if (result == kStatusSuccess) {
|
||||
SetDataCallback(handle, GetHubData, nullptr);
|
||||
devices[handle].handle = handle;
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the program options.
|
||||
* You can input the registered device broadcast code and decide whether to save the log file.
|
||||
*/
|
||||
int SetProgramOption(int argc, const char *argv[]) {
|
||||
apr_status_t rv;
|
||||
apr_pool_t *mp = nullptr;
|
||||
static const apr_getopt_option_t opt_option[] = {
|
||||
/** Long-option, short-option, has-arg flag, description */
|
||||
{ "code", 'c', 1, "Register device broadcast code" },
|
||||
{ "log", 'l', 0, "Save the log file" },
|
||||
{ "time", 't', 1, "Time to save point cloud to the lvx file" },
|
||||
{ "help", 'h', 0, "Show help" },
|
||||
{ nullptr, 0, 0, nullptr },
|
||||
};
|
||||
apr_getopt_t *opt = nullptr;
|
||||
int optch = 0;
|
||||
const char *optarg = nullptr;
|
||||
|
||||
if (apr_initialize() != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (apr_pool_create(&mp, NULL) != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = apr_getopt_init(&opt, mp, argc, argv);
|
||||
if (rv != APR_SUCCESS) {
|
||||
printf("Program options initialization failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Parse the all options based on opt_option[] */
|
||||
bool is_help = false;
|
||||
while ((rv = apr_getopt_long(opt, opt_option, &optch, &optarg)) == APR_SUCCESS) {
|
||||
switch (optch) {
|
||||
case 'c': {
|
||||
printf("Register broadcast code: %s\n", optarg);
|
||||
broadcast_code_list.push_back(optarg);
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
printf("Save the log file.\n");
|
||||
SaveLoggerFile();
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
printf("Time to save point cloud to the lvx file:%s.\n", optarg);
|
||||
lvx_file_save_time = atoi(optarg);
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
printf(
|
||||
" [-c] Register device broadcast code\n"
|
||||
" [-l] Save the log file\n"
|
||||
" [-t] Time to save point cloud to the lvx file\n"
|
||||
" [-h] Show help\n"
|
||||
);
|
||||
is_help = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rv != APR_EOF) {
|
||||
printf("Invalid options.\n");
|
||||
}
|
||||
|
||||
apr_pool_destroy(mp);
|
||||
mp = nullptr;
|
||||
if (is_help)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
/** Set the program options. */
|
||||
if (SetProgramOption(argc, argv))
|
||||
return 0;
|
||||
|
||||
printf("Livox SDK initializing.\n");
|
||||
/** Initialize Livox-SDK. */
|
||||
if (!Init()) {
|
||||
return -1;
|
||||
}
|
||||
printf("Livox SDK has been initialized.\n");
|
||||
|
||||
LivoxSdkVersion _sdkversion;
|
||||
GetLivoxSdkVersion(&_sdkversion);
|
||||
printf("Livox SDK version %d.%d.%d .\n", _sdkversion.major, _sdkversion.minor, _sdkversion.patch);
|
||||
|
||||
memset(devices, 0, sizeof(devices));
|
||||
|
||||
/** Set the callback function receiving broadcast message from Livox LiDAR. */
|
||||
SetBroadcastCallback(OnDeviceBroadcast);
|
||||
|
||||
/** Set the callback function called when device state change,
|
||||
* which means connection/disconnection and changing of LiDAR state.
|
||||
*/
|
||||
SetDeviceStateUpdateCallback(OnDeviceChange);
|
||||
|
||||
/** Start the device discovering routine. */
|
||||
if (!Start()) {
|
||||
Uninit();
|
||||
return -1;
|
||||
}
|
||||
printf("Start discovering device.\n");
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
condition_variable.wait(lock);
|
||||
}
|
||||
|
||||
printf("Start initialize lvx file.\n");
|
||||
if (!lvx_file_handler.InitLvxFile()) {
|
||||
Uninit();
|
||||
return -1;
|
||||
}
|
||||
lvx_file_handler.InitLvxFileHeader();
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; i < lvx_file_save_time * FRAME_RATE; ++i) {
|
||||
std::list<LvxBasePackDetail> point_packet_list_temp;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
condition_variable.wait(lock);
|
||||
point_packet_list_temp.swap(point_packet_list);
|
||||
}
|
||||
|
||||
printf("Finish save %d frame to lvx file.\n", i);
|
||||
lvx_file_handler.SaveFrameToLvxFile(point_packet_list_temp);
|
||||
}
|
||||
|
||||
lvx_file_handler.CloseLvxFile();
|
||||
|
||||
HubStopSampling(OnStopSampleCallback, NULL);
|
||||
printf("stop sample\n");
|
||||
|
||||
Uninit();
|
||||
}
|
||||
+13
-7
@@ -63,15 +63,15 @@ char broadcast_code_list[kMaxLidarCount][kBroadcastCodeSize] = {
|
||||
};*/
|
||||
|
||||
/** Receiving point cloud data from Livox LiDAR. */
|
||||
void GetLidarData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num) {
|
||||
void GetLidarData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num, void *client_data) {
|
||||
if (data) {
|
||||
data_recveive_count[handle] += data_num;
|
||||
if (data_recveive_count[handle] % 10000 == 0) {
|
||||
printf("receive packet count %d %d\n", handle, data_recveive_count[handle]);
|
||||
|
||||
/** Parsing the timestamp and the point cloud data. */
|
||||
uint64_t cur_timestamp = *((uint64_t *)(data->timestamp));
|
||||
LivoxRawPoint *p_point_data = (LivoxRawPoint *)data->data;
|
||||
/** Parsing the timestamp and the point cloud data. */
|
||||
uint64_t cur_timestamp = *((uint64_t *)(data->timestamp));
|
||||
LivoxRawPoint *p_point_data = (LivoxRawPoint *)data->data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ void OnDeviceBroadcast(const BroadcastDeviceInfo *info) {
|
||||
result = AddLidarToConnect(info->broadcast_code, &handle);
|
||||
if (result == kStatusSuccess) {
|
||||
/** Set the point cloud data for a specific Livox LiDAR. */
|
||||
SetDataCallback(handle, GetLidarData);
|
||||
SetDataCallback(handle, GetLidarData, NULL);
|
||||
devices[handle].handle = handle;
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
}
|
||||
@@ -195,8 +195,14 @@ int SetProgramOption(int argc, const char *argv[]) {
|
||||
int optch = 0;
|
||||
const char *optarg = NULL;
|
||||
|
||||
apr_initialize();
|
||||
apr_pool_create(&mp, NULL);
|
||||
if (apr_initialize() != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (apr_pool_create(&mp, NULL) != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = apr_getopt_init(&opt, mp, argc, argv);
|
||||
if (rv != APR_SUCCESS) {
|
||||
printf("Program options initialization failed.\n");
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
set(DEMO_NAME lidar_lvx_sample)
|
||||
add_executable(${DEMO_NAME} main.cpp lvx_file.cpp)
|
||||
target_link_libraries(${DEMO_NAME}
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_static
|
||||
)
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include "lvx_file.h"
|
||||
#include "third_party/rapidxml/rapidxml.hpp"
|
||||
#include "third_party/rapidxml/rapidxml_utils.hpp"
|
||||
|
||||
#define WRITE_BUFFER_LEN 1024 * 1024
|
||||
#define MAGIC_CODE (0xac0ea767)
|
||||
#define PACK_POINT_NUM 100
|
||||
#define M_PI 3.14159265358979323846
|
||||
|
||||
LvxFileHandle::LvxFileHandle() : cur_offset_(0), cur_frame_index_(0) {
|
||||
}
|
||||
|
||||
bool LvxFileHandle::InitLvxFile() {
|
||||
time_t curtime = time(nullptr);
|
||||
char filename[30] = { 0 };
|
||||
|
||||
tm* local_time = localtime(&curtime);
|
||||
sprintf(filename, "%d-%02d-%02d_%02d-%02d-%02d.lvx", local_time->tm_year + 1900,
|
||||
local_time->tm_mon + 1,
|
||||
local_time->tm_mday,
|
||||
local_time->tm_hour,
|
||||
local_time->tm_min,
|
||||
local_time->tm_sec);
|
||||
|
||||
lvx_file_.open(filename, std::ios::out | std::ios::binary);
|
||||
|
||||
if (!lvx_file_.is_open()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LvxFileHandle::InitLvxFileHeader() {
|
||||
LvxFileHeader lvx_file_header = { 0 };
|
||||
std::unique_ptr<char[]> write_buffer(new char[WRITE_BUFFER_LEN]);
|
||||
std::string signature = "livox_tech";
|
||||
memcpy(lvx_file_header.signature, signature.c_str(), signature.size());
|
||||
|
||||
lvx_file_header.version[0] = 1;
|
||||
lvx_file_header.version[1] = 0;
|
||||
lvx_file_header.version[2] = 0;
|
||||
lvx_file_header.version[3] = 0;
|
||||
|
||||
lvx_file_header.magic_code = MAGIC_CODE;
|
||||
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&lvx_file_header, sizeof(LvxFileHeader));
|
||||
cur_offset_ += sizeof(LvxFileHeader);
|
||||
|
||||
uint8_t device_count = static_cast<uint8_t>(device_info_list_.size());
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&device_count, sizeof(uint8_t));
|
||||
cur_offset_ += sizeof(uint8_t);
|
||||
|
||||
for (int i = 0; i < device_count; i++) {
|
||||
memcpy(write_buffer.get() + cur_offset_, (void *)&device_info_list_[i], sizeof(LvxDeviceInfo));
|
||||
cur_offset_ += sizeof(LvxDeviceInfo);
|
||||
}
|
||||
|
||||
lvx_file_.write((char *)write_buffer.get(), cur_offset_);
|
||||
}
|
||||
|
||||
void LvxFileHandle::SaveFrameToLvxFile(std::list<LvxBasePackDetail> &point_packet_list_temp) {
|
||||
uint64_t cur_pos = 0;
|
||||
FrameHeader frame_header = { 0 };
|
||||
std::unique_ptr<char[]> write_buffer(new char[WRITE_BUFFER_LEN]);
|
||||
|
||||
int pack_num = point_packet_list_temp.size();
|
||||
frame_header.current_offset = cur_offset_;
|
||||
frame_header.next_offset = cur_offset_ + (int64_t)pack_num * sizeof(LvxBasePackDetail) + sizeof(FrameHeader);
|
||||
frame_header.package_count = pack_num;
|
||||
frame_header.frame_index = cur_frame_index_;
|
||||
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&frame_header, sizeof(FrameHeader));
|
||||
cur_pos += sizeof(FrameHeader);
|
||||
|
||||
auto iter = point_packet_list_temp.begin();
|
||||
for (; iter != point_packet_list_temp.end(); iter++) {
|
||||
if (cur_pos + sizeof(LvxBasePackDetail) >= WRITE_BUFFER_LEN) {
|
||||
lvx_file_.write((char*)write_buffer.get(), cur_pos);
|
||||
cur_pos = 0;
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&(*iter), sizeof(LvxBasePackDetail));
|
||||
cur_pos += sizeof(LvxBasePackDetail);
|
||||
}
|
||||
else {
|
||||
memcpy(write_buffer.get() + cur_pos, (void*)&(*iter), sizeof(LvxBasePackDetail));
|
||||
cur_pos += sizeof(LvxBasePackDetail);
|
||||
}
|
||||
}
|
||||
lvx_file_.write((char*)write_buffer.get(), cur_pos);
|
||||
|
||||
cur_offset_ = frame_header.next_offset;
|
||||
cur_frame_index_++;
|
||||
}
|
||||
|
||||
void LvxFileHandle::CloseLvxFile() {
|
||||
if (lvx_file_.is_open())
|
||||
lvx_file_.close();
|
||||
}
|
||||
|
||||
void LvxFileHandle::BasePointsHandle(LivoxEthPacket *data, LvxBasePackDetail &packet) {
|
||||
packet.version = data->version;
|
||||
packet.port_id = data->slot;
|
||||
packet.lidar_index = data->id;
|
||||
packet.rsvd = data->rsvd;
|
||||
packet.error_code = data->err_code;
|
||||
packet.timestamp_type = data->timestamp_type;
|
||||
packet.data_type = data->data_type;
|
||||
memcpy(packet.timestamp, data->timestamp, 8 * sizeof(uint8_t));
|
||||
|
||||
if (packet.data_type == 0) {
|
||||
LivoxRawPoint tmp[PACK_POINT_NUM];
|
||||
memcpy(tmp, (void *)data->data, PACK_POINT_NUM * sizeof(LivoxRawPoint));
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
packet.point[i].x = static_cast<float>(tmp[i].x / 1000.0);
|
||||
packet.point[i].y = static_cast<float>(tmp[i].y / 1000.0);
|
||||
packet.point[i].z = static_cast<float>(tmp[i].z / 1000.0);
|
||||
packet.point[i].reflectivity = tmp[i].reflectivity;
|
||||
}
|
||||
}
|
||||
else if (packet.data_type == 1) {
|
||||
LivoxSpherPoint tmp[PACK_POINT_NUM];
|
||||
memcpy(tmp, (void *)data->data, PACK_POINT_NUM * sizeof(LivoxSpherPoint));
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
packet.point[i].x = static_cast<float>(tmp[i].depth / 1000.0);
|
||||
packet.point[i].y = static_cast<float>(tmp[i].theta);
|
||||
packet.point[i].z = static_cast<float>(tmp[i].phi);
|
||||
packet.point[i].reflectivity = tmp[i].reflectivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LvxFileHandle::CalcExtrinsicPoints(LvxBasePackDetail &packet) {
|
||||
LvxDeviceInfo info = device_info_list_[packet.device_index];
|
||||
info.roll = static_cast<float>(info.roll * M_PI / 180.0);
|
||||
info.pitch = static_cast<float>(info.pitch * M_PI / 180.0);
|
||||
info.yaw = static_cast<float>(info.yaw * M_PI / 180.0);
|
||||
float rotate[3][3] = { { cos(info.pitch) * cos(info.yaw), sin(info.roll) * sin(info.pitch) * cos(info.yaw) - cos(info.roll) * sin(info.yaw), cos(info.roll) * sin(info.pitch) * cos(info.yaw) + sin(info.roll) * sin(info.yaw) },
|
||||
{ cos(info.pitch) * sin(info.yaw), sin(info.roll) * sin(info.pitch) * sin(info.yaw) + cos(info.roll) * cos(info.yaw), cos(info.roll) * sin(info.pitch) * sin(info.yaw) - sin(info.roll) * cos(info.yaw) },
|
||||
{ -sin(info.pitch), sin(info.roll) * cos(info.pitch), cos(info.roll) * cos(info.pitch) } };
|
||||
|
||||
float trans[3] = { static_cast<float>(info.x), static_cast<float>(info.y), static_cast<float>(info.z) };
|
||||
if (packet.data_type == 0) {
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
LivoxPoint temp = packet.point[i];
|
||||
packet.point[i].x = temp.x * rotate[0][0] + temp.y * rotate[0][1] + temp.z * rotate[0][2] + trans[0];
|
||||
packet.point[i].y = temp.x * rotate[1][0] + temp.y * rotate[1][1] + temp.z * rotate[1][2] + trans[1];
|
||||
packet.point[i].z = temp.x * rotate[2][0] + temp.y * rotate[2][1] + temp.z * rotate[2][2] + trans[2];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < PACK_POINT_NUM; i++) {
|
||||
LivoxPoint temp = { packet.point[i].x * sin(packet.point[i].y) * cos(packet.point[i].z), packet.point[i].x * sin(packet.point[i].y) * sin(packet.point[i].z), packet.point[i].x * cos(packet.point[i].y) };;
|
||||
packet.point[i].x = temp.x * rotate[0][0] + temp.y * rotate[0][1] + temp.z * rotate[0][2] + trans[0];
|
||||
packet.point[i].y = temp.x * rotate[1][0] + temp.y * rotate[1][1] + temp.z * rotate[1][2] + trans[1];
|
||||
packet.point[i].z = temp.x * rotate[2][0] + temp.y * rotate[2][1] + temp.z * rotate[2][2] + trans[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseExtrinsicXml(DeviceItem &item, LvxDeviceInfo &info) {
|
||||
rapidxml::file<> extrinsic_param("extrinsic.xml");
|
||||
rapidxml::xml_document<> doc;
|
||||
doc.parse<0>(extrinsic_param.data());
|
||||
rapidxml::xml_node<>* root = doc.first_node();
|
||||
if ("Livox" == (std::string)root->name()) {
|
||||
for (rapidxml::xml_node<>* device = root->first_node(); device; device = device->next_sibling()) {
|
||||
if ("Device" == (std::string)device->name() && (strncmp(item.info.broadcast_code, device->value(), kBroadcastCodeSize) == 0)) {
|
||||
memcpy(info.lidar_broadcast_code, device->value(), kBroadcastCodeSize);
|
||||
memset(info.hub_broadcast_code, 0, kBroadcastCodeSize);
|
||||
info.device_type = item.info.type;
|
||||
info.device_index = item.handle;
|
||||
for (rapidxml::xml_attribute<>* param = device->first_attribute(); param; param = param->next_attribute()) {
|
||||
if ("roll" == (std::string)param->name()) info.roll = static_cast<float>(atof(param->value()));
|
||||
if ("pitch" == (std::string)param->name()) info.pitch = static_cast<float>(atof(param->value()));
|
||||
if ("yaw" == (std::string)param->name()) info.yaw = static_cast<float>(atof(param->value()));
|
||||
if ("x" == (std::string)param->name()) info.x = static_cast<float>(atof(param->value()));
|
||||
if ("y" == (std::string)param->name()) info.y = static_cast<float>(atof(param->value()));
|
||||
if ("z" == (std::string)param->name()) info.z = static_cast<float>(atof(param->value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#include <apr_thread_cond.h>
|
||||
#include <apr_thread_mutex.h>
|
||||
#include <apr_thread_proc.h>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include "livox_sdk.h"
|
||||
|
||||
typedef enum {
|
||||
kDeviceStateDisconnect = 0,
|
||||
kDeviceStateConnect = 1,
|
||||
kDeviceStateSampling = 2,
|
||||
} DeviceState;
|
||||
|
||||
typedef struct {
|
||||
uint8_t handle;
|
||||
DeviceState device_state;
|
||||
DeviceInfo info;
|
||||
} DeviceItem;
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
typedef struct {
|
||||
uint8_t signature[16];
|
||||
uint8_t version[4];
|
||||
uint32_t magic_code;
|
||||
}LvxFileHeader;
|
||||
|
||||
typedef struct {
|
||||
uint8_t lidar_broadcast_code[16];
|
||||
uint8_t hub_broadcast_code[16];
|
||||
uint8_t device_index;
|
||||
uint8_t device_type;
|
||||
float roll;
|
||||
float pitch;
|
||||
float yaw;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} LvxDeviceInfo;
|
||||
|
||||
typedef struct {
|
||||
uint8_t device_index;
|
||||
uint8_t version;
|
||||
uint8_t port_id;
|
||||
uint8_t lidar_index;
|
||||
uint8_t rsvd;
|
||||
uint32_t error_code;
|
||||
uint8_t timestamp_type;
|
||||
uint8_t data_type;
|
||||
uint8_t timestamp[8];
|
||||
LivoxPoint point[100];
|
||||
}LvxBasePackDetail;
|
||||
|
||||
typedef struct {
|
||||
uint64_t current_offset;
|
||||
uint64_t next_offset;
|
||||
uint64_t frame_index;
|
||||
uint64_t package_count;
|
||||
}FrameHeader;
|
||||
|
||||
#pragma pack()
|
||||
|
||||
class LvxFileHandle {
|
||||
public:
|
||||
LvxFileHandle();
|
||||
|
||||
bool InitLvxFile();
|
||||
void InitLvxFileHeader();
|
||||
void SaveFrameToLvxFile(std::list<LvxBasePackDetail> &point_packet_list_temp);
|
||||
void CloseLvxFile();
|
||||
|
||||
void AddDeviceInfo(LvxDeviceInfo &info) { device_info_list_.push_back(info); };
|
||||
int GetDeviceInfoListSize() { return device_info_list_.size(); }
|
||||
|
||||
void BasePointsHandle(LivoxEthPacket *data, LvxBasePackDetail &packet);
|
||||
void CalcExtrinsicPoints(LvxBasePackDetail &packet);
|
||||
|
||||
private:
|
||||
std::ofstream lvx_file_;
|
||||
std::vector<LvxDeviceInfo> device_info_list_;
|
||||
uint32_t cur_frame_index_;
|
||||
uint64_t cur_offset_;
|
||||
};
|
||||
|
||||
void ParseExtrinsicXml(DeviceItem &item, LvxDeviceInfo &info);
|
||||
@@ -0,0 +1,395 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2019 Livox. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <apr_general.h>
|
||||
#include <apr_getopt.h>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include "lvx_file.h"
|
||||
|
||||
DeviceItem devices[kMaxLidarCount];
|
||||
LvxFileHandle lvx_file_handler;
|
||||
std::list<LvxBasePackDetail> point_packet_list;
|
||||
std::vector<std::string> broadcast_code_rev;
|
||||
std::condition_variable condition_variable;
|
||||
std::mutex mtx;
|
||||
int lvx_file_save_time = 10;
|
||||
bool is_finish_extrinsic_parameter = false;
|
||||
bool is_read_extrinsic_from_xml = false;
|
||||
|
||||
#define FRAME_RATE 20
|
||||
|
||||
/** Connect all the broadcast device in default and connect specific device when use program options or broadcast_code_list is not empty. */
|
||||
std::vector<std::string> broadcast_code_list = {
|
||||
//"000000000000002",
|
||||
//"000000000000003",
|
||||
//"000000000000004"
|
||||
};
|
||||
|
||||
/** Receiving point cloud data from Livox LiDAR. */
|
||||
void GetLidarData(uint8_t handle, LivoxEthPacket *data, uint32_t data_num, void *client_data) {
|
||||
if (data) {
|
||||
if (handle < broadcast_code_list.size() && is_finish_extrinsic_parameter) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
LvxBasePackDetail packet;
|
||||
packet.device_index = handle;
|
||||
lvx_file_handler.BasePointsHandle(data, packet);
|
||||
lvx_file_handler.CalcExtrinsicPoints(packet);
|
||||
point_packet_list.push_back(packet);
|
||||
|
||||
if (point_packet_list.size() % (50 * broadcast_code_list.size()) == 0) {
|
||||
condition_variable.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of starting sampling. */
|
||||
void OnSampleCallback(uint8_t status, uint8_t handle, uint8_t response, void *data) {
|
||||
printf("OnSampleCallback statue %d handle %d response %d \n", status, handle, response);
|
||||
if (status == kStatusSuccess) {
|
||||
if (response != 0) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
}
|
||||
} else if (status == kStatusTimeout) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of stopping sampling. */
|
||||
void OnStopSampleCallback(uint8_t status, uint8_t handle, uint8_t response, void *data) {
|
||||
}
|
||||
|
||||
/** Callback function of get LiDARs' extrinsic parameter. */
|
||||
void OnGetLidarExtrinsicParameter(uint8_t status, uint8_t handle, LidarGetExtrinsicParameterResponse *response, void *data) {
|
||||
if (status == kStatusSuccess) {
|
||||
if (response != 0) {
|
||||
printf("OnGetLidarExtrinsicParameter statue %d handle %d response %d \n", status, handle, response->ret_code);
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
LvxDeviceInfo lidar_info;
|
||||
strncpy((char *)lidar_info.lidar_broadcast_code, devices[handle].info.broadcast_code, kBroadcastCodeSize);
|
||||
memset(lidar_info.hub_broadcast_code, 0, kBroadcastCodeSize);
|
||||
lidar_info.device_index = handle;
|
||||
lidar_info.device_type = devices[handle].info.type;
|
||||
lidar_info.pitch = response->pitch;
|
||||
lidar_info.roll = response->roll;
|
||||
lidar_info.yaw = response->yaw;
|
||||
lidar_info.x = static_cast<float>(response->x / 1000.0);
|
||||
lidar_info.y = static_cast<float>(response->y / 1000.0);
|
||||
lidar_info.z = static_cast<float>(response->z / 1000.0);
|
||||
lvx_file_handler.AddDeviceInfo(lidar_info);
|
||||
if (lvx_file_handler.GetDeviceInfoListSize() == broadcast_code_list.size()) {
|
||||
is_finish_extrinsic_parameter = true;
|
||||
condition_variable.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (status == kStatusTimeout) {
|
||||
printf("GetLidarExtrinsicParameter timeout! \n");
|
||||
}
|
||||
}
|
||||
|
||||
/** Get LiDARs' extrinsic parameter from file named "extrinsic.xml". */
|
||||
void LidarGetExtrinsicFromXml(uint8_t handle) {
|
||||
LvxDeviceInfo lidar_info;
|
||||
ParseExtrinsicXml(devices[handle], lidar_info);
|
||||
lvx_file_handler.AddDeviceInfo(lidar_info);
|
||||
if (lvx_file_handler.GetDeviceInfoListSize() == broadcast_code_list.size()) {
|
||||
is_finish_extrinsic_parameter = true;
|
||||
condition_variable.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
/** Query the firmware version of Livox LiDAR. */
|
||||
void OnDeviceInformation(uint8_t status, uint8_t handle, DeviceInformationResponse *ack, void *data) {
|
||||
if (status != kStatusSuccess) {
|
||||
printf("Device Query Informations Failed %d\n", status);
|
||||
}
|
||||
if (ack) {
|
||||
printf("firm ver: %d.%d.%d.%d\n",
|
||||
ack->firmware_version[0],
|
||||
ack->firmware_version[1],
|
||||
ack->firmware_version[2],
|
||||
ack->firmware_version[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function of changing of device state. */
|
||||
void OnDeviceChange(const DeviceInfo *info, DeviceEvent type) {
|
||||
if (info == nullptr) {
|
||||
return;
|
||||
}
|
||||
printf("OnDeviceChange broadcast code %s update type %d\n", info->broadcast_code, type);
|
||||
uint8_t handle = info->handle;
|
||||
if (handle >= kMaxLidarCount) {
|
||||
return;
|
||||
}
|
||||
if (type == kEventConnect) {
|
||||
QueryDeviceInformation(handle, OnDeviceInformation, nullptr);
|
||||
if (devices[handle].device_state == kDeviceStateDisconnect) {
|
||||
devices[handle].device_state = kDeviceStateConnect;
|
||||
devices[handle].info = *info;
|
||||
}
|
||||
} else if (type == kEventDisconnect) {
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
} else if (type == kEventStateChange) {
|
||||
devices[handle].info = *info;
|
||||
}
|
||||
|
||||
if (devices[handle].device_state == kDeviceStateConnect) {
|
||||
printf("Device State error_code %d\n", devices[handle].info.status.status_code);
|
||||
printf("Device State working state %d\n", devices[handle].info.state);
|
||||
printf("Device feature %d\n", devices[handle].info.feature);
|
||||
if (devices[handle].info.state == kLidarStateNormal) {
|
||||
if (devices[handle].info.type != kDeviceTypeHub) {
|
||||
if (!is_read_extrinsic_from_xml) {
|
||||
LidarGetExtrinsicParameter(handle, OnGetLidarExtrinsicParameter, nullptr);
|
||||
}
|
||||
else {
|
||||
LidarGetExtrinsicFromXml(handle);
|
||||
}
|
||||
LidarStartSampling(handle, OnSampleCallback, nullptr);
|
||||
devices[handle].device_state = kDeviceStateSampling;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback function when broadcast message received.
|
||||
* You need to add listening device broadcast code and set the point cloud data callback in this function.
|
||||
*/
|
||||
void OnDeviceBroadcast(const BroadcastDeviceInfo *info) {
|
||||
if (info == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Receive Broadcast Code %s\n", info->broadcast_code);
|
||||
|
||||
if (broadcast_code_list.size() > 0) {
|
||||
bool found = false;
|
||||
uint8_t i = 0;
|
||||
for (i = 0; i < broadcast_code_list.size(); ++i) {
|
||||
if (strncmp(info->broadcast_code, broadcast_code_list[i].c_str(), kBroadcastCodeSize) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((broadcast_code_rev.size() == 0) || (std::find(broadcast_code_rev.begin(), broadcast_code_rev.end(), info->broadcast_code) == broadcast_code_rev.end()))
|
||||
broadcast_code_rev.push_back(info->broadcast_code);
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
uint8_t handle = 0;
|
||||
result = AddLidarToConnect(info->broadcast_code, &handle);
|
||||
if (result == kStatusSuccess) {
|
||||
/** Set the point cloud data for a specific Livox LiDAR. */
|
||||
SetDataCallback(handle, GetLidarData, nullptr);
|
||||
devices[handle].handle = handle;
|
||||
devices[handle].device_state = kDeviceStateDisconnect;
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the program options.
|
||||
* You can input the registered device broadcast code and decide whether to save the log file.
|
||||
*/
|
||||
int SetProgramOption(int argc, const char *argv[]) {
|
||||
apr_status_t rv;
|
||||
apr_pool_t *mp = nullptr;
|
||||
static const apr_getopt_option_t opt_option[] = {
|
||||
/** Long-option, short-option, has-arg flag, description */
|
||||
{ "code", 'c', 1, "Register device broadcast code" },
|
||||
{ "log", 'l', 0, "Save the log file" },
|
||||
{ "time", 't', 1, "Time to save point cloud to the lvx file" },
|
||||
{ "param", 'p', 0, "Get the extrinsic parameter from extrinsic.xml file" },
|
||||
{ "help", 'h', 0, "Show help" },
|
||||
{ nullptr, 0, 0, nullptr },
|
||||
};
|
||||
apr_getopt_t *opt = nullptr;
|
||||
int optch = 0;
|
||||
const char *optarg = nullptr;
|
||||
|
||||
if (apr_initialize() != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (apr_pool_create(&mp, NULL) != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = apr_getopt_init(&opt, mp, argc, argv);
|
||||
if (rv != APR_SUCCESS) {
|
||||
printf("Program options initialization failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Parse the all options based on opt_option[] */
|
||||
bool is_help = false;
|
||||
while ((rv = apr_getopt_long(opt, opt_option, &optch, &optarg)) == APR_SUCCESS) {
|
||||
switch (optch) {
|
||||
case 'c': {
|
||||
printf("Register broadcast code: %s\n", optarg);
|
||||
char *sn_list = (char *)malloc(sizeof(char)*(strlen(optarg) + 1));
|
||||
strncpy(sn_list, optarg, sizeof(char)*(strlen(optarg) + 1));
|
||||
char *sn_list_head = sn_list;
|
||||
sn_list = strtok(sn_list, "&");
|
||||
int i = 0;
|
||||
broadcast_code_list.clear();
|
||||
while (sn_list) {
|
||||
broadcast_code_list.push_back(sn_list);
|
||||
sn_list = strtok(nullptr, "&");
|
||||
i++;
|
||||
}
|
||||
free(sn_list_head);
|
||||
sn_list_head = nullptr;
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
printf("Save the log file.\n");
|
||||
SaveLoggerFile();
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
printf("Time to save point cloud to the lvx file:%s.\n", optarg);
|
||||
lvx_file_save_time = atoi(optarg);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
printf("Get the extrinsic parameter from extrinsic.xml file.\n");
|
||||
is_read_extrinsic_from_xml = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
printf(
|
||||
" [-c] Register device broadcast code\n"
|
||||
" [-l] Save the log file\n"
|
||||
" [-t] Time to save point cloud to the lvx file\n"
|
||||
" [-p] Get the extrinsic parameter from extrinsic.xml file\n"
|
||||
" [-h] Show help\n"
|
||||
);
|
||||
is_help = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rv != APR_EOF) {
|
||||
printf("Invalid options.\n");
|
||||
}
|
||||
|
||||
apr_pool_destroy(mp);
|
||||
mp = nullptr;
|
||||
if (is_help)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
/** Set the program options. */
|
||||
if (SetProgramOption(argc, argv))
|
||||
return 0;
|
||||
|
||||
printf("Livox SDK initializing.\n");
|
||||
/** Initialize Livox-SDK. */
|
||||
if (!Init()) {
|
||||
return -1;
|
||||
}
|
||||
printf("Livox SDK has been initialized.\n");
|
||||
|
||||
LivoxSdkVersion _sdkversion;
|
||||
GetLivoxSdkVersion(&_sdkversion);
|
||||
printf("Livox SDK version %d.%d.%d .\n", _sdkversion.major, _sdkversion.minor, _sdkversion.patch);
|
||||
|
||||
memset(devices, 0, sizeof(devices));
|
||||
|
||||
/** Set the callback function receiving broadcast message from Livox LiDAR. */
|
||||
SetBroadcastCallback(OnDeviceBroadcast);
|
||||
|
||||
/** Set the callback function called when device state change,
|
||||
* which means connection/disconnection and changing of LiDAR state.
|
||||
*/
|
||||
SetDeviceStateUpdateCallback(OnDeviceChange);
|
||||
|
||||
/** Start the device discovering routine. */
|
||||
if (!Start()) {
|
||||
Uninit();
|
||||
return -1;
|
||||
}
|
||||
printf("Start discovering device.\n");
|
||||
|
||||
#ifdef WIN32
|
||||
Sleep(2000);
|
||||
#else
|
||||
sleep(2);
|
||||
#endif
|
||||
|
||||
if (broadcast_code_rev.size() != 0)
|
||||
broadcast_code_list = broadcast_code_rev;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
condition_variable.wait(lock);
|
||||
}
|
||||
|
||||
printf("Start initialize lvx file.\n");
|
||||
if (!lvx_file_handler.InitLvxFile()) {
|
||||
Uninit();
|
||||
return -1;
|
||||
}
|
||||
lvx_file_handler.InitLvxFileHeader();
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; i < lvx_file_save_time * FRAME_RATE; ++i) {
|
||||
std::list<LvxBasePackDetail> point_packet_list_temp;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
condition_variable.wait(lock);
|
||||
point_packet_list_temp.swap(point_packet_list);
|
||||
}
|
||||
|
||||
printf("Finish save %d frame to lvx file.\n", i);
|
||||
lvx_file_handler.SaveFrameToLvxFile(point_packet_list_temp);
|
||||
}
|
||||
|
||||
lvx_file_handler.CloseLvxFile();
|
||||
|
||||
for (i = 0; i < kMaxLidarCount; ++i) {
|
||||
if (devices[i].device_state == kDeviceStateSampling) {
|
||||
/** Stop the sampling of Livox LiDAR. */
|
||||
LidarStopSampling(devices[i].handle, OnStopSampleCallback, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/** Uninitialize Livox-SDK. */
|
||||
Uninit();
|
||||
}
|
||||
+2596
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||
#define RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_iterators.hpp This file contains rapidxml iterators
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
//! Iterator of child nodes of xml_node
|
||||
template<class Ch>
|
||||
class node_iterator
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
typedef typename xml_node<Ch> value_type;
|
||||
typedef typename xml_node<Ch> &reference;
|
||||
typedef typename xml_node<Ch> *pointer;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::bidirectional_iterator_tag iterator_category;
|
||||
|
||||
node_iterator()
|
||||
: m_node(0)
|
||||
{
|
||||
}
|
||||
|
||||
node_iterator(xml_node<Ch> *node)
|
||||
: m_node(node->first_node())
|
||||
{
|
||||
}
|
||||
|
||||
reference operator *() const
|
||||
{
|
||||
assert(m_node);
|
||||
return *m_node;
|
||||
}
|
||||
|
||||
pointer operator->() const
|
||||
{
|
||||
assert(m_node);
|
||||
return m_node;
|
||||
}
|
||||
|
||||
node_iterator& operator++()
|
||||
{
|
||||
assert(m_node);
|
||||
m_node = m_node->next_sibling();
|
||||
return *this;
|
||||
}
|
||||
|
||||
node_iterator operator++(int)
|
||||
{
|
||||
node_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
node_iterator& operator--()
|
||||
{
|
||||
assert(m_node && m_node->previous_sibling());
|
||||
m_node = m_node->previous_sibling();
|
||||
return *this;
|
||||
}
|
||||
|
||||
node_iterator operator--(int)
|
||||
{
|
||||
node_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator ==(const node_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_node == rhs.m_node;
|
||||
}
|
||||
|
||||
bool operator !=(const node_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_node != rhs.m_node;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
xml_node<Ch> *m_node;
|
||||
|
||||
};
|
||||
|
||||
//! Iterator of child attributes of xml_node
|
||||
template<class Ch>
|
||||
class attribute_iterator
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
typedef typename xml_attribute<Ch> value_type;
|
||||
typedef typename xml_attribute<Ch> &reference;
|
||||
typedef typename xml_attribute<Ch> *pointer;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::bidirectional_iterator_tag iterator_category;
|
||||
|
||||
attribute_iterator()
|
||||
: m_attribute(0)
|
||||
{
|
||||
}
|
||||
|
||||
attribute_iterator(xml_node<Ch> *node)
|
||||
: m_attribute(node->first_attribute())
|
||||
{
|
||||
}
|
||||
|
||||
reference operator *() const
|
||||
{
|
||||
assert(m_attribute);
|
||||
return *m_attribute;
|
||||
}
|
||||
|
||||
pointer operator->() const
|
||||
{
|
||||
assert(m_attribute);
|
||||
return m_attribute;
|
||||
}
|
||||
|
||||
attribute_iterator& operator++()
|
||||
{
|
||||
assert(m_attribute);
|
||||
m_attribute = m_attribute->next_attribute();
|
||||
return *this;
|
||||
}
|
||||
|
||||
attribute_iterator operator++(int)
|
||||
{
|
||||
attribute_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
attribute_iterator& operator--()
|
||||
{
|
||||
assert(m_attribute && m_attribute->previous_attribute());
|
||||
m_attribute = m_attribute->previous_attribute();
|
||||
return *this;
|
||||
}
|
||||
|
||||
attribute_iterator operator--(int)
|
||||
{
|
||||
attribute_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator ==(const attribute_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_attribute == rhs.m_attribute;
|
||||
}
|
||||
|
||||
bool operator !=(const attribute_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_attribute != rhs.m_attribute;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
xml_attribute<Ch> *m_attribute;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,421 @@
|
||||
#ifndef RAPIDXML_PRINT_HPP_INCLUDED
|
||||
#define RAPIDXML_PRINT_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_print.hpp This file contains rapidxml printer implementation
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
|
||||
// Only include streams if not disabled
|
||||
#ifndef RAPIDXML_NO_STREAMS
|
||||
#include <ostream>
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Printing flags
|
||||
|
||||
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Internal
|
||||
|
||||
//! \cond internal
|
||||
namespace internal
|
||||
{
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Internal character operations
|
||||
|
||||
// Copy characters from given range to given output iterator
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
|
||||
{
|
||||
while (begin != end)
|
||||
*out++ = *begin++;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Copy characters from given range to given output iterator and expand
|
||||
// characters into references (< > ' " &)
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
|
||||
{
|
||||
while (begin != end)
|
||||
{
|
||||
if (*begin == noexpand)
|
||||
{
|
||||
*out++ = *begin; // No expansion, copy character
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (*begin)
|
||||
{
|
||||
case Ch('<'):
|
||||
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('>'):
|
||||
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('\''):
|
||||
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('"'):
|
||||
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('&'):
|
||||
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
|
||||
break;
|
||||
default:
|
||||
*out++ = *begin; // No expansion, copy character
|
||||
}
|
||||
}
|
||||
++begin; // Step to next character
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fill given output iterator with repetitions of the same character
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt fill_chars(OutIt out, int n, Ch ch)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Find character
|
||||
template<class Ch, Ch ch>
|
||||
inline bool find_char(const Ch *begin, const Ch *end)
|
||||
{
|
||||
while (begin != end)
|
||||
if (*begin++ == ch)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Internal printing operations
|
||||
|
||||
// Print node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
// Print proper node type
|
||||
switch (node->type())
|
||||
{
|
||||
|
||||
// Document
|
||||
case node_document:
|
||||
out = print_children(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Element
|
||||
case node_element:
|
||||
out = print_element_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Data
|
||||
case node_data:
|
||||
out = print_data_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// CDATA
|
||||
case node_cdata:
|
||||
out = print_cdata_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Declaration
|
||||
case node_declaration:
|
||||
out = print_declaration_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Comment
|
||||
case node_comment:
|
||||
out = print_comment_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Doctype
|
||||
case node_doctype:
|
||||
out = print_doctype_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Pi
|
||||
case node_pi:
|
||||
out = print_pi_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Unknown
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// If indenting not disabled, add line break after node
|
||||
if (!(flags & print_no_indenting))
|
||||
*out = Ch('\n'), ++out;
|
||||
|
||||
// Return modified iterator
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print children of the node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
|
||||
out = print_node(out, child, flags, indent);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print attributes of the node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
|
||||
{
|
||||
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
|
||||
{
|
||||
if (attribute->name() && attribute->value())
|
||||
{
|
||||
// Print attribute name
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
|
||||
*out = Ch('='), ++out;
|
||||
// Print attribute value using appropriate quote type
|
||||
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
|
||||
{
|
||||
*out = Ch('\''), ++out;
|
||||
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
|
||||
*out = Ch('\''), ++out;
|
||||
}
|
||||
else
|
||||
{
|
||||
*out = Ch('"'), ++out;
|
||||
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
|
||||
*out = Ch('"'), ++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print data node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_data);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print data node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_cdata);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'); ++out;
|
||||
*out = Ch('!'); ++out;
|
||||
*out = Ch('['); ++out;
|
||||
*out = Ch('C'); ++out;
|
||||
*out = Ch('D'); ++out;
|
||||
*out = Ch('A'); ++out;
|
||||
*out = Ch('T'); ++out;
|
||||
*out = Ch('A'); ++out;
|
||||
*out = Ch('['); ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch(']'); ++out;
|
||||
*out = Ch(']'); ++out;
|
||||
*out = Ch('>'); ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print element node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_element);
|
||||
|
||||
// Print element name and attributes, if any
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
out = print_attributes(out, node, flags);
|
||||
|
||||
// If node is childless
|
||||
if (node->value_size() == 0 && !node->first_node())
|
||||
{
|
||||
// Print childless node tag ending
|
||||
*out = Ch('/'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Print normal node tag ending
|
||||
*out = Ch('>'), ++out;
|
||||
|
||||
// Test if node contains a single data node only (and no other nodes)
|
||||
xml_node<Ch> *child = node->first_node();
|
||||
if (!child)
|
||||
{
|
||||
// If node has no children, only print its value without indenting
|
||||
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||
}
|
||||
else if (child->next_sibling() == 0 && child->type() == node_data)
|
||||
{
|
||||
// If node has a sole data child, only print its value without indenting
|
||||
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Print all children with full indenting
|
||||
if (!(flags & print_no_indenting))
|
||||
*out = Ch('\n'), ++out;
|
||||
out = print_children(out, node, flags, indent + 1);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
}
|
||||
|
||||
// Print node end
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('/'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
*out = Ch('>'), ++out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print declaration node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
// Print declaration start
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('x'), ++out;
|
||||
*out = Ch('m'), ++out;
|
||||
*out = Ch('l'), ++out;
|
||||
|
||||
// Print attributes
|
||||
out = print_attributes(out, node, flags);
|
||||
|
||||
// Print declaration end
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print comment node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_comment);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('!'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print doctype node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_doctype);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('!'), ++out;
|
||||
*out = Ch('D'), ++out;
|
||||
*out = Ch('O'), ++out;
|
||||
*out = Ch('C'), ++out;
|
||||
*out = Ch('T'), ++out;
|
||||
*out = Ch('Y'), ++out;
|
||||
*out = Ch('P'), ++out;
|
||||
*out = Ch('E'), ++out;
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print pi node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_pi);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('?'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
//! \endcond
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Printing
|
||||
|
||||
//! Prints XML to given output iterator.
|
||||
//! \param out Output iterator to print to.
|
||||
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||
//! \param flags Flags controlling how XML is printed.
|
||||
//! \return Output iterator pointing to position immediately after last character of printed text.
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
|
||||
{
|
||||
return internal::print_node(out, &node, flags, 0);
|
||||
}
|
||||
|
||||
#ifndef RAPIDXML_NO_STREAMS
|
||||
|
||||
//! Prints XML to given output stream.
|
||||
//! \param out Output stream to print to.
|
||||
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||
//! \param flags Flags controlling how XML is printed.
|
||||
//! \return Output stream.
|
||||
template<class Ch>
|
||||
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
|
||||
{
|
||||
print(std::ostream_iterator<Ch>(out), node, flags);
|
||||
return out;
|
||||
}
|
||||
|
||||
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
|
||||
//! \param out Output stream to print to.
|
||||
//! \param node Node to be printed.
|
||||
//! \return Output stream.
|
||||
template<class Ch>
|
||||
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
|
||||
{
|
||||
return print(out, node);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
#ifndef RAPIDXML_UTILS_HPP_INCLUDED
|
||||
#define RAPIDXML_UTILS_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful
|
||||
//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
//! Represents data loaded from a file
|
||||
template<class Ch = char>
|
||||
class file
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
//! Loads file into the memory. Data will be automatically destroyed by the destructor.
|
||||
//! \param filename Filename to load.
|
||||
file(const char *filename)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
// Open stream
|
||||
basic_ifstream<Ch> stream(filename, ios::binary);
|
||||
if (!stream)
|
||||
throw runtime_error(string("cannot open file ") + filename);
|
||||
stream.unsetf(ios::skipws);
|
||||
|
||||
// Determine stream size
|
||||
stream.seekg(0, ios::end);
|
||||
size_t size = stream.tellg();
|
||||
stream.seekg(0);
|
||||
|
||||
// Load data and add terminating 0
|
||||
m_data.resize(size + 1);
|
||||
stream.read(&m_data.front(), static_cast<streamsize>(size));
|
||||
m_data[size] = 0;
|
||||
}
|
||||
|
||||
//! Loads file into the memory. Data will be automatically destroyed by the destructor
|
||||
//! \param stream Stream to load from
|
||||
file(std::basic_istream<Ch> &stream)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
// Load data and add terminating 0
|
||||
stream.unsetf(ios::skipws);
|
||||
m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
|
||||
if (stream.fail() || stream.bad())
|
||||
throw runtime_error("error reading stream");
|
||||
m_data.push_back(0);
|
||||
}
|
||||
|
||||
//! Gets file data.
|
||||
//! \return Pointer to data of file.
|
||||
Ch *data()
|
||||
{
|
||||
return &m_data.front();
|
||||
}
|
||||
|
||||
//! Gets file data.
|
||||
//! \return Pointer to data of file.
|
||||
const Ch *data() const
|
||||
{
|
||||
return &m_data.front();
|
||||
}
|
||||
|
||||
//! Gets file data size.
|
||||
//! \return Size of file data, in characters.
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::vector<Ch> m_data; // File data
|
||||
|
||||
};
|
||||
|
||||
//! Counts children of node. Time complexity is O(n).
|
||||
//! \return Number of children of node
|
||||
template<class Ch>
|
||||
inline std::size_t count_children(xml_node<Ch> *node)
|
||||
{
|
||||
xml_node<Ch> *child = node->first_node();
|
||||
std::size_t count = 0;
|
||||
while (child)
|
||||
{
|
||||
++count;
|
||||
child = child->next_sibling();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//! Counts attributes of node. Time complexity is O(n).
|
||||
//! \return Number of attributes of node
|
||||
template<class Ch>
|
||||
inline std::size_t count_attributes(xml_node<Ch> *node)
|
||||
{
|
||||
xml_attribute<Ch> *attr = node->first_attribute();
|
||||
std::size_t count = 0;
|
||||
while (attr)
|
||||
{
|
||||
++count;
|
||||
attr = attr->next_attribute();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user