import re
import time
import uuid
import os
from typing import List
import progressbar
from iclientpy.rest.api.model import Rectangle2D, Point2D, ServiceType, TileSize, OutputFormat, PostWorkspaceParameter, \
PostTileJobsItem, BuildState, PostTilesetUpdateJobs, SMTilesTileSourceInfo, TilesetExportJobRunState, TileType, \
TileSourceInfo
from iclientpy.rest.api.management import Management
from iclientpy.rest.api.model import PostFileUploadTasksParam, FileUploadState
from iclientpy.rest.apifactory import APIFactory
from .cacheutils import provider_setting_to_tile_source_info
from iclientpy.dtojson import to_json_str
import zipfile
from io import BufferedIOBase
[文档]def output(tip: str):
print(tip)
field_and_desc = {
'address': '地址',
'username': '用户名',
'password': '密码',
'token': 'token',
'component_name': '待更新缓存服务名称',
'w_loc': '工作空间路径',
'map_name': '切图地图名称',
'original_point': '切图原点',
'cache_bounds': '缓存范围',
'scale': '缓存比例尺分母',
'w_servicetype': '工作空间服务类型',
'tile_size': '切片大小',
'tile_type': '切片类型',
'format': '切片输出格式',
'epsg_code': '投影',
'remote_workspace': '远程工作空间',
'source_component_name': '缓存更新数据来源服务',
'update': '更新服务缓存'
}
[文档]def confirm(**d):
output('执行参数如下:')
for field in field_and_desc.keys():
if field in d:
val = ''
if d[field] is None:
val = ''
elif isinstance(d[field], tuple):
val = ','.join(map(str, d[field]))
elif isinstance(d[field], bool):
val = str(d[field])
elif not isinstance(d[field], str):
val = to_json_str(d[field])
else:
val = d[field]
output(field_and_desc[field] + ": " + val)
print('是否继续?(Y/N)', end=' ')
return input('')
[文档]def recache_tileset(address: str, username: str, password: str, component_name: str, map_name: str,
storageid: str = None, tileset_name: str = None, token: str = None):
api = APIFactory(address, username, password, token)
mng = api.management()
storage_info = mng.get_datastore(storageid)
tilesetinfos = [info for info in storage_info.tilesetInfos if info.metaData.mapName == map_name]
if len(tilesetinfos) != 1 and tileset_name is None:
raise Exception("存在多个地图名称相同的缓存")
tilesetinfo = None
if len(tilesetinfos) == 1:
tilesetinfo = tilesetinfos[0]
else:
for info in tilesetinfos:
if info.name == tileset_name:
tilesetinfo = info
storage_config = storage_info.tileSourceInfo
post_tile_jobs_param = PostTileJobsItem()
post_tile_jobs_param.dataConnectionString = component_name
post_tile_jobs_param.mapName = map_name
post_tile_jobs_param.scaleDenominators = tilesetinfo.metaData.scaleDenominators
if tilesetinfo.metaData.tileHeight == 512:
post_tile_jobs_param.tileSize = TileSize.SIZE_512
elif tilesetinfo.metaData.tileHeight == 1024:
post_tile_jobs_param.tileSize = TileSize.SIZE_1024
else:
post_tile_jobs_param.tileSize = TileSize.SIZE_256
post_tile_jobs_param.tileType = tilesetinfo.metaData.tileType
post_tile_jobs_param.format = tilesetinfo.metaData.tileFormat
post_tile_jobs_param.epsgCode = tilesetinfo.metaData.prjCoordSys.epsgCode
post_tile_jobs_param.storeConfig = storage_config
post_tile_jobs_param.originalPoint = tilesetinfo.metaData.originalPoint
post_tile_jobs_param.cacheBounds = tilesetinfo.metaData.bounds
post_tile_jobs_param.tileVersionDescription = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
post_tile_jobs_param.createNewTileVersion = True
ptjr = mng.post_tilejobs(post_tile_jobs_param)
jobstate = mng.get_tilejob(ptjr.newResourceID).state
bar = _process_bar('切图', _PercentageCounter(), jobstate.total)
while (jobstate.runState is BuildState.BUILDING):
time.sleep(5)
jobstate = mng.get_tilejob(ptjr.newResourceID).state
bar.update(jobstate.completed)
gjr = mng.get_tilejob(ptjr.newResourceID)
if (gjr.state.runState is not BuildState.COMPLETED):
raise Exception('切图失败')
[文档]def update_smtilestileset(address: str, username: str, password: str, component_name: str, w_loc: str, map_name: str,
original_point: tuple, cache_bounds: tuple, scale: List[float] = None,
w_servicetypes: List[ServiceType] = [ServiceType.RESTMAP],
tile_size: TileSize = TileSize.SIZE_256, tile_type: TileType = TileType.Image,
format: OutputFormat = OutputFormat.PNG, epsgcode: int = -1, storageid: str = None,
storageconfig: TileSourceInfo = None, remote_workspace: bool = False,
quiet: bool = False, source_component_name: str = '', update: bool = False,
tile_version: str = None, token: str = None):
if len(original_point) is not 2:
raise Exception("切图原点坐标长度错误")
tem_original_point = Point2D()
tem_original_point.x = original_point[0]
tem_original_point.y = original_point[1]
if len(cache_bounds) is not 4:
raise Exception("切图范围长度错误")
tem_cache_Bounds = Rectangle2D()
tem_cache_Bounds.leftBottom = Point2D()
tem_cache_Bounds.leftBottom.x = cache_bounds[0]
tem_cache_Bounds.leftBottom.y = cache_bounds[1]
tem_cache_Bounds.rightTop = Point2D()
tem_cache_Bounds.rightTop.x = cache_bounds[2]
tem_cache_Bounds.rightTop.y = cache_bounds[3]
api = APIFactory(address, username, password, token)
mng = api.management()
if scale is None:
map_service = api.map_service(component_name + '/rest')
mr = map_service.get_map(map_name)
scale = [1 / x for x in mr.visibleScales]
if scale is None or len(scale) is 0:
raise Exception('无法获取目标地图比例尺且未指定比例尺')
if storageid is not None:
storage_info = mng.get_datastore(storageid)
for info in storage_info.tilesetInfos:
if info.metaData.mapName == map_name:
storageconfig = storage_info.tileSourceInfo
if storageconfig is None:
storageconfig = SMTilesTileSourceInfo()
storageconfig.type = 'SMTiles'
storageconfig.outputPath = '../webapps/iserver/output/sqlite_' + uuid.uuid1().__str__()
if not quiet:
confirmResult = confirm(address=address, username=username, password=password, component_name=component_name,
w_loc=w_loc, map_name=map_name, original_point=original_point,
cache_bounds=cache_bounds, scale=scale, w_servicetype=w_servicetypes,
tile_size=tile_size, tile_type=tile_type, format=format, epsg_code=epsgcode,
storageid=storageid, remote_workspace=remote_workspace,
source_component_name=source_component_name, update=update, token=token)
if confirmResult.lower() == 'n':
return
wkn = source_component_name
if not update:
param = PostFileUploadTasksParam()
if remote_workspace:
remote_workspace_file_full_path = w_loc
else:
pfutsr = mng.post_fileuploadtasks(param)
remote_workspace_file_full_path = _upload_workspace_file(mng, w_loc, pfutsr.newResourceID)
gfutr = mng.get_fileuploadtask(pfutsr.newResourceID)
if gfutr.state is not FileUploadState.COMPLETED:
raise Exception('文件上传失败')
post_param = PostWorkspaceParameter()
post_param.workspaceConnectionInfo = remote_workspace_file_full_path
post_param.servicesTypes = w_servicetypes
pwr = mng.post_workspaces(post_param)
wkn = re.findall('services/[^/]*', pwr[0].serviceAddress)[0].lstrip('services/')
post_tile_jobs_param = PostTileJobsItem()
post_tile_jobs_param.dataConnectionString = wkn
post_tile_jobs_param.mapName = map_name
post_tile_jobs_param.scaleDenominators = scale
post_tile_jobs_param.tileSize = tile_size
post_tile_jobs_param.tileType = tile_type
post_tile_jobs_param.format = format
post_tile_jobs_param.epsgCode = epsgcode
post_tile_jobs_param.storeConfig = storageconfig
post_tile_jobs_param.originalPoint = tem_original_point
post_tile_jobs_param.cacheBounds = tem_cache_Bounds
post_tile_jobs_param.tileVersionDescription = tile_version
ptjr = mng.post_tilejobs(post_tile_jobs_param)
jobstate = mng.get_tilejob(ptjr.newResourceID).state
bar = _process_bar('切图', _PercentageCounter(), jobstate.total)
while (jobstate.runState is BuildState.BUILDING):
time.sleep(5)
jobstate = mng.get_tilejob(ptjr.newResourceID).state
bar.update(jobstate.completed)
gjr = mng.get_tilejob(ptjr.newResourceID)
if (gjr.state.runState is not BuildState.COMPLETED):
raise Exception('切图失败')
if isinstance(storageconfig, SMTilesTileSourceInfo):
post_tile_update_param = PostTilesetUpdateJobs()
post_tile_update_param.scaleDenominators = scale
post_tile_update_param.bounds = tem_cache_Bounds
post_tile_update_param.targetTilesetIdentifier = None
post_tile_update_param.targetTileSourceInfo = _get_tile_source_info_from_service(mng, component_name)
post_tile_update_param.sourceTilesetIdentifier = gjr.targetTilesetInfo.filePath
post_tile_update_param.sourceTileSourceInfo = SMTilesTileSourceInfo()
post_tile_update_param.sourceTileSourceInfo.type = 'SMTiles'
post_tile_update_param.sourceTileSourceInfo.outputPath = post_tile_jobs_param.storeConfig.outputPath
ptur = mng.post_tilesetupdatejobs(post_tile_update_param)
gtur = mng.get_tilesetupdatejob(ptur.newResourceID)
bar = _process_bar('更新', _PercentageCounter(), gtur.state.total)
while (not hasattr(gtur.state, 'runState') or gtur.state.runState is TilesetExportJobRunState.RUNNING):
time.sleep(5)
gtur = mng.get_tilesetupdatejob(ptur.newResourceID)
bar.update(gtur.state.actualCompleted)
if (gtur.state.runState is not TilesetExportJobRunState.COMPLETED):
raise Exception('更新切片失败')
if not update:
mng.delete_mapcomponent(name=wkn)
def _get_tile_source_info_from_service(mng: Management, name: str) -> TileSourceInfo:
service_info = mng.get_service(name)
return provider_setting_to_tile_source_info(service_info.providers[0].spSetting.config)
def _custom_len(value):
total = 0
for c in value:
if c >= u'\u4e00' and c <= u'\u9fa5':
# 中文字符宽度为2
total += 2
else:
total += 1
return total
def _process_bar(name: str, counter: progressbar.Counter, max_value: int) -> progressbar.ProgressBar:
return progressbar.ProgressBar(
widgets=[
name + '进度: ',
progressbar.Bar(),
' ',
counter,
],
len_func=_custom_len,
max_value=max_value
)
class _PercentageCounter(progressbar.Counter):
def __call__(self, progress, data, format=None):
# processbar似乎计算宽度有问题导致换行,给多加几个空格让希望显示的内容不换行。
return '{0:5}% '.format(round(data['value'] / data['max_value'] * 100, 2))
class _DisableSeekAndTellIOWrapper(BufferedIOBase):
"""
zipfile压缩文件的时候会通过seek和tell来定位写入zip文件头信息。
但是管道流不能正常的seek和tell,故通过这个wrapper来禁用seek和tell,从而强迫zipfile使用流式压缩
"""
def __init__(self, towrap):
self._wrapped = towrap
def seek(self, *args, **kwargs):
raise AttributeError()
def tell(self, *args, **kwargs):
raise AttributeError()
def seekable(self, *args, **kwargs):
return False
def __getattribute__(self, item):
if item in ('_wrapped', 'seek', 'seekable', 'tell'):
return object.__getattribute__(self, item)
return self._wrapped.__getattribute__(item)
def __enter__(self):
self._wrapped.__enter__()
return self
def __exit__(self, *args, **kwargs):
self._wrapped.__exit__(*args, **kwargs)
def _zip_files_in_workspace_directory(w_loc: str) -> BufferedIOBase:
rfd, wfd = os.pipe()
def zip_in_thread(wfd):
dirname = os.path.dirname(w_loc)
with os.fdopen(wfd, 'wb') as outputfile:
with zipfile.ZipFile(_DisableSeekAndTellIOWrapper(outputfile), 'w') as zfile:
for root, dirs, files in os.walk(dirname):
for name in files:
fullpath = os.path.join(root, name)
arcname = os.path.relpath(fullpath, dirname)
zfile.write(fullpath, arcname=arcname)
import threading
threading.Thread(target=zip_in_thread, args=(wfd,), name='zip ' + w_loc).start()
return os.fdopen(rfd, 'rb')
def _upload_workspace_file(mng: Management, w_loc: str, upload_task_id: str) -> str:
r_post = None # type PostFileUploadTaskResult
if w_loc.lower().endswith('.zip'):
with zipfile.ZipFile(w_loc) as zipf:
zipfns = zipf.namelist()
workspace_file_name = [item for item in zipfns if item.endswith('.sxwu')][0]
with open(w_loc, 'rb') as wf:
r_post = mng.post_fileuploadtask(upload_task_id, wf, './' + os.path.basename(w_loc), overwrite=True,
unzip=True)
else:
workspace_file_name = os.path.basename(w_loc)
with _zip_files_in_workspace_directory(w_loc) as wf:
r_post = mng.post_fileuploadtask(upload_task_id, wf, './' + workspace_file_name.split('.')[0] + '.zip',
overwrite=True, unzip=True)
return r_post.filePath + './' + workspace_file_name