Skip to content

Commit 47782c9

Browse files
authored
Merge pull request #3 from north-road/master
Merge latest downstream changes
2 parents dfb276b + 7336a8a commit 47782c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2427
-982
lines changed

.github/workflows/build.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Build
2+
3+
on: push
4+
5+
jobs:
6+
build:
7+
name: "Build"
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Get source code
12+
uses: actions/checkout@v4
13+
with:
14+
# To fetch tags
15+
fetch-depth: 0
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.10"
21+
cache: "pip"
22+
cache-dependency-path: "requirements/packaging.txt"
23+
24+
# - name: Install Qt lrelease
25+
# run: |
26+
# sudo apt-get update
27+
# sudo apt-get install qttools5-dev-tools
28+
29+
- name: Install Python requirements
30+
run: pip install -r requirements/packaging.txt
31+
32+
- name: Set plugin version environment variables
33+
run: |
34+
TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
35+
echo "VERSION=$(echo ${TAG} | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}')-alpha" >> $GITHUB_ENV
36+
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
37+
38+
- name: Build package
39+
run: |
40+
qgis-plugin-ci --no-validation package ${{ env.VERSION }}
41+
mkdir tmp
42+
unzip redistrict.${{ env.VERSION }}.zip -d tmp
43+
44+
- name: Save PR number to zips
45+
run: |
46+
cd tmp
47+
echo ${{ github.event.number }} | tee pr_number
48+
echo ${{ github.event.pull_request.head.sha }} | tee git_commit
49+
echo ${{ github.event.number }}
50+
51+
- uses: actions/upload-artifact@v4
52+
with:
53+
name: redistrict_plugin.${{ env.VERSION }}
54+
path: tmp
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: Write build artifact comments
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Build"]
6+
types:
7+
- completed
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
on-success:
14+
15+
permissions:
16+
pull-requests: write
17+
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Get source code
21+
uses: actions/checkout@v4
22+
with:
23+
# To fetch tags
24+
fetch-depth: 0
25+
26+
- name: 'Set plugin version environment variables'
27+
run: |
28+
TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
29+
echo "VERSION=$(echo ${TAG} | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}')-alpha" >> $GITHUB_ENV
30+
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
31+
- name: 'Download artifact'
32+
id: download_artifact
33+
uses: actions/github-script@v7
34+
with:
35+
script: |
36+
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
37+
owner: context.repo.owner,
38+
repo: context.repo.repo,
39+
run_id: context.payload.workflow_run.id,
40+
});
41+
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
42+
return artifact.name == 'redistrict_plugin.${{ env.VERSION }}'
43+
});
44+
matchArtifacts.forEach((artifact) => {
45+
});
46+
if (matchArtifacts.length>0)
47+
{
48+
let download = await github.rest.actions.downloadArtifact({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
artifact_id: matchArtifacts[0].id,
52+
archive_format: 'zip',
53+
});
54+
let fs = require('fs');
55+
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/redistrict_plugin.${{ env.VERSION }}.zip`, Buffer.from(download.data));
56+
core.setOutput('artifact_id', matchArtifacts[0].id);
57+
}
58+
else
59+
{
60+
core.setOutput('artifact_id', 0);
61+
}
62+
63+
- name: 'Unzip artifact'
64+
if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0
65+
run: |
66+
unzip -n redistrict_plugin.${{ env.VERSION }}
67+
68+
- name: 'Post artifact download link as comment on PR'
69+
if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0
70+
uses: actions/github-script@v7
71+
with:
72+
github-token: ${{ secrets.GITHUB_TOKEN }}
73+
script: |
74+
let fs = require('fs');
75+
let issue_number = Number(fs.readFileSync('./pr_number'));
76+
let git_sha = String(fs.readFileSync('./git_commit')).trim();
77+
const prComments = await github.rest.issues.listComments({
78+
owner: context.repo.owner,
79+
repo: context.repo.repo,
80+
issue_number: issue_number,
81+
});
82+
const PREFIX = "## Plugin ready!";
83+
let body = PREFIX + "\n\n" +
84+
"A test version of this PR is available for testing [here](https://github.com/" + context.repo.owner + "/" + context.repo.repo + "/suites/" + context.payload.workflow_run.check_suite_id + "/artifacts/${{steps.download_artifact.outputs.artifact_id}}).";
85+
body += "\n\n*(Built from commit " + git_sha + ")*";
86+
87+
const winBuildComment = prComments.data?.find(c => c.body.startsWith(PREFIX));
88+
if (!!winBuildComment) {
89+
// update the existing comment
90+
await github.rest.issues.updateComment({
91+
owner: context.repo.owner,
92+
repo: context.repo.repo,
93+
comment_id: winBuildComment.id,
94+
body: body
95+
});
96+
} else {
97+
// submit a new comment
98+
await github.rest.issues.createComment({
99+
owner: context.repo.owner,
100+
repo: context.repo.repo,
101+
issue_number: issue_number,
102+
body: body
103+
});
104+
}
105+

pylintrc

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
# pygtk.require().
88
#init-hook=
99

10-
# Profiled execution.
11-
profile=no
12-
1310
# Add files or directories to the blacklist. They should be base names, not
1411
# paths.
1512
ignore=CVS
@@ -49,11 +46,6 @@ disable=locally-disabled,C0103,no-name-in-module,duplicate-code,import-error,lin
4946
# mypackage.mymodule.MyReporterClass.
5047
output-format=text
5148

52-
# Put messages in a separate file for each module / package specified on the
53-
# command line instead of printing them on stdout. Reports (if any) will be
54-
# written in a file name "pylint_global.[txt|html]".
55-
files-output=no
56-
5749
# Tells whether to display a full report or only the messages
5850
reports=yes
5951

@@ -64,23 +56,13 @@ reports=yes
6456
# (RP0004).
6557
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
6658

67-
# Add a comment according to your evaluation note. This is used by the global
68-
# evaluation report (RP0004).
69-
comment=no
70-
7159
# Template used to display messages. This is a python new-style format string
7260
# used to format the message information. See doc for all details
7361
#msg-template=
7462

7563

7664
[BASIC]
7765

78-
# Required attributes for module, separated by a comma
79-
required-attributes=
80-
81-
# List of builtins function names that should not be used, separated by a comma
82-
bad-functions=map,filter,apply,input
83-
8466
# Regular expression which should only match correct module names
8567
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
8668

@@ -144,10 +126,6 @@ ignore-mixin-members=yes
144126
# (useful for classes with attributes dynamically set).
145127
ignored-classes=SQLObject
146128

147-
# When zope mode is activated, add a predefined set of Zope acquired attributes
148-
# to generated-members.
149-
zope=no
150-
151129
# List of members which are set dynamically and missed by pylint inference
152130
# system, and so shouldn't trigger E0201 when accessed. Python regular
153131
# expressions are accepted.
@@ -180,9 +158,6 @@ ignore-long-lines=^\s*(# )?<?https?://\S+>?$
180158
# else.
181159
single-line-if-stmt=no
182160

183-
# List of optional constructs for which whitespace checking is disabled
184-
no-space-check=trailing-comma,dict-separator
185-
186161
# Maximum number of lines in a module
187162
max-module-lines=1000
188163

@@ -260,10 +235,6 @@ max-public-methods=20
260235

261236
[CLASSES]
262237

263-
# List of interface methods to ignore, separated by a comma. This is used for
264-
# instance to not check methods defines in Zope's Interface base class.
265-
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
266-
267238
# List of method names used to declare (i.e. assign) instance attributes.
268239
defining-attr-methods=__init__,__new__,setUp
269240

@@ -275,7 +246,3 @@ valid-metaclass-classmethod-first-arg=mcs
275246

276247

277248
[EXCEPTIONS]
278-
279-
# Exceptions that will emit a warning when being caught. Defaults to
280-
# "Exception"
281-
overgeneral-exceptions=Exception

redistrict/core/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Core module
3+
"""
4+
5+
from .core_utils import CoreUtils # NOQA

redistrict/core/core_utils.py

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
# -*- coding: utf-8 -*-
2-
"""LINZ Redistricting Plugin - Core Utilities
3-
4-
.. note:: This program is free software; you can redistribute it and/or modify
5-
it under the terms of the GNU General Public License as published by
6-
the Free Software Foundation; either version 2 of the License, or
7-
(at your option) any later version.
1+
"""
2+
LINZ Redistricting Plugin - Core Utilities
83
"""
94

10-
__author__ = '(C) 2018 by Nyall Dawson'
11-
__date__ = '20/04/2018'
12-
__copyright__ = 'Copyright 2018, LINZ'
13-
# This will get replaced with a git SHA1 when you do a git archive
14-
__revision__ = '$Format:%H$'
5+
from typing import List
156

16-
from qgis.core import (QgsVectorLayer,
17-
QgsRuleBasedLabeling,
18-
QgsVectorLayerSimpleLabeling)
7+
from qgis.core import (
8+
Qgis,
9+
QgsGeometry,
10+
QgsVectorLayer,
11+
QgsRuleBasedLabeling,
12+
QgsVectorLayerSimpleLabeling,
13+
QgsWkbTypes
14+
)
1915

2016

2117
class CoreUtils:
@@ -49,3 +45,34 @@ def enable_label_rules(rule: QgsRuleBasedLabeling.Rule):
4945
labeling.setSettings(settings)
5046

5147
layer.triggerRepaint()
48+
49+
@staticmethod
50+
def union_geometries(geometries: List[QgsGeometry]) -> QgsGeometry:
51+
"""
52+
Unions geometries, using the optimal method available
53+
"""
54+
if Qgis.QGIS_VERSION_INT >= 34100:
55+
# use optimized GEOS coverage union method
56+
# this is only possible for polygons, which should be safe to
57+
# assume, unless we are running the test suite!
58+
if all(g.type() == QgsWkbTypes.PolygonGeometry for g in
59+
geometries):
60+
collected_multi_polygon = QgsGeometry.collectGeometry(geometries)
61+
# use low-level API so that we can specify a 0.005m tolerance, to avoid
62+
# slivers
63+
geos_engine = QgsGeometry.createGeometryEngine(collected_multi_polygon.constGet(), 0.005)
64+
geom, err = geos_engine.unionCoverage()
65+
return QgsGeometry(geom)
66+
elif Qgis.QGIS_VERSION_INT >= 33600:
67+
# use optimized GEOS coverage union method
68+
# this is only possible for polygons, which should be safe to
69+
# assume, unless we are running the test suite!
70+
if all(g.type() == QgsWkbTypes.PolygonGeometry for g in
71+
geometries):
72+
return QgsGeometry.unionCoverage(
73+
QgsGeometry.collectGeometry(geometries)
74+
)
75+
76+
return QgsGeometry.unaryUnion(
77+
geometries
78+
)

redistrict/core/district_registry.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
1-
# -*- coding: utf-8 -*-
2-
"""LINZ Redistricting Plugin - District registry
3-
4-
.. note:: This program is free software; you can redistribute it and/or modify
5-
it under the terms of the GNU General Public License as published by
6-
the Free Software Foundation; either version 2 of the License, or
7-
(at your option) any later version.
81
"""
9-
10-
__author__ = '(C) 2018 by Nyall Dawson'
11-
__date__ = '20/04/2018'
12-
__copyright__ = 'Copyright 2018, LINZ'
13-
# This will get replaced with a git SHA1 when you do a git archive
14-
__revision__ = '$Format:%H$'
2+
LINZ Redistricting Plugin - District registry
3+
"""
154

165
from collections import OrderedDict
17-
from qgis.core import (QgsCoordinateTransform,
18-
QgsProject,
19-
QgsSettings,
20-
QgsFeatureRequest,
21-
QgsExpression,
22-
NULL)
6+
from qgis.core import (
7+
QgsCoordinateTransform,
8+
QgsProject,
9+
QgsSettings,
10+
QgsFeatureRequest,
11+
QgsExpression,
12+
NULL
13+
)
2314

2415
MAX_RECENT_DISTRICTS = 5
2516

@@ -100,7 +91,7 @@ def settings_key(self):
10091
"""
10192
Returns the QSettings key corresponding to this registry
10293
"""
103-
return 'redistricting/{}'.format(self.name)
94+
return f'redistricting/{self.name}'
10495

10596
def district_list(self):
10697
"""
@@ -119,8 +110,7 @@ def clear_recent_districts(self):
119110
"""
120111
Clears the list of recent districts
121112
"""
122-
QgsSettings().setValue('{}/recent_districts'.format(
123-
self.settings_key()), [])
113+
QgsSettings().setValue(f'{self.settings_key()}/recent_districts', [])
124114

125115
def push_recent_district(self, district):
126116
"""
@@ -132,16 +122,17 @@ def push_recent_district(self, district):
132122
[d for d in recent_districts
133123
if d != district]
134124
recent_districts = recent_districts[:MAX_RECENT_DISTRICTS]
135-
QgsSettings().setValue('{}/recent_districts'.format(
136-
self.settings_key()), recent_districts)
125+
QgsSettings().setValue(f'{self.settings_key()}/recent_districts',
126+
recent_districts)
137127

138128
def recent_districts_list(self):
139129
"""
140130
Returns a list of recently used districts
141131
"""
142132
valid_districts = self.district_list()
143-
return [d for d in QgsSettings().value('{}/recent_districts'.format(
144-
self.settings_key()), []) if d in valid_districts]
133+
return [d for d in QgsSettings().value(
134+
f'{self.settings_key()}/recent_districts',
135+
[]) if d in valid_districts]
145136

146137
def get_district_at_point(self, rect, crs): # pylint: disable=unused-argument
147138
"""

0 commit comments

Comments
 (0)