提交 74ac51e4705b7c7ca4c183ee6dda089baf29ffea

作者 nheweijun
0 个父辈

init

正在显示 53 个修改的文件 包含 4705 行增加0 行删除

要显示太多修改。

为保证性能只显示 53 of 112 个文件。

  1 +*.pyc
  2 +app.db
  3 +./tmp
  4 +./build/*
  5 +.idea
  6 +env
  7 +venv
  8 +.venv/
  9 +.venv
  10 +*.sublime*
  11 +tmp
  12 +tmp/
  13 +
  14 +venv/
  15 +whls/
  16 +tmp/
  17 +depoly/
  18 +logs/
  19 +*.pyc
  20 +__pycache__/
  21 +
  22 +instance/
  23 +
  24 +.pytest_cache/
  25 +.coverage
  26 +htmlcov/
  27 +kservice/tmp
  28 +dist/
  29 +build/
  30 +*.egg-info/
  31 +.git/
\ No newline at end of file
... ...
  1 +
  2 +
... ...
  1 +import decimal
  2 +
  3 +from flask import Flask as _Flask
  4 +from flask.json import JSONEncoder as _JSONEncoder
  5 +from flask_cors import CORS
  6 +import time
  7 +import configure
  8 +from app.util import BlueprintApi
  9 +from app.util import find_class
  10 +from app.models import db,Table,InsertingLayerName,Database,DES,Task
  11 +
  12 +from flasgger import Swagger
  13 +# from rtree import index
  14 +import logging
  15 +from sqlalchemy.orm import Session
  16 +import multiprocessing
  17 +from app.util.component.EntryData import EntryData
  18 +from app.util.component.EntryDataVacuate import EntryDataVacuate
  19 +import json
  20 +import threading
  21 +import traceback
  22 +from sqlalchemy import distinct
  23 +import uuid
  24 +from osgeo.ogr import DataSource
  25 +import datetime
  26 +from sqlalchemy import or_
  27 +from app.util.component.StructuredPrint import StructurePrint
  28 +from app.util.component.PGUtil import PGUtil
  29 +import os
  30 +
  31 +"""
  32 +因为decimal不能序列化,增加Flask对decimal类的解析
  33 +"""
  34 +class JSONEncoder(_JSONEncoder):
  35 + def default(self, o):
  36 + if isinstance(o, decimal.Decimal):
  37 + return float(o)
  38 + super(JSONEncoder, self).default(o)
  39 +
  40 +class Flask(_Flask):
  41 + json_encoder = JSONEncoder
  42 +
  43 +
  44 +# idx =None
  45 +# url_json_list=None
  46 +# sqlite3_connect= None
  47 +def create_app():
  48 +
  49 + # app基本设置
  50 + app = Flask(__name__)
  51 + app.config['SQLALCHEMY_DATABASE_URI'] = configure.SQLALCHEMY_DATABASE_URI
  52 + app.config['echo'] = True
  53 + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
  54 + app.config['JSON_AS_ASCII'] = False
  55 + # app.config['SQLALCHEMY_ECHO'] = True
  56 +
  57 + # 跨域设置
  58 + CORS(app)
  59 +
  60 + #swagger设置
  61 + swagger_config = Swagger.DEFAULT_CONFIG
  62 + swagger_config.update(configure.swagger_configure)
  63 + Swagger(app, config=swagger_config)
  64 +
  65 + # 创建数据库
  66 + db.init_app(app)
  67 + db.create_all(app=app)
  68 +
  69 +
  70 + # 日志
  71 + logging.basicConfig(level=logging.INFO)
  72 + log_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"logs","log.txt")
  73 + handler = logging.FileHandler(log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字
  74 + logging_format = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s')
  75 + handler.setFormatter(logging_format)
  76 + app.logger.addHandler(handler)
  77 +
  78 +
  79 + # 注册blueprint,查找BlueprintApi的子类
  80 + for scan in configure.scan_module:
  81 + for api in find_class(scan, BlueprintApi):
  82 + app.register_blueprint(api.bp)
  83 +
  84 +
  85 + # 入库监测线程
  86 + @app.before_first_request
  87 + def data_entry_process():
  88 + StructurePrint.print("start listen")
  89 + process = threading.Thread(target=data_entry_center)
  90 + process.start()
  91 +
  92 + return app
  93 +
  94 +
  95 +
  96 +
  97 +def data_entry_center():
  98 + running_dict = {}
  99 + sys_session: Session = PGUtil.get_db_session(configure.SQLALCHEMY_DATABASE_URI)
  100 +
  101 + while True:
  102 +
  103 +
  104 + try:
  105 + time.sleep(3)
  106 +
  107 + # 已经结束的进程 从监测中删除
  108 + remove_process = []
  109 +
  110 + # structured_print(running_dict.__len__().__str__())
  111 +
  112 + for process,layer_names in running_dict.items():
  113 + if not process.is_alive():
  114 + for l in layer_names:
  115 + inserted = sys_session.query(InsertingLayerName).filter_by(name=l).one_or_none()
  116 + if inserted:
  117 + sys_session.delete(inserted)
  118 + sys_session.commit()
  119 + remove_process.append(process)
  120 + for process in remove_process:
  121 + running_dict.pop(process)
  122 +
  123 + # StructurePrint.print("listening...")
  124 +
  125 + # 入库进程少于阈值,开启入库进程
  126 +
  127 + inter_size = sys_session.query(distinct(InsertingLayerName.task_guid)).count()
  128 +
  129 + if inter_size < configure.entry_data_thread:
  130 + # 锁表啊
  131 + ready_task:Task = sys_session.query(Task).filter_by(state=0).order_by(Task.create_time).with_lockmode("update").limit(1).one_or_none()
  132 + if ready_task:
  133 +
  134 + try:
  135 + parameter = json.loads(ready_task.parameter)
  136 + StructurePrint.print("检测到入库任务")
  137 + ready_task.state=2
  138 + ready_task.process="入库中"
  139 + sys_session.commit()
  140 +
  141 + metas: list = json.loads(parameter.get("meta").__str__())
  142 + parameter["meta"] = metas
  143 +
  144 +
  145 + database = sys_session.query(Database).filter_by(guid=ready_task.database_guid).one_or_none()
  146 + pg_ds: DataSource = PGUtil.open_pg_data_source(1,DES.decode(database.sqlalchemy_uri))
  147 +
  148 + this_task_layer = []
  149 + for meta in metas:
  150 + overwrite = parameter.get("overwrite", "no")
  151 +
  152 +
  153 +
  154 + for layer_name_origin, layer_name in meta.get("layer").items():
  155 + origin_name = layer_name
  156 + no = 1
  157 +
  158 + while (overwrite.__eq__("no") and pg_ds.GetLayerByName(layer_name)) or sys_session.query(InsertingLayerName).filter_by(name=layer_name).one_or_none() :
  159 + layer_name = origin_name + "_{}".format(no)
  160 + no += 1
  161 +
  162 + # 添加到正在入库的列表中
  163 + iln = InsertingLayerName(guid=uuid.uuid1().__str__(),
  164 + task_guid=ready_task.guid,
  165 + name=layer_name)
  166 +
  167 + sys_session.add(iln)
  168 + sys_session.commit()
  169 + this_task_layer.append(layer_name)
  170 + # 修改表名
  171 + meta["layer"][layer_name_origin] = layer_name
  172 +
  173 + pg_ds.Destroy()
  174 + entry_data_process = multiprocessing.Process(target=EntryDataVacuate().entry,args=(parameter,))
  175 + entry_data_process.start()
  176 + running_dict[entry_data_process] = this_task_layer
  177 + except Exception as e:
  178 + sys_session.query(Task).filter_by(guid=ready_task.guid).update(
  179 + {"state": -1, "process": "入库失败"})
  180 + sys_session.commit()
  181 + StructurePrint.print(e.__str__(), "error")
  182 + else:
  183 + # 解表啊
  184 + sys_session.commit()
  185 + except Exception as e:
  186 + sys_session.commit()
  187 + StructurePrint.print(e.__str__(),"error")
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +import base64
  3 +import os
  4 +from flask_sqlalchemy import SQLAlchemy,Model
  5 +import importlib
  6 +from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime, Time,Float
  7 +from sqlalchemy.orm import relationship
  8 +import base64
  9 +from pyDes import *
  10 +import datetime
  11 +
  12 +from app.util.component.PGUtil import PGUtil
  13 +import glob
  14 +
  15 +class DES():
  16 + '''
  17 + DES密码加解密
  18 + '''
  19 + Des: des = des("Chinadci", ECB, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
  20 +
  21 + @classmethod
  22 + def encode(cls, data):
  23 + return str(base64.b64encode(cls.Des.encrypt(data)), encoding="utf8")
  24 +
  25 +
  26 + @classmethod
  27 + def decode(cls, data):
  28 + if data:
  29 + return str(cls.Des.decrypt(base64.b64decode(data.encode("utf-8"))), encoding="utf8")
  30 +
  31 + else:
  32 + return data
  33 +
  34 +db = SQLAlchemy()
  35 +
  36 +# 动态加载Model
  37 +module_dir = "modules"
  38 +model_pkg = "models"
  39 +current_dir = os.path.abspath(os.path.dirname(__file__))
  40 +for pkg in glob.glob('%s/%s/*/%s' % (current_dir,module_dir,model_pkg)) :
  41 + module = os.path.basename(os.path.dirname(pkg))
  42 + base_name = os.path.basename(pkg).split('.')[0]
  43 + pkg_name = ".".join(["app.modules",module,base_name])
  44 + try:
  45 + __import__(pkg_name)
  46 + except:
  47 + pass
  48 +
  49 +
  50 +
  51 +class Table(db.Model):
  52 + '''
  53 + 数据表元数据
  54 + '''
  55 + __tablename__ = 'dmdms_table'
  56 + guid = Column(String(256), primary_key=True)
  57 + # 数据源外键
  58 + database_guid = Column(String(256), ForeignKey('dmdms_database.guid'))
  59 +
  60 + # 点线面123
  61 + table_type = Column(Integer)
  62 +
  63 + name = Column(String(256))
  64 + alias = Column(Text)
  65 + create_time = Column(DateTime)
  66 + update_time = Column(DateTime)
  67 +
  68 + feature_count = Column(Integer)
  69 +
  70 + description = Column(Text)
  71 + extent = Column(Text)
  72 +
  73 + #用户
  74 + creator = Column(Text)
  75 +
  76 + # 目录外键
  77 + catalog_guid = Column(String(256), ForeignKey('dmdms_catalog.guid'))
  78 +
  79 + relate_columns = relationship('Columns', backref='relate_table', lazy='dynamic')
  80 + relate_table_vacuates = relationship('TableVacuate', backref='relate_table', lazy='dynamic')
  81 +
  82 +
  83 +class TableVacuate(db.Model):
  84 + '''
  85 + sub数据表元数据
  86 + '''
  87 + __tablename__ = 'dmdms_table_vacuate'
  88 + guid = Column(String(256), primary_key=True)
  89 + name = Column(String(256))
  90 + level = Column(Integer)
  91 + pixel_distance = Column(Float)
  92 + table_guid = Column(String(256), ForeignKey('dmdms_table.guid'))
  93 +
  94 +
  95 +
  96 +class Columns(db.Model):
  97 + '''
  98 + 数据表的列
  99 + '''
  100 + __tablename__ = 'dmdms_column'
  101 + guid = Column(String(256), primary_key=True)
  102 + # 表外键
  103 + table_guid= Column(String(256), ForeignKey('dmdms_table.guid'))
  104 + name=Column(String(256))
  105 + alias = Column(String(256))
  106 + create_time = Column(DateTime)
  107 + update_time = Column(DateTime)
  108 +
  109 +class Task(db.Model):
  110 + '''
  111 + 任务表
  112 + '''
  113 + __tablename__ = 'dmdms_task'
  114 + guid = Column(String(256), primary_key=True)
  115 + name = Column(Text)
  116 + process = Column(Text)
  117 + create_time = Column(DateTime)
  118 + update_time = Column(DateTime)
  119 + state = Column(Integer)
  120 + # 数据源外键
  121 + database_guid = Column(String(256), ForeignKey('dmdms_database.guid'))
  122 + # 目录外键
  123 + catalog_guid = Column(String(256), ForeignKey('dmdms_catalog.guid'))
  124 +
  125 + creator = Column(Text)
  126 + file_name = Column(Text)
  127 +
  128 + relate_processes = relationship('Process', backref='relate_task', lazy='dynamic')
  129 +
  130 + # 入库参数
  131 + parameter= Column(Text)
  132 +
  133 +
  134 +
  135 +class Process(db.Model):
  136 + '''
  137 + 过程信息表
  138 + '''
  139 + __tablename__ = 'dmdms_process'
  140 + guid = Column(String(256), primary_key=True)
  141 + # 任务外键
  142 + task_guid = Column(String(256), ForeignKey('dmdms_task.guid'))
  143 + time = Column(DateTime)
  144 + message = Column(Text)
  145 +
  146 +
  147 +class Catalog(db.Model):
  148 + '''
  149 + 目录表
  150 + '''
  151 + __tablename__ = 'dmdms_catalog'
  152 + guid = Column(String(256), primary_key=True)
  153 + database_guid = Column(String(256), ForeignKey('dmdms_database.guid'))
  154 + pguid = Column(String(256))
  155 + path = Column(Text)
  156 + name = Column(String(256))
  157 + sort = Column(Integer)
  158 + description = Column(Text)
  159 + relate_tables = relationship('Table', backref='relate_catalog', lazy='dynamic')
  160 + relate_tasks = relationship('Task', backref='relate_catalog', lazy='dynamic')
  161 +
  162 +class Database(db.Model):
  163 + '''
  164 + 数据源表
  165 + '''
  166 + __tablename__ = 'dmdms_database'
  167 + guid = Column(String(256), primary_key=True)
  168 + name = Column(String(256))
  169 + alias = Column(String(256))
  170 + sqlalchemy_uri = Column(String(256))
  171 + description = Column(Text)
  172 + # 唯一性约束,不能注册相同连接的库
  173 + connectstr= Column(Text,unique=True)
  174 +
  175 + creator = Column(String(256))
  176 +
  177 + create_time = Column(DateTime)
  178 + update_time = Column(DateTime)
  179 +
  180 + relate_catalogs = relationship('Catalog', backref='relate_database', lazy='dynamic')
  181 + relate_tables = relationship('Table', backref='relate_database', lazy='dynamic')
  182 + relate_tasks = relationship('Task', backref='relate_database', lazy='dynamic')
  183 +
  184 +
  185 +
  186 +class InsertingLayerName(db.Model):
  187 + '''
  188 + 正在入库的数据
  189 + '''
  190 + __tablename__ = 'dmdms_iln'
  191 + guid = Column(String(256), primary_key=True)
  192 + task_guid = Column(String(256))
  193 + name = Column(String(256))
  194 +
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/8/27
  4 +# email: nheweijun@sina.com
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/1
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from flasgger import swag_from
  8 +from flask import Blueprint
  9 +from app.util import BlueprintApi
  10 +from . import catalog_create
  11 +from . import catalog_next
  12 +from . import catalog_tree
  13 +from . import catalog_delete
  14 +from . import catalog_edit
  15 +from . import catalog_real_tree
  16 +
  17 +
  18 +class DataManager(BlueprintApi):
  19 +
  20 + bp = Blueprint("Category", __name__, url_prefix="/API/Category")
  21 +
  22 +
  23 + @staticmethod
  24 + @bp.route('/Create', methods=['POST'])
  25 + @swag_from(catalog_create.Api.api_doc)
  26 + def catalog_create():
  27 + """
  28 + 创建目录
  29 + """
  30 + return catalog_create.Api().result
  31 +
  32 + @staticmethod
  33 + @bp.route('/Next', methods=['POST'])
  34 + @swag_from(catalog_next.Api.api_doc)
  35 + def catalog_next():
  36 + """
  37 + 下一级目录
  38 + """
  39 + return catalog_next.Api().result
  40 +
  41 + @staticmethod
  42 + @bp.route('/Tree', methods=['POST'])
  43 + @swag_from(catalog_tree.Api.api_doc)
  44 + def catalog_tree():
  45 + """
  46 + 目录树
  47 + """
  48 + return catalog_tree.Api().result
  49 +
  50 +
  51 + @staticmethod
  52 + @bp.route('/RealTree', methods=['POST'])
  53 + @swag_from(catalog_real_tree.Api.api_doc)
  54 + def catalog_real_tree():
  55 + """
  56 + 目录树
  57 + """
  58 + return catalog_real_tree.Api().result
  59 +
  60 +
  61 + @staticmethod
  62 + @bp.route('/Edit', methods=['POST'])
  63 + @swag_from(catalog_edit.Api.api_doc)
  64 + def catalog_edit():
  65 + """
  66 + 修改目录
  67 + """
  68 + return catalog_edit.Api().result
  69 +
  70 + @staticmethod
  71 + @bp.route('/Delete', methods=['POST'])
  72 + @swag_from(catalog_delete.Api.api_doc)
  73 + def catalog_delete():
  74 + """
  75 + 删除目录
  76 + """
  77 + return catalog_delete.Api().result
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +import uuid
  7 +from app.models import *
  8 +from app.util.component.ApiTemplate import ApiTemplate
  9 +class Api(ApiTemplate):
  10 + api_name = "创建目录"
  11 + def para_check(self):
  12 + if not self.para.get("database_guid"):
  13 + raise Exception("缺乏database_guid参数")
  14 + if not self.para.get("pguid"):
  15 + raise Exception("缺乏pguid参数")
  16 + if not self.para.get("name"):
  17 + raise Exception("缺乏name参数")
  18 +
  19 + def process(self):
  20 +
  21 + # 返回结果
  22 + res = {}
  23 + res["result"] = False
  24 + try:
  25 + # 业务逻辑
  26 +
  27 + if not Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none():
  28 + res["msg"]="数据库不存在!"
  29 + return res
  30 +
  31 +
  32 + # 可以创建已有数据目录的子目录
  33 + # if Table.query.filter_by(catalog_guid=self.para.get("pguid")).all():
  34 + # raise Exception("父目录挂载了数据,不能创建子目录")
  35 +
  36 + if Catalog.query.filter_by(name=self.para.get("name"),pguid=self.para.get("pguid"),database_guid=self.para.get("database_guid")).one_or_none():
  37 + res["msg"]="目录已经存在!"
  38 + return res
  39 +
  40 +
  41 + # if Catalog.query.filter_by(pguid="0",database_guid=self.para.get("database_guid")).one_or_none() and self.para.get("pguid").__eq__("0"):
  42 + # raise Exception("只能有一个根目录!")
  43 +
  44 + guid = uuid.uuid1().__str__()
  45 + path = guid
  46 +
  47 + # 获得目录的全路径
  48 + pguid = self.para.get("pguid")
  49 + count = 0
  50 + while pguid !="0" and count<100:
  51 + count+=1
  52 + path = pguid+":"+path
  53 + p_catalog = Catalog.query.filter_by(guid=pguid).one_or_none()
  54 + pguid = p_catalog.pguid
  55 + if count==100:
  56 + raise Exception("目录结构出现问题!")
  57 + path = "0" + ":" + path
  58 +
  59 + sort = Catalog.query.filter_by(pguid=self.para.get("pguid")).count()
  60 +
  61 + catalog = Catalog(guid=guid,pguid=self.para.get("pguid"),name=self.para.get("name"),
  62 + sort=sort,description=self.para.get("description"),
  63 + database_guid=self.para.get("database_guid"),path=path)
  64 + db.session.add(catalog)
  65 + db.session.commit()
  66 +
  67 +
  68 + res["msg"] = "目录创建成功!"
  69 + res["data"] = guid
  70 + res["result"] = True
  71 + except Exception as e:
  72 + db.session.rollback()
  73 + raise e
  74 + return res
  75 +
  76 + api_doc={
  77 +
  78 + "tags":["目录接口"],
  79 + "parameters":[
  80 + {"name": "name",
  81 + "in": "formData",
  82 + "type": "string",
  83 + "description":"目录名"},
  84 + {"name": "pguid",
  85 + "in": "formData",
  86 + "type": "string","description":"父目录guid,创建根目录时为0"},
  87 + {"name": "database_guid",
  88 + "in": "formData",
  89 + "type": "string","description":"数据库guid"}
  90 +
  91 + ],
  92 + "responses":{
  93 + 200:{
  94 + "schema":{
  95 + "properties":{
  96 + }
  97 + }
  98 + }
  99 + }
  100 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +
  8 +from app.models import *
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +class Api(ApiTemplate):
  11 + api_name = "删除目录"
  12 + def process(self):
  13 +
  14 +
  15 + # 返回结果
  16 + res = {}
  17 + try:
  18 + # 业务逻辑
  19 +
  20 +
  21 + # 啥情况都能删
  22 + # if Table.query.filter_by(catalog_guid=self.para.get("guid")).all():
  23 + # raise Exception("目录挂载了数据,不可删除,可将数据移出目录后删除!")
  24 + # if Catalog.query.filter_by(pguid=self.para.get("guid")).all():
  25 + # raise Exception("目录非子目录,不可删除,请先将子目录删除!")
  26 +
  27 + catalog_guid = self.para.get("guid")
  28 +
  29 + catalog = Catalog.query.filter_by(guid=catalog_guid).one_or_none()
  30 + if not catalog:
  31 + res["msg"]="目录不存在!"
  32 + return res
  33 +
  34 + else:
  35 +
  36 + # 转移目录下的数据
  37 +
  38 + # # 删除根节点
  39 + # if catalog.pguid.__eq__("0"):
  40 + # database_guid = catalog.database_guid
  41 + # Table.query.filter_by(database_guid=database_guid).update({"catalog_guid": None})
  42 + # catalogs = Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all()
  43 + # for cata in catalogs:
  44 + # db.session.delete(cata)
  45 + #
  46 + # # 获取所有子目录:
  47 + # else:
  48 +
  49 + pguid = catalog.pguid
  50 + # 所有目录
  51 + catalogs = Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all()
  52 + for cata in catalogs:
  53 + if pguid.__eq__("0"):
  54 + Table.query.filter_by(catalog_guid=cata.guid).update({"catalog_guid": None})
  55 + db.session.delete(cata)
  56 + else:
  57 + Table.query.filter_by(catalog_guid=cata.guid).update({"catalog_guid": pguid})
  58 + db.session.delete(cata)
  59 +
  60 + db.session.commit()
  61 + res["msg"] = "目录删除成功!"
  62 + res["result"] = True
  63 + except Exception as e:
  64 + db.session.rollback()
  65 + raise e
  66 + return res
  67 +
  68 +
  69 + api_doc = {
  70 + "tags": ["目录接口"],
  71 + "parameters": [
  72 + {"name": "guid",
  73 + "in": "formData",
  74 + "type": "string",
  75 + "description": "目录guid", "required": "true"},
  76 + ],
  77 + "responses": {
  78 + 200: {
  79 + "schema": {
  80 + "properties": {
  81 + }
  82 + }
  83 + }
  84 + }
  85 + }
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import *
  7 +
  8 +from app.util.component.ApiTemplate import ApiTemplate
  9 +class Api(ApiTemplate):
  10 + api_name = "修改目录"
  11 + def process(self):
  12 +
  13 +
  14 + # 返回结果
  15 + res = {}
  16 + try:
  17 + # 业务逻辑
  18 + if not Catalog.query.filter_by(guid=self.para.get("guid")).one_or_none():
  19 + res["msg"]="目录不存在!"
  20 + res["result"]=False
  21 + return res
  22 + else:
  23 + if self.para.get("name"):
  24 + Catalog.query.filter_by(guid=self.para.get("guid")).update({"name":self.para.get("name")})
  25 + if self.para.__contains__("description"):
  26 + Catalog.query.filter_by(guid=self.para.get("guid")).update({"description":self.para.get("description")})
  27 + db.session.commit()
  28 + res["msg"] = "目录修改成功!"
  29 + res["result"] = True
  30 + except Exception as e:
  31 + db.session.rollback()
  32 + raise e
  33 + return res
  34 +
  35 +
  36 + api_doc = {
  37 + "tags": ["目录接口"],
  38 + "parameters": [
  39 + {"name": "guid",
  40 + "in": "formData",
  41 + "type": "string",
  42 + "description": "目录guid", "required": "true"},
  43 + {"name": "name",
  44 + "in": "formData",
  45 + "type": "string",
  46 + "description": "目录名", "required": "true"}
  47 + ],
  48 + "responses": {
  49 + 200: {
  50 + "schema": {
  51 + "properties": {
  52 + }
  53 + }
  54 + }
  55 + }
  56 + }
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from app.models import *
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +from app.util.component.ModelVisitor import ModelVisitor
  11 +class Api(ApiTemplate):
  12 + api_name = "下一级目录"
  13 + def process(self):
  14 +
  15 + # 返回结果
  16 + res = {}
  17 + try:
  18 + # 业务逻辑
  19 +
  20 + res["data"] = []
  21 + catalogs = Catalog.query.filter_by(pguid=self.para.get("catalog_guid"),database_guid=self.para.get("database_guid")).all()
  22 + for cata in catalogs:
  23 + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()]
  24 + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count()
  25 + database_alias = cata.relate_database.alias
  26 + cata_json = ModelVisitor.object_to_json(cata)
  27 + cata_json["table_count"]=table_count
  28 + cata_json["database_alias"] = database_alias
  29 +
  30 + res["data"].append(cata_json)
  31 + res["result"] = True
  32 + except Exception as e:
  33 + raise e
  34 + return res
  35 +
  36 + api_doc={
  37 +
  38 + "tags":["目录接口"],
  39 + "parameters":[
  40 + {"name": "catalog_guid",
  41 + "in": "formData",
  42 + "type": "string",
  43 + "description":"目录guid","required": "true"},
  44 + {"name": "database_guid",
  45 + "in": "formData",
  46 + "type": "string",
  47 + "description": "数据库guid", "required": "true"},
  48 +
  49 + ],
  50 + "responses":{
  51 + 200:{
  52 + "schema":{
  53 + "properties":{
  54 + }
  55 + }
  56 + }
  57 + }
  58 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from app.models import *
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +class Api(ApiTemplate):
  11 + api_name = "目录树"
  12 + def process(self):
  13 +
  14 + # 返回结果
  15 + res = {}
  16 + try:
  17 + # 业务逻辑
  18 + database_guid = self.para.get("database_guid")
  19 + catalogs = Catalog.query.filter_by(database_guid=database_guid).all()
  20 +
  21 + tree_origin = []
  22 + for cata in catalogs:
  23 + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()]
  24 + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count()
  25 +
  26 + # cata_json = object_to_json(cata)
  27 + cata_json ={}
  28 + cata_json["database_guid"]=cata.database_guid
  29 + cata_json["description"] = cata.description
  30 + cata_json["guid"] = cata.guid
  31 + cata_json["name"] = cata.name
  32 + cata_json["path"] = cata.path
  33 + cata_json["pguid"] = cata.pguid
  34 + cata_json["sort"] = cata.sort
  35 + cata_json["table_count"]=table_count
  36 + cata_json["children"] = []
  37 + tree_origin.append(cata_json)
  38 +
  39 + for cata in tree_origin:
  40 + cata_pguid = cata["pguid"]
  41 + if not cata_pguid=="0":
  42 + for c in tree_origin:
  43 + if c["guid"].__eq__(cata_pguid):
  44 + c["children"].append(cata)
  45 +
  46 + res["data"] = [cata for cata in tree_origin if cata["pguid"].__eq__("0")]
  47 + res["result"] = True
  48 + except Exception as e:
  49 + raise e
  50 + return res
  51 +
  52 + api_doc={
  53 +
  54 + "tags":["目录接口"],
  55 + "parameters":[
  56 + {"name": "database_guid",
  57 + "in": "formData",
  58 + "type": "string",
  59 + "description": "数据库guid", "required": "true"},
  60 +
  61 + ],
  62 + "responses":{
  63 + 200:{
  64 + "schema":{
  65 + "properties":{
  66 + }
  67 + }
  68 + }
  69 + }
  70 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from app.models import *
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +class Api(ApiTemplate):
  11 + api_name = "目录"
  12 + def process(self):
  13 +
  14 + # 返回结果
  15 + res = {}
  16 + try:
  17 + # 业务逻辑
  18 + database_guid = self.para.get("database_guid")
  19 + catalogs = Catalog.query.filter_by(database_guid=database_guid).all()
  20 + res["data"]=[]
  21 + for cata in catalogs:
  22 + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()]
  23 + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count()
  24 +
  25 + # cata_json = object_to_json(cata)
  26 + cata_json ={}
  27 + cata_json["database_guid"]=cata.database_guid
  28 + cata_json["description"] = cata.description
  29 + cata_json["guid"] = cata.guid
  30 + cata_json["name"] = cata.name
  31 + cata_json["path"] = cata.path
  32 + cata_json["pguid"] = cata.pguid
  33 + cata_json["sort"] = cata.sort
  34 + cata_json["table_count"]=table_count
  35 + res["data"].append(cata_json)
  36 +
  37 + res["result"] = True
  38 +
  39 + except Exception as e:
  40 + raise e
  41 + return res
  42 +
  43 + api_doc={
  44 +
  45 + "tags":["目录接口"],
  46 + "parameters":[
  47 + {"name": "database_guid",
  48 + "in": "formData",
  49 + "type": "string",
  50 + "description": "数据库guid", "required": "true"},
  51 +
  52 + ],
  53 + "responses":{
  54 + 200:{
  55 + "schema":{
  56 + "properties":{
  57 + }
  58 + }
  59 + }
  60 + }
  61 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +
  8 +from flasgger import swag_from
  9 +from flask import Blueprint
  10 +from app.util import BlueprintApi
  11 +
  12 +from . import database_register
  13 +from . import database_test
  14 +from . import database_list
  15 +from . import database_delete
  16 +from . import database_edit
  17 +from . import database_alias_check
  18 +from . import database_connect_test
  19 +from . import database_info
  20 +
  21 +class DataManager(BlueprintApi):
  22 +
  23 + bp = Blueprint("Database", __name__, url_prefix="/API/Database")
  24 +
  25 +
  26 + @staticmethod
  27 + @bp.route('/Register', methods=['POST'])
  28 + @swag_from(database_register.Api.api_doc)
  29 + def api_database_register():
  30 + """
  31 + 数据源注册
  32 + """
  33 + return database_register.Api().result
  34 +
  35 + @staticmethod
  36 + @bp.route('/List', methods=['POST'])
  37 + @swag_from(database_list.Api.api_doc)
  38 + def api_database_list():
  39 + """
  40 + 数据源列表
  41 + """
  42 + return database_list.Api().result
  43 +
  44 + @staticmethod
  45 + @bp.route('/Delete', methods=['POST'])
  46 + @swag_from(database_delete.Api.api_doc)
  47 + def api_database_delete():
  48 + """
  49 + 数据源注销
  50 + """
  51 + return database_delete.Api().result
  52 +
  53 + @staticmethod
  54 + @bp.route('/Edit', methods=['POST'])
  55 + @swag_from(database_edit.Api.api_doc)
  56 + def database_edit():
  57 + """
  58 + 修改数据源
  59 + """
  60 + return database_edit.Api().result
  61 +
  62 + @staticmethod
  63 + @bp.route('/Test', methods=['POST'])
  64 + @swag_from(database_test.Api.api_doc)
  65 + def api_database_test():
  66 + """
  67 + 数据源测试
  68 + """
  69 + return database_test.Api().result
  70 +
  71 + @staticmethod
  72 + @bp.route('/CheckAlias', methods=['POST'])
  73 + @swag_from(database_alias_check.Api.api_doc)
  74 + def api_database_alias_check():
  75 + """
  76 + 数据源别名测试
  77 + """
  78 + return database_alias_check.Api().result
  79 +
  80 + @staticmethod
  81 + @bp.route('/CheckConnect', methods=['POST'])
  82 + @swag_from(database_connect_test.Api.api_doc)
  83 + def api_database_connect_test():
  84 + """
  85 + 数据源连接测试
  86 + """
  87 + return database_connect_test.Api().result
  88 +
  89 +
  90 + @staticmethod
  91 + @bp.route('/Info', methods=['POST'])
  92 + @swag_from(database_info.Api.api_doc)
  93 + def api_database_info():
  94 + """
  95 + 数据源信息
  96 + """
  97 + return database_info.Api().result
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import Database,db
  7 +
  8 +
  9 +
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +class Api(ApiTemplate):
  12 + api_name = "测试数据库别名"
  13 +
  14 + def process(self):
  15 + res ={}
  16 + try:
  17 + database = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none()
  18 + if database:
  19 + res["msg"]="数据库重名!"
  20 + res["result"]=False
  21 + else:
  22 + res["result"] = True
  23 + except Exception as e:
  24 + raise e
  25 + return res
  26 +
  27 + api_doc={
  28 + "tags":["数据库接口"],
  29 + "parameters":[
  30 + {"name": "alias",
  31 + "in": "formData",
  32 + "type": "string","description":"数据库别名","required": "true"},
  33 +
  34 + ],
  35 + "responses":{
  36 + 200:{
  37 + "schema":{
  38 + "properties":{
  39 + }
  40 + }
  41 + }
  42 + }
  43 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/6/22
  4 +# email: nheweijun@sina.com
  5 +from contextlib import closing
  6 +
  7 +from sqlalchemy import create_engine
  8 +
  9 +from app.models import DES,Database
  10 +
  11 +
  12 +from app.util.component.ApiTemplate import ApiTemplate
  13 +class Api(ApiTemplate):
  14 + api_name = "测试数据库连接"
  15 + def process(self):
  16 + res = {}
  17 + try:
  18 + guid = self.para.get("guid")
  19 + database = Database.query.filter_by(guid=guid)
  20 + dbase:Database = database.one_or_none()
  21 + engine = create_engine(DES.decode(dbase.sqlalchemy_uri), connect_args={'connect_timeout': 1})
  22 + with closing(engine.connect()):
  23 + pass
  24 + res["result"]=True
  25 + res["msg"] = "测试连接成功!"
  26 + except:
  27 + raise Exception("测试连接失败!")
  28 + return res
  29 +
  30 + api_doc={
  31 + "tags":["数据库接口"],
  32 + "parameters":[
  33 +
  34 + {"name": "guid",
  35 + "in": "formData",
  36 + "type": "string", "required": "true"},
  37 +
  38 +
  39 + ],
  40 + "responses":{
  41 + 200:{
  42 + "schema":{
  43 + "properties":{
  44 + }
  45 + }
  46 + }
  47 + }
  48 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from app.models import Database,db
  8 +
  9 +
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +class Api(ApiTemplate):
  12 + api_name = "删除数据库"
  13 + def process(self):
  14 + res ={}
  15 + try:
  16 +
  17 + database = db.session.query(Database).filter_by(guid=self.para.get("guid")).one_or_none()
  18 + if database:
  19 + db.session.delete(database)
  20 + db.session.commit()
  21 + res["msg"] = "数据库删除成功!"
  22 + res["result"] = True
  23 + else:
  24 + res["msg"] = "数据库不存在!"
  25 + res["result"] = False
  26 + except Exception as e:
  27 + db.session.rollback()
  28 + raise e
  29 + return res
  30 +
  31 +
  32 +
  33 + api_doc={
  34 + "tags":["数据库接口"],
  35 + "parameters":[
  36 + {"name": "guid",
  37 + "in": "formData",
  38 + "type": "string","description":"数据库guid","required": "true"},
  39 +
  40 + ],
  41 + "responses":{
  42 + 200:{
  43 + "schema":{
  44 + "properties":{
  45 + }
  46 + }
  47 + }
  48 + }
  49 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import Database,db
  7 +from contextlib import closing
  8 +
  9 +from sqlalchemy import create_engine
  10 +
  11 +from app.models import DES
  12 +import datetime
  13 +from . import database_alias_check
  14 +
  15 +from app.util.component.ApiTemplate import ApiTemplate
  16 +
  17 +from app.util.component.PGUtil import PGUtil
  18 +class Api(ApiTemplate):
  19 + api_name = "修改数据库"
  20 + def process(self):
  21 +
  22 + res = {}
  23 + res["result"] = False
  24 + try:
  25 + guid = self.para.get("guid")
  26 + alias = self.para.get("alias")
  27 +
  28 + passwd = self.para.get("passwd")
  29 + database = Database.query.filter_by(guid=guid)
  30 + dbase:Database = database.one_or_none()
  31 +
  32 + update_dict={}
  33 +
  34 + if not dbase:
  35 + res["msg"] = "数据库不存在!"
  36 + return res
  37 + if not alias and not passwd:
  38 + res["msg"] = "请填写参数!"
  39 + return res
  40 +
  41 +
  42 + if alias:
  43 + # 检测一波别名
  44 + check_alias = database_alias_check.Api().result
  45 + if not check_alias["result"]:
  46 + return check_alias
  47 + update_dict["alias"]=alias
  48 +
  49 + if passwd:
  50 + try:
  51 + user, password, host, port, database = PGUtil.get_info_from_sqlachemy_uri(DES.decode(dbase.sqlalchemy_uri))
  52 +
  53 + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user, passwd,host,
  54 + port, database)
  55 +
  56 + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host, port, database, user,
  57 + passwd)
  58 + engine = create_engine(sqlalchemy_uri, connect_args={'connect_timeout': 2})
  59 + with closing(engine.connect()):
  60 + pass
  61 +
  62 + except :
  63 + res["msg"] = "密码错误!"
  64 + return res
  65 +
  66 + update_dict["sqlalchemy_uri"]= DES.encode(sqlalchemy_uri)
  67 + update_dict["connectstr"] = DES.encode(connectsrt)
  68 +
  69 + if update_dict:
  70 + update_dict["update_time"]=datetime.datetime.now()
  71 + Database.query.filter_by(guid=guid).update(update_dict)
  72 + db.session.commit()
  73 + res["result"] = True
  74 + res["msg"] = "数据修改成功!"
  75 + except Exception as e:
  76 + db.session.rollback()
  77 + raise e
  78 + return res
  79 +
  80 +
  81 +
  82 + api_doc={
  83 + "tags":["数据库接口"],
  84 + "parameters":[
  85 + {"name": "guid",
  86 + "in": "formData",
  87 + "type": "string"},
  88 + {"name": "alias",
  89 + "in": "formData",
  90 + "type": "string","description":"数据库别名"},
  91 + {"name": "passwd",
  92 + "in": "formData",
  93 + "type": "string", "description": "数据库密码"}
  94 + ],
  95 + "responses":{
  96 + 200:{
  97 + "schema":{
  98 + "properties":{
  99 + }
  100 + }
  101 + }
  102 + }
  103 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +from app.models import Database,DES
  6 +from contextlib import closing
  7 +from sqlalchemy import create_engine,or_
  8 +
  9 +from app.models import Database,Catalog,Table
  10 +
  11 +
  12 +from app.util.component.ApiTemplate import ApiTemplate
  13 +
  14 +
  15 +class Api(ApiTemplate):
  16 + api_name = "获取数据库信息"
  17 + def process(self):
  18 + # 返回结果
  19 + res = {}
  20 + try:
  21 + # 业务逻辑
  22 + database_guid = self.para.get("guid")
  23 + table_type = self.para.get("table_type")
  24 + catalogs = Catalog.query.filter_by(database_guid=database_guid).all()
  25 +
  26 + tree_origin = []
  27 + for cata in catalogs:
  28 + cata_json = {}
  29 + cata_json["type"] = "catalog"
  30 + cata_json["guid"] = cata.guid
  31 + cata_json["name"] = cata.name
  32 + cata_json["pguid"] = cata.pguid
  33 + cata_json["children"] = []
  34 + tree_origin.append(cata_json)
  35 +
  36 + #挂接表
  37 + tables =Table.query.filter_by(database_guid=database_guid,catalog_guid=cata.guid).filter(Table.table_type != 0)
  38 + if table_type:
  39 + tables = tables.filter_by(table_type=table_type)
  40 + tables = tables.order_by(Table.name).all()
  41 +
  42 + for table in tables:
  43 + table_json= {}
  44 + table_json["name"]=table.name
  45 + table_json["alias"] = table.alias
  46 + table_json["guid"] = table.guid
  47 + table_json["type"] = "table"
  48 + table_json["table_type"] = table.table_type
  49 + cata_json["children"].append(table_json)
  50 +
  51 + for cata in tree_origin:
  52 + cata_pguid = cata["pguid"]
  53 + if not cata_pguid == "0":
  54 + for c in tree_origin:
  55 + if c["guid"].__eq__(cata_pguid):
  56 + c["children"].append(cata)
  57 + res["data"] = [cata for cata in tree_origin if cata["pguid"].__eq__("0")]
  58 +
  59 + # 挂接表
  60 + tables = Table.query.filter_by(database_guid=database_guid, catalog_guid=None).filter(
  61 + Table.table_type != 0)
  62 + if table_type:
  63 + tables = tables.filter_by(table_type=table_type)
  64 + tables = tables.order_by(Table.name).all()
  65 +
  66 + for table in tables:
  67 + table_json = {}
  68 + table_json["name"] = table.name
  69 + table_json["alias"] = table.alias
  70 + table_json["guid"] = table.guid
  71 + table_json["type"] = "table"
  72 + table_json["table_type"] = table.table_type
  73 + res["data"].append(table_json)
  74 +
  75 + res["result"] = True
  76 + except Exception as e:
  77 + raise e
  78 + return res
  79 +
  80 + api_doc={
  81 + "tags":["数据库接口"],
  82 + "parameters":[
  83 + {"name": "guid",
  84 + "in": "formData",
  85 + "type": "string", "description": "数据库guid"},
  86 + {"name": "table_type",
  87 + "in": "formData",
  88 + "type": "int", "description": "表类型","enum":[1,2,3]}
  89 + ],
  90 + "responses":{
  91 + 200:{
  92 + "schema":{
  93 + "properties":{
  94 + }
  95 + }
  96 + }
  97 + }
  98 + }
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +from app.models import Database,DES
  6 +from contextlib import closing
  7 +from sqlalchemy import create_engine
  8 +
  9 +from app.models import Database
  10 +
  11 +
  12 +from app.util.component.ApiTemplate import ApiTemplate
  13 +from app.util.component.ModelVisitor import ModelVisitor
  14 +class Api(ApiTemplate):
  15 + api_name = "获取数据库列表"
  16 + def process(self):
  17 + res = {}
  18 + res["data"]={}
  19 + try:
  20 + page_index = int(self.para.get("pageIndex", "0"))
  21 + page_size = int(self.para.get("pageSize", "10"))
  22 + alias = self.para.get("alias")
  23 + database_guid = self.para.get("guid")
  24 + database = Database.query.order_by(Database.create_time.desc())
  25 +
  26 + if alias:
  27 + database = database.filter(Database.alias.like("%" + alias + "%"))
  28 + if database_guid:
  29 + database = database.filter_by(guid = database_guid)
  30 +
  31 + res["data"]["count"] = database.count()
  32 +
  33 + database = database.limit(page_size).offset(page_index * page_size).all()
  34 + res["data"]["list"] =[]
  35 + for datab in database:
  36 + # 测试连接
  37 + try:
  38 + engine = create_engine(DES.decode(datab.sqlalchemy_uri), connect_args={'connect_timeout': 1})
  39 + with closing(engine.connect()):
  40 + pass
  41 + available=1
  42 + except:
  43 + available=-1
  44 + table_count = datab.relate_tables.count()
  45 + datab_json = ModelVisitor.database_to_json(datab)
  46 + datab_json["table_count"] = table_count
  47 + datab_json["available"] = available
  48 + res["data"]["list"].append(datab_json)
  49 +
  50 + # 可用非可用排序
  51 + sorted_list = [dat for dat in res["data"]["list"] if dat["available"]==1]
  52 + sorted_list.extend([dat for dat in res["data"]["list"] if dat["available"] == -1])
  53 + res["data"]["list"] = sorted_list
  54 + res["result"]=True
  55 + except Exception as e:
  56 + raise e
  57 + return res
  58 +
  59 +
  60 +
  61 + api_doc={
  62 + "tags":["数据库接口"],
  63 + "parameters":[
  64 + {"name": "page_index",
  65 + "in": "formData",
  66 + "type": "int",
  67 + "default": "0"},
  68 + {"name": "page_size",
  69 + "in": "formData",
  70 + "type": "int",
  71 + "default": "10"},
  72 + {"name": "alias",
  73 + "in": "formData",
  74 + "type": "string","description":"数据库别名"},
  75 + {"name": "guid",
  76 + "in": "formData",
  77 + "type": "string", "description": "数据库guid"}
  78 + ],
  79 + "responses":{
  80 + 200:{
  81 + "schema":{
  82 + "properties":{
  83 + }
  84 + }
  85 + }
  86 + }
  87 + }
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/9
  4 +#email: nheweijun@sina.com
  5 +import datetime
  6 +from app.models import Database,db,DES,Table,Columns,TableVacuate
  7 +# from app.util import get_db_session,get_info_from_sqlachemy_uri
  8 +import uuid
  9 +from . import database_test
  10 +# from app.util import open_pg_data_source
  11 +from osgeo.ogr import DataSource,Layer,FeatureDefn,FieldDefn,Feature
  12 +from sqlalchemy.orm import Session
  13 +from sqlalchemy import create_engine
  14 +from sqlalchemy.orm import sessionmaker
  15 +from app.util.component.ApiTemplate import ApiTemplate
  16 +from app.util.component.PGUtil import PGUtil
  17 +from app.util.component.GeometryAdapter import GeometryAdapter
  18 +class Api(ApiTemplate):
  19 + api_name = "数据库注册"
  20 + def process(self):
  21 + res ={}
  22 + res["result"] = False
  23 + try:
  24 + host = self.para.get("host")
  25 + port = self.para.get("port")
  26 + user = self.para.get("user")
  27 + passwd = self.para.get("passwd")
  28 + database = self.para.get("database")
  29 + encryption = int(self.para.get("encryption","0"))
  30 + if encryption:
  31 + passwd = DES.decode(passwd)
  32 +
  33 + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user,passwd,host,port,database)
  34 + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host,port,database,user,passwd)
  35 +
  36 + #测试连接
  37 + test = database_test.Api().result
  38 +
  39 + if not test["result"]:
  40 + return test
  41 +
  42 + #判断数据库是否存在
  43 + database = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none()
  44 +
  45 + #真实的数据库
  46 + real_database = db.session.query(Database).filter_by(connectstr=DES.encode(connectsrt)).all()
  47 +
  48 + if database:
  49 + res["msg"] = "数据库已存在,请修改别名!"
  50 + return res
  51 +
  52 + elif real_database:
  53 + res["msg"] = "数据库连接已存在,请修改数据库连接!"
  54 + return res
  55 + elif not self.check_space(sqlalchemy_uri):
  56 + res["msg"] = "数据不是空间数据库!"
  57 + return res
  58 +
  59 + else:
  60 + this_time = datetime.datetime.now()
  61 + database_guid = uuid.uuid1().__str__()
  62 +
  63 + db_tuple = PGUtil.get_info_from_sqlachemy_uri(sqlalchemy_uri)
  64 +
  65 + database = Database(guid= database_guid,
  66 + name = db_tuple[4],
  67 + alias=self.para.get("alias"),
  68 + sqlalchemy_uri=DES.encode(sqlalchemy_uri),
  69 + description=self.para.get("description"),
  70 + creator=self.para.get("creator"),
  71 + create_time=this_time,
  72 + update_time=this_time,
  73 + connectstr=DES.encode(connectsrt))
  74 + db.session.add(database)
  75 +
  76 + # 将该库中的数据都注册进来
  77 + self.register_table(database)
  78 + db.session.commit()
  79 + res["msg"] = "注册成功!"
  80 + res["result"]=True
  81 + res["data"] = database_guid
  82 + except Exception as e:
  83 + db.session.rollback()
  84 + raise e
  85 + return res
  86 +
  87 +
  88 + def register_table(self,database):
  89 + this_time = datetime.datetime.now()
  90 + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri))
  91 +
  92 + # 注册空间表
  93 + spatial_table_name,tables = self.register_spatial_table(pg_ds, database, this_time)
  94 +
  95 + #注册抽稀表
  96 + self.regiser_vacuate_talbe(pg_ds,tables)
  97 +
  98 + #注册普通表
  99 + self.register_common_table(this_time,database,spatial_table_name)
  100 +
  101 + pg_ds.Destroy()
  102 +
  103 +
  104 + def register_spatial_table(self,pg_ds,database,this_time):
  105 + spatial_table_name =[]
  106 + tables=[]
  107 + for i in range(pg_ds.GetLayerCount()):
  108 + layer: Layer = pg_ds.GetLayer(i)
  109 + l_name = layer.GetName()
  110 + # 只注册public的空间表,其他表空间的表名会有.
  111 + if layer.GetName().__contains__("."):
  112 + continue
  113 +
  114 + # 不注册抽稀表
  115 + if layer.GetName().__contains__("_vacuate_"):
  116 + spatial_table_name.append(layer.GetName())
  117 + continue
  118 +
  119 + # 范围统计和数量统计以100w为界限
  120 + query_count_layer: Layer = pg_ds.ExecuteSQL(
  121 + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format(l_name))
  122 + feature_count = query_count_layer.GetFeature(0).GetField("ec")
  123 + # 要素少于100w可以精确统计
  124 + if feature_count < 1000000:
  125 + feature_count = layer.GetFeatureCount()
  126 + ext = layer.GetExtent()
  127 + else:
  128 + query_ext_layer: Layer = pg_ds.ExecuteSQL(
  129 + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name,
  130 + layer.GetGeometryColumn()))
  131 + ext = query_ext_layer.GetExtent()
  132 + if ext[0] < 360:
  133 + ext = [round(e, 6) for e in ext]
  134 + else:
  135 + ext = [round(e, 2) for e in ext]
  136 + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3])
  137 +
  138 + table_guid = uuid.uuid1().__str__()
  139 +
  140 + table = Table(guid=table_guid,
  141 + database_guid=database.guid,
  142 + # alias=layer.GetName(),
  143 + name=layer.GetName(), create_time=this_time, update_time=this_time,
  144 + table_type=GeometryAdapter.get_table_type(layer.GetGeomType()),
  145 + extent=extent,
  146 + feature_count=feature_count
  147 + )
  148 +
  149 + db.session.add(table)
  150 + tables.append(table)
  151 +
  152 + feature_defn: FeatureDefn = layer.GetLayerDefn()
  153 +
  154 + for i in range(feature_defn.GetFieldCount()):
  155 + field_defn: FieldDefn = feature_defn.GetFieldDefn(i)
  156 + field_name = field_defn.GetName().lower()
  157 + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__(
  158 + "") else field_defn.GetAlternativeName()
  159 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid,
  160 + name=field_name, alias=field_alias, create_time=this_time, update_time=this_time)
  161 + db.session.add(column)
  162 +
  163 + spatial_table_name.append(layer.GetName())
  164 + return spatial_table_name,tables
  165 +
  166 +
  167 + def register_common_table(self,this_time,database,spatial_table_name):
  168 + # 注册普通表
  169 + db_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri))
  170 +
  171 + # 只注册public中的表
  172 + result = db_session.execute(
  173 + "select relname as tabname from pg_class c where relkind = 'r' and relnamespace=2200 and relname not like 'pg_%' and relname not like 'sql_%' order by relname").fetchall()
  174 + for re in result:
  175 + table_name = re[0]
  176 + if table_name not in spatial_table_name:
  177 +
  178 + table_guid = uuid.uuid1().__str__()
  179 + count = db_session.execute('select count(*) from "{}"'.format(table_name)).fetchone()[0]
  180 +
  181 + table = Table(guid=table_guid,
  182 + database_guid=database.guid,
  183 + # alias=layer.GetName(),
  184 + name=table_name, create_time=this_time, update_time=this_time,
  185 + table_type=0,
  186 + feature_count=count
  187 + )
  188 +
  189 + db.session.add(table)
  190 +
  191 + sql = '''
  192 + SELECT
  193 + a.attnum,
  194 + a.attname AS field
  195 + FROM
  196 + pg_class c,
  197 + pg_attribute a,
  198 + pg_type t
  199 + WHERE
  200 + c.relname = '{}'
  201 + and a.attnum > 0
  202 + and a.attrelid = c.oid
  203 + and a.atttypid = t.oid
  204 + ORDER BY a.attnum
  205 + '''.format(table_name)
  206 +
  207 + cols = db_session.execute(sql).fetchall()
  208 + for col in cols:
  209 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid,
  210 + name=col[1], create_time=this_time, update_time=this_time)
  211 + db.session.add(column)
  212 + db_session.close()
  213 +
  214 + def regiser_vacuate_talbe(self,pg_ds,tables):
  215 +
  216 + # 注册抽稀表
  217 + for i in range(pg_ds.GetLayerCount()):
  218 + layer:Layer = pg_ds.GetLayer(i)
  219 + l_name = layer.GetName()
  220 + if l_name.__contains__("_vacuate_"):
  221 + base_layer_name=l_name.split("_vacuate_")[0]
  222 + level = l_name.split("_")[-2]
  223 + pixel_distance_str: str ="0"
  224 + try:
  225 + pixel_distance_str :str= l_name.split("_")[-1]
  226 + if pixel_distance_str.startswith("0"):
  227 + pixel_distance_str="0.{}".format(pixel_distance_str)
  228 + except:
  229 + pass
  230 + base_table = [table for table in tables if table.name.__eq__(base_layer_name)][0]
  231 + table_vacuate = TableVacuate(guid=uuid.uuid1().__str__(),
  232 + table_guid=base_table.guid,
  233 + level=level,
  234 + name=l_name,
  235 + pixel_distance=float(pixel_distance_str))
  236 + db.session.add(table_vacuate)
  237 +
  238 +
  239 +
  240 + def check_space(self,sqlachemy_uri):
  241 + system_session = None
  242 + check = True
  243 + try:
  244 + test_sql = "select st_geometryfromtext('POINT(1 1)')"
  245 + engine = create_engine(sqlachemy_uri)
  246 + system_session = sessionmaker(bind=engine)()
  247 + system_session.execute(test_sql).fetchone()
  248 +
  249 + except:
  250 + check = False
  251 + finally:
  252 + if system_session:
  253 + system_session.close()
  254 +
  255 + return check
  256 +
  257 + api_doc={
  258 + "tags":["数据库接口"],
  259 + "parameters":[
  260 + {"name": "host",
  261 + "in": "formData",
  262 + "type": "string", "required": "true"},
  263 + {"name": "port",
  264 + "in": "formData",
  265 + "type": "string", "required": "true"},
  266 + {"name": "user",
  267 + "in": "formData",
  268 + "type": "string", "required": "true"},
  269 + {"name": "passwd",
  270 + "in": "formData",
  271 + "type": "string", "required": "true"},
  272 + {"name": "database",
  273 + "in": "formData",
  274 + "type": "string", "required": "true"},
  275 + {"name": "creator",
  276 + "in": "formData",
  277 + "type": "string", "required": "true"},
  278 +
  279 + {"name": "alias",
  280 + "in": "formData",
  281 + "type": "string","description":"数据库别名","required": "true"},
  282 +
  283 + {"name": "encryption",
  284 + "in": "formData",
  285 + "type": "integer", "description": "密码是否加密", "enum": [0,1]},
  286 + ],
  287 + "responses":{
  288 + 200:{
  289 + "schema":{
  290 + "properties":{
  291 + }
  292 + }
  293 + }
  294 + }
  295 +}
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/6/22
  4 +# email: nheweijun@sina.com
  5 +from contextlib import closing
  6 +
  7 +from sqlalchemy import create_engine
  8 +
  9 +from app.models import DES,Database,db
  10 +
  11 +from sqlalchemy.orm import sessionmaker
  12 +from app.util.component.ApiTemplate import ApiTemplate
  13 +class Api(ApiTemplate):
  14 + api_name = "数据库连接测试"
  15 + def process(self):
  16 + res = {}
  17 + res["result"] = False
  18 + try:
  19 +
  20 +
  21 + host = self.para.get("host")
  22 + port = self.para.get("port")
  23 + user = self.para.get("user")
  24 + passwd = self.para.get("passwd")
  25 + database = self.para.get("database")
  26 +
  27 + encryption = int(self.para.get("encryption", "0"))
  28 + if encryption:
  29 + passwd = DES.decode(passwd)
  30 +
  31 +
  32 + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user,passwd,host,port,database)
  33 +
  34 + # if is_encrypt == "1":
  35 + # sqlalchemy_uri=DES.decode(sqlalchemy_uri)
  36 +
  37 + engine = create_engine(sqlalchemy_uri, connect_args={'connect_timeout': 2})
  38 + with closing(engine.connect()):
  39 + pass
  40 +
  41 +
  42 + #判断数据库是否存在
  43 + datab = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none()
  44 + #真实的数据库
  45 + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host, port, database, user,
  46 + passwd)
  47 + real_database = db.session.query(Database).filter_by(connectstr=DES.encode(connectsrt)).all()
  48 +
  49 + if datab:
  50 + res["msg"] = "数据库已存在,请修改别名!"
  51 + return res
  52 +
  53 + elif real_database:
  54 + res["msg"] = "数据库连接已存在,请修改数据库连接!"
  55 + return res
  56 +
  57 + elif not self.check_space(sqlalchemy_uri):
  58 + res["msg"] = "数据不是空间数据库!"
  59 + return res
  60 + else:
  61 + res["result"] = True
  62 + res["msg"] = "测试连接成功"
  63 + except:
  64 + raise Exception("测试连接失败!")
  65 + return res
  66 +
  67 +
  68 + def check_space(self,sqlachemy_uri):
  69 + system_session = None
  70 + check = True
  71 + try:
  72 + test_sql = "select st_geometryfromtext('POINT(1 1)')"
  73 + engine = create_engine(sqlachemy_uri)
  74 + system_session = sessionmaker(bind=engine)()
  75 + system_session.execute(test_sql).fetchone()
  76 + except:
  77 + check = False
  78 + finally:
  79 + if system_session:
  80 + system_session.close()
  81 +
  82 + return check
  83 +
  84 + api_doc={
  85 + "tags":["数据库接口"],
  86 + "parameters":[
  87 + # {"name": "sqlalchemy_uri",
  88 + # "in": "formData",
  89 + # "type": "string","description":"数据库uri","required": "true"},
  90 + {"name": "host",
  91 + "in": "formData",
  92 + "type": "string", "required": "true"},
  93 + {"name": "port",
  94 + "in": "formData",
  95 + "type": "string", "required": "true"},
  96 + {"name": "user",
  97 + "in": "formData",
  98 + "type": "string", "required": "true"},
  99 + {"name": "passwd",
  100 + "in": "formData",
  101 + "type": "string", "required": "true"},
  102 + {"name": "database",
  103 + "in": "formData",
  104 + "type": "string", "required": "true"},
  105 + # {"name": "alias",
  106 + # "in": "formData",
  107 + # "type": "string", "required": "true"},
  108 + {"name": "encryption",
  109 + "in": "formData",
  110 + "type": "int", "description": "密码是否加密", "enum": [0, 1]},
  111 +
  112 + ],
  113 + "responses":{
  114 + 200:{
  115 + "schema":{
  116 + "properties":{
  117 + }
  118 + }
  119 + }
  120 + }
  121 +}
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/2/23
  4 +#email: nheweijun@sina.com
  5 +
  6 +from flask import Blueprint
  7 +from app.util import BlueprintApi
  8 +from flasgger import swag_from
  9 +from . import index
  10 +from . import release
  11 +from . import get_app_name
  12 +class Template(BlueprintApi):
  13 +
  14 + bp = Blueprint("Index", __name__, url_prefix="/")
  15 +
  16 + @staticmethod
  17 + @bp.route('/', methods=['GET'])
  18 + @swag_from(index.Api.api_doc)
  19 + def api_index():
  20 + """
  21 + Index接口
  22 + """
  23 + return index.Api().result
  24 +
  25 + @staticmethod
  26 + @bp.route('/release', methods=['GET'])
  27 + @swag_from(release.Api.api_doc)
  28 + def release():
  29 + """
  30 + release接口
  31 + """
  32 + return release.Api().result
  33 +
  34 + @staticmethod
  35 + @bp.route('/GetAppName', methods=['GET'])
  36 + @swag_from(get_app_name.Api.api_doc)
  37 + def get_app_name():
  38 + """
  39 + GetAppName接口
  40 + """
  41 + return get_app_name.Api().result
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/7/20
  4 +#email: nheweijun@sina.com
  5 +
  6 +import hashlib
  7 +from flask import current_app as app
  8 +import socket
  9 +import configure
  10 +
  11 +from app.util.component.ApiTemplate import ApiTemplate
  12 +class Api(ApiTemplate):
  13 +
  14 + def process(self):
  15 + return configure.application_name
  16 +
  17 + api_doc={
  18 + "tags":["Index接口"],
  19 + "description":"Index接口",
  20 + "parameters":[
  21 + ],
  22 + "responses":{
  23 + 200:{
  24 + "schema":{
  25 + "properties":{
  26 + "content":{
  27 + "type": "string",
  28 + "description": "The name of the user"
  29 + }
  30 + }
  31 + }
  32 + }
  33 + }
  34 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/2/23
  4 +#email: nheweijun@sina.com
  5 +import hashlib
  6 +from flask import current_app as app
  7 +import socket
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +class Api(ApiTemplate):
  11 +
  12 + def process(self):
  13 + return {"Name":socket.gethostname(),"Type":"DMapDMS"}
  14 +
  15 + api_doc={
  16 + "tags":["Index接口"],
  17 + "description":"Index接口",
  18 + "parameters":[
  19 + ],
  20 + "responses":{
  21 + 200:{
  22 + "schema":{
  23 + "properties":{
  24 + "content":{
  25 + "type": "string",
  26 + "description": "The name of the user"
  27 + }
  28 + }
  29 + }
  30 + }
  31 + }
  32 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/2/23
  4 +#email: nheweijun@sina.com
  5 +import hashlib
  6 +from flask import current_app as app
  7 +import socket
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +from app.models import Task,InsertingLayerName,db
  11 +import datetime
  12 +import traceback
  13 +from sqlalchemy import or_
  14 +class Api(ApiTemplate):
  15 +
  16 + def process(self):
  17 + try:
  18 + # 遗留任务变成失败
  19 + last_tasks = db.session.query(Task).filter(or_(Task.state == 0, Task.state == 2))
  20 + last_tasks.update({"state": -1,"process":"入库失败","update_time":datetime.datetime.now()})
  21 + last_lins = db.session.query(InsertingLayerName).all()
  22 + if last_lins:
  23 + for lin in last_lins:
  24 + db.session.delete(lin)
  25 + db.session.commit()
  26 + except Exception as e:
  27 + # print(e)
  28 + print(traceback.format_exc())
  29 +
  30 + return {"state": 1}
  31 +
  32 + api_doc={
  33 + "tags":["Index接口"],
  34 + "description":"Index接口",
  35 + "parameters":[
  36 + ],
  37 + "responses":{
  38 + 200:{
  39 + "schema":{
  40 + "properties":{
  41 + "content":{
  42 + "type": "string",
  43 + "description": "The name of the user"
  44 + }
  45 + }
  46 + }
  47 + }
  48 + }
  49 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/12/8
  4 +#email: nheweijun@sina.com
  5 +from flasgger import swag_from
  6 +from flask import Blueprint
  7 +from app.util import BlueprintApi
  8 +
  9 +from flask import send_from_directory,current_app,send_file
  10 +import os
  11 +import shutil
  12 +from . import data_download
  13 +from . import get_meta
  14 +from . import data_entry_by_meta
  15 +from . import get_data_list
  16 +from flask import after_this_request
  17 +import time
  18 +
  19 +class DataManager(BlueprintApi):
  20 +
  21 + bp:Blueprint = Blueprint("DataTool", __name__, url_prefix="/API/IO")
  22 +
  23 +
  24 + @staticmethod
  25 + @bp.route('/Download/<file>', methods=['GET'])
  26 + def table_download_file(file):
  27 + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  28 + dirpath = os.path.join(parent,"file_tmp")
  29 +
  30 + # @staticmethod
  31 + # @after_this_request
  32 + # def delete(response):
  33 + # try:
  34 + # os.remove(os.path.join(dirpath,file))
  35 + # # shutil.rmtree(dirpath)
  36 + # print("删除文件成功!")
  37 + # except Exception as e:
  38 + # print(e)
  39 + # print("删除文件失败!")
  40 + # return response
  41 +
  42 + return send_from_directory(dirpath, filename=file, as_attachment=True)
  43 +
  44 + @staticmethod
  45 + @bp.route('/DeleteFile/<file>', methods=['GET'])
  46 + def d_file(file):
  47 + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  48 + dirpath = os.path.join(parent, "file_tmp")
  49 +
  50 + result={}
  51 + try:
  52 + os.remove(os.path.join(dirpath, file))
  53 + # shutil.rmtree(dirpath)
  54 + result["message"]="删除文件成功!"
  55 + except Exception as e:
  56 + print(e)
  57 + result["message"] ="删除文件失败!"
  58 + return result
  59 +
  60 + @staticmethod
  61 + @bp.route('/DataDownload', methods=['POST'])
  62 + @swag_from(data_download.Api.api_doc)
  63 + def table_download():
  64 + """
  65 + 下载数据
  66 + """
  67 + return data_download.Api().result
  68 +
  69 +
  70 + @staticmethod
  71 + @bp.route('/GetMeta', methods=['POST'])
  72 + @swag_from(get_meta.Api.api_doc)
  73 + def get_meta():
  74 + """
  75 + 数据Meta
  76 + """
  77 + return get_meta.Api().result
  78 +
  79 + @staticmethod
  80 + @bp.route('/GetDataList', methods=['POST'])
  81 + @swag_from(get_data_list.Api.api_doc)
  82 + def get_data_list():
  83 + """
  84 + 本地数据list
  85 + """
  86 + return get_data_list.Api().result
  87 +
  88 + @staticmethod
  89 + @bp.route('/DataEntryByMeta', methods=['POST'])
  90 + @swag_from(data_entry_by_meta.Api.api_doc)
  91 + def data_entry_by_meta():
  92 + """
  93 + 数据入库ByMeta
  94 + """
  95 + return data_entry_by_meta.Api().result
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2021/1/27
  4 +# email: nheweijun@sina.com
  5 +
  6 +from osgeo.ogr import *
  7 +import uuid
  8 +
  9 +import time
  10 +from app.models import *
  11 +import json
  12 +import re
  13 +from app.util.component.ApiTemplate import ApiTemplate
  14 +from app.util.component.PGUtil import PGUtil
  15 +
  16 +
  17 +class Api(ApiTemplate):
  18 + api_name = "检查meta"
  19 + def process(self):
  20 +
  21 + # 设置任务信息
  22 + self.para["task_guid"] = uuid.uuid1().__str__()
  23 + self.para["task_time"] = time.time()
  24 +
  25 + # 返回结果
  26 + res = {}
  27 +
  28 + try:
  29 +
  30 +
  31 + # 图层重名检查
  32 +
  33 + meta_list: list = json.loads(self.para.get("meta").__str__())
  34 + check_meta_only = int(self.para.get("check_meta_only", 0))
  35 +
  36 + res["data"] = {}
  37 +
  38 +
  39 + database = Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none()
  40 + if not database:
  41 + raise Exception("数据库不存在!")
  42 + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri))
  43 +
  44 + res["result"] = True
  45 +
  46 + for meta in meta_list:
  47 + layers: dict = meta.get("layer")
  48 + for layer_name_origin in layers.keys():
  49 + layer_name = layers.get(layer_name_origin)
  50 + if pg_ds.GetLayerByName(layer_name) or InsertingLayerName.query.filter_by(
  51 + name=layer_name).one_or_none():
  52 + res["data"][layer_name_origin] = 0
  53 + res["result"] = False
  54 + # 判断特殊字符
  55 + elif re.search(r"\W", layer_name):
  56 + res["data"][layer_name_origin] = -1
  57 + res["result"] = False
  58 + else:
  59 + res["data"][layer_name_origin] = 1
  60 + if pg_ds:
  61 + try:
  62 + pg_ds.Destroy()
  63 + except:
  64 + print("关闭数据库失败!")
  65 +
  66 + except Exception as e:
  67 + raise e
  68 +
  69 + return res
  70 +
  71 + api_doc = {
  72 + "tags": ["IO接口"],
  73 + "parameters": [
  74 + {"name": "meta",
  75 + "in": "formData",
  76 + "type": "string",
  77 + "description": "数据meta"}
  78 + ],
  79 + "responses": {
  80 + 200: {
  81 + "schema": {
  82 + "properties": {
  83 + }
  84 + }
  85 + }
  86 + }
  87 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/11/27
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from app.models import *
  8 +
  9 +import traceback
  10 +
  11 +from osgeo.ogr import DataSource,Layer,FeatureDefn,FieldDefn
  12 +from osgeo import gdal ,ogr
  13 +import os
  14 +import uuid
  15 +import configure
  16 +from app.util.component.ApiTemplate import ApiTemplate
  17 +from app.util.component.PGUtil import PGUtil
  18 +from app.util.component.ZipUtil import ZipUtil
  19 +class Api(ApiTemplate):
  20 +
  21 + api_name = "下载数据"
  22 + def process(self):
  23 + #获取参数
  24 +
  25 + #返回结果
  26 + res={}
  27 + #设置编码
  28 + encoding = self.para.get("encoding")
  29 + if encoding:
  30 + gdal.SetConfigOption("SHAPE_ENCODING",encoding)
  31 + else:
  32 + gdal.SetConfigOption("SHAPE_ENCODING", "GBK")
  33 +
  34 + ds:DataSource = None
  35 + try:
  36 + table_names = self.para.get("table_name").split(",")
  37 + database_guid = self.para.get("database_guid")
  38 + database = Database.query.filter_by(guid=database_guid).one_or_none()
  39 + if not database:
  40 + raise Exception("数据库不存在!")
  41 +
  42 + ds:DataSource = PGUtil.open_pg_data_source(0,DES.decode(database.sqlalchemy_uri))
  43 +
  44 + download_type = self.para.get("download_type")
  45 +
  46 + data = None
  47 + if download_type.__eq__("shp"):
  48 + data = self.download_shp(table_names,ds)
  49 + if download_type.__eq__("gdb"):
  50 + data = self.download_gdb(table_names, ds,database_guid)
  51 +
  52 + res["data"] = data
  53 + res["result"] = True
  54 + except Exception as e:
  55 + print(traceback.format_exc())
  56 + res["msg"]= e.__str__()
  57 + res["result"]=False
  58 + finally:
  59 + if ds:
  60 + ds.Destroy()
  61 + return res
  62 +
  63 +
  64 + def download_shp(self,table_names,ds):
  65 + data = []
  66 + for table_name in table_names:
  67 + url = self.download_one(ds, table_name)
  68 + data.append({"name": table_name, "download_url": url})
  69 + return data
  70 +
  71 + def download_one(self,ds,table_name):
  72 +
  73 + layer: Layer = ds.GetLayerByName(table_name)
  74 + driver = ogr.GetDriverByName("ESRI Shapefile")
  75 + uuid_ = uuid.uuid1().__str__()
  76 + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  77 + dirpath = os.path.join(parent, "file_tmp", uuid_)
  78 + os.makedirs(dirpath)
  79 + data_source: DataSource = driver.CreateDataSource(dirpath + "/{}.shp".format(table_name))
  80 + data_source.CopyLayer(layer, table_name)
  81 + data_source.Destroy()
  82 + ZipUtil.create_zip(os.path.join(parent, "file_tmp", table_name+"_"+uuid_) + ".zip", [dirpath])
  83 + return "http://" + configure.deploy_ip_host + "/API/IO/Download/{}".format(table_name+"_"+uuid_ + ".zip")
  84 +
  85 +
  86 + def download_gdb(self,table_names,ds,database_guid):
  87 + ogr.RegisterAll()
  88 + data = []
  89 + gdal.UseExceptions()
  90 + gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")
  91 +
  92 + # 创建一个gdb datasource
  93 + gdb_driver = ogr.GetDriverByName('FileGDB')
  94 + uuid_ = uuid.uuid1().__str__()
  95 + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  96 + gdb_path = os.path.join(parent, "file_tmp", uuid_+".gdb")
  97 + gdb_ds: DataSource = gdb_driver.CreateDataSource(gdb_path)
  98 +
  99 +
  100 + for table_name in table_names:
  101 +
  102 + layer: Layer = ds.GetLayerByName(table_name)
  103 + table = Table.query.filter_by(name=table_name, database_guid=database_guid).one_or_none()
  104 + feature_defn: FeatureDefn = layer.GetLayerDefn()
  105 +
  106 + for i in range(feature_defn.GetFieldCount()):
  107 + field_defn:FieldDefn = feature_defn.GetFieldDefn(i)
  108 + field_alias = Columns.query.filter_by(table_guid=table.guid,name=field_defn.GetName()).one_or_none().alias
  109 + field_defn.SetAlternativeName(field_alias)
  110 +
  111 + table_alias= table.alias
  112 +
  113 + # if is_chinese(table_name):
  114 + # if not table_alias:
  115 + # table_alias = table_name
  116 + # table_name = "table{}".format(table_name.__hash__())
  117 +
  118 + gdb_ds.CopyLayer(layer, table_name,["LAYER_ALIAS={}".format(table_alias)])
  119 +
  120 + gdb_ds.Destroy()
  121 + ZipUtil.create_zip(gdb_path + ".zip", [gdb_path])
  122 + data.append({"name": ",".join(table_names), "download_url": "http://" + configure.deploy_ip_host + "/API/IO/Download/{}".format(uuid_+".gdb" + ".zip")})
  123 +
  124 +
  125 + return data
  126 +
  127 +
  128 +
  129 + api_doc={
  130 + "tags":["IO接口"],
  131 + "description":"下载数据",
  132 + "parameters":[
  133 + {"name": "table_name",
  134 + "in": "formData",
  135 + "type":"string","description":"支持多图层下载,以逗号相隔","required":"true"},
  136 + {"name": "encoding",
  137 + "in": "formData",
  138 + "type": "string",
  139 + "enum":["GBK","UTF-8"]},
  140 + {"name": "download_type",
  141 + "in": "formData",
  142 + "type": "string",
  143 + "enum": ["shp", "gdb"],"required":"true"
  144 + },
  145 + {"name": "database_guid",
  146 + "in": "formData",
  147 + "type": "string","required":"true"
  148 + }
  149 + ],
  150 + "responses":{
  151 + 200:{
  152 + "schema":{
  153 + "properties":{
  154 + "content":{
  155 + "type": "string",
  156 + "description": "The name of the user"
  157 + }
  158 + }
  159 + }
  160 + }
  161 + }
  162 +}
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/1/27
  4 +#email: nheweijun@sina.com
  5 +
  6 +from osgeo.ogr import *
  7 +import uuid
  8 +
  9 +import time
  10 +from app.models import *
  11 +import json
  12 +import re
  13 +from app.util.component.ApiTemplate import ApiTemplate
  14 +from app.util.component.PGUtil import PGUtil
  15 +
  16 +
  17 +
  18 +class Api(ApiTemplate):
  19 +
  20 + api_name = "通过meta入库"
  21 +
  22 + def process(self):
  23 +
  24 + #设置任务信息
  25 + self.para["task_guid"] = uuid.uuid1().__str__()
  26 + self.para["task_time"] = time.time()
  27 +
  28 + #返回结果
  29 + res={}
  30 +
  31 + try:
  32 + #检测目录
  33 + if Catalog.query.filter_by(pguid=self.para.get("guid")).all():
  34 + raise Exception("目录非子目录,不可入库!")
  35 +
  36 + # 图层重名检查
  37 + meta_list:list = json.loads(self.para.get("meta").__str__())
  38 + check_meta_only = int(self.para.get("check_meta_only",0))
  39 +
  40 + res["data"] = {}
  41 + if check_meta_only:
  42 +
  43 + database = Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none()
  44 + if not database:
  45 + raise Exception("数据库不存在!")
  46 + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri))
  47 +
  48 + res["result"] = True
  49 +
  50 + for meta in meta_list:
  51 + layers:dict = meta.get("layer")
  52 + for layer_name_origin in layers.keys():
  53 + layer_name = layers.get(layer_name_origin)
  54 + if pg_ds.GetLayerByName(layer_name) or InsertingLayerName.query.filter_by(name=layer_name).one_or_none():
  55 + res["data"][layer_name_origin]=0
  56 + res["result"] = False
  57 + # 判断特殊字符
  58 + elif re.search(r"\W",layer_name):
  59 + res["data"][layer_name_origin]=-1
  60 + res["result"] = False
  61 + else :
  62 + res["data"][layer_name_origin] = 1
  63 +
  64 + if pg_ds:
  65 + try:
  66 + pg_ds.Destroy()
  67 + except:
  68 + print("关闭数据库失败!")
  69 + return res
  70 +
  71 + # 录入数据后台进程,录入主函数为entry
  72 + # 初始化task
  73 + task = Task(guid=self.para.get("task_guid"),
  74 + name=self.para.get("task_name"),
  75 + create_time=datetime.datetime.now(),
  76 + state=0,
  77 + creator=self.para.get("creator"),
  78 + file_name=meta_list[0].get("filename"),
  79 + database_guid=self.para.get("database_guid"),
  80 + catalog_guid=self.para.get("catalog_guid"),
  81 + process="等待入库",
  82 + parameter=json.dumps(self.para))
  83 + db.session.add(task)
  84 + db.session.commit()
  85 +
  86 + res["result"] = True
  87 + res["msg"] = "数据录入提交成功!"
  88 + res["data"] = self.para["task_guid"]
  89 + except Exception as e:
  90 + raise e
  91 + return res
  92 +
  93 +
  94 + api_doc={
  95 + "tags":["IO接口"],
  96 + "parameters":[
  97 + {"name": "meta",
  98 + "in": "formData",
  99 + "type": "string",
  100 + "description": "数据meta"},
  101 + {"name": "encoding",
  102 + "in": "formData",
  103 + "type": "string",
  104 + "description": "原shp文件编码,非必要,优先使用cpg文件中编码,没有则默认GBK","enum":["UTF-8","GBK"]},
  105 + {"name": "overwrite",
  106 + "in": "formData",
  107 + "type": "string",
  108 + "description": "是否覆盖",
  109 + "enum":["yes","no"]},
  110 + {"name": "fid",
  111 + "in": "formData",
  112 + "type": "string",
  113 + "description": "fid列名"},
  114 + {"name": "geom_name",
  115 + "in": "formData",
  116 + "type": "string",
  117 + "description": "空间属性列名"},
  118 + {"name": "task_name",
  119 + "in": "formData",
  120 + "type": "string",
  121 + "description": "任务名",
  122 + "required":"true"},
  123 + {"name": "creator",
  124 + "in": "formData",
  125 + "type": "string",
  126 + "description": "创建人"},
  127 + {"name": "database_guid",
  128 + "in": "formData",
  129 + "type": "string",
  130 + "description": "数据库guid",
  131 + "required": "true"},
  132 + {"name": "catalog_guid",
  133 + "in": "formData",
  134 + "type": "string",
  135 + "description": "目录guid"},
  136 + {"name": "check_meta_only",
  137 + "in": "formData",
  138 + "type": "int",
  139 + "description": "是否只检查meta","enum":[0,1]}
  140 +
  141 + ],
  142 + "responses":{
  143 + 200:{
  144 + "schema":{
  145 + "properties":{
  146 + }
  147 + }
  148 + }
  149 + }
  150 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/9/4
  4 +# email: nheweijun@sina.com
  5 +
  6 +import traceback
  7 +from osgeo.ogr import *
  8 +from osgeo import ogr
  9 +
  10 +from flask import request
  11 +import os
  12 +import uuid
  13 +
  14 +import json
  15 +from app.util.component.ApiTemplate import ApiTemplate
  16 +from app.util.component.ZipUtil import ZipUtil
  17 +from app.util.component.FileProcess import FileProcess
  18 +import datetime
  19 +import time
  20 +class Api(ApiTemplate):
  21 + api_name = "本地数据list"
  22 + def process(self):
  23 + res = {}
  24 +
  25 + try:
  26 + project_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
  27 + base_path = os.path.join(project_path,"tmp")
  28 + if self.para.get("data_path"):
  29 + base_path = os.path.normpath(self.para.get("data_path"))
  30 + data_list:list = []
  31 + for f in os.listdir(base_path):
  32 +
  33 + file_path = os.path.normpath(os.path.join(base_path, f))
  34 + file_size = FileProcess.get_file_size(file_path)
  35 +
  36 + fctime = datetime.datetime.fromtimestamp(os.path.getctime(file_path)).strftime('%Y-%m-%d %H:%M:%S')
  37 +
  38 + file_info ={"name":f,"path":file_path,"size":file_size,"create_time":fctime}
  39 +
  40 + if file_path.endswith("shp"):
  41 + file_info["type"]="shp"
  42 + data_list.append(file_info)
  43 + elif file_path.endswith("gdb"):
  44 + file_info["type"]="gdb"
  45 + data_list.append(file_info)
  46 + elif file_path.endswith("zip"):
  47 + file_info["type"]="zip"
  48 + data_list.append(file_info)
  49 + elif os.path.isdir(file_path):
  50 + bn = os.path.basename(file_path)
  51 + if not len(bn.split("-"))==5:
  52 + file_info["type"] = "dir"
  53 + data_list.append(file_info)
  54 +
  55 + data_list_sorted = sorted(data_list, key=lambda x: x["name"])
  56 + res["data"]=data_list_sorted
  57 + res["result"]=True
  58 + except Exception as e:
  59 + raise e
  60 + return res
  61 +
  62 +
  63 +
  64 + api_doc={
  65 + "tags":["IO接口"],
  66 + "parameters":[
  67 + {"name": "data_path",
  68 + "in": "formData",
  69 + "type": "string",
  70 + "description": "数据文件路径"}
  71 + ],
  72 + "responses":{
  73 + 200:{
  74 + "schema":{
  75 + "properties":{
  76 + }
  77 + }
  78 + }
  79 + }
  80 +}
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/9/4
  4 +# email: nheweijun@sina.com
  5 +
  6 +import traceback
  7 +from osgeo.ogr import *
  8 +from osgeo import ogr
  9 +
  10 +from flask import request
  11 +import os
  12 +import uuid
  13 +
  14 +import json
  15 +from app.util.component.ApiTemplate import ApiTemplate
  16 +from app.util.component.ZipUtil import ZipUtil
  17 +from app.util.component.FileProcess import FileProcess
  18 +
  19 +
  20 +class Api(ApiTemplate):
  21 + api_name = "获取meta"
  22 + def process(self):
  23 + res = {}
  24 +
  25 + try:
  26 +
  27 + spatial_files=[]
  28 + if self.para.get("data_path"):
  29 + filename = os.path.basename(self.para.get("data_path"))
  30 + #处理
  31 +
  32 + if self.para.get("data_path").endswith("zip"):
  33 +
  34 + store_path = ZipUtil.unzip(self.para.get("data_path"),True)
  35 + spatial_files = FileProcess.get_spatial_file(store_path)
  36 +
  37 + elif self.para.get("data_path").endswith("shp"):
  38 + data_path=self.para.get("data_path")
  39 + encoding_cpg_path = data_path.split(".shp")[0]+".cpg"
  40 + with open(encoding_cpg_path) as fd:
  41 + encoding_cpg = fd.readline().strip()
  42 + if not os.path.exists(encoding_cpg_path):
  43 + encoding_cpg=None
  44 + spatial_files.append((data_path, encoding_cpg))
  45 + elif self.para.get("data_path").endswith("gdb"):
  46 + data_path=self.para.get("data_path")
  47 + encoding_cpg=None
  48 + spatial_files.append((data_path, encoding_cpg))
  49 + else:
  50 + raise Exception("文件不符合规格!")
  51 +
  52 + else:
  53 + # 保存文件
  54 + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  55 + dir_path, store_file = FileProcess.save(parent)
  56 + store_path = ZipUtil.unzip(store_file)
  57 +
  58 + spatial_files = FileProcess.get_spatial_file(store_path)
  59 +
  60 + file = request.files['file']
  61 + filename = file.filename.split('"')[0]
  62 +
  63 + res["data"] =[]
  64 +
  65 + for data_path,code in spatial_files:
  66 + one_data = self.get_meta(data_path)
  67 + one_data["encoding"]=code
  68 + one_data["filename"] = filename
  69 + res["data"].append(one_data)
  70 + res["result"] = True
  71 +
  72 + except Exception as e:
  73 + raise e
  74 + return json.dumps(res,ensure_ascii=False)
  75 +
  76 +
  77 + def get_meta(self,data_path):
  78 + ds: DataSource = None
  79 + info = {}
  80 + layer_name = {}
  81 + info["data_path"] = os.path.normpath(data_path)
  82 + info["layer"] =layer_name
  83 + try:
  84 + # 分为shp和gdb 2种
  85 + if data_path.endswith("shp"):
  86 + info["type"]="shp"
  87 + driver: Driver = ogr.GetDriverByName("ESRI Shapefile")
  88 + ds: DataSource = driver.Open(data_path, 1)
  89 + if not ds:
  90 + raise Exception("打开数据失败!")
  91 + layer: Layer = ds.GetLayer(0)
  92 + layer_name[layer.GetName().lower()] = layer.GetName().lower()
  93 +
  94 + if data_path.endswith("gdb"):
  95 + info["type"] = "gdb"
  96 + driver: Driver = ogr.GetDriverByName("OpenFileGDB")
  97 + ds: DataSource = driver.Open(data_path, 0)
  98 + if not ds:
  99 + raise Exception("打开数据失败!")
  100 + for i in range(ds.GetLayerCount()):
  101 + layer: Layer = ds.GetLayer(i)
  102 + layer_name[layer.GetName().lower()] = layer.GetName().lower()
  103 + except Exception as e :
  104 + print(traceback.format_exc())
  105 + info={}
  106 + finally:
  107 + if ds:
  108 + ds.Destroy()
  109 + return info
  110 +
  111 +
  112 + api_doc={
  113 + "tags":["IO接口"],
  114 + "parameters":[
  115 + {"name": "file",
  116 + "in": "formData",
  117 + "type":"file",
  118 + "description":"数据文件zip压缩包"},
  119 + {"name": "data_path",
  120 + "in": "formData",
  121 + "type": "string",
  122 + "description": "数据文件路径"}
  123 + ],
  124 + "responses":{
  125 + 200:{
  126 + "schema":{
  127 + "properties":{
  128 + }
  129 + }
  130 + }
  131 + }
  132 +}
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/11/30
  4 +#email: nheweijun@sina.com
  5 +from app.util import *
  6 +import configure
  7 +from osgeo import ogr
  8 +
  9 +from configure import SQLALCHEMY_DATABASE_URI
  10 +from sqlalchemy import create_engine
  11 +from sqlalchemy.orm import sessionmaker, session
  12 +
  13 +
  14 +
  15 +
  16 +
  17 +
  18 +
  19 +
  20 +
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/1
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from flasgger import swag_from
  8 +from flask import Blueprint
  9 +from app.util import BlueprintApi
  10 +
  11 +from . import field_edit
  12 +
  13 +from . import table_list
  14 +from . import field_list
  15 +from . import table_edit
  16 +from . import table_info
  17 +from . import table_refresh
  18 +from . import table_delete
  19 +from . import table_view
  20 +class DataManager(BlueprintApi):
  21 +
  22 + bp = Blueprint("DataManager", __name__, url_prefix="/API/Manager")
  23 +
  24 + @staticmethod
  25 + @bp.route('/FieldEdit', methods=['POST'])
  26 + @swag_from(field_edit.Api.api_doc)
  27 + def field_edit():
  28 + """
  29 + 修改属性别名
  30 + """
  31 + return field_edit.Api().result
  32 +
  33 + @staticmethod
  34 + @bp.route('/FieldList', methods=['POST'])
  35 + @swag_from(field_list.Api.api_doc)
  36 + def field_list():
  37 + """
  38 + 属性列表
  39 + """
  40 + return field_list.Api().result
  41 +
  42 + @staticmethod
  43 + @bp.route('/TableList', methods=['POST'])
  44 + @swag_from(table_list.Api.api_doc)
  45 + def table_list():
  46 + """
  47 + 数据列表
  48 + """
  49 + return table_list.Api().result
  50 +
  51 +
  52 + @staticmethod
  53 + @bp.route('/TableEdit', methods=['POST'])
  54 + @swag_from(table_edit.Api.api_doc)
  55 + def table_edit():
  56 + """
  57 + 修改数据
  58 + """
  59 + return table_edit.Api().result
  60 +
  61 +
  62 + @staticmethod
  63 + @bp.route('/TableDelete', methods=['POST'])
  64 + @swag_from(table_delete.Api.api_doc)
  65 + def table_delete():
  66 + """
  67 + 删除数据
  68 + """
  69 + return table_delete.Api().result
  70 +
  71 +
  72 + @staticmethod
  73 + @bp.route('/TableInfo', methods=['POST'])
  74 + @swag_from(table_info.Api.api_doc)
  75 + def table_info():
  76 + """
  77 + 数据信息
  78 + """
  79 + return table_info.Api().result
  80 +
  81 +
  82 + @staticmethod
  83 + @bp.route('/TableRefresh', methods=['POST'])
  84 + @swag_from(table_refresh.Api.api_doc)
  85 + def table_refresh():
  86 + """
  87 + 刷新数据
  88 + """
  89 + return table_refresh.Api().result
  90 +
  91 + @staticmethod
  92 + @bp.route('/TableView', methods=['POST'])
  93 + @swag_from(table_view.Api.api_doc)
  94 + def table_view():
  95 + """
  96 + 数据浏览
  97 + """
  98 + return table_view.Api().result
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +from app.models import Columns
  6 +import datetime
  7 +
  8 +
  9 +from app.models import db
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +
  12 +
  13 +class Api(ApiTemplate):
  14 + api_name = "修改属性"
  15 + def process(self):
  16 +
  17 + #返回结果
  18 + res={}
  19 +
  20 + column_guid = self.para.get("column_guid")
  21 + try:
  22 + Columns.query.filter_by(guid = column_guid).update({"alias":self.para.get("column_alias"),"update_time":datetime.datetime.now()})
  23 + db.session.commit()
  24 + res["result"] = True
  25 + res["msg"] = "属性别名修改成功!"
  26 +
  27 + except Exception as e:
  28 + raise e
  29 +
  30 + return res
  31 +
  32 + api_doc={
  33 + "tags":["管理接口"],
  34 + "parameters":[
  35 + {"name": "column_guid",
  36 + "in": "formData",
  37 + "type": "string",
  38 + "description": "属性guid","required":"true"},
  39 + {"name": "column_alias",
  40 + "in": "formData",
  41 + "type": "string",
  42 + "description": "属性别名","required":"true"},
  43 +
  44 + ],
  45 + "responses":{
  46 + 200:{
  47 + "schema":{
  48 + "properties":{
  49 + }
  50 + }
  51 + }
  52 + }
  53 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +import traceback
  7 +from app.models import Table
  8 +from app.util.component.ApiTemplate import ApiTemplate
  9 +from app.util.component.ModelVisitor import ModelVisitor
  10 +
  11 +class Api(ApiTemplate):
  12 + api_name = "属性列表"
  13 + def process(self):
  14 +
  15 + #返回结果
  16 + dir_path = None
  17 + res={}
  18 +
  19 + try:
  20 + table_guid = self.para.get("guid")
  21 + table:Table = Table.query.filter_by(guid=table_guid).one_or_none()
  22 + if table:
  23 + res["result"]=True
  24 + res["data"] = ModelVisitor.objects_to_jsonarray(table.relate_columns)
  25 + else:
  26 + res["result"]=False
  27 + res["msg"] = "数据表不存在!"
  28 + except Exception as e:
  29 + print(traceback.format_exc())
  30 + raise e
  31 +
  32 + return res
  33 +
  34 + api_doc={
  35 + "tags":["管理接口"],
  36 + "parameters":[
  37 + {"name": "guid",
  38 + "in": "formData",
  39 + "type": "string",
  40 + "description": "表guid"},
  41 +
  42 + ],
  43 + "responses":{
  44 + 200:{
  45 + "schema":{
  46 + "properties":{
  47 + }
  48 + }
  49 + }
  50 + }
  51 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +
  7 +import traceback
  8 +from app.models import Table,Database,DES
  9 +
  10 +from osgeo.ogr import DataSource
  11 +
  12 +
  13 +from app.models import db
  14 +from app.util.component.ApiTemplate import ApiTemplate
  15 +
  16 +from app.util.component.PGUtil import PGUtil
  17 +class Api(ApiTemplate):
  18 + api_name = "删除表"
  19 + def process(self):
  20 +
  21 + res = {}
  22 + pg_ds = None
  23 + try:
  24 +
  25 + table_guid = self.para.get("guid")
  26 +
  27 + table = Table.query.filter_by(guid=table_guid)
  28 +
  29 + if not table.one_or_none():
  30 + res["result"]=False
  31 + res["msg"]= "数据不存在!"
  32 + return res
  33 +
  34 +
  35 + table = table.one_or_none()
  36 +
  37 +
  38 +
  39 + # 删除真实数据
  40 + database = Database.query.filter_by(guid=table.database_guid).one_or_none()
  41 + if not database:
  42 + res["result"]=False
  43 + res["msg"]= "数据库不存在!"
  44 + return res
  45 +
  46 +
  47 + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri))
  48 +
  49 +
  50 + #删除抽稀表
  51 + vacuate_tables=table.relate_table_vacuates.all()
  52 + for vt in vacuate_tables:
  53 + pg_ds.DeleteLayer(vt.name)
  54 +
  55 + pg_ds.DeleteLayer(table.name)
  56 +
  57 + # 删除元数据
  58 +
  59 + db.session.delete(table)
  60 + db.session.commit()
  61 + res["result"] = True
  62 + res["msg"] ="删除成功!"
  63 + except Exception as e:
  64 + raise e
  65 + finally:
  66 + if pg_ds:
  67 + pg_ds.Destroy()
  68 + return res
  69 +
  70 + api_doc={
  71 + "tags":["管理接口"],
  72 + "parameters":[
  73 + {"name": "guid",
  74 + "in": "formData",
  75 + "type": "string",
  76 + "description": "表guid","required":"true"},
  77 + ],
  78 + "responses":{
  79 + 200:{
  80 + "schema":{
  81 + "properties":{
  82 + }
  83 + }
  84 + }
  85 + }
  86 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +
  7 +from app.models import Table,Catalog,Database,DES
  8 +
  9 +from app.models import db,Columns
  10 +import json
  11 +
  12 +from sqlalchemy.orm import Session
  13 +import datetime
  14 +from app.util.component.ApiTemplate import ApiTemplate
  15 +from app.util.component.PGUtil import PGUtil
  16 +
  17 +class Api(ApiTemplate):
  18 + api_name = "修改表信息"
  19 + def process(self):
  20 +
  21 + res = {}
  22 + try:
  23 +
  24 + table_guid = self.para.get("guid")
  25 + table_alias = self.para.get("alias")
  26 + catalog_guid = self.para.get("catalog_guid")
  27 + description = self.para.get("description")
  28 + name = self.para.get("name")
  29 + columns_edit = self.para.get("columns_edit")
  30 +
  31 +
  32 + table = Table.query.filter_by(guid=table_guid)
  33 + if not table.one_or_none():
  34 + res["result"]=False
  35 + res["msg"]= "数据不存在!"
  36 + return res
  37 +
  38 +
  39 + if self.para.__contains__("catalog_guid"):
  40 + if catalog_guid is None:
  41 + table.update({"catalog_guid": None})
  42 +
  43 + else:
  44 + # 检测目录的有效性
  45 +
  46 + if not Catalog.query.filter_by(guid=catalog_guid).one_or_none():
  47 + raise Exception("目录不存在!")
  48 +
  49 + if not table.one_or_none().database_guid.__eq__(
  50 + Catalog.query.filter_by(guid=catalog_guid).one_or_none().database_guid
  51 + ):
  52 + raise Exception("该目录不属于表所在的数据库!")
  53 + table.update({"catalog_guid": catalog_guid})
  54 +
  55 +
  56 + if self.para.__contains__("alias"):
  57 + table.update({"alias":table_alias})
  58 +
  59 + if self.para.__contains__("description"):
  60 + table.update({"description":description})
  61 +
  62 + if columns_edit:
  63 + columns_edit_list = json.loads(columns_edit)
  64 + for ce in columns_edit_list:
  65 + Columns.query.filter_by(guid=ce["guid"]).update({"alias": ce.get("alias")})
  66 +
  67 + if name:
  68 + sys_session=None
  69 + try:
  70 + this_table = table.one_or_none()
  71 + database:Database= this_table.relate_database
  72 + sys_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri))
  73 + rename_sql = 'alter table "{}" rename to "{}"'.format(this_table.name,name)
  74 + sys_session.execute(rename_sql)
  75 + sys_session.commit()
  76 + print(rename_sql)
  77 + # 更新所有相关业务表
  78 + same_databases = Database.query.filter_by(sqlalchemy_uri=this_table.relate_database.sqlalchemy_uri).all()
  79 + same_databases_database_guid = [d.guid for d in same_databases]
  80 + re_tables = Table.query.filter(Table.name==this_table.name).filter(Table.database_guid.in_(same_databases_database_guid))
  81 + re_tables.update({"name": name},synchronize_session=False)
  82 + except:
  83 + raise Exception("表名更新失败,表名已存在!")
  84 + finally:
  85 + if sys_session:
  86 + sys_session.close()
  87 + db.session.commit()
  88 +
  89 + if catalog_guid or table_alias or name or columns_edit:
  90 + Table.query.filter_by(guid=table_guid).update({"update_time":datetime.datetime.now()})
  91 + db.session.commit()
  92 + res["result"] = True
  93 + res["msg"] = "更新成功!"
  94 + except Exception as e:
  95 + db.session.rollback()
  96 + raise e
  97 + return res
  98 +
  99 + api_doc={
  100 + "tags":["管理接口"],
  101 + "parameters":[
  102 + {"name": "guid",
  103 + "in": "formData",
  104 + "type": "string",
  105 + "description": "表guid","required":"true"},
  106 + {"name": "alias",
  107 + "in": "formData",
  108 + "type": "string",
  109 + "description": "表别名"},
  110 + {"name": "name",
  111 + "in": "formData",
  112 + "type": "string",
  113 + "description": "表名"},
  114 + {"name": "description",
  115 + "in": "formData",
  116 + "type": "string",
  117 + "description": "表描述"},
  118 + {"name": "catalog_guid",
  119 + "in": "formData",
  120 + "type": "string",
  121 + "description": "目录guid,如果要撤销挂载目录,传None"},
  122 + {"name": "columns_edit",
  123 + "in": "formData",
  124 + "type": "string",
  125 + "description": '需要修改的属性的列表,如 [{"guid":"1fda","alias":"测试"}]'},
  126 +
  127 + ],
  128 + "responses":{
  129 + 200:{
  130 + "schema":{
  131 + "properties":{
  132 + }
  133 + }
  134 + }
  135 + }
  136 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +from app.models import Table,Columns
  6 +
  7 +from app.util.component.ApiTemplate import ApiTemplate
  8 +from app.util.component.ModelVisitor import ModelVisitor
  9 +
  10 +class Api(ApiTemplate):
  11 + api_name = "表信息"
  12 + def process(self):
  13 + res = {}
  14 + try:
  15 + table_guid = self.para.get("guid")
  16 + table = Table.query.filter_by(guid=table_guid)
  17 + if not table.one_or_none():
  18 + raise Exception("数据不存在!")
  19 + table = table.one_or_none()
  20 + columns = table.relate_columns.order_by(Columns.name).all()
  21 + res["data"]=ModelVisitor.table_to_json(table)
  22 + res["data"]["columns"] = ModelVisitor.objects_to_jsonarray(columns)
  23 + res["result"] = True
  24 +
  25 + except Exception as e:
  26 + raise e
  27 + return res
  28 +
  29 + api_doc={
  30 + "tags":["管理接口"],
  31 + "parameters":[
  32 + {"name": "guid",
  33 + "in": "formData",
  34 + "type": "string",
  35 + "description": "表guid","required":"true"},
  36 + ],
  37 + "responses":{
  38 + 200:{
  39 + "schema":{
  40 + "properties":{
  41 + }
  42 + }
  43 + }
  44 + }
  45 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +from app.models import Table,Catalog,Database,DES,Columns,db
  7 +
  8 +from sqlalchemy import or_,and_
  9 +
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +
  12 +from app.util.component.ModelVisitor import ModelVisitor
  13 +class Api(ApiTemplate):
  14 + api_name = "表列表"
  15 + def process(self):
  16 +
  17 + res = {}
  18 + try:
  19 +
  20 + page_index = int(self.para.get("page_index", "0"))
  21 + page_size = int(self.para.get("page_size", "10"))
  22 +
  23 + alias = self.para.get("alias")
  24 + name = self.para.get("name")
  25 +
  26 +
  27 + database_guid = self.para.get("database_guid")
  28 + catalog_guid = self.para.get("catalog_guid")
  29 + table_type = self.para.get("table_type")
  30 +
  31 + start_time = self.para.get("start_time")
  32 + end_time = self.para.get("end_time")
  33 +
  34 +
  35 +
  36 + recursion = int(self.para.get("recursion","0"))
  37 +
  38 + sort = int(self.para.get("sort","1"))
  39 + if sort.__eq__(1):
  40 + tables = Table.query.order_by(Table.update_time.desc())
  41 + else:
  42 + tables = Table.query.order_by(Table.name)
  43 +
  44 + if database_guid:
  45 + tables = tables.filter_by(database_guid=database_guid)
  46 + if catalog_guid:
  47 +
  48 + if recursion.__eq__(0):
  49 + tables = tables.filter_by(catalog_guid=catalog_guid)
  50 + else:
  51 + #所有子目录id
  52 + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all()]
  53 + tables = tables.filter(Table.catalog_guid.in_(catalog_guids))
  54 +
  55 + if table_type:
  56 + tables = tables.filter_by(table_type=table_type)
  57 + if start_time and end_time:
  58 + tables = tables.filter(Table.create_time.between(start_time, end_time))
  59 +
  60 + # 并集
  61 + if alias and name:
  62 + tables = tables.filter(or_(Table.alias.like("%" + alias + "%") , Table.name.like("%" + name + "%")))
  63 + else:
  64 + if alias:
  65 + tables = tables.filter(Table.alias.like("%" + alias + "%"))
  66 + if name:
  67 + tables = tables.filter(Table.name.like("%" + name + "%"))
  68 + res["data"]={}
  69 + res["data"]["count"] = tables.count()
  70 + tables = tables.limit(page_size).offset(page_index * page_size).all()
  71 + res["data"]["list"]=[]
  72 +
  73 + for t in tables:
  74 + res["data"]["list"].append(ModelVisitor.table_to_json(t))
  75 +
  76 + res["result"] = True
  77 + except Exception as e:
  78 + raise e
  79 + return res
  80 +
  81 +
  82 +
  83 +
  84 +
  85 + api_doc={
  86 + "tags":["管理接口"],
  87 + "parameters":[
  88 + {"name": "page_index",
  89 + "in": "formData",
  90 + "type": "int",
  91 + "description": "页"},
  92 + {"name": "page_size",
  93 + "in": "formData",
  94 + "type": "int",
  95 + "description": "页大小"},
  96 + {"name": "alias",
  97 + "in": "formData",
  98 + "type": "string",
  99 + "description": "表别名"},
  100 + {"name": "name",
  101 + "in": "formData",
  102 + "type": "string",
  103 + "description": "表名"},
  104 + {"name": "table_type",
  105 + "in": "formData",
  106 + "type": "int",
  107 + "description": "点线面123","enum":[1,2,3]},
  108 + {"name": "database_guid",
  109 + "in": "formData",
  110 + "type": "string",
  111 + "description": "数据库guid"},
  112 + {"name": "catalog_guid",
  113 + "in": "formData",
  114 + "type": "string",
  115 + "description": "目录guid"},
  116 + {"name": "recursion",
  117 + "in": "formData",
  118 + "type": "int",
  119 + "description": "是否递归目录,0不递归,1递归","enum":[0,1]},
  120 + {"name": "start_time",
  121 + "in": "formData",
  122 + "type": "string",
  123 + "description": "开始时间 2020-12-12 00:00:00"},
  124 + {"name": "end_time",
  125 + "in": "formData",
  126 + "type": "string",
  127 + "description": "结束时间"},
  128 + {"name": "sort",
  129 + "in": "formData",
  130 + "type": "int",
  131 + "description": "排序","enum":[1,2]},
  132 + ],
  133 + "responses":{
  134 + 200:{
  135 + "schema":{
  136 + "properties":{
  137 + }
  138 + }
  139 + }
  140 + }
  141 + }
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +
  7 +import traceback
  8 +from app.models import Table,Database,DES,Columns,db,TableVacuate
  9 +
  10 +from osgeo.ogr import DataSource,FeatureDefn,FieldDefn,Layer
  11 +
  12 +import datetime
  13 +import uuid
  14 +
  15 +from sqlalchemy.orm import Session
  16 +
  17 +from app.util.component.PGUtil import PGUtil
  18 +from app.util.component.ModelVisitor import ModelVisitor
  19 +from app.util.component.StructuredPrint import StructurePrint
  20 +from app.util.component.ApiTemplate import ApiTemplate
  21 +from app.util.component.GeometryAdapter import GeometryAdapter
  22 +
  23 +class Api(ApiTemplate):
  24 + api_name = "数据刷新"
  25 + def process(self):
  26 +
  27 + res = {}
  28 + pg_ds =None
  29 + data_session=None
  30 + this_time = datetime.datetime.now()
  31 + try:
  32 +
  33 + database_guid = self.para.get("database_guid")
  34 + database = Database.query.filter_by(guid=database_guid).one_or_none()
  35 +
  36 + if not database:
  37 + raise Exception("数据库不存在!")
  38 + spatial_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type!=0).all()
  39 + spatial_tables_names = [table.name for table in spatial_tables]
  40 + db_tables_names = []
  41 +
  42 +
  43 + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri))
  44 + # 更新空间表
  45 + # 增加表
  46 + self.add_spatail_table(database, pg_ds, spatial_tables_names, db_tables_names, this_time)
  47 +
  48 + # 删除/修改表
  49 + self.edit_spatial_table(pg_ds, spatial_tables, db_tables_names, this_time)
  50 +
  51 + # 处理抽稀表
  52 + self.deal_vacuate_table(pg_ds,database.guid)
  53 +
  54 +
  55 + # 空间表处理完毕
  56 + db.session.commit()
  57 +
  58 +
  59 + # 注册普通表
  60 + # 实体库连接
  61 + data_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri))
  62 +
  63 + # 空间表
  64 + spatial_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type!=0).all()
  65 + spatial_tables_names = [table.name for table in spatial_tables]
  66 +
  67 +
  68 + #原有普通表
  69 + common_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type==0).all()
  70 + origin_common_tables_name = [table.name for table in common_tables]
  71 +
  72 +
  73 + # 现有普通表
  74 + real_common_tables_name =[]
  75 + # 只注册public中的表
  76 + common_result = data_session.execute(
  77 + "select relname as tabname from pg_class c where relkind = 'r' and relnamespace=2200 and relname not like 'pg_%' and relname not like 'sql_%' order by relname").fetchall()
  78 + for re in common_result:
  79 + table_name = re[0]
  80 + if table_name not in spatial_tables_names and (not table_name.__contains__("_vacuate_")):
  81 + real_common_tables_name.append(table_name)
  82 +
  83 + # 增加新普通表
  84 +
  85 + self.add_common_table(data_session, database_guid, real_common_tables_name, origin_common_tables_name,
  86 + this_time)
  87 +
  88 +
  89 + #删除、修改普通表
  90 + self.edit_common_table(data_session, database_guid, real_common_tables_name, origin_common_tables_name,
  91 + this_time)
  92 +
  93 + db.session.commit()
  94 + res["msg"] = "刷新数据成功!"
  95 + res["result"] = True
  96 +
  97 + except Exception as e:
  98 + raise e
  99 +
  100 + finally:
  101 + if pg_ds:
  102 + pg_ds.Destroy()
  103 + if data_session:
  104 + data_session.close()
  105 +
  106 + return res
  107 +
  108 +
  109 + def add_spatail_table(self,database,pg_ds,spatial_tables_names,db_tables_names,this_time):
  110 + # 更新空间表
  111 + # 增加表
  112 +
  113 + for i in range(pg_ds.GetLayerCount()):
  114 + layer: Layer = pg_ds.GetLayer(i)
  115 + geom_column = layer.GetGeometryColumn()
  116 + db_tables_names.append(layer.GetName())
  117 + if not geom_column:
  118 + continue
  119 + if layer.GetName() not in spatial_tables_names:
  120 + l_name = layer.GetName()
  121 +
  122 + # 只注册public的空间表,其他表空间的表名会有.
  123 + if layer.GetName().__contains__("."):
  124 + continue
  125 +
  126 + if layer.GetName().__contains__("_vacuate_"):
  127 + continue
  128 +
  129 + # 范围统计和数量统计以100w为界限
  130 + query_count_layer: Layer = pg_ds.ExecuteSQL(
  131 + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format(
  132 + l_name))
  133 + feature_count = query_count_layer.GetFeature(0).GetField("ec")
  134 + # 要素少于100w可以精确统计
  135 + if feature_count < 1000000:
  136 + feature_count = layer.GetFeatureCount()
  137 + ext = layer.GetExtent()
  138 + else:
  139 + query_ext_layer: Layer = pg_ds.ExecuteSQL(
  140 + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name,
  141 + layer.GetGeometryColumn()))
  142 + ext = query_ext_layer.GetExtent()
  143 + if ext[0] < 360:
  144 + ext = [round(e, 6) for e in ext]
  145 + else:
  146 + ext = [round(e, 2) for e in ext]
  147 + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3])
  148 +
  149 + StructurePrint.print("空间表增加!")
  150 +
  151 + table_guid = uuid.uuid1().__str__()
  152 + table = Table(guid=table_guid,
  153 + database_guid=database.guid,
  154 + alias=layer.GetName(),
  155 + name=layer.GetName(), create_time=this_time, update_time=this_time,
  156 + table_type=GeometryAdapter.get_table_type(layer.GetGeomType()),
  157 + extent=extent,
  158 + feature_count=feature_count
  159 + )
  160 + db.session.add(table)
  161 +
  162 + feature_defn: FeatureDefn = layer.GetLayerDefn()
  163 +
  164 + for i in range(feature_defn.GetFieldCount()):
  165 + field_defn: FieldDefn = feature_defn.GetFieldDefn(i)
  166 + field_name = field_defn.GetName().lower()
  167 + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__(
  168 + "") else field_defn.GetAlternativeName()
  169 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid,
  170 + name=field_name, alias=field_alias, create_time=this_time, update_time=this_time)
  171 + db.session.add(column)
  172 +
  173 + def deal_vacuate_table(self,pg_ds,database_guid):
  174 +
  175 +
  176 + for i in range(pg_ds.GetLayerCount()):
  177 + layer: Layer = pg_ds.GetLayer(i)
  178 + geom_column = layer.GetGeometryColumn()
  179 +
  180 + if not geom_column:
  181 + continue
  182 +
  183 +
  184 +
  185 + if layer.GetName().__contains__("_vacuate_"):
  186 + l_name = layer.GetName()
  187 +
  188 +
  189 + base_layer_name = l_name.split("_vacuate_")[0]
  190 + level = l_name.split("_")[-2]
  191 +
  192 + pixel_distance_str: str ="0"
  193 + try:
  194 + pixel_distance_str: str = l_name.split("_")[-1]
  195 + if pixel_distance_str.startswith("0"):
  196 + pixel_distance_str = "0.{}".format(pixel_distance_str)
  197 + except:
  198 + pass
  199 +
  200 + base_table =Table.query.filter_by(name=base_layer_name,database_guid=database_guid).one_or_none()
  201 + if base_table:
  202 + if not TableVacuate.query.filter_by(table_guid=base_table.guid,name=l_name).one_or_none():
  203 + table_vacuate = TableVacuate(guid=uuid.uuid1().__str__(),
  204 + table_guid=base_table.guid,
  205 + level=level,
  206 + name=l_name,
  207 + pixel_distance=float(pixel_distance_str))
  208 + db.session.add(table_vacuate)
  209 + else:
  210 + kk=1
  211 +
  212 +
  213 +
  214 +
  215 + def edit_spatial_table(self,pg_ds,spatial_tables,db_tables_names,this_time):
  216 +
  217 + for table in spatial_tables:
  218 +
  219 + # 删除表
  220 + if table.name not in db_tables_names:
  221 + StructurePrint.print("空间表减少!")
  222 + db.session.delete(table)
  223 + # 修改表
  224 + else:
  225 + layer: Layer = pg_ds.GetLayerByName(table.name)
  226 + l_name = layer.GetName()
  227 +
  228 + # 只注册public的空间表,其他表空间的表名会有.
  229 + if layer.GetName().__contains__("."):
  230 + continue
  231 +
  232 + if layer.GetName().__contains__("_vacuate_"):
  233 + continue
  234 +
  235 + columns = table.relate_columns
  236 + columns_names = [column.name for column in columns]
  237 + feature_defn: FeatureDefn = layer.GetLayerDefn()
  238 + db_columns_names = []
  239 +
  240 + # 增加列
  241 + for i in range(feature_defn.GetFieldCount()):
  242 + field_defn: FieldDefn = feature_defn.GetFieldDefn(i)
  243 + field_name = field_defn.GetName().lower()
  244 + db_columns_names.append(field_name)
  245 +
  246 + if field_name not in columns_names:
  247 + StructurePrint.print("{}空间表属性增加!".format(table.name))
  248 + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__(
  249 + "") else field_defn.GetAlternativeName()
  250 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table.guid,
  251 + name=field_name, alias=field_alias, create_time=this_time,
  252 + update_time=this_time)
  253 + db.session.add(column)
  254 +
  255 + # 删除列
  256 + for column in columns:
  257 + if column.name not in db_columns_names:
  258 + StructurePrint.print("{}空间表属性减少!".format(table.name))
  259 + db.session.delete(column)
  260 +
  261 +
  262 +
  263 + # 范围统计和数量统计以100w为界限
  264 + query_count_layer: Layer = pg_ds.ExecuteSQL(
  265 + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format(
  266 + l_name))
  267 + feature_count = query_count_layer.GetFeature(0).GetField("ec")
  268 + # 要素少于100w可以精确统计
  269 + if feature_count < 1000000:
  270 + feature_count = layer.GetFeatureCount()
  271 + ext = layer.GetExtent()
  272 + else:
  273 + query_ext_layer: Layer = pg_ds.ExecuteSQL(
  274 + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name,
  275 + layer.GetGeometryColumn()))
  276 + ext = query_ext_layer.GetExtent()
  277 + if ext[0] < 360:
  278 + ext = [round(e, 6) for e in ext]
  279 + else:
  280 + ext = [round(e, 2) for e in ext]
  281 + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3])
  282 +
  283 + # 修改要素量
  284 + if not table.feature_count.__eq__(feature_count):
  285 + StructurePrint.print("{}空间表要素!".format(table.name))
  286 + Table.query.filter_by(guid=table.guid).update({"feature_count": feature_count,
  287 + "extent": extent})
  288 +
  289 +
  290 + def add_common_table(self,data_session,database_guid,real_common_tables_name,origin_common_tables_name,this_time):
  291 + for table_name in real_common_tables_name:
  292 + if table_name not in origin_common_tables_name:
  293 + StructurePrint.print("{}非空间表增加!".format(table_name))
  294 + table_guid = uuid.uuid1().__str__()
  295 + count = data_session.execute('select count(*) from "{}"'.format(table_name)).fetchone()[0]
  296 +
  297 + table = Table(guid=table_guid,
  298 + database_guid=database_guid,
  299 + name=table_name, create_time=this_time, update_time=this_time,
  300 + table_type=0,
  301 + feature_count=count
  302 + )
  303 +
  304 + db.session.add(table)
  305 +
  306 + sql = '''
  307 + SELECT
  308 + a.attnum,
  309 + a.attname AS field
  310 + FROM
  311 + pg_class c,
  312 + pg_attribute a,
  313 + pg_type t
  314 + WHERE
  315 + c.relname = '{}'
  316 + and a.attnum > 0
  317 + and a.attrelid = c.oid
  318 + and a.atttypid = t.oid
  319 + ORDER BY a.attnum
  320 + '''.format(table_name)
  321 +
  322 + cols = data_session.execute(sql).fetchall()
  323 + for col in cols:
  324 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid,
  325 + name=col[1], create_time=this_time, update_time=this_time)
  326 + db.session.add(column)
  327 +
  328 + # 删除不存在的表
  329 + for n in origin_common_tables_name:
  330 + if n not in real_common_tables_name:
  331 + table = Table.query.filter_by(name=n).filter_by(database_guid=database_guid).one_or_none()
  332 + if table:
  333 + db.session.delete(table)
  334 +
  335 + def edit_common_table(self,data_session,database_guid,real_common_tables_name,origin_common_tables_name,this_time):
  336 + for table_name in origin_common_tables_name:
  337 + table = Table.query.filter_by(name=table_name).filter_by(database_guid=database_guid).one_or_none()
  338 + if table:
  339 + if table_name not in real_common_tables_name:
  340 + StructurePrint.print("{}非空间表减少!".format(table_name))
  341 + db.session.delete(table)
  342 + # 修改表
  343 + else:
  344 + columns = table.relate_columns
  345 + columns_names = [column.name for column in columns]
  346 +
  347 + sql = '''
  348 + SELECT
  349 + a.attnum,
  350 + a.attname AS field
  351 + FROM
  352 + pg_class c,
  353 + pg_attribute a,
  354 + pg_type t
  355 + WHERE
  356 + c.relname = '{}'
  357 + and a.attnum > 0
  358 + and a.attrelid = c.oid
  359 + and a.atttypid = t.oid
  360 + ORDER BY a.attnum
  361 + '''.format(table_name)
  362 +
  363 + cols = data_session.execute(sql).fetchall()
  364 + real_cols_name = [col[1] for col in cols]
  365 +
  366 + # 属性增加
  367 + for col in real_cols_name:
  368 + if col not in columns_names:
  369 + StructurePrint.print("{}表要素属性增加!".format(table_name))
  370 + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table.guid,
  371 + name=col, create_time=this_time, update_time=this_time)
  372 + db.session.add(column)
  373 +
  374 + # 属性减少
  375 + for column in columns:
  376 + if column.name not in real_cols_name:
  377 + StructurePrint.print("{}表要素属性减少!".format(table_name))
  378 + db.session.delete(column)
  379 +
  380 + # 修改要素量
  381 + sql = 'select count(*) from "{}"'.format(table_name)
  382 +
  383 + count = data_session.execute(sql).fetchone()[0]
  384 + if not table.feature_count.__eq__(count):
  385 + StructurePrint.print("{}表要素变化!".format(table_name))
  386 + Table.query.filter_by(guid=table.guid).update({"feature_count": count})
  387 +
  388 +
  389 +
  390 +
  391 + api_doc={
  392 + "tags":["管理接口"],
  393 + "parameters":[
  394 + {"name": "database_guid",
  395 + "in": "formData",
  396 + "type": "string",
  397 + "description": "数据库guid",
  398 + "required":"true"},
  399 + {"name": "user",
  400 + "in": "formData",
  401 + "type": "string",
  402 + "description": "用户"}
  403 +
  404 + ],
  405 + "responses":{
  406 + 200:{
  407 + "schema":{
  408 + "properties":{
  409 + }
  410 + }
  411 + }
  412 + }
  413 +}
