【Python Mapnik+GDAL实现免发布切片服务】

项目需求,现有一批shp和tif数据,前端想要预览,但是没有服务发布环境;giser给他解决。

不废话、上代码。

1、搭建Python Web服务

我选择的是tornado框架,实现对外请求接口

import os
import sys
import tornado.web
import tornado.gen as gen

class MapnikTileServerHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, rid, bsm, col, row, level):
        self.set_header("Content-type", "image/png")
        self.set_header('Access-Control-Allow-Origin', '*')
        print('===========MapnikTileServerTileHandler============Starting...')
        try:
            rootPath = "{}/{}".format(cfg.TemDir, rid)
            if not os.path.exists(rootPath):
                self.write(u"无数据源,请检查。")
                return None
            mp = MapnikHandlerClass()
            jsonFile_local = "{}/cof.json".format(rootPath)
            with open(jsonFile_local, 'r') as fp:
                key = json.loads(fp.read())
            localPaths = json.loads(mp.b64decode(bsm))
            pngFile = mp.main(rid, localPaths, key, float(col), float(row), level)

            with open(pngFile, 'rb') as pic:
                pics = pic.read()
                self.write(pics)

        except Exception as err:
            log.writeLogWithTime(rid, str(err))
            print (err)
        finally:
            print('==================================================End')

2、引入mapnik

实现简单样式渲染和数据转换

    def main(self, rid, localPaths, key,col=-1,row=-1,level=-1):

        filePath = "{}/{}/L{}/{}_{}.png".format(cfg.MxdDir, rid, level, row, col)
        if os.path.exists(filePath):
            return filePath
        datatype = key["dataType"].lower()
        polygon_color = str(key["polygon_color"]) if "polygon_color" in key else "#F5F9F9"
        line_color = str(key["line_color"]) if "line_color" in key else "#FF1400"
        line_width = key["line_width"] if "line_width" in key else "0.5"

        m = mapnik.Map(256, 256)
        # m.background = mapnik.Color('steelblue')
        # localPaths = ["D:\Data\shp/4407042014JBNTBHPK_ZW2_V2014.shp"]
        box_main = None
        for localPath in localPaths:
            print (localPath)
            name = localPath.replace('\\','/').split('/')[-1].split('.')[0]
            print (name)
            lyr = mapnik.Layer("{}_{}".format(name, datatype))
            if datatype == "shp":
                localPath = self.transform4326(localPath)
                lyr.datasource = mapnik.Shapefile(file=localPath)
                style_value = self.set_style_polygon(polygon_color, line_color, line_width)
                style_name = "{}_Style".format(name)
                m.append_style(style_name, style_value)
                lyr.styles.append(style_name)
                srs = lyr.srs
                m.layers.append(lyr)

            elif datatype == "tif":
                localPath = self.transform4326(localPath)
                localPath = self.create_map(localPath)
                # lyr.datasource = mapnik.Gdal(file=localPath)
                mapnik.load_map(m, localPath)

            else:
                return None

        if "-1" in [str(row), str(col), str(level)]:
            m.zoom_all()
            savePath = "{}/{}/{}.png".format(cfg.TemDir, rid, uuid.uuid4())
            mapnik.render_to_file(m, savePath)
        else:
            savePath = self.getTile(rid, m, col, row, level) #if datatype == "shp" else self.makeTile(rid, m, col, row, level,box_main)

        print (savePath)

        return savePath

3、基于GDAL

实现数据转换,任何数据进来都按内部坐标系进行统一

    def transform4326(self,input_lyr):

        file_path, file_name = os.path.split(input_lyr)
        out_lyr = "{}/copy_{}".format(file_path, file_name)
        if os.path.exists(out_lyr):
            return out_lyr

        srs_proj4 = osr.SpatialReference()
        srs_proj4.ImportFromEPSG(4326)
        if input_lyr.endswith('.tif'):
            # ds = gdal.Warp(out_lyr, input_lyr, srcSRS='EPSG:{}'.format(wkid), dstSRS=srs_proj4)
            gdal.Warp(out_lyr, input_lyr, dstSRS=srs_proj4)
        elif input_lyr.endswith('.shp'):
            gdal.VectorTranslate(out_lyr, input_lyr, dstSRS=srs_proj4, reproject=True)
        return out_lyr

4、切片算法

