基于geoserver开发地图发布服务

写在前面:我在github上创建了对应的项目,可点此跳转,本文的所有源码均可在项目里找到,欢迎大家访问交流

一、开发背景

gis领域,geoserver是后端地图发布的开源项目。目前我们在启动服务后,可通过自带的web应用进行地图发布操作,这个过程是简单有效的,但是,如果我们想要把地图发布的功能集成到自己的平台里,仍旧使用geoserverweb应用就显得格格不入,为此我们需要对geoserver的功能进行封装,开发自己的后端服务,并配套对应的前端功能(自带的web应用只将其当作管理工具)。本篇研究了基于python语言开发的geoserver-restconfig库,该库封装了geoserver的常用操作函数,考虑到自身的需求以及该库存在的问题,本篇在该库的基础上开发了新的服务类,主要用于发布shp普通tiff大型tiff,经过测试,该服务类可正常使用,可用于后端服务的构建

二、geoserver-restconfig

基于python语言开发的geoserver使用库,安装: pip install geoserver-restconfig

可进行工作区的创建删除、shp图层的创建删除、tiff图层的创建删除等。但是在使用时存在两个问题:
1、类实例化失败(requests版本为2.30):TypeError: Retry.__init__() got an unexpected keyword argument 'method_whitelist'
2、该库不支持切片tiff的发布

三、GeoServerCatalog

针对上述问题,对该库的核心类Catalog进行派生,主要做两个改动

3.1 重构create_coveragestore函数

该函数可用于栅格数据的发布,其中GeoTIFF类型就是tiff文件发布对应的类型,在我的另一篇文章:使用geoserver发布shp和tiff数据 中已经介绍了使用geoserver发布切片tiff目录的方法,对应的类型是ImagePyramid,但此类型在该类中没有(没有是正常的,因为切片tiff类型是通过插件方式增加的),所以我们重写该函数,在allowed_types中增加ImagePyramid即可,其他相同
在这里插入图片描述

3.2 重构setup_connection函数

经调试发现,Retry 的报错是由于对象实例化时参数不对,主要是method_whitelist 这个变量名不对,经过排查,确认是requests库的版本不对,2.30版本的Retry类用的是allowed_methods2.28版本用的是method_whitelist,为了兼容不同的版本,在派生类中重写该函数,通过try except来捕获由于版本差异导致的异常,在捕获异常时,使用另一版本的参数

3.3 源码

# GeoServerCatalog.py
import os
from geoserver.catalog import Catalog, ConflictingDataError, _name, FailedRequestError
from geoserver.store import UnsavedCoverageStore
from geoserver.support import build_url

import requests
from requests.packages.urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

try:
    from urllib.parse import urlparse
except ImportError:
    from urlparse import urlparse

class GeoServerCatalog(Catalog):
    """
    geoserver.Catalog的派生类
    用于扩展基类的create_coveragestore函数,使其支持"ImagePyramid"类型
    """
    def __init__(self, service_url, username="admin", password="geoserver", validate_ssl_certificate=True, access_token=None, retries=3, backoff_factor=0.9):
        super().__init__(service_url, username, password, validate_ssl_certificate, access_token, retries, backoff_factor)

    def create_coveragestore(self, name, workspace=None, path=None, type='GeoTIFF',
                             create_layer=True, layer_name=None, source_name=None, upload_data=False, contet_type="image/tiff",
                             overwrite=False):
        """
        Create a coveragestore for locally hosted rasters.
        If create_layer is set to true, will create a coverage/layer.
        layer_name and source_name are only used if create_layer ia enabled. If not specified, the raster name will be used for both.
        """
        if path is None:
            raise Exception('You must provide a full path to the raster')

        if layer_name is not None and ":" in layer_name:
            ws_name, layer_name = layer_name.split(':')

        allowed_types = [
            'ImageMosaic',
            'GeoTIFF',
            'Gtopo30',
            'WorldImage',
            'AIG',
            'ArcGrid',
            'DTED',
            'EHdr',
            'ERDASImg',
            'ENVIHdr',
            'GeoPackage (mosaic)',
            'NITF',
            'RPFTOC',
            'RST',
            'VRT',
            'ImagePyramid'
        ]

        if type is None:
            raise Exception('Type must be declared')
        elif type not in allowed_types:
            raise Exception(f"Type must be one of {', '.join(allowed_types)}")

        if workspace is None:
            workspace = self.get_default_workspace()
        workspace = _name(workspace)

        if not overwrite:
            stores = self.get_stores(names=name, workspaces=[workspace])
            if len(stores) > 0:
                msg = f"There is already a store named {name} in workspace {workspace}"
                raise ConflictingDataError(msg)

        if upload_data is False:
            cs = UnsavedCoverageStore(self, name, workspace)
            cs.type = type
            cs.url = path if path.startswith("file:") else f"file:{path}"
            self.save(cs)

            if create_layer:
                if layer_name is None:
                    layer_name = os.path.splitext(os.path.basename(path))[0]
                if source_name is None:
                    source_name = os.path.splitext(os.path.basename(path))[0]

                data = f"<coverage><name>{layer_name}</name><nativeName>{source_name}</nativeName></coverage>"
                url = f"{self.service_url}/workspaces/{workspace}/coveragestores/{name}/coverages.xml"
                headers = {"Content-type": "application/xml"}

                resp = self.http_request(url, method='post', data=data, headers=headers)
                if resp.status_code != 201:
                    raise FailedRequestError('Failed to create coverage/layer {} for : {}, {}'.format(layer_name, name,
                                                                                                      resp.status_code, resp.text))
                self._cache.clear()
                return self.get_resources(names=layer_name, workspaces=[workspace])[0]
        else:
            data = open(path, 'rb')
            params = {"configure": "first", "coverageName": name}
            url = build_url(
                self.service_url,
                [
                    "workspaces",
                    workspace,
                    "coveragestores",
                    name,
                    f"file.{type.lower()}"
                ],
                params
            )

            headers = {"Content-type": contet_type}
            resp = self.http_request(url, method='put', data=data, headers=headers)

            if hasattr(data, "close"):
                data.close()

            if resp.status_code != 201:
                raise FailedRequestError('Failed to create coverage/layer {} for : {}, {}'.format(layer_name, name, resp.status_code, resp.text))

        return self.get_stores(names=name, workspaces=[workspace])[0]
    
    def setup_connection(self, retries=3, backoff_factor=0.9):
        self.client = requests.session()
        self.client.verify = self.validate_ssl_certificate
        parsed_url = urlparse(self.service_url)

        try:
            retry = Retry(
                total = retries or self.retries,
                status = retries or self.retries,
                read = retries or self.retries,
                connect = retries or self.retries,
                backoff_factor = backoff_factor or self.backoff_factor,
                status_forcelist = [502, 503, 504],
                allowed_methods = set(['HEAD', 'TRACE', 'GET', 'PUT', 'POST', 'OPTIONS', 'DELETE'])
            ) # allowed_methods : requests > 2.30.0
        except TypeError:
            retry = Retry(
                total = retries or self.retries,
                status = retries or self.retries,
                read = retries or self.retries,
                connect = retries or self.retries,
                backoff_factor = backoff_factor or self.backoff_factor,
                status_forcelist = [502, 503, 504],
                method_whitelist = set(['HEAD', 'TRACE', 'GET', 'PUT', 'POST', 'OPTIONS', 'DELETE'])
            ) # method_whitelist : requests <= 2.30.0

        self.client.mount(f"{parsed_url.scheme}://", HTTPAdapter(max_retries=retry))

四、GeoServerService

Catalog类提供的都是一些基本功能函数,为了能快速的发布数据,我们对GeoServerCatalog进行封装,构建GeoServerService类,该类主要用于实现发布shptiff金字塔tiff的功能

4.1 发布shp图层

