Merge pull request #9857 from marcel-dancak/tiles_xyz

New Processing Algorithm to generate raster XYZ tiles
This commit is contained in:
Martin Dobias 2019-04-26 08:41:30 +02:00 committed by GitHub
commit 5e4ea73399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1210 additions and 1 deletions

View File

@ -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.

View File

@ -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(),

View 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)

View File

@ -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()

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -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

View File

@ -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
View 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", '&lt;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", '&lt;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>