PK XEHVh
qtiles/aboutdialog.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
import os
import ConfigParser
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ui.ui_aboutdialogbase import Ui_Dialog
import resources_rc
class AboutDialog(QDialog, Ui_Dialog):
def __init__(self):
QDialog.__init__(self)
self.setupUi(self)
self.btnHelp = self.buttonBox.button(QDialogButtonBox.Help)
self.lblLogo.setPixmap(QPixmap(':/icons/qtiles.png'))
cfg = ConfigParser.SafeConfigParser()
cfg.read(os.path.join(os.path.dirname(__file__), 'metadata.txt'))
version = cfg.get('general', 'version')
self.lblVersion.setText(self.tr('Version: %s') % version)
doc = QTextDocument()
doc.setHtml(self.getAboutText())
self.textBrowser.setDocument(doc)
self.textBrowser.setOpenExternalLinks(True)
self.buttonBox.helpRequested.connect(self.openHelp)
def reject(self):
QDialog.reject(self)
def openHelp(self):
overrideLocale = QSettings().value('locale/overrideFlag', False, type=bool)
if not overrideLocale:
localeFullName = QLocale.system().name()
else:
localeFullName = QSettings().value('locale/userLocale', '')
localeShortName = localeFullName[0:2]
if localeShortName in ['ru', 'uk']:
QDesktopServices.openUrl(QUrl('http://gis-lab.info/qa/qtiles.html'))
else:
QDesktopServices.openUrl(QUrl('http://gis-lab.info/qa/qtiles-eng.html'))
def getAboutText(self):
return self.tr('
Generate tiles from QGIS project.
'
'Plugin generates raster tiles from QGIS project corresponding '
'to '
'Slippy Map '
'specification. Output tiles can be saved in directory or as zip '
'archive.
'
'Developers: '
'(NextGIS), portions of code by '
'Andrew Naplavkov and Giovanni Allegri.
'
'Homepage: '
''
'https://github.com/nextgis/QTiles
'
'Please report bugs at '
''
'bugtracker
'
)
PK WWGB
d qtiles/MakefileUI_PATH=ui
UI_SOURCES=$(wildcard $(UI_PATH)/*.ui)
UI_FILES=$(patsubst $(UI_PATH)/%.ui, $(UI_PATH)/ui_%.py, $(UI_SOURCES))
LANG_PATH=i18n
LANG_SOURCES=$(wildcard $(LANG_PATH)/*.ts)
LANG_FILES=$(patsubst $(LANG_PATH)/%.ts, $(LANG_PATH)/%.qm, $(LANG_SOURCES))
RES_PATH=.
RES_SOURCES=$(wildcard $(RES_PATH)/*.qrc)
RES_FILES=$(patsubst $(RES_PATH)/%.qrc, $(RES_PATH)/%_rc.py, $(RES_SOURCES))
PRO_PATH=.
PRO_FILES=$(wildcard $(PRO_PATH)/*.pro)
ALL_FILES= ${RES_FILES} ${UI_FILES} ${LANG_FILES}
all: $(ALL_FILES)
ui: $(UI_FILES)
ts: $(PRO_FILES)
pylupdate4 -verbose $<
lang: $(LANG_FILES)
res: $(RES_FILES)
$(UI_FILES): $(UI_PATH)/ui_%.py: $(UI_PATH)/%.ui
pyuic4 -o $@ $<
$(LANG_FILES): $(LANG_PATH)/%.qm: $(LANG_PATH)/%.ts
lrelease $<
$(RES_FILES): $(RES_PATH)/%_rc.py: $(RES_PATH)/%.qrc
pyrcc4 -o $@ $<
pep8:
@echo
@echo "-----------"
@echo "PEP8 issues"
@echo "-----------"
@pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude mbutils.py,resources_rc.py . || true
clean:
rm -f $(ALL_FILES)
find -name "*.pyc" -exec rm -f {} \;
rm -f *.zip
package:
cd .. && rm -f *.zip && zip -r qtiles.zip qtiles -x \*.pyc \*.ts \*.ui \*.qrc \*.pro \*~ \*.git\* \resources\* \*Makefile*
mv ../qtiles.zip .
upload:
plugin_uploaderNG.py qtiles.zip
PK O'Gކkخ1 1 qtiles/mbutils.py#!/usr/bin/env python
# MBUtil: a tool for MBTiles files
# Supports importing, exporting, and more
#
# (c) Development Seed 2012
# Licensed under BSD
# for additional reference on schema see:
# https://github.com/mapbox/node-mbtiles/blob/master/lib/schema.sql
import sqlite3, uuid, sys, logging, time, os, json, zlib, re
logger = logging.getLogger(__name__)
def flip_y(zoom, y):
return (2**zoom-1) - y
def mbtiles_setup(cur):
cur.execute("""
create table tiles (
zoom_level integer,
tile_column integer,
tile_row integer,
tile_data blob);
""")
cur.execute("""create table metadata
(name text, value text);""")
cur.execute("""CREATE TABLE grids (zoom_level integer, tile_column integer,
tile_row integer, grid blob);""")
cur.execute("""CREATE TABLE grid_data (zoom_level integer, tile_column
integer, tile_row integer, key_name text, key_json text);""")
cur.execute("""create unique index name on metadata (name);""")
cur.execute("""create unique index tile_index on tiles
(zoom_level, tile_column, tile_row);""")
def mbtiles_connect(mbtiles_file):
try:
con = sqlite3.connect(mbtiles_file, check_same_thread=False)
return con
except Exception, e:
logger.error("Could not connect to database")
logger.exception(e)
sys.exit(1)
def optimize_connection(cur):
cur.execute("""PRAGMA synchronous=0""")
cur.execute("""PRAGMA locking_mode=EXCLUSIVE""")
cur.execute("""PRAGMA journal_mode=DELETE""")
def compression_prepare(cur, con):
cur.execute("""
CREATE TABLE if not exists images (
tile_data blob,
tile_id VARCHAR(256));
""")
cur.execute("""
CREATE TABLE if not exists map (
zoom_level integer,
tile_column integer,
tile_row integer,
tile_id VARCHAR(256));
""")
def optimize_database(cur):
logger.debug('analyzing db')
cur.execute("""ANALYZE;""")
logger.debug('cleaning db')
cur.execute("""VACUUM;""")
def compression_do(cur, con, chunk):
overlapping = 0
unique = 0
total = 0
cur.execute("select count(zoom_level) from tiles")
res = cur.fetchone()
total_tiles = res[0]
logging.debug("%d total tiles to fetch" % total_tiles)
for i in range(total_tiles / chunk + 1):
logging.debug("%d / %d rounds done" % (i, (total_tiles / chunk)))
ids = []
files = []
start = time.time()
cur.execute("""select zoom_level, tile_column, tile_row, tile_data
from tiles where rowid > ? and rowid <= ?""", ((i * chunk), ((i + 1) * chunk)))
logger.debug("select: %s" % (time.time() - start))
rows = cur.fetchall()
for r in rows:
total = total + 1
if r[3] in files:
overlapping = overlapping + 1
start = time.time()
query = """insert into map
(zoom_level, tile_column, tile_row, tile_id)
values (?, ?, ?, ?)"""
logger.debug("insert: %s" % (time.time() - start))
cur.execute(query, (r[0], r[1], r[2], ids[files.index(r[3])]))
else:
unique = unique + 1
id = str(uuid.uuid4())
ids.append(id)
files.append(r[3])
start = time.time()
query = """insert into images
(tile_id, tile_data)
values (?, ?)"""
cur.execute(query, (str(id), sqlite3.Binary(r[3])))
logger.debug("insert into images: %s" % (time.time() - start))
start = time.time()
query = """insert into map
(zoom_level, tile_column, tile_row, tile_id)
values (?, ?, ?, ?)"""
cur.execute(query, (r[0], r[1], r[2], id))
logger.debug("insert into map: %s" % (time.time() - start))
con.commit()
def compression_finalize(cur):
cur.execute("""drop table tiles;""")
cur.execute("""create view tiles as
select map.zoom_level as zoom_level,
map.tile_column as tile_column,
map.tile_row as tile_row,
images.tile_data as tile_data FROM
map JOIN images on images.tile_id = map.tile_id;""")
cur.execute("""
CREATE UNIQUE INDEX map_index on map
(zoom_level, tile_column, tile_row);""")
cur.execute("""
CREATE UNIQUE INDEX images_id on images
(tile_id);""")
cur.execute("""vacuum;""")
cur.execute("""analyze;""")
def getDirs(path):
return [name for name in os.listdir(path)
if os.path.isdir(os.path.join(path, name))]
def disk_to_mbtiles(directory_path, mbtiles_file, **kwargs):
logger.info("Importing disk to MBTiles")
logger.debug("%s --> %s" % (directory_path, mbtiles_file))
con = mbtiles_connect(mbtiles_file)
cur = con.cursor()
optimize_connection(cur)
mbtiles_setup(cur)
#~ image_format = 'png'
image_format = kwargs.get('format', 'png')
try:
metadata = json.load(open(os.path.join(directory_path, 'metadata.json'), 'r'))
image_format = kwargs.get('format')
for name, value in metadata.items():
cur.execute('insert into metadata (name, value) values (?, ?)',
(name, value))
logger.info('metadata from metadata.json restored')
except IOError:
logger.warning('metadata.json not found')
count = 0
start_time = time.time()
msg = ""
for zoomDir in getDirs(directory_path):
if kwargs.get("scheme") == 'ags':
if not "L" in zoomDir:
logger.warning("You appear to be using an ags scheme on an non-arcgis Server cache.")
z = int(zoomDir.replace("L", ""))
else:
if "L" in zoomDir:
logger.warning("You appear to be using a %s scheme on an arcgis Server cache. Try using --scheme=ags instead" % kwargs.get("scheme"))
z = int(zoomDir)
for rowDir in getDirs(os.path.join(directory_path, zoomDir)):
if kwargs.get("scheme") == 'ags':
y = flip_y(z, int(rowDir.replace("R", ""), 16))
else:
x = int(rowDir)
for current_file in os.listdir(os.path.join(directory_path, zoomDir, rowDir)):
file_name, ext = current_file.split('.', 1)
f = open(os.path.join(directory_path, zoomDir, rowDir, current_file), 'rb')
file_content = f.read()
f.close()
if kwargs.get('scheme') == 'xyz':
y = flip_y(int(z), int(file_name))
elif kwargs.get("scheme") == 'ags':
x = int(file_name.replace("C", ""), 16)
else:
y = int(file_name)
if (ext == image_format):
logger.debug(' Read tile from Zoom (z): %i\tCol (x): %i\tRow (y): %i' % (z, x, y))
cur.execute("""insert into tiles (zoom_level,
tile_column, tile_row, tile_data) values
(?, ?, ?, ?);""",
(z, x, y, sqlite3.Binary(file_content)))
count = count + 1
if (count % 100) == 0:
for c in msg: sys.stdout.write(chr(8))
msg = "%s tiles inserted (%d tiles/sec)" % (count, count / (time.time() - start_time))
sys.stdout.write(msg)
elif (ext == 'grid.json'):
logger.debug(' Read grid from Zoom (z): %i\tCol (x): %i\tRow (y): %i' % (z, x, y))
# Remove potential callback with regex
has_callback = re.match(r'[\w\s=+-/]+\(({(.|\n)*})\);?', file_content)
if has_callback:
file_content = has_callback.group(1)
utfgrid = json.loads(file_content)
data = utfgrid.pop('data')
compressed = zlib.compress(json.dumps(utfgrid))
cur.execute("""insert into grids (zoom_level, tile_column, tile_row, grid) values (?, ?, ?, ?) """, (z, x, y, sqlite3.Binary(compressed)))
grid_keys = [k for k in utfgrid['keys'] if k != ""]
for key_name in grid_keys:
key_json = data[key_name]
cur.execute("""insert into grid_data (zoom_level, tile_column, tile_row, key_name, key_json) values (?, ?, ?, ?, ?);""", (z, x, y, key_name, json.dumps(key_json)))
logger.debug('tiles (and grids) inserted.')
optimize_database(con)
def mbtiles_to_disk(mbtiles_file, directory_path, **kwargs):
logger.debug("Exporting MBTiles to disk")
logger.debug("%s --> %s" % (mbtiles_file, directory_path))
con = mbtiles_connect(mbtiles_file)
os.mkdir("%s" % directory_path)
metadata = dict(con.execute('select name, value from metadata;').fetchall())
json.dump(metadata, open(os.path.join(directory_path, 'metadata.json'), 'w'), indent=4)
count = con.execute('select count(zoom_level) from tiles;').fetchone()[0]
done = 0
msg = ''
base_path = directory_path
if not os.path.isdir(base_path):
os.makedirs(base_path)
# if interactivity
formatter = metadata.get('formatter')
if formatter:
layer_json = os.path.join(base_path,'layer.json')
formatter_json = {"formatter":formatter}
open(layer_json,'w').write('grid(' + json.dumps(formatter_json) + ')')
tiles = con.execute('select zoom_level, tile_column, tile_row, tile_data from tiles;')
t = tiles.fetchone()
while t:
z = t[0]
x = t[1]
y = t[2]
if kwargs.get('scheme') == 'xyz':
y = flip_y(z,y)
print 'flipping'
tile_dir = os.path.join(base_path, str(z), str(x))
elif kwargs.get('scheme') == 'wms':
tile_dir = os.path.join(base_path,
"%02d" % (z),
"%03d" % (int(x) / 1000000),
"%03d" % ((int(x) / 1000) % 1000),
"%03d" % (int(x) % 1000),
"%03d" % (int(y) / 1000000),
"%03d" % ((int(y) / 1000) % 1000))
else:
tile_dir = os.path.join(base_path, str(z), str(x))
if not os.path.isdir(tile_dir):
os.makedirs(tile_dir)
if kwargs.get('scheme') == 'wms':
tile = os.path.join(tile_dir,'%03d.%s' % (int(y) % 1000, kwargs.get('format', 'png')))
else:
tile = os.path.join(tile_dir,'%s.%s' % (y, kwargs.get('format', 'png')))
f = open(tile, 'wb')
f.write(t[3])
f.close()
done = done + 1
for c in msg: sys.stdout.write(chr(8))
logger.info('%s / %s tiles exported' % (done, count))
t = tiles.fetchone()
# grids
callback = kwargs.get('callback')
done = 0
msg = ''
try:
count = con.execute('select count(zoom_level) from grids;').fetchone()[0]
grids = con.execute('select zoom_level, tile_column, tile_row, grid from grids;')
g = grids.fetchone()
except sqlite3.OperationalError:
g = None # no grids table
while g:
zoom_level = g[0] # z
tile_column = g[1] # x
y = g[2] # y
grid_data_cursor = con.execute('''select key_name, key_json FROM
grid_data WHERE
zoom_level = %(zoom_level)d and
tile_column = %(tile_column)d and
tile_row = %(y)d;''' % locals() )
if kwargs.get('scheme') == 'xyz':
y = flip_y(zoom_level,y)
grid_dir = os.path.join(base_path, str(zoom_level), str(tile_column))
if not os.path.isdir(grid_dir):
os.makedirs(grid_dir)
grid = os.path.join(grid_dir,'%s.grid.json' % (y))
f = open(grid, 'w')
grid_json = json.loads(zlib.decompress(g[3]))
# join up with the grid 'data' which is in pieces when stored in mbtiles file
grid_data = grid_data_cursor.fetchone()
data = {}
while grid_data:
data[grid_data[0]] = json.loads(grid_data[1])
grid_data = grid_data_cursor.fetchone()
grid_json['data'] = data
if callback in (None, "", "false", "null"):
f.write(json.dumps(grid_json))
else:
f.write('%s(%s);' % (callback, json.dumps(grid_json)))
f.close()
done = done + 1
for c in msg: sys.stdout.write(chr(8))
logger.info('%s / %s grids exported' % (done, count))
g = grids.fetchone()
PK vEHEbl l qtiles/metadata.txt[general]
name=QTiles
description=Generate tiles from QGIS project
category=Plugins
version=1.5.1
qgisMinimumVersion=2.0
author=NextGIS
email=info@nextgis.com
changelog=1.5.1:
* create tiles for NextGIS Mobile
* add MBTiles compression
* add export MBTiles metadata to .json file
* add image overview for MBTiles
* add option for skiping tiles outside of layers extents (within combined extent)
1.5.0:
* change MBTiles parameters vаlues: format in lower case, description is 'Created with QTiles'
* tiles are now produced correctly when transparency is set
* geojson is now rendered correctly
* CRS shift when using 3857 is fixed
1.4.6:
* works fine now with non-english characters in folder names
* add MBTiles initialize arguments for Geopaparazzi4
* take into account the actual zoom level when generating tiles
icon=icons/qtiles.png
tags=raster,tiles
homepage=https://github.com/nextgis/QTiles
tracker=https://github.com/nextgis/QTiles/issues
repository=https://github.com/nextgis/QTiles
experimental=True
deprecated=False
about=Plugin generates raster tiles from QGIS project using selected zoom levels and tile naming conventions (Slippy Map or TMS). Also it can write .mapurl file for GeoPaparazzi and simple Leaflet-based viewer.
PK aEHKr r qtiles/qtiles.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
import qtilesdialog
import aboutdialog
import resources_rc
class QTilesPlugin:
def __init__(self, iface):
self.iface = iface
self.qgsVersion = unicode(QGis.QGIS_VERSION_INT)
userPluginPath = QFileInfo(QgsApplication.qgisUserDbFilePath()).path() + '/python/plugins/qtiles'
systemPluginPath = QgsApplication.prefixPath() + '/python/plugins/qtiles'
overrideLocale = QSettings().value('locale/overrideFlag', False, type=bool)
if not overrideLocale:
localeFullName = QLocale.system().name()
else:
localeFullName = QSettings().value('locale/userLocale', '')
if QFileInfo(userPluginPath).exists():
translationPath = userPluginPath + '/i18n/qtiles_' + localeFullName + '.qm'
else:
translationPath = systemPluginPath + '/i18n/qtiles_' + localeFullName + '.qm'
self.localePath = translationPath
if QFileInfo(self.localePath).exists():
self.translator = QTranslator()
self.translator.load(self.localePath)
QCoreApplication.installTranslator(self.translator)
def initGui(self):
if int(self.qgsVersion) < 20000:
qgisVersion = self.qgsVersion[0] + '.' + self.qgsVersion[2] + '.' + self.qgsVersion[3]
QMessageBox.warning(self.iface.mainWindow(), QCoreApplication.translate('QTiles', 'Error'), QCoreApplication.translate('QTiles', 'QGIS %s detected.\n') % qgisVersion + QCoreApplication.translate('QTiles', 'This version of QTiles requires at least QGIS 2.0. Plugin will not be enabled.'))
return None
self.actionRun = QAction(QCoreApplication.translate('QTiles', 'QTiles'), self.iface.mainWindow())
self.iface.registerMainWindowAction(self.actionRun, 'Shift+T')
self.actionRun.setIcon(QIcon(':/icons/qtiles.png'))
self.actionRun.setWhatsThis('Generate tiles from current project')
self.actionAbout = QAction(QCoreApplication.translate('QTiles', 'About QTiles...'), self.iface.mainWindow())
self.actionAbout.setIcon(QIcon(':/icons/about.png'))
self.actionAbout.setWhatsThis('About QTiles')
self.iface.addPluginToMenu(QCoreApplication.translate('QTiles', 'QTiles'), self.actionRun)
self.iface.addPluginToMenu(QCoreApplication.translate('QTiles', 'QTiles'), self.actionAbout)
self.iface.addToolBarIcon(self.actionRun)
self.actionRun.triggered.connect(self.run)
self.actionAbout.triggered.connect(self.about)
def unload(self):
self.iface.unregisterMainWindowAction(self.actionRun)
self.iface.removeToolBarIcon(self.actionRun)
self.iface.removePluginMenu(QCoreApplication.translate('QTiles', 'QTiles'), self.actionRun)
self.iface.removePluginMenu(QCoreApplication.translate('QTiles', 'QTiles'), self.actionAbout)
def run(self):
d = qtilesdialog.QTilesDialog(self.iface)
d.show()
d.exec_()
def about(self):
d = aboutdialog.AboutDialog()
d.exec_()
PK EH$%YJ J qtiles/qtilesdialog.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
import os
import locale
import math
import operator
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
import tilingthread
from ui.ui_qtilesdialogbase import Ui_Dialog
import qtiles_utils as utils
class QTilesDialog(QDialog, Ui_Dialog):
MAX_ZOOM_LEVEL = 18
MIN_ZOOM_LEVEL = 0
def __init__(self, iface):
QDialog.__init__(self)
self.setupUi(self)
self.btnOk = self.buttonBox.addButton(self.tr("Run"), QDialogButtonBox.AcceptRole)
self.spnZoomMax.setMaximum(self.MAX_ZOOM_LEVEL)
self.spnZoomMax.setMinimum(self.MIN_ZOOM_LEVEL)
self.spnZoomMin.setMaximum(self.MAX_ZOOM_LEVEL)
self.spnZoomMin.setMinimum(self.MIN_ZOOM_LEVEL)
self.spnZoomMin.valueChanged.connect(self.spnZoomMax.setMinimum)
self.spnZoomMax.valueChanged.connect(self.spnZoomMin.setMaximum)
self.iface = iface
self.verticalLayout_2.setAlignment(Qt.AlignTop)
self.workThread = None
self.FORMATS = {
self.tr('ZIP archives (*.zip *.ZIP)'): '.zip',
self.tr('MBTiles databases (*.mbtiles *.MBTILES)'): '.mbtiles'}
self.settings = QSettings('NextGIS', 'QTiles')
self.grpParameters.setSettings(self.settings)
self.btnClose = self.buttonBox.button(QDialogButtonBox.Close)
self.rbExtentLayer.toggled.connect(self.__toggleLayerSelector)
self.chkLockRatio.stateChanged.connect(self.__toggleHeightEdit)
self.spnTileWidth.valueChanged.connect(self.__updateTileSize)
self.btnBrowse.clicked.connect(self.__select_output)
self.cmbFormat.activated.connect(self.formatChanged)
self.rbOutputZip.toggled.connect(self.__toggleTarget)
self.rbOutputDir.toggled.connect(self.__toggleTarget)
self.rbOutputNGM.toggled.connect(self.__toggleTarget)
self.rbOutputNGM.setIcon(QIcon(':/icons/ngm_index_24x24.png'))
self.lInfoIconOutputZip.linkActivated.connect(self.show_output_info)
self.lInfoIconOutputDir.linkActivated.connect(self.show_output_info)
self.lInfoIconOutputNGM.linkActivated.connect(self.show_output_info)
self.manageGui()
def show_output_info(self, href):
title = self.tr("Output type info")
message = ""
if self.sender() is self.lInfoIconOutputZip:
message = self.tr("Save tiles as Zip or MBTiles")
elif self.sender() is self.lInfoIconOutputDir:
message = self.tr("Save tiles as directory structure")
elif self.sender() is self.lInfoIconOutputNGM:
message = " \
\
\
| \
\
%s \
| \
" % self.tr("Prepare package for NextGIS Mobile ")
# QMessageBox.information(
# self,
# title,
# message
# )
msgBox = QMessageBox()
msgBox.setWindowTitle(title)
msgBox.setText(message)
msgBox.exec_()
def formatChanged(self):
if self.cmbFormat.currentText() == 'JPG':
self.spnTransparency.setEnabled(False)
self.spnQuality.setEnabled(True)
else:
self.spnTransparency.setEnabled(True)
self.spnQuality.setEnabled(False)
def manageGui(self):
layers = utils.getMapLayers()
relations = self.iface.legendInterface().groupLayerRelationship()
for layer in sorted(layers.iteritems(), cmp=locale.strcoll, key=operator.itemgetter(1)):
groupName = utils.getLayerGroup(relations, layer[0])
if groupName == '':
self.cmbLayers.addItem(layer[1], layer[0])
else:
self.cmbLayers.addItem('%s - %s' % (layer[1], groupName), layer[0])
self.rbOutputZip.setChecked(self.settings.value('outputToZip', True, type=bool))
self.rbOutputDir.setChecked(self.settings.value('outputToDir', False, type=bool))
self.rbOutputNGM.setChecked(self.settings.value('outputToNGM', False, type=bool))
if self.rbOutputZip.isChecked():
self.leDirectoryName.setEnabled(False)
self.leTilesFroNGM.setEnabled(False)
elif self.rbOutputDir.isChecked():
self.leZipFileName.setEnabled(False)
self.leTilesFroNGM.setEnabled(False)
elif self.rbOutputNGM.isChecked():
self.leZipFileName.setEnabled(False)
self.leDirectoryName.setEnabled(False)
else:
self.leZipFileName.setEnabled(False)
self.leDirectoryName.setEnabled(False)
self.leTilesFroNGM.setEnabled(False)
self.leZipFileName.setText(self.settings.value('outputToZip_Path', ''))
self.leDirectoryName.setText(self.settings.value('outputToDir_Path', ''))
self.leTilesFroNGM.setText(self.settings.value('outputToNGM_Path', ''))
self.cmbLayers.setEnabled(False)
self.leRootDir.setText(self.settings.value('rootDir', 'Mapnik'))
self.rbExtentCanvas.setChecked(self.settings.value('extentCanvas', True, type=bool))
self.rbExtentFull.setChecked(self.settings.value('extentFull', False, type=bool))
self.rbExtentLayer.setChecked(self.settings.value('extentLayer', False, type=bool))
self.spnZoomMin.setValue(self.settings.value('minZoom', 0, type=int))
self.spnZoomMax.setValue(self.settings.value('maxZoom', 18, type=int))
self.chkLockRatio.setChecked(self.settings.value('keepRatio', True, type=bool))
self.spnTileWidth.setValue(self.settings.value('tileWidth', 256, type=int))
self.spnTileHeight.setValue(self.settings.value('tileHeight', 256, type=int))
self.spnTransparency.setValue(self.settings.value('transparency', 255, type=int))
self.spnQuality.setValue(self.settings.value('quality', 70, type=int))
self.cmbFormat.setCurrentIndex(int(self.settings.value('format', 0)))
self.chkAntialiasing.setChecked(self.settings.value('enable_antialiasing', False, type=bool))
self.chkTMSConvention.setChecked(self.settings.value('use_tms_filenames', False, type=bool))
self.chkMBTilesCompression.setChecked(self.settings.value('use_mbtiles_compression', False, type=bool))
self.chkWriteJson.setChecked(self.settings.value("write_json", False, type=bool))
self.chkWriteOverview.setChecked(self.settings.value("write_overview", False, type=bool))
self.chkWriteMapurl.setChecked(self.settings.value("write_mapurl", False, type=bool))
self.chkWriteViewer.setChecked(self.settings.value("write_viewer", False, type=bool))
self.chkRenderOutsideTiles.setChecked(self.settings.value("renderOutsideTiles", True, type=bool))
self.formatChanged()
def reject(self):
QDialog.reject(self)
def accept(self):
if self.rbOutputZip.isChecked():
output = self.leZipFileName.text()
elif self.rbOutputDir.isChecked():
output = self.leDirectoryName.text()
if not QFileInfo(output).exists():
os.mkdir(QFileInfo(output).absoluteFilePath())
elif self.rbOutputNGM.isChecked():
output = self.leTilesFroNGM.text()
if not output:
QMessageBox.warning(self, self.tr('No output'), self.tr('Output path is not set. Please enter correct path and try again.'))
return
fileInfo = QFileInfo(output)
if fileInfo.isDir() and not len(QDir(output).entryList(QDir.Dirs | QDir.Files | QDir.NoDotAndDotDot)) == 0:
res = QMessageBox.warning(
self,
self.tr('Directory not empty'),
self.tr('Selected directory is not empty. Continue?'),
QMessageBox.Yes | QMessageBox.No
)
if res == QMessageBox.No:
return
if self.spnZoomMin.value() > self.spnZoomMax.value():
QMessageBox.warning(self, self.tr('Wrong zoom'), self.tr('Maximum zoom value is lower than minimum. Please correct this and try again.'))
return
self.settings.setValue('rootDir', self.leRootDir.text())
self.settings.setValue('outputToZip', self.rbOutputZip.isChecked())
self.settings.setValue('outputToDir', self.rbOutputDir.isChecked())
self.settings.setValue('outputToNGM', self.rbOutputNGM.isChecked())
self.settings.setValue('extentCanvas', self.rbExtentCanvas.isChecked())
self.settings.setValue('extentFull', self.rbExtentFull.isChecked())
self.settings.setValue('extentLayer', self.rbExtentLayer.isChecked())
self.settings.setValue('minZoom', self.spnZoomMin.value())
self.settings.setValue('maxZoom', self.spnZoomMax.value())
self.settings.setValue('keepRatio', self.chkLockRatio.isChecked())
self.settings.setValue('tileWidth', self.spnTileWidth.value())
self.settings.setValue('tileHeight', self.spnTileHeight.value())
self.settings.setValue('format', self.cmbFormat.currentIndex())
self.settings.setValue('transparency', self.spnTransparency.value())
self.settings.setValue('quality', self.spnQuality.value())
self.settings.setValue('enable_antialiasing', self.chkAntialiasing.isChecked())
self.settings.setValue('use_tms_filenames', self.chkTMSConvention.isChecked())
self.settings.setValue('use_mbtiles_compression', self.chkMBTilesCompression.isChecked())
self.settings.setValue('write_json', self.chkWriteJson.isChecked())
self.settings.setValue('write_overview', self.chkWriteOverview.isChecked())
self.settings.setValue('write_mapurl', self.chkWriteMapurl.isChecked())
self.settings.setValue('write_viewer', self.chkWriteViewer.isChecked())
self.settings.setValue('renderOutsideTiles', self.chkRenderOutsideTiles.isChecked())
canvas = self.iface.mapCanvas()
if self.rbExtentCanvas.isChecked():
extent = canvas.extent()
elif self.rbExtentFull.isChecked():
extent = canvas.fullExtent()
else:
layer = utils.getLayerById(self.cmbLayers.itemData(self.cmbLayers.currentIndex()))
extent = canvas.mapRenderer().layerExtentToOutputExtent(layer, layer.extent())
extent = QgsCoordinateTransform(canvas.mapRenderer().destinationCrs(), QgsCoordinateReferenceSystem('EPSG:4326')).transform(extent)
arctanSinhPi = math.degrees(math.atan(math.sinh(math.pi)))
extent = extent.intersect(QgsRectangle(-180, -arctanSinhPi, 180, arctanSinhPi))
layers = canvas.layers()
writeMapurl = self.chkWriteMapurl.isEnabled() and self.chkWriteMapurl.isChecked()
writeViewer = self.chkWriteViewer.isEnabled() and self.chkWriteViewer.isChecked()
self.workThread = tilingthread.TilingThread(
layers,
extent,
self.spnZoomMin.value(),
self.spnZoomMax.value(),
self.spnTileWidth.value(),
self.spnTileHeight.value(),
self.spnTransparency.value(),
self.spnQuality.value(),
self.cmbFormat.currentText(),
fileInfo,
self.leRootDir.text(),
self.chkAntialiasing.isChecked(),
self.chkTMSConvention.isChecked(),
self.chkMBTilesCompression.isChecked(),
self.chkWriteJson.isChecked(),
self.chkWriteOverview.isChecked(),
self.chkRenderOutsideTiles.isChecked(),
writeMapurl,
writeViewer
)
self.workThread.rangeChanged.connect(self.setProgressRange)
self.workThread.updateProgress.connect(self.updateProgress)
self.workThread.processFinished.connect(self.processFinished)
self.workThread.processInterrupted.connect(self.processInterrupted)
self.btnOk.setEnabled(False)
self.btnClose.setText(self.tr('Cancel'))
self.buttonBox.rejected.disconnect(self.reject)
self.btnClose.clicked.connect(self.stopProcessing)
self.workThread.start()
def setProgressRange(self, message, value):
self.progressBar.setFormat(message)
self.progressBar.setRange(0, value)
def updateProgress(self):
self.progressBar.setValue(self.progressBar.value() + 1)
def processInterrupted(self):
self.restoreGui()
def processFinished(self):
self.stopProcessing()
self.restoreGui()
def stopProcessing(self):
if self.workThread is not None:
self.workThread.stop()
self.workThread = None
def restoreGui(self):
self.progressBar.setFormat('%p%')
self.progressBar.setRange(0, 1)
self.progressBar.setValue(0)
self.buttonBox.rejected.connect(self.reject)
self.btnClose.clicked.disconnect(self.stopProcessing)
self.btnClose.setText(self.tr('Close'))
self.btnOk.setEnabled(True)
def __toggleTarget(self, checked):
if checked:
if self.sender() is self.rbOutputZip:
self.leZipFileName.setEnabled(True)
self.leDirectoryName.setEnabled(False)
self.leTilesFroNGM.setEnabled(False)
self.chkWriteMapurl.setEnabled(False)
self.chkWriteViewer.setEnabled(False)
self.chkWriteJson.setEnabled(True)
self.spnTileWidth.setEnabled(True)
self.chkLockRatio.setEnabled(True)
self.cmbFormat.setEnabled(True)
self.chkMBTilesCompression.setEnabled(True)
self.chkWriteOverview.setEnabled(True)
elif self.sender() is self.rbOutputDir:
self.leZipFileName.setEnabled(False)
self.leDirectoryName.setEnabled(True)
self.leTilesFroNGM.setEnabled(False)
self.chkWriteMapurl.setEnabled(True)
self.chkWriteViewer.setEnabled(True)
self.chkWriteJson.setEnabled(True)
self.chkMBTilesCompression.setEnabled(False)
self.spnTileWidth.setEnabled(True)
self.chkLockRatio.setEnabled(True)
self.cmbFormat.setEnabled(True)
self.chkWriteOverview.setEnabled(True)
elif self.sender() is self.rbOutputNGM:
self.leZipFileName.setEnabled(False)
self.leDirectoryName.setEnabled(False)
self.leTilesFroNGM.setEnabled(True)
self.chkWriteMapurl.setEnabled(False)
self.chkWriteViewer.setEnabled(False)
self.chkMBTilesCompression.setEnabled(False)
self.spnTileWidth.setValue(256)
self.spnTileWidth.setEnabled(False)
self.chkLockRatio.setCheckState(Qt.Checked)
self.chkLockRatio.setEnabled(False)
self.cmbFormat.setCurrentIndex(0)
self.cmbFormat.setEnabled(False)
self.chkWriteOverview.setChecked(False)
self.chkWriteOverview.setEnabled(False)
self.chkWriteJson.setChecked(False)
self.chkWriteJson.setEnabled(False)
def __toggleLayerSelector(self, checked):
self.cmbLayers.setEnabled(checked)
def __toggleHeightEdit(self, state):
if state == Qt.Checked:
self.lblHeight.setEnabled(False)
self.spnTileHeight.setEnabled(False)
self.spnTileHeight.setValue(self.spnTileWidth.value())
else:
self.lblHeight.setEnabled(True)
self.spnTileHeight.setEnabled(True)
@pyqtSlot(int)
def __updateTileSize(self, value):
if self.chkLockRatio.isChecked():
self.spnTileHeight.setValue(value)
def __select_output(self):
if self.rbOutputZip.isChecked():
file_directory = QFileInfo(self.settings.value('outputToZip_Path', '.')).absolutePath()
outPath, outFilter = QFileDialog.getSaveFileNameAndFilter(self, self.tr('Save to file'), file_directory, ';;'.join(self.FORMATS.iterkeys()), self.FORMATS.keys()[self.FORMATS.values().index('.zip')])
if not outPath:
return
if not outPath.lower().endswith(self.FORMATS[outFilter]):
outPath += self.FORMATS[outFilter]
self.leZipFileName.setText(outPath)
self.settings.setValue('outputToZip_Path', QFileInfo(outPath).absoluteFilePath())
elif self.rbOutputDir.isChecked():
dir_directory = QFileInfo(self.settings.value('outputToDir_Path', '.')).absolutePath()
outPath = QFileDialog.getExistingDirectory(self, self.tr('Save to directory'), dir_directory, QFileDialog.ShowDirsOnly)
if not outPath:
return
self.leDirectoryName.setText(outPath)
self.settings.setValue('outputToDir_Path', QFileInfo(outPath).absoluteFilePath())
elif self.rbOutputNGM.isChecked():
zip_directory = QFileInfo(self.settings.value('outputToNGM_Path', '.')).absolutePath()
outPath, outFilter = QFileDialog.getSaveFileNameAndFilter(self, self.tr('Save to file'), zip_directory, 'ngrc')
if not outPath:
return
if not outPath.lower().endswith('ngrc'):
outPath += '.ngrc'
self.leTilesFroNGM.setText(outPath)
self.settings.setValue('outputToNGM_Path', QFileInfo(outPath).absoluteFilePath())
PK O'G^Hy y qtiles/qtiles_utils.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
from PyQt4.QtCore import *
from qgis.core import *
def getMapLayers():
layerMap = QgsMapLayerRegistry.instance().mapLayers()
layers = dict()
for name, layer in layerMap.iteritems():
if layer.type() == QgsMapLayer.VectorLayer:
if layer.id() not in layers.keys():
layers[layer.id()] = unicode(layer.name())
if layer.type() == QgsMapLayer.RasterLayer and layer.providerType() == 'gdal':
if layer.id() not in layers.keys():
layers[layer.id()] = unicode(layer.name())
return layers
def getLayerById(layerId):
layerMap = QgsMapLayerRegistry.instance().mapLayers()
for name, layer in layerMap.iteritems():
if layer.id() == layerId:
if layer.isValid():
return layer
else:
return None
def getLayerGroup(relations, layerId):
group = None
for item in relations:
group = unicode(item[0])
for lid in item[1]:
if unicode(lid) == unicode(layerId):
return group
return group
PK O'GӑF
qtiles/READMEQTiles
------
Generate tiles from QGIS project.
Plugin generates raster tiles from QGIS project using selected zoom levels and
tile naming conventions (Slippy Map or TMS). Also it can write .mapurl file for
GeoPaparazzi and simple Leaflet-based viewer.
Manual (Russian): http://gis-lab.info/qa/qtiles.html
Manual (English): http://gis-lab.info/qa/qtiles-eng.html
NextGIS - Open source geospatial solutions
http://nextgis.org
PK sEHʹ"* * qtiles/resources_rc.py# -*- coding: utf-8 -*-
# Resource object code
#
# Created: Пт 5. фев 14:29:43 2016
# by: The Resource Compiler for PyQt (Qt v4.8.6)
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore
qt_resource_data = "\
\x00\x00\x09\x32\
\x3c\
\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x68\x74\x6d\x6c\x3e\x0d\x0a\
\x3c\x68\x74\x6d\x6c\x3e\x0d\x0a\x3c\x68\x65\x61\x64\x3e\x0d\x0a\
\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x40\x74\x69\x6c\x65\x73\x65\
\x74\x6e\x61\x6d\x65\x20\x2d\x20\x4c\x65\x61\x66\x4c\x65\x74\x20\
\x50\x72\x65\x76\x69\x65\x77\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0d\
\x0a\x20\x20\x3c\x6d\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\
\x3d\x22\x75\x74\x66\x2d\x38\x22\x20\x2f\x3e\x0d\x0a\x0d\x0a\x20\
\x20\x3c\x6d\x65\x74\x61\x20\x6e\x61\x6d\x65\x3d\x22\x76\x69\x65\
\x77\x70\x6f\x72\x74\x22\x20\x63\x6f\x6e\x74\x65\x6e\x74\x3d\x22\
\x77\x69\x64\x74\x68\x3d\x64\x65\x76\x69\x63\x65\x2d\x77\x69\x64\
\x74\x68\x2c\x20\x69\x6e\x69\x74\x69\x61\x6c\x2d\x73\x63\x61\x6c\
\x65\x3d\x31\x2e\x30\x22\x3e\x0d\x0a\x0d\x0a\x20\x20\x3c\x6c\x69\
\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\
\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\
\x2f\x63\x64\x6e\x2e\x6c\x65\x61\x66\x6c\x65\x74\x6a\x73\x2e\x63\
\x6f\x6d\x2f\x6c\x65\x61\x66\x6c\x65\x74\x2d\x30\x2e\x37\x2e\x32\
\x2f\x6c\x65\x61\x66\x6c\x65\x74\x2e\x63\x73\x73\x22\x20\x2f\x3e\
\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x68\x72\x65\x66\x3d\x22\
\x68\x74\x74\x70\x3a\x2f\x2f\x61\x6a\x61\x78\x2e\x67\x6f\x6f\x67\
\x6c\x65\x61\x70\x69\x73\x2e\x63\x6f\x6d\x2f\x61\x6a\x61\x78\x2f\
\x6c\x69\x62\x73\x2f\x6a\x71\x75\x65\x72\x79\x75\x69\x2f\x31\x2e\
\x31\x30\x2e\x34\x2f\x74\x68\x65\x6d\x65\x73\x2f\x73\x6d\x6f\x6f\
\x74\x68\x6e\x65\x73\x73\x2f\x6a\x71\x75\x65\x72\x79\x2d\x75\x69\
\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x20\x72\x65\x6c\x3d\x22\x73\
\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x74\x79\x70\x65\x3d\
\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x2f\x3e\x0d\x0a\x20\x20\
\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\
\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\x20\x20\x20\x20\x23\x62\x61\
\x73\x65\x6d\x61\x70\x73\x6c\x69\x64\x65\x72\x63\x6f\x6e\x74\x61\
\x69\x6e\x65\x72\x20\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\x70\x6f\
\x73\x69\x74\x69\x6f\x6e\x3a\x20\x61\x62\x73\x6f\x6c\x75\x74\x65\
\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\x74\x6f\x70\x3a\x20\x35\x30\
\x70\x78\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\x72\x69\x67\x68\x74\
\x3a\x20\x31\x30\x70\x78\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\x7a\
\x2d\x69\x6e\x64\x65\x78\x3a\x20\x31\x30\x30\x30\x3b\x0d\x0a\x20\
\x20\x20\x20\x7d\x0d\x0a\x0d\x0a\x20\x20\x20\x20\x23\x62\x61\x73\
\x65\x6d\x61\x70\x73\x6c\x69\x64\x65\x72\x7b\x0d\x0a\x20\x20\x20\
\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x36\x32\x2e\
\x35\x25\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\x6d\x61\x72\x67\x69\
\x6e\x3a\x20\x31\x34\x70\x78\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\
\x68\x65\x69\x67\x68\x74\x3a\x20\x31\x32\x35\x70\x78\x3b\x0d\x0a\
\x20\x20\x20\x20\x20\x20\x77\x69\x64\x74\x68\x3a\x37\x70\x78\x3b\
\x0d\x0a\x20\x20\x20\x20\x7d\x0d\x0a\x0d\x0a\x20\x20\x20\x20\x23\
\x6d\x61\x70\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x77\x69\x64\
\x74\x68\x3a\x20\x38\x30\x30\x70\x78\x3b\x0d\x0a\x20\x20\x20\x20\
\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3a\x20\x36\x30\x30\x70\x78\
\x0d\x0a\x20\x20\x20\x20\x7d\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\
\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\x73\x63\x72\x69\x70\x74\x20\x73\
\x72\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x6a\x61\x78\x2e\
\x67\x6f\x6f\x67\x6c\x65\x61\x70\x69\x73\x2e\x63\x6f\x6d\x2f\x61\
\x6a\x61\x78\x2f\x6c\x69\x62\x73\x2f\x6a\x71\x75\x65\x72\x79\x2f\
\x31\x2e\x31\x31\x2e\x30\x2f\x6a\x71\x75\x65\x72\x79\x2e\x6d\x69\
\x6e\x2e\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e\x0d\
\x0a\x20\x20\x3c\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3d\x22\
\x68\x74\x74\x70\x3a\x2f\x2f\x61\x6a\x61\x78\x2e\x67\x6f\x6f\x67\
\x6c\x65\x61\x70\x69\x73\x2e\x63\x6f\x6d\x2f\x61\x6a\x61\x78\x2f\
\x6c\x69\x62\x73\x2f\x6a\x71\x75\x65\x72\x79\x75\x69\x2f\x31\x2e\
\x31\x30\x2e\x34\x2f\x6a\x71\x75\x65\x72\x79\x2d\x75\x69\x2e\x6d\
\x69\x6e\x2e\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e\
\x0d\x0a\x20\x20\x3c\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3d\
\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x64\x6e\x2e\x6c\x65\x61\x66\
\x6c\x65\x74\x6a\x73\x2e\x63\x6f\x6d\x2f\x6c\x65\x61\x66\x6c\x65\
\x74\x2d\x30\x2e\x37\x2e\x32\x2f\x6c\x65\x61\x66\x6c\x65\x74\x2e\
\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e\x0d\x0a\x20\
\x20\x3c\x73\x63\x72\x69\x70\x74\x3e\x0d\x0a\x20\x20\x20\x20\x24\
\x28\x64\x6f\x63\x75\x6d\x65\x6e\x74\x29\x2e\x72\x65\x61\x64\x79\
\x28\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x28\x29\x20\x7b\x0d\x0a\
\x20\x20\x20\x20\x20\x20\x24\x28\x22\x23\x62\x61\x73\x65\x6d\x61\
\x70\x73\x6c\x69\x64\x65\x72\x22\x29\x2e\x73\x6c\x69\x64\x65\x72\
\x28\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x61\x6e\x69\x6d\x61\x74\x65\x3a\x20\x74\x72\x75\x65\x2c\x0d\x0a\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x76\x61\x6c\x75\
\x65\x3a\x20\x31\x2c\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x6f\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3a\x20\
\x22\x76\x65\x72\x74\x69\x63\x61\x6c\x22\x2c\x0d\x0a\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x6d\x69\x6e\x3a\x20\x30\x2c\
\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x6d\x61\
\x78\x3a\x20\x31\x2c\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x73\x74\x65\x70\x3a\x20\x30\x2e\x31\x2c\x0d\x0a\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x73\x6c\x69\x64\x65\
\x3a\x20\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x28\x65\x76\x65\x6e\
\x74\x2c\x20\x75\x69\x29\x20\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x6d\x79\x74\x69\x6c\x65\
\x2e\x73\x65\x74\x4f\x70\x61\x63\x69\x74\x79\x28\x75\x69\x2e\x76\
\x61\x6c\x75\x65\x29\x3b\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x7d\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x7d\x29\
\x3b\x0d\x0a\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x24\x28\x27\
\x23\x62\x61\x73\x65\x6d\x61\x70\x73\x6c\x69\x64\x65\x72\x27\x29\
\x2e\x6d\x6f\x75\x73\x65\x64\x6f\x77\x6e\x28\x66\x75\x6e\x63\x74\
\x69\x6f\x6e\x28\x29\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x6d\x61\x70\x2e\x64\x72\x61\x67\x67\x69\x6e\x67\x2e\x64\
\x69\x73\x61\x62\x6c\x65\x28\x29\x3b\x0d\x0a\x20\x20\x20\x20\x20\
\x20\x20\x20\x7d\x29\x0d\x0a\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\
\x20\x24\x28\x27\x23\x62\x61\x73\x65\x6d\x61\x70\x73\x6c\x69\x64\
\x65\x72\x27\x29\x2e\x6d\x6f\x75\x73\x65\x75\x70\x28\x66\x75\x6e\
\x63\x74\x69\x6f\x6e\x28\x29\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x6d\x61\x70\x2e\x64\x72\x61\x67\x67\x69\x6e\x67\
\x2e\x65\x6e\x61\x62\x6c\x65\x28\x29\x3b\x0d\x0a\x20\x20\x20\x20\
\x20\x20\x20\x20\x7d\x29\x0d\x0a\x0d\x0a\x20\x20\x20\x20\x20\x20\
\x76\x61\x72\x20\x6d\x61\x70\x20\x3d\x20\x4c\x2e\x6d\x61\x70\x28\
\x27\x6d\x61\x70\x27\x29\x2e\x73\x65\x74\x56\x69\x65\x77\x28\x5b\
\x40\x63\x65\x6e\x74\x65\x72\x79\x2c\x20\x40\x63\x65\x6e\x74\x65\
\x72\x78\x5d\x2c\x20\x40\x61\x76\x67\x7a\x6f\x6f\x6d\x29\x3b\x0d\
\x0a\x20\x20\x20\x20\x20\x20\x76\x61\x72\x20\x62\x61\x73\x65\x6c\
\x61\x79\x65\x72\x20\x3d\x20\x4c\x2e\x74\x69\x6c\x65\x4c\x61\x79\
\x65\x72\x28\x27\x68\x74\x74\x70\x3a\x2f\x2f\x7b\x73\x7d\x2e\x74\
\x69\x6c\x65\x2e\x6f\x70\x65\x6e\x73\x74\x72\x65\x65\x74\x6d\x61\
\x70\x2e\x6f\x72\x67\x2f\x7b\x7a\x7d\x2f\x7b\x78\x7d\x2f\x7b\x79\
\x7d\x2e\x70\x6e\x67\x27\x2c\x20\x7b\x0d\x0a\x20\x20\x20\x20\x20\
\x20\x20\x20\x6d\x61\x78\x5a\x6f\x6f\x6d\x3a\x20\x31\x38\x2c\x0d\
\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x61\x74\x74\x72\x69\x62\x75\
\x74\x69\x6f\x6e\x3a\x20\x27\x4d\x61\x70\x20\x64\x61\x74\x61\x20\
\x26\x63\x6f\x70\x79\x3b\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\
\x68\x74\x74\x70\x3a\x2f\x2f\x6f\x70\x65\x6e\x73\x74\x72\x65\x65\
\x74\x6d\x61\x70\x2e\x6f\x72\x67\x2f\x63\x6f\x70\x79\x72\x69\x67\
\x68\x74\x22\x3e\x4f\x70\x65\x6e\x53\x74\x72\x65\x65\x74\x4d\x61\
\x70\x3c\x2f\x61\x3e\x20\x63\x6f\x6e\x74\x72\x69\x62\x75\x74\x6f\
\x72\x73\x27\x0d\x0a\x20\x20\x20\x20\x20\x20\x7d\x29\x2e\x61\x64\
\x64\x54\x6f\x28\x6d\x61\x70\x29\x3b\x0d\x0a\x0d\x0a\x20\x20\x20\
\x20\x20\x20\x76\x61\x72\x20\x6d\x79\x74\x69\x6c\x65\x20\x3d\x4c\
\x2e\x74\x69\x6c\x65\x4c\x61\x79\x65\x72\x28\x27\x66\x69\x6c\x65\
\x3a\x2f\x2f\x2f\x40\x74\x69\x6c\x65\x73\x64\x69\x72\x2f\x7b\x7a\
\x7d\x2f\x7b\x78\x7d\x2f\x7b\x79\x7d\x2e\x40\x74\x69\x6c\x65\x73\
\x65\x78\x74\x27\x2c\x20\x7b\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\
\x20\x6d\x61\x78\x5a\x6f\x6f\x6d\x3a\x20\x40\x6d\x61\x78\x7a\x6f\
\x6f\x6d\x2c\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x74\x6d\x73\
\x3a\x20\x40\x74\x6d\x73\x2c\x0d\x0a\x20\x20\x20\x20\x20\x20\x20\
\x20\x61\x74\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x3a\x20\x27\x47\
\x65\x6e\x65\x72\x61\x74\x65\x64\x20\x62\x79\x20\x51\x54\x69\x6c\
\x65\x73\x27\x0d\x0a\x20\x20\x20\x20\x20\x20\x7d\x29\x2e\x61\x64\
\x64\x54\x6f\x28\x6d\x61\x70\x29\x3b\x0d\x0a\x0d\x0a\x20\x20\x20\
\x20\x20\x20\x4c\x2e\x63\x6f\x6e\x74\x72\x6f\x6c\x2e\x6c\x61\x79\
\x65\x72\x73\x28\x7b\x27\x42\x61\x73\x65\x6d\x61\x70\x27\x3a\x62\
\x61\x73\x65\x6c\x61\x79\x65\x72\x7d\x2c\x7b\x27\x40\x74\x69\x6c\
\x65\x73\x65\x74\x6e\x61\x6d\x65\x27\x3a\x6d\x79\x74\x69\x6c\x65\
\x7d\x29\x2e\x61\x64\x64\x54\x6f\x28\x6d\x61\x70\x29\x3b\x0d\x0a\
\x20\x20\x20\x20\x7d\x29\x0d\x0a\x20\x20\x3c\x2f\x73\x63\x72\x69\
\x70\x74\x3e\x0d\x0a\x3c\x2f\x68\x65\x61\x64\x3e\x0d\x0a\x3c\x62\
\x6f\x64\x79\x3e\x0d\x0a\x20\x20\x3c\x64\x69\x76\x20\x69\x64\x3d\
\x22\x6d\x61\x70\x22\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\
\x20\x69\x64\x3d\x22\x62\x61\x73\x65\x6d\x61\x70\x73\x6c\x69\x64\
\x65\x72\x63\x6f\x6e\x74\x61\x69\x6e\x65\x72\x22\x3e\x0d\x0a\x20\
\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x69\x64\x3d\x22\x62\x61\
\x73\x65\x6d\x61\x70\x73\x6c\x69\x64\x65\x72\x22\x3e\x0d\x0a\x20\
\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0d\x0a\x20\x20\x3c\x2f\x64\
\x69\x76\x3e\x0d\x0a\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0d\x0a\x3c\
\x2f\x62\x6f\x64\x79\x3e\x0d\x0a\x3c\x2f\x68\x74\x6d\x6c\x3e\x0d\
\x0a\
\x00\x00\x03\x30\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
\x00\x00\x02\xf7\x49\x44\x41\x54\x38\x8d\x6d\xd1\x49\x4c\x53\x51\
\x14\x06\xe0\x83\x82\x60\xd4\x05\x93\xc6\x89\x84\x18\x54\x10\x1b\
\x8d\xa0\x16\x91\x21\x2a\xd6\x80\x6d\xdf\xbb\xaf\xaf\x03\x95\x42\
\x29\x85\x92\x8e\x16\x8c\x20\x28\x48\x00\x07\x10\x4a\x07\x81\xda\
\x82\x94\x49\x05\x15\x4d\x8c\xe8\x42\x13\x62\xe2\x46\xd9\xa8\xd1\
\x9d\x89\xd1\xb8\x65\xc5\x02\x93\xe3\x6d\x81\x9a\xaa\x37\xf9\x73\
\x92\x7b\xcf\xfb\xee\x79\xb9\x00\x7f\xad\x35\xd1\xb1\xb0\x21\x3e\
\xe5\xbf\x89\xdb\xb4\xf9\xef\xf6\xc8\xb5\x35\x2d\x0f\x72\x49\x0b\
\x9c\xad\x72\x03\x6f\xf6\x83\xf2\xfc\x08\xa8\xec\x81\x50\xe5\x4c\
\x7e\x38\xa3\x75\x42\x66\xbe\xee\xff\x50\xea\x01\x09\x14\x6b\xfb\
\x40\x69\x1b\x06\x75\xdd\x18\x28\x6c\x81\xf5\x9c\x79\x38\x5b\x66\
\x19\xc9\x51\xd8\xc7\x13\xcb\x2f\x3d\x84\xf2\xc6\x29\x50\xda\x47\
\xe1\x38\xd7\x16\x89\x6c\x4c\x48\x81\x22\x75\x17\x28\xcc\x3e\x90\
\x99\x86\x62\x19\xc3\x60\xa7\xfe\xf2\xd4\x42\x43\xdf\x6b\x6c\xf2\
\xbc\x41\xeb\x8d\x17\x4b\xa5\xf5\x13\x93\xbc\x6d\x6c\x47\x10\x0a\
\x22\xc1\x49\xc2\x6b\x77\xb6\x1c\xd8\x1a\x0f\x90\xda\xc1\x68\xd6\
\x30\xf0\xdc\xda\xf9\x0c\x89\xf5\x3e\x9e\xd4\x8f\xe3\xa9\x9a\x7b\
\x48\xea\x9f\xa0\xbd\x77\x0e\xf5\x2d\x33\x5f\x43\x08\x9d\x24\xf8\
\x3b\xe1\x29\xb2\x4e\x9f\x07\x59\x6d\x3f\x94\x68\x9d\x35\xa6\xab\
\x8f\xb0\xb0\xcc\x8b\xb9\x6a\x2f\x3a\x7d\x33\x38\xff\xf9\x27\x9e\
\xac\x9e\x44\x91\x71\x1a\xed\x3d\x73\xa8\xaa\x1f\x9f\xd4\x34\x4e\
\x03\x31\xfa\x20\x7e\xdb\xfe\x65\xe0\x18\x73\x15\x78\x0a\x50\x64\
\x9e\x31\xf8\xf0\x08\x71\xa0\x50\xee\xc1\x73\xa6\x6e\xbc\xd2\xf3\
\x18\x0b\x2b\x47\x43\x08\xa9\x9b\x41\xeb\xf5\xd9\x25\xb9\x6d\x34\
\x51\x6e\x19\x82\x2d\xbb\x72\xff\x00\xac\xde\x15\x55\x66\xf3\xfd\
\x2a\x54\x3a\x30\x4b\xd2\x8d\x87\x29\xd2\xed\x1a\xc5\xf7\x9f\x7e\
\x60\x9e\x66\x88\x22\x81\x10\xd2\xe4\x9e\x43\x49\xcd\x60\xbe\xc2\
\x3a\x1c\x09\x90\x6a\x17\x94\x9a\x06\x16\x0b\xe4\xb7\xf0\x60\xf1\
\x35\x3c\x24\xe9\xc2\x2e\x67\x00\xdf\x7d\xfc\x8e\xc7\x54\x83\x98\
\x57\xe6\xc7\x13\x74\x92\x06\xc7\x2b\x94\x1a\xbc\xc2\x88\x09\x72\
\xa4\xad\x74\x02\x27\x48\x2b\x7b\x5f\x32\x3a\x0f\x0a\x4e\xb7\x51\
\xa4\x13\xbb\xfa\x46\xf0\xdd\x87\x6f\x78\x94\x77\x53\x64\x00\x19\
\xf3\x04\x56\x35\x3f\x58\xe0\x2d\x77\xd7\x73\xc6\x3b\x7f\x80\xcc\
\x5c\x0d\x30\xda\xeb\x20\x52\xb5\x8b\x74\x75\x7e\xcc\x23\x37\x50\
\x50\xd4\x86\x9c\xa6\x11\x1b\x3a\xc6\x30\x9b\xed\xc1\x02\x75\x3f\
\x5a\xda\x9f\xa2\x54\xef\xea\x54\x9a\x3d\x20\xd6\xde\xa4\xcf\xbf\
\x73\xe5\x19\x33\x85\x20\x51\x5a\x80\x51\x5f\x00\x91\xb2\xa5\xb5\
\xd2\xee\x47\x46\xe7\xc6\x02\xbe\x1b\x0b\x15\xbd\x28\xad\xf6\xa2\
\xb1\x65\x1a\x59\xbd\x63\x96\x94\x37\xc7\xf1\x15\x17\x41\x2c\xab\
\x82\xa4\xa4\xe4\x65\x20\x3d\x3d\x03\x24\x44\x0d\x2c\xaf\x89\xe3\
\x38\x6e\x7b\x31\xd1\x55\xb1\x15\x1d\x6f\x55\xc6\xdb\x8b\x6a\x8b\
\x77\x49\x6e\xe8\xfd\x52\x22\x37\xb7\xb1\x84\x4f\xe3\x64\x8a\x04\
\x56\x5e\x11\xc5\xb0\x32\x48\x4e\x0e\x03\xe9\xc0\x12\x42\xc3\x85\
\x00\x9a\x34\x42\xc8\x5e\x5a\x33\x68\xf6\xad\xd4\x3d\x34\xa9\x84\
\x70\x09\xb4\x2f\x8a\x9e\x47\x02\xc1\x0d\xb2\x8c\x44\xd1\xac\xa5\
\xb7\xc5\xd0\xac\x23\x44\x16\xbb\x52\xa3\xe9\xfe\x9a\xd5\xbe\x08\
\x20\x26\x26\x06\x84\x42\x21\x88\xc5\xe2\xf0\x21\xbd\xed\x9f\xac\
\x9e\x89\x44\x22\x10\x08\x04\xa1\x6f\x7f\x03\x6c\xd7\x55\xbb\xa7\
\xf7\xc4\xac\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
\x00\x00\x08\xb7\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
\x00\x00\x08\x6e\x49\x44\x41\x54\x58\x85\xc5\x97\x7b\x70\x55\xd5\
\x15\xc6\x7f\x7b\x9f\x73\x6e\x6e\x6e\xde\xb9\x97\x20\x84\x47\x5e\
\x04\x12\x02\x11\x05\x05\x22\x05\x44\x0a\xa2\x28\xd6\xe2\x4c\xa7\
\x75\xa8\x76\xac\xa3\x33\x3a\x63\x67\xb4\xd3\x87\x6d\x1c\x2b\x7d\
\x4d\xff\x68\xcb\xd8\x56\x1d\x29\x76\xaa\x45\xc6\xe7\x0c\xa1\xa9\
\xa0\x11\x5a\x01\x8d\x41\x1e\x01\xcc\x83\x24\x4a\x42\x42\x72\xf3\
\xbc\xaf\xdc\xdc\xb3\x77\xff\x38\xe7\xde\x24\xd5\x19\x69\xa7\x33\
\xee\x99\x35\xe7\xdc\x39\x77\xaf\xf5\x9d\x6f\x7d\x6b\xad\xb3\xe1\
\x4b\x5e\xe2\x4a\xff\xb8\x6e\x5d\xad\xd9\x91\x61\xad\x11\x88\xed\
\x02\xb1\x46\x43\x00\xc8\x71\x1f\x8f\x08\x18\x10\xe8\x23\x36\x7a\
\x5f\x71\x78\xe2\x48\x43\x43\x6d\xe2\xff\x02\xa0\x68\x5b\x6d\xae\
\x18\x4f\xfb\x31\x82\xfb\xca\xe6\xcd\xe0\xc6\x15\xa5\x59\x2b\xaa\
\xe6\x8a\xab\x02\x59\xf8\xb3\xd3\x01\x18\x1c\x8d\xd2\x1b\x1c\xa3\
\xb1\xf9\xa2\x3e\xf4\x41\xfb\x58\x6b\xe7\x65\xd0\x3c\xab\xd3\xc6\
\x7f\xd6\xf9\x7a\xed\xf0\xff\x0c\xa0\x68\xf3\xce\x87\xa5\x21\x9e\
\xdc\x5c\xb3\x28\xfd\xfe\xaf\xaf\xb4\xca\xe7\x05\xae\x88\xb3\xb6\
\x4f\x83\x3c\xf3\xca\xf1\x89\xfd\x87\xcf\x46\x95\x96\x8f\x77\xd6\
\x7d\xff\x77\xff\x15\x80\xa2\x75\xb5\x5e\xed\xf3\xfe\x75\x71\xe9\
\xcc\x4d\x4f\x3e\xf8\xd5\x8c\xca\xe2\x02\x10\x8e\xe3\x7f\x9d\xfc\
\x84\x13\x1f\x5f\x22\x38\x1a\x61\x70\x24\x86\x10\x10\xc8\x4d\xc7\
\x9f\x93\xc1\xb2\xf2\x59\xac\x5e\x3a\x97\x92\x39\xf9\x00\x7c\xdc\
\xd9\xcf\xe3\x4f\xbf\x15\x3e\xdd\xd6\x5b\x4f\x38\xfa\xcd\xce\x86\
\xda\xd8\x17\x02\x58\xbc\xbd\xd6\x13\x0e\xa7\xbd\xbf\x6d\x7d\x55\
\xc5\x4f\xee\xdf\xe0\x49\xf7\x58\xb4\x7e\x1a\xe4\xb9\x37\x1a\x69\
\x3c\xdb\x8d\x34\x24\x86\x94\x08\x21\x90\x52\x20\x10\x29\x2f\x5a\
\x6b\xb4\xd6\x2c\xaf\x28\x64\xc7\x2d\x57\x53\x3a\x27\x9f\x58\x3c\
\xc1\x53\xcf\xbe\x1d\x7f\xe5\xd0\xe9\x73\x3e\x5f\xec\xba\xe6\x7d\
\xb5\xf1\xa9\xf1\x8c\xe9\xe1\xb5\xc8\x2c\x3e\xf6\xea\xb6\x1b\xab\
\x56\xee\x7c\x68\x53\x9a\x65\x1a\xbc\xde\x70\x96\x27\x9e\x7b\x9b\
\x4b\x03\x21\x4c\xd3\xc0\x9a\x62\xa6\x61\x60\x9a\x06\x86\x29\x31\
\x0c\x89\x69\x18\x48\x43\x72\x29\x18\xe2\xad\xe3\xed\x64\xf9\xd2\
\xa8\x2c\x9e\xc1\x8d\x2b\x4a\x8d\xbe\xa1\x70\xde\xc9\x96\x81\x6b\
\x86\x5b\x0e\xee\x85\x27\x3e\x1f\x40\xf1\x16\xef\xa3\xab\xae\x2e\
\xba\xf7\x37\x8f\xdc\x92\x6e\x18\x92\xdf\xef\x3d\xca\x9e\xfd\x1f\
\x61\x18\x06\xa6\x69\x62\x59\x6e\x70\xcb\xf9\x6d\x9a\xc6\xa4\x49\
\x03\xc3\x65\xc7\x30\x24\x00\x8d\xe7\x7b\x08\x45\xe2\x5c\x5b\x31\
\x9b\x35\xcb\x8a\xcd\x33\xed\x7d\x73\x43\xd9\xfb\xe3\x43\x2d\x07\
\xdf\xfb\x4c\x0a\x0a\xef\xd8\xe9\xcf\xd4\x56\xcb\x9b\xbf\xfd\x76\
\xfe\xbc\x99\xb9\xbc\x54\x7f\x92\xe7\xde\xf8\x10\xd3\x32\xa7\xbc\
\xad\x64\x69\xd9\x0c\x96\x94\x14\x50\x51\x14\xa0\x20\xcf\x47\x6e\
\x56\x3a\xe3\xf1\x04\x7d\x83\x61\x9a\x5a\xfa\xa8\x3f\xde\x41\x28\
\x1a\xc7\x56\x0a\xdb\x56\x24\x6c\xc5\xdd\x9b\x97\x72\xe7\xfa\x0a\
\x2e\x5e\x1e\x65\xeb\xc3\xbb\x07\x47\x89\x97\x77\xbf\xf6\xc3\xe0\
\x34\x06\xfc\xa5\x37\xed\xba\xe7\xf6\xe5\xd7\xdf\xbc\x7a\xa1\x3c\
\x73\xa1\x8f\x9f\xef\x39\x8c\x69\x18\x58\x2e\x00\xcb\x72\xee\x7f\
\xfd\xc0\x57\x58\x34\xdf\xcf\x8c\x5c\x1f\x3e\xaf\x07\xc3\x90\xa4\
\x79\x4c\xf2\xb2\xbc\x2c\x9a\x9f\xcf\xaa\xaa\xd9\x9c\x6a\xef\x27\
\x16\xb7\x1d\x9d\x00\xa7\xda\xfb\xa8\x2e\x9b\x49\x49\x61\x1e\x13\
\xb6\xf2\x9c\x38\xdd\x5d\x30\xd4\x72\xf0\x4d\x00\x09\xb0\x78\x5d\
\x6d\xa6\x65\xca\xbb\xee\xb9\x6d\x85\xa1\xd1\x3c\xbd\xef\x38\x52\
\xc8\x69\x14\x3b\x40\x4c\x84\x10\x08\xe1\x10\x17\x4f\x28\xe2\x09\
\x85\x94\x12\xe9\x0a\x33\x3f\xcb\xcb\xf6\xf5\x0b\xb1\x2c\x13\x8f\
\x65\x62\x59\x26\x86\x34\xd8\xbd\xff\x23\x34\x70\xf7\x96\x6b\x0c\
\xd3\x34\xee\x5a\xbc\xae\x36\x13\xc0\x04\x88\xf8\xbc\x5b\xd6\x5e\
\x5d\x2c\xf3\xb3\xd3\x39\xd5\xd6\x4b\xcb\x27\x41\x3c\x1e\x73\x8a\
\xe8\xdc\x34\x98\x06\x09\x5b\x71\xfc\x7c\x3f\xef\x35\xf7\xd2\x3b\
\x14\xc1\x10\x82\x0d\xcb\xe7\x73\x7b\x4d\x69\x0a\x5c\xe5\x7c\x3f\
\x1e\xcb\x24\x21\x6d\x5c\xac\x74\x74\x0f\x73\xae\x73\x80\xca\xa2\
\x00\x6b\xaf\x2d\x91\x07\x8f\xb6\x6e\x01\x5e\x96\x8e\xf6\xf5\x8e\
\x9b\xae\x5b\xe0\x43\xc0\x91\x8f\xba\x1c\x31\x19\x4e\xde\x0d\x37\
\xf7\x49\x30\xbf\x78\xe9\x04\xaf\xfd\xf3\x02\x03\xa3\xe3\xa4\x79\
\x2c\x2c\x8f\xc5\xd9\xae\x41\xb4\xd6\x29\x31\x4b\x49\x8a\xb1\xa9\
\x2c\xbe\xdf\xdc\x0d\xc0\xc6\x95\xe5\x3e\x21\xc5\x8e\x14\x03\x68\
\x96\x55\x95\xcd\x04\xa0\xf5\x93\xe0\x34\x35\x9b\xc6\x64\x2a\x2c\
\xd3\x24\x34\x6e\x63\x59\xa6\x0b\x4e\x22\xa5\x60\xcd\x92\xc2\x54\
\x0f\xd0\x5a\x73\xb1\x3f\x8c\x65\x19\xae\x6b\x8d\xd6\xa0\x94\xe6\
\xc2\xa5\x21\x00\x2a\x4b\x0a\xd0\x5a\x2f\x9b\x04\x20\xf0\x17\xe4\
\x67\x02\x4e\x5f\x97\x6e\xf0\xa9\xe6\x00\x91\x93\x3d\xc0\xb5\x9a\
\xc5\x57\xb1\xaa\x72\x66\x2a\xb8\xad\x14\xef\x9e\xee\xc3\x32\x0d\
\xd0\x4e\x73\x52\x4a\x63\x18\x92\x91\xd0\x38\x08\xc1\x8c\xdc\x0c\
\x34\xda\x3f\x95\x01\xf2\xdc\xc1\x32\x16\x89\xbb\xa2\x12\x93\x40\
\xa4\x93\x12\xc3\x6d\x3c\xc9\xd2\x5c\xbb\x74\x16\x6b\x16\x4f\x09\
\x6e\xdb\x1c\x68\xec\xa6\x67\x30\x8a\x69\x18\x4e\x60\x25\x31\x6c\
\xc7\x4f\x24\xe6\x0c\xc8\x9c\xcc\xb4\x54\xfd\xcb\x64\x37\x18\x1a\
\x8d\xba\x0f\xbd\x48\x21\xa6\x9b\x94\x18\x52\x4c\x63\xa3\xba\x38\
\x9f\xd5\x8b\x02\x28\xa5\x50\x4a\x61\xdb\x36\x75\x1f\x5e\xe2\xe3\
\xee\x31\xb7\x3b\x4e\x6f\x4c\x52\x0a\xb2\x33\xbd\x00\x8c\x84\xc7\
\x53\x2d\x48\x02\x08\x4d\xb0\x7f\x28\x0c\x40\x20\xcf\x87\x10\x38\
\x8a\x96\x8e\x49\x29\x10\x2e\x2b\x49\x87\xd7\x2f\xf4\xa7\x82\x2b\
\xa5\x38\xd6\x32\x44\x57\x7f\xd4\x65\xca\x01\x9c\x64\x52\xb8\x2f\
\x92\x9f\xed\x00\x08\x0e\x47\x10\x10\x9c\x04\x20\xe4\x89\xe6\x0b\
\x97\x01\x58\x38\x2f\xe0\xd4\xb9\x10\x08\x40\x20\xdc\xf2\x22\x55\
\x66\x52\x08\xb2\xd3\x8d\x14\xf5\x4a\x29\xda\xfa\x22\x29\x86\xa4\
\x10\x08\x91\xbc\x3a\x7b\x11\x82\x92\xd9\x79\x00\x9c\xeb\xec\x47\
\x48\x79\x22\xa5\x01\xad\xf5\x9e\x43\xc7\xdb\xd6\xdd\xb1\xbe\xd2\
\x57\xb3\x74\x2e\x07\x8e\xb6\x4d\xf6\x68\x91\xbc\x4c\x19\x9c\x42\
\xf0\x87\xfa\x0e\xd2\x3c\x16\x1e\x8f\x85\xc7\x63\xa2\x34\xa9\x06\
\xe5\xe2\x9f\xb2\x9c\xdd\xcb\x17\xcd\x02\xe0\xad\x63\xad\x11\x65\
\xab\x3d\x29\x00\xbe\x48\xac\xee\x48\xd3\x05\x35\x3c\x16\xa3\xb2\
\xa4\x80\x05\xf3\xfc\x74\xf5\x8e\x90\x14\x68\xf2\x4d\xd1\xc9\xb2\
\xd2\xec\x58\x3b\x27\x35\x92\x01\x5e\x3e\xd6\x8f\x52\xca\x61\x24\
\xb9\x07\x8d\x76\x9d\x14\xcd\xca\x61\xc1\x9c\x7c\x46\x43\xe3\xbc\
\xf3\x7e\xab\xf2\x45\x62\x75\xa9\x14\x34\x37\xd4\x86\x12\x09\xb5\
\xf7\x2f\x75\x4d\xb6\x00\xee\xbd\x75\x19\x42\x08\xd7\x99\x53\xc7\
\xc9\x7b\xa5\x34\xca\x56\x58\x06\x58\x06\x98\xd2\xb1\xf8\x44\x82\
\x89\x84\x4d\xc2\x56\x28\x5b\x39\xff\x53\x1a\xad\x1c\xe0\x77\xad\
\xaf\x00\x01\x7f\xfb\xc7\x49\xdb\xb6\xd5\xde\xe6\x86\xda\xd0\x64\
\x15\x00\xf1\x84\xf5\xd8\xee\xd7\x3e\x18\xee\xee\x1f\x63\xd1\x7c\
\x3f\xdf\xd8\x58\xe5\x04\x57\x4e\x8e\xed\xe4\xd5\x9d\x70\x53\x05\
\xa8\x94\x22\x3e\x31\xc1\xc4\x84\x4d\x22\x61\x93\xb0\x6d\x6c\xdb\
\x4e\x3d\xdb\x5a\x53\x46\x69\x61\x2e\x7d\xc1\x10\x7f\xda\x77\x6c\
\x38\x16\xb7\x1e\x4b\xc6\x4d\x4d\xc3\xd1\xf6\xfa\x68\x4e\xd9\x06\
\xdd\xd1\x33\x5c\xb3\xb9\xa6\xdc\xaa\x2c\x0a\x10\x8e\x4d\xd0\xde\
\x3d\xe4\x28\x5a\x48\xa7\x22\x84\x93\xd1\x6b\x4a\x73\xb1\xb5\x26\
\x61\x3b\xa0\x3e\x68\x1b\x26\x61\x2b\x07\x80\xcb\xc4\x44\xc2\xe6\
\x86\x25\x85\x6c\x5d\x5d\x4a\xc2\x56\xfc\x60\x57\x7d\xa4\xad\xab\
\xff\xa9\xae\x03\x8f\x1e\xfa\x0f\x89\x25\x97\x16\xa5\xb7\xfe\xea\
\xf5\xed\x9b\xaa\x37\xfd\xf4\xbb\x1b\xd2\x84\x10\x1c\x6a\xec\x60\
\xef\xa1\x73\x48\x29\xdd\xe9\x96\xfc\x18\x71\x9b\x93\x5b\x66\x5a\
\x83\xd2\x0e\xfd\x09\x37\x05\xb7\xad\x2e\xe1\x86\x25\x85\x68\x60\
\xe7\xf3\x0d\xe3\x2f\xd6\x35\xd5\xb7\xbd\xf9\xe8\x36\x10\xfa\x33\
\x0c\x38\xeb\x09\x66\x57\xdf\xf0\xea\xc9\xf3\x03\x5b\x07\x46\xa2\
\x81\x55\x4b\xe7\x19\x0b\xe6\xfa\xa9\x2e\x2b\x20\x38\x1a\xa5\x6f\
\x28\xe2\x40\x76\xb7\x27\xf3\x3c\xf5\xe3\x23\x61\xdb\x2c\x28\xcc\
\xe5\x5b\x1b\x2b\xa8\x2a\x0e\x30\x3e\x61\xf3\xcb\x17\x0e\xc7\x5f\
\xac\x6b\x3a\x93\x96\x16\xd9\xdc\x7f\x76\xbd\x3d\xbd\x3e\x3e\x67\
\xcd\x59\xf5\x48\xba\x95\x1f\x78\xa9\xba\x7c\xce\xc6\xa7\x1e\xba\
\xd9\x57\xea\x7e\xe5\x5e\xbc\x3c\xca\xa9\xf6\x7e\x5a\xbb\x87\x18\
\x0b\x4f\x30\x16\x8b\x23\x85\x20\x27\xd3\x4b\x8e\x2f\x8d\xb2\xc2\
\x5c\xaa\x8a\xfd\xcc\x0e\x38\x73\xa5\xe3\xd2\x30\x3f\xda\xf5\xf7\
\x68\x53\xf3\x85\x77\x7b\x8f\xbd\x70\x5f\x74\xb0\x2d\x08\x44\xbf\
\x10\x00\xe0\x03\xb2\x0b\xd7\x7e\xef\x41\x6f\x76\xe0\x91\x3b\x37\
\x2c\xf3\x7e\xe7\x6b\x2b\xcc\xf9\x57\xe5\x5e\xd1\x51\xea\xd3\xcb\
\xa3\x3c\xff\x46\x63\xe2\xe5\x03\x1f\x8e\x47\x83\x5d\xcf\x74\x1f\
\xfd\xe3\x6e\x60\xcc\xb5\x21\x40\x7d\x11\x80\x34\x20\x0b\xc8\x4a\
\x2f\x28\x9d\xed\xaf\xdc\xf6\x80\x95\xe1\xdf\x56\x55\x3a\x4b\x6c\
\x5e\x53\x99\x7e\x6d\x45\xa1\x98\x91\xe7\x23\x27\xc3\x0b\x02\x46\
\x43\xe3\x0c\x8c\x44\x68\x3a\xdf\xa3\xeb\x0e\x37\xc7\xce\xb4\xf6\
\xe8\xd8\x58\x4f\x7d\xf0\xd4\xab\x7f\x1e\x1f\xee\xe9\x75\x03\x87\
\x80\x41\x20\x7c\x25\x0c\x80\x53\xa2\x19\x40\x36\x90\x61\x59\xbe\
\xac\xec\xf2\x8d\x2b\x7d\x05\x0b\x36\x5a\xe9\xd9\xd5\x60\xe4\x68\
\x21\x7d\x8e\x13\x15\x45\xdb\xa3\xf1\xf0\xd0\x99\x48\xdf\xf9\x77\
\x46\xdb\xde\x69\x4a\x24\x62\x21\x1c\xba\x43\xc0\xb0\x0b\xe2\x33\
\xe7\xc5\x2b\x3d\x9c\x1a\x80\x05\x78\x5d\x76\xbc\x38\x5d\xd4\x72\
\x81\x2a\xc0\x06\xe2\xae\xc5\x80\x71\xf7\xfe\x8a\x0e\xa9\x5f\xda\
\xfa\x37\x3d\x87\xb9\xea\xf9\xa4\xbb\x03\x00\x00\x00\x00\x49\x45\
\x4e\x44\xae\x42\x60\x82\
\x00\x00\x04\xc4\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
\x79\x71\xc9\x65\x3c\x00\x00\x04\x66\x49\x44\x41\x54\x78\xda\xec\
\x57\x3d\x6c\x1c\x45\x14\x7e\x33\xb7\xde\x3b\xc7\xc0\xf2\xe3\x20\
\x30\x86\x20\x82\x90\xe8\xe8\x22\x71\xa9\x02\xc4\xc5\x15\x18\x64\
\x93\x88\x38\x24\x08\xda\xd0\x40\x5c\x58\xa2\x42\xa4\x30\x54\x40\
\x19\x09\x91\x84\xf0\xe3\x4b\xb2\x48\x5c\x91\x10\xa2\x80\x59\x24\
\x77\x34\x34\x88\x48\x38\x1c\x4e\xe0\x9c\xc8\x76\x38\xfb\x7e\x76\
\x67\x78\xf3\xb3\x7b\x33\x7b\xeb\x8b\x5c\x18\x37\x39\x69\x4e\xf3\
\xe6\x7b\x33\xf3\xe6\xe7\xfb\xe6\x2d\x01\x00\x67\xfa\x0b\xf0\x21\
\x07\x25\xce\x40\xfe\x08\x05\x34\xe1\xca\xdb\xaf\xc0\xd3\x68\xf2\
\xd7\x4e\xcc\xfb\x9c\x42\x89\x31\x2e\x71\x4a\x09\x70\xce\x2b\xa7\
\x0e\x3e\x3e\x2a\xec\xd5\xa9\x97\x7d\x87\x90\x12\x36\xea\x01\x08\
\xb4\x81\x57\x06\xde\x3f\x2b\xf1\x4f\xa7\x9b\x7e\x0e\xdc\x92\x01\
\x03\x83\x56\xe5\xf0\x64\x7e\xd4\x41\xbb\x5f\x4c\x3e\x5e\x3c\x8f\
\x13\xe7\xa4\x03\x67\x11\x94\x83\x91\x9d\x58\xdd\x86\x85\x31\xea\
\x94\x8e\xec\x1d\x02\x87\xa8\x01\x42\x1c\xe8\xa3\xef\xaa\x25\xd9\
\x17\x7f\x2e\xe1\xa5\xdc\xd8\x1b\xb8\x94\x82\x76\x68\x00\x29\x1f\
\x4f\x70\x4a\xdc\xd2\x73\x2f\x8a\xc0\x15\xcc\x70\xa1\xdf\xfb\xae\
\xc4\x45\x00\x05\x11\xd9\x5a\x6b\x11\x7e\xff\xab\x2c\x1d\x9e\x7c\
\x64\x0c\xf4\x62\x5d\x19\x10\x3a\x2c\x35\x01\x7e\xae\xaa\x01\x9e\
\x1d\x16\x83\x48\x07\x35\x23\x56\xa3\xb5\x25\x08\x7f\x39\x2f\x4d\
\xe7\x99\x11\xd0\xbb\x51\x50\x0b\x02\x68\x60\xff\xea\x6f\xaa\xff\
\xf0\x53\x31\x0c\x05\x11\x00\x01\x69\x34\xc1\x1b\x18\x56\x1e\x58\
\x57\x6d\x40\xe2\xbf\x26\x0e\x72\x6f\xbf\x46\x99\xda\x46\x0b\x0f\
\x57\x21\xbc\x6f\x50\xad\x10\xeb\x6e\x07\x4a\x8e\x75\xe0\x81\x4e\
\x3d\x6e\x76\xe2\x86\x82\xeb\xc2\xa0\xb7\x53\x85\x85\x75\xda\x71\
\xc2\xc9\x08\x44\x38\xd4\xfd\x77\x29\x5b\xd4\x29\x21\x86\x03\x85\
\x46\x5f\x08\x6c\xfb\x83\xea\x04\xb0\xee\x9a\xb3\xa0\xab\x83\x87\
\x79\x8f\x82\xc1\xe9\x4f\x16\x00\x22\x00\x5e\x5f\x82\xa0\x1c\x4c\
\x14\xcd\x4b\x22\xda\x40\xef\xc3\xda\x4a\x2d\x38\x33\xcb\x8a\x5c\
\x3b\x88\x80\x1a\x2b\x8b\x09\x7e\xbd\xde\x08\x1e\xfa\xf6\x62\xd1\
\xbc\x84\xd7\xb0\x2d\xc6\x57\xea\x0b\xc1\x0f\x33\x43\x45\xf3\x92\
\x8b\x36\x81\x8b\x38\xee\xc6\xf2\x18\x96\x41\x1d\x90\x5c\x04\x96\
\x45\x2c\x57\xb5\xbd\x69\x38\xd1\x8d\xfd\xfa\xc2\xc4\xfb\x2a\x22\
\x6f\x88\xc5\x6b\x7b\xd3\x70\x19\xc0\x67\xc8\x53\x8a\x3c\x65\x7a\
\x8b\xc4\xf9\x47\x24\x9c\x3b\x7c\xb4\x6f\xb7\xb0\x1b\x53\xa3\xbe\
\x03\x36\xcf\x23\x80\x4a\xfe\xd8\x39\xc9\xf3\x57\x4f\xcd\xfb\xb8\
\xaf\x96\x4e\x10\x06\x95\xcf\x0f\x3e\x2a\xf1\xe9\x2f\x51\x67\x84\
\x8e\xe8\xee\x54\xcc\x8a\xf8\xe4\x7e\x50\x3a\x40\x70\xf2\x3d\xa3\
\x36\x4f\x2f\x7e\xe3\xec\xd2\x3a\xc0\x73\x28\x32\xb9\xb1\x37\x2d\
\x9e\x83\xc1\x73\x82\x93\xbf\xf5\xc2\xb0\xa5\x13\x1f\x5f\x58\xe8\
\xe0\x38\xf9\x58\x4a\x67\x66\x82\x11\x5b\x07\xba\x78\xaa\x76\x23\
\xaf\x7a\xf0\x0c\x9e\x43\xc2\x73\xb1\xf2\xb4\x4e\x70\x43\x07\x58\
\x86\xce\xd8\x3a\xd0\x83\xa7\xea\x8f\x40\x2b\xc5\xf3\xbc\x89\x93\
\x0c\x9d\x30\xf0\x5e\x3a\xe3\xdc\x8e\xa7\xeb\xf1\x3c\x6f\x44\x49\
\x33\x74\x82\x18\x03\xd0\x0c\x9d\x89\xbb\x4b\x1d\xc8\xe2\xe9\x5a\
\xf3\xe6\xaf\x26\xcf\x1f\x4e\xf1\xfc\xba\xc1\x73\xa1\x09\x67\x66\
\x21\xa5\x13\x37\x12\xfc\xdf\x1e\x3a\xb3\x9e\x0e\xe0\x25\x87\x1b\
\x58\xe6\xf5\x20\x5b\xa2\x03\x4d\xf1\xd2\xfe\x2f\x3a\xf0\xc1\x57\
\xf0\x13\xee\xc5\x2e\x93\xa7\x58\xae\xbc\x33\xae\xf2\x81\x03\x27\
\xff\xec\xca\x07\x90\x26\x95\xd3\x13\x3b\x24\xcf\x9b\x53\x2f\xe1\
\x7b\x0f\x96\x4e\x84\x98\x0f\x14\x8e\xf9\x49\x3e\xe0\xa4\x74\x46\
\xe4\x03\x87\x74\x3e\xb0\x4d\x4c\xde\x2b\x1f\x50\x3c\x5f\x3f\x1f\
\xc8\x11\xe8\xd2\x09\x9e\xca\x07\xf6\xf4\xc8\x07\xf2\x77\xf2\x81\
\x2d\xcf\x07\x6e\x21\x27\x67\x90\xa7\x2c\x16\x0e\xb1\x8a\x36\x54\
\x3b\x3c\xff\x3b\x38\x3b\xdb\xb6\x78\x6c\xf2\xfc\x5a\xbd\x8d\x3a\
\x71\xd9\xd2\x89\x85\x7a\xab\x2b\x1f\x00\x63\x82\x5b\xb7\xc9\x07\
\x84\xeb\x4d\x2c\x7f\xe8\xfa\x96\xe8\x40\x4b\xeb\x00\xdf\x6c\x1d\
\xe8\x6b\xbf\xbb\xbf\x46\xa2\xc8\x03\xeb\xc3\x80\x2c\x3b\xef\x7d\
\xbd\x5d\x98\x27\x3f\x64\x35\x24\xa3\x67\xc2\x94\xf0\xe5\x03\x47\
\xa9\xc4\xa7\xcb\x50\x43\x6a\x79\xe6\x11\x51\x0e\x73\x93\xfb\x40\
\xe4\x13\xe4\xd0\xe9\x6a\x2d\x62\xa1\x67\xe9\x0c\xb0\xb9\x13\x13\
\x4f\xec\x16\xab\x1f\xe0\x08\x3a\xe3\xaf\x5b\x3c\x0e\xcb\xc7\x3d\
\x81\xa9\xe7\x94\x78\xcf\xa7\xf2\x85\x4b\x3e\x49\x70\x3c\x24\x6f\
\x5f\xb7\x8e\xc4\xf9\x04\x8d\x30\xf8\x23\x7b\x77\x58\x3a\xf2\xc9\
\x85\xab\x12\x77\x24\xd7\x33\xdf\x7b\xde\xd1\x81\x0c\x1e\xdb\x3a\
\xb1\xae\x8e\x88\x57\x9b\x64\xe9\x88\x7e\xb8\xf2\xf1\xa5\xe8\x7a\
\xef\x5d\x30\xdf\xe3\x0c\x1e\x73\x48\xfd\x36\xa8\x23\xb6\x0e\x6c\
\x9c\xc7\xb6\x4e\x6c\x5c\x47\x08\x25\x1d\x1d\x58\xa8\x37\x83\xa1\
\xca\x25\x8b\xc7\xab\x8c\x27\x3a\xb0\x8c\x9c\xbd\x2c\xf2\x05\xe3\
\x92\xc5\x79\xbd\xb0\xb3\xbe\x2b\x56\x8d\xef\x8a\xe6\xf2\x3f\xc1\
\xb9\x1f\x43\x2b\x5f\x10\x6d\x77\x74\x40\xe0\xff\x09\x30\x00\x56\
\x47\x0d\x40\x22\x61\x77\xb1\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
\x42\x60\x82\
\x00\x00\x04\x4f\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
\xa7\x93\x00\x00\x04\x04\x49\x44\x41\x54\x48\xc7\x75\x96\x4f\x68\
\x5c\x55\x14\xc6\x7f\xe7\xde\xf7\x66\x92\x26\x93\x49\xd3\x26\x1a\
\x8b\xa9\x58\x17\xda\x14\x21\x52\x10\x8a\x2e\xdc\x48\x85\x56\x10\
\x0c\x0a\x5d\x74\x61\x15\xc4\x95\x0a\x2d\x45\x71\xe5\x4a\x44\x04\
\x77\xfe\x29\x82\x7f\x36\x4a\x15\x45\xb2\x10\xb1\x05\x49\xb5\xad\
\x52\xbb\xd1\x86\x5a\x9a\x7f\x26\x69\xda\x49\x6c\x32\x93\xc9\x9b\
\x77\xef\x71\xf1\xde\xbc\x4c\x32\xaf\x17\x1e\xcc\xdc\x7b\xf9\xbe\
\x73\xce\xf7\x9d\xc3\x95\x91\xb1\x89\xd3\xc0\x33\x80\x02\x42\xce\
\x12\xc0\x08\x48\x7a\xac\x80\x57\x45\xb9\xe3\x6a\x62\x7d\x17\x00\
\x87\x5b\x70\xda\x96\x15\xa1\xe1\x95\xd5\xd8\x13\xab\x66\x7b\xdb\
\xac\x21\x34\x82\xd3\x5c\x9a\x26\xd6\xa1\xe0\x4e\x21\x34\x6f\x2c\
\x45\x31\x3b\x8b\x01\x07\xfa\xbb\xb9\xbf\xbb\x80\x00\x93\xd5\x06\
\xbf\x57\xd6\x58\xa8\x37\x28\x87\x36\x0b\x39\x0f\x26\xc8\x8b\x5c\
\x00\x0f\xac\x3b\xcf\x0b\x7b\xfa\x38\xb6\xa7\x8f\xfe\x62\x90\x81\
\x08\x50\x89\x1c\x9f\x5e\x5b\xe2\xd4\xb5\x0a\x81\x08\x56\x72\x49\
\x24\xc8\x03\x57\xc0\x79\xe5\xbd\x47\xee\xe1\xe0\x60\x09\xaf\xed\
\x35\xec\x0d\x2d\xaf\x3d\xb8\x93\x91\xed\x1d\xbc\xfa\xc7\x1c\x1e\
\x30\x39\x99\x98\xad\x04\x46\x60\x35\xf6\x9c\xd8\x3b\xc0\xc1\xc1\
\x12\x91\x57\x8c\x24\x11\x9f\xb9\x51\xe5\xcc\x8d\x2a\xb7\xd6\x1d\
\x46\x20\xf2\xca\x13\x77\x75\xf3\xc6\xf0\x00\xd5\xd8\x23\x39\x2a\
\x06\x79\xe0\xfb\xfb\x3a\x39\x72\x5f\x2f\x4e\x95\x82\x11\x4e\x4f\
\xff\xc7\xbb\x7f\x2f\x72\x6b\xdd\x01\xd0\x57\xb0\xbc\xfe\x50\x3f\
\xcf\xde\x5b\x26\x56\x65\x74\xa8\xcc\xf7\xb3\xb7\xb9\x50\xa9\xd1\
\x15\x98\x2c\xe3\xb6\x0c\x84\xc4\x31\x87\x77\xf5\x64\x6e\xf9\x65\
\xb1\xca\xf1\x4b\x73\xac\xc6\x9e\x9e\xd0\xd0\x13\x1a\x6a\xce\x73\
\xe2\xd2\x1c\x67\x6f\x54\x09\xd2\xb0\x0f\xed\xea\xa1\xe1\x37\xac\
\x9c\x4b\xe0\x55\xe9\xb4\x86\x7d\xe5\x8e\x6c\xef\x8b\xeb\xcb\x58\
\x11\x3a\x8c\xc1\x29\x38\x85\xa2\x31\x04\x22\x7c\x79\x7d\x29\xbb\
\x37\x5c\x2e\xb2\x2d\x10\xfc\x16\xdb\x6e\x26\x00\x0a\x46\x28\x85\
\x26\x25\x84\x85\x7a\xdc\xe6\x77\xa7\x4a\x68\x84\xf9\x7a\x9c\xf5\
\x46\x29\xb4\x14\x8d\xe0\xb7\x98\x61\x4b\x89\x92\x08\x1b\x69\x11\
\x8d\xc0\x8e\xa2\x25\xf6\x8a\x69\x51\xd0\x08\xc4\xaa\xec\x28\x06\
\x59\x89\x22\xaf\xc4\xba\xe1\xc2\x5c\x02\x23\xb0\xe6\x3c\xb3\xb5\
\x46\xb6\x37\x3a\x54\xa6\xee\x15\xa7\x4a\x20\x42\x20\x82\x53\xa8\
\xbb\x44\xdc\xe6\x9a\xaa\x46\xd4\x62\x8f\xd9\xe2\xa4\x36\x91\x9d\
\x2a\xe3\x37\x6b\x00\xc4\x5e\x79\xf2\xee\x12\x6f\xed\x1b\xc0\x2b\
\x54\xa2\x98\xa5\x28\xc6\xa9\xf2\xe6\xf0\x00\x4f\x0d\x96\xb2\x12\
\xfd\xbc\x50\xc5\x6b\xbb\xc8\x32\x32\x36\x11\x03\xb6\x59\xa2\x58\
\x95\xde\xd0\xf2\xf5\xe3\xbb\xe9\x2f\x06\x38\x55\xac\x08\x53\xd5\
\x06\x17\x2b\x35\x14\xd8\xdf\xd7\xc9\xee\xae\x42\x76\x76\x75\x65\
\x9d\xe7\xc7\xa7\x70\xda\xde\x6c\x9b\xfa\x40\x81\x82\x31\xcc\xae\
\x35\xf8\xf8\x9f\x0a\x27\xf7\x0e\xa4\x93\x13\x86\xba\x42\x86\xba\
\x36\x4a\xd2\x1a\xed\x3b\x7f\x2d\xb2\xd2\x48\x6c\xec\xb6\xb4\x72\
\x5b\x27\x3b\x55\xca\xa1\xe5\xf3\xeb\xcb\x9c\xbb\x59\x23\x10\x41\
\x51\x54\x93\x33\xa7\x8a\x57\xf0\x24\x1d\xfe\xe1\xd5\x0a\x3f\x2d\
\xac\xd2\x13\xda\x36\xf0\x5c\x02\x00\x91\xe4\xe0\xe4\x9f\xf3\xcc\
\xd4\x1a\x58\x49\xec\x67\x45\xd2\xdf\x89\xe0\x3f\xce\xaf\xf2\xfe\
\x95\x45\x7a\x43\x7b\xa7\xb1\x8d\x49\xed\xbf\x69\x79\x85\x0e\x2b\
\x2c\xd4\x1b\xbc\x74\x7e\x86\xf9\x7a\x8c\x15\xd2\x46\x4b\xc0\x2f\
\xdc\xaa\x71\xfc\xd2\x1c\x05\x93\x1b\x63\x06\x65\x80\x7a\xfa\x27\
\x6e\xd5\xc7\x29\x74\x07\x96\x6b\xd5\x88\xa3\xe7\xa6\x99\xac\x46\
\x58\x49\xb2\xb8\xbc\x5c\xe7\x95\x8b\xff\x26\x64\xa6\x6d\x82\x6a\
\x8a\x05\x10\x19\xe0\xed\x16\xc1\x05\x70\xe9\x87\x53\xa5\x14\x58\
\xa6\x6b\x11\x47\xc6\xa7\xb9\x72\x7b\x9d\x89\x95\x75\x5e\x3c\x3f\
\xc3\x9a\xf3\x49\xe7\xb6\x34\x78\xfa\x49\xd3\x95\xc0\x07\x32\x32\
\x36\x01\xf0\x28\xf0\x32\xf0\x34\xb0\xbd\x25\x12\x07\x18\x2b\x62\
\x6a\xb1\xa7\x5c\x30\x58\x11\x2a\x91\xa3\xd3\x0a\x5e\xf1\x69\x89\
\x2d\x1b\x13\xa2\x02\x7c\x0b\x7c\x04\xfc\x1a\xa4\x3a\x9c\x07\x7e\
\x03\xfa\x52\x92\xa3\xc0\x63\x4d\x1b\x3b\x55\xed\x0c\xc4\xd7\x62\
\xb5\x8a\xd2\x69\xc5\x79\x4d\xde\x02\x6c\x58\xff\x1c\xf0\x09\xf0\
\x4d\x4a\x92\xbc\x15\xd2\x0c\x2c\x80\x2a\xae\x65\xe4\x3c\x00\x8c\
\x02\xcf\x01\x0f\xa7\xe6\x6a\xa6\xd6\x7c\x35\x4c\x02\x5f\x01\x9f\
\x01\x97\x5b\x74\x68\x96\xc8\x35\x09\x32\x87\xa6\x87\x1e\x51\x8f\
\x4a\xf3\xf2\x01\xe0\x18\xc9\xf3\x26\x04\xce\x02\xa7\x80\x1f\x80\
\x6a\x8b\x23\x4d\x5a\xd6\x4c\x99\xff\x01\xeb\x12\xd2\xfd\x28\x54\
\x1d\x9f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = "\
\x00\x05\
\x00\x6f\xa6\x53\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x73\
\x00\x09\
\x0a\x6c\x78\x43\
\x00\x72\
\x00\x65\x00\x73\x00\x6f\x00\x75\x00\x72\x00\x63\x00\x65\x00\x73\
\x00\x0b\
\x0c\xad\x02\x9c\
\x00\x76\
\x00\x69\x00\x65\x00\x77\x00\x65\x00\x72\x00\x2e\x00\x68\x00\x74\x00\x6d\x00\x6c\
\x00\x08\
\x04\xd2\x59\x47\
\x00\x69\
\x00\x6e\x00\x66\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\
\x00\x09\
\x06\xc7\x98\x67\
\x00\x61\
\x00\x62\x00\x6f\x00\x75\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\
\x00\x0a\
\x02\xcd\x41\x47\
\x00\x71\
\x00\x74\x00\x69\x00\x6c\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
\x00\x13\
\x0d\x4e\x9e\xa7\
\x00\x6e\
\x00\x67\x00\x6d\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x78\x00\x5f\x00\x32\x00\x34\x00\x78\x00\x32\x00\x34\x00\x2e\x00\x70\
\x00\x6e\x00\x67\
"
qt_resource_struct = "\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x04\
\x00\x00\x00\x10\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x00\x72\x00\x00\x00\x00\x00\x01\x00\x00\x15\x25\
\x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x09\x36\
\x00\x00\x00\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x6a\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x19\xed\
"
def qInitResources():
QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
PK ג(G?lSw w qtiles/tile.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
import math
from qgis.core import *
class Tile:
def __init__(self, x=0, y=0, z=0, tms=1):
self.x = x
self.y = y
self.z = z
self.tms = tms
def toPoint(self):
n = math.pow(2, self.z)
longitude = float(self.x) / n * 360.0 - 180.0
latitude = self.tms * math.degrees(math.atan(math.sinh(math.pi * (1.0 - 2.0 * float(self.y) / n))))
return QgsPoint(longitude, latitude)
def toRectangle(self):
return QgsRectangle(self.toPoint(), Tile(self.x + 1, self.y + 1, self.z, self.tms).toPoint())
PK dEH[x- - qtiles/tilingthread.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
import time
import codecs
import json
from string import Template
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from tile import Tile
from writers import *
import resources_rc
class TilingThread(QThread):
rangeChanged = pyqtSignal(str, int)
updateProgress = pyqtSignal()
processFinished = pyqtSignal()
processInterrupted = pyqtSignal()
def __init__(self, layers, extent, minZoom, maxZoom, width, height, transp, quality, format, outputPath, rootDir, antialiasing, tmsConvention, mbtilesCompression, jsonFile, overview, renderOutsideTiles, mapUrl, viewer):
QThread.__init__(self, QThread.currentThread())
self.mutex = QMutex()
self.stopMe = 0
self.interrupted = False
self.layers = layers
self.extent = extent
self.minZoom = minZoom
self.maxZoom = maxZoom
self.output = outputPath
self.width = width
if rootDir:
self.rootDir = rootDir
else:
self.rootDir = 'tileset_%s' % unicode(time.time()).split('.')[0]
self.antialias = antialiasing
self.tmsConvention = tmsConvention
self.mbtilesCompression = mbtilesCompression
self.format = format
self.quality = quality
self.jsonFile = jsonFile
self.overview = overview
self.renderOutsideTiles = renderOutsideTiles
self.mapurl = mapUrl
self.viewer = viewer
if self.output.isDir():
self.mode = 'DIR'
elif self.output.suffix().lower() == "zip":
self.mode = 'ZIP'
elif self.output.suffix().lower() == "ngrc":
self.mode = 'NGM'
elif self.output.suffix().lower() == 'mbtiles':
self.mode = 'MBTILES'
self.tmsConvention = True
self.interrupted = False
self.tiles = []
self.layersId = []
for layer in self.layers:
self.layersId.append(layer.id())
myRed = QgsProject.instance().readNumEntry('Gui', '/CanvasColorRedPart', 255)[0]
myGreen = QgsProject.instance().readNumEntry('Gui', '/CanvasColorGreenPart', 255)[0]
myBlue = QgsProject.instance().readNumEntry('Gui', '/CanvasColorBluePart', 255)[0]
self.color = QColor(myRed, myGreen, myBlue, transp)
image = QImage(width, height, QImage.Format_ARGB32_Premultiplied)
self.projector = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem('EPSG:3395'))
self.scaleCalc = QgsScaleCalculator()
self.scaleCalc.setDpi(image.logicalDpiX())
self.scaleCalc.setMapUnits(QgsCoordinateReferenceSystem('EPSG:3395').mapUnits())
self.settings = QgsMapSettings()
self.settings.setBackgroundColor(self.color)
self.settings.setCrsTransformEnabled(True)
self.settings.setOutputDpi(image.logicalDpiX())
self.settings.setOutputImageFormat(QImage.Format_ARGB32_Premultiplied)
self.settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3395'))
self.settings.setOutputSize(image.size())
self.settings.setLayers(self.layersId)
self.settings.setMapUnits(QgsCoordinateReferenceSystem('EPSG:3395').mapUnits())
if self.antialias:
self.settings.setFlag(QgsMapSettings.Antialiasing, True)
else:
self.settings.setFlag(QgsMapSettings.DrawLabeling, True)
def run(self):
self.mutex.lock()
self.stopMe = 0
self.mutex.unlock()
if self.mode == 'DIR':
self.writer = DirectoryWriter(self.output, self.rootDir)
if self.mapurl:
self.writeMapurlFile()
if self.viewer:
self.writeLeafletViewer()
elif self.mode == 'ZIP':
self.writer = ZipWriter(self.output, self.rootDir)
elif self.mode == 'NGM':
self.writer = NGMArchiveWriter(self.output, self.rootDir)
elif self.mode == 'MBTILES':
self.writer = MBTilesWriter(self.output, self.rootDir, self.format, self.minZoom, self.maxZoom, self.extent, self.mbtilesCompression)
if self.jsonFile:
self.writeJsonFile()
if self.overview:
self.writeOverviewFile()
self.rangeChanged.emit(self.tr('Searching tiles...'), 0)
useTMS = 1
if self.tmsConvention:
useTMS = -1
self.countTiles(Tile(0, 0, 0, useTMS))
if self.interrupted:
del self.tiles[:]
self.tiles = None
self.processInterrupted.emit()
self.rangeChanged.emit(self.tr('Rendering: %v from %m (%p%)'), len(self.tiles))
for t in self.tiles:
self.render(t)
self.updateProgress.emit()
self.mutex.lock()
s = self.stopMe
self.mutex.unlock()
if s == 1:
self.interrupted = True
break
self.writer.finalize()
if not self.interrupted:
self.processFinished.emit()
else:
self.processInterrupted.emit()
def stop(self):
self.mutex.lock()
self.stopMe = 1
self.mutex.unlock()
QThread.wait(self)
def writeJsonFile(self):
filePath = '%s.json' % self.output.absoluteFilePath()
if self.mode == 'DIR':
filePath = '%s/%s.json' % (self.output.absoluteFilePath(), self.rootDir)
info = {
'name': self.rootDir,
'format': self.format.lower(),
'minZoom': self.minZoom,
'maxZoom': self.maxZoom,
'bounds': str(self.extent.xMinimum()) + ',' + str(self.extent.yMinimum()) + ',' + str(self.extent.xMaximum()) + ','+ str(self.extent.yMaximum())
}
with open(filePath, 'w') as f:
f.write( json.dumps(info) )
def writeOverviewFile(self):
self.settings.setExtent(self.projector.transform(self.extent))
image = QImage(self.settings.outputSize(), QImage.Format_ARGB32)
image.fill(Qt.transparent)
dpm = self.settings.outputDpi() / 25.4 * 1000
image.setDotsPerMeterX(dpm)
image.setDotsPerMeterY(dpm)
# job = QgsMapRendererSequentialJob(self.settings)
# job.start()
# job.waitForFinished()
# image = job.renderedImage()
painter = QPainter(image)
job = QgsMapRendererCustomPainterJob(self.settings, painter)
job.renderSynchronously()
painter.end()
filePath = '%s.%s' % (self.output.absoluteFilePath(), self.format.lower())
if self.mode == 'DIR':
filePath = '%s/%s.%s' % (self.output.absoluteFilePath(), self.rootDir, self.format.lower())
image.save(filePath, self.format, self.quality)
def writeMapurlFile(self):
filePath = '%s/%s.mapurl' % (self.output.absoluteFilePath(), self.rootDir)
tileServer = 'tms' if self.tmsConvention else 'google'
with open(filePath, 'w') as mapurl:
mapurl.write('%s=%s\n' % ('url', self.rootDir + '/ZZZ/XXX/YYY.png'))
mapurl.write('%s=%s\n' % ('minzoom', self.minZoom))
mapurl.write('%s=%s\n' % ('maxzoom', self.maxZoom))
mapurl.write('%s=%f %f\n' % ('center', self.extent.center().x(), self.extent.center().y()))
mapurl.write('%s=%s\n' % ('type', tileServer))
def writeLeafletViewer(self):
templateFile = QFile(':/resources/viewer.html')
if templateFile.open(QIODevice.ReadOnly | QIODevice.Text):
viewer = MyTemplate(unicode(templateFile.readAll()))
tilesDir = '%s/%s' % (self.output.absoluteFilePath(), self.rootDir)
useTMS = 'true' if self.tmsConvention else 'false'
substitutions = {
'tilesdir': tilesDir,
'tilesext': self.format.lower(),
'tilesetname': self.rootDir,
'tms': useTMS,
'centerx': self.extent.center().x(),
'centery': self.extent.center().y(),
'avgzoom': (self.maxZoom + self.minZoom) / 2,
'maxzoom': self.maxZoom
}
filePath = '%s/%s.html' % (self.output.absoluteFilePath(), self.rootDir)
with codecs.open(filePath, 'w', 'utf-8') as fOut:
fOut.write(viewer.substitute(substitutions))
templateFile.close()
def countTiles(self, tile):
if self.interrupted or not self.extent.intersects(tile.toRectangle()):
return
if self.minZoom <= tile.z and tile.z <= self.maxZoom:
if not self.renderOutsideTiles:
for layer in self.layers:
if layer.extent().intersects(tile.toRectangle()):
self.tiles.append(tile)
break
else:
self.tiles.append(tile)
if tile.z < self.maxZoom:
for x in xrange(2 * tile.x, 2 * tile.x + 2, 1):
for y in xrange(2 * tile.y, 2 * tile.y + 2, 1):
self.mutex.lock()
s = self.stopMe
self.mutex.unlock()
if s == 1:
self.interrupted = True
return
subTile = Tile(x, y, tile.z + 1, tile.tms)
self.countTiles(subTile)
def render(self, tile):
# scale = self.scaleCalc.calculate(
# self.projector.transform(tile.toRectangle()), self.width)
self.settings.setExtent(self.projector.transform(tile.toRectangle()))
image = QImage(self.settings.outputSize(), QImage.Format_ARGB32)
image.fill(Qt.transparent)
dpm = self.settings.outputDpi() / 25.4 * 1000
image.setDotsPerMeterX(dpm)
image.setDotsPerMeterY(dpm)
# job = QgsMapRendererSequentialJob(self.settings)
# job.start()
# job.waitForFinished()
# image = job.renderedImage()
painter = QPainter(image)
job = QgsMapRendererCustomPainterJob(self.settings, painter)
job.renderSynchronously()
painter.end()
self.writer.writeTile(tile, image, self.format, self.quality)
class MyTemplate(Template):
delimiter = '@'
def __init__(self, templateString):
Template.__init__(self, templateString)
PK EH qtiles/writers.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
import sqlite3
import zipfile
import json
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from mbutils import *
class DirectoryWriter:
def __init__(self, outputPath, rootDir):
self.output = outputPath
self.rootDir = rootDir
def writeTile(self, tile, image, format, quality):
path = '%s/%s/%s' % (self.rootDir, tile.z, tile.x)
dirPath = '%s/%s' % (self.output.absoluteFilePath(), path)
QDir().mkpath(dirPath)
image.save('%s/%s.%s' % (dirPath, tile.y, format.lower()), format, quality)
def finalize(self):
pass
class ZipWriter:
def __init__(self, outputPath, rootDir):
self.output = outputPath
self.rootDir = rootDir
self.zipFile = zipfile.ZipFile(unicode(self.output.absoluteFilePath()), 'w')
self.tempFile = QTemporaryFile()
self.tempFile.setAutoRemove(False)
self.tempFile.open(QIODevice.WriteOnly)
self.tempFileName = self.tempFile.fileName()
self.tempFile.close()
def writeTile(self, tile, image, format, quality):
path = '%s/%s/%s' % (self.rootDir, tile.z, tile.x)
image.save(self.tempFileName, format, quality)
tilePath = '%s/%s.%s' % (path, tile.y, format.lower())
self.zipFile.write(unicode(self.tempFileName), unicode(tilePath).encode('utf8'))
def finalize(self):
self.tempFile.close()
self.tempFile.remove()
self.zipFile.close()
class NGMArchiveWriter(ZipWriter):
def __init__(self, outputPath, rootDir):
ZipWriter.__init__(self, outputPath, "Mapnik")
self.levels = {}
self.__layerName = rootDir
def writeTile(self, tile, image, format, quality):
ZipWriter.writeTile(self, tile, image, format, quality)
level = self.levels.get(tile.z, {"x": [], "y": []})
level["x"].append(tile.x)
level["y"].append(tile.y)
self.levels[tile.z] = level
def finalize(self):
archive_info = {
"cache_size_multiply": 0,
"levels": [],
'max_level': max(self.levels.keys()),
'min_level': min(self.levels.keys()),
"name": self.__layerName,
"renderer_properties": {
"alpha": 255,
"antialias": True,
"brightness": 0,
"contrast": 1,
"dither": True,
"filterbitmap": True,
"greyscale": False,
"type": "tms_renderer"
},
"tms_type": 2,
"type": 32,
"visible": True
}
for level, coords in self.levels.items():
level_json = {
"level": level,
"bbox_maxx": max(coords["x"]),
"bbox_maxy": max(coords["y"]),
"bbox_minx": min(coords["x"]),
"bbox_miny": min(coords["y"]),
}
archive_info["levels"].append(level_json)
tempFile = QTemporaryFile()
tempFile.setAutoRemove(False)
tempFile.open(QIODevice.WriteOnly)
tempFile.write(json.dumps(archive_info))
tempFileName = tempFile.fileName()
tempFile.close()
self.zipFile.write(tempFileName, "%s.json" % self.rootDir)
ZipWriter.finalize(self)
class MBTilesWriter:
def __init__(self, outputPath, rootDir, formatext, minZoom, maxZoom, extent, compression):
self.output = outputPath
self.rootDir = rootDir
self.compression = compression
s = str(extent.xMinimum()) + ',' + str(extent.yMinimum()) + ',' + str(extent.xMaximum()) + ','+ str(extent.yMaximum())
self.connection = mbtiles_connect(unicode(self.output.absoluteFilePath()))
self.cursor = self.connection.cursor()
optimize_connection(self.cursor)
mbtiles_setup(self.cursor)
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('name', rootDir))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('description', 'Created with QTiles'))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('format', formatext.lower()))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('minZoom', str(minZoom)))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('maxZoom', str(maxZoom)))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('type', 'baselayer'))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('version', '1.1'))
self.cursor.execute('''INSERT INTO metadata(name, value) VALUES (?, ?);''', ('bounds', s))
self.connection.commit()
def writeTile(self, tile, image, format, quality):
data = QByteArray()
buff = QBuffer(data)
image.save(buff, format, quality)
self.cursor.execute('''INSERT INTO tiles(zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?);''', (tile.z, tile.x, tile.y, sqlite3.Binary(buff.data())))
buff.close()
def finalize(self):
optimize_database(self.connection)
self.connection.commit()
if self.compression:
# start compression
compression_prepare(self.cursor, self.connection)
self.cursor.execute("select count(zoom_level) from tiles")
res = self.cursor.fetchone()
total_tiles = res[0]
compression_do(self.cursor, self.connection, total_tiles)
compression_finalize(self.cursor)
optimize_database(self.connection)
self.connection.commit()
# end compression
self.connection.close()
self.cursor = None
PK O'G:۸ qtiles/__init__.py# -*- coding: utf-8 -*-
#******************************************************************************
#
# QTiles
# ---------------------------------------------------------
# Generates tiles from QGIS project
#
# Copyright (C) 2012-2014 NextGIS (info@nextgis.org)
#
# This source is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# This code is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at . You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************
def classFactory(iface):
from qtiles import QTilesPlugin
return QTilesPlugin(iface)
PK jtEH3 qtiles/i18n/QTiles_ru.qm