Files
PVPlant/PVPlantGeoreferencing.py
2025-03-28 19:40:11 +06:00

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