根据前端传过来的行列号和层级进行切片计算,然后切成地图瓦片

    def getTile(self,rid,m,col,row,level):

        tiledirPath = "{}/{}".format(cfg.MxdDir, rid)
        pathroot = "{}/L{}".format(tiledirPath, level)

        imgname = "{}_{}.png".format(row, col)

        filePath = "{}/{}".format(pathroot,imgname)

        if os.path.exists(filePath):
            return filePath

        if not os.path.exists(pathroot):
            os.makedirs(pathroot)

        tilesize = 256
        # m = mapnik.Map(tilesize, tilesize)
        tileInfo = self.getResolution("4326")[str(level)]
        step = float(tileInfo["resolution"]) * tilesize
        origin = cfg.GetOrigin("4326")

        minx = origin["x"] + step * float(col)
        miny = origin["y"] - step * (float(row) + 1)
        maxx = minx + step
        maxy = miny + step

        print ([minx,miny,maxx,maxy])


        m.zoom_to_box(mapnik.Box2d(minx,miny,maxx,maxy))

        mapnik.render_to_file(m, filePath)

        return filePath

5、整体实现

class MapnikHandlerClass():

    def create_map(self,localPath):
        file_path, file_name = os.path.split(localPath)
        savePath = "{}/map_{}.xml".format(file_path, file_name.split('.')[0])
        if os.path.exists(savePath):
            return savePath
        str_proj = "+proj=longlat +datum=WGS84 +no_defs +type=crs"
        doc = Document()
        map = doc.createElement('Map')
        map.setAttribute('srs', str_proj)
        doc.appendChild(map)

        s = doc.createElement("Style")
        s.setAttribute("name", "color relief style")
        map.appendChild(s)

        rule = doc.createElement("Rule")
        s.appendChild(rule)

        raster = doc.createElement("RasterSymbolizer")
        raster.setAttribute("mode", "normal")
        rule.appendChild(raster)

        lyr = doc.createElement("Layer")
        lyr.setAttribute("name", "color relief")
        map.appendChild(lyr)

        s_name = doc.createElement("StyleName")
        text = doc.createTextNode("color relief style")
        s_name.appendChild(text)
        lyr.appendChild(s_name)

        ds = doc.createElement("Datasource")
        ty = doc.createElement("Parameter")
        ty.setAttribute("name", "type")
        p_ty = doc.createTextNode("gdal")
        ty.appendChild(p_ty)
        ds.appendChild(ty)

        fe = doc.createElement("Parameter")
        fe.setAttribute("name", "file")
        p_fe = doc.createTextNode(localPath)
        fe.appendChild(p_fe)
        ds.appendChild(fe)
        lyr.appendChild(ds)

        with open(savePath, "w+") as f:
            # 文件转码decode是为了保证输出的是UTF-8格式
            f.write(doc.toprettyxml(encoding="UTF-8").decode("UTF-8"))
            f.close()

        return savePath

    def transform4326(self,input_lyr):

        file_path, file_name = os.path.split(input_lyr)
        out_lyr = "{}/copy_{}".format(file_path, file_name)
        if os.path.exists(out_lyr):
            return out_lyr

        srs_proj4 = osr.SpatialReference()
        srs_proj4.ImportFromEPSG(4326)
        if input_lyr.endswith('.tif'):
            # ds = gdal.Warp(out_lyr, input_lyr, srcSRS='EPSG:{}'.format(wkid), dstSRS=srs_proj4)
            gdal.Warp(out_lyr, input_lyr, dstSRS=srs_proj4)
        elif input_lyr.endswith('.shp'):
            gdal.VectorTranslate(out_lyr, input_lyr, dstSRS=srs_proj4, reproject=True)
        return out_lyr


    def b64decode(self,value):
        u"""
        base64解码
        :param value: 待解码字符串
        :return: 解码后的字符串
        """
        return base64.b64decode(value + '=' * 3).decode('utf-8')

    def b64encode(self,s):
        u"""
        base64编码
        :param s: 待编码字符串
        :return: 编码后的字符串
        """
        return base64.b64encode(s.encode('utf-8')).decode('utf-8').replace('=', '')

    def set_PolygonSymbol(self,rule, color='#F5F9F9'):
        rule.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color(color)))

    def set_LineSymbol(self,rule, color='#FF1400',width=0.5):
        rule.symbols.append(mapnik.LineSymbolizer(mapnik.Color(color), float(width)))

    def set_style_polygon(self, polygon_color, line_color, line_width):
        style = mapnik.Style()
        rule = mapnik.Rule()
        self.set_PolygonSymbol(rule,polygon_color)
        self.set_LineSymbol(rule,line_color,line_width)
        style.rules.append(rule)
        return style

    def set_RasterSymbol(self):
        style = mapnik.Style()
        rule = mapnik.Rule()
        r = mapnik.RasterSymbolizer()
        r.colorizer = mapnik.RasterColorizer()
        for value, color in [
            (0, "#7FFFD4"),
            (1, "#76EEC6"),
            (2, "#66CDAA"),
            (3, "#458B74"),
            (4, "#DAA520"),
            (5, "#EEDD82"),
            (6, "#7FFFD4"),
            (7, "#76EEC6"),
            (8, "#66CDAA"),
            (9, "#458B74")
        ]:
            r.colorizer.add_stop(value, mapnik.Color(color))
        rule.symbols.append(r)
        style.rules.append(rule)
        return style



    def getlyrinfo(self, dic_key,dataType):
        extent = None
        for i in dic_key:
            localPath = self.transform4326(i)
            if dataType == "shp":
                dataset = ogr.Open(localPath)  # 读取SHP数据
                lyr = dataset.GetLayer(0)  # 获取图层
                extent = lyr.GetExtent()
                xmin = extent[0]
                xmax = extent[1]
                ymin = extent[2]
                ymax = extent[3]
                
                extent = {'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax}
            elif dataType == "tif":
                dataset = gdal.Open(localPath)

                RasterYSize = dataset.RasterYSize
                RasterXSize = dataset.RasterXSize

                transform = dataset.GetGeoTransform()
                x0 = transform[0]
                y0 = transform[3]
                cx = transform[1]
                cy = transform[5]

                xmin = x0
                ymin = y0 + RasterYSize * cy
                xmax = x0 + (RasterXSize * cx)
                ymax = y0
                
                extent = {'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax}



        return extent

    def main(self, rid, localPaths, key,col=-1,row=-1,level=-1):

        filePath = "{}/{}/L{}/{}_{}.png".format(cfg.MxdDir, rid, level, row, col)
        if os.path.exists(filePath):
            return filePath
        datatype = key["dataType"].lower()
        polygon_color = str(key["polygon_color"]) if "polygon_color" in key else "#F5F9F9"
        line_color = str(key["line_color"]) if "line_color" in key else "#FF1400"
        line_width = key["line_width"] if "line_width" in key else "0.5"

        m = mapnik.Map(256, 256)
        # m.background = mapnik.Color('steelblue')
        # localPaths = ["D:\Data\shp/4407042014JBNTBHPK_ZW2_V2014.shp"]
        box_main = None
        for localPath in localPaths:
            print (localPath)
            name = localPath.replace('\\','/').split('/')[-1].split('.')[0]
            print (name)
            lyr = mapnik.Layer("{}_{}".format(name, datatype))
            if datatype == "shp":
                localPath = self.transform4326(localPath)
                lyr.datasource = mapnik.Shapefile(file=localPath)
                style_value = self.set_style_polygon(polygon_color, line_color, line_width)
                style_name = "{}_Style".format(name)
                m.append_style(style_name, style_value)
                lyr.styles.append(style_name)
                srs = lyr.srs
                m.layers.append(lyr)

            elif datatype == "tif":
                localPath = self.transform4326(localPath)
                localPath = self.create_map(localPath)
                # lyr.datasource = mapnik.Gdal(file=localPath)
                mapnik.load_map(m, localPath)

            else:
                return None

        if "-1" in [str(row), str(col), str(level)]:
            m.zoom_all()
            savePath = "{}/{}/{}.png".format(cfg.TemDir, rid, uuid.uuid4())
            mapnik.render_to_file(m, savePath)
        else:
            savePath = self.getTile(rid, m, col, row, level) #if datatype == "shp" else self.makeTile(rid, m, col, row, level,box_main)

        print (savePath)

        return savePath

    

    def getResolution(self,wkid):

        dic = {
            "4326":{ str(i):{"resolution":1.40625/pow(2,i), "Scale":279541132.014359*2/pow(2,i)} for i in range(30)}
        }
        # for i in range(20):
        #     dic["4326"][str(i)] =
        return dic[wkid] if wkid in dic else None

    def getTile(self,rid,m,col,row,level):

        tiledirPath = "{}/{}".format(cfg.MxdDir, rid)
        pathroot = "{}/L{}".format(tiledirPath, level)

        imgname = "{}_{}.png".format(row, col)

        filePath = "{}/{}".format(pathroot,imgname)

        if os.path.exists(filePath):
            return filePath

        if not os.path.exists(pathroot):
            os.makedirs(pathroot)

        tilesize = 256
        # m = mapnik.Map(tilesize, tilesize)
        tileInfo = self.getResolution("4326")[str(level)]
        step = float(tileInfo["resolution"]) * tilesize
        origin = cfg.GetOrigin("4326")

        minx = origin["x"] + step * float(col)
        miny = origin["y"] - step * (float(row) + 1)
        maxx = minx + step
        maxy = miny + step

        print ([minx,miny,maxx,maxy])

        # if str(int(level)) == "0":
        #     m.zoom_all()
        # else:
        m.zoom_to_box(mapnik.Box2d(minx,miny,maxx,maxy))

        mapnik.render_to_file(m, filePath)

        return filePath

   

6、切片效果

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值