primera subida

This commit is contained in:
2025-01-28 00:04:13 +01:00
commit a91237c3e1
577 changed files with 457418 additions and 0 deletions

140
lib/GoogleMapDownloader.py Normal file
View 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()

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

View 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
View 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
View 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
View 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()