diff --git a/_alp/Agents/UI_Tabs/EmbeddedObjects.xml b/_alp/Agents/UI_Tabs/EmbeddedObjects.xml index 5c31394..16b4ac3 100644 --- a/_alp/Agents/UI_Tabs/EmbeddedObjects.xml +++ b/_alp/Agents/UI_Tabs/EmbeddedObjects.xml @@ -75,13 +75,13 @@ - + - + - + true diff --git a/_alp/Agents/Zero_Interface/AOC.Zero_Interface.xml b/_alp/Agents/Zero_Interface/AOC.Zero_Interface.xml index 11d49d4..e747af1 100644 --- a/_alp/Agents/Zero_Interface/AOC.Zero_Interface.xml +++ b/_alp/Agents/Zero_Interface/AOC.Zero_Interface.xml @@ -19,6 +19,9 @@ import java.time.DayOfWeek; import java.time.Instant; import java.text.SimpleDateFormat; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; //Possibility to turn of traceln import java.io.OutputStream; diff --git a/_alp/Agents/Zero_Interface/Code/Functions.java b/_alp/Agents/Zero_Interface/Code/Functions.java index bcb7e43..e616632 100644 --- a/_alp/Agents/Zero_Interface/Code/Functions.java +++ b/_alp/Agents/Zero_Interface/Code/Functions.java @@ -375,6 +375,36 @@ else if(b.p_annotation != null){ //Set the button for going to the company UI (needs to be at the end of this function!) f_setUIButton(); +//alle panden met meerdere adressen hebben op dit moment (16-7-24) dezelfde functie(s) voor ieder adres, dus dit is op dit moment zinloos +//f_listFunctions(); + +f_updateCustomGCSolarfarmSettings(); +f_updateCustomGCWindfarmSettings(); +f_updateCustomGCGridBatterySettings(); + +boolean anyCustomSettingsOpen = false; +if (!c_selectedGridConnections.isEmpty()) { + GridConnection selectedGC = c_selectedGridConnections.get(0); + if(selectedGC instanceof GCEnergyProduction){ + if (v_clickedObjectType == OL_GISObjectType.SOLARFARM && c_customSolarfarmGCs.contains((GCEnergyProduction)selectedGC)) { + anyCustomSettingsOpen = true; + } + else if (v_clickedObjectType == OL_GISObjectType.WINDFARM && c_customWindfarmGCs.contains((GCEnergyProduction)selectedGC)) { + anyCustomSettingsOpen = true; + } + } + else if (selectedGC instanceof GCGridBattery){ + if (v_clickedObjectType == OL_GISObjectType.BATTERY && c_customGridBatteryGCs.contains((GCGridBattery)selectedGC)) { + anyCustomSettingsOpen = true; + } + } +} + +if (anyCustomSettingsOpen) { + uI_Tabs_presentation.setVisible(false); +} else { + uI_Tabs_presentation.setVisible(true); +} /*ALCODEEND*/} double f_deselectPreviousSelect() @@ -423,6 +453,9 @@ else if (previousClickedObjectType == OL_GISObjectType.BUILDING || v_customEnergyCoop = null; } } + +// RESTORE tab visibility when closing/deselecting settings +uI_Tabs_presentation.setVisible(true); /*ALCODEEND*/} double f_connectResultsUI() @@ -1006,8 +1039,7 @@ GISRoute f_createGISLine(double[] GISCoords,OL_GISObjectType objectType) f_filterGridLoops(toBeFilteredGC); } else{ - - f_setForcedClickScreenText("Selecteer een lus"); + f_setForcedClickScreenMessageText("Selecteer een lus"); if(!b_inEnergyHubSelectionMode){ f_setForcedClickScreenVisibility(true); } @@ -1029,7 +1061,7 @@ GISRoute f_createGISLine(double[] GISCoords,OL_GISObjectType objectType) f_filterNeighborhoods(toBeFilteredGC); } else{ - f_setForcedClickScreenText("Selecteer een buurt"); + f_setForcedClickScreenMessageText("Selecteer een buurt"); if(!b_inEnergyHubSelectionMode){ f_setForcedClickScreenVisibility(true); } @@ -1158,7 +1190,7 @@ else if(c_selectedFilterOptions.contains(selectedFilter)){ // Remove filter } if(gr_forceMapSelection.isVisible()){ - f_setForcedClickScreenText(""); + f_setForcedClickScreenMessageText(""); if(!b_inEnergyHubSelectionMode){ f_setForcedClickScreenVisibility(false); } @@ -1353,7 +1385,7 @@ else if(c_selectedFilterOptions.contains(selectedFilter)){ // Remove filter } if(gr_forceMapSelection.isVisible()){ - f_setForcedClickScreenText(""); + f_setForcedClickScreenMessageText(""); if(!b_inEnergyHubSelectionMode){ f_setForcedClickScreenVisibility(false); } @@ -1512,15 +1544,14 @@ else if(c_manualFilterDeselectedGC.contains(clickedGC)){ } /*ALCODEEND*/} -double f_setForcedClickScreenText(String forcedClickScreenText) +double f_setForcedClickScreenMessageText(String forcedClickScreenMessageText) {/*ALCODESTART::1742300624199*/ -t_forcedClickMessage.setText(forcedClickScreenText); +t_forcedClickMessage.setText(forcedClickScreenMessageText); +gr_ForceMapSelectionMessageText.setVisible(false); -if(t_forcedClickMessage.getText().equals("")){ - gr_ForceMapSelectionText.setVisible(false); -} -else{ - gr_ForceMapSelectionText.setVisible(true); +if(!t_forcedClickMessage.getText().equals("")){ + f_adjustTextToFitRectangle(t_forcedClickMessage, rect_selectText, 25.0, 36); + gr_ForceMapSelectionMessageText.setVisible(true); } /*ALCODEEND*/} @@ -2704,7 +2735,7 @@ else if (c_selectedFilterOptions.contains(OL_FilterOptionsGC.GRIDTOPOLOGY_SELECT b_inEnergyHubMode = true; b_inEnergyHubSelectionMode = true; -f_setForcedClickScreenText(""); +f_setForcedClickScreenTextBoxes("Energie Hub Configurator", uI_EnergyHub.p_energyHubBackGroundColor, uI_EnergyHub.p_energyHubLineColor, "", new Color(255, 255, 255), new Color(0, 0, 0)); f_setForcedClickScreenVisibility(true); cb_showFilterInterface.setSelected(true, true); @@ -3027,7 +3058,7 @@ ShapeRadioButtonGroup f_getScenarioButtons() b_inEnergyHubMode = false; b_inEnergyHubSelectionMode = false; -f_setForcedClickScreenText(""); +f_setForcedClickScreenTextBoxes("", new Color(255, 255, 255), new Color(0, 0, 0), "", new Color(255, 255, 255), new Color(0, 0, 0)); f_setForcedClickScreenVisibility(false); cb_showFilterInterface.setSelected(false, true); @@ -3837,6 +3868,16 @@ List f_getSelectedChartTypes_Energy() else if(b_inManualFilterSelectionMode){ f_selectManualFilteredGC(clickx, clicky); } +else if (b_addCustomGC) { + if (!b_customGCPolygonCreated) { + f_addCustomGCLocationSelection(clickx, clicky); + } else { + f_addCustomGCTransformerSelection(clickx, clicky); + } +} +else if (b_removeCustomGC) { + f_removeCustomGCSelection(clickx,clicky); +} else{ if (uI_Tabs.pop_tabEHub.size() > 0 && uI_Tabs.pop_tabEHub.get(0).b_inCapacitySharingSelectionMode) { uI_Tabs.pop_tabEHub.get(0).f_checkGISRegion(clickx, clicky); @@ -3882,3 +3923,799 @@ else if (c_selectedFilterOptions.contains(OL_FilterOptionsGC.GRIDTOPOLOGY_SELECT uI_Results.f_updateResultsUI(c_selectedGridConnections.get(0)); /*ALCODEEND*/} +double f_addCustomSolarfarmGC(GridNode gn) +{/*ALCODESTART::1777995108231*/ +String id = "Custom_Solarfarm_" + v_customSolarfarmGCCounter++; + +// 0. Get existic sliderGC owner +ConnectionOwner owner = f_getSliderOwnerForCustomGC(OL_EnergyAssetType.PHOTOVOLTAIC); +if(owner == null){ + throw new RuntimeException("No owner made your custom EA type!"); +} + +// 1. Create the GCEnergyProduction agent +GCEnergyProduction solarpark = energyModel.add_EnergyProductionSites(); +solarpark.set_p_gridConnectionID(id); +solarpark.set_p_ownerID(owner.p_actorID); +solarpark.set_p_owner(owner); +solarpark.p_parentNodeElectricID = gn.p_gridNodeID; +solarpark.p_isSliderGC = false; // This affects the slider range + setValue + +// 2. Create the GIS Object +GIS_Object area = f_createAndLinkGISObject(solarpark, id, OL_GISObjectType.SOLARFARM, v_solarParkColor, v_solarParkLineColor); + +double area_ha = area.gisRegion.area() / 10000; +double installedCapacity_kW = area_ha * energyModel.avgc_data.p_avgSolarFieldPower_kWppha; +double maxContractedCapacity_kW = Math.ceil(installedCapacity_kW / 50.0) * 50.0; +solarpark.v_liveConnectionMetaData.setCapacities_kW(0, maxContractedCapacity_kW, maxContractedCapacity_kW); +solarpark.v_liveConnectionMetaData.setCapacitiesKnown(true, true, true); + +solarpark.v_liveAssetsMetaData.PVOrientation = OL_PVOrientation.SOUTH; + +// 3. Initialize GridConnection +solarpark.f_initialize(energyModel.p_timeParameters); + +// 4. Create the energy asset +J_EAProduction pvAsset = new J_EAProduction(solarpark, OL_EnergyAssetType.PHOTOVOLTAIC, "Custom PV", OL_EnergyCarriers.ELECTRICITY, installedCapacity_kW, energyModel.p_timeParameters, energyModel.pp_PVProduction35DegSouth_fr); + +// 6. Update collections, sliders and legend +c_customSolarfarmGCs.add(solarpark); +if (!c_modelActiveSpecialGISObjects.contains(area.p_GISObjectType)) { + c_modelActiveSpecialGISObjects.add(area.p_GISObjectType); +} +if (!uI_Tabs.pop_tabElectricity.isEmpty()) { + uI_Tabs.pop_tabElectricity.get(0).f_updateSliders_Electricity(); +} +f_refreshLegend(); + +// 7. Select the newly created asset immediately to update and set settings panel sliders +ArrayList selectedList = new ArrayList<>(); +selectedList.add(area); +f_selectBuilding(area, selectedList); + +traceln("Successfully added custom solarfarm"); +/*ALCODEEND*/} + +double f_removeCustomGC(GridConnection gc) +{/*ALCODESTART::1777995108233*/ +gc.f_setActive(false, energyModel.p_timeVariables); + +// 1. Remove energy assets +for(J_EA ea : new ArrayList<>(gc.c_energyAssets)) { + ea.removeEnergyAsset(); +} + +// 2. Remove GIS Object +OL_GISObjectType removedGISType = null; +for (GIS_Object obj : new ArrayList<>(gc.c_connectedGISObjects)) { + removedGISType = obj.p_GISObjectType; + obj.gisRegion.setVisible(false); + energyModel.remove_pop_GIS_Objects(obj); +} + +// 3. Remove from collections +energyModel.c_pausedGridConnections.remove(gc); +if (gc instanceof GCEnergyProduction) { + energyModel.remove_EnergyProductionSites((GCEnergyProduction)gc); + if(c_customSolarfarmGCs.contains((GCEnergyProduction)gc)){ + c_customSolarfarmGCs.remove(gc); + } + else if(c_customWindfarmGCs.contains((GCEnergyProduction)gc)){ + c_customWindfarmGCs.remove(gc); + } +} else if (gc instanceof GCGridBattery) { + energyModel.remove_GridBatteries((GCGridBattery)gc); + c_customGridBatteryGCs.remove((GCGridBattery)gc); +} + +// 4. Refresh slider + legend to account for changes +if (removedGISType != null) { + boolean typeExists = false; + // Verify if any assets of this type still exist in the simulation + for (GIS_Object obj : energyModel.pop_GIS_Objects) { + if (obj.p_GISObjectType == removedGISType && obj.gisRegion != null && obj.gisRegion.isVisible()) { + typeExists = true; + break; + } + } + if (!typeExists) { + c_modelActiveSpecialGISObjects.remove(removedGISType); + } +} +if (!uI_Tabs.pop_tabElectricity.isEmpty()) { + uI_Tabs.pop_tabElectricity.get(0).f_updateSliders_Electricity(); +} +f_refreshLegend(); +/*ALCODEEND*/} + +double f_addCustomWindfarmGC(GridNode gn) +{/*ALCODESTART::1777995108235*/ +String id = "Custom_Windfarm_" + v_customWindfarmGCCounter++; + +// 0. Get existic sliderGC owner +ConnectionOwner owner = f_getSliderOwnerForCustomGC(OL_EnergyAssetType.WINDMILL); +if(owner == null){ + throw new RuntimeException("No owner made your custom EA type!"); +} + +// 1. Create the GCEnergyProduction agent +GCEnergyProduction windpark = energyModel.add_EnergyProductionSites(); +windpark.set_p_gridConnectionID(id); +windpark.set_p_ownerID(owner.p_actorID); +windpark.set_p_owner(owner); +windpark.p_parentNodeElectricID = gn.p_gridNodeID; +windpark.p_isSliderGC = false; // This affects the slider range + setValue + +// 2. Set capacity +double defaultCapacity_kW = 500; +windpark.v_liveConnectionMetaData.setCapacities_kW(0, defaultCapacity_kW, defaultCapacity_kW); +windpark.v_liveConnectionMetaData.setCapacitiesKnown(true, true, true); + +// 3. Initialize GridConnection +windpark.f_initialize(energyModel.p_timeParameters); + +// 4. Create the Energy Asset +J_EAProduction windAsset = new J_EAProduction(windpark, OL_EnergyAssetType.WINDMILL, "Custom Windpark", OL_EnergyCarriers.ELECTRICITY, defaultCapacity_kW, energyModel.p_timeParameters, energyModel.pp_windProduction_fr); + +// 5. Create the GIS Object +GIS_Object area = f_createAndLinkGISObject(windpark, id, OL_GISObjectType.WINDFARM, v_windFarmColor, v_windFarmLineColor); + +// 6. Update collections, sliders and legend +c_customWindfarmGCs.add(windpark); +if (!c_modelActiveSpecialGISObjects.contains(area.p_GISObjectType)) { + c_modelActiveSpecialGISObjects.add(area.p_GISObjectType); +} +if (!uI_Tabs.pop_tabElectricity.isEmpty()) { + uI_Tabs.pop_tabElectricity.get(0).f_updateSliders_Electricity(); +} +f_refreshLegend(); + +// 7. Select the newly created asset immediately to update and set settings panel sliders +ArrayList selectedList = new ArrayList<>(); +selectedList.add(area); +f_selectBuilding(area, selectedList); + +traceln("Successfully added custom windfarm"); +/*ALCODEEND*/} + +double f_addCustomGridBatteryGC(GridNode gn) +{/*ALCODESTART::1777995108237*/ +String id = "Custom_Grid_Battery_" + v_customGridBatteryGCCounter++; + +// 0. Get existic sliderGC owner +ConnectionOwner owner = f_getSliderOwnerForCustomGC(OL_EnergyAssetType.STORAGE_ELECTRIC); +if(owner == null){ + throw new RuntimeException("No owner made your custom EA type!"); +} + +// 1. Create the GCGridBattery agent +GCGridBattery battery = energyModel.add_GridBatteries(); +battery.set_p_gridConnectionID(id); +battery.set_p_ownerID(owner.p_actorID); +battery.set_p_owner(owner); +battery.p_parentNodeElectricID = gn.p_gridNodeID; +battery.p_isSliderGC = false; // This affects the slider range + setValue + +// 2. Set capacity +double defaultCapacity_kW = 250; +double defaultStorageCapacity_kWh = 2*defaultCapacity_kW; +battery.v_liveConnectionMetaData.setCapacities_kW(defaultCapacity_kW, defaultCapacity_kW, defaultCapacity_kW); +battery.v_liveConnectionMetaData.setCapacitiesKnown(true, true, true); + +// 3. Initialize GridConnection +battery.f_initialize(energyModel.p_timeParameters); + +// 4. Create the energy asset + pick default operation mode management class +J_EAStorageElectric batteryAsset = new J_EAStorageElectric(battery, defaultCapacity_kW, defaultStorageCapacity_kWh, 0.5, energyModel.p_timeParameters); +I_BatteryManagement batteryAlgorithm = new J_BatteryManagementSelfConsumptionGridNode(battery, energyModel.p_timeParameters); +battery.f_setBatteryManagement(batteryAlgorithm); + +// 5. Create GIS Object +GIS_Object area = f_createAndLinkGISObject(battery, id, OL_GISObjectType.BATTERY, v_batteryColor, v_batteryLineColor); + +// 6. Update collections, sliders and legend +c_customGridBatteryGCs.add(battery); +if (!c_modelActiveSpecialGISObjects.contains(area.p_GISObjectType)) { + c_modelActiveSpecialGISObjects.add(area.p_GISObjectType); +} +if (!uI_Tabs.pop_tabElectricity.isEmpty()) { + uI_Tabs.pop_tabElectricity.get(0).f_updateSliders_Electricity(); +} +f_refreshLegend(); + +// 7. Select the newly created asset immediately to update and set settings panel sliders +ArrayList selectedList = new ArrayList<>(); +selectedList.add(area); +f_selectBuilding(area, selectedList); + +traceln("Successfully added custom grid battery"); +/*ALCODEEND*/} + +double f_updateCustomGCSolarfarmSettings() +{/*ALCODESTART::1778162093872*/ +if (c_selectedGridConnections.isEmpty()) { + return; +} + +GridConnection selectedGC = c_selectedGridConnections.get(0); +if (!(selectedGC instanceof GCEnergyProduction) || !c_customSolarfarmGCs.contains(selectedGC)) { + return; +} + +GCEnergyProduction gc = (GCEnergyProduction) selectedGC; +J_EAProduction pvAsset = (J_EAProduction) gc.c_productionAssets.get(0); + +// Installed capacity per hectare +double currentCapacity_kW = pvAsset.getCapacityElectric_kW(); +double area_m2 = gc.c_connectedGISObjects.get(0).gisRegion.area(); +double area_ha = area_m2 / 10000.0; +double currentCapacity_kWpha = currentCapacity_kW / area_ha; + +sl_customGCSolarfarmInstalledCapacity_kWpha.setRange((int)(0.5*energyModel.avgc_data.p_avgSolarFieldPower_kWppha), (int)(2*energyModel.avgc_data.p_avgSolarFieldPower_kWppha)); +sl_customGCSolarfarmInstalledCapacity_kWpha.setValue(currentCapacity_kWpha, false); + +// PV Orientation +J_ProfilePointer currentProfile = pvAsset.getProfilePointer(); +String currentOrientationLabel = "Zuid (15°)"; // Default +if (currentProfile == energyModel.pp_PVProduction15DegEastWest_fr) { + currentOrientationLabel = "Oost/West (35°)"; +} +cb_customGCSolarfarmPVOrientation.setValue(currentOrientationLabel, false); + +// Curtailment +boolean hasCurtailment = gc.f_isAssetManagementActive(I_CurtailManagement.class); +cb_customGCSolarfarmCurtailment.setSelected(hasCurtailment, false); + +// Contracted capacity limit +double maxContractedCapacity_kW = Math.ceil(currentCapacity_kW / 50.0) * 50.0; + +sl_customGCSolarfarmContractedCapacity_kW.setRange(0, maxContractedCapacity_kW); +sl_customGCSolarfarmContractedCapacity_kW.setValue(gc.v_liveConnectionMetaData.getContractedFeedinCapacity_kW(), false); + +/*ALCODEEND*/} + +double f_updateCustomGCWindfarmSettings() +{/*ALCODESTART::1778241627082*/ +if (c_selectedGridConnections.isEmpty()) { + return; +} + +GridConnection selectedGC = c_selectedGridConnections.get(0); +if (!(selectedGC instanceof GCEnergyProduction) || !c_customWindfarmGCs.contains(selectedGC)) { + return; +} + +GCEnergyProduction gc = (GCEnergyProduction) selectedGC; +J_EAProduction windAsset = (J_EAProduction) gc.c_productionAssets.get(0); + +// Installed capacity +double currentCapacity_MW = windAsset.getCapacityElectric_kW()/1000; + +sl_customGCWindfarmInstalledCapacity_MW.setRange(0.1, 10); +sl_customGCWindfarmInstalledCapacity_MW.setValue(currentCapacity_MW, false); // false prevents triggering ActionCode + +// Curtailment +boolean hasCurtailment = gc.f_isAssetManagementActive(I_CurtailManagement.class); +cb_customGCWindfarmCurtailment.setSelected(hasCurtailment, false); + +// Contracted capacity limit +sl_customGCWindfarmContractedCapacity_MW.setRange(0, currentCapacity_MW); +sl_customGCWindfarmContractedCapacity_MW.setValue(gc.v_liveConnectionMetaData.getContractedFeedinCapacity_kW()/1000, false); +/*ALCODEEND*/} + +double f_updateCustomGCGridBatterySettings() +{/*ALCODESTART::1778281342673*/ +if (c_selectedGridConnections.isEmpty()) { + return; +} + +GridConnection selectedGC = c_selectedGridConnections.get(0); +if (!(selectedGC instanceof GCGridBattery) || !c_customGridBatteryGCs.contains(selectedGC)) { + return; +} + +GCGridBattery gc = (GCGridBattery) selectedGC; +J_EAStorageElectric batteryAsset = (J_EAStorageElectric)gc.c_storageAssets.get(0); + +// Installed capacity +double currentCapacity_kWh = batteryAsset.getStorageCapacity_kWh(); +double currentCapacity_kW = batteryAsset.getCapacityElectric_kW(); + +sl_customGCGridBatteryInstalledCapacity_kWh.setRange(100, 5000); +sl_customGCGridBatteryInstalledCapacity_kWh.setValue(currentCapacity_kWh, false); + +sl_customGCGridBatteryInstalledCapacity_kW.setRange(50, 2500); +sl_customGCGridBatteryInstalledCapacity_kW.setValue(currentCapacity_kW, false); + +// Battery management selection +I_BatteryManagement currentBatteryManagement = gc.f_getBatteryManagement(); +String currentBMS_str = "Zelfverbruik"; // Default fallback + +if (currentBatteryManagement instanceof J_BatteryManagementSelfConsumptionGridNode) { + currentBMS_str = "Zelfverbruik"; +} else if (currentBatteryManagement instanceof J_BatteryManagementPeakShaving) { + currentBMS_str = "Peak shaving"; +} else if (currentBatteryManagement instanceof J_BatteryManagementPrice) { + currentBMS_str = "Prijssturing"; +} +cb_customGCGridBatteryAlgorithm.setValue(currentBMS_str, false); +/*ALCODEEND*/} + +ConnectionOwner f_getSliderOwnerForCustomGC(OL_EnergyAssetType eaType) +{/*ALCODESTART::1778854379860*/ +if (!uI_Tabs.pop_tabElectricity.isEmpty()) { + tabElectricity tabElec = uI_Tabs.pop_tabElectricity.get(0); + for (GridConnection gc : tabElec.c_electricityTabEASliderGCs) { + if (eaType == OL_EnergyAssetType.PHOTOVOLTAIC || eaType == OL_EnergyAssetType.WINDMILL) { + if (gc instanceof GCEnergyProduction) { + for (J_EAProduction ea : ((GCEnergyProduction)gc).c_productionAssets) { + if (ea.getEAType() == eaType) { + return gc.p_owner; + } + } + } + } else if (eaType == OL_EnergyAssetType.STORAGE_ELECTRIC) { + if (gc instanceof GCGridBattery) { + return gc.p_owner; + } + } + } +} +return null; +/*ALCODEEND*/} + +GIS_Object f_createAndLinkGISObject(GridConnection gc,String id,OL_GISObjectType gisType,Color fillColor,Color lineColor) +{/*ALCODESTART::1778855159263*/ +GIS_Object area = energyModel.add_pop_GIS_Objects(); +area.p_id = id; +area.p_GISObjectType = gisType; +area.p_latitude = c_tempSavedCoordinates.get(0).getFirst(); +area.p_longitude = c_tempSavedCoordinates.get(0).getSecond(); + +// 2. Generate coordinates (circular for windfarm, square for others) and assign to region +double[] polyCoords; +if (gisType == OL_GISObjectType.WINDFARM || gisType == OL_GISObjectType.BATTERY) { + double area_m2 = f_calculateGISObjectArea(gc, gisType); + if (gisType == OL_GISObjectType.WINDFARM) { + polyCoords = f_calculateCircleCoordinates(area.p_latitude, area.p_longitude, area_m2); + area.gisRegion = f_createGISObject(polyCoords); + } else if (gisType == OL_GISObjectType.BATTERY) { + polyCoords = f_calculateSquareCoordinates(area.p_latitude, area.p_longitude, area_m2); + area.gisRegion = f_createGISObject(polyCoords); + } +} else { + polyCoords = f_calculateCustomPolygonCoordinates(c_tempSavedCoordinates); + area.gisRegion = f_createGISObject(polyCoords); +} + +// 3. Add to collections +area.c_containedGridConnections.add(gc); +gc.c_connectedGISObjects.add(area); + +// 4. Apply styling +area.set_p_defaultFillColor(fillColor); +area.set_p_defaultLineColor(lineColor); +area.set_p_defaultLineWidth(v_energyAssetLineWidth); +f_styleAreas(area); + +return area; +/*ALCODEEND*/} + +GIS_Object f_refreshLegend() +{/*ALCODESTART::1778856248260*/ +// Hide the maximum possible special legend items before rebuilding to prevent UI overlap +for (int i = 1; i <= 10; i++) { + try { + Pair legendShapes = f_getNextSpecialLegendShapes(i); + if (legendShapes != null && legendShapes.getFirst() != null && legendShapes.getSecond() != null) { + legendShapes.getFirst().setVisible(false); + legendShapes.getSecond().setVisible(false); + } + } catch (Exception e) { + break; // Stop if we run out of defined legend shapes in the UI + } +} +// Rebuild the legend using existing functionality +f_initializeLegend(); +/*ALCODEEND*/} + +double f_calculateGISObjectArea(GridConnection gc,OL_GISObjectType gisType) +{/*ALCODESTART::1778997814360*/ +/** + * Calculates the footprint area in square meters (m2) of a custom energy asset based on its capacity. + * + * - Solar farm: 1 hectare per 'p_avgSolarFieldPower_kWppha' kW + * - Wind farm: 1 hectare per 5 MW (5000 kW) capacity + * - Battery: 1 hectare per 100 MWh storage capacity (100 m2 per MWh) + */ + +double area_m2 = 100.0; // Default fallback size +if (gc instanceof GCEnergyProduction prodGC) { + if (gisType == OL_GISObjectType.SOLARFARM) { + double capacity_kW = 0.0; + for (J_EAProduction asset : prodGC.c_productionAssets) { + if (asset.getEAType() == OL_EnergyAssetType.PHOTOVOLTAIC) { + capacity_kW += asset.getCapacityElectric_kW(); + } + } + double capacity_ha = capacity_kW / energyModel.avgc_data.p_avgSolarFieldPower_kWppha; + area_m2 = capacity_ha * 10000.0; + } + else if (gisType == OL_GISObjectType.WINDFARM) { + double defaultCapacity_kW = 500.0; + double defaultCapacity_ha = (defaultCapacity_kW / 1000.0) / 5.0; // 5 MW per hectare -> 0.1 + area_m2 = defaultCapacity_ha * 10000.0; + } +} +else if (gc instanceof GCGridBattery batteryGC) { + if (gisType == OL_GISObjectType.BATTERY) { + double defaultCapacity_MWh = 500.0 / 1000.0; + double defaultCapacity_ha = defaultCapacity_MWh / 100.0; // 100 MWh per hectare -> 0.005 ha + area_m2 = defaultCapacity_ha * 10000.0; + } + } +return area_m2; +/*ALCODEEND*/} + +double[] f_calculateSquareCoordinates(double lat,double lon,double area_m2) +{/*ALCODESTART::1778997955081*/ +/** + * Computes a list of double coordinates representing a perfectly square polygon centered + * at (lat, lon) with a physical area of 'area_m2' in square meters, correcting for local latitude "deformation". + */ + +double side_m = Math.sqrt(area_m2); +double halfSide_m = side_m / 2.0; + +// Earth conversion factors (approximate for localized areas) +double metersPerDegLat = 111320.0; +double metersPerDegLon = 111320.0 * Math.cos(Math.toRadians(lat)); + +double offsetLat = halfSide_m / metersPerDegLat; +double offsetLon = halfSide_m / metersPerDegLon; + +return new double[]{ + lat + offsetLat, lon - offsetLon, // Top-left + lat + offsetLat, lon + offsetLon, // Top-right + lat - offsetLat, lon + offsetLon, // Bottom-right + lat - offsetLat, lon - offsetLon // Bottom-left +}; +/*ALCODEEND*/} + +double[] f_calculateCircleCoordinates(double lat,double lon,double area_m2) +{/*ALCODESTART::1781623122174*/ +// A circle's radius in meters from its area in m²: Area = pi * r^2 => r = sqrt(Area / pi) +double radius_m = Math.sqrt(area_m2 / Math.PI); + +// Number of points (vertices) to approximate the circle +int numPoints = 32; +double[] polyCoords = new double[numPoints * 2]; + +// Earth conversion factors (approximate for localized areas) +double metersPerDegLat = 111320.0; +double metersPerDegLon = 111320.0 * Math.cos(Math.toRadians(lat)); + +for (int i = 0; i < numPoints; i++) { + double angle = 2.0 * Math.PI * i / numPoints; + double offsetLat = (radius_m * Math.sin(angle)) / metersPerDegLat; + double offsetLon = (radius_m * Math.cos(angle)) / metersPerDegLon; + + polyCoords[2 * i] = lat + offsetLat; + polyCoords[2 * i + 1] = lon + offsetLon; +} + +return polyCoords; +/*ALCODEEND*/} + +double f_addCustomGCLocationSelection(double clickx,double clicky) +{/*ALCODESTART::1781677700343*/ +// --- PHASE 1: Drawing Polygon Vertices --- + +// Add a vertex to the coordinates list +Pair clickedCoord = new Pair<>(clickx, clicky); +c_tempSavedCoordinates.add(clickedCoord); + +// Place a small square dot on the map representing this vertex +c_tempSavedDots.add(f_drawTempCoordinateDot(clickx, clicky)); + +if (v_addCustomGCType == OL_EnergyAssetType.WINDMILL || v_addCustomGCType == OL_EnergyAssetType.STORAGE_ELECTRIC){ + b_customGCPolygonCreated = true; + f_setForcedClickScreenMessageText("Kies een trafo op de kaart"); +} else { + // Update instruction text + int minNbRequiredVertices = 3; + int currentNbOfSavedCoordinates = c_tempSavedCoordinates.size(); + if(minNbRequiredVertices - currentNbOfSavedCoordinates > 1){ + f_setForcedClickScreenMessageText("Teken je locatie op de kaart. Kies nog minimaal " + (minNbRequiredVertices - currentNbOfSavedCoordinates) + " hoekpunten."); + } + else if(minNbRequiredVertices - currentNbOfSavedCoordinates == 1){ + f_setForcedClickScreenMessageText("Teken je locatie op de kaart. Kies nog minimaal " + (minNbRequiredVertices - currentNbOfSavedCoordinates) + " hoekpunt."); + } else { + if (previewGISRegion != null) { + previewGISRegion.remove(); + } + double[] previewCoords = f_calculateCustomPolygonCoordinates(c_tempSavedCoordinates); + previewGISRegion = f_createGISObject(previewCoords); + previewGISRegion.setFillColor(new Color(255, 0, 0, 50)); // Semi-transparent red + previewGISRegion.setLineColor(Color.RED); + previewGISRegion.setLineWidth(1.0); + f_setForcedClickScreenMessageText("Huidig aantal hoekpunten: " + currentNbOfSavedCoordinates + ". Klik op 'Voltooien' om te bevestigen."); + } +} + +f_deselectPreviousSelect(); +/*ALCODEEND*/} + +double f_addCustomGCTransformerSelection(double clickx,double clicky) +{/*ALCODESTART::1781677735606*/ +// --- PHASE 2: Choose transformer to connect to --- +GridNode clickedGN = null; +for (GridNode GN : energyModel.pop_gridNodes) { + if (GN.gisRegion != null && GN.gisRegion.contains(clickx, clicky) && GN.gisRegion.isVisible()) { + clickedGN = GN; + break; + } +} + +if (clickedGN != null) { + if (v_addCustomGCType == OL_EnergyAssetType.PHOTOVOLTAIC){ + f_addCustomSolarfarmGC(clickedGN); + } else if (v_addCustomGCType == OL_EnergyAssetType.WINDMILL){ + f_addCustomWindfarmGC(clickedGN); + } else if (v_addCustomGCType == OL_EnergyAssetType.STORAGE_ELECTRIC){ + f_addCustomGridBatteryGC(clickedGN); + } + // Clean up coordinate temporary dots, state variables, lists + f_stopCustomGCCreation(); + + return; +} else { + traceln("Please click on a valid transformer (GridNode)."); + return; +} +/*ALCODEEND*/} + +double f_removeCustomGCSelection(double clickx,double clicky) +{/*ALCODESTART::1781678055851*/ +// Group all GIS objects to check for the click +List allGISObjects = new ArrayList<>(); +for(GIS_Building b : energyModel.pop_GIS_Buildings) { + allGISObjects.add(b); +} +for(GIS_Object object : energyModel.pop_GIS_Objects){ + allGISObjects.add(object); +} +for (GIS_Object GISObject : allGISObjects) { + if (GISObject.gisRegion != null && GISObject.gisRegion.contains(clickx, clicky) && GISObject.gisRegion.isVisible()) { + if (GISObject.c_containedGridConnections.size() > 0) { + GridConnection gc = GISObject.c_containedGridConnections.get(0); + + // Only allow deletion of specific energy asset types + if (gc instanceof GCEnergyProduction || gc instanceof GCGridBattery) { + f_removeCustomGC(gc); + b_removeCustomGC = false; // Reset mode after deletion + traceln("Removed the energy asset: " + gc.p_gridConnectionID); + return; + } + } + } +} +// If the user clicks elsewhere, cancel the deletion mode +b_removeCustomGC = false; +traceln("Deletion mode cancelled."); +/*ALCODEEND*/} + +GISRegion f_drawTempCoordinateDot(double lat,double lon) +{/*ALCODESTART::1781682728045*/ +//Draw a small dot (2m x 2m square = 4m²) on map at clicked coordinates +double[] polyCoords = f_calculateSquareCoordinates(lat, lon, 25); + +GISRegion dot = f_createGISObject(polyCoords); +dot.setFillColor(Color.RED); +dot.setLineColor(Color.WHITE); +dot.setLineWidth(1.0); + +return dot; +/*ALCODEEND*/} + +double f_stopCustomGCCreation() +{/*ALCODESTART::1781688506944*/ +// Clean up temporary dot markers, preview polygon, and coordinates list +f_resetCustomGCCreation(); + +// Reset state variables +b_customGCPolygonCreated = false; +b_addCustomGC = false; +v_addCustomGCType = null; +b_removeCustomGC = false; + +// Hide forced click screen, if needed +f_setForcedClickScreenVisibility(false); +f_setForcedClickScreenTextBoxes("", new Color(255, 255, 255), new Color(0, 0, 0), "", new Color(255, 255, 255), new Color(0, 0, 0)); +/*ALCODEEND*/} + +double[] f_calculateCustomPolygonCoordinates(List> coordinateList) +{/*ALCODESTART::1781694164851*/ +int size = coordinateList.size(); +double[] polyCoords = new double[size * 2]; +for (int i = 0; i < size; i++) { + Pair p = coordinateList.get(i); + polyCoords[2 * i] = p.getFirst(); + polyCoords[2 * i + 1] = p.getSecond(); +} +return polyCoords; +/*ALCODEEND*/} + +double f_setForcedClickScreenTitleText(String forcedClickScreenText) +{/*ALCODESTART::1781861106301*/ +txt_forcedClickTitle.setText(forcedClickScreenText); +gr_forcedClickTitleTxt.setVisible(false); + +if(!txt_forcedClickTitle.getText().equals("")){ + f_adjustTextToFitRectangle(txt_forcedClickTitle, rect_forcedClickTitle, 25.0, 48); + gr_forcedClickTitleTxt.setVisible(true); +} +/*ALCODEEND*/} + +double f_setForcedClickScreenTitleBackgroundColor(Color fillColor,Color lineColor) +{/*ALCODESTART::1781863042854*/ +rect_forcedClickTitle.setFillColor(fillColor); +rect_forcedClickTitle.setLineColor(lineColor); +/*ALCODEEND*/} + +double f_setForcedClickScreenTextBoxes(String titleText,Color titleBackgroundFillColor,Color titleBackgroundLineColor,String messageText,Color messageBackgroundFillColor,Color messageBackgroundLineColor) +{/*ALCODESTART::1782123595562*/ +f_setForcedClickScreenTitleText(titleText); +f_setForcedClickScreenTitleBackgroundColor(titleBackgroundFillColor, titleBackgroundLineColor); +f_setForcedClickScreenMessageText(messageText); +f_setForcedClickScreenMessageBackgroundColor(messageBackgroundFillColor, messageBackgroundLineColor); +/*ALCODEEND*/} + +double f_setForcedClickScreenMessageBackgroundColor(Color fillColor,Color lineColor) +{/*ALCODESTART::1782123808280*/ +rect_selectText.setFillColor(fillColor); +rect_selectText.setLineColor(lineColor); +/*ALCODEEND*/} + +double f_adjustTextToFitRectangle(ShapeText textShape,ShapeRectangle rectShape,double margin,int defaultFontSize) +{/*ALCODESTART::1782206478165*/ +if (textShape == null || textShape.getText() == null || textShape.getText().isEmpty() || rectShape == null) { + return; +} + +// Resetting of default fontsize to prevent overflow +Font font = textShape.getFont(); +if (font != null) { + Font defaultFont = font.deriveFont((float) defaultFontSize); + textShape.setFont(defaultFont); +} + +double rectWidth = rectShape.getWidth(); +double rectHeight = rectShape.getHeight(); +double rectX = rectShape.getX(); +double rectY = rectShape.getY(); +double targetWidth = rectWidth - 2 * margin; + + +String text = textShape.getText(); +Font currentFont = textShape.getFont(); +BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); +Graphics2D g2d = img.createGraphics(); +try { + FontMetrics fm = g2d.getFontMetrics(currentFont); + + // Find longest line of text (if ShapeText contains multiple sentences) + String[] lines = text.split("\n"); + double maxLineWidth = 0; + for (String line : lines) { + double lineWidth = fm.stringWidth(line); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + } + + // Scale down the font if it exceeds the maximum target width + if (maxLineWidth > targetWidth) { + double scaleFactor = targetWidth / maxLineWidth; + double newSizeDouble = defaultFontSize * scaleFactor; + float newSize = (float) Math.floor(newSizeDouble); + + Font scaledFont = currentFont.deriveFont(newSize); + textShape.setFont(scaledFont); + fm = g2d.getFontMetrics(scaledFont); + } + + double textHeight = lines.length * fm.getHeight(); + double newY = rectY + 0.5*(rectHeight - textHeight); + textShape.setY(newY); + +} finally { + g2d.dispose(); +} +/*ALCODEEND*/} + +double f_resetCustomGCCreation() +{/*ALCODESTART::1782220715178*/ +// Clean up temporary dot markers +for (GISRegion dot : c_tempSavedDots) { + if (dot != null) { + dot.remove(); + } +} +c_tempSavedDots.clear(); + +// Clean up preview polygon +if (previewGISRegion != null) { + previewGISRegion.remove(); + previewGISRegion = null; +} + +// Clear coordinates list +c_tempSavedCoordinates.clear(); +/*ALCODEEND*/} + +boolean f_checkCustomPolygonSelfIntersection() +{/*ALCODESTART::1782222661974*/ +int n = c_tempSavedCoordinates.size(); +if (n < 4) { + return false; // A polygon with less than 4 vertices cannot self-intersect +} +// Helper class for robust geometric computations +class GeoUtil { + // Checks if point q lies on line segment pr + boolean onSegment(double px, double py, double qx, double qy, double rx, double ry) { + return qx <= Math.max(px, rx) && qx >= Math.min(px, rx) && + qy <= Math.max(py, ry) && qy >= Math.min(py, ry); + } + // Finds the orientation of ordered triplet (p, q, r). + // Returns: + // 0 -> p, q and r are collinear + // 1 -> Clockwise + // 2 -> Counterclockwise + int orientation(double px, double py, double qx, double qy, double rx, double ry) { + double val = (qy - py) * (rx - qx) - (qx - px) * (ry - qy); + if (Math.abs(val) < 1e-9) return 0; // Collinear within precision threshold + return (val > 0) ? 1 : 2; + } + // Checks if line segment p1q1 and p2q2 intersect + boolean doIntersect(double p1x, double p1y, double q1x, double q1y, + double p2x, double p2y, double q2x, double q2y) { + int o1 = orientation(p1x, p1y, q1x, q1y, p2x, p2y); + int o2 = orientation(p1x, p1y, q1x, q1y, q2x, q2y); + int o3 = orientation(p2x, p2y, q2x, q2y, p1x, p1y); + int o4 = orientation(p2x, p2y, q2x, q2y, q1x, q1y); + // General Case: Segments cross each other + if (o1 != o2 && o3 != o4) return true; + // Special Cases (Collinear segments overlapping) + if (o1 == 0 && onSegment(p1x, p1y, p2x, p2y, q1x, q1y)) return true; + if (o2 == 0 && onSegment(p1x, p1y, q2x, q2y, q1x, q1y)) return true; + if (o3 == 0 && onSegment(p2x, p2y, p1x, p1y, q2x, q2y)) return true; + if (o4 == 0 && onSegment(p2x, p2y, q1x, q1y, q2x, q2y)) return true; + return false; + } +} + +GeoUtil geo = new GeoUtil(); +// Verify every pair of non-adjacent edges for intersection +for (int i = 0; i < n; i++) { + double p1x = c_tempSavedCoordinates.get(i).getFirst(); + double p1y = c_tempSavedCoordinates.get(i).getSecond(); + double q1x = c_tempSavedCoordinates.get((i + 1) % n).getFirst(); + double q1y = c_tempSavedCoordinates.get((i + 1) % n).getSecond(); + for (int j = i + 2; j < n; j++) { + // Skip adjacent edges (they naturally touch at their common vertex) + if (i == 0 && j == n - 1) { + continue; + } + double p2x = c_tempSavedCoordinates.get(j).getFirst(); + double p2y = c_tempSavedCoordinates.get(j).getSecond(); + double q2x = c_tempSavedCoordinates.get((j + 1) % n).getFirst(); + double q2y = c_tempSavedCoordinates.get((j + 1) % n).getSecond(); + if (geo.doIntersect(p1x, p1y, q1x, q1y, p2x, p2y, q2x, q2y)) { + return true; // Self-intersection detected + } + } +} +return false; // Polygon is valid +/*ALCODEEND*/} + diff --git a/_alp/Agents/Zero_Interface/Code/Functions.xml b/_alp/Agents/Zero_Interface/Code/Functions.xml index fc67cf6..122c980 100644 --- a/_alp/Agents/Zero_Interface/Code/Functions.xml +++ b/_alp/Agents/Zero_Interface/Code/Functions.xml @@ -1118,9 +1118,9 @@ VOID double 1742300624199 - - 140 - -880 + + 160 + -2240