FME2012 应用示例-FME Python 和Mapnik

原文发布时间:2012-06-04 09:14:46

作者:Mark

翻译:怕冷的企鹅

各位FME用户,大家好,

上个月我发表了关于FME2012版本的FME对Python支持的博文(译文地址http://blog.163.com/antufme@126/blog/static/1404924922012410104546501/),所以此次我会尝试展示利用此技术做一些工作的示例。

此示例集成了FME名为Mapnik的映射工具包。

Mapnik在Safe软件中和Dale很长一段时间内,是我们最喜爱的工具包,并一直想尝试整合。因此,非常需要我带来的一个适合的Python示例。

除了希望本篇文章可以展示出的不仅是FME与Mapnik的接口设置,也可以表现出一些2012版的新功能,以及在FME中应用 Python是怎样的简单。

Mapnik是什么?

扫描二维码关注公众号,回复: 2394291 查看本文章

Mapnik本质上是一个纯空间数据的工具包,它可以渲染出一个真正漂亮的栅格地图。非常精准的符号控制提供了一个可以利用脚本或XML文件定义的规则集。

下面是一张拷贝自某网站的应用了Mapnik的Vmap数据集展示示例:

对于FME用户来说,最可能的使用情况就是可以有一个类似Mapnik的转换器来完成矢量要素栅格化输出的工作。FME以及可以做此项工作,相关的转换器为ImageRasterizers,而非利用Mapnik任何的地方。同样,Mapnik可以读写不同的数据类型,但不具有接近于FME做到的任何格式范围。

幸运的是,虽然Mapnik是C++编写的,但其与Python的绑定,让我们这些业余的黑客更容易的使用它。

Mapnik和FME安装

我是在一个旧版的Mapnik(V0.7.1)的基础上创建了以下示例。最新的Mapnik程序版本为2.0,但我仍然认为它是个很好的例证。

下载Mapnik,我注意到的第一点是,它需要的PythonV1.6的版本支持;FME默认使用的是V1.7。因此,首要任务是安装的Python V1.6与FME的连接。通过一些试验和错误发现:Mapnik需要安装的Python为32位(这也就意味着我们仅限于32位的FME)。

在FME2012版中,获得FME需要的Python安装是轻而易举的。这是因为在【工具】-【FME选项】界面中有一项功能是指向正确的DLL。功能操作详见地址:FMEpedia Page,以我的安装环境(Python是32位,Windows是64位)目录为:C:\Windows\SysWOW64\python26.dll

这就是我现在FME使用的解译器版本。然后我下载并按照安装指南安装Mapnik。若此时被卡住的话,就不得不在PATH环境变量中添加两项内容:

c:\mapnik-0.7.1\lib\

c:\Python26\

这里看的话就有点模糊了,因为我不得不尝试某些试验以确定如何获得从FME到Mapnik的要素,与程序安装之间有什么影响。

如前所述,Mapnik有一些原始的最简单的格式插件(在V0.7.1),能够实现在FME中转换自输入数据到GeoJSON属性,Mapnik使用的种子为OGR插件。自OGR成为GDAL库的一部分,我就开始安装它。当然FME也在使用这个库,我只是说明Mapnik在使用而已。

所以,简而言之,我只是创建了一个新的系统环境变量GDAL_DATA,值为:c:\apps\FME2012\plugins\gdal\data\。如下图:

显然,您的路径可能会有所不同,具体取决于操作系统(因为我已经按照了2011版FME,所以我的安装的路径为:\apps\FME2012)。

FME工作空间

好了,安装完Mapnik,它的工作空间/脚本是什么样呢?

嗯,我喜欢创建一个合适的转换器-利用插件创建全部-去完成某工作,但最终它有点超出我的意料。我需要利用PythonCaller转换器来替代我创建的脚本。

以下是关于一个工作空间本身的截图:

当我给新建的要素设置颜色时,除此外,我还设置Mapnik中的符号例如线宽、显示顺序等属性信息。我可以利用Mapnik的涂抹 表面的功能,以及更多我能做的。我还可以将这些写入到XML定义的映射文件中,我决定据此做个小演示:手动定义符号与要素并直接利用脚本实现。我的意思就是下面将展示脚本。

某些转换器可以重定义数据投影到经纬度,然后我希望在Mapnik中显示的顺序排序。再然后获取运行脚本…

Python脚本

以下就是脚本。它不会赢得任何奖项,因为我已经过了在BASIC编程模式设置的思想时代,但它仍然存在。

import mapnik

import fmeobjects

import __main__

 

# Get FME Features

class MyFeatureProcessor(object):

def __init__(self):

self.featureList = []

 

def input(self,feature):

 

# Add to FeatureList

self.featureList.append(feature)

 

# Set up map and add FME features as individual layers

def close(self):

 

# Extract chosen background colour

fmeback = __main__.FME_MacroValues['MapnikBackground']

fmebrgb = fmeback.split(",")

mpnkbrgb = 'rgb('+str(int(float(fmebrgb[0])*100))+'%,'+str(int(float(fmebrgb[1])*100))+'%,'+str(int(float(fmebrgb[2])*100))+'%)'

 

# Create Mapnik Map

m = mapnik.Map(4000,3500,"+proj=latlong +datum=WGS84")

m.background = mapnik.Color(mpnkbrgb)

 

# Create Mapnik Layers/Rules/Styles

lyrs = {}

ruls = {}

styl = {}

stylcount = 0

 

for feature in self.featureList:

# Convert geometry to GeoJSON

feature.performFunction('@JSONGeometry(TO_ATTRIBUTE,GeoJSON,featGeom)')

fmegeom = feature.getAttribute('featGeom')

mpnkwid = feature.getAttribute('MapnikLineWidth')

 

# Fetch FME Fill Color

fmefill = feature.getAttribute('fme_fill_color')

fmefrgb = fmefill.split(",")

mpnkfrgb = 'rgb('+str(int(float(fmefrgb[0])*100))+'%,'+str(int(float(fmefrgb[1])*100))+'%,'+str(int(float(fmefrgb[2])*100))+'%)'

 

# Fetch FME Line Color

fmeline = feature.getAttribute('fme_color')

fmelrgb = fmeline.split(",")

mpnklrgb = 'rgb('+str(int(float(fmelrgb[0])*100))+'%,'+str(int(float(fmelrgb[1])*100))+'%,'+str(int(float(fmelrgb[2])*100))+'%)'

 

# Create Mapnik Rule

rulsLine = mapnik.LineSymbolizer(mapnik.Color(mpnklrgb),float(mpnkwid))

rulsPoly = mapnik.PolygonSymbolizer(mapnik.Color(mpnkfrgb))

rulsPoint = mapnik.PointSymbolizer("c:\FMEInput\BusStop.png", "png", 16, 16)

 

ruls[feature] = mapnik.Rule()

ruls[feature].symbols.append(rulsPoly)

ruls[feature].symbols.append(rulsLine)

ruls[feature].symbols.append(rulsPoint)

 

# Create Mapnik Style

styl[feature] = mapnik.Style()

styl[feature].rules.append(ruls[feature])

m.append_style('My Style'+str(stylcount),styl[feature])

 

lyrs[feature] = mapnik.Layer('world',"+proj=latlong +datum=WGS84")

lyrs[feature].datasource = mapnik.Ogr(file=fmegeom,layer='OGRGeoJSON')

lyrs[feature].styles.append('My Style'+str(stylcount))

 

m.layers.append(lyrs[feature])

 

stylcount += 1

 

# Render Mapnik Output

m.zoom_all()

mapnik.render_to_file(m,'c:\FMEOutput\ParksFromFME.png', 'png')

脚本解释

让我们来看一下脚本是如何运行的。

Mapnik有个地图-图层-样式-规则的结构。就像它在其文档中说的,地图对象是这个过程的核心。这是很容易在我的脚本中处理的:

# Create Mapnik Map

m = mapnik.Map(4000,3500,"+proj=latlong +datum=WGS84")

m.background = mapnik.Color(mpnkbrgb)

每张地图有一定数量的图层,每个图层有一个数据源,然后可以利用他们设置图层的图形样式和规则。

理想的情况下,每个图层都将从一系列FME样式和规则匹配每层的要素的符合设置。然而,我有点担心我是否可以传递一个整层的数据使用GeoJSON。所以我所做的是使每个FME要素在不同层

以下我将一个要素的几何图形写入GeoJSON中:

# Convert geometry to GeoJSON

feature.performFunction('@JSONGeometry(TO_ATTRIBUTE,GeoJSON,featGeom)')

fmegeom = feature.getAttribute('featGeom')

…and here I create a layer using that GeoJSON geometry as a datasource:

lyrs[feature] = mapnik.Layer('world',"+proj=latlong +datum=WGS84")

lyrs[feature].datasource = mapnik.Ogr(file=fmegeom,layer='OGRGeoJSON')

获取FME符合正确设置符号,将FME的RGB(1,1,1)转换到Mapnik的RGB(100%,100%,100%)…

# Fetch FME Fill Color

fmefill = feature.getAttribute('fme_fill_color')

fmefrgb = fmefill.split(",")

mpnkfrgb = 'rgb('+str(int(float(fmefrgb[0])*100))+'%,'+str(int(float(fmefrgb[1])*100))+'%,'+str(int(float(fmefrgb[2])*100))+'%)'

[btw, that mpnkfrgb= line is both beautiful and horrific to me at the same time!]

…创建一个Mapnik规则,利用此规则创建一个样式,并追加此样式到图层:

# Create Mapnik Rule

rulsPoly = mapnik.PolygonSymbolizer(mapnik.Color(mpnkfrgb))

ruls[feature] = mapnik.Rule()

ruls[feature].symbols.append(rulsPoly)

 

# Create Mapnik Style

styl[feature] = mapnik.Style()

styl[feature].rules.append(ruls[feature])

m.append_style('My Style'+str(stylcount),styl[feature])

 

lyrs[feature].styles.append('My Style'+str(stylcount))

做类似的线和点要素后,以下最后一行有一个渲染输出:

mapnik.render_to_file(m,'c:\FMEOutput\ParksFromFME.png', 'png')

这是Mapnik的部分。我认为理想的结构将是每个Mapnik的图层对应FME的要素类型,为每个要素“组”的符号规则的分配各自的样式。

Dale认为在一个外部的XML文件中定义将是很有用的。我同意此说法,在功能化最大下,我更喜欢利用其定义得到一个不错的转换器对话框(作为最终用户)。

顺便说一下,若我想要获得Mapnik 输出反馈到Workbench中,需要在PythonCaller的转换器之后使用RasterReader转换器。然后将有能力覆盖使用FME支持的其他任意栅格格式的输出。例如,我可以插入数据到一个数据库(Oracle Georaster或者ArcSDE Raster)中,或者写入到ECW或者GeoTIFF的格式文件中。

Function或者Class?

剩下的一个解释是我如何在此处理FME要素。

这是因为通常每个经过FME的要素将会触发Python脚本,并不会在这里做因为我会得到每个要素的单独图形。事实上PythonCaller转换器帮助文档介绍了在使用PythonCaller作为class或者function直接的不同点。

作为一个函数,PythonCaller调用了只有一个参数的Python 函数:FME要素对象。每个从输入端口通过的要素都会将用此函数。然后每个要素将继续通过工作空间管道到输出端口。

作为一个类,PythonCaller调用2个方法:input()和close()。input()方法将被每个通过输入端口的FME要素调用。当不再有FME要素,close()方法将被调用。

所以,来看一下我的脚本。根据输入方法,我简单的将每个输入要素添加到一个要素列表,然后利用close方法作为结尾。

def input(self,feature):

# Add to FeatureList

self.featureList.append(feature) 

def close(self):

脚本中“per feature”部分的结论。现在我将可以进行一次性建立Mapnik地图对象的操作,然后通过的每个要素运行以下的脚本:

for feature in self.featureList:

Simple….. sort of!

输出

当我完成这些操作后,输出将是什么样呢?

如下:

可能并不令人惊讶,但我很高兴,通过它可以展示给我们看,其整合以后的用处。

我认为至此做的关于Python的试验以完成,希望它可以帮助大家利用在FME中利用Python去说明一些工作,并可以得到其使用方法。

我希望接下来的文章是关于FME Server2012的安全更新中推出的文章。

原文地址http://evangelism.safe.com/fmeevangelist92/

猜你喜欢

转载自blog.csdn.net/fmechina/article/details/81213873