Minimal Plugin setup

Plugins are dedicated analyzer to check the tags of one object at the time.

For explanation purpose only, we just here make a plugin that report fountains, it is not looking for issue in the data.


In [1]:
%cd "/opt/osmose-backend/"


/opt/osmose-backend

Each plugin is a class inheriting from Plugin. The class must define:

  • An init(self, logger)
  • A method for each type of object to check node, way or relation. Method can call each other to factorize the code. But the method are optional.

init()

Should define the class of issue. It is meta-information about the produced issues. See general documentation for details.

node(), way() and relation()

The tags argument is a dictionary of OSM tags.

The method should return a dictionary or an array of dictionary of Osmose-QA issues. See general documentation for details. But the returned dictionary must contain:

  • class: the same id from the definition
  • subclass optional: to make issue unique if required
  • text optional: detail about the issue
  • fix optional: fix suggestion

In [2]:
from plugins.Plugin import Plugin

class Fountain(Plugin):

    def init(self, logger):
        Plugin.init(self, logger)
        
        # Define meta-information about the produced Osmose issues
        self.errors[1] = self.def_class(item = 2020, level = 3, tags = ['tag', 'fix:survey'],
            title = T_('Fountain here'),
            detail = T_(
'''Nice report of Fountain.'''))

    def node(self, data, tags):
        if tags.get("amenity") == "fountain":
            # When we found OSM object with tag amenity=fountain,
            # return Osmose issue of class 1, with name tag issue subtitle
            return {"class": 1, "text": T_f("Name: {0}", tags.get("name"))}

    def way(self, data, tags, nds):
        # Same check as node
        return self.node(data, tags)
    
    def relation(self, data, tags, members):
        # Same check as node
        return self.node(data, tags)

Each plugin should come with unitary test. It checks that the plugin done the expect job. You must call the plugin method with various OSM object definition. For each you must check if the plugin return an Osmose issue or not.

  • self.check_err(plugin_return, expect_issue) assert the return of the plugin is valid and match the optional expect_issue.
  • assert not plugin_return assert the plugin return nothing

In [3]:
from plugins.Plugin import TestPluginCommon

class Test(TestPluginCommon):
    def test(self):

        # Instantiate and initialize the Plugin
        a = Fountain(None)
        a.init(None)

        # Assert the OSM object with tag amenity=fountain
        # returns an Osmose issue of class 1
        self.check_err(
            a.node(None, {"amenity": "fountain"}),
            {'class': 1}
        )
        
        # Assert the plugin return nothing for OSM object with tag natural=peak
        assert not a.node(None, {"natural": "peak"})

# Run the test
Test().test() # Returns nothing where it is OK, else error.

To run the analyze we need a context of execution. Each country or area have a entry in the file osmose_config.py. Parameters from the configuration can be used in the plugin, eg. self.father.config.options.get("phone_code").


In [4]:
import osmose_config as config

country_conf = config.config['monaco']
country_conf.init()

country_conf.analyser_options


Out[4]:
{'project': 'openstreetmap',
 'country': 'MC',
 'language': 'fr',
 'proj': 2154,
 'phone_code': '377',
 'phone_len': 8,
 'phone_format': '^[+]%s([- ./]*[469])([- ./]*[0-9]){6}[0-9]$',
 'phone_international': '00'}

The plugins are run by the analyzer "sax". The result can be fetched by Jupyter and displayed. By default it is in Osmose-QA XML format. CSV en GeoJson format are for debug only and have partial content.


In [5]:
from analysers.analyser_sax import Analyser_Sax
from modules.jupyter import *

csv = run(country_conf, Analyser_Sax, plugin = Fountain, format = 'csv')
print_csv(csv)


Out[5]:
classs subclass ids types text lon lat fix
0 1 0 [456295834] ['node'] Name: None 7.422254 43.731620 NaN
1 1 0 [4065627219] ['node'] Name: Fontaine globe 7.421072 43.728590 NaN
2 1 0 [5915727561] ['node'] Name: Fontaine de l'amphithéâtre de Cap d'ail 7.412227 43.725283 NaN
3 1 0 [5918146876] ['node'] Name: Fontaine de la roseraie Princesse Grace 7.419279 43.727257 NaN
4 1 0 [5918204005] ['node'] Name: Fontaine au port de Cap d'ail 7.413440 43.726025 NaN
5 1 0 [5918204081] ['node'] Name: Fontaine des droits de l'enfant 7.418953 43.726833 NaN
6 1 0 [6123590359] ['node'] Name: None 7.417662 43.725323 NaN
7 1 0 [6696261621] ['node'] Name: None 7.423629 43.730971 NaN
8 1 0 [572933762] ['way'] Name: None 7.419157 43.732581 NaN
9 1 0 [572935477] ['way'] Name: None 7.426977 43.739671 NaN
10 1 0 [572935479] ['way'] Name: Fontaine du Casino 7.427476 43.739418 NaN
11 1 0 [572938550] ['way'] Name: Fontaine Japonaise 7.430044 43.741546 NaN
12 1 0 [572947839] ['way'] Name: None 7.434892 43.747410 NaN
13 1 0 [572948182] ['way'] Name: None 7.438353 43.749129 NaN
14 1 0 [572948695] ['way'] Name: None 7.438335 43.747669 NaN
15 1 0 [686461248] ['way'] Name: None 7.416823 43.730711 NaN
16 1 0 [686461249] ['way'] Name: None 7.416799 43.730598 NaN
17 1 0 [686461252] ['way'] Name: None 7.416663 43.730625 NaN
18 1 0 [686461253] ['way'] Name: None 7.416713 43.730753 NaN
19 1 0 [686461255] ['way'] Name: None 7.416779 43.730590 NaN
20 1 0 [688300839] ['way'] Name: None 7.416750 43.730519 NaN
21 1 0 [688300840] ['way'] Name: None 7.416842 43.730533 NaN
22 1 0 [778494661] ['way'] Name: None 7.425755 43.740410 NaN
23 1 0 [778494663] ['way'] Name: None 7.426416 43.739964 NaN

In [6]:
geojson = run(country_conf, Analyser_Sax, plugin = Fountain, format = 'geojson')
print_geojson(geojson)



In [ ]: