基于geoserver开发地图发布服务

news2024/11/15 17:20:15

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

一、开发背景

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

二、geoserver-restconfig

基于python语言开发的geoserver使用库,可进行工作区的创建删除、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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/612491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

科研工具-R-META分析与【文献计量分析、贝叶斯、机器学习等】多技术融合实践

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

【AIGC】14、GLIPv2 | 在 GLIP 上扩展 negative phrase 并新增分割功能

文章目录 一、背景二、方法2.1 A Unified VL Formulation and Architecture2.2 GLIPv2 pre-training2.3 将 GLIPv2 迁移到 Localization 和 VL task 三、结果3.1 One model architecture for all3.2 One set of model parameters for all3.3 GLIPv2 as a strong few-shot learn…

Latex使用algorithm2e包写伪代码

用Latex写伪代码我们需要用到一个包&#xff0c;Algorithm2e&#xff0c;这个工具包的使用手册下载地址为&#xff08;http://mlg.ulb.ac.be/files/algorithm2e.pdf&#xff09;CSDN的链接为&#xff08;&#xff09; 准备 导入该包 \usepackage[ruled,linesnumbered]{algor…

上海亚商投顾:沪指小幅震荡微涨 AI应用端持续活跃

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 大小指数今日走势分化&#xff0c;沪指全天窄幅震荡&#xff0c;创业板指低开低走&#xff0c;盘中一度跌超1.6%&a…

【Java基础】I/O流 —— Java中的流都需要关闭吗?

目录 一、为什么要关闭流&#xff1f;二、close方法和flush方法1.使用close方法2.使用flush方法 三、流按指向分类四、不用关闭的流 一、为什么要关闭流&#xff1f; 涉及到对外部资源的读写操作&#xff0c;包括网络、硬盘等等的I/O流&#xff0c;如果在使用完毕之后不关闭&a…

Unity基础框架从0到1(六)对象池模块

索引 这是Unity基础框架从0到1的第六篇文章&#xff0c;框架系列的项目地址是&#xff1a;https://github.com/tang-xiaolong/SimpleGameFramework 文章最后有目前框架系列的思维导图&#xff0c;前面的文章和对应的视频我一起列到这里&#xff1a; 文章 Unity基础框架从0到…

算力不竭如江海,天翼云“息壤”如何助力千行百业算力智能调度?

科技云报道原创。 数字时代下&#xff0c;算力已成为新型生产力&#xff0c;并朝着多元泛在、安全可靠、绿色低碳的方向演进。以算力为核心的数字信息基础设施&#xff0c;是国家战略性布局的关键组成部分&#xff0c;也成为数字经济时代的“大国重器”。 作为云服务国家队&am…

报表生成器FastReport .Net教程:“Text“对象、文本编辑

FastReport .Net是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案&#xff0c;使用FastReport .NET可以创建独立于应用程序的.NET报表&#xff0c;同时FastReport .Net支持中文、英语等14种语言&#xff0c;可以让你的产品保证真正的国际性。 FastReport.NET官方版…

es elasticsearch 十四 各种机制 评分机制 正序索引 解决跳跃结果问题 解决耗时过长问题 解决相同属性值都到一个地方

目录 评分机制 机制 查看评分实现如何算出来的explaintrue 分析能否被搜索到 Doc value 正排序索引 Query phase Fetch phase Preference 问题 解决跳跃结果问题 Timeout 到达时间直接返回&#xff0c;解决耗时过长问题 Routing 数据准确分配到某地&#xff0c;解决相…

这才叫软件测试工程师,你那最多是混口饭吃罢了....

前些天和大学室友小聚了一下&#xff0c;喝酒喝大发了&#xff0c;谈天谈地谈人生理想&#xff0c;也谈到了我们各自的发展&#xff0c;感触颇多。曾经找工作我迷茫过、徘徊不&#xff0c;毕业那会我屡屡面试失败&#xff0c;处处碰壁&#xff1b;工作两年后我一度想要升职加薪…

006+limou+C语言“堆的实现”与“树的相关概念”

0.前言 这里是limou3434的一篇个人博文&#xff0c;感兴趣可以看看我的其他内容。本次我给您带来的是树的相关只是&#xff0c;并且把堆这一数据结构做了实现&#xff0c;后面还有大量的oj题目。但是树重点也就在这十多道oj题目中&#xff0c;您可以尝试着自己做一下&#xff…

我的创作纪念日|写在CSDN创作第512天

机缘 今天无意中发现CSDN后台给我发送私信&#xff0c;才发觉原来我的第一篇博客更新已经过去512天了&#xff0c;512天一晃而过居然还有点恍然。 作为一名网络专业的在校大学生&#xff0c;最初开始查找相关的资料其实更习惯于从外站进行查找&#xff0c;却总是在不经意中进入…

人事管理项目-前端实现

人事管理项目-前端实现 引入Element和Axios开发Login页面配置路由配置请求转发启动前端项目 引入Element和Axios 前端UI使用Element&#xff0c;网络请求则使用Axios&#xff0c;因此首先安装Element和Axios依赖&#xff0c;代码如下&#xff1a; 依赖添加成功后&#xff0c;接…

N-propargyloxycarbonyl-L-lysine,1215204-46-8,是一种基于赖氨酸的非天然氨基酸 (UAA)

产品描述&#xff1a; N-ε-propargyloxycarbonyl-L-lysine (H-L-Lys(Poc)-OH) 是一种基于赖氨酸的非天然氨基酸 (UAA)。 广泛用于多种生物体中荧光探针的生物偶联。 N- ε- Propargyloxycarbonyl-L-lysine (H-L-Lys (Poc) - OH) is a non natural amino acid (UAA) based on …

Kotlin Channel系列(一)之读懂Channel每一行源码

文章目录 有话说概述初识ChannelChannel种类Channel五大金刚SendReceiveClosedQueueBuffer Channel的行为Channel源码分析发送数据大动脉接收数据大动脉父类默认实现方式(RendezvousChannel)发送流程send()函数onSend()函数 接收流程receiveCatching()函数onReceiveCatching()函…

基于图像处理的圆检测与深度学习

基于图像处理的圆检测与深度学习 摘 要一、 绪论二 、图像预处理2.1 滤波算法2.2 边缘检测 三 、圆识别与定位算法3.2 定位算法3.2.1 迭代算法 4.1 数据处理 五、深度学习介绍&#xff1a;参考文献 摘 要 本文主要论述在图像处理的的基础上&#xff0c;为了克服图像背景中的亮…

SpringBootWeb案例-2(下)

3. 修改员工 需求&#xff1a;修改员工信息 在进行修改员工信息的时候&#xff0c;我们首先先要根据员工的ID查询员工的信息用于页面回显展示&#xff0c;然后用户修改员工数据之后&#xff0c;点击保存按钮&#xff0c;就可以将修改的数据提交到服务端&#xff0c;保存到数据…

面试专题:java虚拟机(2)

8.垃圾收集有哪些算法&#xff0c;各自的特点&#xff1f; 标记清除 直接将要回收的对象标记&#xff0c;发送gc的时候直接回收&#xff1a;特点回收特别快&#xff0c;但是回收以后会造成 很多不连续的内存空间&#xff0c;因此适合在老年代进行回收&#xff0c;CMS(current…

Linux--ServerProgramming--(4)详解 I/O复用

1. I/O 复用功能 I/O 复用能同时监听多个文件描述符。 I/O 复用本身是阻塞的。 当有多个文件描述符同时就绪时&#xff1a;若不采取额外措施&#xff0c;程序就只能按顺序一次处理其中的每一个文件描述符&#xff0c;这使得服务器程序看起来是串行工作的。若要实现并发&#…

ChatGPT市场营销指南震撼出炉,你错过了?!

ChatGPT是一种基于AI技术的语言模型&#xff0c;它可以与用户进行对话和交互。它被广泛应用于各个领域&#xff0c;包括市场营销。作为一名市场营销人员&#xff0c;您可以使用ChatGPT来获得创意、解决问题和生成内容。 下面是190个ChatGPT提示&#xff0c;可帮助营销人员更好…