primera subida
This commit is contained in:
140
lib/GoogleMapDownloader.py
Normal file
140
lib/GoogleMapDownloader.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/python
|
||||
# GoogleMapDownloader.py
|
||||
# Created by Hayden Eskriett [http://eskriett.com]
|
||||
#
|
||||
# A script which when given a longitude, latitude and zoom level downloads a
|
||||
# high resolution google map
|
||||
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
||||
|
||||
import math
|
||||
# from PIL import Image
|
||||
import os
|
||||
import urllib
|
||||
|
||||
|
||||
# alternativa a PIL: Image
|
||||
# CV2
|
||||
|
||||
class GoogleMapDownloader:
|
||||
"""
|
||||
A class which generates high resolution google maps images given
|
||||
a longitude, latitude and zoom level
|
||||
"""
|
||||
|
||||
def __init__(self, lat, lng, zoom=12):
|
||||
"""
|
||||
GoogleMapDownloader Constructor
|
||||
|
||||
Args:
|
||||
lat: The latitude of the location required
|
||||
lng: The longitude of the location required
|
||||
zoom: The zoom level of the location required, ranges from 0 - 23
|
||||
defaults to 12
|
||||
"""
|
||||
self._lat = lat
|
||||
self._lng = lng
|
||||
self._zoom = zoom
|
||||
|
||||
def getXY(self):
|
||||
"""
|
||||
Generates an X,Y tile coordinate based on the latitude, longitude
|
||||
and zoom level
|
||||
|
||||
Returns: An X,Y tile coordinate
|
||||
"""
|
||||
|
||||
tile_size = 256
|
||||
|
||||
# Use a left shift to get the power of 2
|
||||
# i.e. a zoom level of 2 will have 2^2 = 4 tiles
|
||||
numTiles = 1 << self._zoom
|
||||
|
||||
# Find the x_point given the longitude
|
||||
point_x = (tile_size/ 2 + self._lng * tile_size / 360.0) * numTiles // tile_size
|
||||
|
||||
# Convert the latitude to radians and take the sine
|
||||
sin_y = math.sin(self._lat * (math.pi / 180.0))
|
||||
|
||||
# Calulate the y coorindate
|
||||
point_y = ((tile_size / 2) + 0.5 * math.log((1+sin_y)/(1-sin_y)) * -(tile_size / (2 * math.pi))) * numTiles // tile_size
|
||||
|
||||
return int(point_x), int(point_y)
|
||||
|
||||
def generateImage(self, **kwargs):
|
||||
"""
|
||||
Generates an image by stitching a number of google map tiles together.
|
||||
|
||||
Args:
|
||||
start_x: The top-left x-tile coordinate
|
||||
start_y: The top-left y-tile coordinate
|
||||
tile_width: The number of tiles wide the image should be -
|
||||
defaults to 5
|
||||
tile_height: The number of tiles high the image should be -
|
||||
defaults to 5
|
||||
Returns:
|
||||
A high-resolution Goole Map image.
|
||||
"""
|
||||
|
||||
start_x = kwargs.get('start_x', None)
|
||||
start_y = kwargs.get('start_y', None)
|
||||
tile_width = kwargs.get('tile_width', 5)
|
||||
tile_height = kwargs.get('tile_height', 5)
|
||||
|
||||
# Check that we have x and y tile coordinates
|
||||
if start_x == None or start_y == None :
|
||||
start_x, start_y = self.getXY()
|
||||
|
||||
# Determine the size of the image
|
||||
width, height = 256 * tile_width, 256 * tile_height
|
||||
|
||||
#Create a new image of the size require
|
||||
map_img = Image.new('RGB', (width,height))
|
||||
|
||||
"""
|
||||
Las capas disponibles de Google son:
|
||||
Carreteras
|
||||
http://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}
|
||||
Terreno
|
||||
http://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}
|
||||
Carreteras modificadas
|
||||
http://mt0.google.com/vt/lyrs=r&hl=en&x={x}&y={y}&z={z}
|
||||
Solo satelital
|
||||
http://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}
|
||||
Terreno
|
||||
http://mt0.google.com/vt/lyrs=t&hl=en&x={x}&y={y}&z={z}
|
||||
Hibrido
|
||||
http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}
|
||||
"""
|
||||
|
||||
for x in range(0, tile_width):
|
||||
for y in range(0, tile_height) :
|
||||
url = 'https://mt0.google.com/vt?x='+str(start_x+x)+'&y='+str(start_y+y)+'&z='+str(self._zoom)
|
||||
print (url)
|
||||
current_tile = str(x)+'-'+str(y)
|
||||
urllib.urlretrieve(url, current_tile)
|
||||
|
||||
im = Image.open(current_tile)
|
||||
map_img.paste(im, (x*256, y*256))
|
||||
|
||||
os.remove(current_tile)
|
||||
|
||||
return map_img
|
||||
|
||||
def main():
|
||||
# Create a new instance of GoogleMap Downloader
|
||||
gmd = GoogleMapDownloader(51.5171, 0.1062, 13)
|
||||
|
||||
print("The tile coorindates are {}".format(gmd.getXY()))
|
||||
|
||||
try:
|
||||
# Get the high resolution image
|
||||
img = gmd.generateImage()
|
||||
except IOError:
|
||||
print("Could not generate the image - try adjusting the zoom level and checking your coordinates")
|
||||
else:
|
||||
#Save the image to disk
|
||||
img.save("high_resolution_image.png")
|
||||
print("The map has successfully been created")
|
||||
|
||||
|
||||
#if __name__ == '__main__': main()
|
||||
598
lib/catastro/Spanish_Inspire_Catastral_Downloader.py
Normal file
598
lib/catastro/Spanish_Inspire_Catastral_Downloader.py
Normal file
@@ -0,0 +1,598 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
/***************************************************************************
|
||||
Spanish_Inspire_Catastral_Downloader
|
||||
A QGIS plugin
|
||||
Spanish Inspire Catastral Downloader
|
||||
-------------------
|
||||
begin : 2017-06-18
|
||||
git sha : $Format:%H$
|
||||
copyright : (C) 2017 by Patricio Soriano :: SIGdeletras.com
|
||||
email : pasoriano@sigdeletras.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 json
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from urllib import parse, request
|
||||
|
||||
# Import the PyQt and QGIS libraries
|
||||
from qgis.PyQt.QtCore import Qt
|
||||
|
||||
# from PyQt5.QtWidgets import QDialog
|
||||
# For Debug
|
||||
try:
|
||||
from pydevd import *
|
||||
except ImportError:
|
||||
None
|
||||
|
||||
from PyQt5 import QtNetwork, uic
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from qgis.core import Qgis
|
||||
|
||||
QT_VERSION = 5
|
||||
os.environ['QT_API'] = 'pyqt5'
|
||||
|
||||
from qgis.core import *
|
||||
from qgis.core import (QgsMessageLog)
|
||||
|
||||
from .Spanish_Inspire_Catastral_Downloader_dialog import \
|
||||
Spanish_Inspire_Catastral_DownloaderDialog
|
||||
|
||||
from .Config import _port, _proxy
|
||||
|
||||
CODPROV = ''
|
||||
CODMUNI = ''
|
||||
|
||||
|
||||
class Spanish_Inspire_Catastral_Downloader:
|
||||
"""QGIS Plugin Implementation."""
|
||||
|
||||
def __init__(self, iface):
|
||||
"""Constructor.
|
||||
|
||||
:param iface: An interface instance that will be passed to this class
|
||||
which provides the hook by which you can manipulate the QGIS
|
||||
application at run time.
|
||||
:type iface: QgsInterface
|
||||
"""
|
||||
# Save reference to the QGIS interface
|
||||
self.iface = iface
|
||||
self.msgBar = iface.messageBar()
|
||||
self.data_dir = ''
|
||||
|
||||
# initialize plugin directory
|
||||
self.plugin_dir = os.path.dirname(__file__)
|
||||
# initialize locale
|
||||
locale = QSettings().value('locale/userLocale')[0:2]
|
||||
locale_path = os.path.join(
|
||||
self.plugin_dir,
|
||||
'i18n',
|
||||
'Spanish_Inspire_Catastral_Downloader_{}.qm'.format(locale))
|
||||
|
||||
if os.path.exists(locale_path):
|
||||
self.translator = QTranslator()
|
||||
self.translator.load(locale_path)
|
||||
|
||||
if qVersion() > '4.3.3':
|
||||
QCoreApplication.installTranslator(self.translator)
|
||||
|
||||
# Declare instance attributes
|
||||
self.actions = []
|
||||
self.menu = self.tr(u'&Spanish Inspire Catastral Downloader')
|
||||
self.toolbar = self.iface.addToolBar(u'Spanish_Inspire_Catastral_Downloader')
|
||||
self.toolbar.setObjectName(u'Spanish_Inspire_Catastral_Downloader')
|
||||
|
||||
socket.setdefaulttimeout(5)
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def tr(self, message):
|
||||
"""Get the translation for a string using Qt translation API.
|
||||
|
||||
We implement this ourselves since we do not inherit QObject.
|
||||
|
||||
:param message: String for translation.
|
||||
:type message: str, QString
|
||||
|
||||
:returns: Translated version of message.
|
||||
:rtype: QString
|
||||
"""
|
||||
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
|
||||
return QCoreApplication.translate('Spanish_Inspire_Catastral_Downloader', message)
|
||||
|
||||
def add_action(
|
||||
self,
|
||||
icon_path,
|
||||
text,
|
||||
callback,
|
||||
enabled_flag=True,
|
||||
add_to_menu=True,
|
||||
add_to_toolbar=True,
|
||||
status_tip=None,
|
||||
whats_this=None,
|
||||
parent=None):
|
||||
"""Add a toolbar icon to the toolbar.
|
||||
|
||||
:param icon_path: Path to the icon for this action. Can be a resource
|
||||
path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
|
||||
:type icon_path: str
|
||||
|
||||
:param text: Text that should be shown in menu items for this action.
|
||||
:type text: str
|
||||
|
||||
:param callback: Function to be called when the action is triggered.
|
||||
:type callback: function
|
||||
|
||||
:param enabled_flag: A flag indicating if the action should be enabled
|
||||
by default. Defaults to True.
|
||||
:type enabled_flag: bool
|
||||
|
||||
:param add_to_menu: Flag indicating whether the action should also
|
||||
be added to the menu. Defaults to True.
|
||||
:type add_to_menu: bool
|
||||
|
||||
:param add_to_toolbar: Flag indicating whether the action should also
|
||||
be added to the toolbar. Defaults to True.
|
||||
:type add_to_toolbar: bool
|
||||
|
||||
:param status_tip: Optional text to show in a popup when mouse pointer
|
||||
hovers over the action.
|
||||
:type status_tip: str
|
||||
|
||||
:param parent: Parent widget for the new action. Defaults None.
|
||||
:type parent: QWidget
|
||||
|
||||
:param whats_this: Optional text to show in the status bar when the
|
||||
mouse pointer hovers over the action.
|
||||
|
||||
:returns: The action that was created. Note that the action is also
|
||||
added to self.actions list.
|
||||
:rtype: QAction
|
||||
"""
|
||||
|
||||
# Create the dialog (after translation) and keep reference
|
||||
self.dlg = Spanish_Inspire_Catastral_DownloaderDialog()
|
||||
# self.dlg.setWindowFlags(Qt.WindowSystemMenuHint | Qt.WindowTitleHint)
|
||||
|
||||
icon = QIcon(icon_path)
|
||||
action = QAction(icon, text, parent)
|
||||
action.triggered.connect(callback)
|
||||
action.setEnabled(enabled_flag)
|
||||
|
||||
if status_tip is not None:
|
||||
action.setStatusTip(status_tip)
|
||||
|
||||
if whats_this is not None:
|
||||
action.setWhatsThis(whats_this)
|
||||
|
||||
if add_to_toolbar:
|
||||
self.toolbar.addAction(action)
|
||||
|
||||
if add_to_menu:
|
||||
self.iface.addPluginToMenu(
|
||||
self.menu,
|
||||
action)
|
||||
|
||||
self.actions.append(action)
|
||||
|
||||
return action
|
||||
|
||||
def initGui(self):
|
||||
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
|
||||
|
||||
icon_path = ':/plugins/Spanish_Inspire_Catastral_Downloader/icon.png'
|
||||
self.add_action(
|
||||
icon_path,
|
||||
text=self.tr(u'&Spanish Inspire Catastral Downloader'),
|
||||
callback=self.run,
|
||||
parent=self.iface.mainWindow())
|
||||
|
||||
self.dlg.pushButton_select_path.clicked.connect(self.select_output_folder)
|
||||
self.dlg.pushButton_run.clicked.connect(self.download)
|
||||
self.dlg.pushButton_add_layers.clicked.connect(self.add_layers)
|
||||
self.dlg.comboBox_province.currentTextChanged.connect(self.on_combobox_changed)
|
||||
self.dlg.comboBox_province.currentTextChanged.connect(self.on_combobox_changed)
|
||||
|
||||
def unload(self):
|
||||
"""Removes the plugin menu item and icon from QGIS GUI."""
|
||||
for action in self.actions:
|
||||
self.iface.removePluginMenu(
|
||||
self.tr(u'&Spanish Inspire Catastral Downloader'),
|
||||
action)
|
||||
self.iface.removeToolBarIcon(action)
|
||||
# remove the toolbar
|
||||
del self.toolbar
|
||||
|
||||
def select_output_folder(self) -> None:
|
||||
"""Select output folder"""
|
||||
|
||||
self.dlg.lineEdit_path.clear()
|
||||
folder = QFileDialog.getExistingDirectory(self.dlg, "Select folder")
|
||||
self.dlg.lineEdit_path.setText(folder)
|
||||
|
||||
def check_form(self, option: int) -> None:
|
||||
"""Message for fields without information"""
|
||||
|
||||
messages = {
|
||||
1: self.tr('You must complete the data of the province and municipality and indicate the download route.'),
|
||||
2: self.tr('You must select at least one cadastral entity to download.')
|
||||
}
|
||||
|
||||
QgsMessageLog.logMessage(messages[option], 'SICD',
|
||||
level=Qgis.Warning)
|
||||
|
||||
self.msgBar.pushMessage(messages[option], level=Qgis.Warning, duration=3)
|
||||
|
||||
# Progress Download
|
||||
def reporthook(self, blocknum, blocksize, totalsize):
|
||||
readsofar = blocknum * blocksize
|
||||
if totalsize > 0:
|
||||
percent = readsofar * 1e2 / totalsize
|
||||
self.dlg.progressBar.setValue(int(percent))
|
||||
|
||||
# Set Proxy
|
||||
def set_proxy(self):
|
||||
proxy_handler = request.ProxyHandler({
|
||||
'http': '%s:%s' % (_proxy, _port),
|
||||
'https': '%s:%s' % (_proxy, _port)
|
||||
})
|
||||
opener = request.build_opener(proxy_handler)
|
||||
request.install_opener(opener)
|
||||
return
|
||||
|
||||
def unset_proxy(self):
|
||||
""" Unset Proxy """
|
||||
|
||||
proxy_handler = request.ProxyHandler({})
|
||||
opener = request.build_opener(proxy_handler)
|
||||
request.install_opener(opener)
|
||||
return
|
||||
|
||||
def encode_url(self, url):
|
||||
""" Encode URL Download """
|
||||
|
||||
url = parse.urlsplit(url)
|
||||
url = list(url)
|
||||
url[2] = parse.quote(url[2])
|
||||
encoded_link = parse.urlunsplit(url)
|
||||
return encoded_link
|
||||
|
||||
def formatFolderName(self, foldername) -> str:
|
||||
""" """
|
||||
foldernameformat = foldername.replace(' ', "_")
|
||||
return foldernameformat
|
||||
|
||||
def gml2geojson(self, input, output):
|
||||
""" Convert a GML to a GeoJSON file """
|
||||
|
||||
try:
|
||||
connect_command = """ogr2ogr -f GeoJSON {} {} -a_srs EPSG:25830""".format(output, input)
|
||||
print("\n Executing: ", connect_command)
|
||||
process = subprocess.Popen(connect_command, shell=True)
|
||||
process.communicate()
|
||||
process.wait()
|
||||
QgsMessageLog.logMessage(f'09.1 Función gml2geojson()', 'SICD', level=Qgis.Info)
|
||||
QgsMessageLog.logMessage(f'09.2 Input {input}', 'SICD', level=Qgis.Info)
|
||||
QgsMessageLog.logMessage(f'09.3 GML {input} converted to {output}', 'SICD', level=Qgis.Info)
|
||||
|
||||
except Exception as err:
|
||||
msg = self.tr("Error converting files to GML")
|
||||
QgsMessageLog.logMessage(f'{msg}', 'SICD', level=Qgis.Warning)
|
||||
self.msgBar.pushMessage(f'{msg}', level=Qgis.Warning, duration=3)
|
||||
raise
|
||||
return
|
||||
|
||||
def search_url(self, inecode_catastro, tipo, codtipo, wd):
|
||||
|
||||
inecode_catastro = inecode_catastro.split(' - ')[0]
|
||||
CODPROV = inecode_catastro[0:2]
|
||||
|
||||
ATOM = f'https://www.catastro.minhap.es/INSPIRE/{tipo}/{CODPROV}/ES.SDGC.{codtipo}.atom_{CODPROV}.xml?tipo={tipo}&wd={wd}'
|
||||
|
||||
req = QtNetwork.QNetworkRequest(QUrl(ATOM))
|
||||
self.manager_ATOM.get(req)
|
||||
|
||||
def generate_download_url(self, reply):
|
||||
|
||||
QgsMessageLog.logMessage(f'06.1 Genera url de descarga generate_download_url()', 'SICD', level=Qgis.Info)
|
||||
|
||||
inecode_catastro = self.dlg.comboBox_municipality.currentText().split(' - ')[0]
|
||||
|
||||
er = reply.error()
|
||||
|
||||
if er == QtNetwork.QNetworkReply.NetworkError.NoError:
|
||||
bytes_string = reply.readAll()
|
||||
response = str(bytes_string, 'iso-8859-1')
|
||||
root = ET.fromstring(response)
|
||||
for entry in root.findall('{http://www.w3.org/2005/Atom}entry'):
|
||||
try:
|
||||
url_cadastre = entry.find('{http://www.w3.org/2005/Atom}id').text
|
||||
QgsMessageLog.logMessage(f'06.2 {url_cadastre}', 'SICD', level=Qgis.Info)
|
||||
except:
|
||||
msg = self.tr("The data set was not found.")
|
||||
self.msgBar.pushMessage(msg, level=Qgis.Info, duration=3)
|
||||
|
||||
if url_cadastre is not None and url_cadastre.endswith('{}.zip'.format(inecode_catastro)):
|
||||
params = parse.parse_qs(parse.urlparse(reply.request().url().toString()).query)
|
||||
tipo = params['tipo'][0]
|
||||
wd = params['wd'][0]
|
||||
self.create_download_file(inecode_catastro, tipo, url_cadastre, wd)
|
||||
break
|
||||
|
||||
def create_download_file(self, inecode_catastro, tipo, url, wd):
|
||||
|
||||
QgsMessageLog.logMessage(f'07.1 Función create_download_file', 'SICD', level=Qgis.Info)
|
||||
QgsMessageLog.logMessage(
|
||||
f'07.2 Parámetros inecode_catastro {inecode_catastro}, tipo {tipo}, url {url}, wd {wd})',
|
||||
'SICD', level=Qgis.Info)
|
||||
|
||||
self.data_dir = os.path.normpath(os.path.join(wd, inecode_catastro))
|
||||
|
||||
QgsMessageLog.logMessage(f'07.1 {self.data_dir}', 'SICD', level=Qgis.Info)
|
||||
try:
|
||||
os.makedirs(self.data_dir)
|
||||
QgsMessageLog.logMessage(
|
||||
f'07.3 Creada carpeta en {self.data_dir})', 'SICD', level=Qgis.Success)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
zip_file = os.path.join(self.data_dir, "{}_{}.zip".format(inecode_catastro, tipo)) # poner fecha
|
||||
|
||||
if not os.path.exists(zip_file):
|
||||
e_url = self.encode_url(url)
|
||||
try:
|
||||
request.urlretrieve(e_url, zip_file, self.reporthook)
|
||||
|
||||
QgsMessageLog.logMessage(f"7.4 Ficheros descargados correctamente en {self.data_dir}", 'SICD',
|
||||
level=Qgis.Success)
|
||||
txt = self.tr('Files downloaded correctly in')
|
||||
msg = f'😎 {txt} <a href="file:///{self.data_dir}">{self.data_dir}</a>'
|
||||
self.msgBar.pushMessage(msg, level=Qgis.Success, duration=5)
|
||||
self.unzip_files(self.data_dir)
|
||||
|
||||
except:
|
||||
shutil.rmtree(self.data_dir)
|
||||
raise
|
||||
else:
|
||||
QApplication.restoreOverrideCursor()
|
||||
txt1 = self.tr('The data set already exists in the folder')
|
||||
txt2 = self.tr('You must delete them first if you want to download them to the same location')
|
||||
msg = f'{txt1} <a href="file:///{self.data_dir}">{self.data_dir}</a>. {txt2} '
|
||||
|
||||
QgsMessageLog.logMessage(msg, 'SICD', level=Qgis.Critical)
|
||||
|
||||
self.msgBar.pushMessage(msg, level=Qgis.Critical)
|
||||
pass
|
||||
|
||||
def unzip_files(self, wd):
|
||||
|
||||
try:
|
||||
if os.path.isdir(wd):
|
||||
for zipfilecatastro in os.listdir(wd):
|
||||
if zipfilecatastro.endswith('.zip'):
|
||||
with zipfile.ZipFile(os.path.join(wd, zipfilecatastro), "r") as z:
|
||||
z.extractall(wd)
|
||||
QgsMessageLog.logMessage(f'08.1 Zip descomprimidos', 'SICD', level=Qgis.Info)
|
||||
|
||||
self.dlg.pushButton_add_layers.setEnabled(1)
|
||||
else:
|
||||
msg = self.tr("Select at least one data set to download")
|
||||
self.msgBar.pushMessage(msg, level=Qgis.Critical)
|
||||
return
|
||||
except:
|
||||
|
||||
self.msgBar.pushMessage(self.tr("An error occurred while decompressing the file."), level=Qgis.Warning, duration=3)
|
||||
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
self.dlg.progressBar.setValue(100) # No llega al 100% aunque lo descargue,es random
|
||||
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def add_layers(self):
|
||||
|
||||
inecode_catastro = self.dlg.comboBox_municipality.currentText().split(' - ')
|
||||
zippath = self.dlg.lineEdit_path.text()
|
||||
wd = os.path.join(zippath, inecode_catastro[0])
|
||||
|
||||
group_name = self.dlg.comboBox_municipality.currentText()
|
||||
project = QgsProject.instance()
|
||||
tree_root = project.layerTreeRoot()
|
||||
layers_group = tree_root.addGroup(group_name)
|
||||
|
||||
for gmlfile in os.listdir(wd):
|
||||
if gmlfile.endswith('.gml'):
|
||||
layer_path = os.path.join(wd, gmlfile)
|
||||
file_name = os.path.splitext(gmlfile)[0]
|
||||
QgsMessageLog.logMessage(layer_path, 'SICD', level=Qgis.Info)
|
||||
gml_layer = QgsVectorLayer(layer_path, file_name, "ogr")
|
||||
project.addMapLayer(gml_layer, False)
|
||||
layers_group.addLayer(gml_layer)
|
||||
|
||||
QgsMessageLog.logMessage("10. Capas cargadas", 'SICD', level=Qgis.Info)
|
||||
|
||||
def run(self):
|
||||
"""Run method that performs all the real work"""
|
||||
|
||||
self.dlg.lineEdit_path.clear()
|
||||
self.dlg.comboBox_province.clear()
|
||||
self.dlg.comboBox_municipality.clear()
|
||||
|
||||
self.obtener_provincias()
|
||||
|
||||
self.dlg.checkBox_parcels.setChecked(0)
|
||||
self.dlg.checkBox_buildings.setChecked(0)
|
||||
self.dlg.checkBox_addresses.setChecked(0)
|
||||
|
||||
# self.dlg.checkBox_load_layers.setChecked(0)
|
||||
|
||||
self.dlg.pushButton_add_layers.setEnabled(0)
|
||||
|
||||
# show the dialog
|
||||
self.dlg.progressBar.setValue(0)
|
||||
self.dlg.setWindowIcon(QIcon(':/plugins/Spanish_Inspire_Catastral_Downloader/icon.png'));
|
||||
self.dlg.show()
|
||||
|
||||
# Run the dialog event loop
|
||||
result = self.dlg.exec_()
|
||||
|
||||
# See if OK was pressed
|
||||
if result: pass
|
||||
|
||||
def on_combobox_changed(self):
|
||||
self.dlg.lineEdit_path.clear()
|
||||
self.dlg.checkBox_parcels.setChecked(0)
|
||||
self.dlg.checkBox_buildings.setChecked(0)
|
||||
self.dlg.checkBox_addresses.setChecked(0)
|
||||
self.dlg.pushButton_add_layers.setEnabled(0)
|
||||
|
||||
def obtener_provincias(self):
|
||||
|
||||
QgsMessageLog.logMessage("01.1 Obtenindo provincias (obtener_provincias)", 'SICD', level=Qgis.Info)
|
||||
|
||||
self.manager_provincias = QtNetwork.QNetworkAccessManager()
|
||||
self.manager_provincias.finished.connect(self.rellenar_provincias)
|
||||
|
||||
url = 'http://ovc.catastro.meh.es/OVCServWeb/OVCWcfCallejero/COVCCallejero.svc/json/ObtenerProvincias'
|
||||
|
||||
QgsMessageLog.logMessage(f'01.2 URL JSON Provincias de Catastro {url}', 'SICD', level=Qgis.Info)
|
||||
|
||||
req = QtNetwork.QNetworkRequest(QUrl(url))
|
||||
self.manager_provincias.get(req)
|
||||
|
||||
def rellenar_provincias(self, reply):
|
||||
|
||||
QgsMessageLog.logMessage("02. Rellenando provincias (rellenar_provincias)", 'SICD', level=Qgis.Info)
|
||||
er = reply.error()
|
||||
if er == QtNetwork.QNetworkReply.NetworkError.NoError:
|
||||
bytes_string = reply.readAll()
|
||||
response = str(bytes_string, 'utf-8')
|
||||
response_json = json.loads(response)
|
||||
provincias = response_json['consulta_provincieroResult']['provinciero']['prov']
|
||||
|
||||
list_provincias = [self.tr('Select a province...')]
|
||||
|
||||
for provincia in provincias:
|
||||
list_provincias.append('{} - {}'.format(provincia['cpine'], provincia['np']))
|
||||
|
||||
self.dlg.comboBox_province.addItems(list_provincias)
|
||||
self.dlg.comboBox_province.currentIndexChanged.connect(self.obtener_municipos)
|
||||
|
||||
def obtener_municipos(self):
|
||||
|
||||
try:
|
||||
self.manager_municipios = QtNetwork.QNetworkAccessManager()
|
||||
self.manager_municipios.finished.connect(self.rellenar_municipios)
|
||||
provincia_cod = self.dlg.comboBox_province.currentText()
|
||||
msg = f'03.1 Obteniendo municipios (obtener_municipios) de la provincia {provincia_cod}'
|
||||
QgsMessageLog.logMessage(msg, 'SICD', level=Qgis.Info)
|
||||
provincia = provincia_cod.split(' - ')[0]
|
||||
|
||||
url = 'http://ovc.catastro.meh.es/OVCServWeb/OVCWcfCallejero/COVCCallejeroCodigos.svc/json/ObtenerMunicipiosCodigos?CodigoProvincia=' + str(
|
||||
provincia)
|
||||
|
||||
QgsMessageLog.logMessage(f'03.2 URL JSON Municipios de Catastro de la {provincia_cod}: {url}', 'SICD',
|
||||
level=Qgis.Info)
|
||||
|
||||
req = QtNetwork.QNetworkRequest(QUrl(url))
|
||||
self.manager_municipios.get(req)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def rellenar_municipios(self, reply):
|
||||
|
||||
er = reply.error()
|
||||
if er == QtNetwork.QNetworkReply.NetworkError.NoError:
|
||||
|
||||
bytes_string = reply.readAll()
|
||||
response = str(bytes_string, 'utf-8')
|
||||
response_json = json.loads(response)
|
||||
list_municipios = []
|
||||
|
||||
try:
|
||||
municipios = response_json['consulta_municipieroResult']['municipiero']['muni']
|
||||
QgsMessageLog.logMessage("04. Rellenando municipios (rellenar_municipios)", 'SICD', level=Qgis.Info)
|
||||
for municipio in municipios:
|
||||
codigo_provincia = str(municipio['locat']['cd']).zfill(2)
|
||||
codigo_municipio = str(municipio['locat']['cmc']).zfill(3)
|
||||
codigo = codigo_provincia + codigo_municipio
|
||||
list_municipios.append(codigo + ' - ' + municipio['nm'])
|
||||
except:
|
||||
pass
|
||||
|
||||
self.dlg.comboBox_municipality.clear()
|
||||
self.dlg.comboBox_municipality.addItems(list_municipios)
|
||||
|
||||
def download(self):
|
||||
"""Download data funtion"""
|
||||
|
||||
if self.dlg.comboBox_municipality.currentText() == '' or self.dlg.lineEdit_path.text() == '':
|
||||
self.check_form(1)
|
||||
elif not (
|
||||
self.dlg.checkBox_parcels.isChecked() or self.dlg.checkBox_buildings.isChecked() or self.dlg.checkBox_addresses.isChecked()):
|
||||
self.check_form(2)
|
||||
|
||||
else:
|
||||
QgsMessageLog.logMessage("05 Inicio de descarga", 'SICD', level=Qgis.Info)
|
||||
try:
|
||||
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
inecode_catastro = self.dlg.comboBox_municipality.currentText()
|
||||
|
||||
zippath = self.dlg.lineEdit_path.text()
|
||||
# wd = os.path.join(zippath , inecode_catastro.replace(' ', "_"))
|
||||
# wd = os.path.join(zippath, CODMUNI)
|
||||
|
||||
QgsMessageLog.logMessage(f'05.1 Genera variables zippath {zippath}', 'SICD', level=Qgis.Info)
|
||||
|
||||
proxy_support = request.ProxyHandler({})
|
||||
opener = request.build_opener(proxy_support)
|
||||
request.install_opener(opener)
|
||||
|
||||
# Estabelcemos un proxy si lo ha definido el usuario
|
||||
try:
|
||||
if (_proxy is not None and _proxy != "") and (_port is not None and _port != ""):
|
||||
self.set_proxy()
|
||||
else:
|
||||
self.unset_proxy()
|
||||
except Exception as e:
|
||||
QApplication.restoreOverrideCursor()
|
||||
txt = self.tr('Error setting proxy')
|
||||
msg = f"{txt} : {str(e)}"
|
||||
self.msgBar.pushMessage(msg, level=Qgis.Warning, duration=3)
|
||||
raise
|
||||
|
||||
self.manager_ATOM = QtNetwork.QNetworkAccessManager()
|
||||
|
||||
self.manager_ATOM.finished.connect(self.generate_download_url)
|
||||
|
||||
if self.dlg.checkBox_parcels.isChecked():
|
||||
self.search_url(inecode_catastro, 'CadastralParcels', 'CP', zippath)
|
||||
|
||||
if self.dlg.checkBox_buildings.isChecked():
|
||||
self.search_url(inecode_catastro, 'Buildings', 'BU', zippath)
|
||||
|
||||
if self.dlg.checkBox_addresses.isChecked():
|
||||
self.search_url(inecode_catastro, 'Addresses', 'AD', zippath)
|
||||
|
||||
except Exception as e:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self.dlg.pushButton_add_layers.setEnabled(0)
|
||||
self.msgBar.pushMessage(self.tr("Failed!") + str(e), level=Qgis.Warning, duration=3)
|
||||
138
lib/catastro/Spanish_Inspire_Catastral_Downloader.ui
Normal file
138
lib/catastro/Spanish_Inspire_Catastral_Downloader.ui
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>formGate</class>
|
||||
<widget class="QWidget" name="formGate">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>382</width>
|
||||
<height>160</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Puerta:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Provincia</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Municipio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QComboBox" name="comboBox_province"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBox_municipality"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Carpeta de descarga</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="lineEdit_path"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_3" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_parcels">
|
||||
<property name="text">
|
||||
<string>Parcelas</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_buildings">
|
||||
<property name="text">
|
||||
<string>Edificios</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_addresses">
|
||||
<property name="text">
|
||||
<string>Direcciones</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
92
lib/convert.py
Normal file
92
lib/convert.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Copyright Jonah Lefkoff
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
import os
|
||||
import argparse
|
||||
import zipfile
|
||||
from geopy.distance import distance
|
||||
import json
|
||||
|
||||
|
||||
# arg parser
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Converts KMZ radar video maps to GeoJSON.')
|
||||
parser.add_argument('kmz_path', metavar='kmz_path', type=str,
|
||||
help='The path to a folder of KMZ files to be converted.')
|
||||
parser.add_argument('json_out', metavar='json_out', type=str,
|
||||
help='The path to output the GeoJSON files.')
|
||||
parser.add_argument('-r', '--radius',
|
||||
help='The maximum radius of the map in nautical miles. Defaults to 100.', type=int, required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
# takes a file path to a KMZ file and converts it to a kml to pass to the kml_to_geojson function
|
||||
def kmz_to_kml(fname):
|
||||
zf = zipfile.ZipFile(fname,'r')
|
||||
for fn in zf.namelist():
|
||||
if fn.endswith('.kml'):
|
||||
content = zf.read(fn)
|
||||
if args.radius:
|
||||
parse_kml_to_geojson(content, fname, args.radius)
|
||||
else:
|
||||
parse_kml_to_geojson(content, fname)
|
||||
else:
|
||||
print("no kml file")
|
||||
|
||||
# takes a kml file and converts it to a GeoJSON file and writes it to the json_out path
|
||||
|
||||
def parse_kml_to_geojson(kml, fname, max_radius=100):
|
||||
root = ET.fromstring(kml)
|
||||
|
||||
features = []
|
||||
|
||||
look_at_coordinates = (
|
||||
float(root.find('.//{http://earth.google.com/kml/2.0}LookAt/{http://earth.google.com/kml/2.0}longitude').text),
|
||||
float(root.find('.//{http://earth.google.com/kml/2.0}LookAt/{http://earth.google.com/kml/2.0}latitude').text)
|
||||
)
|
||||
|
||||
for line_string in root.findall('.//{http://earth.google.com/kml/2.0}LineString'):
|
||||
coordinates = line_string.find('{http://earth.google.com/kml/2.0}coordinates').text.strip().split()
|
||||
|
||||
coordinates_list = []
|
||||
for coord in coordinates:
|
||||
lon, lat, _ = coord.split(',')
|
||||
coordinates_list.append([float(lon), float(lat)])
|
||||
|
||||
# Calculate distance between each point in LineString and LookAt coordinates, needs to be lat long not long lat
|
||||
distances = []
|
||||
for coord in coordinates_list:
|
||||
distances.append(distance([coord[1],coord[0]], [look_at_coordinates[1],look_at_coordinates[0]]).nautical)
|
||||
|
||||
# Filter LineStrings based on the maximum radius
|
||||
if max(distances) <= max_radius:
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"color": "#ffffff",
|
||||
"style": "solid",
|
||||
"thickness": "1",
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": coordinates_list
|
||||
}
|
||||
}
|
||||
features.append(feature)
|
||||
|
||||
geojson_data = {
|
||||
"type": "FeatureCollection",
|
||||
"features": features
|
||||
}
|
||||
|
||||
# get the map name from the file name
|
||||
map_name = re.search(r'([a-zA-Z0-9]+)\.kmz', fname).group(1)
|
||||
# create the output file named after the input file
|
||||
with open(f"{args.json_out}/{map_name}.geojson", "w+") as output_file:
|
||||
# write to the file
|
||||
json.dump(geojson_data, output_file, indent=2)
|
||||
|
||||
|
||||
# loop through each file in the kmz_path and run the kmz_to_kml function on it
|
||||
for fname in os.listdir(args.kmz_path):
|
||||
kmz_to_kml(f"{args.kmz_path}/{fname}")
|
||||
689
lib/kml2geojson.py
Normal file
689
lib/kml2geojson.py
Normal file
@@ -0,0 +1,689 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path, PurePath
|
||||
import json
|
||||
from lxml import html
|
||||
|
||||
# from kml2geojson import build_layers, build_feature_collection, disambiguate, to_filename, STYLE_TYPES
|
||||
from zipfile import ZipFile
|
||||
|
||||
import xml.dom.minidom as md
|
||||
import xml.dom.minicompat as mc
|
||||
import re
|
||||
import pathlib as pl
|
||||
from typing import Optional, TextIO, BinaryIO
|
||||
|
||||
#: Atomic KML geometry types supported.
|
||||
#: MultiGeometry is handled separately.
|
||||
GEOTYPES = [
|
||||
"Polygon",
|
||||
"LineString",
|
||||
"Point",
|
||||
"Track",
|
||||
"gx:Track",
|
||||
]
|
||||
|
||||
#: Supported style types
|
||||
STYLE_TYPES = [
|
||||
"svg",
|
||||
"leaflet",
|
||||
]
|
||||
|
||||
SPACE = re.compile(r"\s+")
|
||||
|
||||
|
||||
def get(node: md.Document, name: str) -> mc.NodeList:
|
||||
"""
|
||||
Given a KML Document Object Model (DOM) node, return a list of its sub-nodes that have the given tag name.
|
||||
"""
|
||||
return node.getElementsByTagName(name)
|
||||
|
||||
|
||||
def get1(node: md.Document, name: str) -> md.Element | None:
|
||||
"""
|
||||
Return the first element of ``get(node, name)``, if it exists.
|
||||
Otherwise return ``None``.
|
||||
"""
|
||||
s = get(node, name)
|
||||
if s:
|
||||
return s[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def attr(node: md.Document, name: str) -> str:
|
||||
"""
|
||||
Return as a string the value of the given DOM node's attribute named by ``name``, if it exists.
|
||||
Otherwise, return an empty string.
|
||||
"""
|
||||
return node.getAttribute(name)
|
||||
|
||||
|
||||
def val(node: md.Document) -> str:
|
||||
"""
|
||||
Normalize the given DOM node and return the value of its first child (the string content of the node) stripped of leading and trailing whitespace.
|
||||
"""
|
||||
try:
|
||||
node.normalize()
|
||||
return node.firstChild.wholeText.strip() # Handles CDATASection too
|
||||
except AttributeError:
|
||||
return ""
|
||||
|
||||
|
||||
def valf(node: md.Document) -> float:
|
||||
"""
|
||||
Cast ``val(node)`` as a float.
|
||||
Return ``None`` if that does not work.
|
||||
"""
|
||||
try:
|
||||
return float(val(node))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def numarray(a: list) -> list[float]:
|
||||
"""
|
||||
Cast the given list into a list of floats.
|
||||
"""
|
||||
return [float(aa) for aa in a]
|
||||
|
||||
|
||||
def coords1(s: str) -> list[float]:
|
||||
"""
|
||||
Convert the given KML string containing one coordinate tuple into a list of floats.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> coords1(' -112.2,36.0,2357 ')
|
||||
[-112.2, 36.0, 2357.0]
|
||||
|
||||
"""
|
||||
return numarray(re.sub(SPACE, "", s).split(","))
|
||||
|
||||
|
||||
def coords(s: str) -> list[list[float]]:
|
||||
"""
|
||||
Convert the given KML string containing multiple coordinate tuples into a list of lists of floats.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> coords('''
|
||||
... -112.0,36.1,0
|
||||
... -113.0,36.0,0
|
||||
... ''')
|
||||
[[-112.0, 36.1, 0.0], [-113.0, 36.0, 0.0]]
|
||||
|
||||
"""
|
||||
s = s.split() # sub(TRIM_SPACE, '', v).split()
|
||||
return [coords1(ss) for ss in s]
|
||||
|
||||
|
||||
def gx_coords1(s: str) -> list[float]:
|
||||
"""
|
||||
Convert the given KML string containing one gx coordinate tuple into a list of floats.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> gx_coords1('-113.0 36.0 0')
|
||||
[-113.0, 36.0, 0.0]
|
||||
|
||||
"""
|
||||
return numarray(s.split(" "))
|
||||
|
||||
|
||||
def gx_coords(node: md.Document) -> dict:
|
||||
"""
|
||||
Given a KML DOM node, grab its <gx:coord> and <gx:timestamp><when>subnodes, and convert them into a dictionary with the keys and values
|
||||
|
||||
- ``'coordinates'``: list of lists of float coordinates
|
||||
- ``'times'``: list of timestamps corresponding to the coordinates
|
||||
|
||||
"""
|
||||
els = get(node, "gx:coord")
|
||||
coordinates = []
|
||||
times = []
|
||||
coordinates = [gx_coords1(val(el)) for el in els]
|
||||
time_els = get(node, "when")
|
||||
times = [val(t) for t in time_els]
|
||||
return {
|
||||
"coordinates": coordinates,
|
||||
"times": times,
|
||||
}
|
||||
|
||||
|
||||
def disambiguate(names: list[str], mark: str = "1") -> list[str]:
|
||||
"""
|
||||
Given a list of strings ``names``, return a new list of names where repeated names have been disambiguated by repeatedly appending the given mark.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> disambiguate(['sing', 'song', 'sing', 'sing'])
|
||||
['sing', 'song', 'sing1', 'sing11']
|
||||
|
||||
"""
|
||||
names_seen = set()
|
||||
new_names = []
|
||||
for name in names:
|
||||
new_name = name
|
||||
while new_name in names_seen:
|
||||
new_name += mark
|
||||
new_names.append(new_name)
|
||||
names_seen.add(new_name)
|
||||
|
||||
return new_names
|
||||
|
||||
|
||||
def to_filename(s: str) -> str:
|
||||
"""
|
||||
Based on `django/utils/text.py <https://github.com/django/django/blob/master/django/utils/text.py>`_.
|
||||
Return the given string converted to a string that can be used for a clean filename.
|
||||
Specifically, leading and trailing spaces are removed; other spaces are converted to underscores, and anything that is not a unicode alphanumeric, dash, underscore, or dot, is removed.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> to_filename("% A dbla'{-+)(ç? ")
|
||||
'A_dsbla-ç'
|
||||
|
||||
"""
|
||||
s = re.sub(r"(?u)[^-\w. ]", "", s)
|
||||
s = s.strip().replace(" ", "_")
|
||||
return s
|
||||
|
||||
|
||||
# ---------------
|
||||
# Main functions
|
||||
# ---------------
|
||||
def build_rgb_and_opacity(s: str) -> tuple:
|
||||
"""
|
||||
Given a KML color string, return an equivalent RGB hex color string and an opacity float rounded to 2 decimal places.
|
||||
|
||||
EXAMPLE::
|
||||
|
||||
>>> build_rgb_and_opacity('ee001122')
|
||||
('#221100', 0.93)
|
||||
|
||||
"""
|
||||
# Set defaults
|
||||
color = "000000"
|
||||
opacity = 1
|
||||
|
||||
if s.startswith("#"):
|
||||
s = s[1:]
|
||||
if len(s) == 8:
|
||||
color = s[6:8] + s[4:6] + s[2:4]
|
||||
opacity = round(int(s[0:2], 16) / 256, 2)
|
||||
elif len(s) == 6:
|
||||
color = s[4:6] + s[2:4] + s[0:2]
|
||||
elif len(s) == 3:
|
||||
color = s[::-1]
|
||||
|
||||
return "#" + color, opacity
|
||||
|
||||
|
||||
def build_svg_style(node: md.Document) -> dict:
|
||||
"""
|
||||
Given a DOM node, grab its top-level Style nodes, convert every one into a SVG style dictionary, put them in a master dictionary of the form
|
||||
|
||||
#style ID -> SVG style dictionary,
|
||||
|
||||
and return the result.
|
||||
|
||||
The possible keys and values of each SVG style dictionary, the style options, are
|
||||
|
||||
- ``iconUrl``: URL of icon
|
||||
- ``stroke``: stroke color; RGB hex string
|
||||
- ``stroke-opacity``: stroke opacity
|
||||
- ``stroke-width``: stroke width in pixels
|
||||
- ``fill``: fill color; RGB hex string
|
||||
- ``fill-opacity``: fill opacity
|
||||
"""
|
||||
d = {}
|
||||
for item in get(node, "Style"):
|
||||
style_id = "#" + attr(item, "id")
|
||||
# Create style properties
|
||||
props = {}
|
||||
for x in get(item, "PolyStyle"):
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["fill"] = rgb
|
||||
props["fill-opacity"] = opacity
|
||||
# Set default border style
|
||||
props["stroke"] = rgb
|
||||
props["stroke-opacity"] = opacity
|
||||
props["stroke-width"] = 1
|
||||
fill = valf(get1(x, "fill"))
|
||||
if fill == 0:
|
||||
props["fill-opacity"] = fill
|
||||
elif fill == 1 and "fill-opacity" not in props:
|
||||
props["fill-opacity"] = fill
|
||||
outline = valf(get1(x, "outline"))
|
||||
if outline == 0:
|
||||
props["stroke-opacity"] = outline
|
||||
elif outline == 1 and "stroke-opacity" not in props:
|
||||
props["stroke-opacity"] = outline
|
||||
for x in get(item, "LineStyle"):
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["stroke"] = rgb
|
||||
props["stroke-opacity"] = opacity
|
||||
width = valf(get1(x, "width"))
|
||||
if width is not None:
|
||||
props["stroke-width"] = width
|
||||
for x in get(item, "IconStyle"):
|
||||
icon = get1(x, "Icon")
|
||||
if not icon:
|
||||
continue
|
||||
# Clear previous style properties
|
||||
props = {}
|
||||
props["iconUrl"] = val(get1(icon, "href"))
|
||||
|
||||
d[style_id] = props
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def build_leaflet_style(node: md.Document) -> dict:
|
||||
"""
|
||||
Given a DOM node, grab its top-level Style nodes, convert every one into a Leaflet style dictionary, put them in a master dictionary of the form
|
||||
|
||||
#style ID -> Leaflet style dictionary,
|
||||
|
||||
and return the result.
|
||||
|
||||
The the possible keys and values of each Leaflet style dictionary, the style options, are
|
||||
|
||||
- ``iconUrl``: URL of icon
|
||||
- ``color``: stroke color; RGB hex string
|
||||
- ``opacity``: stroke opacity
|
||||
- ``weight``: stroke width in pixels
|
||||
- ``fillColor``: fill color; RGB hex string
|
||||
- ``fillOpacity``: fill opacity
|
||||
"""
|
||||
d = {}
|
||||
for item in get(node, "Style"):
|
||||
style_id = "#" + attr(item, "id")
|
||||
# Create style properties
|
||||
props = {}
|
||||
for x in get(item, "PolyStyle"):
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["fillColor"] = rgb
|
||||
props["fillOpacity"] = opacity
|
||||
# Set default border style
|
||||
props["color"] = rgb
|
||||
props["opacity"] = opacity
|
||||
props["weight"] = 1
|
||||
fill = valf(get1(x, "fill"))
|
||||
if fill == 0:
|
||||
props["fillOpacity"] = fill
|
||||
elif fill == 1 and "fillOpacity" not in props:
|
||||
props["fillOpacity"] = fill
|
||||
outline = valf(get1(x, "outline"))
|
||||
if outline == 0:
|
||||
props["opacity"] = outline
|
||||
elif outline == 1 and "opacity" not in props:
|
||||
props["opacity"] = outline
|
||||
for x in get(item, "LineStyle"):
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["color"] = rgb
|
||||
props["opacity"] = opacity
|
||||
width = valf(get1(x, "width"))
|
||||
if width is not None:
|
||||
props["weight"] = width
|
||||
for x in get(item, "IconStyle"):
|
||||
icon = get1(x, "Icon")
|
||||
if not icon:
|
||||
continue
|
||||
# Clear previous style properties
|
||||
props = {}
|
||||
props["iconUrl"] = val(get1(icon, "href"))
|
||||
|
||||
d[style_id] = props
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def build_geometry(node: md.Document) -> dict:
|
||||
"""
|
||||
Return a (decoded) GeoJSON geometry dictionary corresponding to the given KML node.
|
||||
"""
|
||||
geoms = []
|
||||
times = []
|
||||
if get1(node, "MultiGeometry"):
|
||||
return build_geometry(get1(node, "MultiGeometry"))
|
||||
if get1(node, "MultiTrack"):
|
||||
return build_geometry(get1(node, "MultiTrack"))
|
||||
if get1(node, "gx:MultiTrack"):
|
||||
return build_geometry(get1(node, "gx:MultiTrack"))
|
||||
for geotype in GEOTYPES:
|
||||
geonodes = get(node, geotype)
|
||||
if not geonodes:
|
||||
continue
|
||||
for geonode in geonodes:
|
||||
if geotype == "Point":
|
||||
geoms.append(
|
||||
{
|
||||
"type": "Point",
|
||||
"coordinates": coords1(val(get1(geonode, "coordinates"))),
|
||||
}
|
||||
)
|
||||
elif geotype == "LineString":
|
||||
geoms.append(
|
||||
{
|
||||
"type": "LineString",
|
||||
"coordinates": coords(val(get1(geonode, "coordinates"))),
|
||||
}
|
||||
)
|
||||
elif geotype == "Polygon":
|
||||
rings = get(geonode, "LinearRing")
|
||||
coordinates = [coords(val(get1(ring, "coordinates"))) for ring in rings]
|
||||
geoms.append(
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": coordinates,
|
||||
}
|
||||
)
|
||||
elif geotype in ["Track", "gx:Track"]:
|
||||
track = gx_coords(geonode)
|
||||
geoms.append(
|
||||
{
|
||||
"type": "LineString",
|
||||
"coordinates": track["coordinates"],
|
||||
}
|
||||
)
|
||||
if track["times"]:
|
||||
times.append(track["times"])
|
||||
|
||||
return {"geoms": geoms, "times": times}
|
||||
|
||||
|
||||
def build_feature(node: md.Document) -> dict | None:
|
||||
"""
|
||||
Build and return a (decoded) GeoJSON Feature corresponding to this KML node (typically a KML Placemark).
|
||||
Return ``None`` if no Feature can be built.
|
||||
"""
|
||||
geoms_and_times = build_geometry(node)
|
||||
if not geoms_and_times["geoms"]:
|
||||
return None
|
||||
|
||||
props = {}
|
||||
for x in get(node, "name")[:1]:
|
||||
name = val(x)
|
||||
if name:
|
||||
props["name"] = val(x)
|
||||
for x in get(node, "description")[:1]:
|
||||
desc = val(x)
|
||||
if desc:
|
||||
props["description"] = desc
|
||||
for x in get(node, "styleUrl")[:1]:
|
||||
style_url = val(x)
|
||||
if style_url[0] != "#":
|
||||
style_url = "#" + style_url
|
||||
props["styleUrl"] = style_url
|
||||
for x in get(node, "PolyStyle")[:1]:
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["fill"] = rgb
|
||||
props["fill-opacity"] = opacity
|
||||
# Set default border style
|
||||
props["stroke"] = rgb
|
||||
props["stroke-opacity"] = opacity
|
||||
props["stroke-width"] = 1
|
||||
fill = valf(get1(x, "fill"))
|
||||
if fill == 0:
|
||||
props["fill-opacity"] = fill
|
||||
elif fill == 1 and "fill-opacity" not in props:
|
||||
props["fill-opacity"] = fill
|
||||
outline = valf(get1(x, "outline"))
|
||||
if outline == 0:
|
||||
props["stroke-opacity"] = outline
|
||||
elif outline == 1 and "stroke-opacity" not in props:
|
||||
props["stroke-opacity"] = outline
|
||||
for x in get(node, "LineStyle")[:1]:
|
||||
color = val(get1(x, "color"))
|
||||
if color:
|
||||
rgb, opacity = build_rgb_and_opacity(color)
|
||||
props["stroke"] = rgb
|
||||
props["stroke-opacity"] = opacity
|
||||
width = valf(get1(x, "width"))
|
||||
if width:
|
||||
props["stroke-width"] = width
|
||||
for x in get(node, "ExtendedData")[:1]:
|
||||
datas = get(x, "Data")
|
||||
for data in datas:
|
||||
props[attr(data, "name")] = val(get1(data, "value"))
|
||||
simple_datas = get(x, "SimpleData")
|
||||
for simple_data in simple_datas:
|
||||
props[attr(simple_data, "name")] = val(simple_data)
|
||||
for x in get(node, "TimeSpan")[:1]:
|
||||
begin = val(get1(x, "begin"))
|
||||
end = val(get1(x, "end"))
|
||||
props["timeSpan"] = {"begin": begin, "end": end}
|
||||
if geoms_and_times["times"]:
|
||||
times = geoms_and_times["times"]
|
||||
if len(times) == 1:
|
||||
props["times"] = times[0]
|
||||
else:
|
||||
props["times"] = times
|
||||
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": props,
|
||||
}
|
||||
|
||||
geoms = geoms_and_times["geoms"]
|
||||
if len(geoms) == 1:
|
||||
feature["geometry"] = geoms[0]
|
||||
else:
|
||||
feature["geometry"] = {
|
||||
"type": "GeometryCollection",
|
||||
"geometries": geoms,
|
||||
}
|
||||
|
||||
if attr(node, "id"):
|
||||
feature["id"] = attr(node, "id")
|
||||
|
||||
return feature
|
||||
|
||||
|
||||
def build_feature_collection(node: md.Document, name: Optional[str] = None) -> dict:
|
||||
"""
|
||||
Build and return a (decoded) GeoJSON FeatureCollection corresponding to this KML DOM node (typically a KML Folder).
|
||||
If a name is given, store it in the FeatureCollection's ``'name'`` attribute.
|
||||
"""
|
||||
# Initialize
|
||||
geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [],
|
||||
}
|
||||
|
||||
# Build features
|
||||
for placemark in get(node, "Placemark"):
|
||||
feature = build_feature(placemark)
|
||||
if feature is not None:
|
||||
geojson["features"].append(feature)
|
||||
|
||||
# Give the collection a name if requested
|
||||
if name is not None:
|
||||
geojson["name"] = name
|
||||
|
||||
return geojson
|
||||
|
||||
|
||||
def build_layers(node: md.Document, *, disambiguate_names: bool = True) -> list[dict]:
|
||||
"""
|
||||
Return a list of GeoJSON FeatureCollections, one for each folder in the given KML DOM node that contains geodata.
|
||||
Name each FeatureCollection (via a ``'name'`` attribute) according to its corresponding KML folder name.
|
||||
|
||||
If ``disambiguate_names == True``, then disambiguate repeated layer names via :func:`disambiguate`.
|
||||
|
||||
Warning: this can produce layers with the same geodata in case the KML node has nested folders with geodata.
|
||||
"""
|
||||
layers = []
|
||||
names = []
|
||||
for i, folder in enumerate(get(node, "Folder")):
|
||||
name = val(get1(folder, "name"))
|
||||
geojson = build_feature_collection(folder, name)
|
||||
if geojson["features"]:
|
||||
layers.append(geojson)
|
||||
names.append(name)
|
||||
|
||||
if not layers:
|
||||
# No folders, so use the root node
|
||||
name = val(get1(node, "name"))
|
||||
geojson = build_feature_collection(node, name)
|
||||
if geojson["features"]:
|
||||
layers.append(geojson)
|
||||
names.append(name)
|
||||
|
||||
if disambiguate_names:
|
||||
new_names = disambiguate(names)
|
||||
new_layers = []
|
||||
for i, layer in enumerate(layers):
|
||||
layer["name"] = new_names[i]
|
||||
new_layers.append(layer)
|
||||
layers = new_layers
|
||||
|
||||
return layers
|
||||
|
||||
|
||||
def convert(
|
||||
kml_path_or_buffer: str | pl.Path | TextIO | BinaryIO,
|
||||
feature_collection_name: Optional[str] = None,
|
||||
style_type: Optional[str] = None,
|
||||
*,
|
||||
separate_folders: bool = False,
|
||||
):
|
||||
"""
|
||||
Given a path to a KML file or given a KML file object,
|
||||
convert it to a single GeoJSON FeatureCollection dictionary named
|
||||
``feature_collection_name``.
|
||||
Close the KML file afterwards.
|
||||
|
||||
If ``separate_folders``, then return several FeatureCollections,
|
||||
one for each folder in the KML file that contains geodata or that has a descendant
|
||||
node that contains geodata.
|
||||
Warning: this can produce FeatureCollections with the same geodata in case the KML
|
||||
file has nested folders with geodata.
|
||||
|
||||
If a style type from :const:`STYLE_TYPES` is given, then also create a JSON
|
||||
dictionary that encodes into the style type the style information contained in the
|
||||
KML file.
|
||||
|
||||
Return a tuple (style dict, FeatureCollection 1, ..., FeatureCollection n),
|
||||
where the style dict is present if and only if ``style_type`` is given and
|
||||
where n > 1 if and only if ``separate_folders`` and the KML file contains more than
|
||||
one folder of geodata.
|
||||
"""
|
||||
# Read KML
|
||||
if isinstance(kml_path_or_buffer, (str, pl.Path)):
|
||||
kml_path_or_buffer = pl.Path(kml_path_or_buffer).resolve()
|
||||
with kml_path_or_buffer.open(encoding="utf-8", errors="ignore") as src:
|
||||
kml_str = src.read()
|
||||
else:
|
||||
kml_str = kml_path_or_buffer.read()
|
||||
kml_path_or_buffer.close()
|
||||
|
||||
# Parse KML
|
||||
root = md.parseString(kml_str)
|
||||
|
||||
# Build GeoJSON layers
|
||||
if separate_folders:
|
||||
result = build_layers(root)
|
||||
else:
|
||||
result = [build_feature_collection(root, name=feature_collection_name)]
|
||||
|
||||
if style_type is not None:
|
||||
# Build style dictionary
|
||||
if style_type not in STYLE_TYPES:
|
||||
raise ValueError(f"style type must be one of {STYLE_TYPES}")
|
||||
else:
|
||||
builder_name = f"build_{style_type}_style"
|
||||
style_dict = globals()[builder_name](root)
|
||||
result = style_dict, *result
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def kmz_convert(kmz_path, output_dir, separate_folders=False,
|
||||
style_type=None, style_filename='style.json'):
|
||||
"""
|
||||
Given a path to a KML file, convert it to one or several GeoJSON FeatureCollection files and save the result(s) to the given output directory.
|
||||
If not ``separate_folders`` (the default), then create one GeoJSON file.
|
||||
Otherwise, create several GeoJSON files, one for each folder in the KML file that contains geodata or that has a descendant node that contains geodata.
|
||||
Warning: this can produce GeoJSON files with the same geodata in case the KML file has nested folders with geodata.
|
||||
If a ``style_type`` is given, then also build a JSON style file of the given style type and save it to the output directory under the name given by ``style_filename``.
|
||||
"""
|
||||
|
||||
# Create absolute paths
|
||||
kmz_path = Path(kmz_path).resolve()
|
||||
output_dir = Path(output_dir)
|
||||
if not output_dir.exists():
|
||||
output_dir.mkdir()
|
||||
output_dir = output_dir.resolve()
|
||||
|
||||
# opening the zip file in READ mode
|
||||
with ZipFile(kmz_path, 'r') as zip:
|
||||
names = zip.namelist()
|
||||
# Find the KML file in the archive
|
||||
# There should be only one KML per KNZ
|
||||
for name in names:
|
||||
if '.kml' in name:
|
||||
kml_file = name
|
||||
|
||||
kml_str = zip.read(kml_file)
|
||||
# Parse KML
|
||||
root = md.parseString(kml_str)
|
||||
|
||||
# Build GeoJSON layers
|
||||
if separate_folders:
|
||||
layers = build_layers(root)
|
||||
else:
|
||||
layers = [build_feature_collection(root, name=kmz_path.stem)]
|
||||
|
||||
# Handle HTML Description Tables
|
||||
for layer in layers:
|
||||
for feature in layer['features']:
|
||||
if feature['properties'].get('description'):
|
||||
if "<table>" in feature['properties']['description']:
|
||||
tree = html.fromstring(feature['properties']['description'])
|
||||
|
||||
feature['properties']['date'] = tree.xpath('//table/tr[3]/td/text()')[0].strip()
|
||||
feature['properties']['location'] = tree.xpath('//table/tr[5]/td/b/text()')[0].strip()
|
||||
feature['properties']['pressure'] = float(
|
||||
tree.xpath('//table/tr[7]/td/text()')[0].strip().split(" ")[0])
|
||||
feature['properties']['speed'] = float(
|
||||
tree.xpath('//table/tr[9]/td/text()')[0].strip().split(";")[2].strip().replace(" kph", ""))
|
||||
|
||||
del feature['properties']['name']
|
||||
del feature['properties']['styleUrl']
|
||||
del feature['properties']['description']
|
||||
|
||||
return layers
|
||||
|
||||
# Create filenames for layers
|
||||
'''filenames = disambiguate([to_filename(layer['name']) for layer in layers])
|
||||
filenames = [name + '.geojson' for name in filenames]
|
||||
|
||||
# Write layers to files
|
||||
for i in range(len(layers)):
|
||||
path = output_dir/filenames[i]
|
||||
with path.open('w') as tgt:
|
||||
json.dump(layers[i], tgt, indent = 2)
|
||||
|
||||
# Build and export style file if desired
|
||||
if style_type is not None:
|
||||
if style_type not in STYLE_TYPES:
|
||||
raise ValueError('style type must be one of {!s}'.format(
|
||||
STYLE_TYPES))
|
||||
builder_name = 'build_{!s}_style'.format(style_type)
|
||||
style_dict = globals()[builder_name](root)
|
||||
path = output_dir/style_filename
|
||||
with path.open('w') as tgt:
|
||||
json.dump(style_dict, tgt, indent=2)'''
|
||||
145
lib/postprocessor.py
Normal file
145
lib/postprocessor.py
Normal file
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#-------------------------------------------------
|
||||
#-- textures on nurbs use cases heights and sun intensity
|
||||
#--
|
||||
#-- microelly 2016 v 0.3
|
||||
#--
|
||||
#-- GNU Lesser General Public License (LGPL)
|
||||
#-------------------------------------------------
|
||||
|
||||
|
||||
import cv2
|
||||
import geodat
|
||||
import matplotlib
|
||||
import numpy as np
|
||||
|
||||
|
||||
def getHeights(nsf,size=16):
|
||||
|
||||
kzs=np.zeros((size+1,size+1),np.float)
|
||||
for ux in range(size+1):
|
||||
for vx in range(size+1):
|
||||
u=float(ux)/size
|
||||
v=float(vx)/size
|
||||
p=nsf.value(u,v)
|
||||
kzs[ux,vx]=p[2]
|
||||
|
||||
kzs *= 0.001
|
||||
|
||||
# normalize
|
||||
kzs -= kzs.min()
|
||||
kzs /= kzs.max()
|
||||
return kzs
|
||||
|
||||
|
||||
def getNormals(nsf,size=16,direction=FreeCAD.Vector(0,0,1)):
|
||||
|
||||
direction.normalize()
|
||||
kzs=np.zeros((size+1,size+1),np.float)
|
||||
for ux in range(size+1):
|
||||
for vx in range(size+1):
|
||||
u=float(ux)/size
|
||||
v=float(vx)/size
|
||||
(t1,t2)=nsf.tangent(u,v)
|
||||
# calculate the normale vector and how it differs from the given direction
|
||||
n=t1.cross(t2)
|
||||
kzs[ux,vx]=n*direction
|
||||
|
||||
return kzs
|
||||
|
||||
|
||||
|
||||
|
||||
def createColor(kzs,size,mode):
|
||||
|
||||
img= np.zeros((size+1,size+1,3), np.uint8)
|
||||
|
||||
#cmap = matplotlib.cm.get_cmap('jet')
|
||||
cmap = matplotlib.cm.get_cmap('hsv')
|
||||
|
||||
for ux in range(size+1):
|
||||
for vx in range(size+1):
|
||||
t=kzs[ux,vx]
|
||||
if mode == 1: (r,g,b,a)=cmap(1-t)
|
||||
if mode == 2: (r,g,b,a)=cmap(t)
|
||||
img[size-vx,size-ux]=(255*r,255*g,255*b)
|
||||
|
||||
# cv2.applyColorMap(img, cv2.COLORMAP_JET)
|
||||
|
||||
#cv2.imshow('image2',img)
|
||||
fn='/tmp/image_'+str(size)+'.png'
|
||||
cv2.imwrite(fn,img)
|
||||
return fn
|
||||
|
||||
#
|
||||
#
|
||||
# use cases
|
||||
#
|
||||
|
||||
|
||||
nurbs=App.ActiveDocument.QuadNurbs
|
||||
|
||||
|
||||
|
||||
'''
|
||||
#
|
||||
# Height map
|
||||
#
|
||||
|
||||
s=64
|
||||
|
||||
kzs=getHeights(nurbs.Shape.Surface,s)
|
||||
fn=createColor(kzs,s,1)
|
||||
geodat.geodat_lib.addImageTexture(nurbs,fn,scale=(1,1))
|
||||
Gui.updateGui()
|
||||
App.ActiveDocument.Text.LabelText=["Height Map","colormap HSV",str(s**2) + " color pixel"]
|
||||
|
||||
'''
|
||||
|
||||
'''
|
||||
#
|
||||
# Height map animation with different solutions
|
||||
#
|
||||
|
||||
for s in 4,8,16,32:
|
||||
kzs=getHeights(nurbs.Shape.Surface,s)
|
||||
fn=createColor(kzs,s,1)
|
||||
geodat.geodat_lib.addImageTexture(nurbs,fn,scale=(1,1))
|
||||
Gui.updateGui()
|
||||
time.sleep(0.4)
|
||||
'''
|
||||
|
||||
'''
|
||||
#
|
||||
# How planar is the surface - normals
|
||||
#
|
||||
|
||||
for s in 4,8,16,32,64,256:
|
||||
kzs=getNormals(nurbs.Shape.Surface,s)
|
||||
fn=createColor(kzs,s,1)
|
||||
geodat.geodat_lib.addImageTexture(nurbs,fn,scale=(1,1))
|
||||
Gui.updateGui()
|
||||
time.sleep(0.4)
|
||||
'''
|
||||
|
||||
|
||||
#
|
||||
# flow of the sun from 6:00 a.m. until 6:00 p.m. in 60 steps
|
||||
#
|
||||
|
||||
for h in range(61):
|
||||
s=100
|
||||
|
||||
App.ActiveDocument.Text.LabelText=["Simulation Sun, Day time",str(6.0+ 12.0*h/60),str(s**2) + " color pixel"]
|
||||
|
||||
kzs=getNormals(nurbs.Shape.Surface,s,FreeCAD.Vector(np.cos(np.pi*h/60),-np.sin(np.pi*h/60),np.sin(np.pi*h/60)))
|
||||
|
||||
# evening sun
|
||||
# kzs=getNormals(nurbs.Shape.Surface,s,FreeCAD.Vector(-1,-1,2-0.05*h))
|
||||
|
||||
# from axo view
|
||||
# kzs=getNormals(nurbs.Shape.Surface,s,FreeCAD.Vector(1,-1,2-0.05*h))
|
||||
|
||||
fn=createColor(kzs,s,2)
|
||||
geodat.geodat_lib.addImageTexture(nurbs,fn,scale=(1,1))
|
||||
Gui.updateGui()
|
||||
Reference in New Issue
Block a user