Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #37041 from olivierdalang/rotated_ticks
Support for rotated ticks/annotation
  • Loading branch information
m-kuhn committed Aug 4, 2020
2 parents f2bbb3e + a3dc6b3 commit 63f939e
Show file tree
Hide file tree
Showing 38 changed files with 1,332 additions and 893 deletions.
102 changes: 99 additions & 3 deletions python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in
Expand Up @@ -172,7 +172,10 @@ An individual grid which is drawn above the map content in a
Horizontal,
Vertical,
VerticalDescending,
BoundaryDirection
BoundaryDirection,
AboveTick,
OnTick,
UnderTick,
};

enum AnnotationFormat
Expand All @@ -193,7 +196,7 @@ An individual grid which is drawn above the map content in a
Left,
Right,
Bottom,
Top
Top,
};

enum FrameStyle
Expand All @@ -208,6 +211,12 @@ An individual grid which is drawn above the map content in a
ZebraNautical,
};

enum TickLengthMode
{
OrthogonalTicks,
NormalizedTicks,
};

enum FrameSideFlag
{
FrameLeft,
Expand Down Expand Up @@ -586,6 +595,7 @@ Returns the text format used when rendering grid annotations.
void setAnnotationFont( const QFont &font ) /Deprecated/;
%Docstring
Sets the ``font`` used for drawing grid annotations.
Shortcut for :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.setFont().

.. seealso:: :py:func:`annotationFont`

Expand All @@ -596,6 +606,7 @@ Sets the ``font`` used for drawing grid annotations.
QFont annotationFont() const /Deprecated/;
%Docstring
Returns the font used for drawing grid annotations.
Shortcut for :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.font().

.. seealso:: :py:func:`setAnnotationFont`

Expand All @@ -606,6 +617,7 @@ Returns the font used for drawing grid annotations.
void setAnnotationFontColor( const QColor &color ) /Deprecated/;
%Docstring
Sets the font ``color`` used for drawing grid annotations.
Shortcut for :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.setColor() and :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.setOpacity().

.. seealso:: :py:func:`annotationFontColor`

Expand All @@ -616,6 +628,7 @@ Sets the font ``color`` used for drawing grid annotations.
QColor annotationFontColor() const /Deprecated/;
%Docstring
Returns the font color used for drawing grid annotations.
Shortcut for :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.color() and :py:func:`~QgsLayoutItemMapGrid.annotationTextFormat`.opacity().

.. seealso:: :py:func:`setAnnotationFontColor`

Expand Down Expand Up @@ -844,6 +857,90 @@ The size of the line outlines drawn in the frame can be retrieved via the
framePenSize method.

.. seealso:: :py:func:`setFrameWidth`
%End

void setRotatedTicksEnabled( const bool state );
%Docstring
Enable/disable ticks rotation for rotated or reprojected grids.

.. seealso:: :py:func:`rotatedTicksEnabled`
%End

double rotatedTicksEnabled() const;
%Docstring
Gets whether ticks rotation for rotated or reprojected grids is enabled.

.. seealso:: :py:func:`setRotatedTicksEnabled`
%End

void setRotatedTicksLengthMode( const TickLengthMode mode );
%Docstring
Sets the tick length calculation mode.

.. seealso:: :py:func:`rotatedTicksLengthMode`
%End

TickLengthMode rotatedTicksLengthMode() const;
%Docstring
Returns the grid frame style.

.. seealso:: :py:func:`setRotatedTicksLengthMode`
%End

void setRotatedTicksMinimumAngle( const double angle );
%Docstring
Sets the ``minimum`` angle (in degrees) below which ticks are not drawn.

.. seealso:: :py:func:`rotatedTicksMinimumAngle`
%End

double rotatedTicksMinimumAngle() const;
%Docstring
Gets the ``minimum`` angle (in degrees) below which ticks are not drawn.

.. seealso:: :py:func:`setRotatedTicksMinimumAngle`
%End

void setRotatedAnnotationsEnabled( const bool state );
%Docstring
Enable/disable annotations rotation for rotated or reprojected grids.

.. seealso:: :py:func:`rotatedAnnotationsEnabled`
%End

double rotatedAnnotationsEnabled() const;
%Docstring
Gets whether annotations rotation for rotated or reprojected grids is enabled.

.. seealso:: :py:func:`setRotatedAnnotationsEnabled`
%End

void setRotatedAnnotationsLengthMode( const TickLengthMode mode );
%Docstring
Sets the annotation length calculation mode.

.. seealso:: :py:func:`rotatedAnnotationsLengthMode`
%End

TickLengthMode rotatedAnnotationsLengthMode() const;
%Docstring
Returns the grid frame style.

.. seealso:: :py:func:`setRotatedAnnotationsLengthMode`
%End

void setRotatedAnnotationsMinimumAngle( const double angle );
%Docstring
Sets the ``minimum`` angle (in degrees) below which annotated are not drawn.

.. seealso:: :py:func:`rotatedAnnotationsMinimumAngle`
%End

double rotatedAnnotationsMinimumAngle() const;
%Docstring
Gets the ``minimum`` angle (in degrees) below which annotated are not drawn.

.. seealso:: :py:func:`setRotatedAnnotationsMinimumAngle`
%End

void setFrameMargin( const double margin );
Expand Down Expand Up @@ -961,7 +1058,6 @@ Retrieves the second fill color for the grid frame.
virtual void refresh();


public:
};

