https://github.com/minorua/Qgis2threejs
Revision 96476af712037e4ab4bd9eeb6756d89036974e72 authored by Minoru Akagi on 12 October 2018, 07:03:11 UTC, committed by Minoru Akagi on 12 October 2018, 07:31:13 UTC
1 parent 5908c24
Raw File
Tip revision: 96476af712037e4ab4bd9eeb6756d89036974e72 authored by Minoru Akagi on 12 October 2018, 07:03:11 UTC
remove redundant characters in data filename
Tip revision: 96476af
exportsettings.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 ExportSettings
                              -------------------
        begin                : 2014-01-16
        copyright            : (C) 2014 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 os
import json

from PyQt5.QtCore import QSettings
from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsMapLayer, QgsProject, QgsWkbTypes

from . import q3dconst
from .conf import def_vals
from .pluginmanager import pluginManager
from .propertyreader import DEMPropertyReader, VectorPropertyReader
from .rotatedrect import RotatedRect
from .qgis2threejscore import MapTo3D, GDALDEMProvider, FlatDEMProvider
from .qgis2threejstools import getLayersInProject, getTemplateConfig, logMessage, settingsFilePath
from .vectorobject import objectTypeRegistry


class Layer:

  def __init__(self, layerId, name, geomType, properties=None, visible=False):
    self.layerId = layerId
    self.name = name
    self.geomType = geomType
    self.properties = properties
    self.visible = visible

    self.id = None
    self.jsLayerId = None
    self.mapLayer = None
    self.updated = False

  def toDict(self):
    return {"layerId": self.layerId,
            "name": self.name,
            "geomType": self.geomType,
            "properties": self.properties,
            "visible": self.visible}

  @classmethod
  def fromDict(self, obj):
    id = obj["layerId"]
    lyr = Layer(id, obj["name"], obj["geomType"], obj["properties"], obj["visible"])
    lyr.mapLayer = QgsProject.instance().mapLayer(id)
    return lyr

  @classmethod
  def fromQgsMapLayer(cls, layer):
    lyr = Layer(layer.id(), layer.name(), cls.getGeometryType(layer))
    lyr.mapLayer = layer
    return lyr

  @classmethod
  def getGeometryType(cls, layer):
    """layer: QgsMapLayer sub-class object"""
    layerType = layer.type()
    if layerType == QgsMapLayer.VectorLayer:
      return {QgsWkbTypes.PointGeometry: q3dconst.TYPE_POINT,
              QgsWkbTypes.LineGeometry: q3dconst.TYPE_LINESTRING,
              QgsWkbTypes.PolygonGeometry: q3dconst.TYPE_POLYGON}.get(layer.geometryType())

    elif layerType == QgsMapLayer.RasterLayer and layer.providerType() == "gdal" and layer.bandCount() == 1:
      return q3dconst.TYPE_DEM

    return None


