From 76a21c59798c9909339878903588307e2768cf22 Mon Sep 17 00:00:00 2001 From: DavidArmahJr <111519747+DavidArmahJr@users.noreply.github.com> Date: Fri, 24 May 2024 11:50:16 -0400 Subject: [PATCH 1/6] Update resilientCommunity.py --- omf/models/resilientCommunity.py | 46 +++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/omf/models/resilientCommunity.py b/omf/models/resilientCommunity.py index a137cb3d8..7753bb85c 100644 --- a/omf/models/resilientCommunity.py +++ b/omf/models/resilientCommunity.py @@ -651,6 +651,26 @@ def getPercentile(loads, columnName): for i, (k,v) in enumerate(loads.items()): loads[k][new_str] = round(result[i],2) +def coordCheck(long, lat, latlonList): + point = Point(long, lat) + + for k,v in latlonList.items(): + coords, geoType = v[0], v[1] + + ## Need to figure out when we are dealing with a multipolygon or polygon list + + if geoType == 'Polygon': + poly = Polygon(coords) + + if poly.intersects(point): + return k + else: + for i in coords: + if poly.intersects(point): + return k + + return '' + def getDownLineLoadsEquipment(pathToOmd,nriGeoJson, equipmentList): ''' @@ -665,6 +685,7 @@ def getDownLineLoadsEquipment(pathToOmd,nriGeoJson, equipmentList): tracts = {} tractData = [] geos = [] + lon_lat = {} cols = ['TRACT','BUILDVALUE','AGRIVALUE','EAL_VALT','EAL_VALB','EAL_VALP','EAL_VALA','SOVI_SCORE','SOVI_RATNG','RESL_RATNG','RESL_VALUE','AVLN_AFREQ','CFLD_AFREQ','CWAV_AFREQ','DRGT_AFREQ','ERQK_AFREQ','HAIL_AFREQ','HWAV_AFREQ','HRCN_AFREQ','ISTM_AFREQ','LNDS_AFREQ','LTNG_AFREQ','RFLD_AFREQ','SWND_AFREQ','TRND_AFREQ','TSUN_AFREQ','VLCN_AFREQ','WFIR_AFREQ','WNTW_AFREQ'] for ob in omd.get('tree', {}).values(): obType = ob['object'] @@ -685,11 +706,20 @@ def getDownLineLoadsEquipment(pathToOmd,nriGeoJson, equipmentList): loads[key]["base crit score"]= ((math.sqrt((kw * kw) + (kvar * kvar) ))/ (5)) * 4 - - lat = float(ob['latitude']) long = float(ob['longitude']) - - tract = findCensusTract(lat, long) + lat = float(ob['latitude']) + + if lon_lat: + check = coordCheck(long,lat, lon_lat) + if check: + svi_score = round(float(tracts.get(tract)['SOVI_SCORE']),2) + loads[key]["community crit score"] = round((((math.sqrt((kw * kw) + (kvar * kvar) ))/ (5)) * 4) * svi_score,2) + loads[key]['SOVI_SCORE'] = svi_score + continue + else: + tract = findCensusTract(lat,long) + else: + tract = findCensusTract(lat, long) # if api call failed. repeat it while tract == None: @@ -725,13 +755,21 @@ def getDownLineLoadsEquipment(pathToOmd,nriGeoJson, equipmentList): tracts[tractID] = i['properties'] if (i['geometry']['type'] == 'MultiPolygon'): + lon_lat_list = [] for j in i['geometry']['coordinates']: + ## changes values so make copy + lon_lat_list.append(transform(j.copy())) geos.append(j) tractData.append(vals) + lon_lat[tract] = (lon_lat_list,'MultiPolygon') else: + # changes values so make copy + lon_lat[tract] = (transform(i['geometry']['coordinates'][0].copy()), 'Polygon') geos.append(i['geometry']['coordinates'][0]) tractData.append(vals) + + break From 809a0adc379752b8d13e85ba9378c5735f7959ad Mon Sep 17 00:00:00 2001 From: ASC95 <16790624+ASC95@users.noreply.github.com> Date: Sat, 25 May 2024 20:37:30 -0400 Subject: [PATCH 2/6] fixed circuit editor bug due to loading the LeafletLayer class before the marker cluster plugin --- .../v3/lib/MarkerCluster.Default.css | 60 ------------------- .../geoJsonMap/v3/lib/MarkerCluster.css | 14 ----- .../v3/lib/leaflet.markercluster.js | 3 - omf/templates/geoJson.html | 8 +-- omf/templates/geoJson_offline.html | 4 ++ 5 files changed, 8 insertions(+), 81 deletions(-) delete mode 100644 omf/static/geoJsonMap/v3/lib/MarkerCluster.Default.css delete mode 100644 omf/static/geoJsonMap/v3/lib/MarkerCluster.css delete mode 100644 omf/static/geoJsonMap/v3/lib/leaflet.markercluster.js diff --git a/omf/static/geoJsonMap/v3/lib/MarkerCluster.Default.css b/omf/static/geoJsonMap/v3/lib/MarkerCluster.Default.css deleted file mode 100644 index bbc8c9fb0..000000000 --- a/omf/static/geoJsonMap/v3/lib/MarkerCluster.Default.css +++ /dev/null @@ -1,60 +0,0 @@ -.marker-cluster-small { - background-color: rgba(181, 226, 140, 0.6); - } -.marker-cluster-small div { - background-color: rgba(110, 204, 57, 0.6); - } - -.marker-cluster-medium { - background-color: rgba(241, 211, 87, 0.6); - } -.marker-cluster-medium div { - background-color: rgba(240, 194, 12, 0.6); - } - -.marker-cluster-large { - background-color: rgba(253, 156, 115, 0.6); - } -.marker-cluster-large div { - background-color: rgba(241, 128, 23, 0.6); - } - - /* IE 6-8 fallback colors */ -.leaflet-oldie .marker-cluster-small { - background-color: rgb(181, 226, 140); - } -.leaflet-oldie .marker-cluster-small div { - background-color: rgb(110, 204, 57); - } - -.leaflet-oldie .marker-cluster-medium { - background-color: rgb(241, 211, 87); - } -.leaflet-oldie .marker-cluster-medium div { - background-color: rgb(240, 194, 12); - } - -.leaflet-oldie .marker-cluster-large { - background-color: rgb(253, 156, 115); - } -.leaflet-oldie .marker-cluster-large div { - background-color: rgb(241, 128, 23); -} - -.marker-cluster { - background-clip: padding-box; - border-radius: 20px; - } -.marker-cluster div { - width: 30px; - height: 30px; - margin-left: 5px; - margin-top: 5px; - - text-align: center; - border-radius: 15px; - font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; - } -.marker-cluster span { - line-height: 30px; - } \ No newline at end of file diff --git a/omf/static/geoJsonMap/v3/lib/MarkerCluster.css b/omf/static/geoJsonMap/v3/lib/MarkerCluster.css deleted file mode 100644 index c60d71b7a..000000000 --- a/omf/static/geoJsonMap/v3/lib/MarkerCluster.css +++ /dev/null @@ -1,14 +0,0 @@ -.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { - -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in; - -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in; - -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in; - transition: transform 0.3s ease-out, opacity 0.3s ease-in; -} - -.leaflet-cluster-spider-leg { - /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */ - -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in; - -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in; - -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in; - transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in; -} diff --git a/omf/static/geoJsonMap/v3/lib/leaflet.markercluster.js b/omf/static/geoJsonMap/v3/lib/leaflet.markercluster.js deleted file mode 100644 index 67c52dcd6..000000000 --- a/omf/static/geoJsonMap/v3/lib/leaflet.markercluster.js +++ /dev/null @@ -1,3 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.Leaflet=e.Leaflet||{},e.Leaflet.markercluster=e.Leaflet.markercluster||{}))}(this,function(e){"use strict";var t=L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animate:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:.5},chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),this._featureGroup=L.featureGroup(),this._featureGroup.addEventParent(this),this._nonPointGroup=L.featureGroup(),this._nonPointGroup.addEventParent(this),this._inZoomAnimation=0,this._needsClustering=[],this._needsRemoving=[],this._currentShownBounds=null,this._queue=[],this._childMarkerEventHandlers={dragstart:this._childMarkerDragStart,move:this._childMarkerMoved,dragend:this._childMarkerDragEnd};var t=L.DomUtil.TRANSITION&&this.options.animate;L.extend(this,t?this._withAnimation:this._noAnimation),this._markerCluster=t?L.MarkerCluster:L.MarkerClusterNonAnimated},addLayer:function(e){if(e instanceof L.LayerGroup)return this.addLayers([e]);if(!e.getLatLng)return this._nonPointGroup.addLayer(e),this.fire("layeradd",{layer:e}),this;if(!this._map)return this._needsClustering.push(e),this.fire("layeradd",{layer:e}),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom),this.fire("layeradd",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons();var t=e,i=this._zoom;if(e.__parent)for(;t.__parent._zoom>=i;)t=t.__parent;return this._currentShownBounds.contains(t.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,t):this._animationAddLayerNonAnimated(e,t)),this},removeLayer:function(e){return e instanceof L.LayerGroup?this.removeLayers([e]):e.getLatLng?this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),this.fire("layerremove",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),e.off(this._childMarkerEventHandlers,this),this._featureGroup.hasLayer(e)&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow()),this):this:(!this._arraySplice(this._needsClustering,e)&&this.hasLayer(e)&&this._needsRemoving.push({layer:e,latlng:e._latlng}),this.fire("layerremove",{layer:e}),this):(this._nonPointGroup.removeLayer(e),this.fire("layerremove",{layer:e}),this)},addLayers:function(e,t){if(!L.Util.isArray(e))return this.addLayer(e);var i,n=this._featureGroup,r=this._nonPointGroup,s=this.options.chunkedLoading,o=this.options.chunkInterval,a=this.options.chunkProgress,h=e.length,l=0,u=!0;if(this._map){var _=(new Date).getTime(),d=L.bind(function(){for(var c=(new Date).getTime();h>l;l++){if(s&&0===l%200){var p=(new Date).getTime()-c;if(p>o)break}if(i=e[l],i instanceof L.LayerGroup)u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length;else if(i.getLatLng){if(!this.hasLayer(i)&&(this._addLayer(i,this._maxZoom),t||this.fire("layeradd",{layer:i}),i.__parent&&2===i.__parent.getChildCount())){var f=i.__parent.getAllChildMarkers(),m=f[0]===i?f[1]:f[0];n.removeLayer(m)}}else r.addLayer(i),t||this.fire("layeradd",{layer:i})}a&&a(l,h,(new Date).getTime()-_),l===h?(this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else for(var c=this._needsClustering;h>l;l++)i=e[l],i instanceof L.LayerGroup?(u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length):i.getLatLng?this.hasLayer(i)||c.push(i):r.addLayer(i);return this},removeLayers:function(e){var t,i,n=e.length,r=this._featureGroup,s=this._nonPointGroup,o=!0;if(!this._map){for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):(this._arraySplice(this._needsClustering,i),s.removeLayer(i),this.hasLayer(i)&&this._needsRemoving.push({layer:i,latlng:i._latlng}),this.fire("layerremove",{layer:i}));return this}if(this._unspiderfy){this._unspiderfy();var a=e.slice(),h=n;for(t=0;h>t;t++)i=a[t],i instanceof L.LayerGroup?(this._extractNonGroupLayers(i,a),h=a.length):this._unspiderfyLayer(i)}for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):i.__parent?(this._removeLayer(i,!0,!0),this.fire("layerremove",{layer:i}),r.hasLayer(i)&&(r.removeLayer(i),i.clusterShow&&i.clusterShow())):(s.removeLayer(i),this.fire("layerremove",{layer:i}));return this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},clearLayers:function(){return this._map||(this._needsClustering=[],this._needsRemoving=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(e){e.off(this._childMarkerEventHandlers,this),delete e.__parent},this),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;this._topClusterLevel&&e.extend(this._topClusterLevel._bounds);for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e.extend(this._nonPointGroup.getBounds()),e},eachLayer:function(e,t){var i,n,r,s=this._needsClustering.slice(),o=this._needsRemoving;for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(s),n=s.length-1;n>=0;n--){for(i=!0,r=o.length-1;r>=0;r--)if(o[r].layer===s[n]){i=!1;break}i&&e.call(t,s[n])}this._nonPointGroup.eachLayer(e,t)},getLayers:function(){var e=[];return this.eachLayer(function(t){e.push(t)}),e},getLayer:function(e){var t=null;return e=parseInt(e,10),this.eachLayer(function(i){L.stamp(i)===e&&(t=i)}),t},hasLayer:function(e){if(!e)return!1;var t,i=this._needsClustering;for(t=i.length-1;t>=0;t--)if(i[t]===e)return!0;for(i=this._needsRemoving,t=i.length-1;t>=0;t--)if(i[t].layer===e)return!1;return!(!e.__parent||e.__parent._group!==this)||this._nonPointGroup.hasLayer(e)},zoomToShowLayer:function(e,t){"function"!=typeof t&&(t=function(){});var i=function(){!e._icon&&!e.__parent._icon||this._inZoomAnimation||(this._map.off("moveend",i,this),this.off("animationend",i,this),e._icon?t():e.__parent._icon&&(this.once("spiderfied",t,this),e.__parent.spiderfy()))};e._icon&&this._map.getBounds().contains(e.getLatLng())?t():e.__parent._zoomt;t++)n=this._needsRemoving[t],n.newlatlng=n.layer._latlng,n.layer._latlng=n.latlng;for(t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],this._removeLayer(n.layer,!0),n.layer._latlng=n.newlatlng;this._needsRemoving=[],this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i,!0)},onRemove:function(e){e.off("zoomend",this._zoomEnd,this),e.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),delete this._maxLat,this._hideCoverage(),this._featureGroup.remove(),this._nonPointGroup.remove(),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(e){for(var t=e;t&&!t._icon;)t=t.__parent;return t||null},_arraySplice:function(e,t){for(var i=e.length-1;i>=0;i--)if(e[i]===t)return e.splice(i,1),!0},_removeFromGridUnclustered:function(e,t){for(var i=this._map,n=this._gridUnclustered,r=Math.floor(this._map.getMinZoom());t>=r&&n[t].removeObject(e,i.project(e.getLatLng(),t));t--);},_childMarkerDragStart:function(e){e.target.__dragStart=e.target._latlng},_childMarkerMoved:function(e){if(!this._ignoreMove&&!e.target.__dragStart){var t=e.target._popup&&e.target._popup.isOpen();this._moveChild(e.target,e.oldLatLng,e.latlng),t&&e.target.openPopup()}},_moveChild:function(e,t,i){e._latlng=t,this.removeLayer(e),e._latlng=i,this.addLayer(e)},_childMarkerDragEnd:function(e){var t=e.target.__dragStart;delete e.target.__dragStart,t&&this._moveChild(e.target,t,e.target._latlng)},_removeLayer:function(e,t,i){var n=this._gridClusters,r=this._gridUnclustered,s=this._featureGroup,o=this._map,a=Math.floor(this._map.getMinZoom());t&&this._removeFromGridUnclustered(e,this._maxZoom);var h,l=e.__parent,u=l._markers;for(this._arraySplice(u,e);l&&(l._childCount--,l._boundsNeedUpdate=!0,!(l._zoomt?"small":100>t?"medium":"large",new L.DivIcon({html:"
"+t+"
",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=this._map,t=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(t||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),e.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(e){for(var t=e.layer,i=t;1===i._childClusters.length;)i=i._childClusters[0];i._zoom===this._maxZoom&&i._childCount===t._childCount&&this.options.spiderfyOnMaxZoom?t.spiderfy():this.options.zoomToBoundsOnClick&&t.zoomToBounds(),e.originalEvent&&13===e.originalEvent.keyCode&&this._map._container.focus()},_showCoverage:function(e){var t=this._map;this._inZoomAnimation||(this._shownPolygon&&t.removeLayer(this._shownPolygon),e.layer.getChildCount()>2&&e.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(e.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(e||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),t&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,Math.round(this._map._zoom),e),this._currentShownBounds=e}},_generateInitialClusters:function(){var e=Math.ceil(this._map.getMaxZoom()),t=Math.floor(this._map.getMinZoom()),i=this.options.maxClusterRadius,n=i;"function"!=typeof i&&(n=function(){return i}),null!==this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var r=e;r>=t;r--)this._gridClusters[r]=new L.DistanceGrid(n(r)),this._gridUnclustered[r]=new L.DistanceGrid(n(r));this._topClusterLevel=new this._markerCluster(this,t-1)},_addLayer:function(e,t){var i,n,r=this._gridClusters,s=this._gridUnclustered,o=Math.floor(this._map.getMinZoom());for(this.options.singleMarkerMode&&this._overrideMarkerIcon(e),e.on(this._childMarkerEventHandlers,this);t>=o;t--){i=this._map.project(e.getLatLng(),t);var a=r[t].getNearObject(i);if(a)return a._addChild(e),e.__parent=a,void 0;if(a=s[t].getNearObject(i)){var h=a.__parent;h&&this._removeLayer(a,!1);var l=new this._markerCluster(this,t,a,e);r[t].addObject(l,this._map.project(l._cLatLng,t)),a.__parent=l,e.__parent=l;var u=l;for(n=t-1;n>h._zoom;n--)u=new this._markerCluster(this,n,u),r[n].addObject(u,this._map.project(a.getLatLng(),n));return h._addChild(u),this._removeFromGridUnclustered(a,t),void 0}s[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel},_refreshClustersIcons:function(){this._featureGroup.eachLayer(function(e){e instanceof L.MarkerCluster&&e._iconNeedsUpdate&&e._updateIcon()})},_enqueue:function(e){this._queue.push(e),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var e=0;ee?(this._animationStart(),this._animationZoomOut(this._zoom,e)):this._moveEnd()},_getExpandedVisibleBounds:function(){return this.options.removeOutsideVisibleBounds?L.Browser.mobile?this._checkBoundsMaxLat(this._map.getBounds()):this._checkBoundsMaxLat(this._map.getBounds().pad(1)):this._mapBoundsInfinite},_checkBoundsMaxLat:function(e){var t=this._maxLat;return void 0!==t&&(e.getNorth()>=t&&(e._northEast.lat=1/0),e.getSouth()<=-t&&(e._southWest.lat=-1/0)),e},_animationAddLayerNonAnimated:function(e,t){if(t===e)this._featureGroup.addLayer(e);else if(2===t._childCount){t._addToMap();var i=t.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else t._updateIcon()},_extractNonGroupLayers:function(e,t){var i,n=e.getLayers(),r=0;for(t=t||[];r=0;i--)o=h[i],n.contains(o._latlng)||r.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,t),r.eachLayer(function(e){e instanceof L.MarkerCluster||!e._icon||e.clusterShow()}),this._topClusterLevel._recursively(n,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),this._ignoreMove=!1,this._enqueue(function(){this._topClusterLevel._recursively(n,e,s,function(e){r.removeLayer(e),e.clusterShow()}),this._animationEnd()})},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){var i=this,n=this._featureGroup;n.addLayer(e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.clusterHide(),this._enqueue(function(){n.removeLayer(e),e.clusterShow(),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(t,this._map.getMaxZoom(),this._zoom)))}},_animationZoomOutSingle:function(e,t,i){var n=this._getExpandedVisibleBounds(),r=Math.floor(this._map.getMinZoom());e._recursivelyAnimateChildrenInAndAddSelfToMap(n,r,t+1,i);var s=this;this._forceLayout(),e._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===e._childCount){var o=e._markers[0];this._ignoreMove=!0,o.setLatLng(o.getLatLng()),this._ignoreMove=!1,o.clusterShow&&o.clusterShow()}else e._recursively(n,i,r,function(e){e._recursivelyRemoveChildrenFromMap(n,r,t+1)});s._animationEnd()})},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}),L.markerClusterGroup=function(e){return new L.MarkerClusterGroup(e)};var i=L.MarkerCluster=L.Marker.extend({options:L.Icon.prototype.options,initialize:function(e,t,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this,pane:e.options.clusterPane}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(e,t){e=e||[];for(var i=this._childClusters.length-1;i>=0;i--)this._childClusters[i].getAllChildMarkers(e);for(var n=this._markers.length-1;n>=0;n--)t&&this._markers[n].__dragStart||e.push(this._markers[n]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(e){for(var t,i=this._childClusters.slice(),n=this._group._map,r=n.getBoundsZoom(this._bounds),s=this._zoom+1,o=n.getZoom();i.length>0&&r>s;){s++;var a=[];for(t=0;ts?this._group._map.setView(this._latlng,s):o>=r?this._group._map.setView(this._latlng,o+1):this._group._map.fitBounds(this._bounds,e)},getBounds:function(){var e=new L.LatLngBounds;return e.extend(this._bounds),e},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._setClusterCenter(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_setClusterCenter:function(e){this._cLatLng||(this._cLatLng=e._cLatLng||e._latlng)},_resetBounds:function(){var e=this._bounds;e._southWest&&(e._southWest.lat=1/0,e._southWest.lng=1/0),e._northEast&&(e._northEast.lat=-1/0,e._northEast.lng=-1/0)},_recalculateBounds:function(){var e,t,i,n,r=this._markers,s=this._childClusters,o=0,a=0,h=this._childCount;if(0!==h){for(this._resetBounds(),e=0;e=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())},function(e){var i,n,r=e._childClusters;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,i,n){this._recursively(e,n,t,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),i),r._isSingleParent()&&i-1===n?(r.clusterShow(),r._recursivelyRemoveChildrenFromMap(e,t,i)):r.clusterHide(),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,this._group._map.getMinZoom(),t,null,function(e){e.clusterShow()})},_recursivelyAddChildrenToMap:function(e,t,i){this._recursively(i,this._group._map.getMinZoom()-1,t,function(n){if(t!==n._zoom)for(var r=n._markers.length-1;r>=0;r--){var s=n._markers[r];i.contains(s._latlng)&&(e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.clusterHide&&s.clusterHide()),n._group._featureGroup.addLayer(s))}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var i=this._markers[t];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(e-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,i,n){var r,s;this._recursively(e,t-1,i-1,function(e){for(s=e._markers.length-1;s>=0;s--)r=e._markers[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())},function(e){for(s=e._childClusters.length-1;s>=0;s--)r=e._childClusters[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())})},_recursively:function(e,t,i,n,r){var s,o,a=this._childClusters,h=this._zoom;if(h>=t&&(n&&n(this),r&&h===i&&r(this)),t>h||i>h)for(s=a.length-1;s>=0;s--)o=a[s],o._boundsNeedUpdate&&o._recalculateBounds(),e.intersects(o._bounds)&&o._recursively(e,t,i,n,r)},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}});L.Marker.include({clusterHide:function(){var e=this.options.opacity;return this.setOpacity(0),this.options.opacity=e,this},clusterShow:function(){return this.setOpacity(this.options.opacity)}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var i=this._getCoord(t.x),n=this._getCoord(t.y),r=this._grid,s=r[n]=r[n]||{},o=s[i]=s[i]||[],a=L.Util.stamp(e);this._objectPoint[a]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var i,n,r=this._getCoord(t.x),s=this._getCoord(t.y),o=this._grid,a=o[s]=o[s]||{},h=a[r]=a[r]||[];for(delete this._objectPoint[L.Util.stamp(e)],i=0,n=h.length;n>i;i++)if(h[i]===e)return h.splice(i,1),1===n&&delete a[r],!0},eachObject:function(e,t){var i,n,r,s,o,a,h,l=this._grid;for(i in l){o=l[i];for(n in o)for(a=o[n],r=0,s=a.length;s>r;r++)h=e.call(t,a[r]),h&&(r--,s--)}},getNearObject:function(e){var t,i,n,r,s,o,a,h,l=this._getCoord(e.x),u=this._getCoord(e.y),_=this._objectPoint,d=this._sqCellSize,c=null;for(t=u-1;u+1>=t;t++)if(r=this._grid[t])for(i=l-1;l+1>=i;i++)if(s=r[i])for(n=0,o=s.length;o>n;n++)a=s[n],h=this._sqDist(_[L.Util.stamp(a)],e),(d>h||d>=h&&null===c)&&(d=h,c=a);return c},_getCoord:function(e){var t=Math.floor(e/this._cellSize);return isFinite(t)?t:e},_sqDist:function(e,t){var i=t.x-e.x,n=t.y-e.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(e,t){var i=t[1].lat-t[0].lat,n=t[0].lng-t[1].lng;return n*(e.lat-t[0].lat)+i*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var i,n,r,s=0,o=null,a=[];for(i=t.length-1;i>=0;i--)n=t[i],r=this.getDistant(n,e),r>0&&(a.push(n),r>s&&(s=r,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(e,t){var i=[],n=this.findMostDistantPointFromBaseLine(e,t);return n.maxPoint?(i=i.concat(this.buildConvexHull([e[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,e[1]],n.newPoints))):[e[0]]},getConvexHull:function(e){var t,i=!1,n=!1,r=!1,s=!1,o=null,a=null,h=null,l=null,u=null,_=null;for(t=e.length-1;t>=0;t--){var d=e[t];(i===!1||d.lat>i)&&(o=d,i=d.lat),(n===!1||d.latr)&&(h=d,r=d.lng),(s===!1||d.lng=0;t--)e=i[t].getLatLng(),n.push(e);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:0,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var e,t=this.getAllChildMarkers(null,!0),i=this._group,n=i._map,r=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,t.length>=this._circleSpiralSwitchover?e=this._generatePointsSpiral(t.length,r):(r.y+=10,e=this._generatePointsCircle(t.length,r)),this._animationSpiderfy(t,e)}},unspiderfy:function(e){this._group._inZoomAnimation||(this._animationUnspiderfy(e),this._group._spiderfied=null)},_generatePointsCircle:function(e,t){var i,n,r=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),s=r/this._2PI,o=this._2PI/e,a=[];for(s=Math.max(s,35),a.length=e,i=0;e>i;i++)n=this._circleStartAngle+i*o,a[i]=new L.Point(t.x+s*Math.cos(n),t.y+s*Math.sin(n))._round();return a},_generatePointsSpiral:function(e,t){var i,n=this._group.options.spiderfyDistanceMultiplier,r=n*this._spiralLengthStart,s=n*this._spiralFootSeparation,o=n*this._spiralLengthFactor*this._2PI,a=0,h=[];for(h.length=e,i=e;i>=0;i--)e>i&&(h[i]=new L.Point(t.x+r*Math.cos(a),t.y+r*Math.sin(a))._round()),a+=s/r+5e-4*i,r+=o/a;return h},_noanimationUnspiderfy:function(){var e,t,i=this._group,n=i._map,r=i._featureGroup,s=this.getAllChildMarkers(null,!0);for(i._ignoreMove=!0,this.setOpacity(1),t=s.length-1;t>=0;t--)e=s[t],r.removeLayer(e),e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng),e.setZIndexOffset&&e.setZIndexOffset(0),e._spiderLeg&&(n.removeLayer(e._spiderLeg),delete e._spiderLeg);i.fire("unspiderfied",{cluster:this,markers:s}),i._ignoreMove=!1,i._spiderfied=null}}),L.MarkerClusterNonAnimated=L.MarkerCluster.extend({_animationSpiderfy:function(e,t){var i,n,r,s,o=this._group,a=o._map,h=o._featureGroup,l=this._group.options.spiderLegPolylineOptions;for(o._ignoreMove=!0,i=0;i=0;i--)a=u.layerPointToLatLng(t[i]),n=e[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(a),n.clusterShow&&n.clusterShow(),p&&(r=n._spiderLeg,s=r._path,s.style.strokeDashoffset=0,r.setStyle({opacity:m}));this.setOpacity(.3),l._ignoreMove=!1,setTimeout(function(){l._animationEnd(),l.fire("spiderfied",{cluster:h,markers:e})},200)},_animationUnspiderfy:function(e){var t,i,n,r,s,o,a=this,h=this._group,l=h._map,u=h._featureGroup,_=e?l._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):l.latLngToLayerPoint(this._latlng),d=this.getAllChildMarkers(null,!0),c=L.Path.SVG;for(h._ignoreMove=!0,h._animationStart(),this.setOpacity(1),i=d.length-1;i>=0;i--)t=d[i],t._preSpiderfyLatlng&&(t.closePopup(),t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng,o=!0,t._setPos&&(t._setPos(_),o=!1),t.clusterHide&&(t.clusterHide(),o=!1),o&&u.removeLayer(t),c&&(n=t._spiderLeg,r=n._path,s=r.getTotalLength()+.1,r.style.strokeDashoffset=s,n.setStyle({opacity:0})));h._ignoreMove=!1,setTimeout(function(){var e=0;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&e++;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&(t.clusterShow&&t.clusterShow(),t.setZIndexOffset&&t.setZIndexOffset(0),e>1&&u.removeLayer(t),l.removeLayer(t._spiderLeg),delete t._spiderLeg);h._animationEnd(),h.fire("unspiderfied",{cluster:a,markers:d})},200)}}),L.MarkerClusterGroup.include({_spiderfied:null,unspiderfy:function(){this._unspiderfy.apply(this,arguments)},_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Browser.touch||this._map.getRenderer(this)},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._map.off("zoomend",this._noanimationUnspiderfy,this),this._noanimationUnspiderfy() -},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(e){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(e){e._spiderLeg&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow(),e.setZIndexOffset&&e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}}),L.MarkerClusterGroup.include({refreshClusters:function(e){return e?e instanceof L.MarkerClusterGroup?e=e._topClusterLevel.getAllChildMarkers():e instanceof L.LayerGroup?e=e._layers:e instanceof L.MarkerCluster?e=e.getAllChildMarkers():e instanceof L.Marker&&(e=[e]):e=this._topClusterLevel.getAllChildMarkers(),this._flagParentsIconsNeedUpdate(e),this._refreshClustersIcons(),this.options.singleMarkerMode&&this._refreshSingleMarkerModeMarkers(e),this},_flagParentsIconsNeedUpdate:function(e){var t,i;for(t in e)for(i=e[t].__parent;i;)i._iconNeedsUpdate=!0,i=i.__parent},_refreshSingleMarkerModeMarkers:function(e){var t,i;for(t in e)i=e[t],this.hasLayer(i)&&i.setIcon(this._overrideMarkerIcon(i))}}),L.Marker.include({refreshIconOptions:function(e,t){var i=this.options.icon;return L.setOptions(i,e),this.setIcon(i),t&&this.__parent&&this.__parent._group.refreshClusters(this),this}}),e.MarkerClusterGroup=t,e.MarkerCluster=i}); -//# sourceMappingURL=leaflet.markercluster.js.map \ No newline at end of file diff --git a/omf/templates/geoJson.html b/omf/templates/geoJson.html index af1500123..5e782bc92 100644 --- a/omf/templates/geoJson.html +++ b/omf/templates/geoJson.html @@ -17,6 +17,10 @@ + + + + @@ -26,10 +30,6 @@ - - - - diff --git a/omf/templates/geoJson_offline.html b/omf/templates/geoJson_offline.html index 54a562304..f4e697236 100644 --- a/omf/templates/geoJson_offline.html +++ b/omf/templates/geoJson_offline.html @@ -17,6 +17,10 @@ + + + + {{ css }} From 0d3b127eee12d26f4d306213074b1ca955b0ccf3 Mon Sep 17 00:00:00 2001 From: astronobri Date: Sun, 26 May 2024 01:24:03 -0400 Subject: [PATCH 3/6] derConsumer: Added options to disable models and plot functions for vbatDispatch, PV, BESS, and outages. --- omf/models/derConsumer.html | 19 ++- omf/models/derConsumer.py | 225 +++++++++++++++++++++--------------- 2 files changed, 138 insertions(+), 106 deletions(-) diff --git a/omf/models/derConsumer.html b/omf/models/derConsumer.html index 569e796d9..1c1ff0bec 100644 --- a/omf/models/derConsumer.html +++ b/omf/models/derConsumer.html @@ -20,7 +20,7 @@
- +
@@ -41,7 +41,7 @@
-

