https://github.com/minorua/Qgis2threejs
Revision 0058a0738f45d58f694f3f5f2d97c75d843c9f32 authored by Minoru Akagi on 09 April 2018, 04:37:43 UTC, committed by Minoru Akagi on 09 April 2018, 04:37:43 UTC
1 parent 046139a
Raw File
Tip revision: 0058a0738f45d58f694f3f5f2d97c75d843c9f32 authored by Minoru Akagi on 09 April 2018, 04:37:43 UTC
JS: move app.addEventListeners() into app.init()
Tip revision: 0058a07
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 datetime
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, temporaryOutputDir
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:

  WORLD = "WORLD"
  CONTROLS = "CTRL"
  LAYERS = "LAYERS"

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

    self.isOrthoCamera = False

    self.base64 = False

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

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

  def worldProperties(self):
    return self.data.get(ExportSettings.WORLD, {})

  def setWorldProperties(self, properties):
    self.data[ExportSettings.WORLD] = properties
    self._mapTo3d = None

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

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

    world = self.worldProperties()
    baseSize = world.get("lineEdit_BaseSize", def_vals.baseSize)
    verticalExaggeration = world.get("lineEdit_zFactor", def_vals.zExaggeration)
    verticalShift = world.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)
back to top