From 1b7ed124decb4596e887fa0363abff128cefda53 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 14:04:46 +0100 Subject: [PATCH 01/25] Add htmx to vendors --- Makefile | 4 ++++ digiplan/static/vendors/htmx/js/htmx.min.js | 1 + package-lock.json | 6 ++++++ package.json | 1 + 4 files changed, 12 insertions(+) create mode 100644 digiplan/static/vendors/htmx/js/htmx.min.js diff --git a/Makefile b/Makefile index 509dfaaf..9c7042bf 100644 --- a/Makefile +++ b/Makefile @@ -89,4 +89,8 @@ update_vendor_assets: cp node_modules/shepherd.js/dist/js/shepherd.* digiplan/static/vendors/shepherd/ cp node_modules/shepherd.js/dist/css/shepherd.css digiplan/static/vendors/shepherd/ + # HTMX https://htmx.org/ + rm -r digiplan/static/vendors/htmx/js/* + cp node_modules/htmx.org/dist/htmx.min.js digiplan/static/vendors/htmx/js/ + # Done diff --git a/digiplan/static/vendors/htmx/js/htmx.min.js b/digiplan/static/vendors/htmx/js/htmx.min.js new file mode 100644 index 00000000..d68f3c6c --- /dev/null +++ b/digiplan/static/vendors/htmx/js/htmx.min.js @@ -0,0 +1 @@ +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:B,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Fr,logAll:V,logNone:j,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.11"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function s(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/",0);var a=i.querySelector("template").content;if(Q.config.allowScriptTags){oe(a.querySelectorAll("script"),function(e){if(Q.config.inlineScriptNonce){e.nonce=Q.config.inlineScriptNonce}e.htmxExecuted=navigator.userAgent.indexOf("Firefox")===-1})}else{oe(a.querySelectorAll("script"),function(e){_(e)})}return a}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return s(""+n+"
",1);case"col":return s(""+n+"
",2);case"tr":return s(""+n+"
",2);case"td":case"th":return s(""+n+"
",3);case"script":case"style":return s("
"+n+"
",1);default:return s(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function F(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function B(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=p(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=p(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=p(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=p(e);e.classList.toggle(t)}function W(e,t){e=p(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=p(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function g(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(g(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function p(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:p(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var pe=re().createElement("output");function me(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[pe]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Be(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Fe(r,o,a);Re(o);return Be(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){pt(e)})}},200)}}function pt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function mt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);pt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(!e.htmxExecuted&&Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Ft(o)}for(var l in r){Bt(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=F(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=me(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=me(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return mr(n)}else{return pr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:p(r),returnPromise:true})}else{return he(e,t,p(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:p(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=g(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==pe){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var p=ne(n,"hx-sync");var m=null;var x=false;if(p){var F=p.split(":");var B=F[0].trim();if(B==="this"){g=xe(n,"hx-sync")}else{g=ue(n,B)}p=(F[1]||"drop").trim();f=ae(g);if(p==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(p==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(p==="replace"){ce(g,"htmx:abort")}else if(p.indexOf("queue")===0){var V=p.split(" ");m=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(m==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){m=y.triggerSpec.queue}}if(m==null){m="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(m==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=pr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var p=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:p},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;p=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){p=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var m=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!p){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(m)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){m=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Fr(e){delete Xr[e]}function Br(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Br(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9e6b4a91..a63c755b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "@popperjs/core": "^2.11.8", "bootstrap": "^5.2.1", "echarts": "^5.4.2", + "htmx.org": "^1.9.11", "ion-rangeslider": "^2.3.1", "jquery": "^3.6.1", "maplibre-gl": "^4.0.0", @@ -283,6 +284,11 @@ "node": ">=6" } }, + "node_modules/htmx.org": { + "version": "1.9.11", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz", + "integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", diff --git a/package.json b/package.json index 8a0299dc..c11feedd 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@popperjs/core": "^2.11.8", "bootstrap": "^5.2.1", "echarts": "^5.4.2", + "htmx.org": "^1.9.11", "ion-rangeslider": "^2.3.1", "jquery": "^3.6.1", "maplibre-gl": "^4.0.0", From f13ef559b397ccbf7f0659de688a34137827ab1d Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 14:10:59 +0100 Subject: [PATCH 02/25] Install and add django-template-partials --- config/settings/base.py | 1 + poetry.lock | 18 ++++++++++++++++++ pyproject.toml | 1 + 3 files changed, 20 insertions(+) diff --git a/config/settings/base.py b/config/settings/base.py index ef999a4f..421ae23b 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -90,6 +90,7 @@ THIRD_PARTY_APPS = [ "rest_framework", "django_distill", + "template_partials", ] LOCAL_APPS = ["digiplan.map.apps.MapConfig", "django_oemof", "django_mapengine"] diff --git a/poetry.lock b/poetry.lock index aaf83c6b..377a692f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1175,6 +1175,24 @@ files = [ Django = ">=2.2" redis = ">=3.0.0" +[[package]] +name = "django-template-partials" +version = "23.4" +description = "django-template-partials" +optional = false +python-versions = "*" +files = [ + {file = "django-template-partials-23.4.tar.gz", hash = "sha256:f762b0b7b2222462df0845f0556792640b769eb832eae218a0e7dadd4e5606cc"}, + {file = "django_template_partials-23.4-py2.py3-none-any.whl", hash = "sha256:d83d9c2d2836be769919e9aaf394d5feb1ac86e1187083030398308070122fca"}, +] + +[package.dependencies] +Django = "*" + +[package.extras] +docs = ["Sphinx"] +tests = ["coverage", "django_coverage_plugin"] + [[package]] name = "djangorestframework" version = "3.15.1" diff --git a/pyproject.toml b/pyproject.toml index f39ad363..57618cf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ django-oemof = {git = "https://github.com/rl-institut/django-oemof.git", tag="v0 django-mapengine = "1.3.0" geojson = "^3.0.1" oemof-network = "0.5.0a1" +django-template-partials = "^23.4" [tool.poetry.group.dev.dependencies] Werkzeug = "^2.0.1" # https://github.com/pallets/werkzeug From 97d9eb0ab464446b057e37ec5f17ece5d045584a Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 14:31:45 +0100 Subject: [PATCH 03/25] Refactor detail key results into partials --- digiplan/templates/forms/panel_energy.html | 246 +++++++-------------- 1 file changed, 81 insertions(+), 165 deletions(-) diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index a55b6b37..9c0029da 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -1,5 +1,76 @@ {% load i18n %} {% load static %} +{% load partials %} + +{% partialdef wind_key_results %} +
+
+
+ + {{ wind_key_result_area }} ha + +
+
+ + {% trans "werden genutzt" %} + +
+
+
+
+ + {{ wind_key_result_turbines }} + +
+
+ {% trans "Anlagen werden errichtet" %} +
+
+
+
+ + {{ wind_key_result_energy }} + +
+
+ + {% trans "können gewonnen werden" %} + +
+
+
+{% endpartialdef %} + +{% partialdef pv_key_results %} +
+
+
+ + ... ha + +
+
+ + {% trans "werden genutzt" %} + +
+
+
+
+ + ... TWh + +
+
+ + {% trans "können gewonnen werden" %} + +
+
+
+{% endpartialdef %} + +

{% trans "Generation" %}

@@ -68,43 +139,8 @@

{% trans "Generation" %}

-
-
-
-
- - ... ha - -
-
- - {% trans "werden genutzt" %} - -
-
-
-
- - ... - -
-
- {% trans "Anlagen werden errichtet" %} -
-
-
-
- - ... - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
+
+ {% partial wind_key_results %}
@@ -147,43 +183,8 @@

{% trans "Generation" %}

-
-
-
-
- - ... ha - -
-
- - {% trans "werden genutzt" %} - -
-
-
-
- - ... - -
-
- {% trans "Anlagen werden errichtet" %} -
-
-
-
- - ... TWh - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
+
+ {% partial wind_key_results %}
@@ -238,43 +239,8 @@

{% trans "Generation" %}

-
-
-
-
- - ... ha - -
-
- - {% trans "werden genutzt" %} - -
-
-
-
- - ... - -
-
- {% trans "Anlagen werden errichtet" %} -
-
-
-
- - ... TWh - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
+
+ {% partial wind_key_results %}
@@ -359,33 +325,8 @@

{% trans "Generation" %}

-
-
-
-
- - ... ha - -
-
- - {% trans "werden genutzt" %} - -
-
-
-
- - ... TWh - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
+
+ {% partial pv_key_results %}
@@ -427,33 +368,8 @@

{% trans "Generation" %}

-
-
-
-
- - ... ha - -
-
- - {% trans "werden genutzt" %} - -
-
-
-
- - ... TWh - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
+
+ {% partial pv_key_results %}
From 7e476a54c1ccf8f3ec973d7c6d2bf1224e5a19ad Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 14:32:07 +0100 Subject: [PATCH 04/25] Add htmx JS --- digiplan/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/digiplan/templates/base.html b/digiplan/templates/base.html index 1283d4b7..f7da12d3 100644 --- a/digiplan/templates/base.html +++ b/digiplan/templates/base.html @@ -63,6 +63,7 @@ + {% endcompress %} {% compress js %} From 8b06d571ee367334404e0e519fafe839bf151f61 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 14:40:47 +0100 Subject: [PATCH 05/25] Prepare initialisation of detail key results for wind and pv --- digiplan/templates/forms/panel_energy.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index 9c0029da..06456819 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -46,7 +46,7 @@
- ... ha + {{ pv_key_result_area }} ha
@@ -58,7 +58,7 @@
- ... TWh + {{ pv_key_result_energy }} TWh
@@ -140,7 +140,7 @@

{% trans "Generation" %}

- {% partial wind_key_results %} + {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2018 wind_key_result_turbines=wind_key_result_turbines_2018 wind_key_result_energy=wind_key_result_energy_2018 %}
@@ -184,7 +184,7 @@

{% trans "Generation" %}

- {% partial wind_key_results %} + {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2024 wind_key_result_turbines=wind_key_result_turbines_2024 wind_key_result_energy=wind_key_result_energy_2024 %}
@@ -240,7 +240,7 @@

{% trans "Generation" %}

- {% partial wind_key_results %} + {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2027 wind_key_result_turbines=wind_key_result_turbines_2027 wind_key_result_energy=wind_key_result_energy_2027 %}
@@ -326,7 +326,7 @@

{% trans "Generation" %}

- {% partial pv_key_results %} + {% partial pv_key_results with pv_key_result_area=pv_ground_key_result_area pv_key_result_energy=pv_ground_key_result_energy %}
@@ -369,7 +369,7 @@

{% trans "Generation" %}

- {% partial pv_key_results %} + {% partial pv_key_results with pv_key_result_area=pv_roof_key_result_area pv_key_result_energy=pv_roof_key_result_energy %}
From ac76115c0a9525e0b860984d62add5a0f260e2e2 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 15:52:03 +0100 Subject: [PATCH 06/25] Set up detail key results from dummy calculation using partials --- digiplan/map/forms.py | 23 ++++++- digiplan/map/menu.py | 16 +++++ digiplan/map/urls.py | 1 + digiplan/map/views.py | 15 ++++- digiplan/templates/forms/panel_energy.html | 75 +++++++++------------- 5 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 digiplan/map/menu.py diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 76a43513..e0652de0 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -4,7 +4,7 @@ from django.utils.safestring import mark_safe from django_mapengine import legend -from . import charts +from . import charts, menu from .widgets import SwitchWidget @@ -49,7 +49,19 @@ def __init__(self, layer: legend.LegendLayer, *args, **kwargs) -> None: # noqa: self.layer = layer -class PanelForm(TemplateForm): # noqa: D101 +class TemplateExtraContentForm(TemplateForm): + """Add extra content to template form.""" + + extra_content = {} + + def __str__(self) -> str: # noqa: D105 + if self.template_name: + renderer = renderers.get_default_renderer() + return mark_safe(renderer.render(self.template_name, {"form": self, **self.extra_content})) # noqa: S308 + return super().__str__() + + +class PanelForm(TemplateExtraContentForm): # noqa: D101 def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: D107, ANN001 super().__init__(**kwargs) self.fields = {item["name"]: item["field"] for item in self.generate_fields(parameters, additional_parameters)} @@ -103,6 +115,13 @@ def generate_fields(parameters, additional_parameters=None): # noqa: ANN001, AN class EnergyPanelForm(PanelForm): # noqa: D101 template_name = "forms/panel_energy.html" + def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: D107, ANN001 + super().__init__(parameters, additional_parameters, **kwargs) + for technology in ("wind_2018", "wind_2024", "wind_2027", "pv_ground", "pv_roof"): + key_results = menu.detail_key_results(technology) + for key, value in key_results.items(): + self.extra_content[f"{technology}_key_result_{key}"] = value + class HeatPanelForm(PanelForm): # noqa: D101 template_name = "forms/panel_heat.html" diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py new file mode 100644 index 00000000..f90dfa52 --- /dev/null +++ b/digiplan/map/menu.py @@ -0,0 +1,16 @@ +"""Add calculations for menu items.""" + + +def detail_key_results(technology: str, **kwargs: dict) -> dict: # noqa: ARG001 + """Calculate detail key results for given technology.""" + if technology == "wind_2018": + return {"area": 300, "turbines": 20, "energy": 2000} + if technology == "wind_2024": + return {"area": 400, "turbines": 30, "energy": 3000} + if technology == "wind_2027": + return {"area": 500, "turbines": 50, "energy": 4000} + if technology == "pv_ground": + return {"area": 33, "energy": 2001} + if technology == "pv_roof": + return {"area": 34, "energy": 2002} + raise KeyError(f"Unknown technology '{technology}'.") diff --git a/digiplan/map/urls.py b/digiplan/map/urls.py index 2a3d8b28..9bdb13f8 100644 --- a/digiplan/map/urls.py +++ b/digiplan/map/urls.py @@ -12,4 +12,5 @@ path("choropleth//", views.get_choropleth, name="choropleth"), path("popup//", views.get_popup, name="popup"), path("charts", views.get_charts, name="charts"), + path("detail_key_results", views.DetailKeyResultsView.as_view(), name="detail_key_results"), ] diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 87c7abeb..47374fc0 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -11,7 +11,7 @@ from django_mapengine import views from digiplan import __version__ -from digiplan.map import config +from digiplan.map import config, menu from . import charts, choropleths, forms, map_config, popups, utils @@ -185,3 +185,16 @@ def get_charts(request: HttpRequest) -> response.JsonResponse: return response.JsonResponse( {lookup: charts.CHARTS[lookup](simulation_id=simulation_id).render() for lookup in lookups}, ) + + +class DetailKeyResultsView(TemplateView): + """Return HTMX-partial for requested detail key results.""" + + template_name = "forms/panel_energy.html#key_results" + + def get_context_data(self, **kwargs) -> dict: # noqa: ARG002 + """Get detail key results for requested technology.""" + technology = self.request.GET["technology"] + return { + f"key_result_{key}": value for key, value in menu.detail_key_results(technology, **self.request.GET).items() + } diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index 06456819..2352e5d5 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -2,12 +2,12 @@ {% load static %} {% load partials %} -{% partialdef wind_key_results %} +{% partialdef key_results %}
- {{ wind_key_result_area }} ha + {{ key_result_area }} ha
@@ -16,49 +16,22 @@
-
-
- - {{ wind_key_result_turbines }} - -
-
- {% trans "Anlagen werden errichtet" %} -
-
-
-
- - {{ wind_key_result_energy }} - -
-
- - {% trans "können gewonnen werden" %} - -
-
-
-{% endpartialdef %} - -{% partialdef pv_key_results %} -
-
-
- - {{ pv_key_result_area }} ha - -
-
- - {% trans "werden genutzt" %} - + {% if key_result_turbines %} +
+
+ + {{ key_result_turbines }} + +
+
+ {% trans "Anlagen werden errichtet" %} +
-
+ {% endif %}
- {{ pv_key_result_energy }} TWh + {{ key_result_energy }}
@@ -140,7 +113,9 @@

{% trans "Generation" %}

- {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2018 wind_key_result_turbines=wind_key_result_turbines_2018 wind_key_result_energy=wind_key_result_energy_2018 %} + {% with key_result_area=wind_2018_key_result_area key_result_turbines=wind_2018_key_result_turbines key_result_energy=wind_2018_key_result_energy %} + {% partial key_results %} + {% endwith %}
@@ -184,7 +159,9 @@

{% trans "Generation" %}

- {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2024 wind_key_result_turbines=wind_key_result_turbines_2024 wind_key_result_energy=wind_key_result_energy_2024 %} + {% with key_result_area=wind_2024_key_result_area key_result_turbines=wind_2024_key_result_turbines key_result_energy=wind_2024_key_result_energy %} + {% partial key_results %} + {% endwith %}
@@ -240,7 +217,9 @@

{% trans "Generation" %}

- {% partial wind_key_results with wind_key_result_area=wind_key_result_area_2027 wind_key_result_turbines=wind_key_result_turbines_2027 wind_key_result_energy=wind_key_result_energy_2027 %} + {% with key_result_area=wind_2027_key_result_area key_result_turbines=wind_2027_key_result_turbines key_result_energy=wind_2027_key_result_energy %} + {% partial key_results %} + {% endwith %}
@@ -326,7 +305,9 @@

{% trans "Generation" %}

- {% partial pv_key_results with pv_key_result_area=pv_ground_key_result_area pv_key_result_energy=pv_ground_key_result_energy %} + {% with key_result_area=pv_ground_key_result_area key_result_energy=pv_ground_key_result_energy %} + {% partial key_results %} + {% endwith %}
@@ -369,7 +350,9 @@

{% trans "Generation" %}

- {% partial pv_key_results with pv_key_result_area=pv_roof_key_result_area pv_key_result_energy=pv_roof_key_result_energy %} + {% with key_result_area=pv_roof_key_result_area key_result_energy=pv_roof_key_result_energy %} + {% partial key_results %} + {% endwith %}
From 97916a9b1eda30a013cf69c94301980b09b7dd0a Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 17:03:10 +0100 Subject: [PATCH 07/25] Started HTMX integration on wind sliders Unfortunately ionrangeslider seems not to work with HTMX yet. See opened issue here: https://github.com/IonDen/ion.rangeSlider/issues/792 --- digiplan/map/forms.py | 58 +++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index e0652de0..bf522799 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -1,6 +1,7 @@ from itertools import count # noqa: D100 from django.forms import BooleanField, Form, IntegerField, TextInput, renderers +from django.shortcuts import reverse from django.utils.safestring import mark_safe from django_mapengine import legend @@ -66,33 +67,36 @@ def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # super().__init__(**kwargs) self.fields = {item["name"]: item["field"] for item in self.generate_fields(parameters, additional_parameters)} - @staticmethod - def generate_fields(parameters, additional_parameters=None): # noqa: ANN001, ANN205, D102 + def get_field_attrs(self, name: str, parameters: dict) -> dict: # noqa: ARG002 + """Set up field attributes from parameters.""" + attrs = { + "class": parameters["class"], + "data-min": parameters["min"], + "data-max": parameters["max"], + "data-from": parameters["start"], + "data-grid": "true" if "grid" in parameters and parameters["grid"] else "false", + "data-has-sidepanel": "true" if "sidepanel" in parameters else "false", + "data-color": parameters["color"] if "color" in parameters else "", + } + if "to" in parameters: + attrs["data-to"] = parameters["to"] + if "step" in parameters: + attrs["data-step"] = parameters["step"] + if "from-min" in parameters: + attrs["data-from-min"] = parameters["from-min"] + if "from-max" in parameters: + attrs["data-from-max"] = parameters["from-max"] + return attrs + + def generate_fields(self, parameters: dict, additional_parameters: dict | None = None) -> dict: + """Create fields from config parameters.""" if additional_parameters is not None: charts.merge_dicts(parameters, additional_parameters) for name, item in parameters.items(): if item["type"] == "slider": - attrs = { - "class": item["class"], - "data-min": item["min"], - "data-max": item["max"], - "data-from": item["start"], - "data-grid": "true" if "grid" in item and item["grid"] else "false", - "data-has-sidepanel": "true" if "sidepanel" in item else "false", - "data-color": item["color"] if "color" in item else "", - } - if "to" in item: - attrs["data-to"] = item["to"] - if "step" in item: - attrs["data-step"] = item["step"] - if "from-min" in item: - attrs["data-from-min"] = item["from-min"] - if "from-max" in item: - attrs["data-from-max"] = item["from-max"] - field = IntegerField( label=item["label"], - widget=TextInput(attrs=attrs), + widget=TextInput(attrs=self.get_field_attrs(name, item)), help_text=item["tooltip"], required=item.get("required", True), ) @@ -115,13 +119,23 @@ def generate_fields(parameters, additional_parameters=None): # noqa: ANN001, AN class EnergyPanelForm(PanelForm): # noqa: D101 template_name = "forms/panel_energy.html" - def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: D107, ANN001 + def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: ANN001 + """Overwrite init function to add initial key results for detail panels.""" super().__init__(parameters, additional_parameters, **kwargs) for technology in ("wind_2018", "wind_2024", "wind_2027", "pv_ground", "pv_roof"): key_results = menu.detail_key_results(technology) for key, value in key_results.items(): self.extra_content[f"{technology}_key_result_{key}"] = value + def get_field_attrs(self, name: str, parameters: dict) -> dict: + """Add HTMX attributes to wind and pv detail sliders.""" + detail_slider_targets = {"s_w_6": "wind_key_results_2024", "s_w_7": "wind_key_results_2027"} + attrs = super().get_field_attrs(name, parameters) + if name in detail_slider_targets: + attrs["hx-get"] = reverse("map:detail_key_results") + attrs["hx-target"] = detail_slider_targets[name] + return attrs + class HeatPanelForm(PanelForm): # noqa: D101 template_name = "forms/panel_heat.html" From 1aad24bc61108ce331b69be91681ce3e243e2086 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 26 Mar 2024 18:35:26 +0100 Subject: [PATCH 08/25] Refactor potential areas to be used in other contexts --- digiplan/map/datapackage.py | 121 ++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index 6ce5ba37..5d0c4a7e 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -1,17 +1,35 @@ """Read functionality for digipipe datapackage.""" import csv import json -from collections import defaultdict +from collections import defaultdict, namedtuple from pathlib import Path -from typing import Optional +from typing import Optional, Union import pandas as pd from django.conf import settings from django_oemof.settings import OEMOF_DIR -from config.settings.base import DATA_DIR +from config.settings.base import DIGIPIPE_DIR from digiplan.map import config, models +Source = namedtuple("Source", ["csv_file", "column"]) + + +def get_data_from_sources(sources: Union[Source, list[Source]]) -> pd.DataFrame: + """Extract data from single or multiple sources and merge into dataframe.""" + source_files = defaultdict(list) + if isinstance(sources, Source): + source_files[sources.csv_file].append(sources.column) + else: + for source in sources: + source_files[source.csv_file].append(source.column) + + dfs = [] + for source_file, columns in source_files.items(): + source_path = Path(DIGIPIPE_DIR, "scalars", source_file) + dfs.append(pd.read_csv(source_path, usecols=columns)) + return pd.concat(dfs) + def get_employment() -> pd.DataFrame: """Return employment data.""" @@ -144,70 +162,79 @@ def get_thermal_efficiency(component: str) -> float: return pd.read_csv(sequence_filename, sep=";").iloc[:, 1] -def get_potential_values(*, per_municipality: bool = False) -> dict: +def get_potential_values() -> dict: """ Calculate max_values for sliders. - Parameters - ---------- - per_municipality: bool - If set to True, potentials are not aggregated, but given per municipality - Returns ------- dict dictionary with each slider / switch and respective max_value """ - scalars = { - "wind": "potentialarea_wind_area_stats_muns.csv", - "pv_ground": "potentialarea_pv_ground_area_stats_muns.csv", - "pv_roof": "potentialarea_pv_roof_area_stats_muns.csv", - } - - areas = { - "wind": { - "wind_2018": "stp_2018_eg", - "wind_2024": "stp_2024_vr", - "wind_2027": area - if (area := models.Municipality.objects.all().values("area").aggregate(models.Sum("area"))["area__sum"]) - else 0, # to prevent None if regions are empty - }, - "pv_ground": { - "pv_soil_quality_low": "soil_quality_low_region", - "pv_soil_quality_medium": "soil_quality_medium_region", - "pv_permanent_crops": "permanent_crops_region", - }, - "pv_roof": {"pv_roof": "installable_power"}, - } - + areas = get_potential_areas() pv_density = { "pv_soil_quality_low": "pv_ground", "pv_soil_quality_medium": "pv_ground_vertical_bifacial", "pv_permanent_crops": "pv_ground_elevated", + "pv_roof": "pv_roof", } power_density = json.load(Path.open(Path(settings.DIGIPIPE_DIR, "scalars/technology_data.json")))["power_density"] potentials = {} - for profile in areas: - path = Path(DATA_DIR, "digipipe/scalars", scalars[profile]) - reader = pd.read_csv(path) - for key, value in areas[profile].items(): - if key == "wind_2027": - # Value is already calculated from region area (see above) - potentials[key] = value - else: - if per_municipality: # noqa: PLR5501 - potentials[key] = reader[value] - else: - potentials[key] = reader[value].sum() - if profile == "wind": - potentials[key] = potentials[key] * power_density["wind"] - if profile == "pv_ground": - potentials[key] = potentials[key] * power_density[pv_density[key]] + for key in areas: + if key.startswith("wind"): + potentials[key] = areas[key] * power_density["wind"] + if key.startswith("pv"): + potentials[key] = areas[key] * power_density[pv_density[key]] return potentials +def get_potential_areas(technology: Optional[str] = None) -> dict: + """ + Return potential areas. + + Parameters + ---------- + technology: str + If given, potential area only for this technology is returned + + Returns + ------- + dict + Potential areas of all technologies or specified one + """ + sources = { + "wind_2018": Source("potentialarea_wind_area_stats_muns.csv", "stp_2018_eg"), + "wind_2024": Source("potentialarea_wind_area_stats_muns.csv", "stp_2024_vr"), + "pv_soil_quality_low": Source("potentialarea_pv_ground_area_stats_muns.csv", "soil_quality_low_region"), + "pv_soil_quality_medium": Source("potentialarea_pv_ground_area_stats_muns.csv", "soil_quality_medium_region"), + "pv_permanent_crops": Source("potentialarea_pv_ground_area_stats_muns.csv", "permanent_crops_region"), + "pv_roof": Source("potentialarea_pv_roof_area_stats_muns.csv", "installable_power"), + } + + # Add wind for 2027 directly from model data, as it is not included in datapackage + areas = { + "wind_2027": area + if (area := models.Municipality.objects.all().values("area").aggregate(models.Sum("area"))["area__sum"]) + else 0, + } + if technology is not None: + if technology == "wind_2027": + return areas["wind_2027"] + sources = {technology: sources[technology]} + + data = get_data_from_sources(sources.values()) + data = data.sum() + for index in data.index: + # Add extracted data to areas and map source columns to keys accordingly + areas[next(key for key, source in sources.items() if source.column == index)] = data[index] + + if technology is not None: + return areas[technology] + return areas + + def get_full_load_hours(year: int) -> pd.Series: """Return full load hours for given year.""" full_load_hours = pd.Series( From d6717b4ae9620dc65851eca6642ebc55dd86602e Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 28 Mar 2024 12:46:53 +0100 Subject: [PATCH 09/25] Fix pv roof potential calculation --- digiplan/map/datapackage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index 5d0c4a7e..b2359cc1 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -176,7 +176,6 @@ def get_potential_values() -> dict: "pv_soil_quality_low": "pv_ground", "pv_soil_quality_medium": "pv_ground_vertical_bifacial", "pv_permanent_crops": "pv_ground_elevated", - "pv_roof": "pv_roof", } power_density = json.load(Path.open(Path(settings.DIGIPIPE_DIR, "scalars/technology_data.json")))["power_density"] @@ -185,6 +184,9 @@ def get_potential_values() -> dict: for key in areas: if key.startswith("wind"): potentials[key] = areas[key] * power_density["wind"] + if key == "pv_roof": + potentials[key] = areas[key] + continue if key.startswith("pv"): potentials[key] = areas[key] * power_density[pv_density[key]] return potentials From 2d79491ea5fef8aa767d0e1e7135efca0efbe2f3 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 28 Mar 2024 13:39:00 +0100 Subject: [PATCH 10/25] Enable caching for datapackage functions --- digiplan/map/datapackage.py | 6 ++++++ poetry.lock | 14 ++++++++++++++ pyproject.toml | 1 + 3 files changed, 21 insertions(+) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index b2359cc1..af16a620 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -6,6 +6,7 @@ from typing import Optional, Union import pandas as pd +from cache_memoize import cache_memoize from django.conf import settings from django_oemof.settings import OEMOF_DIR @@ -162,6 +163,7 @@ def get_thermal_efficiency(component: str) -> float: return pd.read_csv(sequence_filename, sep=";").iloc[:, 1] +@cache_memoize(timeout=None) def get_potential_values() -> dict: """ Calculate max_values for sliders. @@ -192,6 +194,7 @@ def get_potential_values() -> dict: return potentials +@cache_memoize(timeout=None) def get_potential_areas(technology: Optional[str] = None) -> dict: """ Return potential areas. @@ -237,6 +240,7 @@ def get_potential_areas(technology: Optional[str] = None) -> dict: return areas +@cache_memoize(timeout=None) def get_full_load_hours(year: int) -> pd.Series: """Return full load hours for given year.""" full_load_hours = pd.Series( @@ -246,6 +250,7 @@ def get_full_load_hours(year: int) -> pd.Series: return full_load_hours +@cache_memoize(timeout=None) def get_capacities_from_datapackage() -> pd.DataFrame: """Return renewable capacities for given year from datapackage.""" capacities = pd.concat( @@ -285,6 +290,7 @@ def get_capacities_from_sliders(year: int) -> pd.Series: return slider_settings +@cache_memoize(timeout=None) def get_power_density(technology: Optional[str] = None) -> dict: """Return power density for technology.""" if technology: diff --git a/poetry.lock b/poetry.lock index 377a692f..d8ce4aaf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -963,6 +963,20 @@ files = [ [package.dependencies] django = "*" +[[package]] +name = "django-cache-memoize" +version = "0.2.0" +description = "Django utility for a memoization decorator that uses the Django cache framework." +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-cache-memoize-0.2.0.tar.gz", hash = "sha256:79950a027ba40e4aff4efed587b76036bf5ba1f59329d7b158797b832be72ca6"}, + {file = "django_cache_memoize-0.2.0-py3-none-any.whl", hash = "sha256:a6bfd112da699d1fa85955a1e15b7c48ee25e58044398958e269678db10736f3"}, +] + +[package.extras] +dev = ["black", "flake8", "therapist", "tox", "twine"] + [[package]] name = "django-compressor" version = "4.4" diff --git a/pyproject.toml b/pyproject.toml index 57618cf3..f727786c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ django-mapengine = "1.3.0" geojson = "^3.0.1" oemof-network = "0.5.0a1" django-template-partials = "^23.4" +django-cache-memoize = "^0.2.0" [tool.poetry.group.dev.dependencies] Werkzeug = "^2.0.1" # https://github.com/pallets/werkzeug From 5dec44bd6aecd6a57ead40effb11474162ff55a8 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 28 Mar 2024 13:39:32 +0100 Subject: [PATCH 11/25] Calculate wind detail key results --- digiplan/map/forms.py | 7 +++++- digiplan/map/menu.py | 26 ++++++++++++++++------ digiplan/templates/forms/panel_energy.html | 6 ++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index bf522799..ccdc2587 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -123,7 +123,12 @@ def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # """Overwrite init function to add initial key results for detail panels.""" super().__init__(parameters, additional_parameters, **kwargs) for technology in ("wind_2018", "wind_2024", "wind_2027", "pv_ground", "pv_roof"): - key_results = menu.detail_key_results(technology) + # get initial slider values for wind and pv: + key_results = menu.detail_key_results( + technology, + id_s_w_6=parameters["s_w_6"]["start"], + id_s_w_7=parameters["s_w_7"]["start"], + ) for key, value in key_results.items(): self.extra_content[f"{technology}_key_result_{key}"] = value diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index f90dfa52..b18918a9 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -1,14 +1,26 @@ """Add calculations for menu items.""" +from . import config, datapackage -def detail_key_results(technology: str, **kwargs: dict) -> dict: # noqa: ARG001 + +def detail_key_results(technology: str, **kwargs: dict) -> dict: """Calculate detail key results for given technology.""" - if technology == "wind_2018": - return {"area": 300, "turbines": 20, "energy": 2000} - if technology == "wind_2024": - return {"area": 400, "turbines": 30, "energy": 3000} - if technology == "wind_2027": - return {"area": 500, "turbines": 50, "energy": 4000} + areas = datapackage.get_potential_areas() + potential_capacities = datapackage.get_potential_values() # in MW + full_load_hours = datapackage.get_full_load_hours(2045) + nominal_power_per_unit = config.TECHNOLOGY_DATA["nominal_power_per_unit"]["wind"] + + if technology.startswith("wind"): + percentage = 1 + if technology == "wind_2024": + percentage = int(kwargs["id_s_w_6"]) / 100 + if technology == "wind_2027": + percentage = int(kwargs["id_s_w_7"]) / 100 + return { + "area": areas[technology] / 100 * percentage, + "turbines": potential_capacities[technology] / nominal_power_per_unit * percentage, + "energy": potential_capacities[technology] * full_load_hours["wind"] * percentage * 1e-6, + } if technology == "pv_ground": return {"area": 33, "energy": 2001} if technology == "pv_roof": diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index 2352e5d5..1b26a517 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -7,7 +7,7 @@
- {{ key_result_area }} ha + {{ key_result_area | floatformat:2 }} ha
@@ -20,7 +20,7 @@
- {{ key_result_turbines }} + {{ key_result_turbines | floatformat:0 }}
@@ -31,7 +31,7 @@
- {{ key_result_energy }} + {{ key_result_energy | floatformat:2 }}
From a8a98144d1f544900feda85f17fe380844c1763a Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 28 Mar 2024 14:05:10 +0100 Subject: [PATCH 12/25] Calculate pv detail key results --- digiplan/map/forms.py | 4 ++++ digiplan/map/menu.py | 25 ++++++++++++++++++++-- digiplan/templates/forms/panel_energy.html | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index ccdc2587..7950ae44 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -128,6 +128,10 @@ def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # technology, id_s_w_6=parameters["s_w_6"]["start"], id_s_w_7=parameters["s_w_7"]["start"], + id_s_pv_ff_3=parameters["s_pv_ff_3"]["start"], + id_s_pv_ff_4=parameters["s_pv_ff_4"]["start"], + id_s_pv_ff_5=parameters["s_pv_ff_5"]["start"], + id_s_pv_d_3=parameters["s_pv_d_3"]["start"], ) for key, value in key_results.items(): self.extra_content[f"{technology}_key_result_{key}"] = value diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index b18918a9..9bce3497 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -22,7 +22,28 @@ def detail_key_results(technology: str, **kwargs: dict) -> dict: "energy": potential_capacities[technology] * full_load_hours["wind"] * percentage * 1e-6, } if technology == "pv_ground": - return {"area": 33, "energy": 2001} + percentages = { + "pv_soil_quality_low": int(kwargs["id_s_pv_ff_3"]) / 100, + "pv_soil_quality_medium": int(kwargs["id_s_pv_ff_4"]) / 100, + "pv_permanent_crops": int(kwargs["id_s_pv_ff_5"]) / 100, + } + flh_mapping = { + "pv_soil_quality_low": "pv_ground", + "pv_soil_quality_medium": "pv_ground_elevated", + "pv_permanent_crops": "pv_ground_vertical_bifacial", + } + return { + "area": sum(areas[pv_type] * percentages[pv_type] for pv_type in percentages), + "energy": sum( + potential_capacities[pv_type] * full_load_hours[flh_mapping[pv_type]] * percentages[pv_type] + for pv_type in percentages + ) + * 1e-6, + } if technology == "pv_roof": - return {"area": 34, "energy": 2002} + percentage = int(kwargs["id_s_pv_d_3"]) / 100 + return { + "area": areas[technology] * percentage, + "energy": potential_capacities[technology] * full_load_hours[technology] * percentage * 1e-6, + } raise KeyError(f"Unknown technology '{technology}'.") diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index 1b26a517..b7fc4b61 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -31,7 +31,7 @@
- {{ key_result_energy | floatformat:2 }} + {{ key_result_energy | floatformat:2 }} TWh
From 64182647ce70d83e0b29ae93521a3491a7f293a2 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 3 Apr 2024 15:00:04 +0200 Subject: [PATCH 13/25] Fix future annotation --- digiplan/map/forms.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 7950ae44..1e1217a6 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -1,13 +1,19 @@ -from itertools import count # noqa: D100 +"""Module containing django forms.""" +from __future__ import annotations + +from itertools import count +from typing import TYPE_CHECKING from django.forms import BooleanField, Form, IntegerField, TextInput, renderers from django.shortcuts import reverse from django.utils.safestring import mark_safe -from django_mapengine import legend from . import charts, menu from .widgets import SwitchWidget +if TYPE_CHECKING: + from django_mapengine import legend + class TemplateForm(Form): # noqa: D101 template_name = None From 31638cdbae9e770fe67b8b72b091d56333f98cac Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 10:57:17 +0200 Subject: [PATCH 14/25] Minor change --- digiplan/map/forms.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 1e1217a6..73c4cfd9 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -17,11 +17,12 @@ class TemplateForm(Form): # noqa: D101 template_name = None + extra_content = {} def __str__(self) -> str: # noqa: D105 if self.template_name: renderer = renderers.get_default_renderer() - return mark_safe(renderer.render(self.template_name, {"form": self})) # noqa: S308 + return mark_safe(renderer.render(self.template_name, {"form": self, **self.extra_content})) # noqa: S308 return super().__str__() @@ -56,19 +57,7 @@ def __init__(self, layer: legend.LegendLayer, *args, **kwargs) -> None: # noqa: self.layer = layer -class TemplateExtraContentForm(TemplateForm): - """Add extra content to template form.""" - - extra_content = {} - - def __str__(self) -> str: # noqa: D105 - if self.template_name: - renderer = renderers.get_default_renderer() - return mark_safe(renderer.render(self.template_name, {"form": self, **self.extra_content})) # noqa: S308 - return super().__str__() - - -class PanelForm(TemplateExtraContentForm): # noqa: D101 +class PanelForm(TemplateForm): # noqa: D101 def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: D107, ANN001 super().__init__(**kwargs) self.fields = {item["name"]: item["field"] for item in self.generate_fields(parameters, additional_parameters)} From e5d5056c31079e0ed4b65644291dd7addc1a1678 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 10:57:36 +0200 Subject: [PATCH 15/25] Fix floating numbers for wind detail sliders --- digiplan/map/menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index 9bce3497..d8f41e62 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -13,9 +13,9 @@ def detail_key_results(technology: str, **kwargs: dict) -> dict: if technology.startswith("wind"): percentage = 1 if technology == "wind_2024": - percentage = int(kwargs["id_s_w_6"]) / 100 + percentage = float(kwargs["id_s_w_6"]) / 100 if technology == "wind_2027": - percentage = int(kwargs["id_s_w_7"]) / 100 + percentage = float(kwargs["id_s_w_7"]) / 100 return { "area": areas[technology] / 100 * percentage, "turbines": potential_capacities[technology] / nominal_power_per_unit * percentage, From b7e3c16788cdbc54edb594ee88c5697170acae21 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 10:58:03 +0200 Subject: [PATCH 16/25] Fix detail key results parameters from request --- digiplan/map/views.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 47374fc0..059d2c79 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -194,7 +194,4 @@ class DetailKeyResultsView(TemplateView): def get_context_data(self, **kwargs) -> dict: # noqa: ARG002 """Get detail key results for requested technology.""" - technology = self.request.GET["technology"] - return { - f"key_result_{key}": value for key, value in menu.detail_key_results(technology, **self.request.GET).items() - } + return {f"key_result_{key}": value for key, value in menu.detail_key_results(**self.request.GET.dict()).items()} From 2560d47289f35cd339f63ccd5d96da177e6eaba2 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 10:58:32 +0200 Subject: [PATCH 17/25] Update detail key results on slider changes --- .jshintrc | 3 +- digiplan/static/js/sliders.js | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index 591e845d..58a1441a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -36,7 +36,8 @@ "subscribeToEvents": false, "createChart": false, "clearChart": false, - "hidePotentialLayers": false + "hidePotentialLayers": false, + "URLSearchParams": false }, "strict": "implied" } diff --git a/digiplan/static/js/sliders.js b/digiplan/static/js/sliders.js index b5466046..6114c6b6 100644 --- a/digiplan/static/js/sliders.js +++ b/digiplan/static/js/sliders.js @@ -92,6 +92,7 @@ PubSub.subscribe( PubSub.subscribe(eventTopics.PANEL_SLIDER_CHANGE, hidePotentialLayers); PubSub.subscribe(eventTopics.PANEL_SLIDER_CHANGE, adaptDetailSliders); PubSub.subscribe(eventTopics.DETAIL_PANEL_SLIDER_CHANGE, adaptMainSliders); +PubSub.subscribe(eventTopics.DETAIL_PANEL_SLIDER_CHANGE, adaptDetailKeyResults); PubSub.subscribe( eventTopics.MORE_LABEL_CLICK, showOrHideSidepanelsOnMoreLabelClick, @@ -407,6 +408,65 @@ function highlightPVMapControls(msg) { return logMessage(msg); } +function adaptDetailKeyResults(msg, data) { + const slider_id = data.input[0].id; + let technology; + let target; + let url_data = {}; + + if (slider_id === "id_s_w_6") { + technology = "wind_2024"; + url_data.id_s_w_6 = data.from; + target = "wind_key_results_2024"; + } else if (slider_id === "id_s_w_7") { + technology = "wind_2027"; + url_data.id_s_w_7 = data.from; + target = "wind_key_results_2027"; + } else if ( + ["id_s_pv_ff_3", "id_s_pv_ff_4", "id_s_pv_ff_5"].includes(slider_id) + ) { + technology = "pv_ground"; + url_data.id_s_pv_ff_3 = + $("#id_s_pv_ff_3").data("ionRangeSlider").result.from; + url_data.id_s_pv_ff_4 = + $("#id_s_pv_ff_4").data("ionRangeSlider").result.from; + url_data.id_s_pv_ff_5 = + $("#id_s_pv_ff_5").data("ionRangeSlider").result.from; + target = "pv_ground_key_results"; + } else if (slider_id === "id_s_pv_d_3") { + technology = "pv_roof"; + url_data.id_s_pv_d_3 = data.from; + target = "pv_roof_key_results"; + } else { + return logMessage(msg); + } + + const query = new URLSearchParams(url_data).toString(); + let url = `/detail_key_results?technology=${technology}&${query}`; + fetch(url) + .then((response) => { + // Check if the response is successful + if (!response.ok) { + throw new Error( + `Error requesting detail key results for slider ${slider_id}`, + ); + } + // Return the response as HTML + return response.text(); + }) + .then((html) => { + // Insert the HTML into the DOM + document.getElementById(target).innerHTML = html; + }) + .catch((error) => { + console.error( + `Error requesting detail key results for slider ${slider_id}:`, + error, + ); + }); + return logMessage(msg); +} + // Helper Functions function getColorsByIds(ids) { From 1c84cc8209873ac2e3f21d51f2721af2941e1ff1 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 10:59:34 +0200 Subject: [PATCH 18/25] Change slider callback events to "onFinish" to reduce callbacks due to rapid changing --- digiplan/static/js/sliders.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/digiplan/static/js/sliders.js b/digiplan/static/js/sliders.js index 6114c6b6..640af9a9 100644 --- a/digiplan/static/js/sliders.js +++ b/digiplan/static/js/sliders.js @@ -39,19 +39,19 @@ const pvMapControl = document.getElementsByClassName("map__layers-pv")[0]; // Setup $(".js-slider.js-slider-panel.js-power-mix").ionRangeSlider({ - onChange: function (data) { + onFinish: function (data) { PubSub.publish(eventTopics.POWER_PANEL_SLIDER_CHANGE, data); }, }); $(".js-slider.js-slider-panel").ionRangeSlider({ - onChange: function (data) { + onFinish: function (data) { PubSub.publish(eventTopics.PANEL_SLIDER_CHANGE, data); }, }); $(".js-slider.js-slider-detail-panel").ionRangeSlider({ - onChange: function (data) { + onFinish: function (data) { PubSub.publish(eventTopics.DETAIL_PANEL_SLIDER_CHANGE, data); }, }); From 53cf9c50988f162598e77dad407d6b5cd7295ab5 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 18 Apr 2024 15:39:19 +0200 Subject: [PATCH 19/25] Update poetry lock --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index ddd37e52..6dc2be50 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3820,4 +3820,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "79ed8b7549efce9000f41b17c4a28f561f047d842f8960642727018b151b834d" +content-hash = "c598d0d2ed1130eb6716ea03611be35d3c849a30041ea0c283b4fa25cbb06af8" From 17cbd237c22b434b1a417ad54cbd1f48ea85322d Mon Sep 17 00:00:00 2001 From: nesnoj Date: Thu, 18 Apr 2024 16:29:10 +0200 Subject: [PATCH 20/25] Amend docstring --- digiplan/map/datapackage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index af16a620..ea13f8e9 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -207,7 +207,7 @@ def get_potential_areas(technology: Optional[str] = None) -> dict: Returns ------- dict - Potential areas of all technologies or specified one + Potential areas of all technologies or specified one (in sqkm) """ sources = { "wind_2018": Source("potentialarea_wind_area_stats_muns.csv", "stp_2018_eg"), From be904f93faa27517a116eb42ee77aed7853b4856 Mon Sep 17 00:00:00 2001 From: nesnoj Date: Thu, 18 Apr 2024 16:29:54 +0200 Subject: [PATCH 21/25] Fix key result calc for PV ground --- digiplan/map/menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index d8f41e62..d019ca5b 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -29,11 +29,11 @@ def detail_key_results(technology: str, **kwargs: dict) -> dict: } flh_mapping = { "pv_soil_quality_low": "pv_ground", - "pv_soil_quality_medium": "pv_ground_elevated", - "pv_permanent_crops": "pv_ground_vertical_bifacial", + "pv_soil_quality_medium": "pv_ground_vertical_bifacial", + "pv_permanent_crops": "pv_ground_elevated", } return { - "area": sum(areas[pv_type] * percentages[pv_type] for pv_type in percentages), + "area": sum(areas[pv_type] * 100 * percentages[pv_type] for pv_type in percentages), "energy": sum( potential_capacities[pv_type] * full_load_hours[flh_mapping[pv_type]] * percentages[pv_type] for pv_type in percentages From 4b5709e62f5e35ee279ad9d8434e94fc7b0a5b14 Mon Sep 17 00:00:00 2001 From: nesnoj Date: Thu, 18 Apr 2024 16:30:25 +0200 Subject: [PATCH 22/25] Reduce fp precision in key results --- digiplan/templates/forms/panel_energy.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index b7fc4b61..588c807c 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -7,7 +7,7 @@
- {{ key_result_area | floatformat:2 }} ha + {{ key_result_area | floatformat:0 }} ha
@@ -31,7 +31,7 @@
- {{ key_result_energy | floatformat:2 }} TWh + {{ key_result_energy | floatformat:1 }} TWh
From bb090f83648d7c9f7e26de52f91ebb14e0da831e Mon Sep 17 00:00:00 2001 From: nesnoj Date: Thu, 18 Apr 2024 17:22:53 +0200 Subject: [PATCH 23/25] Fix key result calc for wind --- digiplan/map/menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index d019ca5b..5ae7010a 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -13,11 +13,11 @@ def detail_key_results(technology: str, **kwargs: dict) -> dict: if technology.startswith("wind"): percentage = 1 if technology == "wind_2024": - percentage = float(kwargs["id_s_w_6"]) / 100 + percentage = float(kwargs["id_s_w_6"]) / float(config.ENERGY_SETTINGS_PANEL["s_w_6"]["max"]) if technology == "wind_2027": percentage = float(kwargs["id_s_w_7"]) / 100 return { - "area": areas[technology] / 100 * percentage, + "area": areas[technology] * 100 * percentage, "turbines": potential_capacities[technology] / nominal_power_per_unit * percentage, "energy": potential_capacities[technology] * full_load_hours["wind"] * percentage * 1e-6, } From ede4085bf4f52d463ab51a80de3682afccb6b7f4 Mon Sep 17 00:00:00 2001 From: nesnoj Date: Thu, 18 Apr 2024 18:45:25 +0200 Subject: [PATCH 24/25] Fix key result calc for PV roof --- digiplan/map/datapackage.py | 6 ++---- digiplan/map/menu.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index ea13f8e9..690d5d6e 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -178,6 +178,7 @@ def get_potential_values() -> dict: "pv_soil_quality_low": "pv_ground", "pv_soil_quality_medium": "pv_ground_vertical_bifacial", "pv_permanent_crops": "pv_ground_elevated", + "pv_roof": "pv_roof", } power_density = json.load(Path.open(Path(settings.DIGIPIPE_DIR, "scalars/technology_data.json")))["power_density"] @@ -186,9 +187,6 @@ def get_potential_values() -> dict: for key in areas: if key.startswith("wind"): potentials[key] = areas[key] * power_density["wind"] - if key == "pv_roof": - potentials[key] = areas[key] - continue if key.startswith("pv"): potentials[key] = areas[key] * power_density[pv_density[key]] return potentials @@ -215,7 +213,7 @@ def get_potential_areas(technology: Optional[str] = None) -> dict: "pv_soil_quality_low": Source("potentialarea_pv_ground_area_stats_muns.csv", "soil_quality_low_region"), "pv_soil_quality_medium": Source("potentialarea_pv_ground_area_stats_muns.csv", "soil_quality_medium_region"), "pv_permanent_crops": Source("potentialarea_pv_ground_area_stats_muns.csv", "permanent_crops_region"), - "pv_roof": Source("potentialarea_pv_roof_area_stats_muns.csv", "installable_power"), + "pv_roof": Source("potentialarea_pv_roof_area_stats_muns.csv", "roof_area_pv_potential_sqkm"), } # Add wind for 2027 directly from model data, as it is not included in datapackage diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index 5ae7010a..73cecbba 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -43,7 +43,7 @@ def detail_key_results(technology: str, **kwargs: dict) -> dict: if technology == "pv_roof": percentage = int(kwargs["id_s_pv_d_3"]) / 100 return { - "area": areas[technology] * percentage, + "area": areas[technology] * 100 * percentage, "energy": potential_capacities[technology] * full_load_hours[technology] * percentage * 1e-6, } raise KeyError(f"Unknown technology '{technology}'.") From 69bd9634d1a805012682cf0fba194eb257709821 Mon Sep 17 00:00:00 2001 From: nesnoj Date: Tue, 23 Apr 2024 10:01:45 +0200 Subject: [PATCH 25/25] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a069def5..e0f1f9d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project tries to adhere to [Semantic Versioning](https://semver.org/spe ### Added - coupling of duplicated map panel controls - dependabot +- key results for wind, pv ground and pv roof settings panels ### Changed - Adapt municipality label font size according to zoom level