Merge pull request #9857 from marcel-dancak/tiles_xyz
New Processing Algorithm to generate raster XYZ tiles
@ -509,6 +509,13 @@ qgis:sumlinelengths: >
|
||||
qgis:texttofloat: >
|
||||
This algorithm modifies the type of a given attribute in a vector layer, converting a text attribute containing numeric strings into a numeric attribute.
|
||||
|
||||
qgis:tilesxyz: >
|
||||
This algorithm generates raster XYZ tiles of map canvas content.
|
||||
|
||||
Tile images can be saved as individual images in directory structure, or as single file in MBTiles format.
|
||||
|
||||
Tile size is fixed to 256x256.
|
||||
|
||||
qgis:topologicalcoloring: >
|
||||
This algorithm assigns a color index to polygon features in such a way that no adjacent polygons share the same color index, whilst minimizing the number of colors required.
|
||||
|
||||
|
@ -134,6 +134,7 @@ from .SpatialJoinSummary import SpatialJoinSummary
|
||||
from .StatisticsByCategories import StatisticsByCategories
|
||||
from .SumLines import SumLines
|
||||
from .TextToFloat import TextToFloat
|
||||
from .TilesXYZ import TilesXYZ
|
||||
from .TinInterpolation import TinInterpolation
|
||||
from .TopoColors import TopoColor
|
||||
from .TruncateTable import TruncateTable
|
||||
@ -244,6 +245,7 @@ class QgisAlgorithmProvider(QgsProcessingProvider):
|
||||
StatisticsByCategories(),
|
||||
SumLines(),
|
||||
TextToFloat(),
|
||||
TilesXYZ(),
|
||||
TinInterpolation(),
|
||||
TopoColor(),
|
||||
TruncateTable(),
|
||||
|
393
python/plugins/processing/algs/qgis/TilesXYZ.py
Normal file
@ -0,0 +1,393 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
***************************************************************************
|
||||
TilesXYZ.py
|
||||
---------------------
|
||||
Date : April 2019
|
||||
Copyright : (C) 2019 by Lutra Consulting Limited
|
||||
Email : marcel.dancak@lutraconsulting.co.uk
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
***************************************************************************
|
||||
"""
|
||||
|
||||
__author__ = 'Marcel Dancak'
|
||||
__date__ = 'April 2019'
|
||||
__copyright__ = '(C) 2019 by Lutra Consulting Limited'
|
||||
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
import os
|
||||
import math
|
||||
from uuid import uuid4
|
||||
|
||||
import ogr
|
||||
import gdal
|
||||
from qgis.PyQt.QtCore import QSize, Qt, QByteArray, QBuffer
|
||||
from qgis.PyQt.QtGui import QColor, QImage, QPainter
|
||||
from qgis.core import (QgsProcessingException,
|
||||
QgsProcessingParameterEnum,
|
||||
QgsProcessingParameterNumber,
|
||||
QgsProcessingParameterBoolean,
|
||||
QgsProcessingParameterString,
|
||||
QgsProcessingParameterExtent,
|
||||
QgsProcessingOutputFile,
|
||||
QgsProcessingParameterFileDestination,
|
||||
QgsProcessingParameterFolderDestination,
|
||||
QgsGeometry,
|
||||
QgsRectangle,
|
||||
QgsMapSettings,
|
||||
QgsCoordinateTransform,
|
||||
QgsCoordinateReferenceSystem,
|
||||
QgsMapRendererCustomPainterJob)
|
||||
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
||||
|
||||
|
||||
# Math functions taken from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames #spellok
|
||||
def deg2num(lat_deg, lon_deg, zoom):
|
||||
lat_rad = math.radians(lat_deg)
|
||||
n = 2.0 ** zoom
|
||||
xtile = int((lon_deg + 180.0) / 360.0 * n)
|
||||
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
|
||||
return (xtile, ytile)
|
||||
|
||||
|
||||
def num2deg(xtile, ytile, zoom):
|
||||
n = 2.0 ** zoom
|
||||
lon_deg = xtile / n * 360.0 - 180.0
|
||||
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
|
||||
lat_deg = math.degrees(lat_rad)
|
||||
return (lat_deg, lon_deg)
|
||||
|
||||
|
||||
class Tile:
|
||||
def __init__(self, x, y, z):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
def extent(self):
|
||||
lat1, lon1 = num2deg(self.x, self.y, self.z)
|
||||
lat2, lon2 = num2deg(self.x + 1, self.y + 1, self.z)
|
||||
return [lon1, lat2, lon2, lat1]
|
||||
|
||||
|
||||
class MetaTile:
|
||||
def __init__(self):
|
||||
# list of tuple(row index, column index, Tile)
|
||||
self.tiles = []
|
||||
|
||||
def add_tile(self, row, column, tile):
|
||||
self.tiles.append((row, column, tile))
|
||||
|
||||
def rows(self):
|
||||
return max([r for r, _, _ in self.tiles]) + 1
|
||||
|
||||
def columns(self):
|
||||
return max([c for _, c, _ in self.tiles]) + 1
|
||||
|
||||
def extent(self):
|
||||
_, _, first = self.tiles[0]
|
||||
_, _, last = self.tiles[-1]
|
||||
lat1, lon1 = num2deg(first.x, first.y, first.z)
|
||||
lat2, lon2 = num2deg(last.x + 1, last.y + 1, first.z)
|
||||
return [lon1, lat2, lon2, lat1]
|
||||
|
||||
|
||||
def get_metatiles(extent, zoom, size=4):
|
||||
west_edge, south_edge, east_edge, north_edge = extent
|
||||
left_tile, top_tile = deg2num(north_edge, west_edge, zoom)
|
||||
right_tile, bottom_tile = deg2num(south_edge, east_edge, zoom)
|
||||
|
||||
metatiles = {}
|
||||
for i, x in enumerate(range(left_tile, right_tile + 1)):
|
||||
for j, y in enumerate(range(top_tile, bottom_tile + 1)):
|
||||
meta_key = '{}:{}'.format(int(i / size), int(j / size))
|
||||
if meta_key not in metatiles:
|
||||
metatiles[meta_key] = MetaTile()
|
||||
metatile = metatiles[meta_key]
|
||||
metatile.add_tile(i % size, j % size, Tile(x, y, zoom))
|
||||
|
||||
return list(metatiles.values())
|
||||
|
||||
|
||||
class DirectoryWriter:
|
||||
def __init__(self, folder, tile_params):
|
||||
self.folder = folder
|
||||
self.format = tile_params.get('format', 'PNG')
|
||||
self.quality = tile_params.get('quality', -1)
|
||||
|
||||
def writeTile(self, tile, image):
|
||||
directory = os.path.join(self.folder, str(tile.z), str(tile.x))
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
path = os.path.join(directory, '{}.{}'.format(tile.y, self.format.lower()))
|
||||
image.save(path, self.format, self.quality)
|
||||
return path
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class MBTilesWriter:
|
||||
def __init__(self, filename, tile_params, extent, min_zoom, max_zoom):
|
||||
base_dir = os.path.dirname(filename)
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
self.filename = filename
|
||||
self.extent = extent
|
||||
self.tile_width = tile_params.get('width', 256)
|
||||
self.tile_height = tile_params.get('height', 256)
|
||||
tile_format = tile_params['format']
|
||||
if tile_format == 'JPG':
|
||||
tile_format = 'JPEG'
|
||||
options = ['QUALITY=%s' % tile_params.get('quality', 75)]
|
||||
else:
|
||||
options = ['ZLEVEL=8']
|
||||
driver = gdal.GetDriverByName('MBTiles')
|
||||
ds = driver.Create(filename, 1, 1, 1, options=['TILE_FORMAT=%s' % tile_format] + options)
|
||||
ds = None
|
||||
sqlite_driver = ogr.GetDriverByName('SQLite')
|
||||
ds = sqlite_driver.Open(filename, 1)
|
||||
ds.ExecuteSQL("INSERT INTO metadata(name, value) VALUES ('{}', '{}');".format('minzoom', min_zoom))
|
||||
ds.ExecuteSQL("INSERT INTO metadata(name, value) VALUES ('{}', '{}');".format('maxzoom', max_zoom))
|
||||
# will be set properly after writing all tiles
|
||||
ds.ExecuteSQL("INSERT INTO metadata(name, value) VALUES ('{}', '');".format('bounds'))
|
||||
ds = None
|
||||
self._zoom = None
|
||||
|
||||
def _initZoomLayer(self, zoom):
|
||||
west_edge, south_edge, east_edge, north_edge = self.extent
|
||||
first_tile = Tile(*deg2num(north_edge, west_edge, zoom), zoom)
|
||||
last_tile = Tile(*deg2num(south_edge, east_edge, zoom), zoom)
|
||||
|
||||
first_tile_extent = first_tile.extent()
|
||||
last_tile_extent = last_tile.extent()
|
||||
zoom_extent = [
|
||||
first_tile_extent[0],
|
||||
last_tile_extent[1],
|
||||
last_tile_extent[2],
|
||||
first_tile_extent[3]
|
||||
]
|
||||
|
||||
sqlite_driver = ogr.GetDriverByName('SQLite')
|
||||
ds = sqlite_driver.Open(self.filename, 1)
|
||||
bounds = ','.join(map(str, zoom_extent))
|
||||
ds.ExecuteSQL("UPDATE metadata SET value='{}' WHERE name='bounds'".format(bounds))
|
||||
ds = None
|
||||
|
||||
self._zoomDs = gdal.OpenEx(self.filename, 1, open_options=['ZOOM_LEVEL=%s' % first_tile.z])
|
||||
self._first_tile = first_tile
|
||||
self._zoom = zoom
|
||||
|
||||
def writeTile(self, tile, image):
|
||||
if tile.z != self._zoom:
|
||||
self._initZoomLayer(tile.z)
|
||||
|
||||
data = QByteArray()
|
||||
buff = QBuffer(data)
|
||||
image.save(buff, 'PNG')
|
||||
|
||||
mmap_name = '/vsimem/' + uuid4().hex
|
||||
gdal.FileFromMemBuffer(mmap_name, data.data())
|
||||
gdal_dataset = gdal.Open(mmap_name)
|
||||
data = gdal_dataset.ReadRaster(0, 0, self.tile_width, self.tile_height)
|
||||
gdal_dataset = None
|
||||
gdal.Unlink(mmap_name)
|
||||
|
||||
xoff = (tile.x - self._first_tile.x) * self.tile_width
|
||||
yoff = (tile.y - self._first_tile.y) * self.tile_height
|
||||
self._zoomDs.WriteRaster(xoff, yoff, self.tile_width, self.tile_height, data)
|
||||
|
||||
def close(self):
|
||||
self._zoomDs = None
|
||||
sqlite_driver = ogr.GetDriverByName('SQLite')
|
||||
ds = sqlite_driver.Open(self.filename, 1)
|
||||
bounds = ','.join(map(str, self.extent))
|
||||
ds.ExecuteSQL("UPDATE metadata SET value='{}' WHERE name='bounds'".format(bounds))
|
||||
ds = None
|
||||
|
||||
|
||||
class TilesXYZ(QgisAlgorithm):
|
||||
EXTENT = 'EXTENT'
|
||||
ZOOM_MIN = 'ZOOM_MIN'
|
||||
ZOOM_MAX = 'ZOOM_MAX'
|
||||
TILE_FORMAT = 'TILE_FORMAT'
|
||||
DPI = 'DPI'
|
||||
OUTPUT_FORMAT = 'OUTPUT_FORMAT'
|
||||
OUTPUT_DIRECTORY = 'OUTPUT_DIRECTORY'
|
||||
OUTPUT_FILE = 'OUTPUT_FILE'
|
||||
|
||||
def group(self):
|
||||
return self.tr('Raster tools')
|
||||
|
||||
def groupId(self):
|
||||
return 'rastertools'
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.addParameter(QgsProcessingParameterExtent(self.EXTENT, self.tr('Extent')))
|
||||
self.addParameter(QgsProcessingParameterNumber(self.ZOOM_MIN,
|
||||
self.tr('Minimum zoom'),
|
||||
minValue=0,
|
||||
maxValue=25,
|
||||
defaultValue=12))
|
||||
self.addParameter(QgsProcessingParameterNumber(self.ZOOM_MAX,
|
||||
self.tr('Maximum zoom'),
|
||||
minValue=0,
|
||||
maxValue=25,
|
||||
defaultValue=12))
|
||||
self.addParameter(QgsProcessingParameterNumber(self.DPI,
|
||||
self.tr('DPI'),
|
||||
minValue=48,
|
||||
maxValue=600,
|
||||
defaultValue=96))
|
||||
self.formats = ['PNG', 'JPG']
|
||||
self.addParameter(QgsProcessingParameterEnum(self.TILE_FORMAT,
|
||||
self.tr('Tile format'),
|
||||
self.formats,
|
||||
defaultValue=0))
|
||||
self.outputs = ['Directory', 'MBTiles']
|
||||
self.addParameter(QgsProcessingParameterEnum(self.OUTPUT_FORMAT,
|
||||
self.tr('Output format'),
|
||||
self.outputs,
|
||||
defaultValue=0))
|
||||
self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT_DIRECTORY,
|
||||
self.tr('Output directory'),
|
||||
optional=True))
|
||||
self.addParameter(QgsProcessingParameterFileDestination(self.OUTPUT_FILE,
|
||||
self.tr('Output file (for MBTiles)'),
|
||||
self.tr('MBTiles files (*.mbtiles)'),
|
||||
optional=True))
|
||||
|
||||
def name(self):
|
||||
return 'tilesxyz'
|
||||
|
||||
def displayName(self):
|
||||
return self.tr('Generate XYZ tiles')
|
||||
|
||||
def prepareAlgorithm(self, parameters, context, feedback):
|
||||
project = context.project()
|
||||
visible_layers = [item.layer() for item in project.layerTreeRoot().findLayers() if item.isVisible()]
|
||||
self.layers = [l for l in project.layerTreeRoot().layerOrder() if l in visible_layers]
|
||||
return True
|
||||
|
||||
def processAlgorithm(self, parameters, context, feedback):
|
||||
feedback.setProgress(1)
|
||||
|
||||
extent = self.parameterAsExtent(parameters, self.EXTENT, context)
|
||||
min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context)
|
||||
max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context)
|
||||
dpi = self.parameterAsInt(parameters, self.DPI, context)
|
||||
tile_format = self.formats[self.parameterAsEnum(parameters, self.TILE_FORMAT, context)]
|
||||
output_format = self.outputs[self.parameterAsEnum(parameters, self.OUTPUT_FORMAT, context)]
|
||||
if output_format == 'Directory':
|
||||
output_dir = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context)
|
||||
if not output_dir:
|
||||
raise QgsProcessingException(self.tr('You need to specify output directory.'))
|
||||
else: # MBTiles
|
||||
output_file = self.parameterAsString(parameters, self.OUTPUT_FILE, context)
|
||||
if not output_file:
|
||||
raise QgsProcessingException(self.tr('You need to specify output filename.'))
|
||||
tile_width = 256
|
||||
tile_height = 256
|
||||
|
||||
wgs_crs = QgsCoordinateReferenceSystem('EPSG:4326')
|
||||
dest_crs = QgsCoordinateReferenceSystem('EPSG:3857')
|
||||
|
||||
project = context.project()
|
||||
src_to_wgs = QgsCoordinateTransform(project.crs(), wgs_crs, context.transformContext())
|
||||
wgs_to_dest = QgsCoordinateTransform(wgs_crs, dest_crs, context.transformContext())
|
||||
|
||||
settings = QgsMapSettings()
|
||||
settings.setOutputImageFormat(QImage.Format_ARGB32_Premultiplied)
|
||||
settings.setDestinationCrs(dest_crs)
|
||||
settings.setLayers(self.layers)
|
||||
settings.setOutputDpi(dpi)
|
||||
|
||||
wgs_extent = src_to_wgs.transformBoundingBox(extent)
|
||||
wgs_extent = [wgs_extent.xMinimum(), wgs_extent.yMinimum(), wgs_extent.xMaximum(), wgs_extent.yMaximum()]
|
||||
|
||||
metatiles_by_zoom = {}
|
||||
metatiles_count = 0
|
||||
for zoom in range(min_zoom, max_zoom + 1):
|
||||
metatiles = get_metatiles(wgs_extent, zoom, 4)
|
||||
metatiles_by_zoom[zoom] = metatiles
|
||||
metatiles_count += len(metatiles)
|
||||
|
||||
lab_buffer_px = 100
|
||||
progress = 0
|
||||
|
||||
tile_params = {
|
||||
'format': tile_format,
|
||||
'quality': 75,
|
||||
'width': tile_width,
|
||||
'height': tile_height
|
||||
}
|
||||
if output_format == 'Directory':
|
||||
writer = DirectoryWriter(output_dir, tile_params)
|
||||
else:
|
||||
writer = MBTilesWriter(output_file, tile_params, wgs_extent, min_zoom, max_zoom)
|
||||
|
||||
for zoom in range(min_zoom, max_zoom + 1):
|
||||
feedback.pushConsoleInfo('Generating tiles for zoom level: %s' % zoom)
|
||||
|
||||
for i, metatile in enumerate(metatiles_by_zoom[zoom]):
|
||||
size = QSize(tile_width * metatile.rows(), tile_height * metatile.columns())
|
||||
extent = QgsRectangle(*metatile.extent())
|
||||
settings.setExtent(wgs_to_dest.transformBoundingBox(extent))
|
||||
settings.setOutputSize(size)
|
||||
|
||||
label_area = QgsRectangle(settings.extent())
|
||||
lab_buffer = label_area.width() * (lab_buffer_px / size.width())
|
||||
label_area.set(
|
||||
label_area.xMinimum() + lab_buffer,
|
||||
label_area.yMinimum() + lab_buffer,
|
||||
label_area.xMaximum() - lab_buffer,
|
||||
label_area.yMaximum() - lab_buffer
|
||||
)
|
||||
settings.setLabelBoundaryGeometry(QgsGeometry.fromRect(label_area))
|
||||
|
||||
image = QImage(size, QImage.Format_ARGB32_Premultiplied)
|
||||
image.fill(Qt.transparent)
|
||||
dpm = settings.outputDpi() / 25.4 * 1000
|
||||
image.setDotsPerMeterX(dpm)
|
||||
image.setDotsPerMeterY(dpm)
|
||||
painter = QPainter(image)
|
||||
job = QgsMapRendererCustomPainterJob(settings, painter)
|
||||
job.renderSynchronously()
|
||||
painter.end()
|
||||
|
||||
# For analysing metatiles (labels, etc.)
|
||||
# metatile_dir = os.path.join(output_dir, str(zoom))
|
||||
# os.makedirs(metatile_dir, exist_ok=True)
|
||||
# image.save(os.path.join(metatile_dir, 'metatile_%s.png' % i))
|
||||
|
||||
for r, c, tile in metatile.tiles:
|
||||
tile_img = image.copy(tile_width * r, tile_height * c, tile_width, tile_height)
|
||||
writer.writeTile(tile, tile_img)
|
||||
|
||||
progress += 1
|
||||
feedback.setProgress(100 * (progress / metatiles_count))
|
||||
|
||||
writer.close()
|
||||
|
||||
results = {}
|
||||
if output_format == 'Directory':
|
||||
results['OUTPUT_DIRECTORY'] = output_dir
|
||||
else: # MBTiles
|
||||
results['OUTPUT_FILE'] = output_file
|
||||
return results
|
||||
|
||||
def checkParameterValues(self, parameters, context):
|
||||
min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context)
|
||||
max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context)
|
||||
if max_zoom < min_zoom:
|
||||
return False, self.tr('Invalid zoom levels range.')
|
||||
|
||||
return super().checkParameterValues(parameters, context)
|
@ -86,7 +86,12 @@ class AlgorithmsTest(object):
|
||||
:param defs: A python dict containing a test algorithm definition
|
||||
"""
|
||||
self.vector_layer_params = {}
|
||||
QgsProject.instance().removeAllMapLayers()
|
||||
QgsProject.instance().clear()
|
||||
|
||||
if 'project' in defs:
|
||||
full_project_path = os.path.join(processingTestDataPath(), defs['project'])
|
||||
project_read_success = QgsProject.instance().read(full_project_path)
|
||||
self.assertTrue(project_read_success, 'Failed to load project file: ' + defs['project'])
|
||||
|
||||
if 'project_crs' in defs:
|
||||
QgsProject.instance().setCrs(QgsCoordinateReferenceSystem(defs['project_crs']))
|
||||
@ -212,6 +217,9 @@ class AlgorithmsTest(object):
|
||||
basename = 'raster.tif'
|
||||
filepath = os.path.join(outdir, basename)
|
||||
return filepath
|
||||
elif param['type'] == 'directory':
|
||||
outdir = tempfile.mkdtemp()
|
||||
return outdir
|
||||
|
||||
raise KeyError("Unknown type '{}' specified for parameter".format(param['type']))
|
||||
|
||||
@ -350,6 +358,11 @@ class AlgorithmsTest(object):
|
||||
result_filepath = results[id]
|
||||
|
||||
self.assertFilesEqual(expected_filepath, result_filepath)
|
||||
elif 'directory' == expected_result['type']:
|
||||
expected_dirpath = self.filepath_from_param(expected_result)
|
||||
result_dirpath = results[id]
|
||||
|
||||
self.assertDirectoriesEqual(expected_dirpath, result_dirpath)
|
||||
elif 'regex' == expected_result['type']:
|
||||
with open(results[id], 'r') as file:
|
||||
data = file.read()
|
||||
|
@ -194,6 +194,25 @@ OUTPUT:
|
||||
- 'Feature Count: 6'
|
||||
```
|
||||
|
||||
#### Directories
|
||||
|
||||
You can compare the content of an output directory by en expected result reference directory
|
||||
|
||||
```yaml
|
||||
OUTPUT_DIR:
|
||||
name: expected/tiles_xyz/test_1
|
||||
type: directory
|
||||
```
|
||||
|
||||
### Algorithm Context
|
||||
|
||||
There are few more definitions that can modify context of the algorithm - these can be specified at top level of test:
|
||||
|
||||
- `project` - will load a specified QGIS project file before running the algorithm. If not specified, algorithm will run with empty project
|
||||
- `project_crs` - overrides the default project CRS - e.g. `EPSG:27700`
|
||||
- `ellipsoid` - overrides the default project ellipsoid used for measurements - e.g. `GRS80`
|
||||
|
||||
|
||||
Running tests locally
|
||||
------------------
|
||||
```bash
|
||||
|
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/1/0/0.png
vendored
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/2/0/1.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/2/1/1.png
vendored
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/3/1/2.png
vendored
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/3/1/3.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/3/2/2.png
vendored
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
python/plugins/processing/tests/testdata/expected/xyztiles/3/2/3.png
vendored
Normal file
After Width: | Height: | Size: 5.5 KiB |
@ -7524,5 +7524,21 @@ tests:
|
||||
name: expected/join_to_nearest_no_matches.gml
|
||||
type: vector
|
||||
|
||||
- name: Generate XYZ tiles
|
||||
algorithm: qgis:tilesxyz
|
||||
project: ../../../../../tests/testdata/xyztiles.qgs
|
||||
project_crs: EPSG:3857
|
||||
params:
|
||||
EXTENT: -12535000,-9883000,3360000,5349000 [EPSG:3857]
|
||||
ZOOM_MIN: 1
|
||||
ZOOM_MAX: 3
|
||||
TILE_FORMAT: 0 # png
|
||||
OUTPUT_FORMAT: 0 # directory
|
||||
|
||||
results:
|
||||
OUTPUT_DIRECTORY:
|
||||
type: directory
|
||||
name: expected/xyztiles
|
||||
|
||||
|
||||
# See ../README.md for a description of the file format
|
||||
|
@ -29,6 +29,7 @@ import os
|
||||
import sys
|
||||
import difflib
|
||||
import functools
|
||||
import filecmp
|
||||
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import QgsApplication, QgsFeatureRequest, NULL
|
||||
@ -196,6 +197,20 @@ class TestCase(_TestCase):
|
||||
diff = list(diff)
|
||||
self.assertEqual(0, len(diff), ''.join(diff))
|
||||
|
||||
def assertDirectoriesEqual(self, dirpath_expected, dirpath_result):
|
||||
""" Checks whether both directories have the same content (recursively) and raises an assertion error if not. """
|
||||
dc = filecmp.dircmp(dirpath_expected, dirpath_result)
|
||||
dc.report_full_closure()
|
||||
|
||||
def _check_dirs_equal_recursive(dcmp):
|
||||
self.assertEqual(dcmp.left_only, [])
|
||||
self.assertEqual(dcmp.right_only, [])
|
||||
self.assertEqual(dcmp.diff_files, [])
|
||||
for sub_dcmp in dcmp.subdirs.values():
|
||||
_check_dirs_equal_recursive(sub_dcmp)
|
||||
|
||||
_check_dirs_equal_recursive(dc)
|
||||
|
||||
def assertGeometriesEqual(self, geom0, geom1, geom0_id='geometry 1', geom1_id='geometry 2', precision=14, topo_equal_check=False):
|
||||
self.checkGeometriesEqual(geom0, geom1, geom0_id, geom1_id, use_asserts=True, precision=precision, topo_equal_check=topo_equal_check)
|
||||
|
||||
|
744
tests/testdata/xyztiles.qgs
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
||||
<qgis version="3.7.0-Master" projectname="">
|
||||
<homePath path=""/>
|
||||
<title></title>
|
||||
<autotransaction active="0"/>
|
||||
<evaluateDefaultValues active="0"/>
|
||||
<trust active="0"/>
|
||||
<projectCrs>
|
||||
<spatialrefsys>
|
||||
<proj4>+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs</proj4>
|
||||
<srsid>3857</srsid>
|
||||
<srid>3857</srid>
|
||||
<authid>EPSG:3857</authid>
|
||||
<description>WGS 84 / Pseudo-Mercator</description>
|
||||
<projectionacronym>merc</projectionacronym>
|
||||
<ellipsoidacronym>WGS84</ellipsoidacronym>
|
||||
<geographicflag>false</geographicflag>
|
||||
</spatialrefsys>
|
||||
</projectCrs>
|
||||
<layer-tree-group>
|
||||
<customproperties/>
|
||||
<layer-tree-layer name="Land" checked="Qt::Checked" expanded="1" source="./polys.shp" providerKey="ogr" id="polys20151123133114244">
|
||||
<customproperties/>
|
||||
</layer-tree-layer>
|
||||
<layer-tree-layer name="Roads" checked="Qt::Checked" expanded="1" source="./lines.shp" providerKey="ogr" id="lines20151123133101198">
|
||||
<customproperties/>
|
||||
</layer-tree-layer>
|
||||
<custom-order enabled="0">
|
||||
<item>polys20151123133114244</item>
|
||||
<item>lines20151123133101198</item>
|
||||
</custom-order>
|
||||
</layer-tree-group>
|
||||
<snapping-settings mode="1" type="2" unit="1" intersection-snapping="0" enabled="1" tolerance="20">
|
||||
<individual-layer-settings>
|
||||
<layer-setting type="2" units="1" enabled="1" tolerance="20" id="polys20151123133114244"/>
|
||||
<layer-setting type="2" units="1" enabled="1" tolerance="20" id="lines20151123133101198"/>
|
||||
</individual-layer-settings>
|
||||
</snapping-settings>
|
||||
<relations/>
|
||||
<mapcanvas name="theMapCanvas" annotationsVisible="1">
|
||||
<units>meters</units>
|
||||
<extent>
|
||||
<xmin>-12244360.81901275180280209</xmin>
|
||||
<ymin>3360185.32420947728678584</ymin>
|
||||
<xmax>-10173775.61578787304461002</xmax>
|
||||
<ymax>5348862.70644816849380732</ymax>
|
||||
</extent>
|
||||
<rotation>0</rotation>
|
||||
<destinationsrs>
|
||||
<spatialrefsys>
|
||||
<proj4>+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs</proj4>
|
||||
<srsid>3857</srsid>
|
||||
<srid>3857</srid>
|
||||
<authid>EPSG:3857</authid>
|
||||
<description>WGS 84 / Pseudo-Mercator</description>
|
||||
<projectionacronym>merc</projectionacronym>
|
||||
<ellipsoidacronym>WGS84</ellipsoidacronym>
|
||||
<geographicflag>false</geographicflag>
|
||||
</spatialrefsys>
|
||||
</destinationsrs>
|
||||
<rendermaptile>0</rendermaptile>
|
||||
<expressionContextScope/>
|
||||
</mapcanvas>
|
||||
<projectModels/>
|
||||
<legend updateDrawingOrder="true">
|
||||
<legendlayer checked="Qt::Checked" name="Land" showFeatureCount="0" drawingOrder="-1" open="true">
|
||||
<filegroup hidden="false" open="true">
|
||||
<legendlayerfile layerid="polys20151123133114244" visible="1" isInOverview="0"/>
|
||||
</filegroup>
|
||||
</legendlayer>
|
||||
<legendlayer checked="Qt::Checked" name="Roads" showFeatureCount="0" drawingOrder="-1" open="true">
|
||||
<filegroup hidden="false" open="true">
|
||||
<legendlayerfile layerid="lines20151123133101198" visible="1" isInOverview="0"/>
|
||||
</filegroup>
|
||||
</legendlayer>
|
||||
</legend>
|
||||
<mapViewDocks/>
|
||||
<mapViewDocks3D/>
|
||||
<projectlayers>
|
||||
<maplayer styleCategories="AllStyleCategories" minScale="1e+8" hasScaleBasedVisibilityFlag="0" simplifyAlgorithm="0" simplifyLocal="1" readOnly="0" type="vector" autoRefreshTime="0" autoRefreshEnabled="0" simplifyMaxScale="1" simplifyDrawingTol="1" refreshOnNotifyEnabled="0" geometry="Line" simplifyDrawingHints="1" labelsEnabled="1" refreshOnNotifyMessage="" maxScale="1">
|
||||
<extent>
|
||||
<xmin>-117.62319839219053108</xmin>
|
||||
<ymin>23.20820580488508966</ymin>
|
||||
<xmax>-82.32264950769274492</xmax>
|
||||
<ymax>46.18290982947509349</ymax>
|
||||
</extent>
|
||||
<id>lines20151123133101198</id>
|
||||
<datasource>./lines.shp</datasource>
|
||||
<keywordList>
|
||||
<value></value>
|
||||
</keywordList>
|
||||
<layername>Roads</layername>
|
||||
<srs>
|
||||
<spatialrefsys>
|
||||
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
|
||||
<srsid>3452</srsid>
|
||||
<srid>4326</srid>
|
||||
<authid>EPSG:4326</authid>
|
||||
<description>WGS 84</description>
|
||||
<projectionacronym>longlat</projectionacronym>
|
||||
<ellipsoidacronym>WGS84</ellipsoidacronym>
|
||||
<geographicflag>true</geographicflag>
|
||||
</spatialrefsys>
|
||||
</srs>
|
||||
<resourceMetadata>
|
||||
<identifier></identifier>
|
||||
<parentidentifier></parentidentifier>
|
||||
<language></language>
|
||||
<type></type>
|
||||
<title></title>
|
||||
<abstract></abstract>
|
||||
<links/>
|
||||
<fees></fees>
|
||||
<encoding></encoding>
|
||||
<crs>
|
||||
<spatialrefsys>
|
||||
<proj4></proj4>
|
||||
<srsid>0</srsid>
|
||||
<srid>0</srid>
|
||||
<authid></authid>
|
||||
<description></description>
|
||||
<projectionacronym></projectionacronym>
|
||||
<ellipsoidacronym></ellipsoidacronym>
|
||||
<geographicflag>false</geographicflag>
|
||||
</spatialrefsys>
|
||||
</crs>
|
||||
<extent/>
|
||||
</resourceMetadata>
|
||||
<provider encoding="UTF-8">ogr</provider>
|
||||
<vectorjoins/>
|
||||
<layerDependencies/>
|
||||
<dataDependencies/>
|
||||
<legend type="default-vector"/>
|
||||
<expressionfields/>
|
||||
<map-layer-style-manager current="default">
|
||||
<map-layer-style name="default"/>
|
||||
</map-layer-style-manager>
|
||||
<auxiliaryLayer/>
|
||||
<flags>
|
||||
<Identifiable>1</Identifiable>
|
||||
<Removable>1</Removable>
|
||||
<Searchable>1</Searchable>
|
||||
</flags>
|
||||
<renderer-v2 type="categorizedSymbol" forceraster="0" symbollevels="0" attr="Name" enableorderby="0">
|
||||
<categories>
|
||||
<category symbol="0" label="Arterial" render="true" value="Arterial"/>
|
||||
<category symbol="1" label="Highway" render="true" value="Highway"/>
|
||||
</categories>
|
||||
<symbols>
|
||||
<symbol name="0" clip_to_extent="1" type="line" alpha="1" force_rhr="0">
|
||||
<layer locked="0" pass="0" class="SimpleLine" enabled="1">
|
||||
<prop v="square" k="capstyle"/>
|
||||
<prop v="5;2" k="customdash"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="customdash_map_unit_scale"/>
|
||||
<prop v="MM" k="customdash_unit"/>
|
||||
<prop v="0" k="draw_inside_polygon"/>
|
||||
<prop v="bevel" k="joinstyle"/>
|
||||
<prop v="154,139,116,255" k="line_color"/>
|
||||
<prop v="dash dot dot" k="line_style"/>
|
||||
<prop v="1" k="line_width"/>
|
||||
<prop v="MM" k="line_width_unit"/>
|
||||
<prop v="0" k="offset"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/>
|
||||
<prop v="MM" k="offset_unit"/>
|
||||
<prop v="0" k="ring_filter"/>
|
||||
<prop v="0" k="use_custom_dash"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="width_map_unit_scale"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties"/>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
<symbol name="1" clip_to_extent="1" type="line" alpha="1" force_rhr="0">
|
||||
<layer locked="0" pass="0" class="SimpleLine" enabled="1">
|
||||
<prop v="square" k="capstyle"/>
|
||||
<prop v="5;2" k="customdash"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="customdash_map_unit_scale"/>
|
||||
<prop v="MM" k="customdash_unit"/>
|
||||
<prop v="0" k="draw_inside_polygon"/>
|
||||
<prop v="bevel" k="joinstyle"/>
|
||||
<prop v="94,89,55,255" k="line_color"/>
|
||||
<prop v="solid" k="line_style"/>
|
||||
<prop v="1.3" k="line_width"/>
|
||||
<prop v="MM" k="line_width_unit"/>
|
||||
<prop v="0" k="offset"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/>
|
||||
<prop v="MM" k="offset_unit"/>
|
||||
<prop v="0" k="ring_filter"/>
|
||||
<prop v="0" k="use_custom_dash"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="width_map_unit_scale"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties"/>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
</symbols>
|
||||
<rotation/>
|
||||
<sizescale/>
|
||||
</renderer-v2>
|
||||
<customproperties>
|
||||
<property key="embeddedWidgets/count" value="0"/>
|
||||
<property key="variableNames"/>
|
||||
<property key="variableValues"/>
|
||||
</customproperties>
|
||||
<blendMode>0</blendMode>
|
||||
<featureBlendMode>0</featureBlendMode>
|
||||
<layerOpacity>1</layerOpacity>
|
||||
<SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Pie">
|
||||
<DiagramCategory minScaleDenominator="1" width="15" rotationOffset="270" lineSizeType="MM" minimumSize="0" scaleBasedVisibility="0" scaleDependency="Area" lineSizeScale="3x:0,0,0,0,0,0" penAlpha="255" enabled="0" maxScaleDenominator="1e+8" diagramOrientation="Up" barWidth="5" penColor="#000000" opacity="1" backgroundAlpha="255" sizeType="MM" penWidth="0" sizeScale="3x:0,0,0,0,0,0" backgroundColor="#ffffff" labelPlacementMethod="XHeight" height="15">
|
||||
<fontProperties description="Ubuntu,13,-1,5,50,0,0,0,0,0" style=""/>
|
||||
<attribute color="#000000" label="" field=""/>
|
||||
</DiagramCategory>
|
||||
</SingleCategoryDiagramRenderer>
|
||||
<DiagramLayerSettings obstacle="0" dist="0" zIndex="0" linePlacementFlags="10" showAll="1" placement="2" priority="0">
|
||||
<properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties" type="Map">
|
||||
<Option name="show" type="Map">
|
||||
<Option name="active" type="bool" value="true"/>
|
||||
<Option name="field" type="QString" value="Name"/>
|
||||
<Option name="type" type="int" value="2"/>
|
||||
</Option>
|
||||
</Option>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</properties>
|
||||
</DiagramLayerSettings>
|
||||
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
|
||||
<activeChecks/>
|
||||
<checkConfiguration/>
|
||||
</geometryOptions>
|
||||
<fieldConfiguration>
|
||||
<field name="Name">
|
||||
<editWidget type="TextEdit">
|
||||
<config>
|
||||
<Option type="Map">
|
||||
<Option name="IsMultiline" type="QString" value="0"/>
|
||||
<Option name="UseHtml" type="QString" value="0"/>
|
||||
</Option>
|
||||
</config>
|
||||
</editWidget>
|
||||
</field>
|
||||
<field name="Value">
|
||||
<editWidget type="TextEdit">
|
||||
<config>
|
||||
<Option type="Map">
|
||||
<Option name="IsMultiline" type="QString" value="0"/>
|
||||
<Option name="UseHtml" type="QString" value="0"/>
|
||||
</Option>
|
||||
</config>
|
||||
</editWidget>
|
||||
</field>
|
||||
</fieldConfiguration>
|
||||
<aliases>
|
||||
<alias name="" index="0" field="Name"/>
|
||||
<alias name="" index="1" field="Value"/>
|
||||
</aliases>
|
||||
<excludeAttributesWMS/>
|
||||
<excludeAttributesWFS/>
|
||||
<defaults>
|
||||
<default expression="" field="Name" applyOnUpdate="0"/>
|
||||
<default expression="" field="Value" applyOnUpdate="0"/>
|
||||
</defaults>
|
||||
<constraints>
|
||||
<constraint unique_strength="0" notnull_strength="0" field="Name" constraints="0" exp_strength="0"/>
|
||||
<constraint unique_strength="0" notnull_strength="0" field="Value" constraints="0" exp_strength="0"/>
|
||||
</constraints>
|
||||
<constraintExpressions>
|
||||
<constraint desc="" field="Name" exp=""/>
|
||||
<constraint desc="" field="Value" exp=""/>
|
||||
</constraintExpressions>
|
||||
<expressionfields/>
|
||||
<attributeactions/>
|
||||
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="">
|
||||
<columns>
|
||||
<column name="Name" type="field" hidden="0" width="-1"/>
|
||||
<column name="Value" type="field" hidden="0" width="-1"/>
|
||||
<column type="actions" hidden="1" width="-1"/>
|
||||
<column name="Blocked" type="field" hidden="0" width="-1"/>
|
||||
<column name="Situation" type="field" hidden="0" width="-1"/>
|
||||
<column name="BlockStart" type="field" hidden="0" width="-1"/>
|
||||
</columns>
|
||||
</attributetableconfig>
|
||||
<conditionalstyles>
|
||||
<rowstyles/>
|
||||
<fieldstyles/>
|
||||
</conditionalstyles>
|
||||
<editform tolerant="1"></editform>
|
||||
<editforminit/>
|
||||
<editforminitcodesource>0</editforminitcodesource>
|
||||
<editforminitfilepath>../src/quickgui/app/qgis-data</editforminitfilepath>
|
||||
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
|
||||
"""
|
||||
QGIS forms can have a Python function that is called when the form is
|
||||
opened.
|
||||
|
||||
Use this function to add extra logic to your forms.
|
||||
|
||||
Enter the name of the function in the "Python Init function"
|
||||
field.
|
||||
An example follows:
|
||||
"""
|
||||
from qgis.PyQt.QtWidgets import QWidget
|
||||
|
||||
def my_form_open(dialog, layer, feature):
|
||||
geom = feature.geometry()
|
||||
control = dialog.findChild(QWidget, "MyLineEdit")
|
||||
]]></editforminitcode>
|
||||
<featformsuppress>0</featformsuppress>
|
||||
<editorlayout>generatedlayout</editorlayout>
|
||||
<editable/>
|
||||
<labelOnTop/>
|
||||
<widgets/>
|
||||
<previewExpression>COALESCE( "Name", '<NULL>' )</previewExpression>
|
||||
<mapTip></mapTip>
|
||||
</maplayer>
|
||||
<maplayer styleCategories="AllStyleCategories" minScale="1e+8" hasScaleBasedVisibilityFlag="0" simplifyAlgorithm="0" simplifyLocal="1" readOnly="0" type="vector" autoRefreshTime="0" autoRefreshEnabled="0" simplifyMaxScale="1" simplifyDrawingTol="1" refreshOnNotifyEnabled="0" geometry="Polygon" simplifyDrawingHints="1" labelsEnabled="1" refreshOnNotifyMessage="" maxScale="1">
|
||||
<extent>
|
||||
<xmin>-118.92286230599032137</xmin>
|
||||
<ymin>24.50786971868489061</ymin>
|
||||
<xmax>-83.79001199101509201</xmax>
|
||||
<ymax>46.72617265077044379</ymax>
|
||||
</extent>
|
||||
<id>polys20151123133114244</id>
|
||||
<datasource>./polys.shp</datasource>
|
||||
<keywordList>
|
||||
<value></value>
|
||||
</keywordList>
|
||||
<layername>Land</layername>
|
||||
<srs>
|
||||
<spatialrefsys>
|
||||
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
|
||||
<srsid>3452</srsid>
|
||||
<srid>4326</srid>
|
||||
<authid>EPSG:4326</authid>
|
||||
<description>WGS 84</description>
|
||||
<projectionacronym>longlat</projectionacronym>
|
||||
<ellipsoidacronym>WGS84</ellipsoidacronym>
|
||||
<geographicflag>true</geographicflag>
|
||||
</spatialrefsys>
|
||||
</srs>
|
||||
<resourceMetadata>
|
||||
<identifier></identifier>
|
||||
<parentidentifier></parentidentifier>
|
||||
<language></language>
|
||||
<type></type>
|
||||
<title></title>
|
||||
<abstract></abstract>
|
||||
<links/>
|
||||
<fees></fees>
|
||||
<encoding></encoding>
|
||||
<crs>
|
||||
<spatialrefsys>
|
||||
<proj4></proj4>
|
||||
<srsid>0</srsid>
|
||||
<srid>0</srid>
|
||||
<authid></authid>
|
||||
<description></description>
|
||||
<projectionacronym></projectionacronym>
|
||||
<ellipsoidacronym></ellipsoidacronym>
|
||||
<geographicflag>false</geographicflag>
|
||||
</spatialrefsys>
|
||||
</crs>
|
||||
<extent/>
|
||||
</resourceMetadata>
|
||||
<provider encoding="UTF-8">ogr</provider>
|
||||
<vectorjoins/>
|
||||
<layerDependencies/>
|
||||
<dataDependencies/>
|
||||
<legend type="default-vector"/>
|
||||
<expressionfields/>
|
||||
<map-layer-style-manager current="default">
|
||||
<map-layer-style name="default"/>
|
||||
</map-layer-style-manager>
|
||||
<auxiliaryLayer/>
|
||||
<flags>
|
||||
<Identifiable>1</Identifiable>
|
||||
<Removable>1</Removable>
|
||||
<Searchable>1</Searchable>
|
||||
</flags>
|
||||
<renderer-v2 type="categorizedSymbol" forceraster="0" symbollevels="0" attr="Name" enableorderby="0">
|
||||
<categories>
|
||||
<category symbol="0" label="Dam" render="true" value="Dam"/>
|
||||
<category symbol="1" label="Lake" render="true" value="Lake"/>
|
||||
</categories>
|
||||
<symbols>
|
||||
<symbol name="0" clip_to_extent="1" type="fill" alpha="1" force_rhr="0">
|
||||
<layer locked="0" pass="0" class="SimpleFill" enabled="1">
|
||||
<prop v="3x:0,0,0,0,0,0" k="border_width_map_unit_scale"/>
|
||||
<prop v="118,191,227,132" k="color"/>
|
||||
<prop v="bevel" k="joinstyle"/>
|
||||
<prop v="0,0" k="offset"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/>
|
||||
<prop v="MM" k="offset_unit"/>
|
||||
<prop v="31,120,180,255" k="outline_color"/>
|
||||
<prop v="solid" k="outline_style"/>
|
||||
<prop v="0.66" k="outline_width"/>
|
||||
<prop v="MM" k="outline_width_unit"/>
|
||||
<prop v="solid" k="style"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties"/>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
<symbol name="1" clip_to_extent="1" type="fill" alpha="1" force_rhr="0">
|
||||
<layer locked="0" pass="0" class="SimpleFill" enabled="1">
|
||||
<prop v="3x:0,0,0,0,0,0" k="border_width_map_unit_scale"/>
|
||||
<prop v="110,194,217,255" k="color"/>
|
||||
<prop v="bevel" k="joinstyle"/>
|
||||
<prop v="0,0" k="offset"/>
|
||||
<prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/>
|
||||
<prop v="MM" k="offset_unit"/>
|
||||
<prop v="144,144,144,255" k="outline_color"/>
|
||||
<prop v="solid" k="outline_style"/>
|
||||
<prop v="0.66" k="outline_width"/>
|
||||
<prop v="MM" k="outline_width_unit"/>
|
||||
<prop v="solid" k="style"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties"/>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
</symbols>
|
||||
<rotation/>
|
||||
<sizescale/>
|
||||
</renderer-v2>
|
||||
<customproperties>
|
||||
<property key="embeddedWidgets/count" value="0"/>
|
||||
<property key="variableNames"/>
|
||||
<property key="variableValues"/>
|
||||
</customproperties>
|
||||
<blendMode>0</blendMode>
|
||||
<featureBlendMode>0</featureBlendMode>
|
||||
<layerOpacity>1</layerOpacity>
|
||||
<SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Pie">
|
||||
<DiagramCategory minScaleDenominator="1" width="15" rotationOffset="270" lineSizeType="MM" minimumSize="0" scaleBasedVisibility="0" scaleDependency="Area" lineSizeScale="3x:0,0,0,0,0,0" penAlpha="255" enabled="0" maxScaleDenominator="1e+8" diagramOrientation="Up" barWidth="5" penColor="#000000" opacity="1" backgroundAlpha="255" sizeType="MM" penWidth="0" sizeScale="3x:0,0,0,0,0,0" backgroundColor="#ffffff" labelPlacementMethod="XHeight" height="15">
|
||||
<fontProperties description="Ubuntu,13,-1,5,50,0,0,0,0,0" style=""/>
|
||||
<attribute color="#000000" label="" field=""/>
|
||||
</DiagramCategory>
|
||||
</SingleCategoryDiagramRenderer>
|
||||
<DiagramLayerSettings obstacle="0" dist="0" zIndex="0" linePlacementFlags="10" showAll="1" placement="0" priority="0">
|
||||
<properties>
|
||||
<Option type="Map">
|
||||
<Option name="name" type="QString" value=""/>
|
||||
<Option name="properties" type="Map">
|
||||
<Option name="show" type="Map">
|
||||
<Option name="active" type="bool" value="true"/>
|
||||
<Option name="field" type="QString" value="Name"/>
|
||||
<Option name="type" type="int" value="2"/>
|
||||
</Option>
|
||||
</Option>
|
||||
<Option name="type" type="QString" value="collection"/>
|
||||
</Option>
|
||||
</properties>
|
||||
</DiagramLayerSettings>
|
||||
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
|
||||
<activeChecks/>
|
||||
<checkConfiguration/>
|
||||
</geometryOptions>
|
||||
<fieldConfiguration>
|
||||
<field name="Name">
|
||||
<editWidget type="ValueMap">
|
||||
<config>
|
||||
<Option type="Map">
|
||||
<Option name="map" type="Map">
|
||||
<Option name="Dam" type="QString" value="Dam"/>
|
||||
<Option name="Lake" type="QString" value="Lake"/>
|
||||
</Option>
|
||||
</Option>
|
||||
</config>
|
||||
</editWidget>
|
||||
</field>
|
||||
<field name="Value">
|
||||
<editWidget type="TextEdit">
|
||||
<config>
|
||||
<Option type="Map">
|
||||
<Option name="IsMultiline" type="QString" value="0"/>
|
||||
<Option name="UseHtml" type="QString" value="0"/>
|
||||
</Option>
|
||||
</config>
|
||||
</editWidget>
|
||||
</field>
|
||||
</fieldConfiguration>
|
||||
<aliases>
|
||||
<alias name="" index="0" field="Name"/>
|
||||
<alias name="" index="1" field="Value"/>
|
||||
</aliases>
|
||||
<excludeAttributesWMS/>
|
||||
<excludeAttributesWFS/>
|
||||
<defaults>
|
||||
<default expression="" field="Name" applyOnUpdate="0"/>
|
||||
<default expression="" field="Value" applyOnUpdate="0"/>
|
||||
</defaults>
|
||||
<constraints>
|
||||
<constraint unique_strength="0" notnull_strength="0" field="Name" constraints="0" exp_strength="0"/>
|
||||
<constraint unique_strength="0" notnull_strength="0" field="Value" constraints="0" exp_strength="0"/>
|
||||
</constraints>
|
||||
<constraintExpressions>
|
||||
<constraint desc="" field="Name" exp=""/>
|
||||
<constraint desc="" field="Value" exp=""/>
|
||||
</constraintExpressions>
|
||||
<expressionfields/>
|
||||
<attributeactions>
|
||||
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
|
||||
</attributeactions>
|
||||
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="">
|
||||
<columns>
|
||||
<column name="Name" type="field" hidden="0" width="-1"/>
|
||||
<column name="Value" type="field" hidden="0" width="-1"/>
|
||||
<column type="actions" hidden="1" width="-1"/>
|
||||
</columns>
|
||||
</attributetableconfig>
|
||||
<conditionalstyles>
|
||||
<rowstyles/>
|
||||
<fieldstyles/>
|
||||
</conditionalstyles>
|
||||
<editform tolerant="1"></editform>
|
||||
<editforminit/>
|
||||
<editforminitcodesource>0</editforminitcodesource>
|
||||
<editforminitfilepath>../src/quickgui/app/qgis-data</editforminitfilepath>
|
||||
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
|
||||
"""
|
||||
QGIS forms can have a Python function that is called when the form is
|
||||
opened.
|
||||
|
||||
Use this function to add extra logic to your forms.
|
||||
|
||||
Enter the name of the function in the "Python Init function"
|
||||
field.
|
||||
An example follows:
|
||||
"""
|
||||
from qgis.PyQt.QtWidgets import QWidget
|
||||
|
||||
def my_form_open(dialog, layer, feature):
|
||||
geom = feature.geometry()
|
||||
control = dialog.findChild(QWidget, "MyLineEdit")
|
||||
]]></editforminitcode>
|
||||
<featformsuppress>0</featformsuppress>
|
||||
<editorlayout>generatedlayout</editorlayout>
|
||||
<editable/>
|
||||
<labelOnTop/>
|
||||
<widgets/>
|
||||
<previewExpression>COALESCE( "Name", '<NULL>' )</previewExpression>
|
||||
<mapTip></mapTip>
|
||||
</maplayer>
|
||||
</projectlayers>
|
||||
<layerorder>
|
||||
<layer id="polys20151123133114244"/>
|
||||
<layer id="lines20151123133101198"/>
|
||||
</layerorder>
|
||||
<properties>
|
||||
<DefaultStyles>
|
||||
<AlphaInt type="int">255</AlphaInt>
|
||||
<ColorRamp type="QString"></ColorRamp>
|
||||
<Fill type="QString"></Fill>
|
||||
<Line type="QString"></Line>
|
||||
<Marker type="QString"></Marker>
|
||||
<Opacity type="double">1</Opacity>
|
||||
<RandomColors type="bool">true</RandomColors>
|
||||
</DefaultStyles>
|
||||
<Digitizing>
|
||||
<AvoidIntersectionsList type="QStringList"/>
|
||||
<DefaultSnapTolerance type="double">20</DefaultSnapTolerance>
|
||||
<DefaultSnapToleranceUnit type="int">1</DefaultSnapToleranceUnit>
|
||||
<DefaultSnapType type="QString">to vertex and segment</DefaultSnapType>
|
||||
<LayerSnapToList type="QStringList">
|
||||
<value>to_vertex_and_segment</value>
|
||||
<value>to_vertex_and_segment</value>
|
||||
<value>to_vertex_and_segment</value>
|
||||
</LayerSnapToList>
|
||||
<LayerSnappingEnabledList type="QStringList">
|
||||
<value>enabled</value>
|
||||
<value>enabled</value>
|
||||
<value>enabled</value>
|
||||
</LayerSnappingEnabledList>
|
||||
<LayerSnappingList type="QStringList">
|
||||
<value>lines20151123133101198</value>
|
||||
<value>points20151123133104693</value>
|
||||
<value>polys20151123133114244</value>
|
||||
</LayerSnappingList>
|
||||
<LayerSnappingToleranceList type="QStringList">
|
||||
<value>20.000000</value>
|
||||
<value>20.000000</value>
|
||||
<value>20.000000</value>
|
||||
</LayerSnappingToleranceList>
|
||||
<LayerSnappingToleranceUnitList type="QStringList">
|
||||
<value>1</value>
|
||||
<value>1</value>
|
||||
<value>1</value>
|
||||
</LayerSnappingToleranceUnitList>
|
||||
<SnappingMode type="QString">current_layer</SnappingMode>
|
||||
</Digitizing>
|
||||
<Gui>
|
||||
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
|
||||
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
|
||||
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
|
||||
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
|
||||
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
|
||||
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
|
||||
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
|
||||
</Gui>
|
||||
<Identify>
|
||||
<disabledLayers type="QStringList"/>
|
||||
</Identify>
|
||||
<Legend>
|
||||
<filterByMap type="bool">false</filterByMap>
|
||||
</Legend>
|
||||
<Macros>
|
||||
<pythonCode type="QString"></pythonCode>
|
||||
</Macros>
|
||||
<Measure>
|
||||
<Ellipsoid type="QString">WGS84</Ellipsoid>
|
||||
</Measure>
|
||||
<Measurement>
|
||||
<AreaUnits type="QString">m2</AreaUnits>
|
||||
<DistanceUnits type="QString">meters</DistanceUnits>
|
||||
</Measurement>
|
||||
<PAL>
|
||||
<CandidatesLine type="int">50</CandidatesLine>
|
||||
<CandidatesPoint type="int">16</CandidatesPoint>
|
||||
<CandidatesPolygon type="int">30</CandidatesPolygon>
|
||||
<DrawRectOnly type="bool">false</DrawRectOnly>
|
||||
<SearchMethod type="int">0</SearchMethod>
|
||||
<ShowingAllLabels type="bool">false</ShowingAllLabels>
|
||||
<ShowingCandidates type="bool">false</ShowingCandidates>
|
||||
<ShowingPartialsLabels type="bool">true</ShowingPartialsLabels>
|
||||
<TextFormat type="int">0</TextFormat>
|
||||
</PAL>
|
||||
<Paths>
|
||||
<Absolute type="bool">false</Absolute>
|
||||
</Paths>
|
||||
<PositionPrecision>
|
||||
<Automatic type="bool">true</Automatic>
|
||||
<DecimalPlaces type="int">2</DecimalPlaces>
|
||||
<DegreeFormat type="QString">MU</DegreeFormat>
|
||||
</PositionPrecision>
|
||||
<SpatialRefSys>
|
||||
<ProjectCRSID type="int">3452</ProjectCRSID>
|
||||
<ProjectCRSProj4String type="QString">+proj=longlat +datum=WGS84 +no_defs</ProjectCRSProj4String>
|
||||
<ProjectCrs type="QString">EPSG:4326</ProjectCrs>
|
||||
<ProjectionsEnabled type="int">1</ProjectionsEnabled>
|
||||
</SpatialRefSys>
|
||||
<Variables>
|
||||
<variableNames type="QStringList"/>
|
||||
<variableValues type="QStringList"/>
|
||||
</Variables>
|
||||
<WCSLayers type="QStringList"/>
|
||||
<WCSUrl type="QString"></WCSUrl>
|
||||
<WFSLayers type="QStringList"/>
|
||||
<WFSTLayers>
|
||||
<Delete type="QStringList"/>
|
||||
<Insert type="QStringList"/>
|
||||
<Update type="QStringList"/>
|
||||
</WFSTLayers>
|
||||
<WFSUrl type="QString"></WFSUrl>
|
||||
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
|
||||
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
|
||||
<WMSContactMail type="QString"></WMSContactMail>
|
||||
<WMSContactOrganization type="QString"></WMSContactOrganization>
|
||||
<WMSContactPerson type="QString"></WMSContactPerson>
|
||||
<WMSContactPhone type="QString"></WMSContactPhone>
|
||||
<WMSContactPosition type="QString"></WMSContactPosition>
|
||||
<WMSFees type="QString">conditions unknown</WMSFees>
|
||||
<WMSImageQuality type="int">90</WMSImageQuality>
|
||||
<WMSKeywordList type="QStringList">
|
||||
<value></value>
|
||||
</WMSKeywordList>
|
||||
<WMSMaxAtlasFeatures type="int">1</WMSMaxAtlasFeatures>
|
||||
<WMSOnlineResource type="QString"></WMSOnlineResource>
|
||||
<WMSPrecision type="QString">8</WMSPrecision>
|
||||
<WMSRootName type="QString"></WMSRootName>
|
||||
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
|
||||
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
|
||||
<WMSServiceCapabilities type="bool">false</WMSServiceCapabilities>
|
||||
<WMSServiceTitle type="QString"></WMSServiceTitle>
|
||||
<WMSUrl type="QString"></WMSUrl>
|
||||
<WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
|
||||
<WMTSGrids>
|
||||
<CRS type="QStringList"/>
|
||||
<Config type="QStringList"/>
|
||||
</WMTSGrids>
|
||||
<WMTSJpegLayers>
|
||||
<Group type="QStringList"/>
|
||||
<Layer type="QStringList"/>
|
||||
<Project type="bool">false</Project>
|
||||
</WMTSJpegLayers>
|
||||
<WMTSLayers>
|
||||
<Group type="QStringList"/>
|
||||
<Layer type="QStringList"/>
|
||||
<Project type="bool">false</Project>
|
||||
</WMTSLayers>
|
||||
<WMTSMinScale type="int">5000</WMTSMinScale>
|
||||
<WMTSPngLayers>
|
||||
<Group type="QStringList"/>
|
||||
<Layer type="QStringList"/>
|
||||
<Project type="bool">false</Project>
|
||||
</WMTSPngLayers>
|
||||
<WMTSUrl type="QString"></WMTSUrl>
|
||||
<ddt2>
|
||||
<designs type="QString"></designs>
|
||||
</ddt2>
|
||||
</properties>
|
||||
<visibility-presets/>
|
||||
<transformContext/>
|
||||
<projectMetadata>
|
||||
<identifier></identifier>
|
||||
<parentidentifier></parentidentifier>
|
||||
<language></language>
|
||||
<type></type>
|
||||
<title></title>
|
||||
<abstract></abstract>
|
||||
<contact>
|
||||
<name></name>
|
||||
<organization></organization>
|
||||
<position></position>
|
||||
<voice></voice>
|
||||
<fax></fax>
|
||||
<email></email>
|
||||
<role></role>
|
||||
</contact>
|
||||
<links/>
|
||||
<author></author>
|
||||
<creation>2000-01-01T00:00:00</creation>
|
||||
</projectMetadata>
|
||||
<Annotations/>
|
||||
<Layouts/>
|
||||
</qgis>
|