Creating new type features
Goal of the tutorial
This tutorial show hows to create a new type feature. Type feature are used to extent the internal database with additional information available in the OSM data. Feature represent the existance of certain tags or tags combinations optionally together with their values.
Some examples for features are the value of tags like:
Of course any other tag or combination of tags can in principle get converted to a type feature.
For a more detailed description regarding assigning features to types, see Type Definition.
Required C++ headers and base classes
The base classes required for implementing type features can be found in
in the header <osmscout/TypeConfig.h>
, concrete implementations
are placed in <osmscout/TypeFeatures.h>
. Example for a valueless
feature is osmscout::TunnelFeature
, simple example for a type feature
also holding a value is NameFeature
.
The relevant bases classes for implementing a new feature are:
osmscout::Feature
andosmsocut::FeatureRef
- This is the base class that holds meta information about the feature ands contains methods for parsing tags to the feature.
osmscout::FeatureValue
- Derived instances of this class hold the actual value of the feature (if the feature has a value) and also offers methofs for reading and writing feature values.
osmscout::FeatureInstance
- This is an instance of a feature for a given type. This contains some type specific information of the feature for a given type, like the actual bit position of the feature flag in the feature bit mask. You do not need to derive this class for a new feature.
osmscout::FeatureValueBuffer
- The FeatureValueBuffer handles access to the types and all the features of an given object instance. You do not need to derive this class for a new feature.
Registering a feature
Before you be able to use a feature in the OST
file and later on read and write
instances of it during import and later usage you first have to register it
to the type configuration. For this you have to call
TypeConfig::RegisterFeature()
on a type config instance.
Note that you need to call above method during import and later on if you are using the resulting database. You should also make sure that features are registered in the same order in both cases.
Note that registering a feature does not cost you anything. So it makes sense to make sure that your feature is pushed to upstream and automatically registered by the library.
Extending osmscout::Feature
to define a new valueless feature
To define a new feature you need to extend the class ossmcout::Feature
.
See for example the class osmscout::TunnelFeature
, which implements
the feature Tunnel
based on the tag
tunnel.
class OSMSCOUT_API TunnelFeature : public Feature
{
private:
TagId tagTunnel;
public:
/** Name of this feature */
static const char* const NAME;
public:
void Initialize(TypeConfig& typeConfig);
std::string GetName() const;
void Parse(Progress& progress,
const TypeConfig& typeConfig,
const FeatureInstance& feature,
const ObjectOSMRef& object,
const TagMap& tags,
FeatureValueBuffer& buffer) const;
};
After a featured is created and registered to the TypeConfig
instance, the
initialize()
method is called, passing the actual TypeConfig
instance.
If you need the TagId
of any tags, this is the place to get them from the
TypeConfig
and save them. The TunnelFeature
does this for the Tag
tunnel
:
void TunnelFeature::Initialize(TypeConfig& typeConfig)
{
tagTunnel=typeConfig.RegisterTag("tunnel");
}
The method GetName()
has to be implemented to return the name of the feature
as to be used in the OST
file. It makes sense to define a constant for the
name of the feature.
const char* const TunnelFeature::NAME = "Tunnel";
std::string TunnelFeature::GetName() const
{
return NAME;
}
Finally (for valueless features) you need to implement the Parse
method
to analyze the given object with the given map of tags for the existance of the
feature:
void TunnelFeature::Parse(Progress& /*progress*/,
const TypeConfig& /*typeConfig*/,
const FeatureInstance& feature,
const ObjectOSMRef& /*object*/,
const TagMap& tags,
FeatureValueBuffer& buffer) const
{
auto tunnel=tags.find(tagTunnel);
if (tunnel!=tags.end() &&
!(tunnel->second=="no" ||
tunnel->second=="false" ||
tunnel->second=="0")) {
buffer.AllocateValue(feature.GetIndex());
}
}
If the feature exists, you have to allocate a value for it at the index
as defined (and managed by the TypeConfig
) by the passed
FeatureInstance
.
Since the base class has the following (overwriteable) definitions:
/**
* A feature, if set for an object, can hold a value. If there is no value object,
* this method returns 0, else it returns the C++ size of the value object.
*/
inline virtual size_t GetValueSize() const
{
return 0;
}
/**
* This method returns the number of additional feature bits reserved. If there are
* additional features bit, 0 is returned.
*
* A feature may reserve additional feature bits. Feature bits should be used
* if a custom value object is too expensive. Space for feature bits is always reserved
* even if the feature itself is not set for a certain object.
*/
inline virtual size_t GetFeatureBitCount() const
{
return 0;
}
/**
* Returns 'true' if the feature has an value object.
*/
inline virtual bool HasValue() const
{
return GetValueSize()>0;
}
There is no actual value for the feature, since the value size is 0. In this case you are done.
How to implement a feature with a value
For a feature also having a value we have to do further coding. Lets look at the
NameFeature. First the
NameFeatureValue`.
class OSMSCOUT_API NameFeatureValue : public FeatureValue
{
private:
std::string name;
public:
inline NameFeatureValue()
{
// no code
}
inline NameFeatureValue(const std::string& name)
: name(name)
{
// no code
}
inline void SetName(const std::string& name)
{
this->name=name;
}
inline std::string GetName() const
{
return name;
}
inline std::string GetLabel() const
{
return name;
}
void Read(FileScanner& scanner);
void Write(FileWriter& writer);
FeatureValue& operator=(const FeatureValue& other);
bool operator==(const FeatureValue& other) const;
};
The NameFeatureValue
extends FeatureValue
overwriting the GetLabel()
method,
the assignment and comparison methods and the Read()
and Write()
methods.
In this case the actzul value is the attribute name
of type std::string
.
The corresponding ovwrriten methods are thus as expected:
void NameFeatureValue::Read(FileScanner& scanner)
{
scanner.Read(name);
}
void NameFeatureValue::Write(FileWriter& writer)
{
writer.Write(name);
}
The NameFeature
also offers additional SetName()
and GetName()
helper
methods and a constructor with the name as parameter.
If the feature itself has registered a label using the RegisterLabel
base method
the GetLabel()
method should be implemented to return a readable and renderable
representation of the feature. In this case we just return the name itself.
To signal the engine that a feature has a value the following methods of the base class have to be implemented as follows:
size_t NameFeature::GetValueSize() const
{
return sizeof(NameFeatureValue);
}
FeatureValue* NameFeature::AllocateValue(void* buffer)
{
return new (buffer) NameFeatureValue();
}
In this case the size of the value is >0 and AllocateValue uses a special variant
of new to create a feature value instance at the given buffer position. The
buffer was before allocated to be big enough to hold all values of
all feature an actual object instance (FeatureValueBuffer
) has.
Additional feature bits
Using the method GetFeatureBitCount()
you can request further feature bits
to be reserver for you. This is helpfull, if the feature value is
smaller than one byte. Not though that space is reserved for this additonal bit
always. So if if an object does not have a feature, the additional bit
is still allocated on disk and in memory.
You have to access the addtional bit manually. That means to have to find the index of the feature in the feature bit field and get/set the additonal bits which are directly paced behind the feature bit itself manually.