"""
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
###############################################################################
###############################################################################
[docs]class RPCDefs:
# 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()
###############################################################################
[docs]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"
[docs]class DataProvider(RPCServerBase):
"""Fast and exit-safe interface to PyGRASS data delivery functions"""
def __init__(self):
RPCServerBase.__init__(self)
[docs] 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()
[docs] 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")
[docs] 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()
..
""" # noqa: E501
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")
[docs] 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)