# Source code for pygrass.rpc

    """
    Fast and exit-safe interface to PyGRASS Raster and Vector layer
    using multiprocessing

    (C) 2015 by the GRASS Development Team
    This program is free software under the GNU General Public
    License (>=v2). Read the file COPYING that comes with GRASS
    for details.

    :authors: Soeren Gebbert
    """

    import time
    import threading
    import sys
    from multiprocessing import Process, Lock, Pipe
    from ctypes import CFUNCTYPE, c_void_p

    from grass.exceptions import FatalError
    from grass.pygrass.vector import VectorTopo
    from grass.pygrass.vector.basic import Bbox
    from grass.pygrass.raster import RasterRow, raster2numpy_img
    import grass.lib.gis as libgis
    from .base import RPCServerBase
    from grass.pygrass.gis.region import Region
    from grass.pygrass import utils
    import logging

    ###############################################################################
    ###############################################################################

    class RPCDefs(object):
        # Function identifier and index
        STOP = 0
        GET_VECTOR_TABLE_AS_DICT = 1
        GET_VECTOR_FEATURES_AS_WKB = 2
        GET_RASTER_IMAGE_AS_NP = 3
        G_FATAL_ERROR = 14

    def _get_raster_image_as_np(lock, conn, data):
        """Convert a raster map into an image and return
        a numpy array with RGB or Gray values.

        :param lock: A multiprocessing.Lock instance
        :param conn: A multiprocessing.Pipe instance used to send True or False
        :param data: The list of data entries [function_id, raster_name, extent, color]
        """
        array = None
        try:
            name = data[1]
            mapset = data[2]
            extent = data[3]
            color = data[4]

            mapset = utils.get_mapset_raster(name, mapset)

            if not mapset:
                raise ValueError("Unable to find raster map <%s>" % (name))

            rast = RasterRow(name, mapset)

            if rast.exist():
                reg = Region()
                reg.from_rast(name)

                if extent is not None:
                    if "north" in extent:
                        reg.north = extent["north"]
                    if "south" in extent:
                        reg.south = extent["south"]
                    if "east" in extent:
                        reg.east = extent["east"]
                    if "west" in extent:
                        reg.west = extent["west"]
                    if "rows" in extent:
                        reg.rows = extent["rows"]
                    if "cols" in extent:
                        reg.cols = extent["cols"]
                    reg.adjust()

                array = raster2numpy_img(name, reg, color)
        finally:
            # Send even if an exception was raised.
            conn.send(array)

    def _get_vector_table_as_dict(lock, conn, data):
        """Get the table of a vector map layer as dictionary

        :param lock: A multiprocessing.Lock instance
        :param conn: A multiprocessing.Pipe instance used to send True or False
        :param data: The list of data entries [function_id, name, mapset, where]

        """
        ret = None
        try:
            name = data[1]
            mapset = data[2]
            where = data[3]

            mapset = utils.get_mapset_vector(name, mapset)

            if not mapset:
                raise ValueError("Unable to find vector map <%s>" % (name))

            layer = VectorTopo(name, mapset)

            if layer.exist() is True:
                layer.open("r")
                columns = None
                table = None
                if layer.table is not None:
                    columns = layer.table.columns
                    table = layer.table_to_dict(where=where)
                layer.close()

                ret = {}
                ret["table"] = table
                ret["columns"] = columns
        finally:
            # Send even if an exception was raised.
            conn.send(ret)

    def _get_vector_features_as_wkb_list(lock, conn, data):
        """Return vector layer features as wkb list

        supported feature types:
        point, centroid, line, boundary, area

        :param lock: A multiprocessing.Lock instance
        :param conn: A multiprocessing.Pipe instance used to send True or False
        :param data: The list of data entries [function_id,name,mapset,extent,
                                               feature_type, field]

        """
        wkb_list = None
        try:
            name = data[1]
            mapset = data[2]
            extent = data[3]
            feature_type = data[4]
            field = data[5]
            bbox = None

            mapset = utils.get_mapset_vector(name, mapset)

            if not mapset:
                raise ValueError("Unable to find vector map <%s>" % (name))

            layer = VectorTopo(name, mapset)

            if layer.exist() is True:
                if extent is not None:
                    bbox = Bbox(
                        north=extent["north"],
                        south=extent["south"],
                        east=extent["east"],
                        west=extent["west"],
                    )

                layer.open("r")
                if feature_type.lower() == "area":
                    wkb_list = layer.areas_to_wkb_list(bbox=bbox, field=field)
                else:
                    wkb_list = layer.features_to_wkb_list(
                        bbox=bbox, feature_type=feature_type, field=field
                    )
                layer.close()
        finally:
            # Send even if an exception was raised.
            conn.send(wkb_list)

    ###############################################################################

    def _fatal_error(lock, conn, data):
        """Calls G_fatal_error()"""
        libgis.G_fatal_error("Fatal Error in C library server")

    ###############################################################################

    def _stop(lock, conn, data):
        conn.close()
        lock.release()
        sys.exit()

    ###############################################################################

    def data_provider_server(lock, conn):
        """The PyGRASS data provider server designed to be a target for
        multiprocessing.Process

        :param lock: A multiprocessing.Lock
        :param conn: A multiprocessing.Pipe
        """

        def error_handler(data):
            """This function will be called in case of a fatal error in libgis"""
            # sys.stderr.write("Error handler was called\n")
            # We send an exception that will be handled in
            # the parent process, then close the pipe
            # and release any possible lock
            conn.send(FatalError("G_fatal_error() was called in the server process"))
            conn.close()
            lock.release()

        CALLBACK = CFUNCTYPE(c_void_p, c_void_p)
        CALLBACK.restype = c_void_p
        CALLBACK.argtypes = c_void_p

        cerror_handler = CALLBACK(error_handler)

        libgis.G_add_error_handler(cerror_handler, None)

        # Crerate the function array
        functions = [0] * 15
        functions[RPCDefs.GET_VECTOR_TABLE_AS_DICT] = _get_vector_table_as_dict
        functions[RPCDefs.GET_VECTOR_FEATURES_AS_WKB] = _get_vector_features_as_wkb_list
        functions[RPCDefs.GET_RASTER_IMAGE_AS_NP] = _get_raster_image_as_np
        functions[RPCDefs.STOP] = _stop
        functions[RPCDefs.G_FATAL_ERROR] = _fatal_error

        while True:
            # Avoid busy waiting
            conn.poll(None)
            data = conn.recv()
            lock.acquire()
            functions[data[0]](lock, conn, data)
            lock.release()

    test_vector_name = "data_provider_vector_map"
    test_raster_name = "data_provider_raster_map"

    class DataProvider(RPCServerBase):
        """Fast and exit-safe interface to PyGRASS data delivery functions"""

        def __init__(self):
            RPCServerBase.__init__(self)

        def start_server(self):
            """This function must be re-implemented in the subclasses"""
            self.client_conn, self.server_conn = Pipe(True)
            self.lock = Lock()
            self.server = Process(
                target=data_provider_server, args=(self.lock, self.server_conn)
            )
            self.server.daemon = True
            self.server.start()

        def get_raster_image_as_np(self, name, mapset=None, extent=None, color="RGB"):
            """Return the attribute table of a vector map as dictionary.

            See documentation of: pygrass.raster.raster2numpy_img

            Usage:

            .. code-block:: python

             >>> from grass.pygrass.rpc import DataProvider
             >>> import time
             >>> provider = DataProvider()
             >>> ret = provider.get_raster_image_as_np(name=test_raster_name)
             >>> len(ret)
             64

             >>> extent = {"north":30, "south":10, "east":30, "west":10,
             ...           "rows":2, "cols":2}
             >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
             ...                                       extent=extent)
             >>> len(ret)
             16

             >>> extent = {"rows":3, "cols":1}
             >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
             ...                                       extent=extent)
             >>> len(ret)
             12

             >>> extent = {"north":100, "south":10, "east":30, "west":10,
             ...           "rows":2, "cols":2}
             >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
             ...                                       extent=extent)

             >>> provider.stop()
             >>> time.sleep(1)

             >>> extent = {"rows":3, "cols":1}
             >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
             ...                                       extent=extent)
             >>> len(ret)
             12

             ..
            """
            self.check_server()
            self.client_conn.send(
                [RPCDefs.GET_RASTER_IMAGE_AS_NP, name, mapset, extent, color]
            )
            return self.safe_receive("get_raster_image_as_np")

        def get_vector_table_as_dict(self, name, mapset=None, where=None):
            """Return the attribute table of a vector map as dictionary.

            See documentation of: pygrass.vector.VectorTopo::table_to_dict

            Usage:

            .. code-block:: python

             >>> from grass.pygrass.rpc import DataProvider
             >>> provider = DataProvider()
             >>> ret = provider.get_vector_table_as_dict(name=test_vector_name)
             >>> ret["table"]
             {1: [1, 'point', 1.0], 2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
             >>> ret["columns"]
             Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
             >>> ret = provider.get_vector_table_as_dict(name=test_vector_name,
             ...                                           where="value > 1")
             >>> ret["table"]
             {2: [2, 'line', 2.0], 3: [3, 'centroid', 3.0]}
             >>> ret["columns"]
             Columns([('cat', 'INTEGER'), ('name', 'varchar(50)'), ('value', 'double precision')])
             >>> provider.get_vector_table_as_dict(name="no_map",
             ...                                   where="value > 1")
             >>> provider.stop()

             ..
            """
            self.check_server()
            self.client_conn.send([RPCDefs.GET_VECTOR_TABLE_AS_DICT, name, mapset, where])
            return self.safe_receive("get_vector_table_as_dict")

        def get_vector_features_as_wkb_list(
            self, name, mapset=None, extent=None, feature_type="point", field=1
        ):
            """Return the features of a vector map as wkb list.

            :param extent: A dictionary of {"north":double, "south":double,
                                            "east":double, "west":double}
            :param feature_type: point, centroid, line, boundary or area

            See documentation: pygrass.vector.VectorTopo::features_to_wkb_list
                               pygrass.vector.VectorTopo::areas_to_wkb_list

            Usage:

            .. code-block:: python

             >>> from grass.pygrass.rpc import DataProvider
             >>> provider = DataProvider()
             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=None,
             ...                                                feature_type="point")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             1 1 21
             2 1 21
             3 1 21

             >>> extent = {"north":6.6, "south":5.5, "east":14.5, "west":13.5}
             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=extent,
             ...                                                feature_type="point")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             3 1 21

             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=None,
             ...                                                feature_type="line")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             4 2 57
             5 2 57
             6 2 57

             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=None,
             ...                                                feature_type="centroid")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             19 3 21
             18 3 21
             20 3 21
             21 3 21

             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=None,
             ...                                                feature_type="area")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             1 3 225
             2 3 141
             3 3 93
             4 3 141

             >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
             ...                                                extent=None,
             ...                                                feature_type="boundary")
             >>> for entry in wkb:
             ...     f_id, cat, string = entry
             ...     print(f_id, cat, len(string))
             10 None 41
             7 None 41
             8 None 41
             9 None 41
             11 None 89
             12 None 41
             14 None 41
             13 None 41
             17 None 41
             15 None 41
             16 None 41

             >>> provider.stop()

             ..
            """
            self.check_server()
            self.client_conn.send(
                [
                    RPCDefs.GET_VECTOR_FEATURES_AS_WKB,
                    name,
                    mapset,
                    extent,
                    feature_type,
                    field,
                ]
            )
            return self.safe_receive("get_vector_features_as_wkb_list")

    if __name__ == "__main__":
        import doctest
        from grass.pygrass.modules import Module

        Module("g.region", n=40, s=0, e=40, w=0, res=10)
        Module(
            "r.mapcalc",
            expression="%s = row() + (10 * col())" % (test_raster_name),
            overwrite=True,
        )
        utils.create_test_vector_map(test_vector_name)

        doctest.testmod()

        """Remove the generated maps, if exist"""
        mset = utils.get_mapset_raster(test_raster_name, mapset="")
        if mset:
            Module("g.remove", flags="f", type="raster", name=test_raster_name)
        mset = utils.get_mapset_vector(test_vector_name, mapset="")
        if mset:
            Module("g.remove", flags="f", type="vector", name=test_vector_name)

Help Index | Topics Index | Keywords Index | Full Index

© 2003-2023 GRASS Development Team, GRASS GIS 8.4.0dev Reference Manual