Specific REopt Model Inputs

+

General Model Inputs


@@ -82,10 +82,6 @@
-
-
-

Model DER technologies

-
+ @@ -111,8 +107,8 @@
@@ -124,7 +120,7 @@
-

Specific vbatDispatch Model Inputs

+

Thermal Model Inputs


@@ -164,6 +160,7 @@ } } + @@ -313,6 +310,8 @@
+ + {{ rawOutputFiles }} {% endif %} \ No newline at end of file diff --git a/omf/models/derConsumer.py b/omf/models/derConsumer.py index 8ee455b6c..f15286091 100644 --- a/omf/models/derConsumer.py +++ b/omf/models/derConsumer.py @@ -22,10 +22,11 @@ # Model metadata: tooltip = ('The derConsumer model evaluates the financial costs of controlling behind-the-meter \ - distributed energy resources (DERs) at the residential level using the NREL renewable \ - energy optimization tool (REopt) and the OMF virtual battery dispatch module (vbatDispatch).') + distributed energy resources (DERs) at the residential level using the National Renewable Energy \ + Laboratory (NREL) Renewable Energy Optimization Tool (REopt) and the OMF virtual battery dispatch \ + module (vbatDispatch).') modelName, template = __neoMetaModel__.metadata(__file__) -hidden = True +hidden = True ## Keep the model hidden during active development def create_timestamps(start_time='2017-01-01',end_time='2017-12-31 23:00:00',arr_size=8760): ''' Creates an array of timestamps given a start time, stop time, and array size. @@ -175,16 +176,23 @@ def create_REopt_jl_jsonFile(modelDir, inputDict): "ElectricLoad": { "loads_kw": demand, "year": year - }, - "PV": { - }, - "ElectricStorage": { - - }, + } } - ## Outages - if (inputDict['outage']): + ## PV section + if inputDict['PV'] == 'Yes': + scenario['PV'] = { + ##TODO: Add options here, if needed + } + + ## BESS section + if inputDict['BESS'] == 'Yes': + scenario['ElectricStorage'] = { + ##TODO: Add options here, if needed + } + + ## Outage section + if inputDict['outage'] == 'Yes': scenario['ElectricUtility'] = { 'outage_start_time_step': int(inputDict['outage_start_hour']), 'outage_end_time_step': int(inputDict['outage_start_hour'])+int(inputDict['outage_duration']) @@ -222,19 +230,19 @@ def work(modelDir, inputDict): reoptResults = json.load(jsonFile) outData.update(reoptResults) ## Update output file with reopt results + ## Convert data from str to float + temperatures = [float(value) for value in inputDict['tempCurve'].split('\n') if value.strip()] + demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) + ## Create timestamp array from REopt input information try: year = reoptResults['ElectricLoad.year'][0] except KeyError: year = inputDict['year'] # Use the user provided year if none found in reoptResults - arr_size = np.size(reoptResults['ElectricUtility']['electric_to_load_series_kw']) + arr_size = np.size(demand) timestamps = create_timestamps(start_time=f'{year}-01-01', end_time=f'{year}-12-31 23:00:00', arr_size=arr_size) - ## Convert temperature data from str to float - temperatures = [float(value) for value in inputDict['tempCurve'].split('\n') if value.strip()] - demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) - ## If outage is specified, load the resilience results if (inputDict['outage']): try: @@ -247,36 +255,42 @@ def work(modelDir, inputDict): raise ## Run vbatDispatch - vbatResults = vb.work(modelDir,inputDict) - with open(pJoin(modelDir, 'vbatResults.json'), 'w') as jsonFile: - json.dump(vbatResults, jsonFile) - outData.update(vbatResults) ## Update output file with vbat results + if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + vbatResults = vb.work(modelDir,inputDict) + with open(pJoin(modelDir, 'vbatResults.json'), 'w') as jsonFile: + json.dump(vbatResults, jsonFile) + outData.update(vbatResults) ## Update output file with vbat results + + ## vbatDispatch variables + vbpower_series = pd.Series(vbatResults['VBpower'][0]) + vbat_charge = vbpower_series.where(vbpower_series > 0, 0) ##positive values = charging + vbat_discharge = vbpower_series.where(vbpower_series < 0, 0) #negative values = discharging + vbat_discharge_flipsign = vbat_discharge.mul(-1) ## flip sign of vbat discharge for plotting purposes ## DER Overview plot - showlegend = False #temporarily disable the legend toggle - - PV = reoptResults['PV']['electric_to_load_series_kw'] - BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw'] - vbpower_series = pd.Series(vbatResults['VBpower'][0]) - vbat_discharge = vbpower_series.where(vbpower_series < 0, 0) #negative values - vbat_charge = vbpower_series.where(vbpower_series > 0, 0) ## positive values; part of the New Load? - vbat_discharge_flipsign = vbat_discharge.mul(-1) ## flip sign of vbat discharge for plotting purposes + showlegend = True #temporarily disable the legend toggle grid_to_load = reoptResults['ElectricUtility']['electric_to_load_series_kw'] - grid_charging_BESS = reoptResults['ElectricUtility']['electric_to_storage_series_kw'] + if inputDict['PV'] == 'Yes': ## PV + PV = reoptResults['PV']['electric_to_load_series_kw'] + else: + PV = np.zeros_like(demand) + + if inputDict['BESS'] == 'Yes': ## BESS + BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw'] + grid_charging_BESS = reoptResults['ElectricUtility']['electric_to_storage_series_kw'] + else: + BESS = np.zeros_like(demand) + grid_charging_BESS = np.zeros_like(demand) + + ## Create plot object fig = go.Figure() - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(BESS) + np.asarray(demand) + np.asarray(vbat_discharge_flipsign), - yaxis='y1', - mode='none', - fill='tozeroy', - name='BESS Serving Load (kW)', - fillcolor='rgba(0,137,83,1)', - showlegend=showlegend)) - fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)')) - fig.add_trace(go.Scatter(x=timestamps, + if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + vbat_discharge_component = np.asarray(vbat_discharge_flipsign) + vbat_charge_component = np.asarray(vbat_charge) + fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(vbat_discharge_flipsign)+np.asarray(demand), yaxis='y1', mode='none', @@ -284,19 +298,33 @@ def work(modelDir, inputDict): fillcolor='rgba(127,0,255,1)', name='vbat Serving Load (kW)', showlegend=showlegend)) - fig.update_traces(fillpattern_shape='/', selector=dict(name='vbat Serving Load (kW)')) + fig.update_traces(fillpattern_shape='/', selector=dict(name='vbat Serving Load (kW)')) + else: + vbat_discharge_component = np.zeros_like(demand) + vbat_charge_component = np.zeros_like(demand) + + if (inputDict['BESS'] == 'Yes'): + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(BESS) + np.asarray(demand) + vbat_discharge_component, + yaxis='y1', + mode='none', + fill='tozeroy', + name='BESS Serving Load (kW)', + fillcolor='rgba(0,137,83,1)', + showlegend=showlegend)) + fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)')) fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(demand)-np.asarray(BESS)-np.asarray(vbat_discharge_flipsign), - yaxis='y1', - mode='none', - name='Original Load (kW)', - fill='tozeroy', - fillcolor='rgba(100,200,210,1)', - showlegend=showlegend)) + y=np.asarray(demand)-np.asarray(BESS)-vbat_discharge_component, + yaxis='y1', + mode='none', + name='Original Load (kW)', + fill='tozeroy', + fillcolor='rgba(100,200,210,1)', + showlegend=showlegend)) fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(demand) + np.asarray(reoptResults['ElectricUtility']['electric_to_storage_series_kw'][0]) + np.asarray(vbat_charge), + y=np.asarray(demand) + np.asarray(grid_charging_BESS) + vbat_charge_component, yaxis='y1', mode='none', name='Additional Load (Charging BESS and vbat)', @@ -305,7 +333,7 @@ def work(modelDir, inputDict): showlegend=showlegend)) fig.update_traces(fillpattern_shape='.', selector=dict(name='Additional Load (Charging BESS and vbat)')) - grid_serving_new_load = np.asarray(grid_to_load) + np.asarray(grid_charging_BESS)+ np.asarray(vbat_charge) - np.asarray(vbat_discharge_flipsign) + np.asarray(PV) + grid_serving_new_load = np.asarray(grid_to_load) + np.asarray(grid_charging_BESS)+ vbat_charge_component - vbat_discharge_component + np.asarray(PV) fig.add_trace(go.Scatter(x=timestamps, y=grid_serving_new_load, yaxis='y1', @@ -333,15 +361,16 @@ def work(modelDir, inputDict): # showlegend=showlegend)) #fig.update_traces(legendgroup='Demand', visible='legendonly', selector=dict(name='Original Load (kW)')) ## Make demand hidden on plot by default - fig.add_trace(go.Scatter(x=timestamps, - y=PV, - yaxis='y1', - mode='none', - fill='tozeroy', - name='PV Serving Load (kW)', - fillcolor='rgba(255,246,0,1)', - showlegend=showlegend - )) + if (inputDict['PV'] == 'Yes'): + fig.add_trace(go.Scatter(x=timestamps, + y=PV, + yaxis='y1', + mode='none', + fill='tozeroy', + name='PV Serving Load (kW)', + fillcolor='rgba(255,246,0,1)', + showlegend=showlegend + )) fig.update_layout( #title='Residential Data', @@ -361,6 +390,7 @@ def work(modelDir, inputDict): ) fig.show() ## This opens a window that displays the correct figure with the appropriate patterns + ## Encode plot data as JSON for showing in the HTML side outData['derOverviewData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) outData['derOverviewLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder) @@ -429,38 +459,51 @@ def makeGridLine(x,y,color,name): ## Exported Power Plot - PVcurtailed = reoptResults['PV']['electric_curtailed_series_kw'] - electric_to_grid = reoptResults['PV']['electric_to_grid_series_kw'] - fig = go.Figure() - + ## Power used to charge BESS (electric_to_storage_series_kw) - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(grid_charging_BESS), - mode='none', - fill='tozeroy', - name='Power Used to Charge BESS', - fillcolor='rgba(75,137,83,1)', - showlegend=True)) + if inputDict['BESS'] == 'Yes': + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(grid_charging_BESS), + mode='none', + fill='tozeroy', + name='Power Used to Charge BESS', + fillcolor='rgba(75,137,83,1)', + showlegend=True)) ## Power used to charge vbat (vbat_charging) - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(vbat_charge), - mode='none', - fill='tozeroy', - name='Power Used to Charge VBAT', - fillcolor='rgba(155,148,225,1)', - showlegend=True)) - - ## PV curtailed (electric_curtailed_series_kw) - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(PVcurtailed), - mode='none', - fill='tozeroy', - name='PV Curtailed', - fillcolor='rgba(0,137,83,1)', - showlegend=True)) + if inputDict['load_type'] != '0': + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(vbat_charge), + mode='none', + fill='tozeroy', + name='Power Used to Charge VBAT', + fillcolor='rgba(155,148,225,1)', + showlegend=True)) + + if inputDict['PV'] == 'Yes': + PVcurtailed = reoptResults['PV']['electric_curtailed_series_kw'] + electric_to_grid = reoptResults['PV']['electric_to_grid_series_kw'] + + ## PV curtailed (electric_curtailed_series_kw) + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(PVcurtailed), + mode='none', + fill='tozeroy', + name='PV Curtailed', + fillcolor='rgba(0,137,83,1)', + showlegend=True)) + + ## PV exported to grid (electric_to_grid_series_kw) + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(electric_to_grid), + mode='none', + fill='tozeroy', + name='Power Exported to Grid', + fillcolor='rgba(33,78,154,1)', + showlegend=True)) + ## Power used to meet load (NOTE: Does this mean grid to load?) fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(grid_to_load), @@ -469,16 +512,6 @@ def makeGridLine(x,y,color,name): name='Grid Serving Load', fillcolor='rgba(100,131,130,1)', showlegend=True)) - - ## Power exported to grid (electric_to_grid_series_kw) - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(electric_to_grid), - mode='none', - fill='tozeroy', - name='Power Exported to Grid', - fillcolor='rgba(33,78,154,1)', - showlegend=True)) - fig.update_layout( xaxis=dict(title='Timestamp'), @@ -534,7 +567,7 @@ def new(modelDir): "outage_duration": "3", ## vbatDispatch inputs: - "load_type": '2', ## Heat Pump + "load_type": '2', ## Heat Pump = #2 "number_devices": '1', "power": '5.6', "capacitance": '2', From 4f90b4a2352031d49349052b3a242389c11530b6 Mon Sep 17 00:00:00 2001 From: astronobri Date: Sun, 26 May 2024 02:58:07 -0400 Subject: [PATCH 4/6] derUtilityCost: Added SOC plot and options to disable vbatDispatch and DERs. Minor edits to derConsumer. --- omf/models/derConsumer.py | 13 +- omf/models/derUtilityCost.html | 107 ++++++++++++-- omf/models/derUtilityCost.py | 257 +++++++++++++++++++++++---------- 3 files changed, 278 insertions(+), 99 deletions(-) diff --git a/omf/models/derConsumer.py b/omf/models/derConsumer.py index f15286091..1686557f6 100644 --- a/omf/models/derConsumer.py +++ b/omf/models/derConsumer.py @@ -26,7 +26,7 @@ Laboratory (NREL) Renewable Energy Optimization Tool (REopt) and the OMF virtual battery dispatch \ module (vbatDispatch).') modelName, template = __neoMetaModel__.metadata(__file__) -hidden = True ## Keep the model hidden during active development +hidden = False ## Keep the model hidden during active development def create_timestamps(start_time='2017-01-01',end_time='2017-12-31 23:00:00',arr_size=8760): ''' Creates an array of timestamps given a start time, stop time, and array size. @@ -42,16 +42,15 @@ def create_timestamps(start_time='2017-01-01',end_time='2017-12-31 23:00:00',arr return timestamps def create_REopt_jl_jsonFile(modelDir, inputDict): + ## TODO: Insert function explanation here ## Site parameters latitude = float(inputDict['latitude']) longitude = float(inputDict['longitude']) urdbLabel = str(inputDict['urdbLabel']) year = int(inputDict['year']) - outage = inputDict['outage'] demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) - - demand = demand.tolist() if isinstance(demand, np.ndarray) else demand + demand = demand.tolist() if isinstance(demand, np.ndarray) else demand ## make demand into a list ## Energy technologies #solar = inputDict['solar'] @@ -209,19 +208,15 @@ def work(modelDir, inputDict): # Delete output file every run if it exists outData = {} - ## NOTE: This code will be used once reopt_jl is working ## Create REopt input file create_REopt_jl_jsonFile(modelDir, inputDict) - ## NOTE: This code is used temporarily until reopt_jl is working + ## NOTE: This code is used temporarily until reopt_jl is working. ## Read in a static REopt test file #with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","residential_REopt_results.json")) as f: # reoptResults = pd.json_normalize(json.load(f)) # print('Successfully loaded REopt test file. \n') - # Model operations goes here. - - ## NOTE: This code will be used once reopt_jl is working ## Run REopt.jl outage_flag = inputDict['outage'] diff --git a/omf/models/derUtilityCost.html b/omf/models/derUtilityCost.html index a5085477a..d6dc308b0 100644 --- a/omf/models/derUtilityCost.html +++ b/omf/models/derUtilityCost.html @@ -6,7 +6,8 @@ @@ -17,7 +18,7 @@
- +
@@ -38,9 +39,9 @@
-

