正在显示
53 个修改的文件
包含
4705 行增加
和
0 行删除
.gitignore
0 → 100644
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/ |
Dockerfile
0 → 100644
app/__init__.py
0 → 100644
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") |
app/models.py
0 → 100644
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 | + |
app/modules/__init__.py
0 → 100644
app/modules/catalog/__init__.py
0 → 100644
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 |
app/modules/catalog/catalog_create.py
0 → 100644
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 | + } |
app/modules/catalog/catalog_delete.py
0 → 100644
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 | + } |
app/modules/catalog/catalog_edit.py
0 → 100644
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 | + } |
app/modules/catalog/catalog_next.py
0 → 100644
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 | + } |
app/modules/catalog/catalog_real_tree.py
0 → 100644
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 | + } |
app/modules/catalog/catalog_tree.py
0 → 100644
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 | + } |
app/modules/database/__init__.py
0 → 100644
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 |
app/modules/database/database_alias_check.py
0 → 100644
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 | + } |
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 | + } |
app/modules/database/database_delete.py
0 → 100644
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 | + } |
app/modules/database/database_edit.py
0 → 100644
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 | + } |
app/modules/database/database_info.py
0 → 100644
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 | + } |
app/modules/database/database_list.py
0 → 100644
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 | + } |
app/modules/database/database_register.py
0 → 100644
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 | +} |
app/modules/database/database_test.py
0 → 100644
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 | +} |
app/modules/index/__init__.py
0 → 100644
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 |
app/modules/index/get_app_name.py
0 → 100644
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 | + } |
app/modules/index/index.py
0 → 100644
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 | + } |
app/modules/index/release.py
0 → 100644
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 | + } |
app/modules/io/__init__.py
0 → 100644
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 |
app/modules/io/check_meta.py
0 → 100644
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 | + } |
app/modules/io/data_download.py
0 → 100644
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 | +} |
app/modules/io/data_entry_by_meta.py
0 → 100644
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 | + } |
app/modules/io/get_data_list.py
0 → 100644
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 | +} |
app/modules/io/get_meta.py
0 → 100644
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 | +} |
app/modules/io/util/__init__.py
0 → 100644
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 | + |
app/modules/manager/__init__.py
0 → 100644
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 |
app/modules/manager/field_edit.py
0 → 100644
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 | + } |
app/modules/manager/field_list.py
0 → 100644
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 | + } |
app/modules/manager/table_delete.py
0 → 100644
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 | + } |
app/modules/manager/table_edit.py
0 → 100644
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 | + } |
app/modules/manager/table_info.py
0 → 100644
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 | + } |
app/modules/manager/table_list.py
0 → 100644
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 | + } |
app/modules/manager/table_refresh.py
0 → 100644
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 | +} |
app/modules/manager/table_view.py
0 → 100644
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 | + } |
app/modules/monitor/__init__.py
0 → 100644
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 |
app/modules/monitor/models/__init__.py
0 → 100644
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) |
app/modules/monitor/monitor_info.py
0 → 100644
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() |
app/modules/task/__init__.py
0 → 100644
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 | + |
app/modules/task/models/__init__.py
0 → 100644
app/modules/task/task_count.py
0 → 100644
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 | + } |
app/modules/task/task_delete.py
0 → 100644
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 | + } |
app/modules/task/task_detail.py
0 → 100644
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 | + } |
app/modules/task/task_list.py
0 → 100644
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 | + } |
app/modules/template/__init__.py
0 → 100644
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 | + |
app/modules/template/sample.py
0 → 100644
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 | + |
app/static/mapbox-gl.css
0 → 100644
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 | +} |
请
注册
或
登录
后发表评论