/****************************************************************************** * Project: MapServer * Purpose: Python-specific extensions to MapScript objects * Author: Sean Gillies, sgillies@frii.com * Author: Seth Girvin, sethg@geographika.co.uk * ****************************************************************************** * * Python-specific mapscript code has been moved into this * SWIG interface file to improve the readability of the main * interface file. The main mapscript.i file includes this * file when SWIGPYTHON is defined (via 'swig -python ...'). * *****************************************************************************/ // required for Python 3.10+ - see https://bugs.python.org/issue40943 %begin %{ #define PY_SSIZE_T_CLEAN %} /* fromstring: Factory for mapfile objects */ %pythoncode %{ def fromstring(data, mappath=None, configpath=None): """Creates map objects from mapfile strings. Parameters ========== data : string Mapfile in a string. mappath : string Optional root map path, enabling relative paths in mapfile. Example ======= >>> mo = fromstring("MAP\nNAME 'test'\nEND") >>> mo.name 'test' """ import re if re.search(r"^\s*MAP", data, re.I): # create a config object if a path is supplied if configpath: config = configObj(configpath) else: config = None return msLoadMapFromString(data, mappath, config) elif re.search(r"^\s*LAYER", data, re.I): ob = layerObj() ob.updateFromString(data) return ob elif re.search(r"^\s*CLASS", data, re.I): ob = classObj() ob.updateFromString(data) return ob elif re.search(r"^\s*STYLE", data, re.I): ob = styleObj() ob.updateFromString(data) return ob else: raise ValueError("No map, layer, class, or style found. Can not load from provided string") %} /* =========================================================================== Python rectObj extensions ======================================================================== */ %extend rectObj { %pythoncode %{ def __str__(self): return self.toString() def __contains__(self, item): item_type = item.__class__.__name__ if item_type == "pointObj": if item.x >= self.minx and item.x <= self.maxx \ and item.y >= self.miny and item.y <= self.maxy: return True else: return False else: raise TypeError('__contains__ does not yet handle %s' % (item_type)) %} } /* =========================================================================== Python pointObj extensions ======================================================================== */ %extend pointObj { %pythoncode %{ def __str__(self): return self.toString() @property def __geo_interface__(self): if hasattr(self, "z"): coords = (self.x, self.y, self.z) else: coords = (self.x, self.y) return {"type": "Point", "coordinates": coords} %} } /* =========================================================================== Python lineObj extensions ======================================================================== */ %extend lineObj { %pythoncode %{ @property def __geo_interface__(self): coords = [] for idx in range(0, self.numpoints): pt = self.get(idx) geom = pt.__geo_interface__ coords.append(geom["coordinates"]) return {"type": "LineString", "coordinates": coords} %} } /* =========================================================================== Python shapeObj extensions ======================================================================== */ %extend shapeObj { %pythoncode %{ def _convert_item_values(self, property_values, property_types): """ **Python MapScript only** Convert an item value, which is always stored as a string, into a Python type, based on an attributes GML metadata type. These can be one of the following: ``Integer|Long|Real|Character|Date|Boolean`` """ typed_values = [] for value, type_ in zip(property_values, property_types): try: if type_.lower() == "integer": value = int(value) elif type_.lower() == "long": value = long(value) elif type_.lower() == "real": value = float(value) else: pass except ValueError: pass typed_values.append(value) return typed_values @property def __geo_interface__(self): bounds = self.bounds ms_geom_type = self.type # see https://tools.ietf.org/html/rfc7946 for GeoJSON types if ms_geom_type == MS_SHAPE_POINT or ms_geom_type == MS_SHP_POINTZ or ms_geom_type == MS_SHP_POINTM: geom_type = "Point" elif ms_geom_type == MS_SHP_MULTIPOINTZ or ms_geom_type == MS_SHP_MULTIPOINTM: geom_type = "MultiPoint" elif ms_geom_type == MS_SHAPE_LINE or ms_geom_type == MS_SHP_ARCZ or ms_geom_type == MS_SHP_ARCM: if self.numlines == 1: geom_type = "LineString" else: geom_type = "MultiLineString" elif ms_geom_type == MS_SHAPE_POLYGON or ms_geom_type == MS_SHP_POLYGONZ or ms_geom_type == MS_SHP_POLYGONM: if self.numlines == 1: geom_type = "Polygon" else: geom_type = "MultiPolygon" elif ms_geom_type == MS_SHAPE_NULL: return None else: raise TypeError("Shape type {} not supported".format(geom_type)) properties = {} coords = [] # property names are stored at the layer level # https://github.com/mapserver/mapserver/issues/130 property_values = [self.getValue(idx) for idx in range(0, self.numvalues)] if hasattr(self, "_item_definitions"): property_names, property_types = zip(*self._item_definitions) property_values = self._convert_item_values(property_values, property_types) else: property_names = [str(idx) for idx in range(0, self.numvalues)] properties = dict(zip(property_names, property_values)) for idx in range(0, self.numlines): line = self.get(idx) geom = line.__geo_interface__ coords.append(geom["coordinates"]) return { "type": "Feature", "bbox": (bounds.minx, bounds.miny, bounds.maxx, bounds.maxy), "properties": properties, "geometry": { "type": geom_type, "coordinates": coords } } @property def itemdefinitions(self): return self._item_definitions @itemdefinitions.setter def itemdefinitions(self, item_definitions): self._item_definitions = item_definitions %} } /****************************************************************************** * Extensions to mapObj *****************************************************************************/ %extend mapObj { /** \**Python MapScript only** - returns the map layer order as a native sequence */ PyObject *getLayerOrder() { int i; PyObject *order; order = PyTuple_New(self->numlayers); for (i = 0; i < self->numlayers; i++) { PyTuple_SetItem(order,i,PyInt_FromLong((long)self->layerorder[i])); } return order; } /** \**Python MapScript only** - sets the map layer order using a native sequence */ int setLayerOrder(PyObject *order) { int i; Py_ssize_t size = PyTuple_Size(order); for (i = 0; i < size; i++) { self->layerorder[i] = (int)PyInt_AsLong(PyTuple_GetItem(order, i)); } return MS_SUCCESS; } /** \**Python MapScript only** - gets the map size as a tuple */ PyObject* getSize() { PyObject* output ; output = PyTuple_New(2); PyTuple_SetItem(output,0,PyInt_FromLong((long)self->width)); PyTuple_SetItem(output,1,PyInt_FromLong((long)self->height)); return output; } %pythoncode %{ def get_height(self): """ **Python MapScript only** Return the map height from the map size """ return self.getSize()[1] # <-- second member is the height def get_width(self): """ **Python MapScript only** Return the map width from the map size """ return self.getSize()[0] # <-- first member is the width def set_height(self, value): """ **Python MapScript only** Set the map height value of the map size """ return self.setSize(self.getSize()[0], value) def set_width(self, value): """ **Python MapScript only** Set the map width value of the map size """ return self.setSize(value, self.getSize()[1]) width = property(get_width, set_width, doc="See :ref:`SIZE `") height = property(get_height, set_height, doc = "See :ref:`SIZE `") %} } /****************************************************************************** * Extensions to layerObj *****************************************************************************/ %extend layerObj { %pythoncode %{ def getItemDefinitions(self): """ **Python MapScript only** Return item (field) names and their types if available. Field types are specified using GML metadata and can be one of the following: ``Integer|Long|Real|Character|Date|Boolean`` """ item_names = [self.getItem(idx) for idx in range(0, self.numitems)] item_types = [self.getItemType(idx) for idx in range(0, self.numitems)] return zip(item_names, item_types) %} } /****************************************************************************** * Extensions to imageObj *****************************************************************************/ %extend imageObj { /** Write image data to an open file handle. Replaces the removed saveToString function. See ``python/pyextend.i`` for the Python specific version of this method. */ int write( PyObject *file=Py_None ) { unsigned char *imgbuffer=NULL; int imgsize; PyObject *noerr; int retval=MS_FAILURE; /* Return immediately if image driver is not GD */ if ( !MS_RENDERER_PLUGIN(self->format) ) { msSetError(MS_IMGERR, "Writing of %s format not implemented", "imageObj::write", self->format->driver); return MS_FAILURE; } if (file == Py_None) /* write to stdout */ retval = msSaveImage(NULL, self, NULL); else /* presume a Python file-like object */ { imgbuffer = msSaveImageBuffer(self, &imgsize, self->format); if (imgsize == 0) { msSetError(MS_IMGERR, "failed to get image buffer", "write()"); return MS_FAILURE; } %#if PY_MAJOR_VERSION >= 3 // https://docs.python.org/3/c-api/arg.html noerr = PyObject_CallMethod(file, "write", "y#", imgbuffer, (Py_ssize_t)imgsize); %#else // https://docs.python.org/2/c-api/arg.html noerr = PyObject_CallMethod(file, "write", "s#", imgbuffer, imgsize); %#endif free(imgbuffer); if (noerr == NULL) return MS_FAILURE; else Py_DECREF(noerr); retval = MS_SUCCESS; } return retval; } } /****************************************************************************** * Extensions to styleObj *****************************************************************************/ %extend styleObj { /// **Python Only** Set the pattern for the style. void pattern_set(int nListSize, double* pListValues) { if( nListSize < 2 ) { msSetError(MS_SYMERR, "Not enough pattern elements. A minimum of 2 are required", "pattern_set()"); return; } if( nListSize > MS_MAXPATTERNLENGTH ) { msSetError(MS_MISCERR, "Too many elements", "pattern_set()"); return; } memcpy( self->pattern, pListValues, sizeof(double) * nListSize); self->patternlength = nListSize; } /// **Python Only** Get the pattern for the style. void pattern_get(double** argout, int* pnListSize) { *pnListSize = self->patternlength; *argout = (double*) malloc(sizeof(double) * *pnListSize); memcpy( *argout, self->pattern, sizeof(double) * *pnListSize); } %pythoncode %{ pattern = property(pattern_get, pattern_set, doc=r"""pattern : list **Python Only**""") %} } /****************************************************************************** * Extensions to hashTableObj - add dict methods *****************************************************************************/ %extend hashTableObj{ %pythoncode %{ def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): return self.set(key, value) def __delitem__(self, key) : return self.remove(key) def __contains__(self, key): return key.lower() in [k.lower() for k in self.keys()] def __len__(self): return self.numitems def keys(self): """ **Python-only**. In Python MapScript the ``hashTableObj`` can be used and accessed as a dictionary. The ``keys`` method returns a view of all the keys in the ``hashTableObj``. """ keys = [] k = None while True : k = self.nextKey(k) if k : keys.append(k) else : break return keys %} }