libosmscout libosmscout.sf.net

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 and osmsocut::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 theNameFeature. First theNameFeatureValue`.

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.