class ExportSettings:

  SCENE = "SCENE"
  WORLD = "WORLD"   # renamed to SCENE
  CAMERA = "CAMERA"
  CONTROLS = "CTRL"
  LAYERS = "LAYERS"
  OPTIONS = "OPT"   # template specific settings
  INSETS = "INSETS"

  def __init__(self):
    self.data = {}
    self.canvas = None
    self.mapSettings = None
    self.baseExtent = None
    self.crs = None
    self.base64 = False

    # cache
    self._mapTo3d = None
    self._templateConfig = None

  def clear(self):
    self.data = {}

  def sceneProperties(self):
    return self.data.get(ExportSettings.SCENE, self.data.get(ExportSettings.WORLD, {}))   # for backward compatibility

  def setSceneProperties(self, properties):
    self.data[ExportSettings.SCENE] = properties
    self._mapTo3d = None

  def coordsInWGS84(self):
    return self.sceneProperties().get("radioButton_WGS84", False)

  def materialType(self):
    return self.sceneProperties().get("comboBox_MaterialType", 0)

  def isOrthoCamera(self):
    return (self.data.get(ExportSettings.CAMERA) == "ORTHO")

  def setCamera(self, is_ortho):
    self.data[ExportSettings.CAMERA] = "ORTHO" if is_ortho else "PERSPECTIVE"

  def controls(self):
    ctrl = self.data.get(ExportSettings.CONTROLS, {}).get("comboBox_Controls")
    if ctrl:
      return ctrl
    return QSettings().value("/Qgis2threejs/lastControls", def_vals.controls, type=str)

  def setControls(self, name):
    self.data[ExportSettings.CONTROLS] = {"comboBox_Controls": name}

  def loadSettings(self, settings):
    self.data = settings
    self._mapTo3d = None

  def loadSettingsFromFile(self, filepath=None):
    """load settings from a JSON file"""
    self.data = {}
    if filepath is None:
      filepath = settingsFilePath()   # get settings file path for current project
      if filepath is None:
        return False

    try:
      with open(filepath, encoding="UTF-8") as f:
        settings = json.load(f)
    except Exception as e:
      logMessage("Failed to load export settings from file. Error: " + str(e))
      return False

    logMessage("Export settings loaded from file:" + filepath)

    # transform layer dict to Layer object
    settings[ExportSettings.LAYERS] = [Layer.fromDict(lyr) for lyr in settings.get(ExportSettings.LAYERS, [])]

    self.loadSettings(settings)
    return True

  def saveSettings(self, filepath=None):
    """save settings to a JSON file"""
    if filepath is None:
      filepath = settingsFilePath()
      if filepath is None:
        return False

    def default(obj):
      if isinstance(obj, Layer):
        return obj.toDict()
      raise TypeError(repr(obj) + " is not JSON serializable")

    try:
      with open(filepath, "w", encoding="UTF-8") as f:
        json.dump(self.data, f, ensure_ascii=False, indent=2, default=default, sort_keys=True)
      return True
    except Exception as e:
      logMessage("Failed to save export settings: " + str(e))
      return False

  def template(self):
    return self.data.get("Template", def_vals.template)

  def setTemplate(self, filepath):
    """filepath: relative path from html_templates directory or absolute path to a template html file"""
    self.data["Template"] = filepath
    self._templateConfig = None

  def outputFileName(self):
    return self.data.get("OutputFilename", "")

  def outputFileTitle(self):
    return os.path.splitext(os.path.basename(self.outputFileName()))[0]

  def outputDirectory(self):
    return os.path.split(self.outputFileName())[0]

  def outputDataDirectory(self):
    return os.path.join(self.outputDirectory(), "data", self.outputFileTitle())

  def setOutputFilename(self, filepath=""):
    self.data["OutputFilename"] = filepath

  def options(self):
    return self.data.get(ExportSettings.OPTIONS, {})

  def option(self, key):
    return self.data.get(ExportSettings.OPTIONS, {}).get(key)

  def setOption(self, key, value):
    self.data[ExportSettings.OPTIONS] = self.data.get(ExportSettings.OPTIONS, {})
    self.data[ExportSettings.OPTIONS][key] = value

  def setMapCanvas(self, canvas):
    self.setMapSettings(canvas.mapSettings())
    self.canvas = canvas

  def setMapSettings(self, settings):
    """settings: QgsMapSettings"""
    self.canvas = None
    self._mapTo3d = None
    self.mapSettings = settings

    self.baseExtent = RotatedRect.fromMapSettings(settings)
    self.crs = settings.destinationCrs()

  def mapTo3d(self):
    if self._mapTo3d:
      return self._mapTo3d

    if self.mapSettings is None:
      return None

    sp = self.sceneProperties()
    baseSize = sp.get("lineEdit_BaseSize", def_vals.baseSize)
    verticalExaggeration = sp.get("lineEdit_zFactor", def_vals.zExaggeration)
    verticalShift = sp.get("lineEdit_zShift", def_vals.zShift)
    self._mapTo3d = MapTo3D(self.mapSettings, float(baseSize), float(verticalExaggeration), float(verticalShift))
    return self._mapTo3d

  def templateConfig(self):
    if self._templateConfig:
      return self._templateConfig
    self._templateConfig = getTemplateConfig(self.template())
    return self._templateConfig

  def wgs84Center(self):
    if self.crs and self.baseExtent:
      wgs84 = QgsCoordinateReferenceSystem(4326)
      transform = QgsCoordinateTransform(self.crs, wgs84, QgsProject.instance())
      return transform.transform(self.baseExtent.center())
    return None

  def get(self, key, default=None):
    return self.data.get(key, default)

  def checkValidity(self):
    """check validity of export settings. return error message as unicode. return None if valid."""
    return None

  def demProviderByLayerId(self, id):
    if id == "FLAT":
      return FlatDEMProvider()

    if id.startswith("plugin:"):
      provider = pluginManager().findDEMProvider(id[7:])
      if provider:
        return provider(str(self.crs.toWkt()))

      logMessage('Plugin "{0}" not found'.format(id))

    else:
      layer = QgsProject.instance().mapLayer(id)
      if layer:
        return GDALDEMProvider(layer.source(), str(self.crs.toWkt()), source_wkt=str(layer.crs().toWkt()))    # use CRS set to the layer in QGIS

    return FlatDEMProvider()

  def getLayerList(self):
    return self.data.get(ExportSettings.LAYERS, [])

  def updateLayerList(self):
    layers = []

    # DEM and Vector layers
    for layer in getLayersInProject():
      if Layer.getGeometryType(layer) is not None:
        item = self.getItemByLayerId(layer.id())
        if item is None:
          item = Layer.fromQgsMapLayer(layer)
        else:
          item.name = layer.name()    # update layer name
        layers.append(item)

    # DEM provider plugins
    for plugin in pluginManager().demProviderPlugins():
      layerId = "plugin:" + plugin.providerId()
      item = self.getItemByLayerId(layerId)
      if item is None:
        item = Layer(layerId, plugin.providerName(), q3dconst.TYPE_DEM)
      layers.append(item)

    # Flat plane
    layerId = "FLAT"
    item = self.getItemByLayerId(layerId)
    if item is None:
      item = Layer(layerId, "Flat Plane", q3dconst.TYPE_DEM)
    layers.append(item)

    # update id and jsLayerId
    for index, layer in enumerate(layers):
      layer.id = index
      layer.jsLayerId = index      # "{}_{}".format(itemId, layerId[:8])

    self.data[ExportSettings.LAYERS] = layers

  def getItemByLayerId(self, layerId):
    if layerId is not None:
      for layer in self.getLayerList():
        if layer.layerId == layerId:
          return layer
    return None

  def getPropertyReaderByLayerId(self, layerId, renderContext=None):
    """renderContext: required if the layer is a vector layer"""
    layer = self.getItemByLayerId(layerId)
    if layer is None:
      return None

    if layer.geomType == q3dconst.TYPE_DEM:
      return DEMPropertyReader(layer.layerId, layer.properties)

    return VectorPropertyReader(objectTypeRegistry(), renderContext, layer.mapLayer, layer.properties)

  def setNorthArrowVisible(self, visible):
    self.data[ExportSettings.INSETS] = self.data.get(ExportSettings.INSETS, {})
    self.data[ExportSettings.INSETS]["NorthArrow"] = visible

  def isNorthArrowVisible(self):
    return self.data.get(ExportSettings.INSETS, {}).get("NorthArrow", False)
back to top