System Parameters

+

General Model Inputs

-
+
@@ -88,7 +89,7 @@
- @@ -100,16 +101,13 @@
-
-

Resilience - To optimize for resilience, specify an Outage Start Hour greater than 0

-
-
+
@@ -121,7 +119,7 @@
-

Specific vbatDispatch Model Inputs

+

Thermal Model Inputs


@@ -161,6 +159,7 @@ } } + @@ -224,13 +223,89 @@
{{ omfRunDebugBlock }} {% if modelStatus == 'finished' %} + -

Plotly Test Plot

-
-
- {{ rawOutputFiles }} + +

DER Serving Load Overview

+
+ +

Battery Charge Percentage

+
+ +

Monthly Cost Comparison

+
+
+ + +
+
+ + + + + + + + + + + + + + + +
JanFebMarAprMayJunJulAugSepOctNovDec
+ + +
+
+ +

Cash Flow Projection

+
+
+ +
+ + + {{ rawOutputFiles }} {% endif %} \ No newline at end of file diff --git a/omf/models/derUtilityCost.py b/omf/models/derUtilityCost.py index 145d25442..73091ea1c 100644 --- a/omf/models/derUtilityCost.py +++ b/omf/models/derUtilityCost.py @@ -26,7 +26,7 @@ 'distributed energy resources (DERs) using the NREL renewable energy optimization tool (REopt) and ' 'the OMF virtual battery dispatch module (vbatDispatch).') modelName, template = __neoMetaModel__.metadata(__file__) -hidden = True +hidden = True ## Keep the model hidden during active development def work(modelDir, inputDict): @@ -36,9 +36,14 @@ def work(modelDir, inputDict): outData = {} ## Create REopt input file - reopt_input_scenario = derConsumer.create_REopt_jl_jsonFile(modelDir, inputDict) + derConsumer.create_REopt_jl_jsonFile(modelDir, inputDict) + + ## NOTE: This code is temporary + ## Read in a static REopt test file + #with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","utility_reopt_results.json")) as f: + # reoptResults = pd.json_normalize(json.load(f)) + # print('Successfully read in REopt test file. \n') - ## NOTE: This code will be used once reopt_jl is working ## Run REopt.jl outage_flag = inputDict['outage'] @@ -47,11 +52,9 @@ def work(modelDir, inputDict): reoptResults = json.load(jsonFile) outData.update(reoptResults) ## Update output file with reopt results - ## NOTE: This code is temporary - ## Read in a static REopt test file - with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","utility_reopt_results.json")) as f: - reoptResults = pd.json_normalize(json.load(f)) - print('Successfully read in REopt test file. \n') + ## Convert temperature data from str to float + temperatures = [float(value) for value in inputDict['tempCurve'].split('\n') if value.strip()] + demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) ## Create timestamp array from REopt input information try: @@ -59,13 +62,9 @@ def work(modelDir, inputDict): except KeyError: year = inputDict['year'] # Use the user provided year if none found in reoptResults - arr_size = np.size(reoptResults['ElectricUtility']['electric_to_load_series_kw']) + arr_size = np.size(demand) timestamps = derConsumer.create_timestamps(start_time=f'{year}-01-01', end_time=f'{year}-12-31 23:00:00', arr_size=arr_size) - ## Convert temperature data from str to float - temperatures = [float(value) for value in inputDict['tempCurve'].split('\n') if value.strip()] - demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) - ## If outage is specified, load the resilience results if (inputDict['outage']): try: @@ -78,20 +77,17 @@ def work(modelDir, inputDict): raise ## Run vbatDispatch - vbatResults = vb.work(modelDir,inputDict) - with open(pJoin(modelDir, 'vbatResults.json'), 'w') as jsonFile: - json.dump(vbatResults, jsonFile) - outData.update(vbatResults) ## Update output file with vbat results - - - ## Output data - #outData['solar'] = inputDict['solar'] - #outData['generator'] = inputDict['generator'] ## TODO: make generator switch on only during outage? - #outData['battery'] = inputDict['battery'] - #outData['year'] = inputDict['year'] - #outData['urdbLabel'] = inputDict['urdbLabel'] - #out['demandCost'] = results['ElectricTariff']['lifecycle_demand_cost_after_tax'] - #out['powerPVToGrid'] = results['PV']['electric_to_grid_series_kw']#['year_one_to_grid_series_kw'] + if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + vbatResults = vb.work(modelDir,inputDict) + with open(pJoin(modelDir, 'vbatResults.json'), 'w') as jsonFile: + json.dump(vbatResults, jsonFile) + outData.update(vbatResults) ## Update output file with vbat results + + ## vbatDispatch variables + vbpower_series = pd.Series(vbatResults['VBpower']) + vbat_charge = vbpower_series.where(vbpower_series > 0, 0) ##positive values = charging + vbat_discharge = vbpower_series.where(vbpower_series < 0, 0) #negative values = discharging + vbat_discharge_flipsign = vbat_discharge.mul(-1) ## flip sign of vbat discharge for plotting purposes ## Run REopt and gather outputs for vbatDispath ## TODO: Create a function that will gather the urdb label from a user provided location (city,state) @@ -99,32 +95,37 @@ def work(modelDir, inputDict): #reopt_jl.run_reopt_jl(path="/Users/astronobri/Documents/CIDER/reopt/inputs/", inputFile="UP_PV_outage_1hr.json", outages=outage) # UP coop PV #reopt_jl.run_reopt_jl(path="/Users/astronobri/Documents/CIDER/reopt/inputs/", inputFile=pJoin(__neoMetaModel__._omfDir,"static","testFiles","residential_input.json"), outages=True) # residential PV - - ## Test plot - showlegend = False #temporarily disable the legend toggle - - PV = reoptResults['outputs.PV.electric_to_load_series_kw'][0] - BESS = reoptResults['outputs.ElectricStorage.storage_to_load_series_kw'][0] - vbpower_series = pd.Series(vbatResults['VBpower'][0]) - vbat_discharge = vbpower_series.where(vbpower_series < 0, 0) #negative values - vbat_charge = vbpower_series.where(vbpower_series > 0, 0) ## positive values; part of the New Load? - vbat_discharge_flipsign = vbat_discharge.mul(-1) ## flip sign of vbat discharge for plotting purposes - grid_to_load = reoptResults['outputs.ElectricUtility.electric_to_load_series_kw'][0] - grid_charging_BESS = reoptResults['outputs.ElectricUtility.electric_to_storage_series_kw'][0] + ## DER Overview plot + showlegend = True #temporarily disable the legend toggle + grid_to_load = reoptResults['ElectricUtility']['electric_to_load_series_kw'] + if inputDict['PV'] == 'Yes': ## PV + PV = reoptResults['PV']['electric_to_load_series_kw'] + else: + PV = np.zeros_like(demand) + + if inputDict['BESS'] == 'Yes': ## BESS + #BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw'] + BESS = np.ones_like(demand) ## Ad-hoc line used because BESS is not being built in REopt for some reason. Currently debugging 5/2024 + grid_charging_BESS = reoptResults['ElectricUtility']['electric_to_storage_series_kw'] + #outData['chargeLevelBattery'] = reoptResults['ElectricStorage']['soc_series_fraction'] + + ## NOTE: The following 3 lines of code are temporary. + ## It read the SOC info from a static reopt test file until the issue with REopt producing BESS results is resolved. + with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","utility_reopt_results.json")) as f: + static_reopt_results = json.load(f) + outData['chargeLevelBattery'] = static_reopt_results['outputs']['ElectricStorage']['soc_series_fraction'] + else: + BESS = np.zeros_like(demand) + grid_charging_BESS = np.zeros_like(demand) + + ## Create plot object fig = go.Figure() - fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(BESS) + np.asarray(demand) + np.asarray(vbat_discharge_flipsign), - yaxis='y1', - mode='none', - fill='tozeroy', - name='BESS Serving Load (kW)', - fillcolor='rgba(0,137,83,1)', - showlegend=showlegend)) - fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)')) - - fig.add_trace(go.Scatter(x=timestamps, + if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + vbat_discharge_component = np.asarray(vbat_discharge_flipsign) + vbat_charge_component = np.asarray(vbat_charge) + fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(vbat_discharge_flipsign)+np.asarray(demand), yaxis='y1', mode='none', @@ -132,19 +133,33 @@ def work(modelDir, inputDict): fillcolor='rgba(127,0,255,1)', name='vbat Serving Load (kW)', showlegend=showlegend)) - fig.update_traces(fillpattern_shape='/', selector=dict(name='vbat Serving Load (kW)')) + fig.update_traces(fillpattern_shape='/', selector=dict(name='vbat Serving Load (kW)')) + else: + vbat_discharge_component = np.zeros_like(demand) + vbat_charge_component = np.zeros_like(demand) + + if (inputDict['BESS'] == 'Yes'): + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(BESS) + np.asarray(demand) + vbat_discharge_component, + yaxis='y1', + mode='none', + fill='tozeroy', + name='BESS Serving Load (kW)', + fillcolor='rgba(0,137,83,1)', + showlegend=showlegend)) + fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)')) fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(demand)-np.asarray(BESS)-np.asarray(vbat_discharge_flipsign), - yaxis='y1', - mode='none', - name='Original Load (kW)', - fill='tozeroy', - fillcolor='rgba(100,200,210,1)', - showlegend=showlegend)) + y=np.asarray(demand)-np.asarray(BESS)-vbat_discharge_component, + yaxis='y1', + mode='none', + name='Original Load (kW)', + fill='tozeroy', + fillcolor='rgba(100,200,210,1)', + showlegend=showlegend)) fig.add_trace(go.Scatter(x=timestamps, - y=np.asarray(demand) + np.asarray(reoptResults['outputs.ElectricUtility.electric_to_storage_series_kw'][0]) + np.asarray(vbat_charge), + y=np.asarray(demand) + np.asarray(grid_charging_BESS) + vbat_charge_component, yaxis='y1', mode='none', name='Additional Load (Charging BESS and vbat)', @@ -153,8 +168,7 @@ def work(modelDir, inputDict): showlegend=showlegend)) fig.update_traces(fillpattern_shape='.', selector=dict(name='Additional Load (Charging BESS and vbat)')) - - grid_serving_new_load = np.asarray(grid_to_load) + np.asarray(grid_charging_BESS)+ np.asarray(vbat_charge) - np.asarray(vbat_discharge_flipsign) + np.asarray(PV) + grid_serving_new_load = np.asarray(grid_to_load) + np.asarray(grid_charging_BESS)+ vbat_charge_component - vbat_discharge_component + np.asarray(PV) fig.add_trace(go.Scatter(x=timestamps, y=grid_serving_new_load, yaxis='y1', @@ -182,20 +196,21 @@ def work(modelDir, inputDict): # showlegend=showlegend)) #fig.update_traces(legendgroup='Demand', visible='legendonly', selector=dict(name='Original Load (kW)')) ## Make demand hidden on plot by default - fig.add_trace(go.Scatter(x=timestamps, - y=PV, - yaxis='y1', - mode='none', - fill='tozeroy', - name='PV Serving Load (kW)', - fillcolor='rgba(255,246,0,1)', - showlegend=showlegend - )) + if (inputDict['PV'] == 'Yes'): + fig.add_trace(go.Scatter(x=timestamps, + y=PV, + yaxis='y1', + mode='none', + fill='tozeroy', + name='PV Serving Load (kW)', + fillcolor='rgba(255,246,0,1)', + showlegend=showlegend + )) fig.update_layout( title='Utility Data Test', xaxis=dict(title='Timestamp'), - yaxis=dict(title="Energy (kW)"), + yaxis=dict(title="Power (kW)"), yaxis2=dict(title='degrees Celsius', overlaying='y', side='right' @@ -212,8 +227,102 @@ def work(modelDir, inputDict): fig.show() ## Encode plot data as JSON for showing in the HTML - outData['plotlyPlot'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) - outData['plotlyLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder) + outData['derOverviewData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) + outData['derOverviewLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder) + + ## Exported Power Plot + PVcurtailed = reoptResults['PV']['electric_curtailed_series_kw'] + electric_to_grid = reoptResults['PV']['electric_to_grid_series_kw'] + + fig = go.Figure() + + ## Power used to charge BESS (electric_to_storage_series_kw) + if inputDict['BESS'] == 'Yes': + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(grid_charging_BESS), + mode='none', + fill='tozeroy', + name='Power Used to Charge BESS', + fillcolor='rgba(75,137,83,1)', + showlegend=True)) + + ## Power used to charge vbat (vbat_charging) + if inputDict['load_type'] != '0': + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(vbat_charge), + mode='none', + fill='tozeroy', + name='Power Used to Charge VBAT', + fillcolor='rgba(155,148,225,1)', + showlegend=True)) + + + if inputDict['PV'] == 'Yes': + PVcurtailed = reoptResults['PV']['electric_curtailed_series_kw'] + electric_to_grid = reoptResults['PV']['electric_to_grid_series_kw'] + + ## PV curtailed (electric_curtailed_series_kw) + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(PVcurtailed), + mode='none', + fill='tozeroy', + name='PV Curtailed', + fillcolor='rgba(0,137,83,1)', + showlegend=True)) + + ## PV exported to grid (electric_to_grid_series_kw) + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(electric_to_grid), + mode='none', + fill='tozeroy', + name='Power Exported to Grid', + fillcolor='rgba(33,78,154,1)', + showlegend=True)) + + ## Power used to meet load (NOTE: Does this mean grid to load?) + fig.add_trace(go.Scatter(x=timestamps, + y=np.asarray(grid_to_load), + mode='none', + fill='tozeroy', + name='Grid Serving Load', + fillcolor='rgba(100,131,130,1)', + showlegend=True)) + + + fig.update_layout( + xaxis=dict(title='Timestamp'), + yaxis=dict(title="Power (kW)"), + legend=dict( + orientation='h', + yanchor="bottom", + y=1.02, + xanchor="right", + x=1 + ) + ) + + ## Encode plot data as JSON for showing in the HTML side + outData['exportedPowerData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) + outData['exportedPowerLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder) + + ## Battery State of Charge plot + if inputDict['BESS'] == 'Yes': + fig = go.Figure() + + fig.add_trace(go.Scatter(x=timestamps, + y=outData['chargeLevelBattery'], + mode='none', + fill='tozeroy', + fillcolor='red', + name='Battery Charge Level', + showlegend=True)) + fig.update_layout( + xaxis=dict(title='Timestamp'), + yaxis=dict(title="Charge (%)") + ) + + outData['batteryChargeData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) + outData['batteryChargeLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder) # Model operations typically ends here. # Stdout/stderr. @@ -245,7 +354,7 @@ def new(modelDir): "demandCurve": demand_curve, "tempCurve": temp_curve, "PV": "Yes", - "BESS": "No", + "BESS": "Yes", "generator": "No", "outage": True, "outage_start_hour": '2100', From 7a367fba1688a652ebc10128d28bb9bcecc3dcfa Mon Sep 17 00:00:00 2001 From: astronobri Date: Mon, 27 May 2024 00:18:43 -0400 Subject: [PATCH 5/6] derConsumer: Added utility rate design inputs, ad-hoc BESS workaround, and entire code comments & docstrings --- omf/models/derConsumer.html | 36 +++++- omf/models/derConsumer.py | 221 +++++++++++++++++++++--------------- 2 files changed, 162 insertions(+), 95 deletions(-) diff --git a/omf/models/derConsumer.html b/omf/models/derConsumer.html index 1c1ff0bec..6c49b0faa 100644 --- a/omf/models/derConsumer.html +++ b/omf/models/derConsumer.html @@ -80,24 +80,24 @@
- +
- +
- +
- +
+
+

