1561 lines
52 KiB
C++
1561 lines
52 KiB
C++
|
|
/*
|
|||
|
|
----
|
|||
|
|
This file is part of SECONDO.
|
|||
|
|
|
|||
|
|
Copyright (C) 2019,
|
|||
|
|
Faculty of Mathematics and Computer Science,
|
|||
|
|
Database Systems for New Applications.
|
|||
|
|
|
|||
|
|
SECONDO is free software; you can redistribute it and/or modify
|
|||
|
|
it under the terms of the GNU General Public License as published by
|
|||
|
|
the Free Software Foundation; either version 2 of the License, or
|
|||
|
|
(at your option) any later version.
|
|||
|
|
|
|||
|
|
SECONDO is distributed in the hope that it will be useful,
|
|||
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
|
GNU General Public License for more details.
|
|||
|
|
|
|||
|
|
You should have received a copy of the GNU General Public License
|
|||
|
|
along with SECONDO; if not, write to the Free Software
|
|||
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
//[<] [\ensuremath{<}]
|
|||
|
|
//[>] [\ensuremath{>}]
|
|||
|
|
|
|||
|
|
\setcounter{tocdepth}{3}
|
|||
|
|
\tableofcontents
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
1 The Pointcloud Type Constructor
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
#include "tcPointcloud2.h"
|
|||
|
|
|
|||
|
|
#include "SecondoSystem.h"
|
|||
|
|
#include "ListUtils.h"
|
|||
|
|
|
|||
|
|
using namespace pointcloud2;
|
|||
|
|
|
|||
|
|
extern NestedList *nl;
|
|||
|
|
|
|||
|
|
|
|||
|
|
double Pointcloud2::CELL_SIZE_IN_M = 0.15;
|
|||
|
|
double Pointcloud2::DELTA_ALT_IN_M = 5.0;
|
|||
|
|
bool Pointcloud2::NEIGHBOUR_CELLS = false;
|
|||
|
|
double Pointcloud2::THRESHOLD_MERGE = 0.5;
|
|||
|
|
double Pointcloud2::DISTANCE_SIG_MAXIMA = 0.3;
|
|||
|
|
double Pointcloud2::OVERLAP_RASTERS = 0.1;
|
|||
|
|
size_t Pointcloud2::MIN_GROUND_CELLS = 100;
|
|||
|
|
size_t Pointcloud2::MAX_CELLS_AT_EDGE = 3;
|
|||
|
|
bool Pointcloud2::SPLIT = true;
|
|||
|
|
size_t Pointcloud2::MIN_OBJ_SIZE = 20;
|
|||
|
|
size_t Pointcloud2::MIN_LOC_MAX_SIZE = 5;
|
|||
|
|
double Pointcloud2::RASTER_CLASSIFY_EPSILON = 0.5;
|
|||
|
|
size_t Pointcloud2::RASTER_CLASSIFY_MINPTS = 2;
|
|||
|
|
size_t Pointcloud2::RASTER_CLASSIFY_SCANSERIES = 5;
|
|||
|
|
double Pointcloud2::RASTER_CLASSIFY_SCANSERIES_ADD = 0.5;
|
|||
|
|
size_t Pointcloud2::RASTER_CLASSIFY_NORMALIZE = 3;
|
|||
|
|
bool Pointcloud2::GET_RASTER_NOT_PC = false;
|
|||
|
|
size_t Pointcloud2::SWITCH_FEATURES = 127;
|
|||
|
|
|
|||
|
|
double Pointcloud2::MINIMUM_OBJECT_EXTENT = 2.0;
|
|||
|
|
size_t Pointcloud2::NEIGHBORHOOD_SIZE = 100;
|
|||
|
|
double Pointcloud2::SOFT_DIFFUSION = 0.67;
|
|||
|
|
double Pointcloud2::ROUGH_DIFFUSION = 0.25;
|
|||
|
|
|
|||
|
|
/* min and max values can be entered in the following function: */
|
|||
|
|
std::unique_ptr<Params> Pointcloud2::params = Pointcloud2::initParams();
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
2 Secondo TypeConstrucor Interface
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
TypeConstructor Pointcloud2::typeConstructor(
|
|||
|
|
"pointcloud2", // name of the type in SECONDO
|
|||
|
|
Pointcloud2::Property, // property function describing signature
|
|||
|
|
Pointcloud2::Out, Pointcloud2::In, // Out and In functions
|
|||
|
|
0, 0, // SaveToList, RestoreFromList functions (forget them)
|
|||
|
|
Pointcloud2::Create, Pointcloud2::Delete, // object creation
|
|||
|
|
// and deletion
|
|||
|
|
Pointcloud2::Open, Pointcloud2::Save, // object open, save
|
|||
|
|
Pointcloud2::Close, Pointcloud2::Clone, // close, and clone
|
|||
|
|
Pointcloud2::Cast,
|
|||
|
|
Pointcloud2::SizeOf,
|
|||
|
|
Pointcloud2::TypeCheck);
|
|||
|
|
|
|||
|
|
ListExpr Pointcloud2::Property() {
|
|||
|
|
|
|||
|
|
return (nl->TwoElemList(
|
|||
|
|
nl->FourElemList(
|
|||
|
|
nl->StringAtom("Signature"),
|
|||
|
|
nl->StringAtom("Example Type List"),
|
|||
|
|
nl->StringAtom("List Rep"),
|
|||
|
|
nl->StringAtom("Example List")),
|
|||
|
|
nl->FourElemList(
|
|||
|
|
nl->StringAtom("->SIMPLE"),
|
|||
|
|
nl->StringAtom("(" + BasicType() + " (RefSys TupType))" ),
|
|||
|
|
nl->StringAtom("((x_1 y_1 z_1 (tup_1))*)"),
|
|||
|
|
nl->StringAtom("((1 1 1))"))));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
ListExpr Pointcloud2::Out(ListExpr typeInfo, Word value) {
|
|||
|
|
Pointcloud2* pc2 = static_cast<Pointcloud2*>(value.addr);
|
|||
|
|
|
|||
|
|
if (!pc2->isDefined()) {
|
|||
|
|
return listutils::getUndefined();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const auto secondTypeInfo = nl->Second(typeInfo);
|
|||
|
|
const ListExpr typeInfo2 = listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
|
|||
|
|
const bool hasTuple = nl->HasLength(typeInfo2, 2);
|
|||
|
|
|
|||
|
|
const ListExpr tupleTypeInfo = hasTuple ?
|
|||
|
|
nl->OneElemList(nl->Second(typeInfo2)) : nl->Empty();
|
|||
|
|
// this is how tuple->Out(...) needs tupleTypeInfo
|
|||
|
|
// (see example in Pointcloud2::In function)
|
|||
|
|
|
|||
|
|
ListExpr result = nl->TheEmptyList();
|
|||
|
|
ListExpr last = nl->TheEmptyList();
|
|||
|
|
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
pc2->_pointFile->SelectAll(it);
|
|||
|
|
|
|||
|
|
SmiRecord record;
|
|||
|
|
while(it.Next(record))
|
|||
|
|
{
|
|||
|
|
PcPoint point;
|
|||
|
|
const int read = record.Read(&point, sizeof(PcPoint), 0);
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
assert(!(hasTuple && point._tupleId == 0));
|
|||
|
|
|
|||
|
|
ListExpr pcPointList;
|
|||
|
|
if(hasTuple)
|
|||
|
|
{
|
|||
|
|
Tuple * tuple = pc2->_relation->GetTuple(point._tupleId, false);
|
|||
|
|
const ListExpr tupleList = tuple->Out(tupleTypeInfo);
|
|||
|
|
delete tuple;
|
|||
|
|
|
|||
|
|
pcPointList = nl->FourElemList(
|
|||
|
|
nl->RealAtom(point._x),
|
|||
|
|
nl->RealAtom(point._y),
|
|||
|
|
nl->RealAtom(point._z),
|
|||
|
|
tupleList
|
|||
|
|
);
|
|||
|
|
}else{
|
|||
|
|
pcPointList = nl->ThreeElemList(
|
|||
|
|
nl->RealAtom(point._x),
|
|||
|
|
nl->RealAtom(point._y),
|
|||
|
|
nl->RealAtom(point._z)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(nl->IsEmpty(result))
|
|||
|
|
{
|
|||
|
|
result = nl->OneElemList(pcPointList);
|
|||
|
|
last = result;
|
|||
|
|
}else{
|
|||
|
|
last = nl->Append(last, pcPointList);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Word Pointcloud2::In(ListExpr typeInfo, ListExpr value, int errorPos,
|
|||
|
|
ListExpr& errorInfo, bool& correct) {
|
|||
|
|
correct = true;
|
|||
|
|
Word w;
|
|||
|
|
|
|||
|
|
const auto secondTypeInfo = nl->Second(typeInfo);
|
|||
|
|
const ListExpr typeInfo2 = listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
|
|||
|
|
const bool hasTuple = nl->HasLength(typeInfo2, 2);
|
|||
|
|
const int expectedLength = hasTuple ? 4 : 3;
|
|||
|
|
const ListExpr tupleTypeInfo = hasTuple ?
|
|||
|
|
nl->OneElemList(nl->Second(typeInfo2)) : nl->Empty();
|
|||
|
|
// this is how Tuple::In(...) needs tupleTypeInfo!
|
|||
|
|
// Example: if the following Pointcloud2 is created:
|
|||
|
|
// [const (pointcloud2(EUCLID (tuple((Num int)(Val real)(Name string)))))
|
|||
|
|
// value ((1 2 3 (10 3.1415 "Anton")))];
|
|||
|
|
// then the variables are:
|
|||
|
|
// typeInfo:
|
|||
|
|
// ((152 0) (EUCLID ((3 0) ((Num (1 0)) (Val (1 1)) (Name (1 3))))))
|
|||
|
|
// typeInfo2: (EUCLID ((3 0) ((Num (1 0)) (Val (1 1)) (Name (1 3)))))
|
|||
|
|
// tupleTypeInfo: (((3 0) ((Num (1 0)) (Val (1 1)) (Name (1 3)))))
|
|||
|
|
// without the extra brackets, tupleTypeInfo will not be accepted
|
|||
|
|
|
|||
|
|
if(nl->AtomType(value) != NoAtom){
|
|||
|
|
correct = false;
|
|||
|
|
return w;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Pointcloud2* pc2 = new Pointcloud2(typeInfo2);
|
|||
|
|
w.addr = pc2;
|
|||
|
|
|
|||
|
|
if (!nl->IsEmpty(value) && listutils::isSymbolUndefined(nl->First(value))){
|
|||
|
|
pc2->setDefined(false);
|
|||
|
|
return w;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pc2->startInsert();
|
|||
|
|
while(!nl->IsEmpty(value))
|
|||
|
|
{
|
|||
|
|
ListExpr first = nl->First(value);
|
|||
|
|
value = nl->Rest(value);
|
|||
|
|
|
|||
|
|
if(nl->HasLength(first, expectedLength)
|
|||
|
|
&& listutils::isNumeric(nl->First(first))
|
|||
|
|
&& listutils::isNumeric(nl->Second(first))
|
|||
|
|
&& listutils::isNumeric(nl->Third(first)))
|
|||
|
|
{
|
|||
|
|
TupleId tupleId = 0;
|
|||
|
|
if(hasTuple)
|
|||
|
|
{
|
|||
|
|
Tuple* tuple = Tuple::In(tupleTypeInfo,
|
|||
|
|
nl->Fourth(first), errorPos, errorInfo, correct);
|
|||
|
|
if(!correct){
|
|||
|
|
Pointcloud2::Delete(typeInfo, w);
|
|||
|
|
return w;
|
|||
|
|
}else{
|
|||
|
|
tupleId = pc2->insert(tuple);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PcPoint p {
|
|||
|
|
listutils::getNumValue(nl->First(first)),
|
|||
|
|
listutils::getNumValue(nl->Second(first)),
|
|||
|
|
listutils::getNumValue(nl->Third(first)),
|
|||
|
|
tupleId
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
pc2->insert(p);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
pc2->finalizeInsert();
|
|||
|
|
|
|||
|
|
return w;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Word Pointcloud2::Create(const ListExpr typeInfo) {
|
|||
|
|
const auto secondTypeInfo = nl->Second(typeInfo);
|
|||
|
|
const ListExpr typeInfo2 = listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
|
|||
|
|
Pointcloud2* pc2 = new Pointcloud2(typeInfo2);
|
|||
|
|
|
|||
|
|
return SetWord(pc2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::Close(const ListExpr typeInfo, Word& w) {
|
|||
|
|
Pointcloud2* pc2 = static_cast<Pointcloud2*>(w.addr);
|
|||
|
|
delete pc2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Word Pointcloud2::Clone(const ListExpr typeInfo, const Word& w) {
|
|||
|
|
Pointcloud2* original = static_cast<Pointcloud2*>(w.addr);
|
|||
|
|
|
|||
|
|
const auto secondTypeInfo = nl->Second(typeInfo);
|
|||
|
|
const ListExpr typeInfo2 = listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
|
|||
|
|
Pointcloud2* clone = new Pointcloud2(typeInfo2);
|
|||
|
|
clone->copyAllFrom(original);
|
|||
|
|
|
|||
|
|
return Word(clone);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::Delete(const ListExpr typeInfo, Word& w) {
|
|||
|
|
Pointcloud2* pc2 = static_cast<Pointcloud2*>(w.addr);
|
|||
|
|
|
|||
|
|
if (pc2->_pointFile) {
|
|||
|
|
pc2->_pointFile->Drop();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pc2->_rtree) {
|
|||
|
|
pc2->_rtree->DeleteFile();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pc2->_relation) {
|
|||
|
|
pc2->_relation->Delete(true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
delete pc2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool Pointcloud2::Open(SmiRecord& valueRecord, size_t& offset,
|
|||
|
|
const ListExpr typeInfo, Word& value) {
|
|||
|
|
const auto secondTypeInfo = nl->Second(typeInfo);
|
|||
|
|
const ListExpr typeInfo2 = listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
try{
|
|||
|
|
Pointcloud2* pc2 = new Pointcloud2(valueRecord, offset,
|
|||
|
|
typeInfo2, value);
|
|||
|
|
value = SetWord(pc2);
|
|||
|
|
} catch(std::invalid_argument&)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool Pointcloud2::Save(SmiRecord& valueRecord, size_t& offset,
|
|||
|
|
const ListExpr typeInfo, Word& value) {
|
|||
|
|
|
|||
|
|
Pointcloud2* pc2 = static_cast<Pointcloud2*>(value.addr);
|
|||
|
|
|
|||
|
|
// save whether the pointcloud is defined
|
|||
|
|
valueRecord.Write(&pc2->_isDefined, sizeof(bool), offset);
|
|||
|
|
offset += sizeof(bool);
|
|||
|
|
|
|||
|
|
// save number of Points
|
|||
|
|
valueRecord.Write(&pc2->_pointCount, sizeof(size_t), offset);
|
|||
|
|
offset += sizeof(size_t);
|
|||
|
|
|
|||
|
|
// save pointFileID
|
|||
|
|
SmiFileId pointFileId = 0;
|
|||
|
|
if (pc2->hasValidPointFile()) {
|
|||
|
|
pointFileId = pc2->GetPointFileId();
|
|||
|
|
}
|
|||
|
|
valueRecord.Write(&pointFileId, sizeof(SmiFileId), offset);
|
|||
|
|
offset += sizeof(SmiFileId);
|
|||
|
|
|
|||
|
|
|
|||
|
|
if(!pc2->_rtree->Save(valueRecord, offset)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(pc2->_relation) {
|
|||
|
|
if(!pc2->_relation->Save(valueRecord, offset,
|
|||
|
|
pc2->getRelationTypeInfo(typeInfo))) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool Pointcloud2::TypeCheck(ListExpr type) {
|
|||
|
|
ListExpr dummy = nl->TheEmptyList();
|
|||
|
|
return TypeCheck(type, dummy);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool Pointcloud2::TypeCheck(ListExpr type, ListExpr& errorInfo) {
|
|||
|
|
if (nl->HasLength(type, 2)
|
|||
|
|
&& listutils::isSymbol(nl->First(type), BasicType())) {
|
|||
|
|
|
|||
|
|
const auto secondTypeInfo = nl->Second(type);
|
|||
|
|
/*
|
|||
|
|
At this point we deal with the problem that if you construct
|
|||
|
|
a pointcloud2-object in Secondo with the expression
|
|||
|
|
"const pointcloud2(RefSys)"
|
|||
|
|
the resulting type list will be flat, meaning
|
|||
|
|
(pointcloud2 RefSys) with NO nesting.
|
|||
|
|
However, if the cloud is constructed with an expression like
|
|||
|
|
"const pointcloud2(RefSys TupType)"
|
|||
|
|
the resulting list IS nested:
|
|||
|
|
(pointcloud2 (RefSys TupType)).
|
|||
|
|
Since there is no straightforward way to deal with this behaviour,
|
|||
|
|
the approach taken for now is to explicitly deal with this distinction
|
|||
|
|
in the type-related utility functions in the Pointcloud2::-namespace,
|
|||
|
|
and thus try to hide it from operator type-mappings.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
const ListExpr typeParams =
|
|||
|
|
listutils::isSymbol(secondTypeInfo) ?
|
|||
|
|
nl->OneElemList(secondTypeInfo)
|
|||
|
|
: secondTypeInfo;
|
|||
|
|
|
|||
|
|
// no symbol, but still an atom
|
|||
|
|
if (!nl->AtomType(typeParams == NoAtom)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (nl->IsEmpty(typeParams)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const bool isSymbol = listutils::isSymbol(nl->First(typeParams));
|
|||
|
|
if (!isSymbol) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
Referencesystem::toEnum(nl->SymbolValue(nl->First(typeParams)));
|
|||
|
|
} catch (std::invalid_argument&) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (nl->HasLength(typeParams, 1)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (nl->HasLength(typeParams, 2)
|
|||
|
|
&& Tuple::checkType(nl->Second(typeParams))) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
This function is only meant to be used in Operator Type Mappings.
|
|||
|
|
It does not actually test whether the argument is a Pointcloud,
|
|||
|
|
it assumes it is.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
bool Pointcloud2::isTupleCloud(const ListExpr cloudType ) {
|
|||
|
|
// (? ?)
|
|||
|
|
if (nl->HasLength(cloudType, 2)
|
|||
|
|
//(? (? ?))
|
|||
|
|
&& !listutils::isSymbol(nl->Second(cloudType))
|
|||
|
|
&& nl->HasLength(nl->Second(cloudType), 2)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Returns a tuple type-list, meaning of the form:
|
|||
|
|
(tuple ( (AttrName type) ... ))
|
|||
|
|
It assumes a Cloud isTupleCloud and will certainly crash otherwise.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
ListExpr Pointcloud2::getTupleType(const ListExpr cloudType) {
|
|||
|
|
return nl->Second(nl->Second(cloudType));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
This function works on Clouds having Attributes already,
|
|||
|
|
as well as on Clouds having none.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
ListExpr Pointcloud2::appendAttributesToCloud(const ListExpr cloudType,
|
|||
|
|
ListExpr appendage) {
|
|||
|
|
ListExpr typeParams;
|
|||
|
|
// see TypeCheck for an explanation of this differentiation
|
|||
|
|
if (isTupleCloud(cloudType)) {
|
|||
|
|
typeParams = nl->TwoElemList(
|
|||
|
|
// RefSys
|
|||
|
|
nl->First(nl->Second(cloudType)),
|
|||
|
|
// Tuple Type is of the form (tuple ( (AttrName type){*} ))
|
|||
|
|
nl->TwoElemList(
|
|||
|
|
nl->SymbolAtom(Tuple::BasicType()),
|
|||
|
|
listutils::concat(
|
|||
|
|
nl->Second(getTupleType(cloudType)),
|
|||
|
|
appendage)));
|
|||
|
|
} else {
|
|||
|
|
typeParams = nl->TwoElemList(
|
|||
|
|
// RefSys
|
|||
|
|
nl->Second(cloudType),
|
|||
|
|
// construct a Tuple Type
|
|||
|
|
nl->TwoElemList(
|
|||
|
|
nl->SymbolAtom(Tuple::BasicType()),
|
|||
|
|
appendage));
|
|||
|
|
}
|
|||
|
|
return nl->TwoElemList(
|
|||
|
|
nl->SymbolAtom(Pointcloud2::BasicType()),
|
|||
|
|
typeParams);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ListExpr Pointcloud2::cloudTypeWithParams(ListExpr refSys) {
|
|||
|
|
return nl->TwoElemList(
|
|||
|
|
nl->SymbolAtom(Pointcloud2::BasicType()),
|
|||
|
|
refSys);
|
|||
|
|
}
|
|||
|
|
ListExpr Pointcloud2::cloudTypeWithParams(ListExpr refSys, ListExpr tupType) {
|
|||
|
|
return nl->TwoElemList(
|
|||
|
|
nl->SymbolAtom(Pointcloud2::BasicType()),
|
|||
|
|
nl->TwoElemList(
|
|||
|
|
refSys,
|
|||
|
|
tupType));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
This function is intended to be used for return values of
|
|||
|
|
GetTupleResultType(Supplier s) from RelationCommon in VM.
|
|||
|
|
It takes a name and a list and returns the 0-based index
|
|||
|
|
of the Attribute, and -1 if the name was not found.
|
|||
|
|
(Note: type-values as returned from the supplier are
|
|||
|
|
have such shape:
|
|||
|
|
((152 0) (EUCLID ((3 0) ((ObjID (1 0)) (CatID (1 0))))))
|
|||
|
|
First of each thing might either represent a number in
|
|||
|
|
Secondo Catalogue or a Symbol. But who knows.
|
|||
|
|
Second might represent value - note that VMs typically
|
|||
|
|
return 0 and value is probably nothing of particular
|
|||
|
|
use to us in VM.)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
int Pointcloud2::findAttributeIndexInVM(std::string name,
|
|||
|
|
ListExpr attribute_list) {
|
|||
|
|
int j = -1; // Tuple::PutAttribute etc. expect 0-based indices
|
|||
|
|
ListExpr rest = attribute_list;
|
|||
|
|
while (!nl->IsEmpty(rest)) {
|
|||
|
|
ListExpr current = nl->First(rest);
|
|||
|
|
j++;
|
|||
|
|
if (nl->IsEqual(nl->First(current), name)) {
|
|||
|
|
return j;
|
|||
|
|
}
|
|||
|
|
rest = nl->Rest(rest);
|
|||
|
|
}
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void* Pointcloud2::Cast(void* addr) {
|
|||
|
|
return addr;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int Pointcloud2::SizeOf() {
|
|||
|
|
//Size of relation type is missing
|
|||
|
|
return sizeof(bool) +
|
|||
|
|
sizeof(size_t) +
|
|||
|
|
sizeof(SmiFileId) +
|
|||
|
|
SizeOfRTree<DIMENSIONS>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3 Utilities
|
|||
|
|
3.1 Constructors
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
Pointcloud2::Pointcloud2(const ListExpr& typeInfo) : _pointCount(0)
|
|||
|
|
{
|
|||
|
|
setReferencesystem(nl->First(typeInfo));
|
|||
|
|
|
|||
|
|
_pointFile = RecordFilePtr(new SmiRecordFile(true,
|
|||
|
|
sizeof(PcPoint),
|
|||
|
|
false));
|
|||
|
|
_pointFile->Create();
|
|||
|
|
|
|||
|
|
_rtree = RTreePtr(new RTreeType(PC2PAGE_SIZE, false));
|
|||
|
|
|
|||
|
|
//Relation is optional => no exception throwing
|
|||
|
|
if( nl->HasLength(typeInfo, 2) ) {
|
|||
|
|
createRelationpointer(nl->Second(typeInfo));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Pointcloud2::Pointcloud2(SmiRecord &valueRecord, size_t &offset,
|
|||
|
|
const ListExpr typeInfo, Word &value) {
|
|||
|
|
|
|||
|
|
setReferencesystem(nl->First(typeInfo));
|
|||
|
|
|
|||
|
|
// read whether pointcloud is defined
|
|||
|
|
valueRecord.Read(&_isDefined, sizeof(bool), offset);
|
|||
|
|
offset += sizeof(bool);
|
|||
|
|
|
|||
|
|
// read number of Points
|
|||
|
|
valueRecord.Read(&_pointCount, sizeof(size_t), offset);
|
|||
|
|
offset += sizeof(size_t);
|
|||
|
|
|
|||
|
|
// read pointFileID
|
|||
|
|
SmiFileId pointFileId;
|
|||
|
|
valueRecord.Read(&pointFileId, sizeof(SmiFileId), offset);
|
|||
|
|
offset += sizeof(SmiFileId);
|
|||
|
|
|
|||
|
|
_pointFile = RecordFilePtr(
|
|||
|
|
new SmiRecordFile(true, sizeof(PcPoint), false));
|
|||
|
|
if (pointFileId != 0) {
|
|||
|
|
_pointFile->Open(pointFileId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(!pointcloud2::UnbrokenOpenRTree<DIMENSIONS>(
|
|||
|
|
valueRecord, offset, typeInfo, value)) {
|
|||
|
|
throw std::invalid_argument("Could not restore RTree.");
|
|||
|
|
}
|
|||
|
|
_rtree = RTreePtr(static_cast<RTreeType*>(value.addr));
|
|||
|
|
|
|||
|
|
//Relation is optional => no exception thrPointcloud2Pointcloud2owing
|
|||
|
|
if( nl->HasLength(typeInfo, 2) ){
|
|||
|
|
setRelationpointer(valueRecord, offset, nl->Second(typeInfo));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.2 Getters and Setters
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
PcPointPtr Pointcloud2::getPoint(const size_t pointId) const {
|
|||
|
|
PcPointPtr point = std::make_shared<PcPoint>();
|
|||
|
|
getPoint(pointId, point.get());
|
|||
|
|
return point;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::getPoint(const size_t pointId, PcPoint* pcPoint) const {
|
|||
|
|
SmiRecord record;
|
|||
|
|
int RecordSelected = _pointFile->SelectRecord(pointId, record,
|
|||
|
|
SmiFile::ReadOnly);
|
|||
|
|
assert(RecordSelected); // pointId is 1-based!
|
|||
|
|
|
|||
|
|
const int read = record.Read(pcPoint, sizeof(PcPoint), 0);
|
|||
|
|
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SmiRecordFileIterator* Pointcloud2::getFileIterator() const {
|
|||
|
|
SmiRecordFileIterator* it = new SmiRecordFileIterator();
|
|||
|
|
_pointFile->SelectAll(*it);
|
|||
|
|
return it;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Tuple* Pointcloud2::getTuple(const TupleId& tupleId) const {
|
|||
|
|
return _relation->GetTuple(tupleId, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TupleType* Pointcloud2::getTupleType() const {
|
|||
|
|
return _relation->GetTupleType();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::updateTuple(Tuple *tuple,
|
|||
|
|
const std::vector<int>& changedIndices,
|
|||
|
|
const std::vector<Attribute*>& newAttrs) {
|
|||
|
|
_relation->UpdateTuple(tuple, changedIndices, newAttrs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int Pointcloud2::getMaxValueOfIntAttr(const int attrIndex,
|
|||
|
|
const int defaultValue, std::shared_ptr<BitArray> bitMask) const {
|
|||
|
|
|
|||
|
|
if (!hasRelation())
|
|||
|
|
return defaultValue;
|
|||
|
|
|
|||
|
|
int maxValue = defaultValue;
|
|||
|
|
|
|||
|
|
// iterate over the points in the SmiRecordFile (we cannot iterate
|
|||
|
|
// directly over the tuples since we need the SmiRecordIds)
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
_pointFile->SelectAll(it);
|
|||
|
|
SmiRecord record;
|
|||
|
|
while(it.Next(record)) {
|
|||
|
|
// get the next point
|
|||
|
|
PcPoint point;
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
|
|||
|
|
// get the corresponding tuple
|
|||
|
|
Tuple* tuple = _relation->GetTuple(point._tupleId, true);
|
|||
|
|
|
|||
|
|
CcInt* attrValueRef = (CcInt*)tuple->GetAttribute(attrIndex);
|
|||
|
|
if (attrValueRef->IsDefined()) {
|
|||
|
|
// if the attribute value differs from the default,
|
|||
|
|
// set the bit in the bitMask to false
|
|||
|
|
int attrValue = attrValueRef->GetIntval();
|
|||
|
|
if (attrValue != defaultValue)
|
|||
|
|
bitMask->set(record.GetId(), false);
|
|||
|
|
// update maxValue
|
|||
|
|
maxValue = MAX(maxValue, attrValue);
|
|||
|
|
}
|
|||
|
|
tuple->DeleteIfAllowed();
|
|||
|
|
}
|
|||
|
|
return maxValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::setReferencesystem(const ListExpr& expr)
|
|||
|
|
{
|
|||
|
|
const std::string referenceSytemAsString = nl->SymbolValue(expr);
|
|||
|
|
_referencesystem = Referencesystem::toEnum(referenceSytemAsString);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::setRelationpointer(SmiRecord &valueRecord, size_t &offset,
|
|||
|
|
const ListExpr& expr)
|
|||
|
|
{
|
|||
|
|
const ListExpr relType = getRelationTypeInfo(expr);
|
|||
|
|
_relation = RelationPtr(Relation::Open(valueRecord, offset, relType ));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::createRelationpointer(const ListExpr& expr)
|
|||
|
|
{
|
|||
|
|
const ListExpr relType = getRelationTypeInfo(expr);
|
|||
|
|
_relation = RelationPtr(new Relation(relType));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
Rect3 Pointcloud2::getBoundingBox() const
|
|||
|
|
{
|
|||
|
|
if (_isDefined){
|
|||
|
|
return _rtree->BoundingBox(); // returns undefined BoundingBox if empty
|
|||
|
|
}else{
|
|||
|
|
return BBox<DIMENSIONS>(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
size_t Pointcloud2::getPointCount() const
|
|||
|
|
{
|
|||
|
|
return _pointCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ListExpr Pointcloud2::getRelationTypeInfo(const ListExpr& tupleTypeInfo) const
|
|||
|
|
{
|
|||
|
|
return nl->TwoElemList(nl->SymbolAtom("rel"), tupleTypeInfo);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.3 Insert Functions
|
|||
|
|
|
|||
|
|
Starts the RTree bulk load mode, if possible (i.e. if the RTree is empty).
|
|||
|
|
After calling insert(...) multiple times, bulk load must be finalized by
|
|||
|
|
calling finalizeInsert().
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::startInsert() {
|
|||
|
|
_mayStartBulkMode = (_pointCount == 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Adds the given point and its tuple to the pointcloud. The tuple is being
|
|||
|
|
consumed (i.e. tuple->DeleteIfAllowed() is called), so any subsequent use of
|
|||
|
|
the tuple is forbidden (especially, tuple->DeleteIfAllowed() must not be
|
|||
|
|
called again!)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
size_t Pointcloud2::insert(PcPoint& point, Tuple* const tuple) {
|
|||
|
|
const TupleId tupleId = insert(tuple);
|
|||
|
|
point._tupleId = tupleId;
|
|||
|
|
return insert(point);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Adds the given tuple to the relation. The tuple is being
|
|||
|
|
consumed (i.e. tuple->DeleteIfAllowed() is called), so any subsequent use of
|
|||
|
|
the tuple is forbidden (especially, tuple->DeleteIfAllowed() must not be
|
|||
|
|
called again!)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
TupleId Pointcloud2::insert(Tuple* const tuple) {
|
|||
|
|
_relation->AppendTuple(tuple);
|
|||
|
|
TupleId id = tuple->GetTupleId();
|
|||
|
|
tuple->DeleteIfAllowed();
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
size_t Pointcloud2::insert(const PcPoint& point) {
|
|||
|
|
SmiRecord record;
|
|||
|
|
SmiRecordId pointId; // AppendRecord sets pointId to a free, 1-based ID
|
|||
|
|
|
|||
|
|
bool recordSelected = _pointFile->AppendRecord(pointId, record);
|
|||
|
|
assert(recordSelected);
|
|||
|
|
|
|||
|
|
const int written = record.Write(&point, sizeof(PcPoint), 0);
|
|||
|
|
assert(written == sizeof(PcPoint));
|
|||
|
|
|
|||
|
|
R_TreeLeafEntry<DIMENSIONS,SmiRecordId> entry(
|
|||
|
|
point.getBoundingBox(), pointId);
|
|||
|
|
|
|||
|
|
// if this is the first insert(const PcPoint&) after startInsert() was
|
|||
|
|
// called, try to start the RTree bulk load
|
|||
|
|
if (_mayStartBulkMode) {
|
|||
|
|
_bulkMode = _rtree->InitializeBulkLoad();
|
|||
|
|
_mayStartBulkMode = false; // do not try again
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_bulkMode)
|
|||
|
|
_rtree->InsertBulkLoad(entry);
|
|||
|
|
else
|
|||
|
|
_rtree->Insert(entry);
|
|||
|
|
|
|||
|
|
++_pointCount;
|
|||
|
|
return pointId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::insertTupleForExistingPoint(Tuple* tuple, PcPoint& point,
|
|||
|
|
SmiRecordId smiRecordId) {
|
|||
|
|
assert (point._tupleId == 0);
|
|||
|
|
|
|||
|
|
TupleId tupleId = insert(tuple);
|
|||
|
|
point._tupleId = tupleId;
|
|||
|
|
|
|||
|
|
SmiRecord record;
|
|||
|
|
bool recordSelected = _pointFile->SelectRecord(
|
|||
|
|
smiRecordId, record, SmiFile::Update);
|
|||
|
|
assert(recordSelected);
|
|||
|
|
|
|||
|
|
int offset = 0;
|
|||
|
|
int written = record.Write(&point, sizeof(PcPoint), offset);
|
|||
|
|
assert(written == sizeof(PcPoint));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Copies a point and a tuple (if there is any) from source to this pointcloud2.
|
|||
|
|
The source pointcloud2 must have the same structure as this pointcloud2.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::insert(PcPoint& point, const Pointcloud2* source) {
|
|||
|
|
if (_relation && (point._tupleId != 0)) {
|
|||
|
|
Tuple* tuple = source->getTuple(point._tupleId);
|
|||
|
|
insert(point, tuple); // gets new tupleId
|
|||
|
|
} else {
|
|||
|
|
insert(point);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Finalizes the RTree bulk load mode, making the RTree persistent.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::finalizeInsert() {
|
|||
|
|
if (_bulkMode)
|
|||
|
|
_rtree->FinalizeBulkLoad();
|
|||
|
|
_bulkMode = false;
|
|||
|
|
_mayStartBulkMode = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.4 Copy functions for restrict operators
|
|||
|
|
|
|||
|
|
3.4.1 copyAllFrom source
|
|||
|
|
|
|||
|
|
Copies all points and tuples (if there is any) from source to this pointcloud2.
|
|||
|
|
The source pointcloud2 must have the same structure as this pointcloud2.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::copyAllFrom(const Pointcloud2* source) {
|
|||
|
|
_isDefined = source->_isDefined;
|
|||
|
|
if (!_isDefined)
|
|||
|
|
return; // no need to copy anything else
|
|||
|
|
|
|||
|
|
// assert that this pointcloud2 is empty (otherwise we would need
|
|||
|
|
// to adjust the point._tupleId values!)
|
|||
|
|
assert(_pointCount == 0);
|
|||
|
|
_rtree->Clone(source->_rtree.get());
|
|||
|
|
|
|||
|
|
copy_file(source);
|
|||
|
|
|
|||
|
|
if (source->_relation) { // relation is optional
|
|||
|
|
Tuple *t;
|
|||
|
|
GenericRelationIterator *iter = source->_relation->MakeScan();
|
|||
|
|
while ((t = iter->GetNextTuple()) != 0) {
|
|||
|
|
_relation->AppendTuple(t);
|
|||
|
|
t->DeleteIfAllowed();
|
|||
|
|
}
|
|||
|
|
delete iter;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
This function performs a deep copy of a Pointcloud2. If the source cloud
|
|||
|
|
has a relation already, all tuples in it will be copied. If the given
|
|||
|
|
newAttributes vector contains elements, those attributes will be initialized
|
|||
|
|
in each tuple.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::copyAllWithAdditionalAttributes(
|
|||
|
|
const Pointcloud2* source,
|
|||
|
|
const std::vector<std::unique_ptr<Attribute>>& newAttributes) {
|
|||
|
|
|
|||
|
|
// if newAttributes is empty, we can simply use the copyAll method:
|
|||
|
|
if (newAttributes.size() == 0) {
|
|||
|
|
this->copyAllFrom(source);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_isDefined = source->_isDefined;
|
|||
|
|
if (!_isDefined)
|
|||
|
|
return; // no need to copy anything else
|
|||
|
|
|
|||
|
|
// assert that this pointcloud2 is empty (otherwise we would need
|
|||
|
|
// to adjust the point._tupleId values!)
|
|||
|
|
assert(_pointCount == 0);
|
|||
|
|
|
|||
|
|
// copy the RTree and the SmiFile
|
|||
|
|
_rtree->Clone(source->_rtree.get());
|
|||
|
|
copy_file(source);
|
|||
|
|
|
|||
|
|
// copy the relation only if the source Pointcloud2 has one
|
|||
|
|
if (source->_relation) {
|
|||
|
|
int newAttrsStartIndex = _relation->GetTupleType()->GetNoAttributes()
|
|||
|
|
- newAttributes.size();
|
|||
|
|
Tuple *sourceTuple;
|
|||
|
|
GenericRelationIterator *iter = source->_relation->MakeScan();
|
|||
|
|
while ((sourceTuple = iter->GetNextTuple()) != 0) {
|
|||
|
|
Tuple* tup = this->createEmptyTuple();
|
|||
|
|
// copy existing attributes from source tuple
|
|||
|
|
for (int i = 0; i < sourceTuple->GetNoAttributes(); ++i)
|
|||
|
|
tup->CopyAttribute(i, sourceTuple, i);
|
|||
|
|
// add new attributes
|
|||
|
|
int newAttrsIndex = newAttrsStartIndex;
|
|||
|
|
for (const std::unique_ptr<Attribute>& newAttribute : newAttributes)
|
|||
|
|
tup->PutAttribute(newAttrsIndex++, newAttribute->Clone());
|
|||
|
|
// append tuple to relation. TupleIds must be the same as in
|
|||
|
|
// the source cloud, otherwise we would need to update the
|
|||
|
|
// references in the _pointFile!
|
|||
|
|
_relation->AppendTuple(tup);
|
|||
|
|
assert (tup->GetTupleId() == sourceTuple->GetTupleId());
|
|||
|
|
sourceTuple->DeleteIfAllowed();
|
|||
|
|
tup->DeleteIfAllowed();
|
|||
|
|
}
|
|||
|
|
delete iter;
|
|||
|
|
} else {
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
_pointFile->SelectAll(it);
|
|||
|
|
|
|||
|
|
SmiRecord record;
|
|||
|
|
while (it.Next(record)) {
|
|||
|
|
PcPoint point;
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
int newAttrsIndex = 0;
|
|||
|
|
Tuple* tup = this->createEmptyTuple();
|
|||
|
|
for (const std::unique_ptr<Attribute>&
|
|||
|
|
newAttribute : newAttributes) {
|
|||
|
|
tup->PutAttribute(newAttrsIndex++, newAttribute->Clone());
|
|||
|
|
}
|
|||
|
|
insertTupleForExistingPoint(tup, point, record.GetId());
|
|||
|
|
// do NOT call tup->DeleteIfAllowed() here!
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Pointcloud2::copy_file(const Pointcloud2* source) {
|
|||
|
|
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
source->_pointFile->SelectAll(it);
|
|||
|
|
|
|||
|
|
SmiRecord record;
|
|||
|
|
while (it.Next(record)) {
|
|||
|
|
PcPoint point;
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
SmiRecord newRecord;
|
|||
|
|
SmiRecordId newRecordId; // AppendRecord sets newRecordId to a free,
|
|||
|
|
// 1-based ID.
|
|||
|
|
bool recSelected = _pointFile->AppendRecord(newRecordId, newRecord);
|
|||
|
|
assert (recSelected);
|
|||
|
|
SmiSize written = newRecord.Write(&point, sizeof(PcPoint), 0);
|
|||
|
|
assert(written == read);
|
|||
|
|
}
|
|||
|
|
_pointCount = source->_pointCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.4.2 copySelectionFrom source
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
size_t Pointcloud2::copySelectionFrom(const Pointcloud2* source,
|
|||
|
|
const PcBox* bbox) {
|
|||
|
|
|
|||
|
|
// copying from an undefined source makes this instance undefined, too
|
|||
|
|
if (!source->isDefined() || !_isDefined || !bbox->IsDefined()) {
|
|||
|
|
this->setDefined(false);
|
|||
|
|
return 0; // 0 points were copied
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// calculate the intersection
|
|||
|
|
const PcBox sourceBbox = source->getBoundingBox();
|
|||
|
|
const PcBox intersectionBbox = sourceBbox.Intersection(*bbox);
|
|||
|
|
|
|||
|
|
// are there any source points in the filter bbox?
|
|||
|
|
if (intersectionBbox.IsEmpty()) {
|
|||
|
|
return 0; // nothing to copy
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// is the source bbox completely inside the filter bbox?
|
|||
|
|
if (bbox->Contains(sourceBbox)) {
|
|||
|
|
copyAllFrom(source);
|
|||
|
|
return source->_pointCount; // all points were copied
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// determine whether to use the RTree or read the SmiRecordFile:
|
|||
|
|
|
|||
|
|
// using the RTree to find source points inside the bbox is only
|
|||
|
|
// worth the effort if the bbox is significantly smaller than the
|
|||
|
|
// total source bbox (e.g., below 10-20%). Note that reading RTree entries
|
|||
|
|
// is not alternative, but additional to reading the SmiRecordFile entries
|
|||
|
|
// and that RTree entries require 6 * 8 + 4 = 52 bytes (as they contain a
|
|||
|
|
// box rather than a point) while SmiRecords only need 3 * 8 + 4 = 28 bytes
|
|||
|
|
// (i.e. a given number of points require 85% more RTree blocks than
|
|||
|
|
// SmiRecordFile blocks!)
|
|||
|
|
constexpr double INTERSECTION_RATIO_THRESHOLD = 0.1;
|
|||
|
|
// TODO: in Performancetests einen geeigneten Wert ermitteln: 0.1? 0.2?
|
|||
|
|
|
|||
|
|
// also, using the RTree is only worth the effort if there are more
|
|||
|
|
// than just a few blocks in the SmiRecordFile:
|
|||
|
|
constexpr size_t POINT_COUNT_THRESHOLD = 8 * PC2PAGE_SIZE / sizeof(PcPoint);
|
|||
|
|
|
|||
|
|
const double intersectionRatio = intersectionBbox.Area()/sourceBbox.Area();
|
|||
|
|
const bool useRTree = (intersectionRatio < INTERSECTION_RATIO_THRESHOLD
|
|||
|
|
&& source->getPointCount() > POINT_COUNT_THRESHOLD);
|
|||
|
|
|
|||
|
|
cout << "intersection ratio = " << intersectionRatio;
|
|||
|
|
cout << ", source point count = " << source->getPointCount();
|
|||
|
|
cout << " => " << (useRTree ? "traversing RTree." :
|
|||
|
|
"reading SmiRecordFile") << endl;
|
|||
|
|
|
|||
|
|
const size_t initialPointCount = _pointCount;
|
|||
|
|
startInsert();
|
|||
|
|
if (useRTree) {
|
|||
|
|
// extend the bbox by the FACTOR which is used for the points
|
|||
|
|
// stored in the RTree, so wo can determine whether an RTree entry
|
|||
|
|
// is inside the bbox
|
|||
|
|
auto bboxExtended = std::unique_ptr<PcBox>(bbox->Clone());
|
|||
|
|
bboxExtended->Extend(FACTOR);
|
|||
|
|
|
|||
|
|
// traverse the RTree of the source pointcloud2 to find nodes and
|
|||
|
|
// points that are located inside the bboxExtended
|
|||
|
|
copySelectionFromRTree(source, bboxExtended.get(),
|
|||
|
|
source->_rtree->RootRecordId(), false);
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
// read points directly from the SmiRecordFile
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
source->_pointFile->SelectAll(it);
|
|||
|
|
SmiRecord record;
|
|||
|
|
PcPoint point;
|
|||
|
|
while(it.Next(record)) {
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
|
|||
|
|
// no +-FACTOR required here
|
|||
|
|
const double pointRect[] = { point._x, point._x,
|
|||
|
|
point._y, point._y,
|
|||
|
|
point._z, point._z };
|
|||
|
|
|
|||
|
|
if (bbox->Contains( {true, pointRect} ))
|
|||
|
|
insert(point, source);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
finalizeInsert();
|
|||
|
|
|
|||
|
|
// return the number of points that were added
|
|||
|
|
return _pointCount - initialPointCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.4.3 copySelectionFromRTree
|
|||
|
|
|
|||
|
|
traverse the RTree of the source pointcloud2 to find points that are located
|
|||
|
|
inside the given bbox. If isInsideBbox is true, it is clear from the
|
|||
|
|
node's bounding box that every point in the current node (and subtree!) is
|
|||
|
|
inside the given bbox (with no further checks necessary)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::copySelectionFromRTree(const Pointcloud2* source, PcBox* bbox,
|
|||
|
|
SmiRecordId adr, bool isInsideBbox) {
|
|||
|
|
|
|||
|
|
RTreeNodePtr node = source->_rtree->GetMyNode(adr, false,
|
|||
|
|
source->_rtree->MinEntries(0), source->_rtree->MaxEntries(0));
|
|||
|
|
|
|||
|
|
for (int j = 0; j < node->EntryCount(); ++j) {
|
|||
|
|
if (node->IsLeaf()) {
|
|||
|
|
const RTreeLeafEntry e = (RTreeLeafEntry&)(*node)[j];
|
|||
|
|
if (isInsideBbox || bbox->Contains(e.box)) {
|
|||
|
|
PcPoint point;
|
|||
|
|
size_t read;
|
|||
|
|
source->_pointFile->Read(e.info, &point, sizeof(PcPoint),
|
|||
|
|
0, read);
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
insert(point, source);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
RTreeInternalEntry e = (RTreeInternalEntry&)(*node)[j];
|
|||
|
|
if (bbox->Intersects(e.box)) {
|
|||
|
|
// determine whether the whole node/subtree is inside the bbox;
|
|||
|
|
// if so, further bbox->Contains checks can be omitted
|
|||
|
|
// on this node and its subtree
|
|||
|
|
const bool subtreeInside=isInsideBbox || bbox->Contains(e.box);
|
|||
|
|
copySelectionFromRTree(source, bbox, e.pointer, subtreeInside);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
delete node;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.4.2 copySelectionFrom source using a BitArray
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
size_t Pointcloud2::copySelectionFrom(const Pointcloud2* source,
|
|||
|
|
const BitArray& bitMap) {
|
|||
|
|
|
|||
|
|
// copying from an undefined source makes this instance undefined, too
|
|||
|
|
if (!source->isDefined() || !_isDefined) {
|
|||
|
|
this->setDefined(false);
|
|||
|
|
return 0; // 0 points were copied
|
|||
|
|
}
|
|||
|
|
assert (source->getPointCount() <= bitMap.getSize());
|
|||
|
|
|
|||
|
|
const size_t initialPointCount = _pointCount;
|
|||
|
|
|
|||
|
|
startInsert();
|
|||
|
|
// read points directly from the SmiRecordFile
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
source->_pointFile->SelectAll(it);
|
|||
|
|
SmiRecord record;
|
|||
|
|
PcPoint point;
|
|||
|
|
while(it.Next(record)) {
|
|||
|
|
if (!bitMap.get(record.GetId()))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
insert(point, source);
|
|||
|
|
}
|
|||
|
|
finalizeInsert();
|
|||
|
|
|
|||
|
|
// return the number of points that were added
|
|||
|
|
return _pointCount - initialPointCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.5 Cluster method
|
|||
|
|
|
|||
|
|
Clusters the points in this Pointcloud2 using DbScan and copies the points and
|
|||
|
|
their clusterIds to the given destination Pointcloud2 (destPc2).
|
|||
|
|
|
|||
|
|
For minClusterSizeToCopy, use 0 to copy all points, 1 to eliminate noise,
|
|||
|
|
or any other positive value to remove both noise and small clusters.
|
|||
|
|
|
|||
|
|
For each source attribute index, destAttrIndices[sourceAttrIndex]
|
|||
|
|
returns the corresponding index in destPc2 (or -1 if the attribute should
|
|||
|
|
not be copied to destPc2.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::cluster(const double eps, const size_t minPts,
|
|||
|
|
size_t minClusterSizeToCopy, Pointcloud2* destPc2,
|
|||
|
|
int clusterAttrIndex, std::vector<int> destAttrIndices) const {
|
|||
|
|
// TODO: falls this bereits ein "Cluster"-Attribut hat, sollte auch
|
|||
|
|
// this == destPc2 erlaubt (und dann unten berücksichtigt werden!
|
|||
|
|
|
|||
|
|
// TODO: offen ist noch, wie in dem Fall vorzugehen ist, dass die Punkte
|
|||
|
|
// nicht in den Arbeitsspeicher passen: dann soll laut Aufgabenstellung
|
|||
|
|
// der vorhandene RTree benutzt werden - aber was ist mit der ClusterId?
|
|||
|
|
// - Passt zumindest die in den Arbeitsspeicher?
|
|||
|
|
// - Oder soll grundsätzlich eine ClusterId im RTree / im SmiRecordEntry
|
|||
|
|
// vorgesehen werden?
|
|||
|
|
// und muss die Implementierung dafür dann doppelt vorliegen?
|
|||
|
|
|
|||
|
|
// DEBUG
|
|||
|
|
cout << endl << "Point count: " << _pointCount << endl;
|
|||
|
|
// for(size_t i = 1; i <= _pointCount; ++i) { // getPoint 1-based!
|
|||
|
|
// cout << getPoint(i)->toString() << endl;
|
|||
|
|
// }
|
|||
|
|
// cout << "Starting DBSCAN ..." << endl;
|
|||
|
|
|
|||
|
|
// fill the point data allocated in the main memory
|
|||
|
|
std::shared_ptr<std::vector<DbScanPoint<DIMENSIONS>>> pointSpace =
|
|||
|
|
std::make_shared<std::vector<DbScanPoint<DIMENSIONS>>>();
|
|||
|
|
pointSpace.get()->resize(_pointCount + 1);
|
|||
|
|
std::vector<PointIndex> indexOfSmiId =
|
|||
|
|
getAllPoints(*(pointSpace.get()), SEQUENCE_LENGTH_MAX);
|
|||
|
|
|
|||
|
|
// indexOfSmiId now contains index positions with which the points
|
|||
|
|
// were sorted into the pointSpace in RTree order (i.e. points from
|
|||
|
|
// the same RTree node have neighbor index positions).
|
|||
|
|
// For a given SmiRecordId id, indexOfId.get()[id] returns the unique
|
|||
|
|
// index position in the pointSpace.
|
|||
|
|
|
|||
|
|
// create a DbScan instance using the constant Pointcloud2::DIMENSIONS
|
|||
|
|
DbScan<DIMENSIONS> dbScan(eps, minPts, pointSpace, true);
|
|||
|
|
dbScan.run();
|
|||
|
|
|
|||
|
|
// -----------------------------------------------------
|
|||
|
|
// Transfer the result to the destination Pointcloud2
|
|||
|
|
|
|||
|
|
bool copyAllPoints = (minClusterSizeToCopy == 0) ||
|
|||
|
|
(minClusterSizeToCopy == 1 && dbScan.getNoiseCount() == 0);
|
|||
|
|
bool sourceHasTuples = hasRelation();
|
|||
|
|
bool destHasTuples = destPc2->hasRelation();
|
|||
|
|
|
|||
|
|
destPc2->_isDefined = _isDefined;
|
|||
|
|
destPc2->startInsert();
|
|||
|
|
// as an alternative to inserting points, cloning the RTree was tested:
|
|||
|
|
// if (copyAllPoints) { destPc2->_rtree->Clone(_rtree.get()); } else { ...
|
|||
|
|
// (pc. Pointcloud2::copyAllFrom()), however, this does not work -
|
|||
|
|
// there is a crash when the Pointcloud2 is closed and the RTree performs
|
|||
|
|
// WriteHeader.
|
|||
|
|
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
_pointFile->SelectAll(it); // iterate over source points
|
|||
|
|
|
|||
|
|
SmiRecord sourceRecord;
|
|||
|
|
SmiRecordId sourceSmiId;
|
|||
|
|
while(it.Next(sourceSmiId, sourceRecord)) {
|
|||
|
|
PcPoint point;
|
|||
|
|
const size_t read = sourceRecord.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
|
|||
|
|
// get the clusterId that DbScan has assigned to this point
|
|||
|
|
PointIndex pointSpaceIndex = indexOfSmiId[sourceSmiId];
|
|||
|
|
int clusterId = pointSpace.get()->at(pointSpaceIndex)._clusterId;
|
|||
|
|
|
|||
|
|
// if noise or small clusters should be filtered out, check if
|
|||
|
|
// point must be skipped
|
|||
|
|
if (!copyAllPoints && (clusterId == SCAN_NOISE ||
|
|||
|
|
dbScan.getClusterSize(clusterId) < minClusterSizeToCopy)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (destHasTuples) {
|
|||
|
|
// create tuple
|
|||
|
|
Tuple* destTuple = destPc2->createEmptyTuple();
|
|||
|
|
|
|||
|
|
if (sourceHasTuples) {
|
|||
|
|
// copy attributes from source tuple
|
|||
|
|
Tuple* sourceTuple = getTuple(point._tupleId);
|
|||
|
|
for (size_t i = 0; i < destAttrIndices.size(); i++) {
|
|||
|
|
int destIndex = destAttrIndices[i];
|
|||
|
|
if (destIndex >= 0) // otherwise, this attribute is omitted
|
|||
|
|
destTuple->CopyAttribute(i, sourceTuple, destIndex);
|
|||
|
|
}
|
|||
|
|
sourceTuple->DeleteIfAllowed();
|
|||
|
|
}
|
|||
|
|
// put cluster id to attribute and insert tuple to destPc2
|
|||
|
|
if (clusterAttrIndex >= 0) {
|
|||
|
|
destTuple->PutAttribute(clusterAttrIndex,
|
|||
|
|
new CcInt(true, clusterId));
|
|||
|
|
}
|
|||
|
|
// insert tuple to destPc2
|
|||
|
|
point._tupleId = destPc2->insert(destTuple);
|
|||
|
|
// do NOT call destTuple->DeleteIfAllowed() here!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
destPc2->insert(point);
|
|||
|
|
// do NOT call destTuple->DeleteIfAllowed(); here (done in ->insert)
|
|||
|
|
}
|
|||
|
|
destPc2->finalizeInsert();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.5.1 getAllPoints
|
|||
|
|
|
|||
|
|
Fills the preallocated array pointSpace with the coordinates of all points in
|
|||
|
|
this Pointcloud2. Rather than using the SmiFile order, the persistent RTree is
|
|||
|
|
used to create sequences of points in pointSpace. These sequences can be
|
|||
|
|
refined to a maximum size given by maxSequenceLength. If refineSequneces
|
|||
|
|
is false, the sequences will not be refined.
|
|||
|
|
|
|||
|
|
The return value indexOfSmiId is filled with index positions used to create
|
|||
|
|
this order. For a given SmiRecordId id, the value indexOfId[id]
|
|||
|
|
is a unique index position. All points from the same RTree node are in a
|
|||
|
|
sequence of index positions in pointSpace.
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
std::vector<PointIndex> Pointcloud2::getAllPoints(
|
|||
|
|
std::vector<DbScanPoint<DIMENSIONS>>& pointSpace,
|
|||
|
|
const int maxSequenceLength) const {
|
|||
|
|
|
|||
|
|
bool REPORT_TO_CONSOLE = false;
|
|||
|
|
|
|||
|
|
assert (pointSpace.size() == _pointCount + 1); // 1-based
|
|||
|
|
|
|||
|
|
// create vector to map SmiRecordIds to 1-based index positions
|
|||
|
|
// (SmiRecordIds are 1-based, therefore "_pointCount + 1");
|
|||
|
|
// initialize with value 0 in case of unused SmiRecordIds
|
|||
|
|
std::vector<PointIndex> indexOfSmiId =
|
|||
|
|
std::vector<PointIndex>( _pointCount + 1, 0 );
|
|||
|
|
|
|||
|
|
// initialize pointSpace with _isLastInSeq = false; getRTreeOrder()
|
|||
|
|
// assumes false is set and only stores true where applicable
|
|||
|
|
for (PointIndex i = 0; i <= _pointCount; ++i)
|
|||
|
|
pointSpace[i].initialize(false);
|
|||
|
|
|
|||
|
|
// --------------------------------
|
|||
|
|
// 1. get the order in which to store the points
|
|||
|
|
// (points in the same RTree node will be stored sequentially)
|
|||
|
|
|
|||
|
|
// calculate RTree order; this also sets _isLastInSeq in pointSpace
|
|||
|
|
// (although the points coordinates will only be read below)
|
|||
|
|
PointIndex index = 0;
|
|||
|
|
getRTreeOrder(_rtree->RootRecordId(), indexOfSmiId, index, pointSpace,
|
|||
|
|
maxSequenceLength);
|
|||
|
|
|
|||
|
|
if (REPORT_TO_CONSOLE) {
|
|||
|
|
for (size_t smiId = 1; smiId <= _pointCount; ++smiId) {
|
|||
|
|
size_t index = indexOfSmiId[smiId];
|
|||
|
|
cout << "SmiRecId " << smiId << ": index " << index;
|
|||
|
|
if (pointSpace[index]._isLastInSeq)
|
|||
|
|
cout << " (last in sequence)";
|
|||
|
|
cout << endl;
|
|||
|
|
}
|
|||
|
|
cout << endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --------------------------------
|
|||
|
|
// 2. read points to main memory
|
|||
|
|
|
|||
|
|
// read the points into the main memory, using the "rtree order"
|
|||
|
|
// which was stored in indexOfSmiId
|
|||
|
|
PcPoint point;
|
|||
|
|
for (size_t smiRecId = 1; smiRecId <= _pointCount; ++smiRecId) {
|
|||
|
|
// get the index at which to store this point in pointSpace
|
|||
|
|
PointIndex index = indexOfSmiId[smiRecId];
|
|||
|
|
DbScanPoint<DIMENSIONS>& destPoint = pointSpace[index];
|
|||
|
|
|
|||
|
|
// copy this point's coordinates to DbScanPoint (keeping _isLastInSeq
|
|||
|
|
// which was already calculated by getRTreeOrder above)
|
|||
|
|
getPoint(smiRecId, &point);
|
|||
|
|
double coords[DIMENSIONS] {point._x, point._y, point._z};
|
|||
|
|
destPoint.setCoords(coords);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (REPORT_TO_CONSOLE) {
|
|||
|
|
for(size_t i = 1; i <= _pointCount; ++i) { // pointSpace is 1-based
|
|||
|
|
cout << pointSpace[i].toString() << endl;
|
|||
|
|
if (pointSpace[i]._isLastInSeq)
|
|||
|
|
cout << "----------" << endl;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return indexOfSmiId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.5.2 getRTreeOrder
|
|||
|
|
|
|||
|
|
Fills indexOfSmiId with index positions by which the points can be sorted in
|
|||
|
|
RTree order (i.e. points in the same RTree node get neighbor index positions).
|
|||
|
|
For a given SmiRecordId id, indexOfId.get()[id] is a unique
|
|||
|
|
index position.
|
|||
|
|
|
|||
|
|
pointSpace.get()[index]._isLastInSeq is set to true for the last point in
|
|||
|
|
each sequence (note that index is the pointSpace index, not the SmiRecordId!)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::getRTreeOrder(SmiRecordId adr,
|
|||
|
|
std::vector<PointIndex>& indexOfSmiId, PointIndex& index,
|
|||
|
|
std::vector<DbScanPoint<DIMENSIONS>>& pointSpace,
|
|||
|
|
const int maxSequenceLength) const {
|
|||
|
|
RTreeNodePtr node = _rtree->GetMyNode(adr, false,
|
|||
|
|
_rtree->MinEntries(0), _rtree->MaxEntries(0));
|
|||
|
|
int entryCount = node->EntryCount();
|
|||
|
|
if (!node->IsLeaf()) {
|
|||
|
|
for (int i = 0; i < entryCount; ++i) {
|
|||
|
|
RTreeInternalEntry e = (RTreeInternalEntry&)(*node)[i];
|
|||
|
|
getRTreeOrder(e.pointer, indexOfSmiId, index, pointSpace,
|
|||
|
|
maxSequenceLength);
|
|||
|
|
}
|
|||
|
|
} else if (maxSequenceLength < 0 || entryCount <= maxSequenceLength) {
|
|||
|
|
// use all (up to 76) entries of the RTree node as one sequence
|
|||
|
|
for (int i = 0; i < entryCount; ++i) {
|
|||
|
|
const RTreeLeafEntry e = (RTreeLeafEntry&)(*node)[i];
|
|||
|
|
++index;
|
|||
|
|
indexOfSmiId[e.info] = index;
|
|||
|
|
}
|
|||
|
|
pointSpace[index]._isLastInSeq = true;
|
|||
|
|
} else {
|
|||
|
|
// insert the points from this RTree leaf into a temporary MMRTree
|
|||
|
|
// in order to split them into sequences of min..maxSeqLength points
|
|||
|
|
// (MMRTree expects 1 <= min <= max / 2)
|
|||
|
|
using MMRTreeT = mmrtree::RtreeT<DIMENSIONS, SmiRecordId>;
|
|||
|
|
size_t minSeqLength = maxSequenceLength / 2;
|
|||
|
|
std::unique_ptr<MMRTreeT> mmRTree(
|
|||
|
|
new MMRTreeT(minSeqLength, maxSequenceLength));
|
|||
|
|
for (int i = 0; i < entryCount; ++i) {
|
|||
|
|
const RTreeLeafEntry e = (RTreeLeafEntry&)(*node)[i];
|
|||
|
|
mmRTree->insert(e.box, e.info);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// traverse MMRTree, adding entries in indexOfSmiId and marking
|
|||
|
|
// the sequences in isLastInSeq
|
|||
|
|
std::unique_ptr<MMRTreeT::iterator> it { mmRTree->entries() };
|
|||
|
|
SmiRecordId const *smiId;
|
|||
|
|
uintptr_t lastNodeId = 0;
|
|||
|
|
while( (smiId = it->next()) != 0) {
|
|||
|
|
// is this entry from a different MMTree node?
|
|||
|
|
uintptr_t nodeId = it->getNodeId();
|
|||
|
|
if (nodeId != lastNodeId) {
|
|||
|
|
// mark the previous entry as last in sequence
|
|||
|
|
// (note that this happens before ++index!)
|
|||
|
|
pointSpace[index]._isLastInSeq = true;
|
|||
|
|
}
|
|||
|
|
// add entry to indexOfSmiId
|
|||
|
|
++index;
|
|||
|
|
indexOfSmiId[*smiId] = index;
|
|||
|
|
lastNodeId = nodeId;
|
|||
|
|
}
|
|||
|
|
pointSpace[index]._isLastInSeq = true;
|
|||
|
|
}
|
|||
|
|
delete node;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.5 Temporary Fixes
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
template <unsigned dim>
|
|||
|
|
bool pointcloud2::UnbrokenOpenRTree( SmiRecord& valueRecord,
|
|||
|
|
size_t& offset,
|
|||
|
|
const ListExpr typeInfo,
|
|||
|
|
Word& value )
|
|||
|
|
{
|
|||
|
|
SmiFileId fileid;
|
|||
|
|
valueRecord.Read( &fileid, sizeof( SmiFileId ), offset );
|
|||
|
|
offset += sizeof( SmiFileId );
|
|||
|
|
|
|||
|
|
R_Tree<dim, TupleId> *rtree = new R_Tree<dim, TupleId>( fileid, false );
|
|||
|
|
value = SetWord( rtree );
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
3.4 merge operator functions
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
void Pointcloud2::merge(const Pointcloud2* source,
|
|||
|
|
const bool hasTuple,
|
|||
|
|
const std::vector<int> sourceAttrIndex,
|
|||
|
|
const ListExpr tupleTypeInfo) {
|
|||
|
|
if (!source->_isDefined)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
const int attrCount = hasTuple ? sourceAttrIndex.size() : 0;
|
|||
|
|
|
|||
|
|
SmiRecordFileIterator it;
|
|||
|
|
source->_pointFile->SelectAll(it);
|
|||
|
|
SmiRecord record;
|
|||
|
|
// do NOT call startInsert() here, the caller of this method does!
|
|||
|
|
while(it.Next(record)) {
|
|||
|
|
PcPoint point;
|
|||
|
|
const size_t read = record.Read(&point, sizeof(PcPoint));
|
|||
|
|
assert(read == sizeof(PcPoint));
|
|||
|
|
if (hasTuple) {
|
|||
|
|
Tuple * tuplesource = source->_relation->GetTuple(
|
|||
|
|
point._tupleId, true);
|
|||
|
|
Tuple * tupletarget = new Tuple(tupleTypeInfo);
|
|||
|
|
for (int i = 0; i < attrCount; i++) {
|
|||
|
|
tupletarget->CopyAttribute(sourceAttrIndex[i]-1, tuplesource,i);
|
|||
|
|
}
|
|||
|
|
insert(point, tupletarget);
|
|||
|
|
tuplesource->DeleteIfAllowed();
|
|||
|
|
} else {
|
|||
|
|
insert(point);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// do NOT call finalizeInsert() here, the caller of this method does!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
std::unique_ptr<Params> Pointcloud2::initParams() {
|
|||
|
|
std::unique_ptr<Params> res= std::unique_ptr<Params>(new Params());
|
|||
|
|
|
|||
|
|
// intParam_t MIN_INT = std::numeric_limits<intParam_t>::min();
|
|||
|
|
intParam_t MAX_INT = std::numeric_limits<intParam_t>::max();
|
|||
|
|
double MIN_REAL = -std::numeric_limits<double>::max();
|
|||
|
|
double MAX_REAL = std::numeric_limits<double>::max();
|
|||
|
|
|
|||
|
|
|
|||
|
|
Param::CONTEXT raster = Param::CONTEXT::analyzeRaster;
|
|||
|
|
|
|||
|
|
res->add( { "CELL_SIZE_IN_M", &CELL_SIZE_IN_M, raster,
|
|||
|
|
0.01, 100.0, "Raster: cell size in m" });
|
|||
|
|
|
|||
|
|
res->add( { "DELTA_ALT_IN_M", &DELTA_ALT_IN_M, raster,
|
|||
|
|
0.01, 100.0, "Raster: max delta alt for flooding in m" });
|
|||
|
|
|
|||
|
|
res->add( { "NEIGHBOUR_CELLS", &NEIGHBOUR_CELLS, raster,
|
|||
|
|
"Raster: using 4 oder 8 neighbor cells" });
|
|||
|
|
|
|||
|
|
res->add( { "THRESHOLD_MERGE", &THRESHOLD_MERGE, raster,
|
|||
|
|
0.001, 200.0, "Raster: threshold for merging objects in m" });
|
|||
|
|
|
|||
|
|
res->add( { "DISTANCE_SIG_MAXIMA", &DISTANCE_SIG_MAXIMA, raster,
|
|||
|
|
0.01, 200.0, "Raster: dist. to merge local to sign. maxima in m" });
|
|||
|
|
|
|||
|
|
res->add( { "OVERLAP_RASTERS", &OVERLAP_RASTERS, raster,
|
|||
|
|
0.0, 50.0, "Raster: overlapping of raster in memory in %" });
|
|||
|
|
|
|||
|
|
res->add( { "MIN_GROUND_CELLS", &MIN_GROUND_CELLS, raster,
|
|||
|
|
1L, MAX_INT, "Raster: minimum cells for ground object" });
|
|||
|
|
|
|||
|
|
res->add( { "MAX_CELLS_AT_EDGE", &MAX_CELLS_AT_EDGE, raster,
|
|||
|
|
1L, 100L, "Raster: min cells at edge to exclude object"});
|
|||
|
|
|
|||
|
|
res->add( { "SPLIT", &SPLIT, raster,
|
|||
|
|
"Raster: true uses split objets by local maxima routine"});
|
|||
|
|
|
|||
|
|
res->add( { "MIN_OBJ_SIZE", &MIN_OBJ_SIZE, raster,
|
|||
|
|
1L, 1000L, "Raster: minimum cells of object"});
|
|||
|
|
|
|||
|
|
res->add( { "MIN_LOC_MAX_SIZE", &MIN_LOC_MAX_SIZE, raster,
|
|||
|
|
1L, 100L, "Raster: minimum cells of local maximum"});
|
|||
|
|
|
|||
|
|
res->add( { "RASTER_CLASSIFY_EPSILON", &RASTER_CLASSIFY_EPSILON,
|
|||
|
|
raster, 0.0, MAX_REAL, "Raster: dbscan epsilon for classify" });
|
|||
|
|
|
|||
|
|
res->add( { "RASTER_CLASSIFY_MINPTS", &RASTER_CLASSIFY_MINPTS,
|
|||
|
|
raster, 2, MAX_INT, "Raster: dbscan minimum points for classify" });
|
|||
|
|
|
|||
|
|
res->add( { "RASTER_CLASSIFY_SCANSERIES", &RASTER_CLASSIFY_SCANSERIES,
|
|||
|
|
raster, 1, MAX_INT, "Raster: number of varying dbscans" });
|
|||
|
|
|
|||
|
|
res->add( { "RASTER_CLASSIFY_SCANSERIES_ADD",
|
|||
|
|
&RASTER_CLASSIFY_SCANSERIES_ADD,
|
|||
|
|
raster, MIN_REAL, MAX_REAL,
|
|||
|
|
"Raster: value added to epsilon in each round" });
|
|||
|
|
|
|||
|
|
res->add( { "RASTER_CLASSIFY_NORMALIZE", &RASTER_CLASSIFY_NORMALIZE,
|
|||
|
|
raster, 0, 10, "Raster: std-deviation between 1 and this" });
|
|||
|
|
|
|||
|
|
res->add( { "GET_RASTER_NOT_PC", &GET_RASTER_NOT_PC, raster,
|
|||
|
|
"set true analyzeRaster returns the raster not the points"});
|
|||
|
|
|
|||
|
|
res->add( { "SWITCH_FEATURES", &SWITCH_FEATURES, raster,
|
|||
|
|
1, 127, "switch features for classify as binary number as int"});
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
Param::CONTEXT geom = Param::CONTEXT::analyzeGeom;
|
|||
|
|
|
|||
|
|
// the TYPICAL_POINT_DISTANCE can be retrieved automatically
|
|||
|
|
// res->add( { "TYPICAL_POINT_DISTANCE", &TYPICAL_POINT_DISTANCE, geom,
|
|||
|
|
// 0.0, MAX_REAL, "Geom: typical point distance in cloud" });
|
|||
|
|
|
|||
|
|
res->add( { "MINIMUM_OBJECT_EXTENT", &MINIMUM_OBJECT_EXTENT, geom,
|
|||
|
|
0.001, MAX_REAL, "Geom: extent of small objects in cloud" });
|
|||
|
|
|
|||
|
|
res->add( { "NEIGHBORHOOD_SIZE", &NEIGHBORHOOD_SIZE, geom,
|
|||
|
|
25, 1000, "Geom: size of neighborhoods used for dual point calc." });
|
|||
|
|
|
|||
|
|
res->add( { "SOFT_DIFFUSION", &SOFT_DIFFUSION, geom,
|
|||
|
|
0.01, 10.0, "Geom: deviation of points from regular grid order"});
|
|||
|
|
|
|||
|
|
res->add( { "ROUGH_DIFFUSION", &ROUGH_DIFFUSION, geom,
|
|||
|
|
0.001, 10.0, "Geom: how far points may stick out from the surface" });
|
|||
|
|
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
/*
|
|||
|
|
This function is intended to be used for return values of
|
|||
|
|
GetTupleResultType(Supplier s) from RelationCommon in VM.
|
|||
|
|
It takes a name and a list and returns the 1-based index
|
|||
|
|
of the Attribute, and 0 if the name was not found.
|
|||
|
|
(Note: type-values as returned from the supplier are
|
|||
|
|
have such shape:
|
|||
|
|
((152 0) (EUCLID ((3 0) ((ObjID (1 0)) (CatID (1 0))))))
|
|||
|
|
First of each thing might either represent a number in
|
|||
|
|
Secondo Catalogue or a Symbol. But who knows.
|
|||
|
|
Second might represent value - note that VMs typically
|
|||
|
|
return 0 and value is probably nothing of particular
|
|||
|
|
use to us in VM.)
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
int findAttributeIndexInVM(std::string name, ListExpr attribute_list) {
|
|||
|
|
int j = 0;
|
|||
|
|
ListExpr rest = attribute_list;
|
|||
|
|
while (!nl->IsEmpty(rest)) {
|
|||
|
|
ListExpr current = nl->First(rest);
|
|||
|
|
j++;
|
|||
|
|
if (nl->IsEqual(nl->First(current), name)) {
|
|||
|
|
return j;
|
|||
|
|
}
|
|||
|
|
rest = nl->Rest(rest);
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|