QFlags<QgsLayoutItemMapGrid::FrameSideFlag> operator|(QgsLayoutItemMapGrid::FrameSideFlag f1, QFlags<QgsLayoutItemMapGrid::FrameSideFlag> f2);
Expand Down
52 changes: 42 additions & 10 deletions scripts/parse_dash_results.py
Expand Up @@ -34,6 +34,7 @@
import urllib.error
import re
import json
from PyQt5.QtCore import (Qt)
from PyQt5.QtGui import (
QImage, QColor, qRed, qBlue, qGreen, qAlpha, qRgb, QPixmap)
from PyQt5.QtWidgets import (QDialog,
Expand Down Expand Up @@ -72,12 +73,12 @@ def colorDiff(c1, c2):
def imageFromPath(path):
if (path[:8] == 'https://' or path[:7] == 'file://'):
# fetch remote image
print('fetching remote ({})'.format(path))
print('Fetching remote ({})'.format(path))
data = urllib.request.urlopen(path).read()
image = QImage()
image.loadFromData(data)
else:
print('using local ({})'.format(path))
print('Using local ({})'.format(path))
image = QImage(path)
return image

Expand All @@ -88,6 +89,7 @@ def __init__(self, parent, test_name, images):
super().__init__(parent)

self.setWindowTitle('Select reference image')
self.setWindowFlags(Qt.Window)

self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
Expand All @@ -112,8 +114,9 @@ def selected_image(self):
class ResultHandler(QDialog):

def __init__(self, parent=None):
super(ResultHandler, self).__init__()
super().__init__(parent)
self.setWindowTitle('Dash results')
self.setWindowFlags(Qt.Window)
self.control_label = QLabel()
self.rendered_label = QLabel()
self.diff_label = QLabel()
Expand Down Expand Up @@ -163,12 +166,17 @@ def __init__(self, parent=None):
save_mask_button.setText('Save New Mask')
save_mask_button.pressed.connect(self.save_mask)

add_ref_image_button = QPushButton()
add_ref_image_button.setText('Add Reference Image')
add_ref_image_button.pressed.connect(self.add_reference_image)

button_layout = QHBoxLayout()
button_layout.addWidget(next_image_button)
button_layout.addWidget(QLabel('Mask diff multiplier:'))
button_layout.addWidget(self.overload_spin)
button_layout.addWidget(preview_mask_button)
button_layout.addWidget(save_mask_button)
button_layout.addWidget(add_ref_image_button)
button_layout.addStretch()
v_layout.addLayout(button_layout)
self.setLayout(v_layout)
Expand Down Expand Up @@ -277,6 +285,27 @@ def save_mask(self):
self.new_mask_image.save(self.mask_image_path, "png")
self.load_next()

def add_reference_image(self):
if os.path.abspath(self.control_images_base_path) == os.path.abspath(self.found_control_image_path):
images = glob.glob(os.path.join(self.found_control_image_path, '*.png'))
default_path = os.path.join(self.found_control_image_path, 'set1')
os.makedirs(default_path)
for image in images:
imgname = os.path.basename(image)
os.rename(image, os.path.join(default_path, imgname))

for i in range(2, 100):
new_path = os.path.join(self.control_images_base_path, 'set' + str(i))
if not os.path.exists(new_path):
break
else:
raise RuntimeError('Could not find a suitable directory for another set of reference images')

os.makedirs(new_path)
control_image_name = os.path.basename(self.found_image)
self.rendered_image.save(os.path.join(new_path, control_image_name))
self.load_next()

def create_mask(self, control_image, rendered_image, mask_image, overload=1):
max_width = min(rendered_image.width(), control_image.width())
max_height = min(rendered_image.height(), control_image.height())
Expand Down Expand Up @@ -310,7 +339,7 @@ def create_mask(self, control_image, rendered_image, mask_image, overload=1):
rendered_rgb = struct.unpack(
'I', rendered_scanline[x * 4:x * 4 + 4])[0]
difference = min(
255, colorDiff(expected_rgb, rendered_rgb) * overload)
255, int(colorDiff(expected_rgb, rendered_rgb) * overload))

if difference > currentTolerance:
# update mask image
Expand All @@ -333,6 +362,9 @@ def get_control_image_path(self, test_name):

matching_control_images = [x[0]
for x in os.walk(control_images_folder) if test_name + '/' in x[0] or x[0].endswith(test_name)]

self.control_images_base_path = os.path.commonprefix(matching_control_images)

if len(matching_control_images) > 1:
for item in matching_control_images:
print(' - ' + item)
Expand All @@ -341,25 +373,25 @@ def get_control_image_path(self, test_name):
if not dlg.exec_():
return None

found_control_image_path = dlg.selected_image()
self.found_control_image_path = dlg.selected_image()
elif len(matching_control_images) == 0:
print(termcolor.colored('No matching control images found for {}'.format(test_name), 'yellow'))
return None
else:
found_control_image_path = matching_control_images[0]
self.found_control_image_path = matching_control_images[0]

# check for a single matching expected image
images = glob.glob(os.path.join(found_control_image_path, '*.png'))
images = glob.glob(os.path.join(self.found_control_image_path, '*.png'))
filtered_images = [i for i in images if not i[-9:] == '_mask.png']
if len(filtered_images) > 1:
error(
'Found multiple matching control images for {}'.format(test_name))
elif len(filtered_images) == 0:
error('No matching control images found for {}'.format(test_name))

found_image = filtered_images[0]
print('Found matching control image: {}'.format(found_image))
return found_image
self.found_image = filtered_images[0]
print('Found matching control image: {}'.format(self.found_image))
return self.found_image

def create_diff_image(self, control_image, rendered_image, mask_image):
# loop through pixels in rendered image and compare
Expand Down

0 comments on commit 63f939e

Please sign in to comment.