Distributed Energy Resource (DER) Program Design inputs

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
{{ omfModelButtons }} diff --git a/omf/models/derConsumer.py b/omf/models/derConsumer.py index 1686557f6..97849367d 100644 --- a/omf/models/derConsumer.py +++ b/omf/models/derConsumer.py @@ -26,23 +26,34 @@ Laboratory (NREL) Renewable Energy Optimization Tool (REopt) and the OMF virtual battery dispatch \ module (vbatDispatch).') modelName, template = __neoMetaModel__.metadata(__file__) -hidden = False ## Keep the model hidden during active development +hidden = False ## Keep the model hidden=True during active development def create_timestamps(start_time='2017-01-01',end_time='2017-12-31 23:00:00',arr_size=8760): - ''' Creates an array of timestamps given a start time, stop time, and array size. - Inputs: - start_time (str) Beginning of the timestamp array in 'year-month-day hh:mm:ss format' - end_time (str) End of the timestamp array in 'year-month-day hh:mm:ss format' - arr_size (int) Size of the timestamp array (default=8760 for one year in hourly increments) - Outputs: - timestamps (arr) Of size arr_size from start_time to end_time - + ''' + Creates an array of timestamps (using pandas package) given a start time, stop time, and array size. + ** Inputs + start_time: (str) Beginning of the timestamp array in 'year-month-day hh:mm:ss format' + end_time: (str) End of the timestamp array in 'year-month-day hh:mm:ss format' + arr_size: (int) Size of the timestamp array (default=8760 for one year in hourly increments) + ** Outputs + timestamps: (arr) Of size arr_size from start_time to end_time + ** Required packages + import pandas as pd ''' timestamps = pd.date_range(start=start_time, end=end_time, periods=arr_size) return timestamps def create_REopt_jl_jsonFile(modelDir, inputDict): - ## TODO: Insert function explanation here + ''' + Function that assembles a dictionary (and saves as a JSON file) of all user inputs for the purposes \ + of preparing to run reopt_jl. + ** Inputs + modelDir: (str) path of where to save the ouput JSON file (needed for reopt_jl) + inputDict: (dict) input dictionary containing relevant user inputs to be modeled in REopt + (e.g. latitude, longitude, urdb label, etc.) + ** Outputs + scenario: (dict) output dictionary containing the correct format. + ''' ## Site parameters latitude = float(inputDict['latitude']) @@ -50,20 +61,12 @@ def create_REopt_jl_jsonFile(modelDir, inputDict): urdbLabel = str(inputDict['urdbLabel']) year = int(inputDict['year']) demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) - demand = demand.tolist() if isinstance(demand, np.ndarray) else demand ## make demand into a list - - ## Energy technologies - #solar = inputDict['solar'] - #generator = inputDict['generator'] - #battery = inputDict['battery'] - - ## Load demand file and make it JSON ready - #with open(pJoin(modelDir, "demand.csv")) as loadFile: - # load = pd.read_csv(loadFile, header=None) - # load = load[0].values.tolist() - + demand = demand.tolist() if isinstance(demand, np.ndarray) else demand ## make demand into a list """ + ## NOTE: The following lines of code are optional parameters that may or may not be used in the future. + ## Copied from omf.models.microgridDesign + ## Financial and Load parameters energyCost = float(inputDict['energyCost']) demandCost = float(inputDict['demandCost']) @@ -164,41 +167,50 @@ def create_REopt_jl_jsonFile(modelDir, inputDict): } """ + ## Begin the REopt input dictionary called 'scenario' scenario = { - "Site": { - "latitude": latitude, - "longitude": longitude + 'Site': { + 'latitude': latitude, + 'longitude': longitude }, - "ElectricTariff": { - "urdb_label": urdbLabel + 'ElectricTariff': { + 'urdb_label': urdbLabel }, - "ElectricLoad": { - "loads_kw": demand, - "year": year + 'ElectricLoad': { + 'loads_kw': demand, + 'year': year } } - ## PV section + ## Add a PV section if enabled if inputDict['PV'] == 'Yes': scenario['PV'] = { ##TODO: Add options here, if needed - } + } - ## BESS section + ## Add a Battery Energy Storage System (BESS) section if enabled if inputDict['BESS'] == 'Yes': scenario['ElectricStorage'] = { ##TODO: Add options here, if needed - } + } + + ## Add a Diesel Generator section if enabled + if inputDict['generator'] == 'Yes': + scenario['generator'] = { + ##TODO: Add options here, if needed + } - ## Outage section + ## Add an Outage section if enabled if inputDict['outage'] == 'Yes': scenario['ElectricUtility'] = { 'outage_start_time_step': int(inputDict['outage_start_hour']), 'outage_end_time_step': int(inputDict['outage_start_hour'])+int(inputDict['outage_duration']) - } + } - ## Save scenario file - with open(pJoin(modelDir, "reopt_input_scenario.json"), "w") as jsonFile: + ## Save the scenario file + ## NOTE: reopt_jl currently requires a path for the input file, so the file must be saved to a location + ## preferrably in the modelDir + with open(pJoin(modelDir, 'reopt_input_scenario.json'), 'w') as jsonFile: json.dump(scenario, jsonFile) return scenario @@ -211,21 +223,22 @@ def work(modelDir, inputDict): ## Create REopt input file create_REopt_jl_jsonFile(modelDir, inputDict) - ## NOTE: This code is used temporarily until reopt_jl is working. + ## NOTE: The single commented code below is used temporarily if reopt_jl is not working or for other debugging purposes. + ## Also NOTE: If this is used, you typically have to add a ['outputs'] key before the variable of interest. + ## For example, instead of reoptResults['ElectricStorage']['storage_to_load_series_kw'], it would have to be + ## reoptResults['outputs']['ElectricStorage']['storage_to_load_series_kw'] when using the static reopt file below. ## Read in a static REopt test file - #with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","residential_REopt_results.json")) as f: + #with open(pJoin(__neoMetaModel__._omfDir,"static","testFiles","residential_reopt_results.json")) as f: # reoptResults = pd.json_normalize(json.load(f)) # print('Successfully loaded REopt test file. \n') ## Run REopt.jl - outage_flag = inputDict['outage'] - - reopt_jl.run_reopt_jl(modelDir, "reopt_input_scenario.json", outages=outage_flag) + reopt_jl.run_reopt_jl(modelDir, "reopt_input_scenario.json", outages=inputDict['outage']) with open(pJoin(modelDir, 'results.json')) as jsonFile: reoptResults = json.load(jsonFile) - outData.update(reoptResults) ## Update output file with reopt results + outData.update(reoptResults) ## Update output file outData with REopt results data - ## Convert data from str to float + ## Convert user provided demand and temp data from str to float temperatures = [float(value) for value in inputDict['tempCurve'].split('\n') if value.strip()] demand = np.asarray([float(value) for value in inputDict['demandCurve'].split('\n') if value.strip()]) @@ -235,10 +248,10 @@ def work(modelDir, inputDict): except KeyError: year = inputDict['year'] # Use the user provided year if none found in reoptResults - arr_size = np.size(demand) + arr_size = np.size(demand) # desired array size of the timestamp array timestamps = create_timestamps(start_time=f'{year}-01-01', end_time=f'{year}-12-31 23:00:00', arr_size=arr_size) - ## If outage is specified, load the resilience results + ## If outage is specified in the inputs, load the resilience results if (inputDict['outage']): try: with open(pJoin(modelDir, 'resultsResilience.json')) as jsonFile: @@ -249,8 +262,8 @@ def work(modelDir, inputDict): print(f"File '{results_file}' not found. REopt may not have simulated the outage.") raise - ## Run vbatDispatch - if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + ## Run vbatDispatch, unless it is disabled + if inputDict['load_type'] != '0': ## Load type 0 corresponds to the "None" option, which disables this vbatDispatch function vbatResults = vb.work(modelDir,inputDict) with open(pJoin(modelDir, 'vbatResults.json'), 'w') as jsonFile: json.dump(vbatResults, jsonFile) @@ -264,7 +277,7 @@ def work(modelDir, inputDict): ## DER Overview plot - showlegend = True #temporarily disable the legend toggle + showlegend = True # either enable or disable the legend toggle in the plot grid_to_load = reoptResults['ElectricUtility']['electric_to_load_series_kw'] if inputDict['PV'] == 'Yes': ## PV @@ -273,16 +286,23 @@ def work(modelDir, inputDict): PV = np.zeros_like(demand) if inputDict['BESS'] == 'Yes': ## BESS - BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw'] + #BESS = reoptResults['ElectricStorage']['storage_to_load_series_kw'] + BESS = np.ones_like(demand) ## NOTE: Ad-hoc line used because BESS is not being built in REopt for some reason. Currently debugging 5/2024 grid_charging_BESS = reoptResults['ElectricUtility']['electric_to_storage_series_kw'] + + ## NOTE: The following 3 lines of code are temporary; it reads in the SOC info from a static reopt test file + ## For some reason REopt is not producing BESS results so this is a workaround + with open(pJoin(__neoMetaModel__._omfDir,'static','testFiles','residential_reopt_results.json')) as f: + static_reopt_results = json.load(f) + outData['chargeLevelBattery'] = static_reopt_results['outputs']['ElectricStorage']['soc_series_fraction'] else: BESS = np.zeros_like(demand) grid_charging_BESS = np.zeros_like(demand) - ## Create plot object + ## Create DER overview plot object fig = go.Figure() - if inputDict['load_type'] != '0': ## Load type 0 corresponds to "None" option, which turns off vbatDispatch functions + if inputDict['load_type'] != '0': ## Load type 0 corresponds to the "None" option, which disables this vbatDispatch function vbat_discharge_component = np.asarray(vbat_discharge_flipsign) vbat_charge_component = np.asarray(vbat_charge) fig.add_trace(go.Scatter(x=timestamps, @@ -298,6 +318,7 @@ def work(modelDir, inputDict): vbat_discharge_component = np.zeros_like(demand) vbat_charge_component = np.zeros_like(demand) + ## BESS serving load piece if (inputDict['BESS'] == 'Yes'): fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(BESS) + np.asarray(demand) + vbat_discharge_component, @@ -309,6 +330,7 @@ def work(modelDir, inputDict): showlegend=showlegend)) fig.update_traces(fillpattern_shape='/', selector=dict(name='BESS Serving Load (kW)')) + ## Original load piece (minus any vbat or BESS charging aka 'new/additional loads') fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(demand)-np.asarray(BESS)-vbat_discharge_component, yaxis='y1', @@ -318,16 +340,21 @@ def work(modelDir, inputDict): fillcolor='rgba(100,200,210,1)', showlegend=showlegend)) + ## Additional load (Charging BESS and vbat) + ## NOTE: demand is added here for plotting purposes, so that the additional load shows up above the demand curve. + ## How or if this should be done is still being discussed fig.add_trace(go.Scatter(x=timestamps, y=np.asarray(demand) + np.asarray(grid_charging_BESS) + vbat_charge_component, yaxis='y1', mode='none', - name='Additional Load (Charging BESS and vbat)', + name='Additional Load (Charging BESS and vbat)', ## TODO: Edit this title accordingly if BESS and/or vbat are disabled fill='tonexty', fillcolor='rgba(175,0,42,0)', showlegend=showlegend)) fig.update_traces(fillpattern_shape='.', selector=dict(name='Additional Load (Charging BESS and vbat)')) + ## Grid serving new load + ## TODO: Should PV really be in this? grid_serving_new_load = np.asarray(grid_to_load) + np.asarray(grid_charging_BESS)+ vbat_charge_component - vbat_discharge_component + np.asarray(PV) fig.add_trace(go.Scatter(x=timestamps, y=grid_serving_new_load, @@ -338,6 +365,7 @@ def work(modelDir, inputDict): fillcolor='rgba(192,192,192,1)', showlegend=showlegend)) + ## Temperature line on a secondary y-axis (defined in the plot layout) fig.add_trace(go.Scatter(x=timestamps, y=temperatures, yaxis='y2', @@ -347,6 +375,8 @@ def work(modelDir, inputDict): showlegend=showlegend )) + ## NOTE: This code hides the demand curve initially when the plot is made, but it can be + ## toggled back on by the user by clicking it in the plot legend #fig.add_trace(go.Scatter(x=timestamps, # y=demand, # yaxis='y1', @@ -356,6 +386,7 @@ def work(modelDir, inputDict): # showlegend=showlegend)) #fig.update_traces(legendgroup='Demand', visible='legendonly', selector=dict(name='Original Load (kW)')) ## Make demand hidden on plot by default + ## PV piece, if enabled if (inputDict['PV'] == 'Yes'): fig.add_trace(go.Scatter(x=timestamps, y=PV, @@ -367,6 +398,7 @@ def work(modelDir, inputDict): showlegend=showlegend )) + ## Plot layout fig.update_layout( #title='Residential Data', xaxis=dict(title='Timestamp'), @@ -384,7 +416,10 @@ def work(modelDir, inputDict): ) ) - fig.show() ## This opens a window that displays the correct figure with the appropriate patterns + ## NOTE: This opens a window that displays the correct figure with the appropriate patterns. + ## For some reason, the slash-mark patterns are not showing up on the OMF page otherwise. + ## Eventually we will delete this part. + fig.show() ## Encode plot data as JSON for showing in the HTML side outData['derOverviewData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder) @@ -420,7 +455,7 @@ def makeGridLine(x,y,color,name): powerGridToLoad = makeGridLine(x_values,reoptResults['ElectricUtility']['electric_to_load_series_kw'],'blue','Load met by Grid') plotData.append(powerGridToLoad) - if (inputDict['outage']): + if (inputDict['outage']): ## TODO: condense this code if possible outData['resilience'] = reoptResultsResilience['resilience_by_time_step'] outData['minOutage'] = reoptResultsResilience['resilience_hours_min'] outData['maxOutage'] = reoptResultsResilience['resilience_hours_max'] @@ -453,7 +488,7 @@ def makeGridLine(x,y,color,name): outData["resilienceProbLayout"] = json.dumps(plotlyLayout, cls=plotly.utils.PlotlyJSONEncoder) - ## Exported Power Plot + ## Create Exported Power plot object fig = go.Figure() ## Power used to charge BESS (electric_to_storage_series_kw) @@ -540,49 +575,55 @@ def new(modelDir): defaultInputs = { ## OMF inputs: - "user": "admin", - "modelType": modelName, - "created": str(datetime.datetime.now()), + 'user' : 'admin', + 'modelType': modelName, + 'created': str(datetime.datetime.now()), ## REopt inputs: - "latitude": '39.532165', ## Rivesville, WV - "longitude": '-80.120618', ## TODO: Should these be strings or floats? Update - strings. - "year": '2018', - "analysisYears": '25', - "urdbLabel": '643476222faee2f0f800d8b1', ## Rivesville, WV - Monongahela Power - "fileName": "residential_PV_load.csv", - "tempFileName": "residential_extended_temperature_data.csv", - "demandCurve": demand_curve, - "tempCurve": temp_curve, - "PV": "Yes", - "BESS": "No", - "generator": "No", - "outage": True, - "outage_start_hour": "2100", - "outage_duration": "3", + 'latitude': '39.532165', ## Rivesville, WV + 'longitude': '-80.120618', ## TODO: Should these be strings or floats? Update - strings. + 'year' : '2018', + 'analysis_years' : '25', + 'urdbLabel': '643476222faee2f0f800d8b1', ## Rivesville, WV - Monongahela Power + 'fileName': 'residential_PV_load.csv', + 'tempFileName': 'residential_extended_temperature_data.csv', + 'demandCurve': demand_curve, + 'tempCurve': temp_curve, + 'PV': 'No', + 'BESS': 'Yes', + 'generator': 'No', + 'outage': True, + 'outage_start_hour': '4637', + 'outage_duration': '3', ## vbatDispatch inputs: - "load_type": '2', ## Heat Pump = #2 - "number_devices": '1', - "power": '5.6', - "capacitance": '2', - "resistance": '2', - "cop": '2.5', - "setpoint": '19.5', - "deadband": '0.625', - "demandChargeCost": '25', - "electricityCost": '0.16', - "projectionLength": '25', - "discountRate": '2', - "unitDeviceCost": '150', - "unitUpkeepCost": '5', + 'load_type': '2', ## Heat Pump + 'number_devices': '1', + 'power': '5.6', + 'capacitance': '2', + 'resistance': '2', + 'cop': '2.5', + 'setpoint': '19.5', + 'deadband': '0.625', + 'demandChargeCost': '25', + 'electricityCost': '0.16', + 'projectionLength': '25', + 'discountRate': '2', + 'unitDeviceCost': '150', + 'unitUpkeepCost': '5', + + ## DER Program Design inputs: + 'utilityProgram': 'Yes', ## unit: $/kWh + 'rateCompensation': '0', ## unit: $ + 'fixedCompensation': '0', ## unit: $ + 'fixedCompensationFrequency': '0', ## 0=one-time only, 1=yearly } return __neoMetaModel__.new(modelDir, defaultInputs) @neoMetaModel_test_setup def _tests_disabled(): # Location - modelLoc = pJoin(__neoMetaModel__._omfDir,"data","Model","admin","Automated Testing of " + modelName) + modelLoc = pJoin(__neoMetaModel__._omfDir,'data','Model','admin','Automated Testing of ' + modelName) # Blow away old test results if necessary. try: shutil.rmtree(modelLoc) From 4200679e7da6228e2365aeef3d4d7177b3263f74 Mon Sep 17 00:00:00 2001 From: astronobri Date: Mon, 27 May 2024 00:30:18 -0400 Subject: [PATCH 6/6] derUtilityCost: Added comments to entire code; replaced double quotes with singles. --- omf/models/derUtilityCost.html | 6 +- omf/models/derUtilityCost.py | 212 ++++++++++++--------------------- 2 files changed, 76 insertions(+), 142 deletions(-) diff --git a/omf/models/derUtilityCost.html b/omf/models/derUtilityCost.html index d6dc308b0..58231a5cb 100644 --- a/omf/models/derUtilityCost.html +++ b/omf/models/derUtilityCost.html @@ -81,14 +81,14 @@
- +
- +