\ No newline at end of file
... ...
  1 +#author: 4N
  2 +#createtime: 2021/1/27
  3 +#email: nheweijun@sina.com
  4 +
  5 +
  6 +import datetime
  7 +import traceback
  8 +from app.models import Table,Database,DES
  9 +
  10 +from sqlalchemy.engine import ResultProxy
  11 +from app.util.component.ApiTemplate import ApiTemplate
  12 +from app.util.component.PGUtil import PGUtil
  13 +
  14 +class Api(ApiTemplate):
  15 + api_name = "数据浏览"
  16 + def process(self):
  17 +
  18 + res = {}
  19 + res["data"] = {}
  20 + db_session=None
  21 + try:
  22 + table_guid = self.para.get("guid")
  23 + limit = int(self.para.get("limit",50))
  24 + offset = int(self.para.get("offset", 0))
  25 + table :Table= Table.query.filter_by(guid=table_guid).one_or_none()
  26 +
  27 + if not table:
  28 + raise Exception("数据不存在!")
  29 +
  30 + database:Database = table.relate_database
  31 +
  32 + db_session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri))
  33 +
  34 + geom_col = PGUtil.get_geo_column(table.name,db_session)
  35 +
  36 + query_result : ResultProxy = db_session.execute('select * from "{}" limit {} offset {}'.format(table.name,limit,offset))
  37 +
  38 + res["data"]["count"]=PGUtil.get_table_count(table.name,db_session)
  39 +
  40 + res["data"]["list"]=[]
  41 + for row_proxy in query_result:
  42 + d = {}
  43 + for column, value in row_proxy.items():
  44 + #跳过空间列
  45 + if geom_col:
  46 + if column.__eq__(geom_col):
  47 + continue
  48 + #格式化时间列
  49 + if isinstance(value, datetime.datetime):
  50 + d[column] = value.strftime('%Y-%m-%d %H:%M:%S')
  51 + else:
  52 + d[column]=value
  53 + res["data"]["list"].append(d)
  54 + res["result"]=True
  55 + except Exception as e:
  56 + raise Exception("数据库连接失败!")
  57 + finally:
  58 + if db_session:
  59 + db_session.close()
  60 + return res
  61 +
  62 + api_doc={
  63 + "tags":["管理接口"],
  64 + "parameters":[
  65 + {"name": "guid",
  66 + "in": "formData",
  67 + "type": "string",
  68 + "description": "表guid","required":"true"},
  69 + {"name": "limit",
  70 + "in": "formData",
  71 + "type": "int"
  72 + },
  73 + {"name": "offset",
  74 + "in": "formData",
  75 + "type": "int"},
  76 + ],
  77 + "responses":{
  78 + 200:{
  79 + "schema":{
  80 + "properties":{
  81 + }
  82 + }
  83 + }
  84 + }
  85 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/5/18
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from flasgger import swag_from
  8 +from flask import Blueprint
  9 +from app.util import BlueprintApi
  10 +
  11 +from . import monitor_info
  12 +class DataManager():
  13 +
  14 + bp = Blueprint("Monitor", __name__, url_prefix="/API/Monitor")
  15 +
  16 + @staticmethod
  17 + @bp.route('/Info', methods=['GET'])
  18 + @swag_from(monitor_info.Api.api_doc)
  19 + def monitor_info():
  20 + """
  21 + 性能监控
  22 + """
  23 + return monitor_info.Api().result
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/6/11
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime, Time
  8 +from app.models import db
  9 +
  10 +class TestModel(db.Model):
  11 + '''
  12 + 数据表元数据
  13 + '''
  14 + __tablename__ = 'test_model'
  15 + guid = Column(String(256), primary_key=True)
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/7/9
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import *
  7 +
  8 +from app.util.component.ApiTemplate import ApiTemplate
  9 +from app.util.component.ModelVisitor import ModelVisitor
  10 +import psutil
  11 +
  12 +class Api(ApiTemplate):
  13 + api_name = "监控"
  14 +
  15 + def process(self):
  16 +
  17 + # 返回结果
  18 + res = {}
  19 + res["data"] = {}
  20 + try:
  21 + # 业务逻辑
  22 + cpu_count = psutil.cpu_count(False)
  23 + cpu_per = int(psutil.cpu_percent())
  24 + res["data"]["cpu"] ={"count":cpu_count,"percent":"{}%".format(cpu_per)}
  25 +
  26 +
  27 + mem_total = int(psutil.virtual_memory()[0])
  28 + mem_used = int(psutil.virtual_memory()[3])
  29 + mem_per = int(psutil.virtual_memory()[2])
  30 +
  31 + res["data"]["memory"] = {
  32 + 'total': self.format_value(mem_total),
  33 + 'used': self.format_value(mem_used),
  34 + 'percent': "{}%".format(mem_per)
  35 + }
  36 +
  37 +
  38 +
  39 + network_sent = int(psutil.net_io_counters()[0] / 8 ) # 每秒接受的kb
  40 + network_recv = int(psutil.net_io_counters()[1] / 8 )
  41 +
  42 + res["data"]["network"] = {
  43 + 'sent': self.format_value(network_sent),
  44 + 'recv': self.format_value(network_recv)
  45 + }
  46 +
  47 +
  48 + res["result"] = True
  49 + except Exception as e:
  50 + raise e
  51 + return res
  52 +
  53 + api_doc = {
  54 +
  55 + "tags": ["监控接口"],
  56 + "parameters": [
  57 + ],
  58 + "responses": {
  59 + 200: {
  60 + "schema": {
  61 + "properties": {
  62 + }
  63 + }
  64 + }
  65 + }
  66 + }
  67 +
  68 + def format_value(self,value):
  69 + if value>1024**3:
  70 + value = "{}GB".format(format(value/1024.0**3,'.1f'))
  71 + elif value>1024**2:
  72 + value = "{}MB".format(format(value / 1024.0 ** 2, '.1f'))
  73 + elif value>1024:
  74 + value = "{}KB".format(format(value / 1024.0, '.1f'))
  75 + else:
  76 + value = "{}B".format(format(value, '.1f'))
  77 + return value
  78 +
  79 +if __name__ == '__main__':
  80 + api = Api()
  81 + api.process()
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/3/1
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.util import BlueprintApi
  7 +from flasgger import swag_from
  8 +from flask import Blueprint
  9 +from . import task_list
  10 +from . import task_detail
  11 +from . import task_delete
  12 +from . import task_count
  13 +
  14 +class DataManager(BlueprintApi):
  15 +
  16 + bp = Blueprint("Task", __name__, url_prefix="/API/Task")
  17 +
  18 +
  19 + @staticmethod
  20 + @bp.route('/List', methods=['POST'])
  21 + @swag_from(task_list.Api.api_doc)
  22 + def api_task_list():
  23 + """
  24 + 任务列表
  25 + """
  26 + return task_list.Api().result
  27 +
  28 + @staticmethod
  29 + @bp.route('/Detail', methods=['POST'])
  30 + @swag_from(task_detail.Api.api_doc)
  31 + def api_task_detail():
  32 + """
  33 + 任务详情
  34 + """
  35 + return task_detail.Api().result
  36 +
  37 + @staticmethod
  38 + @bp.route('/Delete', methods=['POST'])
  39 + @swag_from(task_delete.Api.api_doc)
  40 + def task_delete():
  41 + """
  42 + 删除任务
  43 + """
  44 + return task_delete.Api().result
  45 +
  46 + @staticmethod
  47 + @bp.route('/Count', methods=['POST'])
  48 + @swag_from(task_count.Api.api_doc)
  49 + def task_count():
  50 + """
  51 + 任务统计
  52 + """
  53 + return task_count.Api().result
  54 +
  55 +
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2021/6/15
  4 +#email: nheweijun@sina.com
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/9/4
  4 +# email: nheweijun@sina.com
  5 +
  6 +from app.models import db,Task
  7 +
  8 +from sqlalchemy import func
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +
  11 +
  12 +class Api(ApiTemplate):
  13 + api_name = "任务统计"
  14 + def process(self):
  15 + res= {}
  16 + try:
  17 + tasks = db.session.query(func.count(Task.state), Task.state)
  18 + creator = self.para.get("creator")
  19 +
  20 + if creator:
  21 + tasks = tasks.filter_by(creator=creator)
  22 +
  23 + tasks = tasks.group_by(Task.state).all()
  24 + res["data"] = {}
  25 + for task in tasks:
  26 + res["data"][task[1].__str__()] = task[0]
  27 +
  28 + for status in ["-1", "0", "1", "2"]:
  29 + if not res["data"].get(status):
  30 + res["data"][status] = 0
  31 +
  32 + res["result"] = True
  33 + except Exception as e:
  34 + raise e
  35 + return res
  36 +
  37 +
  38 +
  39 + api_doc = {
  40 + "tags": ["任务接口"],
  41 + "parameters": [
  42 + {"name": "creator",
  43 + "in": "formData",
  44 + "type": "string"}
  45 + ],
  46 + "responses": {
  47 + 200: {
  48 + "schema": {
  49 + "properties": {
  50 + }
  51 + }
  52 + }
  53 + }
  54 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/9/4
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import db,Task
  7 +
  8 +
  9 +from app.util.component.ApiTemplate import ApiTemplate
  10 +
  11 +
  12 +class Api(ApiTemplate):
  13 + api_name = "删除任务"
  14 + def para_check(self):
  15 + pass
  16 +
  17 + def process(self):
  18 + res = {}
  19 + try:
  20 + task_guid = self.para.get("task_guid")
  21 + state = self.para.get("state")
  22 + creator = self.para.get("creator")
  23 +
  24 + tasks = Task.query
  25 + if task_guid:
  26 + tasks = tasks.filter_by(guid=task_guid)
  27 + if state:
  28 + state = int(state)
  29 + tasks = tasks.filter_by(state=state)
  30 + if creator:
  31 + tasks = tasks.filter_by(creator=creator)
  32 + tasks = tasks.all()
  33 +
  34 + if not tasks:
  35 + res["result"] = False
  36 + res["msg"] = "数据不存在!"
  37 + return res
  38 + else:
  39 + for task in tasks:
  40 + db.session.delete(task)
  41 + db.session.commit()
  42 + res["msg"] = "删除成功!"
  43 + res["result"] = True
  44 + except Exception as e:
  45 + db.session.rollback()
  46 + raise e
  47 + return res
  48 +
  49 +
  50 + api_doc = {
  51 + "tags": ["任务接口"],
  52 + "parameters": [
  53 + {"name": "task_guid",
  54 + "in": "formData",
  55 + "type": "string"},
  56 + {"name": "state",
  57 + "in": "formData",
  58 + "type": "string"},
  59 + {"name": "creator",
  60 + "in": "formData",
  61 + "type": "string"},
  62 +
  63 + ],
  64 + "responses": {
  65 + 200: {
  66 + "schema": {
  67 + "properties": {
  68 + }
  69 + }
  70 + }
  71 + }
  72 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/9/4
  4 +#email: nheweijun@sina.com
  5 +
  6 +from app.models import db,Task,Process
  7 +from app.util.component.ApiTemplate import ApiTemplate
  8 +from app.util.component.ModelVisitor import ModelVisitor
  9 +class Api(ApiTemplate):
  10 + api_name = "任务详情"
  11 + def para_check(self):
  12 + if not self.para.get("task_guid"):
  13 + raise Exception("缺乏task_guid参数。")
  14 +
  15 + def process(self):
  16 +
  17 + res = {}
  18 + try:
  19 + task_guid = self.para.get("task_guid")
  20 + task:Task = Task.query.filter_by(guid=task_guid).one_or_none()
  21 +
  22 + if not task :
  23 + raise Exception("任务不存在!")
  24 + else:
  25 + task_processes = task.relate_processes.order_by(Process.time).all()
  26 + res["data"] = ModelVisitor.objects_to_jsonarray(task_processes)
  27 +
  28 + res["result"] = True
  29 + except Exception as e:
  30 + raise e
  31 + return res
  32 +
  33 + api_doc={
  34 + "tags":["任务接口"],
  35 + "parameters":[
  36 + {"name": "task_guid",
  37 + "in": "formData",
  38 + "type": "string"},
  39 +
  40 + ],
  41 + "responses":{
  42 + 200:{
  43 + "schema":{
  44 + "properties":{
  45 + }
  46 + }
  47 + }
  48 + }
  49 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +# author: 4N
  3 +# createtime: 2020/9/4
  4 +# email: nheweijun@sina.com
  5 +import datetime
  6 +
  7 +
  8 +
  9 +from app.models import Task
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +from app.util.component.ModelVisitor import ModelVisitor
  12 +class Api(ApiTemplate):
  13 + api_name = "任务列表"
  14 + def para_check(self):
  15 + pass
  16 +
  17 + def process(self):
  18 +
  19 + res = {}
  20 + res["data"] = {}
  21 + try:
  22 + parameter = self.para
  23 + page_index = int(parameter.get("page_index", "0"))
  24 + page_size = int(parameter.get("page_size", "10"))
  25 + name = parameter.get("name")
  26 + tasks = Task.query.order_by(Task.create_time.desc())
  27 + start_time = parameter.get("start_time")
  28 + end_time = parameter.get("end_time")
  29 + creator = parameter.get("creator")
  30 +
  31 + state = parameter.get("state")
  32 +
  33 + if creator:
  34 + tasks = tasks.filter_by(creator=creator)
  35 + if start_time and end_time:
  36 + tasks = tasks.filter(Task.create_time.between(start_time, end_time))
  37 + if name:
  38 + tasks = tasks.filter(Task.name.like("%" + name + "%"))
  39 + if state is not None:
  40 + state_list = state.split(",")
  41 + state_list =[int(x) for x in state_list]
  42 +
  43 + tasks = tasks.filter(Task.state.in_(state_list))
  44 +
  45 + res["data"]["count"] = tasks.count()
  46 +
  47 + tasks = tasks.limit(page_size).offset(page_index * page_size).all()
  48 +
  49 + res["data"]["list"] = [ModelVisitor.task_to_json(task) for task in tasks]
  50 + res["result"] = True
  51 + except Exception as e:
  52 + raise e
  53 + return res
  54 +
  55 +
  56 +
  57 + api_doc={
  58 + "tags":["任务接口"],
  59 + "parameters":[
  60 + {"name": "page_index",
  61 + "in": "formData",
  62 + "type": "int",
  63 + "default": "0"},
  64 + {"name": "page_size",
  65 + "in": "formData",
  66 + "type": "int",
  67 + "default": "10"},
  68 + {"name": "start_time",
  69 + "in": "formData",
  70 + "type": "string"},
  71 + {"name": "end_time",
  72 + "in": "formData",
  73 + "type": "string"},
  74 + {"name": "name",
  75 + "in": "formData",
  76 + "type": "string"},
  77 + {"name": "creator",
  78 + "in": "formData",
  79 + "type": "string"},
  80 + {"name": "state",
  81 + "in": "formData",
  82 + "type": "string"}
  83 + ],
  84 + "responses":{
  85 + 200:{
  86 + "schema":{
  87 + "properties":{
  88 + }
  89 + }
  90 + }
  91 + }
  92 + }
\ No newline at end of file
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/8/27
  4 +#email: nheweijun@sina.com
  5 +
  6 +from flask import Blueprint,request
  7 +from app.util import BlueprintApi
  8 +from . import sample
  9 +from flasgger import swag_from
  10 +
  11 +class Template(BlueprintApi):
  12 +
  13 + bp = Blueprint("Template", __name__, url_prefix="/API/Template")
  14 +
  15 + @staticmethod
  16 + @bp.route('/Sample', methods=['GET'])
  17 + @swag_from(sample.Api.api_doc)
  18 + def api_sample():
  19 + """
  20 + 测试接口
  21 + """
  22 + return sample.Api().result
  23 +
... ...
  1 +# coding=utf-8
  2 +#author: 4N
  3 +#createtime: 2020/8/27
  4 +#email: nheweijun@sina.com
  5 +
  6 +
  7 +from flask import current_app
  8 +import os
  9 +
  10 +from app.util.component.ApiTemplate import ApiTemplate
  11 +from app.util.component.StructuredPrint import StructurePrint
  12 +
  13 +from flask import current_app
  14 +class Api(ApiTemplate):
  15 +
  16 + def para_check(self):
  17 + if self.para.get("content") is None:
  18 + raise Exception("缺乏content参数")
  19 +
  20 + def log(self):
  21 + current_app.logger.info('info log')
  22 +
  23 + def process(self):
  24 +
  25 + result=dict()
  26 + result.update(self.para)
  27 + result["root_path"] = os.path.dirname(current_app.instance_path)
  28 + return result
  29 +
  30 +
  31 + api_doc= {
  32 + "tags": ["测试接口"],
  33 + "description": "测试接口",
  34 + "parameters": [
  35 + {"name": "content",
  36 + "in": "query"}
  37 + ],
  38 + "responses": {
  39 + 200: {
  40 + "schema": {
  41 + "properties": {
  42 + "content": {
  43 + "type": "string",
  44 + "description": "The name of the user"
  45 + }
  46 + }
  47 + }
  48 + }
  49 + }
  50 + }
  51 +
  52 +
... ...
  1 +.mapboxgl-map {
  2 + font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
  3 + overflow: hidden;
  4 + position: relative;
  5 + -webkit-tap-highlight-color: rgba(0,0,0,0);
  6 +}
  7 +
  8 +.mapboxgl-canvas-container.mapboxgl-interactive,
  9 +.mapboxgl-ctrl-nav-compass {
  10 + cursor: -webkit-grab;
  11 + cursor: -moz-grab;
  12 + cursor: grab;
  13 +}
  14 +.mapboxgl-canvas-container.mapboxgl-interactive:active,
  15 +.mapboxgl-ctrl-nav-compass:active {
  16 + cursor: -webkit-grabbing;
  17 + cursor: -moz-grabbing;
  18 + cursor: grabbing;
  19 +}
  20 +
  21 +.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate {
  22 + -ms-touch-action: pan-x pan-y;
  23 + touch-action: pan-x pan-y;
  24 +}
  25 +.mapboxgl-canvas-container.mapboxgl-touch-drag-pan {
  26 + -ms-touch-action: pinch-zoom;
  27 +}
  28 +.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan {
  29 + -ms-touch-action: none;
  30 + touch-action: none;
  31 +}
  32 +.mapboxgl-ctrl-top-left,
  33 +.mapboxgl-ctrl-top-right,
  34 +.mapboxgl-ctrl-bottom-left,
  35 +.mapboxgl-ctrl-bottom-right { position:absolute; pointer-events:none; z-index:2; }
  36 +.mapboxgl-ctrl-top-left { top:0; left:0; }
  37 +.mapboxgl-ctrl-top-right { top:0; right:0; }
  38 +.mapboxgl-ctrl-bottom-left { bottom:0; left:0; }
  39 +.mapboxgl-ctrl-bottom-right { right:0; bottom:0; }
  40 +
  41 +.mapboxgl-ctrl { clear:both; pointer-events:auto }
  42 +.mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; }
  43 +.mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; }
  44 +.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; }
  45 +.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl { margin:0 10px 10px 0; float:right; }
  46 +
  47 +.mapboxgl-ctrl-group {
  48 + border-radius: 4px;
  49 + -moz-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
  50 + -webkit-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
  51 + box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
  52 + overflow: hidden;
  53 + background: #fff;
  54 +}
  55 +.mapboxgl-ctrl-group > button {
  56 + width: 30px;
  57 + height: 30px;
  58 + display: block;
  59 + padding: 0;
  60 + outline: none;
  61 + border: none;
  62 + border-bottom: 1px solid #ddd;
  63 + box-sizing: border-box;
  64 + background-color: rgba(0,0,0,0);
  65 + cursor: pointer;
  66 +}
  67 +/* https://bugzilla.mozilla.org/show_bug.cgi?id=140562 */
  68 +.mapboxgl-ctrl > button::-moz-focus-inner {
  69 + border: 0;
  70 + padding: 0;
  71 +}
  72 +.mapboxgl-ctrl > button:last-child {
  73 + border-bottom: 0;
  74 +}
  75 +.mapboxgl-ctrl > button:hover {
  76 + background-color: rgba(0,0,0,0.05);
  77 +}
  78 +.mapboxgl-ctrl-icon,
  79 +.mapboxgl-ctrl-icon > .mapboxgl-ctrl-compass-arrow {
  80 + speak: none;
  81 + -webkit-font-smoothing: antialiased;
  82 + -moz-osx-font-smoothing: grayscale;
  83 +}
  84 +.mapboxgl-ctrl-icon {
  85 + padding: 5px;
  86 +}
  87 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
  88 + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
  89 +}
  90 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
  91 + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
  92 +}
  93 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
  94 + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23333%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
  95 +}
  96 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.mapboxgl-watching {
  97 + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%2300f%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
  98 +}
  99 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-fullscreen {
  100 + background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAyMCAyMCIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjAgMjA7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkxIHIxMzcyNSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZnVsbHNjcmVlbi5zdmciPjxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTQxODUiPjxyZGY6UkRGPjxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj48ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD48ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+PGRjOnRpdGxlPjwvZGM6dGl0bGU+PC9jYzpXb3JrPjwvcmRmOlJERj48L21ldGFkYXRhPjxkZWZzCiAgICAgaWQ9ImRlZnM0MTgzIiAvPjxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTQ3MSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI2OTUiCiAgICAgaWQ9Im5hbWVkdmlldzQxODEiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjExLjMxMzcwOCIKICAgICBpbmtzY2FwZTpjeD0iMTQuNjk4MjgiCiAgICAgaW5rc2NhcGU6Y3k9IjEwLjUyNjY4OSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNjk3IgogICAgIGlua3NjYXBlOndpbmRvdy15PSIyOTgiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1wYXRocz0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LW5vZGVzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+PGlua3NjYXBlOmdyaWQKICAgICAgIHR5cGU9Inh5Z3JpZCIKICAgICAgIGlkPSJncmlkNjA3NiIgLz48L3NvZGlwb2RpOm5hbWVkdmlldz48cGF0aAogICAgIGQ9Ik0gNSA0IEMgNC41IDQgNCA0LjUgNCA1IEwgNCA2IEwgNCA5IEwgNC41IDkgTCA1Ljc3NzM0MzggNy4yOTY4NzUgQyA2Ljc3NzEzMTkgOC4wNjAyMTMxIDcuODM1NzY1IDguOTU2NTcyOCA4Ljg5MDYyNSAxMCBDIDcuODI1NzEyMSAxMS4wNjMzIDYuNzc2MTc5MSAxMS45NTE2NzUgNS43ODEyNSAxMi43MDcwMzEgTCA0LjUgMTEgTCA0IDExIEwgNCAxNSBDIDQgMTUuNSA0LjUgMTYgNSAxNiBMIDkgMTYgTCA5IDE1LjUgTCA3LjI3MzQzNzUgMTQuMjA1MDc4IEMgOC4wNDI4OTMxIDEzLjE4Nzg4NiA4LjkzOTU0NDEgMTIuMTMzNDgxIDkuOTYwOTM3NSAxMS4wNjgzNTkgQyAxMS4wNDIzNzEgMTIuMTQ2OTkgMTEuOTQyMDkzIDEzLjIxMTIgMTIuNzA3MDMxIDE0LjIxODc1IEwgMTEgMTUuNSBMIDExIDE2IEwgMTQgMTYgTCAxNSAxNiBDIDE1LjUgMTYgMTYgMTUuNSAxNiAxNSBMIDE2IDE0IEwgMTYgMTEgTCAxNS41IDExIEwgMTQuMjA1MDc4IDEyLjcyNjU2MiBDIDEzLjE3Nzk4NSAxMS45NDk2MTcgMTIuMTEyNzE4IDExLjA0MzU3NyAxMS4wMzcxMDkgMTAuMDA5NzY2IEMgMTIuMTUxODU2IDguOTgxMDYxIDEzLjIyNDM0NSA4LjA3OTg2MjQgMTQuMjI4NTE2IDcuMzA0Njg3NSBMIDE1LjUgOSBMIDE2IDkgTCAxNiA1IEMgMTYgNC41IDE1LjUgNCAxNSA0IEwgMTEgNCBMIDExIDQuNSBMIDEyLjcwMzEyNSA1Ljc3NzM0MzggQyAxMS45MzI2NDcgNi43ODY0ODM0IDExLjAyNjY5MyA3Ljg1NTQ3MTIgOS45NzA3MDMxIDguOTE5OTIxOSBDIDguOTU4NDczOSA3LjgyMDQ5NDMgOC4wNjk4NzY3IDYuNzYyNzE4OCA3LjMwNDY4NzUgNS43NzE0ODQ0IEwgOSA0LjUgTCA5IDQgTCA2IDQgTCA1IDQgeiAiCiAgICAgaWQ9InBhdGg0MTY5IiAvPjwvc3ZnPg==");
  101 +}
  102 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-shrink {
  103 + background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAyMCAyMCIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjAgMjA7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkxIHIxMzcyNSIKICAgc29kaXBvZGk6ZG9jbmFtZT0ic2hyaW5rLnN2ZyI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhMTkiPjxyZGY6UkRGPjxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj48ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD48ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+PGRjOnRpdGxlPjwvZGM6dGl0bGU+PC9jYzpXb3JrPjwvcmRmOlJERj48L21ldGFkYXRhPjxkZWZzCiAgICAgaWQ9ImRlZnMxNyIgLz48c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBndWlkZXRvbGVyYW5jZT0iMTAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjIwMjEiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iOTA4IgogICAgIGlkPSJuYW1lZHZpZXcxNSIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMSIKICAgICBpbmtzY2FwZTpjeD0iNC45NTAxMDgyIgogICAgIGlua3NjYXBlOmN5PSIxMC44NTQ3NDciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOnNuYXAtYmJveC1lZGdlLW1pZHBvaW50cz0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LW5vZGVzPSJ0cnVlIgogICAgIGlua3NjYXBlOnNuYXAtYmJveC1taWRwb2ludHM9InRydWUiCiAgICAgaW5rc2NhcGU6b2JqZWN0LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+PGlua3NjYXBlOmdyaWQKICAgICAgIHR5cGU9Inh5Z3JpZCIKICAgICAgIGlkPSJncmlkNDE0NyIgLz48L3NvZGlwb2RpOm5hbWVkdmlldz48cGF0aAogICAgIHN0eWxlPSJmaWxsOiMwMDAwMDAiCiAgICAgZD0iTSA0LjI0MjE4NzUgMy40OTIxODc1IEEgMC43NTAwNzUgMC43NTAwNzUgMCAwIDAgMy43MTg3NSA0Ljc4MTI1IEwgNS45NjQ4NDM4IDcuMDI3MzQzOCBMIDQgOC41IEwgNCA5IEwgOCA5IEMgOC41MDAwMDEgOC45OTk5OTg4IDkgOC40OTk5OTkyIDkgOCBMIDkgNCBMIDguNSA0IEwgNy4wMTc1NzgxIDUuOTU1MDc4MSBMIDQuNzgxMjUgMy43MTg3NSBBIDAuNzUwMDc1IDAuNzUwMDc1IDAgMCAwIDQuMjQyMTg3NSAzLjQ5MjE4NzUgeiBNIDE1LjczNDM3NSAzLjQ5MjE4NzUgQSAwLjc1MDA3NSAwLjc1MDA3NSAwIDAgMCAxNS4yMTg3NSAzLjcxODc1IEwgMTIuOTg0Mzc1IDUuOTUzMTI1IEwgMTEuNSA0IEwgMTEgNCBMIDExIDggQyAxMSA4LjQ5OTk5OTIgMTEuNDk5OTk5IDguOTk5OTk4OCAxMiA5IEwgMTYgOSBMIDE2IDguNSBMIDE0LjAzNTE1NiA3LjAyNzM0MzggTCAxNi4yODEyNSA0Ljc4MTI1IEEgMC43NTAwNzUgMC43NTAwNzUgMCAwIDAgMTUuNzM0Mzc1IDMuNDkyMTg3NSB6IE0gNCAxMSBMIDQgMTEuNSBMIDUuOTY0ODQzOCAxMi45NzI2NTYgTCAzLjcxODc1IDE1LjIxODc1IEEgMC43NTEzMDA5NiAwLjc1MTMwMDk2IDAgMSAwIDQuNzgxMjUgMTYuMjgxMjUgTCA3LjAyNzM0MzggMTQuMDM1MTU2IEwgOC41IDE2IEwgOSAxNiBMIDkgMTIgQyA5IDExLjUwMDAwMSA4LjUwMDAwMSAxMS4wMDAwMDEgOCAxMSBMIDQgMTEgeiBNIDEyIDExIEMgMTEuNDk5OTk5IDExLjAwMDAwMSAxMSAxMS41MDAwMDEgMTEgMTIgTCAxMSAxNiBMIDExLjUgMTYgTCAxMi45NzI2NTYgMTQuMDM1MTU2IEwgMTUuMjE4NzUgMTYuMjgxMjUgQSAwLjc1MTMwMDk2IDAuNzUxMzAwOTYgMCAxIDAgMTYuMjgxMjUgMTUuMjE4NzUgTCAxNC4wMzUxNTYgMTIuOTcyNjU2IEwgMTYgMTEuNSBMIDE2IDExIEwgMTIgMTEgeiAiCiAgICAgaWQ9InBhdGg3IiAvPjwvc3ZnPg==");
  104 +}
  105 +.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > .mapboxgl-ctrl-compass-arrow {
  106 + width: 20px;
  107 + height: 20px;
  108 + margin: 5px;
  109 + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E");
  110 + background-repeat: no-repeat;
  111 + display: inline-block;
  112 +}
  113 +
  114 +a.mapboxgl-ctrl-logo {
  115 + width: 85px;
  116 + height: 21px;
  117 + margin: 0 0 -3px -3px;
  118 + display: block;
  119 + background-repeat: no-repeat;
  120 + cursor: pointer;
  121 + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiAgIHZpZXdCb3g9IjAgMCA4NC40OSAyMSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgODQuNDkgMjE7IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik04My4yNSwxNC4yNmMwLDAuMTItMC4wOSwwLjIxLTAuMjEsMC4yMWgtMS42MWMtMC4xMywwLTAuMjQtMC4wNi0wLjMtMC4xN2wtMS40NC0yLjM5bC0xLjQ0LDIuMzkgICAgYy0wLjA2LDAuMTEtMC4xOCwwLjE3LTAuMywwLjE3aC0xLjYxYy0wLjA0LDAtMC4wOC0wLjAxLTAuMTItMC4wM2MtMC4wOS0wLjA2LTAuMTMtMC4xOS0wLjA2LTAuMjhsMCwwbDIuNDMtMy42OEw3Ni4yLDYuODQgICAgYy0wLjAyLTAuMDMtMC4wMy0wLjA3LTAuMDMtMC4xMmMwLTAuMTIsMC4wOS0wLjIxLDAuMjEtMC4yMWgxLjYxYzAuMTMsMCwwLjI0LDAuMDYsMC4zLDAuMTdsMS40MSwyLjM2bDEuNC0yLjM1ICAgIGMwLjA2LTAuMTEsMC4xOC0wLjE3LDAuMy0wLjE3SDgzYzAuMDQsMCwwLjA4LDAuMDEsMC4xMiwwLjAzYzAuMDksMC4wNiwwLjEzLDAuMTksMC4wNiwwLjI4bDAsMGwtMi4zNywzLjYzbDIuNDMsMy42NyAgICBDODMuMjQsMTQuMTgsODMuMjUsMTQuMjIsODMuMjUsMTQuMjZ6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNjYuMjQsOS41OWMtMC4zOS0xLjg4LTEuOTYtMy4yOC0zLjg0LTMuMjhjLTEuMDMsMC0yLjAzLDAuNDItMi43MywxLjE4VjMuNTFjMC0wLjEzLTAuMS0wLjIzLTAuMjMtMC4yM2gtMS40ICAgIGMtMC4xMywwLTAuMjMsMC4xMS0wLjIzLDAuMjN2MTAuNzJjMCwwLjEzLDAuMSwwLjIzLDAuMjMsMC4yM2gxLjRjMC4xMywwLDAuMjMtMC4xMSwwLjIzLTAuMjNWMTMuNWMwLjcxLDAuNzUsMS43LDEuMTgsMi43MywxLjE4ICAgIGMxLjg4LDAsMy40NS0xLjQxLDMuODQtMy4yOUM2Ni4zNywxMC43OSw2Ni4zNywxMC4xOCw2Ni4yNCw5LjU5TDY2LjI0LDkuNTl6IE02Mi4wOCwxM2MtMS4zMiwwLTIuMzktMS4xMS0yLjQxLTIuNDh2LTAuMDYgICAgYzAuMDItMS4zOCwxLjA5LTIuNDgsMi40MS0yLjQ4czIuNDIsMS4xMiwyLjQyLDIuNTFTNjMuNDEsMTMsNjIuMDgsMTN6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNzEuNjcsNi4zMmMtMS45OC0wLjAxLTMuNzIsMS4zNS00LjE2LDMuMjljLTAuMTMsMC41OS0wLjEzLDEuMTksMCwxLjc3YzAuNDQsMS45NCwyLjE3LDMuMzIsNC4xNywzLjMgICAgYzIuMzUsMCw0LjI2LTEuODcsNC4yNi00LjE5Uzc0LjA0LDYuMzIsNzEuNjcsNi4zMnogTTcxLjY1LDEzLjAxYy0xLjMzLDAtMi40Mi0xLjEyLTIuNDItMi41MXMxLjA4LTIuNTIsMi40Mi0yLjUyICAgIGMxLjMzLDAsMi40MiwxLjEyLDIuNDIsMi41MVM3Mi45OSwxMyw3MS42NSwxMy4wMUw3MS42NSwxMy4wMXoiLz4gIDxwYXRoIGNsYXNzPSJzdDEiIHN0eWxlPSJvcGFjaXR5OjAuMzU7IGVuYWJsZS1iYWNrZ3JvdW5kOm5ldzsiIGQ9Ik02Mi4wOCw3Ljk4Yy0xLjMyLDAtMi4zOSwxLjExLTIuNDEsMi40OHYwLjA2QzU5LjY4LDExLjksNjAuNzUsMTMsNjIuMDgsMTNzMi40Mi0xLjEyLDIuNDItMi41MSAgICBTNjMuNDEsNy45OCw2Mi4wOCw3Ljk4eiBNNjIuMDgsMTEuNzZjLTAuNjMsMC0xLjE0LTAuNTYtMS4xNy0xLjI1di0wLjA0YzAuMDEtMC42OSwwLjU0LTEuMjUsMS4xNy0xLjI1ICAgIGMwLjYzLDAsMS4xNywwLjU3LDEuMTcsMS4yN0M2My4yNCwxMS4yLDYyLjczLDExLjc2LDYyLjA4LDExLjc2eiIvPiAgPHBhdGggY2xhc3M9InN0MSIgc3R5bGU9Im9wYWNpdHk6MC4zNTsgZW5hYmxlLWJhY2tncm91bmQ6bmV3OyIgZD0iTTcxLjY1LDcuOThjLTEuMzMsMC0yLjQyLDEuMTItMi40MiwyLjUxUzcwLjMyLDEzLDcxLjY1LDEzczIuNDItMS4xMiwyLjQyLTIuNTFTNzIuOTksNy45OCw3MS42NSw3Ljk4eiAgICAgTTcxLjY1LDExLjc2Yy0wLjY0LDAtMS4xNy0wLjU3LTEuMTctMS4yN2MwLTAuNywwLjUzLTEuMjYsMS4xNy0xLjI2czEuMTcsMC41NywxLjE3LDEuMjdDNzIuODIsMTEuMjEsNzIuMjksMTEuNzYsNzEuNjUsMTEuNzZ6IiAgICAvPiAgPHBhdGggY2xhc3M9InN0MCIgc3R5bGU9Im9wYWNpdHk6MC45OyBmaWxsOiAjRkZGRkZGOyBlbmFibGUtYmFja2dyb3VuZDogbmV3OyIgZD0iTTQ1Ljc0LDYuNTNoLTEuNGMtMC4xMywwLTAuMjMsMC4xMS0wLjIzLDAuMjN2MC43M2MtMC43MS0wLjc1LTEuNy0xLjE4LTIuNzMtMS4xOCAgICBjLTIuMTcsMC0zLjk0LDEuODctMy45NCw0LjE5czEuNzcsNC4xOSwzLjk0LDQuMTljMS4wNCwwLDIuMDMtMC40MywyLjczLTEuMTl2MC43M2MwLDAuMTMsMC4xLDAuMjMsMC4yMywwLjIzaDEuNCAgICBjMC4xMywwLDAuMjMtMC4xMSwwLjIzLTAuMjNWNi43NGMwLTAuMTItMC4wOS0wLjIyLTAuMjItMC4yMkM0NS43NSw2LjUzLDQ1Ljc1LDYuNTMsNDUuNzQsNi41M3ogTTQ0LjEyLDEwLjUzICAgIEM0NC4xMSwxMS45LDQzLjAzLDEzLDQxLjcxLDEzcy0yLjQyLTEuMTItMi40Mi0yLjUxczEuMDgtMi41MiwyLjQtMi41MmMxLjMzLDAsMi4zOSwxLjExLDIuNDEsMi40OEw0NC4xMiwxMC41M3oiLz4gIDxwYXRoIGNsYXNzPSJzdDEiIHN0eWxlPSJvcGFjaXR5OjAuMzU7IGVuYWJsZS1iYWNrZ3JvdW5kOm5ldzsiIGQ9Ik00MS43MSw3Ljk4Yy0xLjMzLDAtMi40MiwxLjEyLTIuNDIsMi41MVM0MC4zNywxMyw0MS43MSwxM3MyLjM5LTEuMTEsMi40MS0yLjQ4di0wLjA2ICAgIEM0NC4xLDkuMDksNDMuMDMsNy45OCw0MS43MSw3Ljk4eiBNNDAuNTUsMTAuNDljMC0wLjcsMC41Mi0xLjI3LDEuMTctMS4yN2MwLjY0LDAsMS4xNCwwLjU2LDEuMTcsMS4yNXYwLjA0ICAgIGMtMC4wMSwwLjY4LTAuNTMsMS4yNC0xLjE3LDEuMjRDNDEuMDgsMTEuNzUsNDAuNTUsMTEuMTksNDAuNTUsMTAuNDl6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNTIuNDEsNi4zMmMtMS4wMywwLTIuMDMsMC40Mi0yLjczLDEuMThWNi43NWMwLTAuMTMtMC4xLTAuMjMtMC4yMy0wLjIzaC0xLjRjLTAuMTMsMC0wLjIzLDAuMTEtMC4yMywwLjIzICAgIHYxMC43MmMwLDAuMTMsMC4xLDAuMjMsMC4yMywwLjIzaDEuNGMwLjEzLDAsMC4yMy0wLjEsMC4yMy0wLjIzVjEzLjVjMC43MSwwLjc1LDEuNywxLjE4LDIuNzQsMS4xOGMyLjE3LDAsMy45NC0xLjg3LDMuOTQtNC4xOSAgICBTNTQuNTgsNi4zMiw1Mi40MSw2LjMyeiBNNTIuMDgsMTMuMDFjLTEuMzIsMC0yLjM5LTEuMTEtMi40Mi0yLjQ4di0wLjA3YzAuMDItMS4zOCwxLjA5LTIuNDksMi40LTIuNDljMS4zMiwwLDIuNDEsMS4xMiwyLjQxLDIuNTEgICAgUzUzLjQsMTMsNTIuMDgsMTMuMDFMNTIuMDgsMTMuMDF6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNNTIuMDgsNy45OGMtMS4zMiwwLTIuMzksMS4xMS0yLjQyLDIuNDh2MC4wNmMwLjAzLDEuMzgsMS4xLDIuNDgsMi40MiwyLjQ4czIuNDEtMS4xMiwyLjQxLTIuNTEgICAgUzUzLjQsNy45OCw1Mi4wOCw3Ljk4eiBNNTIuMDgsMTEuNzZjLTAuNjMsMC0xLjE0LTAuNTYtMS4xNy0xLjI1di0wLjA0YzAuMDEtMC42OSwwLjU0LTEuMjUsMS4xNy0xLjI1YzAuNjMsMCwxLjE3LDAuNTgsMS4xNywxLjI3ICAgIFM1Mi43MiwxMS43Niw1Mi4wOCwxMS43NnoiLz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik0zNi4wOCwxNC4yNGMwLDAuMTMtMC4xLDAuMjMtMC4yMywwLjIzaC0xLjQxYy0wLjEzLDAtMC4yMy0wLjExLTAuMjMtMC4yM1Y5LjY4YzAtMC45OC0wLjc0LTEuNzEtMS42Mi0xLjcxICAgIGMtMC44LDAtMS40NiwwLjctMS41OSwxLjYybDAuMDEsNC42NmMwLDAuMTMtMC4xMSwwLjIzLTAuMjMsMC4yM2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWOS42OCAgICBjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuODUsMC0xLjU0LDAuNzktMS42LDEuOHY0LjQ4YzAsMC4xMy0wLjEsMC4yMy0wLjIzLDAuMjNoLTEuNGMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWNi43NCAgICBjMC4wMS0wLjEzLDAuMS0wLjIyLDAuMjMtMC4yMmgxLjRjMC4xMywwLDAuMjIsMC4xMSwwLjIzLDAuMjJWNy40YzAuNS0wLjY4LDEuMy0xLjA5LDIuMTYtMS4xaDAuMDNjMS4wOSwwLDIuMDksMC42LDIuNiwxLjU1ICAgIGMwLjQ1LTAuOTUsMS40LTEuNTUsMi40NC0xLjU2YzEuNjIsMCwyLjkzLDEuMjUsMi45LDIuNzhMMzYuMDgsMTQuMjR6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNODQuMzQsMTMuNTlsLTAuMDctMC4xM2wtMS45Ni0yLjk5bDEuOTQtMi45NWMwLjQ0LTAuNjcsMC4yNi0xLjU2LTAuNDEtMi4wMmMtMC4wMiwwLTAuMDMsMC0wLjA0LTAuMDEgICAgYy0wLjIzLTAuMTUtMC41LTAuMjItMC43OC0wLjIyaC0xLjYxYy0wLjU2LDAtMS4wOCwwLjI5LTEuMzcsMC43OEw3OS43Miw2LjZsLTAuMzQtMC41NkM3OS4wOSw1LjU2LDc4LjU3LDUuMjcsNzgsNS4yN2gtMS42ICAgIGMtMC42LDAtMS4xMywwLjM3LTEuMzUsMC45MmMtMi4xOS0xLjY2LTUuMjgtMS40Ny03LjI2LDAuNDVjLTAuMzUsMC4zNC0wLjY1LDAuNzItMC44OSwxLjE0Yy0wLjktMS42Mi0yLjU4LTIuNzItNC41LTIuNzIgICAgYy0wLjUsMC0xLjAxLDAuMDctMS40OCwwLjIzVjMuNTFjMC0wLjgyLTAuNjYtMS40OC0xLjQ3LTEuNDhoLTEuNGMtMC44MSwwLTEuNDcsMC42Ni0xLjQ3LDEuNDd2My43NSAgICBjLTAuOTUtMS4zNi0yLjUtMi4xOC00LjE3LTIuMTljLTAuNzQsMC0xLjQ2LDAuMTYtMi4xMiwwLjQ3Yy0wLjI0LTAuMTctMC41NC0wLjI2LTAuODQtMC4yNmgtMS40Yy0wLjQ1LDAtMC44NywwLjIxLTEuMTUsMC41NiAgICBjLTAuMDItMC4wMy0wLjA0LTAuMDUtMC4wNy0wLjA4Yy0wLjI4LTAuMy0wLjY4LTAuNDctMS4wOS0wLjQ3aC0xLjM5Yy0wLjMsMC0wLjYsMC4wOS0wLjg0LDAuMjZjLTAuNjctMC4zLTEuMzktMC40Ni0yLjEyLTAuNDYgICAgYy0xLjgzLDAtMy40MywxLTQuMzcsMi41Yy0wLjItMC40Ni0wLjQ4LTAuODktMC44My0xLjI1Yy0wLjgtMC44MS0xLjg5LTEuMjUtMy4wMi0xLjI1aC0wLjAxYy0wLjg5LDAuMDEtMS43NSwwLjMzLTIuNDYsMC44OCAgICBjLTAuNzQtMC41Ny0xLjY0LTAuODgtMi41Ny0wLjg4SDI4LjFjLTAuMjksMC0wLjU4LDAuMDMtMC44NiwwLjExYy0wLjI4LDAuMDYtMC41NiwwLjE2LTAuODIsMC4yOGMtMC4yMS0wLjEyLTAuNDUtMC4xOC0wLjctMC4xOCAgICBoLTEuNGMtMC44MiwwLTEuNDcsMC42Ni0xLjQ3LDEuNDd2Ny41YzAsMC44MiwwLjY2LDEuNDcsMS40NywxLjQ3aDEuNGMwLjgyLDAsMS40OC0wLjY2LDEuNDgtMS40OGwwLDBWOS43OSAgICBjMC4wMy0wLjM2LDAuMjMtMC41OSwwLjM2LTAuNTljMC4xOCwwLDAuMzgsMC4xOCwwLjM4LDAuNDd2NC41N2MwLDAuODIsMC42NiwxLjQ3LDEuNDcsMS40N2gxLjQxYzAuODIsMCwxLjQ3LTAuNjYsMS40Ny0xLjQ3ICAgIGwtMC4wMS00LjU3YzAuMDYtMC4zMiwwLjI1LTAuNDcsMC4zNS0wLjQ3YzAuMTgsMCwwLjM4LDAuMTgsMC4zOCwwLjQ3djQuNTdjMCwwLjgyLDAuNjYsMS40NywxLjQ3LDEuNDdoMS40MSAgICBjMC44MiwwLDEuNDctMC42NiwxLjQ3LTEuNDd2LTAuMzhjMC45NiwxLjI5LDIuNDYsMi4wNiw0LjA2LDIuMDZjMC43NCwwLDEuNDYtMC4xNiwyLjEyLTAuNDdjMC4yNCwwLjE3LDAuNTQsMC4yNiwwLjg0LDAuMjZoMS4zOSAgICBjMC4zLDAsMC42LTAuMDksMC44NC0wLjI2djIuMDFjMCwwLjgyLDAuNjYsMS40NywxLjQ3LDEuNDdoMS40YzAuODIsMCwxLjQ3LTAuNjYsMS40Ny0xLjQ3di0xLjc3YzAuNDgsMC4xNSwwLjk5LDAuMjMsMS40OSwwLjIyICAgIGMxLjcsMCwzLjIyLTAuODcsNC4xNy0yLjJ2MC41MmMwLDAuODIsMC42NiwxLjQ3LDEuNDcsMS40N2gxLjRjMC4zLDAsMC42LTAuMDksMC44NC0wLjI2YzAuNjYsMC4zMSwxLjM5LDAuNDcsMi4xMiwwLjQ3ICAgIGMxLjkyLDAsMy42LTEuMSw0LjQ5LTIuNzNjMS41NCwyLjY1LDQuOTUsMy41Myw3LjU4LDEuOThjMC4xOC0wLjExLDAuMzYtMC4yMiwwLjUzLTAuMzZjMC4yMiwwLjU1LDAuNzYsMC45MSwxLjM1LDAuOUg3OCAgICBjMC41NiwwLDEuMDgtMC4yOSwxLjM3LTAuNzhsMC4zNy0wLjYxbDAuMzcsMC42MWMwLjI5LDAuNDgsMC44MSwwLjc4LDEuMzgsMC43OGgxLjZjMC44MSwwLDEuNDYtMC42NiwxLjQ1LTEuNDYgICAgQzg0LjQ5LDE0LjAyLDg0LjQ0LDEzLjgsODQuMzQsMTMuNTlMODQuMzQsMTMuNTl6IE0zNS44NiwxNC40N2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWOS42OCAgICBjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuOCwwLTEuNDYsMC43LTEuNTksMS42MmwwLjAxLDQuNjZjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjMgICAgVjkuNjhjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuODUsMC0xLjU0LDAuNzktMS42LDEuOHY0LjQ4YzAsMC4xMy0wLjEsMC4yMy0wLjIzLDAuMjNoLTEuNGMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjMgICAgVjYuNzRjMC4wMS0wLjEzLDAuMTEtMC4yMiwwLjIzLTAuMjJoMS40YzAuMTMsMCwwLjIyLDAuMTEsMC4yMywwLjIyVjcuNGMwLjUtMC42OCwxLjMtMS4wOSwyLjE2LTEuMWgwLjAzICAgIGMxLjA5LDAsMi4wOSwwLjYsMi42LDEuNTVjMC40NS0wLjk1LDEuNC0xLjU1LDIuNDQtMS41NmMxLjYyLDAsMi45MywxLjI1LDIuOSwyLjc4bDAuMDEsNS4xNkMzNi4wOSwxNC4zNiwzNS45OCwxNC40NiwzNS44NiwxNC40NyAgICBMMzUuODYsMTQuNDd6IE00NS45NywxNC4yNGMwLDAuMTMtMC4xLDAuMjMtMC4yMywwLjIzaC0xLjRjLTAuMTMsMC0wLjIzLTAuMTEtMC4yMy0wLjIzVjEzLjVjLTAuNywwLjc2LTEuNjksMS4xOC0yLjcyLDEuMTggICAgYy0yLjE3LDAtMy45NC0xLjg3LTMuOTQtNC4xOXMxLjc3LTQuMTksMy45NC00LjE5YzEuMDMsMCwyLjAyLDAuNDMsMi43MywxLjE4VjYuNzRjMC0wLjEzLDAuMS0wLjIzLDAuMjMtMC4yM2gxLjQgICAgYzAuMTItMC4wMSwwLjIyLDAuMDgsMC4yMywwLjIxYzAsMC4wMSwwLDAuMDEsMCwwLjAydjcuNTFoLTAuMDFWMTQuMjR6IE01Mi40MSwxNC42N2MtMS4wMywwLTIuMDItMC40My0yLjczLTEuMTh2My45NyAgICBjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40Yy0wLjEzLDAtMC4yMy0wLjEtMC4yMy0wLjIzVjYuNzVjMC0wLjEzLDAuMS0wLjIyLDAuMjMtMC4yMmgxLjRjMC4xMywwLDAuMjMsMC4xMSwwLjIzLDAuMjN2MC43MyAgICBjMC43MS0wLjc2LDEuNy0xLjE4LDIuNzMtMS4xOGMyLjE3LDAsMy45NCwxLjg2LDMuOTQsNC4xOFM1NC41OCwxNC42Nyw1Mi40MSwxNC42N3ogTTY2LjI0LDExLjM5Yy0wLjM5LDEuODctMS45NiwzLjI5LTMuODQsMy4yOSAgICBjLTEuMDMsMC0yLjAyLTAuNDMtMi43My0xLjE4djAuNzNjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40Yy0wLjEzLDAtMC4yMy0wLjExLTAuMjMtMC4yM1YzLjUxYzAtMC4xMywwLjEtMC4yMywwLjIzLTAuMjMgICAgaDEuNGMwLjEzLDAsMC4yMywwLjExLDAuMjMsMC4yM3YzLjk3YzAuNzEtMC43NSwxLjctMS4xOCwyLjczLTEuMTdjMS44OCwwLDMuNDUsMS40LDMuODQsMy4yOEM2Ni4zNywxMC4xOSw2Ni4zNywxMC44LDY2LjI0LDExLjM5ICAgIEw2Ni4yNCwxMS4zOUw2Ni4yNCwxMS4zOXogTTcxLjY3LDE0LjY4Yy0yLDAuMDEtMy43My0xLjM1LTQuMTctMy4zYy0wLjEzLTAuNTktMC4xMy0xLjE5LDAtMS43N2MwLjQ0LTEuOTQsMi4xNy0zLjMxLDQuMTctMy4zICAgIGMyLjM2LDAsNC4yNiwxLjg3LDQuMjYsNC4xOVM3NC4wMywxNC42OCw3MS42NywxNC42OEw3MS42NywxNC42OHogTTgzLjA0LDE0LjQ3aC0xLjYxYy0wLjEzLDAtMC4yNC0wLjA2LTAuMy0wLjE3bC0xLjQ0LTIuMzkgICAgbC0xLjQ0LDIuMzljLTAuMDYsMC4xMS0wLjE4LDAuMTctMC4zLDAuMTdoLTEuNjFjLTAuMDQsMC0wLjA4LTAuMDEtMC4xMi0wLjAzYy0wLjA5LTAuMDYtMC4xMy0wLjE5LTAuMDYtMC4yOGwwLDBsMi40My0zLjY4ICAgIEw3Ni4yLDYuODRjLTAuMDItMC4wMy0wLjAzLTAuMDctMC4wMy0wLjEyYzAtMC4xMiwwLjA5LTAuMjEsMC4yMS0wLjIxaDEuNjFjMC4xMywwLDAuMjQsMC4wNiwwLjMsMC4xN2wxLjQxLDIuMzZsMS40MS0yLjM2ICAgIGMwLjA2LTAuMTEsMC4xOC0wLjE3LDAuMy0wLjE3aDEuNjFjMC4wNCwwLDAuMDgsMC4wMSwwLjEyLDAuMDNjMC4wOSwwLjA2LDAuMTMsMC4xOSwwLjA2LDAuMjhsMCwwbC0yLjM4LDMuNjRsMi40MywzLjY3ICAgIGMwLjAyLDAuMDMsMC4wMywwLjA3LDAuMDMsMC4xMkM4My4yNSwxNC4zOCw4My4xNiwxNC40Nyw4My4wNCwxNC40N0w4My4wNCwxNC40N0w4My4wNCwxNC40N3oiLz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik0xMC41LDEuMjRjLTUuMTEsMC05LjI1LDQuMTUtOS4yNSw5LjI1czQuMTUsOS4yNSw5LjI1LDkuMjVzOS4yNS00LjE1LDkuMjUtOS4yNSAgICBDMTkuNzUsNS4zOCwxNS42MSwxLjI0LDEwLjUsMS4yNHogTTE0Ljg5LDEyLjc3Yy0xLjkzLDEuOTMtNC43OCwyLjMxLTYuNywyLjMxYy0wLjcsMC0xLjQxLTAuMDUtMi4xLTAuMTZjMCwwLTEuMDItNS42NCwyLjE0LTguODEgICAgYzAuODMtMC44MywxLjk1LTEuMjgsMy4xMy0xLjI4YzEuMjcsMCwyLjQ5LDAuNTEsMy4zOSwxLjQyQzE2LjU5LDguMDksMTYuNjQsMTEsMTQuODksMTIuNzd6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNMTAuNS0wLjAxQzQuNy0wLjAxLDAsNC43LDAsMTAuNDlzNC43LDEwLjUsMTAuNSwxMC41UzIxLDE2LjI5LDIxLDEwLjQ5QzIwLjk5LDQuNywxNi4zLTAuMDEsMTAuNS0wLjAxeiAgICAgTTEwLjUsMTkuNzRjLTUuMTEsMC05LjI1LTQuMTUtOS4yNS05LjI1czQuMTQtOS4yNiw5LjI1LTkuMjZzOS4yNSw0LjE1LDkuMjUsOS4yNUMxOS43NSwxNS42MSwxNS42MSwxOS43NCwxMC41LDE5Ljc0eiIvPiAgPHBhdGggY2xhc3M9InN0MSIgc3R5bGU9Im9wYWNpdHk6MC4zNTsgZW5hYmxlLWJhY2tncm91bmQ6bmV3OyIgZD0iTTE0Ljc0LDYuMjVDMTIuOSw0LjQxLDkuOTgsNC4zNSw4LjIzLDYuMWMtMy4xNiwzLjE3LTIuMTQsOC44MS0yLjE0LDguODFzNS42NCwxLjAyLDguODEtMi4xNCAgICBDMTYuNjQsMTEsMTYuNTksOC4wOSwxNC43NCw2LjI1eiBNMTIuNDcsMTAuMzRsLTAuOTEsMS44N2wtMC45LTEuODdMOC44LDkuNDNsMS44Ni0wLjlsMC45LTEuODdsMC45MSwxLjg3bDEuODYsMC45TDEyLjQ3LDEwLjM0eiIgICAgLz4gIDxwb2x5Z29uIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIHBvaW50cz0iMTQuMzMsOS40MyAxMi40NywxMC4zNCAxMS41NiwxMi4yMSAxMC42NiwxMC4zNCA4LjgsOS40MyAxMC42Niw4LjUzIDExLjU2LDYuNjYgMTIuNDcsOC41MyAgICIvPjwvZz48L3N2Zz4=);
  122 +}
  123 +
  124 +.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
  125 + padding: 0 5px;
  126 + background-color: rgba(255, 255, 255, .5);
  127 + margin: 0;
  128 +}
  129 +.mapboxgl-ctrl-attrib.mapboxgl-compact {
  130 + padding-top: 2px;
  131 + padding-bottom: 2px;
  132 + margin: 0 10px 10px 10px;
  133 + position: relative;
  134 + padding-right: 24px;
  135 + background-color: #fff;
  136 + border-radius: 3px 12px 12px 3px;
  137 + visibility: hidden;
  138 +}
  139 +.mapboxgl-ctrl-attrib.mapboxgl-compact:hover {
  140 + visibility: visible;
  141 +}
  142 +.mapboxgl-ctrl-attrib.mapboxgl-compact:after {
  143 + content: '';
  144 + cursor: pointer;
  145 + position: absolute;
  146 + bottom: 0;
  147 + right: 0;
  148 + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%09%3Cpath%20fill%3D%27%23333333%27%20fill-rule%3D%27evenodd%27%20d%3D%27M4%2C10a6%2C6%200%201%2C0%2012%2C0a6%2C6%200%201%2C0%20-12%2C0%20M9%2C7a1%2C1%200%201%2C0%202%2C0a1%2C1%200%201%2C0%20-2%2C0%20M9%2C10a1%2C1%200%201%2C1%202%2C0l0%2C3a1%2C1%200%201%2C1%20-2%2C0%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
  149 + background-color: rgba(255, 255, 255, .5);
  150 + width: 24px;
  151 + height: 24px;
  152 + box-sizing: border-box;
  153 + visibility: visible;
  154 + border-radius: 12px;
  155 +}
  156 +.mapboxgl-ctrl-attrib a {
  157 + color: rgba(0,0,0,0.75);
  158 + text-decoration: none;
  159 +}
  160 +.mapboxgl-ctrl-attrib a:hover {
  161 + color: inherit;
  162 + text-decoration: underline;
  163 +}
  164 +/* stylelint-disable */
  165 +.mapboxgl-ctrl-attrib .mapbox-improve-map {
  166 + font-weight: bold;
  167 + margin-left: 2px;
  168 +}
  169 +/*stylelint-enable*/
  170 +.mapboxgl-ctrl-scale {
  171 + background-color: rgba(255,255,255,0.75);
  172 + font-size: 10px;
  173 + border-width: medium 2px 2px;
  174 + border-style: none solid solid;
  175 + border-color: #333;
  176 + padding: 0 5px;
  177 + color: #333;
  178 +}
  179 +
  180 +.mapboxgl-popup {
  181 + position: absolute;
  182 + top: 0;
  183 + left: 0;
  184 + display: -webkit-flex;
  185 + display: flex;
  186 + will-change: transform;
  187 + pointer-events: none;
  188 +}
  189 +.mapboxgl-popup-anchor-top,
  190 +.mapboxgl-popup-anchor-top-left,
  191 +.mapboxgl-popup-anchor-top-right {
  192 + -webkit-flex-direction: column;
  193 + flex-direction: column;
  194 +}
  195 +.mapboxgl-popup-anchor-bottom,
  196 +.mapboxgl-popup-anchor-bottom-left,
  197 +.mapboxgl-popup-anchor-bottom-right {
  198 + -webkit-flex-direction: column-reverse;
  199 + flex-direction: column-reverse;
  200 +}
  201 +.mapboxgl-popup-anchor-left {
  202 + -webkit-flex-direction: row;
  203 + flex-direction: row;
  204 +}
  205 +.mapboxgl-popup-anchor-right {
  206 + -webkit-flex-direction: row-reverse;
  207 + flex-direction: row-reverse;
  208 +}
  209 +.mapboxgl-popup-tip {
  210 + width: 0;
  211 + height: 0;
  212 + border: 10px solid transparent;
  213 + z-index: 1;
  214 +}
  215 +.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
  216 + -webkit-align-self: center;
  217 + align-self: center;
  218 + border-top: none;
  219 + border-bottom-color: #fff;
  220 +}
  221 +.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
  222 + -webkit-align-self: flex-start;
  223 + align-self: flex-start;
  224 + border-top: none;
  225 + border-left: none;
  226 + border-bottom-color: #fff;
  227 +}
  228 +.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
  229 + -webkit-align-self: flex-end;
  230 + align-self: flex-end;
  231 + border-top: none;
  232 + border-right: none;
  233 + border-bottom-color: #fff;
  234 +}
  235 +.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
  236 + -webkit-align-self: center;
  237 + align-self: center;
  238 + border-bottom: none;
  239 + border-top-color: #fff;
  240 +}
  241 +.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
  242 + -webkit-align-self: flex-start;
  243 + align-self: flex-start;
  244 + border-bottom: none;
  245 + border-left: none;
  246 + border-top-color: #fff;
  247 +}
  248 +.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
  249 + -webkit-align-self: flex-end;
  250 + align-self: flex-end;
  251 + border-bottom: none;
  252 + border-right: none;
  253 + border-top-color: #fff;
  254 +}
  255 +.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
  256 + -webkit-align-self: center;
  257 + align-self: center;
  258 + border-left: none;
  259 + border-right-color: #fff;
  260 +}
  261 +.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
  262 + -webkit-align-self: center;
  263 + align-self: center;
  264 + border-right: none;
  265 + border-left-color: #fff;
  266 +}
  267 +.mapboxgl-popup-close-button {
  268 + position: absolute;
  269 + right: 0;
  270 + top: 0;
  271 + border: none;
  272 + border-radius: 0 3px 0 0;
  273 + cursor: pointer;
  274 + background-color: rgba(0,0,0,0);
  275 +}
  276 +.mapboxgl-popup-close-button:hover {
  277 + background-color: rgba(0,0,0,0.05);
  278 +}
  279 +.mapboxgl-popup-content {
  280 + position: relative;
  281 + background: #fff;
  282 + border-radius: 3px;
  283 + box-shadow: 0 1px 2px rgba(0,0,0,0.10);
  284 + padding: 10px 10px 15px;
  285 + pointer-events: auto;
  286 +}
  287 +.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content {
  288 + border-top-left-radius: 0;
  289 +}
  290 +.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content {
  291 + border-top-right-radius: 0;
  292 +}
  293 +.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content {
  294 + border-bottom-left-radius: 0;
  295 +}
  296 +.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content {
  297 + border-bottom-right-radius: 0;
  298 +}
  299 +
  300 +.mapboxgl-marker {
  301 + position: absolute;
  302 + top: 0;
  303 + left: 0;
  304 + will-change: transform;
  305 +}
  306 +
  307 +.mapboxgl-crosshair,
  308 +.mapboxgl-crosshair .mapboxgl-interactive,
  309 +.mapboxgl-crosshair .mapboxgl-interactive:active {
  310 + cursor: crosshair;
  311 +}
  312 +.mapboxgl-boxzoom {
  313 + position: absolute;
  314 + top: 0;
  315 + left: 0;
  316 + width: 0;
  317 + height: 0;
  318 + background: #fff;
  319 + border: 2px dotted #202020;
  320 + opacity: 0.5;
  321 +}
  322 +
  323 +@media print {
  324 +/* stylelint-disable */
  325 + .mapbox-improve-map {
  326 + display:none;
  327 + }
  328 +/* stylelint-enable */
  329 +}
\ No newline at end of file
... ...
注册登录 后发表评论