369 lines
15 KiB
Python
369 lines
15 KiB
Python
# /**********************************************************************
|
|
# * *
|
|
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify*
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
|
|
# * USA *
|
|
# * *
|
|
# ***********************************************************************
|
|
|
|
import FreeCAD
|
|
import utm
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
import os
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
import PVPlantResources
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
from PVPlantResources import DirResources as DirResources
|
|
|
|
|
|
class MapWindow(QtGui.QWidget):
|
|
def __init__(self, WinTitle="MapWindow"):
|
|
super(MapWindow, self).__init__()
|
|
self.raise_()
|
|
self.lat = None
|
|
self.lon = None
|
|
self.minLat = None
|
|
self.maxLat = None
|
|
self.minLon = None
|
|
self.maxLon = None
|
|
self.WinTitle = WinTitle
|
|
self.setupUi()
|
|
|
|
def setupUi(self):
|
|
from PySide2.QtWebEngineWidgets import QWebEngineView
|
|
from PySide2.QtWebChannel import QWebChannel
|
|
|
|
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
|
|
|
self.resize(1200, 800)
|
|
self.setWindowTitle(self.WinTitle)
|
|
self.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "Location.svg")))
|
|
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
|
|
|
self.layout = QtGui.QHBoxLayout(self)
|
|
self.layout.setContentsMargins(4, 4, 4, 4)
|
|
|
|
LeftWidget = QtGui.QWidget(self)
|
|
LeftLayout = QtGui.QVBoxLayout(LeftWidget)
|
|
LeftWidget.setLayout(LeftLayout)
|
|
LeftLayout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
RightWidget = QtGui.QWidget(self)
|
|
RightWidget.setFixedWidth(350)
|
|
RightLayout = QtGui.QVBoxLayout(RightWidget)
|
|
RightWidget.setLayout(RightLayout)
|
|
RightLayout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.layout.addWidget(LeftWidget)
|
|
self.layout.addWidget(RightWidget)
|
|
|
|
# Left Widgets:
|
|
# -- Search Bar:
|
|
self.valueSearch = QtGui.QLineEdit(self)
|
|
self.valueSearch.setPlaceholderText("Search")
|
|
self.valueSearch.returnPressed.connect(self.onSearch)
|
|
|
|
searchbutton = QtGui.QPushButton('Search')
|
|
searchbutton.setFixedWidth(80)
|
|
searchbutton.clicked.connect(self.onSearch)
|
|
|
|
SearchBarLayout = QtGui.QHBoxLayout(self)
|
|
SearchBarLayout.addWidget(self.valueSearch)
|
|
SearchBarLayout.addWidget(searchbutton)
|
|
LeftLayout.addLayout(SearchBarLayout)
|
|
|
|
# -- Webbroser:
|
|
self.view = QWebEngineView()
|
|
self.channel = QWebChannel(self.view.page())
|
|
self.view.page().setWebChannel(self.channel)
|
|
self.channel.registerObject("MyApp", self)
|
|
file = os.path.join(DirResources, "webs", "main.html")
|
|
self.view.page().loadFinished.connect(self.onLoadFinished)
|
|
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
|
LeftLayout.addWidget(self.view)
|
|
# self.layout.addWidget(self.view, 1, 0, 1, 3)
|
|
|
|
# -- Latitud y longitud:
|
|
self.labelCoordinates = QtGui.QLabel()
|
|
self.labelCoordinates.setFixedHeight(21)
|
|
LeftLayout.addWidget(self.labelCoordinates)
|
|
# self.layout.addWidget(self.labelCoordinates, 2, 0, 1, 3)
|
|
|
|
# Right Widgets:
|
|
labelKMZ = QtGui.QLabel()
|
|
labelKMZ.setText("Cargar un archivo KMZ/KML:")
|
|
self.kmlButton = QtGui.QPushButton()
|
|
self.kmlButton.setFixedSize(32, 32)
|
|
self.kmlButton.setIcon(QtGui.QIcon(os.path.join(DirIcons, "googleearth.svg")))
|
|
widget = QtGui.QWidget(self)
|
|
layout = QtGui.QHBoxLayout(widget)
|
|
widget.setLayout(layout)
|
|
layout.addWidget(labelKMZ)
|
|
layout.addWidget(self.kmlButton)
|
|
RightLayout.addWidget(widget)
|
|
|
|
# -----------------------
|
|
self.groupbox = QtGui.QGroupBox("Importar datos desde:")
|
|
self.groupbox.setCheckable(True)
|
|
self.groupbox.setChecked(True)
|
|
radio1 = QtGui.QRadioButton("Google Elevation")
|
|
radio2 = QtGui.QRadioButton("Nube de Puntos")
|
|
radio3 = QtGui.QRadioButton("Datos GPS")
|
|
radio1.setChecked(True)
|
|
|
|
# buttonDialog = QtGui.QPushButton('...')
|
|
# buttonDialog.setEnabled(False)
|
|
|
|
vbox = QtGui.QVBoxLayout(self)
|
|
vbox.addWidget(radio1)
|
|
vbox.addWidget(radio2)
|
|
vbox.addWidget(radio3)
|
|
|
|
self.groupbox.setLayout(vbox)
|
|
RightLayout.addWidget(self.groupbox)
|
|
# ------------------------
|
|
|
|
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
|
RightLayout.addWidget(self.checkboxImportGis)
|
|
|
|
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
|
RightLayout.addItem(verticalSpacer)
|
|
|
|
self.bAccept = QtGui.QPushButton('Accept')
|
|
self.bAccept.clicked.connect(self.onAcceptClick)
|
|
RightLayout.addWidget(self.bAccept)
|
|
|
|
# signals/slots
|
|
QtCore.QObject.connect(self.kmlButton, QtCore.SIGNAL("clicked()"), self.importKML)
|
|
|
|
|
|
def onLoadFinished(self):
|
|
file = os.path.join(DirResources, "webs", "map.js")
|
|
frame = self.view.page()
|
|
with open(file, 'r') as f:
|
|
frame.runJavaScript(f.read())
|
|
|
|
def onSearch(self):
|
|
if self.valueSearch.text() == "":
|
|
return
|
|
|
|
from geopy.geocoders import Nominatim
|
|
|
|
geolocator = Nominatim(user_agent="http")
|
|
location = geolocator.geocode(self.valueSearch.text())
|
|
self.valueSearch.setText(location.address)
|
|
self.panMap(location.longitude, location.latitude, location.raw['boundingbox'])
|
|
|
|
def onAcceptClick(self):
|
|
frame = self.view.page()
|
|
# 1. georeferenciar
|
|
frame.runJavaScript(
|
|
"MyApp.georeference(drawnItems.getBounds().getCenter().lat, drawnItems.getBounds().getCenter().lng);"
|
|
)
|
|
|
|
# 2. importar todos los elementos dibujados:
|
|
frame.runJavaScript(
|
|
"var data = drawnItems.toGeoJSON();"
|
|
"MyApp.shapes(JSON.stringify(data));"
|
|
)
|
|
self.close()
|
|
|
|
@QtCore.Slot(float, float)
|
|
def onMapMove(self, lat, lng):
|
|
self.lat = lat
|
|
self.lon = lng
|
|
x, y, zone_number, zone_letter = utm.from_latlon(lat, lng)
|
|
self.labelCoordinates.setText('Longitud: {:.5f}, Latitud: {:.5f}'.format(lng, lat) +
|
|
' | UTM: ' + str(zone_number) + zone_letter +
|
|
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
|
|
|
@QtCore.Slot(float, float, float, float)
|
|
def onMapZoom(self, minLat, minLon, maxLat, maxLon):
|
|
self.minLat = min([minLat, maxLat])
|
|
self.maxLat = max([minLat, maxLat])
|
|
self.minLon = min([minLon, maxLon])
|
|
self.maxLon = max([minLon, maxLon])
|
|
|
|
@QtCore.Slot(float, float)
|
|
def georeference(self, lat, lng):
|
|
import PVPlantSite
|
|
from geopy.geocoders import Nominatim
|
|
Site = PVPlantSite.get(create=True)
|
|
Site.Proxy.setLatLon(lat, lng)
|
|
|
|
geolocator = Nominatim(user_agent="http")
|
|
location = geolocator.reverse('{:.5f}, {:.5f}'.format(lat, lng))
|
|
if location:
|
|
if location.raw["address"].get("road"):
|
|
str = location.raw["address"]["road"]
|
|
if location.raw["address"].get("house_number"):
|
|
str += ' ({0})'.format(location.raw["address"]["house_number"])
|
|
Site.Address = str
|
|
if location.raw["address"].get("city"):
|
|
Site.City = location.raw["address"]["city"]
|
|
if location.raw["address"].get("postcode"):
|
|
Site.PostalCode = location.raw["address"]["postcode"]
|
|
if location.raw["address"].get("address"):
|
|
Site.Region = '{0}'.format(location.raw["address"]["province"])
|
|
if location.raw["address"].get("state"):
|
|
if Site.Region != "":
|
|
Site.Region += " - "
|
|
Site.Region += '{0}'.format(location.raw["address"]["state"]) # province - state
|
|
Site.Country = location.raw["address"]["country"]
|
|
|
|
@QtCore.Slot(str)
|
|
def shapes(self, drawnItems):
|
|
import geojson
|
|
import PVPlantImportGrid as ImportElevation
|
|
import Draft
|
|
import PVPlantSite
|
|
Site = PVPlantSite.get()
|
|
|
|
offset = FreeCAD.Vector(0, 0, 0)
|
|
if not (self.lat is None or self.lon is None):
|
|
offset = FreeCAD.Vector(Site.Origin)
|
|
offset.z = 0
|
|
|
|
items = geojson.loads(drawnItems)
|
|
for item in items['features']:
|
|
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
|
coord = item['geometry']['coordinates']
|
|
point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],])
|
|
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
|
|
if item['properties'].get('radius'):
|
|
r = round(item['properties']['radius'] * 1000, 0)
|
|
p = FreeCAD.Placement()
|
|
p.Base = c
|
|
obj = Draft.makeCircle(r, placement=p, face=False)
|
|
else:
|
|
''' do something '''
|
|
obj = Draft.make_point(c * 1000, color=(0.5, 0.3, 0.6), point_size=10)
|
|
else: # 2. if the feature is a Polygon or Line:
|
|
cw = False
|
|
name = "Línea"
|
|
lp = item['geometry']['coordinates']
|
|
if item['geometry']['type'] == "Polygon":
|
|
cw = True
|
|
name = "Area"
|
|
lp = item['geometry']['coordinates'][0]
|
|
|
|
pts = [[cords[1], cords[0]] for cords in lp]
|
|
tmp = ImportElevation.getElevationFromOE(pts)
|
|
pts = [p.sub(offset) for p in tmp]
|
|
|
|
obj = Draft.makeWire(pts, closed=cw, face=False)
|
|
#obj.Placement.Base = offset
|
|
obj.Label = name
|
|
Draft.autogroup(obj)
|
|
|
|
if item['properties'].get('name'):
|
|
obj.Label = item['properties']['name']
|
|
|
|
if self.checkboxImportGis.isChecked():
|
|
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
|
|
|
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
|
import Importer.importOSM as importOSM
|
|
import PVPlantSite
|
|
site = PVPlantSite.get()
|
|
|
|
offset = FreeCAD.Vector(0, 0, 0)
|
|
if not (self.lat is None or self.lon is None):
|
|
offset = FreeCAD.Vector(site.Origin)
|
|
offset.z = 0
|
|
importer = importOSM.OSMImporter(offset)
|
|
osm_data = importer.get_osm_data(f"{min_lat},{min_lon},{max_lat},{max_lon}")
|
|
importer.process_osm_data(osm_data)
|
|
|
|
'''FreeCAD.activeDocument().recompute()
|
|
FreeCADGui.updateGui()
|
|
FreeCADGui.SendMsgToActiveView("ViewFit")'''
|
|
|
|
def panMap_old(self, lng, lat, geometry=""):
|
|
frame = self.view.page()
|
|
bbox = "[{0}, {1}], [{2}, {3}]".format(float(geometry[0]), float(geometry[2]),
|
|
float(geometry[1]), float(geometry[3]))
|
|
command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng)
|
|
command += 'map.fitBounds([{box}]);'.format(box=bbox)
|
|
frame.runJavaScript(command)
|
|
|
|
# deepseek
|
|
def panMap(self, lng, lat, geometry=None):
|
|
frame = self.view.page()
|
|
|
|
# 1. Validación del parámetro geometry
|
|
if not geometry or len(geometry) < 4:
|
|
# Pan básico sin ajuste de bounds
|
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
else:
|
|
try:
|
|
# 2. Mejor manejo de coordenadas (Leaflet usa [lat, lng])
|
|
# Asumiendo que geometry es [min_lng, min_lat, max_lng, max_lat]
|
|
southwest = f"{float(geometry[1])}, {float(geometry[0])}" # min_lat, min_lng
|
|
northeast = f"{float(geometry[3])}, {float(geometry[2])}" # max_lat, max_lng
|
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
command += f'map.fitBounds(L.latLngBounds([{southwest}], [{northeast}]));'
|
|
except (IndexError, ValueError, TypeError) as e:
|
|
print(f"Error en geometry: {str(e)}")
|
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
frame.runJavaScript(command)
|
|
|
|
def importKML(self):
|
|
file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0]
|
|
|
|
from lib.kml2geojson import kmz_convert
|
|
layers = kmz_convert(file, "", )
|
|
frame = self.view.page()
|
|
for layer in layers:
|
|
command = "var geoJsonLayer = L.geoJSON({0}); drawnItems.addLayer(geoJsonLayer); map.fitBounds(geoJsonLayer.getBounds());".format( layer)
|
|
frame.runJavaScript(command)
|
|
|
|
|
|
class CommandPVPlantGeoreferencing:
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
|
|
'Accel': "G, R",
|
|
'MenuText': QT_TRANSLATE_NOOP("Georeferencing","Georeferencing"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Georeferencing","Referenciar el lugar")}
|
|
|
|
def Activated(self):
|
|
self.form = MapWindow()
|
|
self.form.show()
|
|
|
|
def IsActive(self):
|
|
if FreeCAD.ActiveDocument:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
'''if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('PVPlantGeoreferencing',_CommandPVPlantGeoreferencing())
|
|
'''
|