def testConvertToString(self): """ensure styles can be created and output to strings in a round trip""" input_string = """ STYLE SIZE 7.25 COLOR 255 0 0 OFFSET 10.5 20.75 END """ new_style = mapscript.fromstring(input_string) assert new_style.size == 7.25 assert new_style.color.red == 255 assert new_style.color.green == 0 assert new_style.color.blue == 0 assert new_style.offsetx == 10.5 assert new_style.offsety == 20.75 output_string = new_style.convertToString() new_style2 = mapscript.fromstring(output_string) # ensure attributes are output as doubles assert new_style.size == 7.25 assert new_style2.offsetx == 10.5 assert new_style2.offsety == 20.75
def __getMapObj(self): path = self.__pathToShapeFile shapePath, shapeName = os.path.split(path) shapeName = shapeName.split('.')[0] shpfile = ms.shapefileObj(path,-1) # "value of -1 to open an existing shapefile"-ms.docs #print self.projection src_prj = ms.projectionObj(self.projection) shpfile.bounds.project(src_prj,self.targetProj) if not self.idVar: mapobj = ms.fromstring(MAPFILE%{'shapePath':shapePath,'shapeName':shapeName,'color':DEFAULT_COLOR,'projString':self.projection,'size':self.size}) else: mapobj = ms.fromstring(MAPFILE%{'shapePath':self.__workSpace,'shapeName':'dtm','color':'[dtmValue]','projString':self.projection,'size':self.size}) mapobj.extent = shpfile.bounds return mapobj
def runTiler(shpfile,outdir,config,zoom=None): if not os.path.exists(shpfile): raise StandardError('Shape File: %s, does not exist!'%shpfile) if not os.path.exists(outdir): os.mkdir(outdir) shapePath, shapeName = os.path.split(shpfile) shapeName = shapeName.split('.')[0] #c = Classifier(shapePath,shapeName) #c.CheckUID(config['IdName']) #mapProj = ms.projectionObj(config['projString']) shpfile = ms.shapefileObj(shpfile,-1) # "value of -1 to open an existing shapefile"-ms.docs bounds = shpfile.bounds return bounds bounds.project(mapProj,GMERC) mapobj = ms.fromstring(MAPFILE%{'shapePath':shapePath,'shapeName':shapeName,'classes':c(),'projString':config['projString']}) mapobj.extent = bounds mapobj.draw().save('f.png') t = Tiler(mapobj,outdir,zoom) print "extents: " bounds.project(GMERC,WGS) print (bounds.maxx + bounds.minx) / 2 print (bounds.maxy + bounds.miny) / 2 return c,t
def testRuntimeSubstitutions(self): """ For supported parameters see https://mapserver.org/cgi/runsub.html#parameters-supported """ s = """ MAP WEB VALIDATION 'key1' '.*' 'default_key1' 'Test Title' END METADATA "wms_title" "%key1%" END END LAYER TYPE POINT FILTER ([size]<%my_filter%) VALIDATION 'my_filter' '^[0-9]$' END END END """ map = mapscript.fromstring(s) d = {"key1": "New Title", "my_filter": "3"} map.applySubstitutions(d) assert map.web.metadata["wms_title"] == "New Title" assert map.getLayer(0).getFilterString() == "([size]<3)"
def legend_img_from_style(self, style_name, subclass, w, h): with codecs.open(path.join(LAYERS_DEF_STYLES_PATH, style_name + '.xml'), encoding='utf-8') as style_file: map_xml = style_file.read() E = ElementMaker() buf = StringIO() emap = etree.fromstring(map_xml) esymbolset = E.symbolset(resource_filename('nextgisweb_mapserver', 'symbolset')) emap.insert(0, esymbolset) # PIXMAP и SVG маркеры for type_elem in emap.iterfind('./symbol/type'): if type_elem.text not in ('pixmap', 'svg'): continue symbol = type_elem.getparent() image = symbol.find('./image') marker = Marker.filter_by(keyname=image.text).one() image.text = self.env.file_storage.filename(marker.fileobj) # FONTS fonts_e = E.fontset(self.env.mapserver.settings['fontset']) emap.insert(0, fonts_e) mapf_map = Map().from_xml(emap) mapfile(mapf_map, buf) mapobj = mapscript.fromstring(buf.getvalue().encode('utf-8')) layerobj = mapobj.getLayer(0) classobj = layerobj.getClass(subclass) gdimg = classobj.createLegendIcon(mapobj, layerobj, w, h) mem_png = StringIO(gdimg.saveToString()) img = Image.open(mem_png) return img
def save_to_mapfile(self,out_path): path = self.__pathToShapeFile shapePath, shapeName = os.path.split(path) shapeName = shapeName.split('.')[0] shpfile = ms.shapefileObj(path,-1) # "value of -1 to open an existing shapefile"-ms.docs #print self.projection src_prj = ms.projectionObj(self.projection) shpfile.bounds.project(src_prj,self.targetProj) mapobj = ms.fromstring(MAPFILE%{'shapePath':shapePath,'shapeName':shapeName,'color':'[dtmValue]','projString':self.projection,'size':self.size}) mapobj.extent = shpfile.bounds mapobj.save(out_path)
def __init__(self, mapfile_string, label_col_index, mapserver_layers, min_zoom=0, max_zoom=100, label_spacing=1024, img_w=256, img_h=256, tile_buffer=256, info_cache_name=None): tiletree.Renderer.__init__(self, img_w, img_h, info_cache_name) self.mapfile = mapscript.fromstring(mapfile_string) self.label_col_index = label_col_index self.mapserver_layers = mapserver_layers self.label_spacing = label_spacing self.img_w = img_w self.img_h = img_h self.tile_buffer = tile_buffer self.min_zoom = min_zoom self.max_zoom = max_zoom self.label_classes = {} self.blank_img_bytes = None self.font_faces = {}
def testDefaultSubstitutions(self): s = """ MAP WEB VALIDATION 'key1' '.*' 'default_key1' 'Test Title' END METADATA "wms_title" "%key1%" END END END """ map = mapscript.fromstring(s) map.applyDefaultSubstitutions() assert map.web.metadata["wms_title"] == "Test Title"
def testPointDraw(self): """Can create a point, add to a layer, and draw it directly""" map_string = """ MAP EXTENT 0 0 90 90 SIZE 500 500 SYMBOL NAME "circle" TYPE ELLIPSE POINTS 1 1 END FILLED true END LAYER NAME "punkt" STATUS ON TYPE POINT END END""" test_map = mapscript.fromstring(map_string) layer = test_map.getLayerByName('punkt') cls = mapscript.classObj() style = mapscript.styleObj() style.outlinecolor.setHex('#00aa00ff') style.size = 10 style.setSymbolByName(test_map, 'circle') cls.insertStyle(style) class_idx = layer.insertClass(cls) point = mapscript.pointObj(45, 45) img = test_map.prepareImage() point.draw(test_map, layer, img, class_idx, "test") filename = 'testDrawPoint.png' with open(filename, 'wb') as fh: img.write(fh) if have_image: pyimg = Image.open(filename) assert pyimg.size == (500, 500)
def __init__(self, mapfile_template, layers, img_w=256, img_h=256, img_buffer=0, min_zoom=0, max_zoom=19, cache_fulls=True, srs='EPSG:3857', trust_cutter=False, tile_buffer=0, info_cache_name=None, skip_info=False): Renderer.__init__(self, img_w, img_h, info_cache_name=info_cache_name) self.mapfile_template=mapfile_template self.layers=layers self.img_buffer=img_buffer self.min_zoom = min_zoom self.max_zoom = max_zoom self.cache_fulls=cache_fulls self.srs = srs self.trust_cutter = trust_cutter self.tile_buffer = tile_buffer self.skip_info = skip_info #creating a mapfile leaks memory, so only create it once template_args = { #'wkt': shapely.wkt.dumps(geometry), #'shapefile_path' : shapefile_path } self.mapfile = mapscript.fromstring(self.mapfile_template % template_args) self.mapfile.loadOWSParameters(self.build_request(0, 0, 10, 10))
def application(env, start_response): """WSGI application for WMS/WCS""" for key in MAPSERV_ENV: if key in env: os.environ[key] = env[key] else: os.unsetenv(key) layer = None mapfile_ = None request = mapscript.OWSRequest() request.loadParams() lang_ = request.getValueByName('LANG') service_ = request.getValueByName('SERVICE') request_ = request.getValueByName('REQUEST') layers_ = request.getValueByName('LAYERS') layer_ = request.getValueByName('LAYER') coverageid_ = request.getValueByName('COVERAGEID') if lang_ is not None and lang_ in ['f', 'fr', 'fra']: lang = 'fr' else: lang = 'en' if layers_ is not None: layer = layers_ elif layer_ is not None: layer = layer_ elif coverageid_ is not None: layer = coverageid_ else: layer = None if service_ is None: service_ = 'WMS' if layer is not None and len(layer) == 0: layer = None time_error = None LOGGER.debug('service: {}'.format(service_)) LOGGER.debug('language: {}'.format(lang)) if layer == 'GODS': banner = os.path.join(BASEDIR, 'geomet_mapfile/resources', 'other/banner.txt') with open(banner) as fh: start_response('200 OK', [('Content-Type', 'text/plain')]) msg = fh.read() return ['{}'.format(msg).encode()] # fetch mapfile from store or from disk if MAPFILE_STORAGE == 'file': # if a single layer is specified in LAYER param fetch mapfile from disk if layer is not None and ',' not in layer: mapfile_ = '{}/mapfile/geomet-weather-{}.map'.format( BASEDIR, layer) # if mapfile_ is None or its path does not exist if mapfile_ is None or not os.path.exists(mapfile_): mapfile_ = '{}/mapfile/geomet-weather.map'.format(BASEDIR) # if mapfile_ path does not exist set mapfile_ to None if not os.path.exists(mapfile_): mapfile_ = None elif MAPFILE_STORAGE == 'store': st = load_plugin('store', {'type': STORE_TYPE, 'url': STORE_URL}) if layer is not None and ',' not in layer: mapfile_ = st.get_key('{}_mapfile'.format(layer)) if mapfile_ is None: mapfile_ = st.get_key('geomet-weather_mapfile') # if no mapfile at all is found return a Unsupported service exception if not mapfile_: start_response('400 Bad Request', [('Content-Type', 'application/xml')]) msg = 'Unsupported service' return [SERVICE_EXCEPTION.format(msg).encode()] # if requesting GetCapabilities for entire service, return cache if request_ == 'GetCapabilities' and layer is None: LOGGER.debug('Requesting global mapfile') if service_ == 'WMS': filename = 'geomet-weather-ogc-wms-1.3.0-capabilities-{}.xml'.format( lang) # noqa cached_caps = os.path.join(BASEDIR, 'mapfile', filename) if os.path.isfile(cached_caps): start_response('200 OK', [('Content-Type', 'application/xml')]) with io.open(cached_caps, 'rb') as fh: return [fh.read()] else: LOGGER.debug('Requesting layer mapfile') if os.path.exists(mapfile_): # read mapfile from filepath LOGGER.debug('Loading mapfile {} from disk'.format(mapfile_)) mapfile = mapscript.mapObj(mapfile_) else: # read mapfile from string returned from store LOGGER.debug('Loading {}_mapfile from store'.format( layer if layer else 'geomet-mapfile')) mapfile = mapscript.fromstring(mapfile_) layerobj = mapfile.getLayerByName(layer) time = request.getValueByName('TIME') ref_time = request.getValueByName('DIM_REFERENCE_TIME') if any(time_param == '' for time_param in [time, ref_time]): time_error = "Valeur manquante pour la date ou l'heure / Missing value for date or time" # noqa start_response('200 OK', [('Content-type', 'text/xml')]) return [SERVICE_EXCEPTION.format(time_error).encode()] if time is None: time = layerobj.getMetaData('wms_timedefault') if ref_time is None: ref_time = layerobj.getMetaData('wms_reference_time_default') try: filepath, url = get_data_path(layer, time, ref_time) except TileNotFoundError as err: LOGGER.error(err) time_error = ( 'NoMatch: Date et heure invalides / Invalid date and time') start_response('200 OK', [('Content-type', 'text/xml')]) return [SERVICE_EXCEPTION.format(time_error).encode()] try: if request_ in ['GetMap', 'GetFeatureInfo']: if all([ filepath.startswith(os.sep), not os.path.isfile(filepath) ]): LOGGER.debug('File is not on disk: {}'.format(filepath)) if not ALLOW_LAYER_DATA_DOWNLOAD: LOGGER.error('layer data downloading not allowed') _error = 'data not found' start_response('500 Internal Server Error', [('Content-type', 'text/xml')]) return [SERVICE_EXCEPTION.format(_error).encode()] if not os.path.exists(os.path.dirname(filepath)): LOGGER.debug('Creating the filepath') os.makedirs(os.path.dirname(filepath)) LOGGER.debug('Downloading url: {}'.format(url)) with urlopen(url) as r: with open(filepath, 'wb') as fh: fh.write(r.read()) layerobj.data = filepath except ValueError as err: LOGGER.error(err) _error = ( 'NoApplicableCode: Donnée non disponible / Data not available') start_response('500 Internal Server Error', [('Content-type', 'text/xml')]) return [SERVICE_EXCEPTION.format(_error).encode()] if request_ == 'GetCapabilities' and lang == 'fr': metadata_lang(mapfile, layer.split(','), lang) mapscript.msIO_installStdoutToBuffer() # giving we don't use properly use tileindex due to performance issues # we need to remove the time parameter from the request for uvraster layer if 'time' in env['QUERY_STRING'].lower(): query_string = env['QUERY_STRING'].split('&') query_string = [x for x in query_string if 'time' not in x.lower()] request.loadParamsFromURL('&'.join(query_string)) else: request.loadParamsFromURL(env['QUERY_STRING']) try: LOGGER.debug('Dispatching OWS request') mapfile.OWSDispatch(request) except (mapscript.MapServerError, IOError) as err: # let error propagate to service exception LOGGER.error(err) pass headers = mapscript.msIO_getAndStripStdoutBufferMimeHeaders() headers_ = [ ('Content-Type', headers['Content-Type']), ] content = mapscript.msIO_getStdoutBufferBytes() start_response('200 OK', headers_) return [content]
def _mapobj(self, features): # tmpf = NamedTemporaryFile(suffix='.map') # buf = codecs.open(tmpf.name, 'w', 'utf-8') buf = StringIO() fieldnames = [f.keyname for f in self.parent.fields] E = ElementMaker() # Настраиваем map emap = etree.fromstring(self.xml) map_setup = [ E.size(width='800', height='600'), E.maxsize('4096'), E.imagecolor(red='255', green='255', blue='255'), E.imagetype('PNG'), E.outputformat(E.name('png'), E.extension('png'), E.mimetype('image/png'), E.driver('AGG/PNG'), E.imagemode('RGBA'), E.formatoption('INTERLACE=OFF')), E.web( E.metadata( E.item(key='wms_onlineresource', value='http://localhost/'), E.item(key='wfs_onlineresource', value='http://localhost/'), E.item(key='ows_title', value='nextgisweb'), E.item(key='wms_enable_request', value='*'), E.item(key='wms_srs', value='EPSG:3857'))), E.extent(minx='-180', miny='-90', maxx='180', maxy='90'), E.projection("+init=epsg:4326"), E.fontset(env.mapserver.options['fontset']), E.symbolset(resource_filename('nextgisweb_mapserver', 'symbolset')) ] for i in reversed(map_setup): emap.insert(0, i) # Настраиваем layer elayer = emap.find('./layer') layer_setup = [ E.name('main'), E.type({ GEOM_TYPE.POINT: 'point', GEOM_TYPE.LINESTRING: 'line', GEOM_TYPE.POLYGON: 'polygon', GEOM_TYPE.MULTIPOINT: 'point', GEOM_TYPE.MULTILINESTRING: 'line', GEOM_TYPE.MULTIPOLYGON: 'polygon', GEOM_TYPE.POINTZ: 'point', GEOM_TYPE.LINESTRINGZ: 'line', GEOM_TYPE.POLYGONZ: 'polygon', GEOM_TYPE.MULTIPOINTZ: 'point', GEOM_TYPE.MULTILINESTRINGZ: 'line', GEOM_TYPE.MULTIPOLYGONZ: 'polygon' }[self.parent.geometry_type]), E.template('dummy.html'), E.projection("+init=epsg:3857"), E.extent(minx='-20037508.34', miny='-20037508.34', maxx='20037508.34', maxy='20037508.34'), E.status('DEFAULT'), ] for e in reversed(layer_setup): elayer.insert(0, e) # PIXMAP & SVG markers: replace to rectangle for type_elem in emap.iterfind('./symbol/type'): if type_elem.text not in ('pixmap', 'svg'): continue symbol = type_elem.getparent() image = symbol.find('./image') # TODO: Set path to SVG marker library # image.text = ... type_elem.text = 'vector' image.tag = 'points' image.text = '0 0 0 1 1 1 1 0 0 0' symbol.append(E.filled('true')) obj = Map().from_xml(emap) mapfile(obj, buf) val = buf.getvalue() mapobj = mapscript.fromstring(val) layer = mapobj.getLayer(0) items = ','.join(fieldnames) layer.setProcessingKey('ITEMS', items) layer.setProcessingKey('APPROXIMATION_SCALE', 'full') layer.setProcessingKey('LABEL_NO_CLIP', 'true') for f in features: # MapServer has problems while rendering 3D geometries and # geometries with duplicate points. ogr_geom = f.geom.ogr ogr_geom.Set3D(False) ogr_geom.Simplify(0) shape = mapscript.shapeObj.fromWKT(ogr_geom.ExportToIsoWkt()) shape.initValues(len(fieldnames)) for i, fld in enumerate(fieldnames, start=0): v = f.fields[fld] if v is None: # TODO: Возможно есть более удачный способ # передавать mapserver пустые значения, но # пока он мне не известен v = "" elif isinstance(v, str): pass elif isinstance(v, bytes): v = v.decode('utf-8') elif isinstance(v, datetime.date): v = v.strftime(r'%Y-%m-%dT%H:%M:%S') else: v = repr(v) shape.setValue(i, v) layer.addFeature(shape) return mapobj
def _mapobj(self, features): # tmpf = NamedTemporaryFile(suffix='.map') # buf = codecs.open(tmpf.name, 'w', 'utf-8') buf = StringIO() fieldnames = map(lambda f: f.keyname, self.layer.fields) E = ElementMaker() # Настраиваем map emap = etree.fromstring(self.xml) map_setup = [ E.size(width='800', height='600'), E.maxsize('4096'), E.imagecolor(red='255', green='255', blue='255'), E.imagetype('PNG'), E.outputformat( E.name('png'), E.extension('png'), E.mimetype('image/png'), E.driver('AGG/PNG'), E.imagemode('RGBA'), E.formatoption('INTERLACE=OFF') ), E.web( E.metadata( E.item( key='wms_onlineresource', value='http://localhost/' ), E.item( key='wfs_onlineresource', value='http://localhost/' ), E.item( key='ows_title', value='nextgisweb' ), E.item( key='wms_enable_request', value='*' ), E.item( key='wms_srs', value='EPSG:3857' ) ) ), E.extent(minx='-180', miny='-90', maxx='180', maxy='90'), E.projection("+init=epsg:4326"), E.fontset(env.mapserver_style.settings['fontset']), E.symbolset(resource_filename( 'nextgisweb_mapserver', 'symbolset' )) ] for i in reversed(map_setup): emap.insert(0, i) # Настраиваем layer elayer = emap.find('./layer') layer_setup = [ E.name('main'), E.type({ "POINT": 'point', 'LINESTRING': 'line', 'POLYGON': 'polygon' }[self.layer.geometry_type]), E.template('dummy.html'), E.projection("+init=epsg:3857"), E.extent( minx='-20037508.34', miny='-20037508.34', maxx='20037508.34', maxy='20037508.34' ), ] for e in reversed(layer_setup): elayer.insert(0, e) # PIXMAP и SVG маркеры: подставляем путь к файлу в SYMBOL cо значением TYPE 'PIXMAP' или 'SVG' for type_elem in emap.iterfind('./symbol/type'): if type_elem.text not in ('pixmap', 'svg'): continue symbol = type_elem.getparent() image = symbol.find('./image') try: marker = Marker.filter_by( keyname=image.text ).one() image.text = env.file_storage.filename(marker.fileobj) except orm_exc.NoResultFound: # Если маркера не нашлось, то заменяем symbol на квадрат type_elem.text = 'vector' image.tag = 'points' image.text = '0 0 0 1 1 1 1 0 0 0' symbol.append(E.filled('true')) obj = Map().from_xml(emap) mapfile(obj, buf) mapobj = mapscript.fromstring(buf.getvalue().encode('utf-8')) layer = mapobj.getLayer(0) items = ','.join(fieldnames).encode('utf-8') layer.setProcessingKey('ITEMS', items) layer.setProcessingKey('APPROXIMATION_SCALE', 'full') layer.setProcessingKey('LABEL_NO_CLIP', 'true') for f in features: # У MapServer серьёзные проблемы с отрисовкой объектов, # содержащих дублирующиеся узлы, поэтому выкидываем их shape = mapscript.shapeObj.fromWKT(f.geom.simplify(0).wkt) shape.initValues(len(fieldnames)) i = 0 for fld in fieldnames: v = f.fields[fld] if v is None: # TODO: Возможно есть более удачный способ # передавать mapserver пустые значения, но # пока он мне не известен v = "" elif isinstance(v, unicode): v = v.encode('utf-8') else: v = str(v) shape.setValue(i, v) i += 1 layer.addFeature(shape) return mapobj
""" C:\VirtualEnvs\mappyfile\Scripts\activate SET PATH=C:\MapServer\bin;%PATH% SET PROJ_LIB=C:\MapServer\bin\proj\SHARE """ import mapscript s = u""" MAP LAYER TYPE POINT CLUSTER MAXDISTANCE 50 REGION ELLIPSE END END END """ map = mapscript.fromstring(s) print(map.convertToString())
def _mapobj(self, features): # tmpf = NamedTemporaryFile(suffix='.map') # buf = codecs.open(tmpf.name, 'w', 'utf-8') buf = StringIO() fieldnames = map(lambda f: f.keyname, self.layer.fields) E = ElementMaker() # Настраиваем map emap = etree.fromstring(self.xml) map_setup = [ E.size(width='800', height='600'), E.maxsize('4096'), E.imagecolor(red='255', green='255', blue='255'), E.imagetype('PNG'), E.outputformat(E.name('png'), E.extension('png'), E.mimetype('image/png'), E.driver('AGG/PNG'), E.imagemode('RGBA'), E.formatoption('INTERLACE=OFF')), E.web( E.metadata( E.item(key='wms_onlineresource', value='http://localhost/'), E.item(key='wfs_onlineresource', value='http://localhost/'), E.item(key='ows_title', value='nextgisweb'), E.item(key='wms_enable_request', value='*'), E.item(key='wms_srs', value='EPSG:3857'))), E.extent(minx='-180', miny='-90', maxx='180', maxy='90'), E.projection("+init=epsg:4326"), E.fontset(env.mapserver_style.settings['fontset']), E.symbolset(resource_filename('nextgisweb_mapserver', 'symbolset')) ] for i in reversed(map_setup): emap.insert(0, i) # Настраиваем layer elayer = emap.find('./layer') layer_setup = [ E.name('main'), E.type({ "POINT": 'point', 'LINESTRING': 'line', 'POLYGON': 'polygon' }[self.layer.geometry_type]), E.template('dummy.html'), E.projection("+init=epsg:3857"), E.extent(minx='-20037508.34', miny='-20037508.34', maxx='20037508.34', maxy='20037508.34'), ] for e in reversed(layer_setup): elayer.insert(0, e) # PIXMAP и SVG маркеры: подставляем путь к файлу в SYMBOL cо значением TYPE 'PIXMAP' или 'SVG' for type_elem in emap.iterfind('./symbol/type'): if type_elem.text not in ('pixmap', 'svg'): continue symbol = type_elem.getparent() image = symbol.find('./image') try: marker = Marker.filter_by(keyname=image.text).one() image.text = env.file_storage.filename(marker.fileobj) except orm_exc.NoResultFound: # Если маркера не нашлось, то заменяем symbol на квадрат type_elem.text = 'vector' image.tag = 'points' image.text = '0 0 0 1 1 1 1 0 0 0' symbol.append(E.filled('true')) obj = Map().from_xml(emap) mapfile(obj, buf) mapobj = mapscript.fromstring(buf.getvalue().encode('utf-8')) layer = mapobj.getLayer(0) items = ','.join(fieldnames).encode('utf-8') layer.setProcessingKey('ITEMS', items) layer.setProcessingKey('APPROXIMATION_SCALE', 'full') layer.setProcessingKey('LABEL_NO_CLIP', 'true') for f in features: # У MapServer серьёзные проблемы с отрисовкой объектов, # содержащих дублирующиеся узлы, поэтому выкидываем их shape = mapscript.shapeObj.fromWKT(f.geom.simplify(0).wkt) shape.initValues(len(fieldnames)) i = 0 for fld in fieldnames: v = f.fields[fld] if v is None: # TODO: Возможно есть более удачный способ # передавать mapserver пустые значения, но # пока он мне не известен v = "" elif isinstance(v, unicode): v = v.encode('utf-8') else: v = str(v) shape.setValue(i, v) i += 1 layer.addFeature(shape) return mapobj