函数名:createShapeLayer,参数如下。此函数可用于发布shp文件,其中styleParas是图层样式参数,用于控制图层的显示,主要支持点、线、多边形

workspaceName: 图层所在的工作空间的名称
layerName:图层的名称
shapePath:shape文件的路径
charset:dbf的字符集
styleParas:图层的样式参数,
point-{type:circle/rectangle/star, color:“#000000”, transparency:0.5, size:10}
polyline/line-{color:“#000000”, width:1}
polygon-{fill_color:“#AAAAAA”, outline_color:“#000000”, outline_width:1}

点的样式参数:

type:类型,支持圆、正方形、五角星
color: 颜色
transparency: 透明度
size: 大小

线的样式参数:

color: 颜色
width: 宽度

多边形的样式参数:

fill_color:填充颜色
outlin_color: 边框颜色
outline_width: 边框宽度

4.2 发布普通tiff图层

函数名:createTiffLayer,参数如下。此函数可用于发布<2GBtiff文件

workspaceName: 图层所在的工作空间的名称
layerName:图层的名称
tiffPath:tiff文件的路径

4.3 tiff切片

tiff切片需要用到gdal库,对应的下载地址为:windows版linux版

下载的是whl类型的,可通过pip直接安装

该类中的createPyramidTiff可将tiff文件切片生成金字塔级别的目录,参数介绍如下

tiffPath:tiff文件的路径
tiffDir:生成的金字塔文件夹的路径
level:金字塔层级,可选,默认为4
blockWidth: 金字塔切块的宽度分辨率,可选,默认为2048
blockHeight: 金字塔切块的高度分辨率,可选,默认为2048

4.4 发布金字塔切片tiff图层

函数名:createPyramidTiff,参数如下。此函数可用于发布>=2GBtiff文件,该函数会自动调用上述的切片函数,并发布

tiffPath:tiff文件的路径
tiffDir:生成的金字塔文件夹的路径
level:金字塔层级,可选,默认为4
blockWidth: 金字塔切块的宽度分辨率,可选,默认为2048
blockHeight: 金字塔切块的高度分辨率,可选,默认为2048

4.5 其他函数

除了上述4个比较常用的函数外,还有如创建工作空间获取工作空间列表修改样式等函数,可自行研究,不再详述

4.6 源码

# GeoServerService.py
import subprocess
import geoserver.util
from GeoServerCatalog import GeoServerCatalog


class GeoServerService(object):
    """
    基于GeoServerCatalog库封装的常用服务类
    """
    def __init__(self, url, username, password) -> None:
        self.__cat = GeoServerCatalog(url, username, password)

    def save(self, obj):
        self.__cat.save(obj)

    def getWorkspace(self, workspaceName):
        """
        通过名称获取工作空间
        workspaceName: 工作空间的名称
        return Workspace
        """

        return self.__cat.get_workspace(workspaceName)

    def isWorkspaceExist(self, workspaceName):
        """
        判断工作空间是否存在
        workspaceName: 工作空间的名称
        return True-存在;False-不存在
        """

        res = self.getWorkspace(workspaceName)
        if res == None:
            return False
        else:
            return True

    def createWorkspace(self, workspaceName):
        """
        创建工作空间
        workspaceName: 工作空间的名称
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-工作空间已存在;其他信息
            data: None
        }
        """

        res = {
            "status": "fail",
            "info": "",
            "data": None
        }

        try:
            if self.isWorkspaceExist(workspaceName):
                res["info"] = "工作空间已存在:{0}".format(workspaceName)
                return res

            self.__cat.create_workspace(workspaceName, "http://{0}.com".format(workspaceName))
            res["status"] = "success"
            return res
        except Exception as e:
            res["info"] = repr(e)
            return res

    def deleteWorkspace(self, workspaceName):
        """
        删除工作空间(会删除工作空间下的所有内容,包括图层和样式)
        workspaceName: 工作空间的名称
        """

        workspace = self.getWorkspace(workspaceName)
        if workspace != None:
            self.__cat.delete(workspace, None, True)

    def getStore(self, workspaceName, storeName):
        """
        通过名称获取数据存储
        workspaceName: 数据存储所在的工作空间的名称
        storeName:数据存储的名称
        return Store
        """

        return self.__cat.get_store(storeName, workspaceName)
    
    def isStoreExist(self, workspaceName, storeName):
        """
        判断数据存储是否存在
        workspaceName: 数据存储所在的工作空间的名称
        storeName:数据存储的名称
        return True-存在;False-不存在
        """

        res = self.getStore(workspaceName, storeName)
        if res == None:
            return False
        else:
            return True
    
    def deleteStore(self, workspaceName, storeName):
        """
        删除数据存储
        workspaceName: 数据存储所在的工作空间的名称
        storeName:数据存储的名称
        """

        store = self.getStore(workspaceName, storeName)
        if store != None:
            self.__cat.delete(store, None, True)

    def getLayer(self, workspaceName, layerName):
        """
        通过名称获取图层
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        return Layer
        """

        layerNameReg = "{0}:{1}".format(workspaceName, layerName)
        return self.__cat.get_layer(layerNameReg)

    def isLayerExist(self, workspaceName, layerName):
        """
        判断图层是否存在
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        return True-存在;False-不存在
        """

        res = self.getLayer(workspaceName, layerName)
        if res == None:
            return False
        else:
            return True

    def createShapeLayer(self, workspaceName, layerName, shapePath, charset):
        """
        创建Shp图层
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        shapePath:shape文件的路径
        charset:dbf的字符集
        styleParas:图层的样式参数, 
                   point-{type:circle/rectangle/star, color:"#000000", transparency:0.5, size:10}
                   polyline/line-{color:"#000000", width:1}
                   polygon-{fill_color:"#AAAAAA", outline_color:"#000000", outline_width:1}
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-工作空间不存在/图层已存在/其他信息
            data: Layer
        }
        """

        res = {
            "status": "fail",
            "info": "",
            "data": None
        }

        try:
            # 判断工作空间是否存在
            if not self.isWorkspaceExist(workspaceName):
                res["info"] = "工作空间不存在:{0}".format(workspaceName)
                return res

            # 判断图层是否存在
            if self.isLayerExist(workspaceName, layerName):
                res["info"] = "图层已存在:{0}".format(layerName)
                return res

            # 创建图层
            workspace = self.getWorkspace(workspaceName)
            try:
                data = geoserver.util.shapefile_and_friends(shapePath)
                self.__cat.create_featurestore(layerName, data, workspace, True, charset)
            except Exception as e:
                res["info"] = "文件解析错误"
                return res

            # 获取创建的图层
            layer = self.getLayer(workspaceName, layerName)
            styleType = layer.default_style.name
            
            res["status"] = "success"
            res["data"] = {
                "layer": layer,
                "default_style": styleType
            }
            return res
        except Exception as e:
            res["info"] = repr(e)
            return res

    def __createTiffLayer(self, workspaceName, layerName, tiffPath, pyramid=False):
        """
        创建Tiff图层
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        tiffPath:tiff文件/夹的路径
        pyramid: 是否为金字塔结构,默认false
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-工作空间不存在/图层已存在/其他信息
            data: {
                layer:生成的图层对象,
                default_style: 图层的默认样式
            }
        }
        """

        res = {
            "status": "fail",
            "info": "",
            "data": None
        }

        try:
            # 判断工作空间是否存在
            if not self.isWorkspaceExist(workspaceName):
                res["info"] = "工作空间不存在:{0}".format(workspaceName)
                return res

            # 判断图层是否存在
            if self.isLayerExist(workspaceName, layerName):
                res["info"] = "图层已存在:{0}".format(layerName)
                return res

            # 创建图层
            workspace = self.getWorkspace(workspaceName)
            try:
                tiffType = "GeoTIFF"
                if pyramid:
                    tiffType = "ImagePyramid"

                self.__cat.create_coveragestore(name=layerName, workspace=workspace, path=tiffPath, type=tiffType, layer_name=layerName)
            except Exception as e:
                res["info"] = "文件解析错误"
                return res

            # 获取创建的图层
            layer = self.getLayer(workspaceName, layerName)
            styleType = layer.default_style.name
            
            res["status"] = "success"
            res["data"] = {
                "layer": layer,
                "default_style": styleType
            }
            return res
        except Exception as e:
            res["info"] = repr(e)
            return res

    def createTiffLayer(self, workspaceName, layerName, tiffPath):
        """
        创建Tiff图层:适用于文件大小<2GB的Tiff
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        tiffPath:tiff文件的路径
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-工作空间不存在/图层已存在/其他信息
            data: Layer
        }
        """

        return self.__createTiffLayer(workspaceName, layerName, tiffPath)

    def createPyramidTiff(self, tiffPath, tiffDir, levels=4, blockWidth=2048, blockHeight=2048):
        """
        对tiff进行金字塔切片
        tiffPath:tiff文件的路径
        tiffDir:生成的金字塔文件夹的路径
        level:金字塔层级,可选,默认为4
        blockWidth: 金字塔切块的宽度分辨率,可选,默认为2048
        blockHeight: 金字塔切块的高度分辨率,可选,默认为2048
        """
        exeStr = 'python gdal_retile.py -v -r bilinear -ot BYTE -levels {0} -ps {1} {2} -co "ALPHA=YES" -targetDir {3} {4}'.format(levels, blockWidth, blockHeight, tiffDir, tiffPath)
        process = subprocess.Popen(exeStr, shell=True)
        process.wait()

    def createPyramidTiffLayer(self, workspaceName, layerName, tiffPath, tiffDir, levels=4, blockWidth=2048, blockHeight=2048):
        """
        创建金字塔切片后的Tiff图层:适用于文件大小>=2GB的Tiff
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        tiffPath:tiff文件的路径
        tiffDir:生成的金字塔文件夹的路径
        level:金字塔层级,可选,默认为4
        blockWidth: 金字塔切块的宽度分辨率,可选,默认为2048
        blockHeight: 金字塔切块的高度分辨率,可选,默认为2048
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-工作空间不存在/图层已存在/其他信息
            data: {
                layer:生成的图层对象,
                default_style: 图层的默认样式
            }
        }
        """
        res = {
            "status": "fail",
            "info": "",
            "data": None
        }
        try:
            # 先对tiff进行切片,生成金字塔结构目录
            try:
                self.createPyramidTiff(tiffPath, tiffDir, levels, blockWidth, blockHeight)
            except Exception as e:
                res["info"] = "切片错误"
                return res

            # 再创建金字塔数据图层
            return self.__createTiffLayer(workspaceName, layerName, tiffDir, True)
        except Exception as e:
            res["info"] = repr(e)
            return res

    def deleteLayer(self, workspaceName, layerName):
        """
        删除图层
        workspaceName: 图层所在的工作空间的名称
        layerName:图层的名称
        """

        layer = self.getLayer(workspaceName, layerName)
        if layer != None:
            self.__cat.delete(layer, None, True)

    def __getPointStyle(self, styleParas):
        """
        生成点类型的样式xml
        styleParas:样式参数 {type:circle/rectangle/star, color:"#000000", transparency:0.5, size:10}
        return xml
        """
        
        type = "circle"
        if "type" in styleParas:
            type = styleParas["type"]

        color = "#000000"
        if "color" in styleParas:
            color = styleParas["color"]

        transparency = 0
        if "transparency" in styleParas:
            transparency = styleParas["transparency"]

        size = 1
        if "size" in styleParas:
            size = styleParas["size"]

        style = '<?xml version="1.0" encoding="UTF-8"?>\r\n \
            <StyledLayerDescriptor version="1.0.0" \r\n \
                xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" \r\n \
                xmlns="http://www.opengis.net/sld" \r\n \
                xmlns:ogc="http://www.opengis.net/ogc" \r\n \
                xmlns:xlink="http://www.w3.org/1999/xlink" \r\n \
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r\n \
                <NamedLayer>\r\n \
                    <Name>default_point</Name>\r\n \
                    <UserStyle>\r\n \
                        <FeatureTypeStyle>\r\n \
                            <Rule>\r\n \
                                <PointSymbolizer>\r\n \
                                    <Graphic>\r\n \
                                        <Mark>\r\n \
                                            <WellKnownName>{0}</WellKnownName>\r\n \
                                            <Fill>\r\n \
                                              <CssParameter name="fill">{1}</CssParameter>\r\n \
                                              <CssParameter name="fill-opacity">{2}</CssParameter>\r\n \
                                            </Fill>\r\n \
                                        </Mark>\r\n \
                                        <Size>{3}</Size>\r\n \
                                    </Graphic>\r\n \
                                </PointSymbolizer>\r\n \
                            </Rule>\r\n \
                        </FeatureTypeStyle>\r\n \
                    </UserStyle>\r\n \
                </NamedLayer>\r\n\
            </StyledLayerDescriptor>'.format(type, color, 1.0-transparency, size)
        return style
    
    def __getPolylineStyle(self, styleParas):
        """
        生成线类型的样式xml
        styleParas:样式参数 {color:"#000000", width:1}
        return xml
        """

        color = "#000000"
        if "color" in styleParas:
            color = styleParas["color"]

        width = 1
        if "width" in styleParas:
            width = styleParas["width"]

        style = '<?xml version="1.0" encoding="UTF-8"?>\r\n \
            <StyledLayerDescriptor version="1.0.0" \r\n \
                xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" \r\n \
                xmlns="http://www.opengis.net/sld" \r\n \
                xmlns:ogc="http://www.opengis.net/ogc" \r\n \
                xmlns:xlink="http://www.w3.org/1999/xlink" \r\n \
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r\n \
                <NamedLayer>\r\n \
                    <Name>default_line</Name>\r\n \
                    <UserStyle>\r\n \
                        <FeatureTypeStyle>\r\n \
                            <Rule>\r\n \
                                <LineSymbolizer>\r\n \
                                    <Stroke>\r\n \
                                        <CssParameter name="stroke">{0}</CssParameter>\r\n \
                                        <CssParameter name="stroke-width">{1}</CssParameter>\r\n \
                                    </Stroke>\r\n \
                                </LineSymbolizer>\r\n \
                            </Rule>\r\n \
                        </FeatureTypeStyle>\r\n \
                    </UserStyle>\r\n \
                </NamedLayer>\r\n\
            </StyledLayerDescriptor>'.format(color, width)
        return style

    def __getPolygonStyle(self, styleParas):
        """
        生成多边形类型的样式xml
        styleParas:样式参数 {fill_color:"#AAAAAA", outline_color:"#000000", outline_width:1}
        return xml
        """

        fill_color = "#AAAAAA"
        if "fill_color" in styleParas:
            fill_color = styleParas["fill_color"]

        outline_color = "#000000"
        if "outline_color" in styleParas:
            outline_color = styleParas["outline_color"]

        outline_width = 1
        if "outline_width" in styleParas:
            outline_width = styleParas["outline_width"]

        style = '<?xml version="1.0" encoding="UTF-8"?>\r\n \
            <StyledLayerDescriptor version="1.0.0" \r\n \
                xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" \r\n \
                xmlns="http://www.opengis.net/sld" \r\n \
                xmlns:ogc="http://www.opengis.net/ogc" \r\n \
                xmlns:xlink="http://www.w3.org/1999/xlink" \r\n \
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r\n \
                <NamedLayer>\r\n \
                    <Name>default_polygon</Name>\r\n \
                    <UserStyle>\r\n \
                        <FeatureTypeStyle>\r\n \
                            <Rule>\r\n \
                                <PolygonSymbolizer>\r\n \
                                    <Fill>\r\n \
                                        <CssParameter name="fill">{0}</CssParameter>\r\n \
                                    </Fill>\r\n \
                                    <Stroke>\r\n \
                                        <CssParameter name="stroke">{1}</CssParameter>\r\n \
                                        <CssParameter name="stroke-width">{2}</CssParameter>\r\n \
                                    </Stroke>\r\n \
                                </PolygonSymbolizer>\r\n \
                            </Rule>\r\n \
                        </FeatureTypeStyle>\r\n \
                    </UserStyle>\r\n \
                </NamedLayer>\r\n\
            </StyledLayerDescriptor>'.format(fill_color, outline_color, outline_width)
        return style

    def getStyle(self, workspaceName, styleName):
        """
        通过名称获取样式
        workspaceName: 样式所在的工作空间的名称
        styleName:样式的名称
        return Style
        """

        return self.__cat.get_style(styleName, workspaceName)

    def isStyleExist(self, workspaceName, styleName):
        """
        判断样式是否存在
        workspaceName: 样式所在的工作空间的名称
        styleName:样式的名称
        return True-存在;False-不存在
        """

        res = self.getStyle(workspaceName, styleName)
        if res == None:
            return False
        else:
            return True

    def createStyle(self, workspaceName, styleName, styleType, styleParas):
        """
        创建样式
        workspaceName: 样式所在的工作空间的名称
        styleName:样式的名称
        styleType:样式的类型 point/polyline/line/polygon
        styleParas: 样式的参数, 
                   point-{type:circle/rectangle/star, color:"#000000", transparency:0.5, size:10}
                   polyline/line-{color:"#000000", width:1}
                   polygon-{fill_color:"#AAAAAA", outline_color:"#000000", outline_width:1}
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-不支持的样式类型
            data: Style
        }
        """

        res = {
            "status": "fail",
            "info": "",
            "data": None
        }

        styleData = None
        if styleType == "point":
            styleData = self.__getPointStyle(styleParas)
        elif styleType == "polyline" or styleType == "line":
            styleData = self.__getPolylineStyle(styleParas)
        elif styleType == "polygon":
            styleData = self.__getPolygonStyle(styleParas)
        else:
            res["info"] = "不支持的样式类型"
            return res

        style = self.__cat.create_style(styleName, styleData, overwrite=True, workspace=workspaceName)
        res["status"] = "success"
        res["data"] = style
        return res

    def deleteStyle(self, workspaceName, styleName):
        """
        删除样式
        workspaceName: 样式所在的工作空间的名称
        styleName:样式的名称
        """

        style = self.getStyle(workspaceName, styleName)
        if style != None:
            self.__cat.delete(style, None, True)

    def updateStyle(self, workspaceName, styleName, styleType, styleParas):
        """
        更新样式
        workspaceName: 样式所在的工作空间的名称
        styleName:样式的名称
        styleType:样式的类型 point/polyline/line/polygon
        styleParas: 样式的参数, 
                   point-{type:circle/rectangle/star, color:"#000000", transparency:0.5, size:10}
                   polyline/line-{color:"#000000", width:1}
                   polygon-{fill_color:"#AAAAAA", outline_color:"#000000", outline_width:1}
        return {
            status: 状态,success-创建成功;fail-创建失败
            info: 信息, success-""; fail-样式不存在/不支持的样式类型
            data: Style
        }
        """

        res = {
            "status": "fail",
            "info": "",
            "data": None
        }

        if not self.isStyleExist(workspaceName, styleName):
            res["info"] = "样式不存在:{0}".format(styleName)
            return res

        # 获取样式
        style = self.getStyle(workspaceName, styleName)
        
        if styleType == "point":
            data = self.__getPointStyle(styleParas)
        elif styleType == "polyline" or styleType == "line":
            data = self.__getPolylineStyle(styleParas)
        elif styleType == "polygon":
            data = self.__getPolygonStyle(styleParas)
        else:
            res["info"] = "不支持的样式类型"
            return res
        
        style.update_body(data)
        res["status"] = "success"
        return res
    
    def getWorkSpaces(self):
        return self.__cat.get_workspaces()

五、案例

在此提供3demo程序,分别是发布shp发布普通tiff发布大型tiff,创建成功后可通过geoserverweb应用程序进行验证

5.1 发布shp

# demo_publish_shp.py 演示案例:使用GeoServerService发布SHP服务

from GeoServerService import GeoServerService

# 初始化服务
url = "http://localhost:8080/geoserver/rest" # geoserver url
username = "admin" # geoserver username
password = "geoserver" # geoserver password

service = GeoServerService(url, username, password)

# 准备参数
workspaceName = "test" # geoserver 工作空间名称
layerName = "shp_layer" # geoserver 图层名称
shapePath = "" # 用于发布的shp文件路径,精确到文件,不带后缀
charset = "utf-8" # dbf文件的编码格式,不影响发布,主要影响发布后的属性拾取,如果编码格式不对,拾取到的中文属性会乱码

# 检查工作空间是否存在,如果不存在,则创建
if not service.isWorkspaceExist(workspaceName):
    service.createWorkspace(workspaceName)

# 检查图层是否已存在,如果存在,则删除
if service.isStoreExist(workspaceName, layerName):
    service.deleteStore(workspaceName, layerName)

# 发布图层  
res = service.createShapeLayer(workspaceName, layerName, shapePath, charset)
print(res)

5.2 发布普通tiff

# demo_publish_tiff.py 演示案例:使用GeoServerService发布普通TIFF(<2GB)服务

from GeoServerService import GeoServerService

# 初始化服务
url = "http://localhost:8080/geoserver/rest" # geoserver url
username = "admin" # geoserver username
password = "geoserver" # geoserver password

service = GeoServerService(url, username, password)

# 准备参数
workspaceName = "test" # geoserver 工作空间名称
layerName = "tiff_layer" # geoserver 图层名称
tiffPath = "" # 用于发布的TIFF文件路径

# 检查工作空间是否存在,如果不存在,则创建
if not service.isWorkspaceExist(workspaceName):
    service.createWorkspace(workspaceName)

# 检查图层是否已存在,如果存在,则删除
if service.isStoreExist(workspaceName, layerName):
    service.deleteStore(workspaceName, layerName)

# 发布图层  
res = service.createTiffLayer(workspaceName, layerName, tiffPath)
print(res)

5.3 发布大型tiff

# demo_publish_pyramid_tiff.py 演示案例:使用GeoServerService发布金字塔切片的TIFF(>=2GB)服务

import os
import shutil
from GeoServerService import GeoServerService

# 初始化服务
url = "http://localhost:8080/geoserver/rest" # geoserver url
username = "admin" # geoserver username
password = "geoserver" # geoserver password

service = GeoServerService(url, username, password)

# 准备参数
workspaceName = "test" # geoserver 工作空间名称
layerName = "pyramid_tiff_layer" # geoserver 图层名称
tiffPath = "" # 用于发布的TIFF文件路径
tiffDir = "" # 切片后生成的TIFF金字塔文件夹路径
levels = 4 # 金字塔层级
blockWidth = 2048 # 金字塔每个块的宽度
blockHeight = 2048 # 金字塔每个块的高度

# 检查待生成的文件夹是否存在,如果存在则清空,否则创建
if os.path.isdir(tiffDir):
    shutil.rmtree(tiffDir) 
os.mkdir(tiffDir)

# 检查工作空间是否存在,如果不存在,则创建
if not service.isWorkspaceExist(workspaceName):
    service.createWorkspace(workspaceName)

# 检查图层是否已存在,如果存在,则删除
if service.isStoreExist(workspaceName, layerName):
    service.deleteStore(workspaceName, layerName)

# 发布图层
res = service.createPyramidTiffLayer(workspaceName, layerName, tiffPath, tiffDir, levels, blockWidth, blockHeight)
print(res)
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何又沐风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值