https://github.com/minorua/Qgis2threejs
Revision f8d5174c2c53f4ae36682c1cb6a1d30b36b91972 authored by Minoru Akagi on 19 April 2018, 06:23:23 UTC, committed by Minoru Akagi on 19 April 2018, 06:23:23 UTC
1 parent e0791e8
Raw File
Tip revision: f8d5174c2c53f4ae36682c1cb6a1d30b36b91972 authored by Minoru Akagi on 19 April 2018, 06:23:23 UTC
version 2.0.1
Tip revision: f8d5174
rotatedrect.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 RotatedRect
                              -------------------
        begin                : 2015-03-05
        copyright            : (C) 2015 Minoru Akagi
        email                : akaginch@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import math
from qgis.core import QgsPointXY, QgsRectangle, QgsGeometry


class RotatedRect:

  def __init__(self, center, width, height, rotation=0):
    """
    args:
      center        -- QgsPointXY
      width, height -- float
      rotation      -- int/float. in degrees counter-clockwise.
    """
    self._center = center
    self._width = width
    self._height = height
    self._rotation = rotation
    self._updateDerived()

  def clone(self):
    return RotatedRect(self._center, self._width, self._height, self._rotation)

  def _updateDerived(self):
    self._unrotated_rect = self._unrotatedRect()

  def _unrotatedRect(self):
    center = self._center
    half_width = self._width / 2
    half_height = self._height / 2
    return QgsRectangle(center.x() - half_width, center.y() - half_height,
                        center.x() + half_width, center.y() + half_height)

  @staticmethod
  def rotatePoint(point, degrees, origin=None):
    """Rotate point around the origin"""
    theta = degrees * math.pi / 180
    c = math.cos(theta)
    s = math.sin(theta)
    x = point.x()
    y = point.y()

    if origin:
      x -= origin.x()
      y -= origin.y()

    # rotate counter-clockwise
    xd = x * c - y * s
    yd = x * s + y * c

    if origin:
      xd += origin.x()
      yd += origin.y()
    return QgsPointXY(xd, yd)

  def normalizePoint(self, x, y):
    """Normalize given point. In result, lower-left is (0, 0) and upper-right is (1, 1)."""
    pt = QgsPointXY(x, y)
    if self._rotation:
      pt = self.rotatePoint(pt, -self._rotation, self._center)
    rect = self._unrotated_rect
    return QgsPointXY((pt.x() - rect.xMinimum()) / rect.width(),
                    (pt.y() - rect.yMinimum()) / rect.height())

  def scale(self, s):
    self._width *= s
    self._height *= s
    self._updateDerived()
    return self

  def rotate(self, degrees, origin=None):
    """Rotate the center of extent around the origin
    args:
      degrees -- int/float (counter-clockwise)
      origin  -- QgsPointXY
    """
    self._rotation += degrees
    if origin is None:
      return self
    self._center = self.rotatePoint(self._center, degrees, origin)
    self._updateDerived()
    return self

  def point(self, norm_point, y_inverted=False):
    """
    args:
      norm_point -- QgsPointXY (0 <= x <= 1, 0 <= y <= 1)
      y_inverted -- If True, lower-left is (0, 1) and upper-right is (1, 0).
                    Or else lower-left is (0, 0) and upper-right is (1, 1).
    """
    ur_rect = self._unrotated_rect
    x = ur_rect.xMinimum() + norm_point.x() * ur_rect.width()
    if y_inverted:
      y = ur_rect.yMaximum() - norm_point.y() * ur_rect.height()
    else:
      y = ur_rect.yMinimum() + norm_point.y() * ur_rect.height()
    return self.rotatePoint(QgsPointXY(x, y), self._rotation, self._center)

  def subrectangle(self, norm_rect, y_inverted=False):
    """
    args:
      norm_rect  -- QgsRectangle (0 <= xmin, 0 <= ymin, xmax <= 1, ymax <= 1)
      y_inverted -- If True, lower-left is (0, 1) and upper-right is (1, 0).
                    Or else lower-left is (0, 0) and upper-right is (1, 1).
    """
    ur_rect = self._unrotated_rect
    xmin = ur_rect.xMinimum() + norm_rect.xMinimum() * ur_rect.width()
    xmax = ur_rect.xMinimum() + norm_rect.xMaximum() * ur_rect.width()
    if y_inverted:
      ymin = ur_rect.yMaximum() - norm_rect.yMaximum() * ur_rect.height()
      ymax = ur_rect.yMaximum() - norm_rect.yMinimum() * ur_rect.height()
    else:
      ymin = ur_rect.yMinimum() + norm_rect.yMinimum() * ur_rect.height()
      ymax = ur_rect.yMinimum() + norm_rect.yMaximum() * ur_rect.height()

    rect = QgsRectangle(xmin, ymin, xmax, ymax)
    return RotatedRect(rect.center(), rect.width(), rect.height()).rotate(self._rotation, self._center)

  @classmethod
  def fromMapSettings(cls, mapSettings):
    extent = mapSettings.visibleExtent()
    rotation = mapSettings.rotation()
    if rotation == 0:
      return cls(extent.center(), extent.width(), extent.height())

    mupp = mapSettings.mapUnitsPerPixel()
    canvas_size = mapSettings.outputSize()
    return cls(extent.center(), mupp * canvas_size.width(), mupp * canvas_size.height(), rotation)

  def toMapSettings(self, mapSettings=None):
    if mapSettings is None:
      from qgis.core import QgsMapSettings
      mapSettings = QgsMapSettings()
    mapSettings.setExtent(self._unrotated_rect)
    mapSettings.setRotation(self._rotation)
    return mapSettings

  def boundingBox(self):
    theta = self._rotation * math.pi / 180
    c = abs(math.cos(theta))
    s = abs(math.sin(theta))
    hw = (self._width * c + self._height * s) / 2
    hh = (self._width * s + self._height * c) / 2
    return QgsRectangle(self._center.x() - hw, self._center.y() - hh,
                        self._center.x() + hw, self._center.y() + hh)

  def geotransform(self, cols, rows, is_grid_point=True):
    center = self._center
    ur_rect = self._unrotated_rect
    rotation = self._rotation

    segments_x = cols
    segments_y = rows
    if is_grid_point:
      segments_x -= 1
      segments_y -= 1

    if rotation:
      # rotate top-left corner of unrotated extent around center of extent counter-clockwise (map rotates clockwise)
      rpt = self.rotatePoint(QgsPointXY(ur_rect.xMinimum(), ur_rect.yMaximum()), rotation, center)
      res_lr = self._width / segments_x
      res_ul = self._height / segments_y

      theta = rotation * math.pi / 180
      c = math.cos(theta)
      s = math.sin(theta)
      geotransform = [rpt.x(), res_lr * c, res_ul * s, rpt.y(), res_lr * s, -res_ul * c]
      if is_grid_point:
        # top-left corner of extent corresponds to center of top-left pixel.
        geotransform[0] -= 0.5 * geotransform[1] + 0.5 * geotransform[2]
        geotransform[3] -= 0.5 * geotransform[4] + 0.5 * geotransform[5]
    else:
      xres = self._width / segments_x
      yres = self._height / segments_y
      geotransform = [ur_rect.xMinimum(), xres, 0, ur_rect.yMaximum(), 0, -yres]
      if is_grid_point:
        geotransform[0] -= 0.5 * geotransform[1]
        geotransform[3] -= 0.5 * geotransform[5]

    return geotransform

  def center(self):
    return self._center

  def width(self):
    return self._width

  def height(self):
    return self._height

  def rotation(self):
    return self._rotation

  def unrotatedRect(self):
    return self._unrotated_rect

  def geometry(self):
    geom = QgsGeometry.fromRect(self._unrotated_rect)
    if self._rotation:
      geom.rotate(-self._rotation, self._center)
    return geom

  def vertices(self):
    """return vertices of the rect clockwise"""
    rect = self._unrotated_rect
    pts = [QgsPointXY(rect.xMinimum(), rect.yMaximum()),
           QgsPointXY(rect.xMaximum(), rect.yMaximum()),
           QgsPointXY(rect.xMaximum(), rect.yMinimum()),
           QgsPointXY(rect.xMinimum(), rect.yMinimum())]

    if self._rotation:
      return [self.rotatePoint(pt, self._rotation, self._center) for pt in pts]

    return pts

  def __repr__(self):
    return "RotatedRect(c:{0}, w:{1}, h:{2}, r:{3})".format(self._center.toString(), self._width, self._height, self._rotation)

    # print coordinates of vertices
    pts = self.verticies()
    return "RotatedRect:" + ",".join(["P{0}({1})".format(x_y[0], x_y[1].toString()) for x_y in enumerate(pts)])
back to top