/* LDD mapping library
Copyright (C) 2022 Cliff Hammett
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function lddPlanningMapLayer(meta, file, id, title, defaultLegend, defaultColor){
var self = this;
self.meta = meta; // containts leaflet, map, doc [i.e. the page], tabs, pointer
self.id = id;
self.loadState = "Loading.";
self.totalimpact = 0;
self.borZoom = 12; // Zoom level when we focus on a single borough.
self.defaultLegend = defaultLegend; // Starting labels for applications on maps.
self.defaultColor = defaultColor; // Starting color of applications on map
self.housingTotals = [];
self.searchSet = []; //List of planning applications that meet filter criteria
//(used by the search list)
self.title = title; //Title of this layer
self.activeInnerTab = "tabFilter";//Which of the layer's inner tabs are we using (defaults to
//start with fiters
self.state = { ready: false, //Is this layer ready to be interacted with?
active: true, //Is this the active layer?
visible: true //Is this layer visible
}
self.appliedFilters = [];
self.mark = []; //sets up list of 'marks' e.g. planning applications on map
//self.marklabel = []
self.file = file;
self.st = {lat:51.5073219, lon:-0.1276474, zoom:11}; //starting location of map
self.colors = {lg: {base: "rgb(150,255,160)", detail: "rgb(100,205,110)", text: "rgb(50,155,60)", weight: 1},
dg: {base: "rgb(60,200,70)", detail: "rgb(30,150,35)", text: "rgb(0,100,5)", weight: 1},
lb: {base: "rgb(120,120,255)", detail: "rgb(70,70,205)", text: "rgb(20,20,155)", weight: 1},
o: {base: "rgb(240,170,30)", detail: "rgb(190, 120, 15)", text: "rgb(140,70,0)", weight: 1},
pk: {val: "rgb(240,100,140)", detail: "rgb(190,50,90)", text: "rgb(140,15,45)", weight: 1},
pp: {val: "rgb(180,30,200)", detail: "rgb(130,15,150)", text: "rgb(85,0,100)", weight: 1}
}; // Sets up values for color codes
setupCtls();
function setupCtls(){
/* sets up all the interface controls for this layer as lists, for later processing when controls are outputted
*/
var txtFieldCtl = { label:"Search", classname: "dropdown", idName: "txtFieldDropdown" };
var txtFieldItems = [ {val:"descr", itemName: "Description"},
{val:"address", itemName: "Address"},
{val:"borough_ref", itemName: "BoroughID"} ];
var statCtl = { label:"Status", className: "dropdown", idName: "statusDropdown" };
var statItems = [ {val:"1", itemName:"All applications"},
{val:"2", itemName:"In process"},
{val:"3", itemName:"Completed"} ];
var legCtl = { label:"Legend", className: "dropdown", idName: "legDropdown" };
var legItems = [ {val:"housingPcShift", itemName: "Change to housing balance"},
{val:"housingImpact", itemName: "Net housing change"}
];
var colCtl = { label:"Colour", className: "dropdown", idName: "colDropdown" };
var colItems = [ {val: "lg", itemName: "Light Green"},
{val: "dg", itemName: "Dark Green"},
{val: "lb", itemName: "Light Blue"},
{val: "o", itemName: "Orange"},
{val: "pk", itemName: "Pink"},
{val: "pp", itemName: "Purple"}
];
var sortByCtl = { label:"Order by", className: "dropdown", idName: "sortByDropdown" };
var sortByItems = [ {val:"permission_date", itemName: "Permission Date"},
{val:"borough_name", itemName: "Borough"},
{val:"post_code", itemName: "Postcode"},
{val:"housingImpact", itemName: "Housing Impact"}
];
var sortOrderCtl = { label:"", className: "dropdown", idName: "sortOrderDropdown" };
var sortOrderItems = [ {val:"asc", itemName: "Ascending"},
{val:"desc", itemName: "Descending"}
];
var appStageCtl = {label:"Application stage", className: "dropdown", idName: "appStageDropdown"};
var appStageItems = [ {val:"0", itemName: "Loading"}];
var borCtl = { label:"Borough", className: "dropdown", idName: "borDropdown" };
self.borItems = getBoroughList(self.borZoom);
self.headerInterface = [
{ id:"shDataHeader",
html:"<p><div id='shDataHeader'><strong>Data: " + self.title + "</strong></div></p>",
type:"head"},
{ id:"shSrcHeader",
html:"<p><div id='shSrcHeader'><strong>Source: London Development Database, GLA</strong></div><div class='system' id='systemMsg'></div></p>",
type:"head"},
{ id: "infoExaminedHousingType",
html: "<div id='infoExaminedHousingType'></div>",
type: "info" },
{ id:legCtl.idName,
html: "<p>" + buildHTMLdropdown(legCtl, legItems),
value: self.defaultLegend,
refresh: function(){self.refreshMarks();},
type: "control"},
{ id: colCtl.idName,
html: " " + buildHTMLdropdown(colCtl, colItems) + "</p>",
value: self.defaultColor,
refresh: function(){self.refreshMarks();},
type:"control"},
{ id:"tbShowLabels",
html:"<p><input type='checkbox' id='tbShowLabels' checked><strong> Show labels</strong></p>",
value: "checked",
refresh: function(){self.refreshMarks();},
type:"control"},
{ id:"shLoad",
html:"<div id='shLoad'></div>",
type:"info"},
{ id:"infoAppliedFilters",
html:"<div id='infoAppliedFilters></div>",
type:"info"},
{ id:"miniTabCtl",
html:"<div id='miniTabCtl></div>",
type:"tab"
}
]
self.innerTabInterface =
[
{
id: "tabFilter",
label: "Filters",
onclick: function(){ self.activateInnerTab("tabFilter");},
ctls:[
{ id:"shFilter",
html:"<div id='shFilter'><h3>Filter</h3></div>",
type:"head"},
{ id:txtFieldCtl.idName,
html: "<p>" + buildHTMLdropdown(txtFieldCtl, txtFieldItems),
value: "borough_ref",
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:"txtSearch",
html:"<strong> for: </strong><input type='text' id='txtSearch'></p>",
value: "",
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:statCtl.idName,
html:"<p>" + buildHTMLdropdown(statCtl, statItems) + "</p>",
value:1,
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:borCtl.idName,
html:"<p>" + buildHTMLdropdown(borCtl, self.borItems) + "</p>",
value:"All Boroughs",
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:"datFrom",
html:"<p><strong>From:</strong> <input type='date' id='datFrom' value='2006-01-01' min='2006-01-01' max='2020-12-31'>",
value:"2006-01-01",
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:"datTo",
html:" <strong>To:</strong> <input type='date' id='datTo' value='2019-12-31' min='2006-01-01' max='2019-12-31'></p>",
value:"2019-12-31",
refresh: function(){ self.storeTabValues("tabFilter");},
type:"control"},
{ id:"shiSlider",
html:"<p><div id='shiOutput'><strong>Minimum Housing Impact:</strong> 0</div>" +
"<input type='range' min='0' max='150' value='0' class='slider' id='shiSlider'></p>",
value:0,
refresh: function(){ self.storeTabValues("tabFilter");
var shihtml = "<strong>Minimum Housing Impact:</strong> " + self.meta.doc.getElementById("shiSlider").value;
self.meta.doc.getElementById("shiOutput").innerHTML = shihtml;
},
type:"control" },
{ id: "applyBtn",
html: "<button type='button' id='applyBtn' disabled>Apply</button>",
disabledState: true,
onclick: function(){ self.refreshMarks();
self.centreOnGeoUnit();
self.setDisabledStateFilterAppBtn(true)},
type: "button"
}
]
},
{
id: "tabSummary",
label: "Summary",
onclick: function(){ self.activateInnerTab("tabSummary");
self.displaySummary();},
ctls: [
{id:"shSumm", html:"<div id='shSumm'><h3>Summary Data</h3></div>", type:"head"},
{id:"summaryData", html:"<div id='summaryData' class='innerTabScrollBox'></div>", type:"info"}
]
},
{
id: "tabListing",
label: "List",
onclick: function(){ self.activateInnerTab("tabListing");
self.displaySearchList();},
ctls: [
{id:"shList", html:"<div id='shList'><h3>Application List</h3></div>", type:"head"},
{ id:sortByCtl.idName,
html:"<p>" + buildHTMLdropdown(sortByCtl, sortByItems),
value:"permission_date",
refresh: function(){ self.storeTabValues("tabListing");
self.displaySearchList();},
type:"control"},
{ id:sortOrderCtl.idName,
html:" " + buildHTMLdropdown(sortOrderCtl, sortOrderItems) + "</p>",
value: "asc",
refresh: function(){ self.storeTabValues("tabListing");
self.displaySearchList();},
type:"control"},
{id:"searchList", html:"<div id='searchList' class='innerTabScrollBox'></div>", type:"info"}
]
},
{
id: "tabDetails",
label: "Details",
onclick: function(){ self.activateInnerTab("tabDetails");
self.displayDetails(self.examinedApp, true)},
ctls: [
{id:"shDetails", html:"<div id='shDetails'><h3>Details of the Application</h3></div>", type:"info"},
{id:"divOpenAppDetails", html:"<div id='divOpenAppDetails' class='innerTabScrollBox'>"},
{id:"coreAppDetails", html:"<span id='coreAppDetails'></span>", type:"info"},
{id:"shAppStageDetails", html:"<span id='shAppStageDetails'><h3>Application Stages</h3></span>", type:"info"},
{ id:appStageCtl.idName,
html:"<p>" + buildHTMLdropdown(appStageCtl, appStageItems) + "</p>",
value:"0",
refresh: function(){ self.storeTabValues("tabDetails");
self.displayAppStage()},
type:"control",
ctlVals:appStageCtl},
{id:"appStageDetails", html:"<span id='appStageDetails'</span>", type:"info"},
{id:"spanCloseAppDetails", html:"<span id='spanCloseAppDetails></span></div>"}
]
}
];
self.ctlRef = {};
}
function getBoroughList(borZoom){
bor = [
{val:"All Boroughs", itemName:"All Boroughs", lat:51.5073219, lon:-0.1276474, zoom:11, housingImpact:{}},
{val:"Barking and Dagenham", itemName:"Barking and Dagenham", lat:51.5544867, lon:0.1498537, zoom:borZoom, housingImpact:{}},
{val:"Barnet", itemName:"Barnet", lat:51.6135636, lon:-0.2112689, zoom:borZoom, housingImpact:{}},
{val:"Bexley", itemName:"Bexley", lat:51.4625359, lon:0.1424610, zoom:borZoom, housingImpact:{}},
{val:"Brent", itemName:"Brent", lat:51.5637061, lon:-0.2768436, zoom:borZoom, housingImpact:{}},
{val:"Bromley", itemName:"Bromley", lat:51.3674577, lon:0.0604315, zoom:borZoom, housingImpact:{}},
{val:"Camden", itemName:"Camden", lat:51.5431852, lon:-0.1634566, zoom:borZoom, housingImpact:{}},
{val:"Croydon", itemName:"Croydon", lat:51.3557039, lon:-0.0625286, zoom:borZoom, housingImpact:{}},
{val:"Ealing", itemName:"Ealing", lat:51.5252635, lon:-0.3140294, zoom:borZoom, housingImpact:{}},
{val:"Enfield", itemName:"Enfield", lat:51.6484796, lon:-0.0815612, zoom:borZoom, housingImpact:{}},
{val:"Greenwich", itemName:"Greenwich", lat:51.4677524, lon:0.0472697, zoom:borZoom, housingImpact:{}},
{val:"Hackney", itemName:"Hackney", lat:51.5493291, lon:-0.0480898, zoom:borZoom, housingImpact:{}},
{val:"Hammersmith and Fulham", itemName:"Hammersmith and Fulham", lat:51.4981468, lon:-0.2284865, zoom:borZoom, housingImpact:{}},
{val:"Haringey", itemName:"Haringey", lat:51.5877242, lon:-0.1054813, zoom:borZoom, housingImpact:{}},
{val:"Harrow", itemName:"Harrow", lat: 51.5979317, lon:-0.3370282, zoom:borZoom, housingImpact:{}},
{val:"Havering", itemName:"Havering", lat:51.5583163, lon:0.2491486, zoom:borZoom, housingImpact:{}},
{val:"Hillingdon", itemName:"Hillingdon", lat:51.5430928, lon:-0.4485393, zoom:borZoom, housingImpact:{}},
{val:"Hounslow", itemName:"Hounslow", lat:51.4620518, lon:-0.3802763, zoom:borZoom, housingImpact:{}},
{val:"Islington", itemName:"Islington", lat:51.5475893, lon:-0.0995679, zoom:borZoom, housingImpact:{}},
{val:"Kensington and Chelsea", itemName:"Kensington and Chelsea", lat:51.5041000, lon:-0.2008312, zoom:borZoom, housingImpact:{}},
{val:"Kingston upon Thames", itemName:"Kingston upon Thames", lat:51.3817485, lon:-0.2762724, zoom:borZoom, housingImpact:{}},
{val:"Lambeth", itemName:"Lambeth", lat:51.4601959, lon:-0.1224009, zoom:borZoom, housingImpact:{}},
{val:"Lewisham", itemName:"Lewisham", lat:51.4524393, lon:-0.0152435, zoom:borZoom, housingImpact:{}},
{val:"Merton", itemName:"Merton", lat:51.4120432, lon:-0.1856680, zoom:borZoom, housingImpact:{}},
{val:"Newham", itemName:"Newham", lat:51.5296578, lon:0.0308328, zoom:borZoom, housingImpact:{}},
{val:"Redbridge", itemName:"Redbridge", lat:51.5870039, lon:0.0692017, zoom:borZoom, housingImpact:{}},
{val:"Richmond upon Thames", itemName:"Richmond upon Thames", lat:51.4413272, lon:-0.3078970, zoom:borZoom, housingImpact:{}},
{val:"Southwark", itemName:"Southwark", lat:51.4658988, lon:-0.0691303, zoom:borZoom, housingImpact:{}},
{val:"Sutton", itemName:"Sutton", lat:51.3571256, lon:-0.1735669, zoom:borZoom, housingImpact:{}},
{val:"Tower Hamlets", itemName:"Tower Hamlets", lat:51.5147531, lon:-0.0351481, zoom:borZoom, housingImpact:{}},
{val:"Waltham Forest", itemName:"Waltham Forest", lat:51.5983970, lon:-0.0171161, zoom:borZoom, housingImpact:{}},
{val:"Wandsworth", itemName:"Wandsworth", lat:51.4526576, lon:-0.2000280, zoom:borZoom, housingImpact:{}},
{val:"Westminster", itemName:"Westminster", lat:51.5150880, lon:-0.1593473, zoom:borZoom, housingImpact:{}}
];
return bor;
}
function resetBorListImpact(crit){
lowYear = Number(crit.dtlow.substring(0,4));
highYear = Number(crit.dthigh.substring(0,4));
self.summYearRange = [lowYear, highYear];
self.borItems = getBoroughList(self.borZoom);
for(var i=0; i<self.borItems.length; i++){
for(var y=lowYear; y<=highYear; y++){
ys = y.toString();
self.borItems[i].housingImpact[ys] = 0;
}
}
}
function setupCats(meta){
self.resCat = meta.resCat;
self.resCat[null] = "Unclassified";
self.spaceCat = meta.osCat;
self.spaceCat[null] = "Unclassified";
self.planCat = meta.puCat;
self.planCat[null] = "Unclassified";
}
self.centreOnGeoUnit = function(){
var val = self.ctlRef.borDropdown.options[self.ctlRef.borDropdown.selectedIndex].value;
var lat = self.st.lat;
var lon = self.st.lon;
var z = self.st.zoom;
for (var i=0; i<self.borItems.length; i++){
if (self.borItems[i].val == val){
lat = self.borItems[i].lat;
lon = self.borItems[i].lon;
z = self.borItems[i].zoom;
}
}
self.meta.map.setView([lat,lon],z);
}
self.setVisible = function(bol){
self.state.visible = bol;
if(bol){
self.refreshMarks();
}else{
self.removeMarks();
}
}
self.getData = function(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4) {
self.meta.doc.getElementById("systemMsg").innerHTML = "";
initMarkers(this.responseText);
}else if (this.readyState == 3){
self.meta.doc.getElementById("systemMsg").innerHTML = " Loading data...";
}else if (this.readyState == 2){
self.meta.doc.getElementById("systemMsg").innerHTML = " Loading data..";
}else if (this.readyState == 1){
self.meta.doc.getElementById("systemMsg").innerHTML = " Loading data.";
}
};
xmlhttp.open("POST", self.file, true);
xmlhttp.send();
}
self.outputControls = function(){
var rtn = "";
for(var i=0; i<self.headerInterface.length; i++){
rtn += self.headerInterface[i].html;
}
rtn += self.outputInnerTabInterface();
return rtn;
}
self.outputInnerTabInterface = function(){
var rtn = "";
for(var i=0; i<self.innerTabInterface.length; i++){
console.log("making button for : " + self.innerTabInterface[i].id);
rtn += "<button class='tabLinks' id='" + self.innerTabInterface[i].id + "' disabled>" + self.innerTabInterface[i].label + "</button>";
}
rtn += "<div id='innerTabDisplay' class='innerTab'></div>";
return rtn;
}
self.activateInnerTab = function(tabId){
console.log("Activating inner control tabs");
self.activeInnerTab = tabId;
var tabHtml = self.outputActiveTab();
self.meta.doc.getElementById(tabId).disabled = false;
self.meta.doc.getElementById("innerTabDisplay").innerHTML = tabHtml;
self.initInnerTab();
}
self.outputActiveTab = function(){
var tabCtls = "";
for(var i=0; i<self.innerTabInterface.length; i++){
if (self.innerTabInterface[i].id == self.activeInnerTab){
console.log("Correct inner tab found");
for(var j=0; j<self.innerTabInterface[i].ctls.length; j++){
var ctlhtml = self.innerTabInterface[i].ctls[j].html;
console.log("html is : " + ctlhtml);
tabCtls += ctlhtml;
}
}else{
console.log(self.innerTabInterface[i].id + " does not equal " + self.activeInnerTab + " at all");
}
}
return tabCtls;
}
// Once we have outputted the controls, we need to be able to read them. Is there a better way? Almost certainly yes.
self.initControls = function(doc){
for (var i=0; i<self.headerInterface.length; i++){
console.log(self.headerInterface[i].id);
self.headerInterface[i].ctl = doc.getElementById(self.headerInterface[i].id);
self.ctlRef[self.headerInterface[i].id] = self.headerInterface[i].ctl;
//console.log(self.ctls[i].id + " added");
if (self.headerInterface[i].type == "control"){
self.headerInterface[i].ctl.value = self.headerInterface[i].value;
self.headerInterface[i].ctl.oninput = self.headerInterface[i].refresh;
}
}
for (var i=0; i<self.innerTabInterface.length; i++){
var tabBtn = self.meta.doc.getElementById(self.innerTabInterface[i].id);
tabBtn.onclick = self.innerTabInterface[i].onclick;
}
}
self.initInnerTab = function(){
for(var i=0; i<self.innerTabInterface.length; i++){
if (self.innerTabInterface[i].id == self.activeInnerTab){
console.log("Active inner tab readying for initiation");
self.meta.doc.getElementById(self.innerTabInterface[i].id).innerHTML = "<strong>" + self.innerTabInterface[i].label + "</strong>";
for(var j=0; j<self.innerTabInterface[i].ctls.length; j++){
self.innerTabInterface[i].ctls[j].ctl = self.meta.doc.getElementById(self.innerTabInterface[i].ctls[j].id);
console.log("Adding " + self.innerTabInterface[i].ctls[j].id)
self.ctlRef[self.innerTabInterface[i].ctls[j].id] = self.innerTabInterface[i].ctls[j].ctl;
//console.log(self.ctls[i].id + " added");
if (self.innerTabInterface[i].ctls[j].type == "control"){
self.innerTabInterface[i].ctls[j].ctl.value = self.innerTabInterface[i].ctls[j].value;
self.innerTabInterface[i].ctls[j].ctl.oninput = self.innerTabInterface[i].ctls[j].refresh;
}else if(self.innerTabInterface[i].ctls[j].type == "button"){
self.innerTabInterface[i].ctls[j].ctl.disabled = self.innerTabInterface[i].ctls[j].disabledState;
self.innerTabInterface[i].ctls[j].ctl.onclick = self.innerTabInterface[i].ctls[j].onclick;
}
}
}else{
self.meta.doc.getElementById(self.innerTabInterface[i].id).innerHTML = self.innerTabInterface[i].label;
}
}
if (self.activeInnerTab == "tabFilter"){
var shihtml = "<strong>Minimum Housing Impact:</strong> " + self.meta.doc.getElementById("shiSlider").value;
self.meta.doc.getElementById("shiOutput").innerHTML = shihtml;
}
}
function buildHTMLdropdown(ctl, arr){
rtn = "\t<strong>" + ctl.label + ": </strong>\n";
rtn += "\t<select class='" + ctl.className + "' name='" + ctl.idName + "' id = '" + ctl.idName + "'>";
for(var i=0; i<arr.length; i++){
rtn += "\t\t<option value='" + arr[i].val + "'>" + arr[i].itemName + "</option>\n";
}
rtn += "</select>";
return rtn;
}
function initMarkers(rawdata){
self.data = JSON.parse(rawdata);
self.state.ready = true;
self.loadMarks();
}
function findBoroughItem(strName){
var found = -1;
for (var i=0; i<self.borItems.length; i++){
if (self.borItems[i].val == strName){
found = i;
}
}
return found;
}
self.loadMarks = function(){
//console.log("adding map marks");
crit = self.getCriteria();
self.searchSet = [];
resetBorListImpact(crit);
self.totalimpact = 0;
var meta = self.data.planApps.meta;
setupCats(meta);
self.data.planApps.planApp.sort(compareValues("permission_date"));
for(var i=0; i<self.data.planApps.planApp.length; i++){
var app = self.data.planApps.planApp[i];
if(checkIfIncluded(app, crit)){
self.totalimpact += parseInt(app["housingImpact"]);
permYear = app.permission_date.substring(0,4);
borNo = findBoroughItem(app.borough_name);
if (borNo >= 0){
console.log("Adding " + app["housingImpact"].toString() + " to " + app.borough_name + permYear);
self.borItems[borNo].housingImpact[permYear] += Number(app["housingImpact"]);
}
var appMark;
appr = self.setMapMarkAppearance(app, crit);
if (app.geoPoly != null){
appMark = self.renderAppMarkAsPoly(appr, app);
}else{
appMark = self.renderAppMarkAsCircle(appr, app);
}
appMark.addTo(self.meta.map);
var se = self.makeSearchEntry(app);
self.searchSet.push(se);
self.mark.push(appMark);
}
}
self.meta.doc.getElementById("tabSummary").disabled = false;
self.meta.doc.getElementById("tabListing").disabled = false;
}
self.makeSearchEntry = function (app){
var div = "<div class='seElement'>";
var appAdd = app.prim_add_obj_name + ", " + app.street + ". " + app.post_code;
var sehtml = div + appAdd + "</div>" +
div + "<strong>Permission date</strong>:" + app.permission_date + "</div>" +
div + "<strong>Status</strong>:" + app.status_rc + "</div>" +
div + "<strong>Borough</strong>:" + app.borough_name + "</div>" +
div + "<strong>Impact</strong>:" + app.housingImpact + "</div>";
var btnv = { id:"btnView-" + app.permission_id,
html:"<button type='button' class='seBtn' id='btnView-" + app.permission_id
+ "'>view</button>",
input: function(){self.gotoEntryOnMap(app, true)}
};
var btng = { id:"btnGoto-" + app.permission_id,
html:"<button type='button' class='seBtn' id='btnGoto-" + app.permission_id
+ "'>goto</button>",
input: function(){
if (self.examinedApp != null){
oldSelEntry = self.meta.doc.getElementById("se" + self.examinedApp.permission_id);
oldSelEntry.classList.add("seDefault");
oldSelEntry.classList.remove("seSelected");
}
self.gotoEntryOnMap(app, false)
newSelEntry = self.meta.doc.getElementById("se" + app.permission_id);
newSelEntry.classList.add("seSelected");
newSelEntry.classList.remove("seDefault");
}
};
/* console.log(btng.id);
console.log(btng.html);
console.log(btnv.id);
console.log(btnv.html);*/
var se = { "lat": app.lat,
"lon": app.lon,
"planapp": app,
"html": sehtml,
"btnGoto": btng,
"btnView": btnv};
return se;
}
self.renderAppMarkAsCircle = function(appr, app){
var labeltext = "<span style='color:" + appr.tc + ";'>" + appr.pn + "</span>"
var appMark = L.circle([app.lat, app.lon], {
color: appr.lc,
opacity: appr.op,
fillColor: appr.fc,
fillOpacity: appr.op,
radius: appr.rd,
planapp: app,
weight: appr.wt
});
appMark.on("click", function(e){
self.meta.tabs.openTabById(self.id);
self.activateInnerTab("tabDetails");
self.examinedApp = e.target.options.planapp;
self.displayDetails(e.target.options.planapp, true);
});
appMark.bindTooltip(labeltext, {permanent: true, direction: "center", offset: [0, 0], opacity: 1, className: "mapLabel" });
return appMark;
}
self.renderAppMarkAsPoly = function(appr, app){
//console.log("Adding poly of site");
var labeltext = "<span style='color:" + appr.tc + ";'>" + appr.pn + "</span>"
var poly = app.geoPoly.point;
var ll = [];
for (var i=0; i< poly.length; i++){
ll.push([poly[i].lat, poly[i].lon]);
}
var appMark = L.polygon(ll, {
color: appr.lc,
opacity: appr.op,
fillColor: appr.fc,
fillOpacity: appr.filop,
planapp: app,
weight: appr.wt
});
appMark.on("click", function(e){
self.meta.tabs.openTabById(self.id);
self.activateInnerTab("tabDetails");
self.examinedApp = e.target.options.planapp;
self.displayDetails(e.target.options.planapp, true);
});
appMark.bindTooltip(labeltext, {permanent: true, direction: "center", offset: [0, 0], opacity: 1, className: "mapLabel" });
return appMark;
}
self.storeTabValues = function(tabId){
for (var i=0; i<self.innerTabInterface.length; i++){
if (self.innerTabInterface[i].id == tabId){
for (var j=0; j<self.innerTabInterface[i].ctls.length; j++){
if (self.innerTabInterface[i].ctls[j].type == "control"){
self.innerTabInterface[i].ctls[j].value = self.innerTabInterface[i].ctls[j].ctl.value;
}else if(self.innerTabInterface[i].ctls[j].id == "applyBtn"){
self.innerTabInterface[i].ctls[j].disabledState = false;
}
}
}
}
self.ctlRef["applyBtn"].disabled = false;
}
self.setDisabledStateFilterAppBtn = function(bol){
for (var i=0; i<self.innerTabInterface.length; i++){
if (self.innerTabInterface[i].id == "tabFilter"){
for (var j=0; j<self.innerTabInterface[i].ctls.length; j++){
if(self.innerTabInterface[i].ctls[j].id == "applyBtn"){
self.innerTabInterface[i].ctls[j].disabledState = bol;
}
}
}
}
self.ctlRef["applyBtn"].disabled = bol;
}
self.refreshMarks = function(){
for (var i=0; i<self.headerInterface.length; i++){
if(self.headerInterface[i].type == "control"){
console.log(self.headerInterface[i].id + ":" + self.headerInterface[i].ctl.value);
self.headerInterface[i].value = self.headerInterface[i].ctl.value;
}
// self.ctls[i].value
}
if(self.state.ready && self.state.visible){
self.removeMarks();
self.loadMarks();
}
}
self.removeMarks = function(){
//console.log("removing map marks");
for(var i=0; i<self.mark.length; i++){
if (self.mark[i]) { // check
self.meta.map.removeLayer(self.mark[i]); // remove
}
}
self.mark = [];
}
self.setMapMarkAppearance = function (pt, crit){
var ck = crit["color"];
var textColor = self.colors[ck].text;
var lineColor = self.colors[ck].detail;
var fillColor = self.colors[ck].base;
var weight = self.colors[ck].weight;
var ve = "";
var opac = 0.6;
var fillopac = 0.4;
var rad = 12;
if(checkIfIncluded(pt, crit)){
var impact = pt[crit.legend]
if (crit.legend == "housingPcShift" && crit.showlabels){
ve = Math.round(impact * 100).toString() + "%";
}else if (crit.showlabels){
ve = Math.round(impact).toString();
}
if (impact < 0){
lineColor = 'red';
textColor = 'red';
}else if (impact == 0){
lineColor = fillColor;
textColor = self.colors[ck].detail;
}else if (crit.showlabels){
ve = "+" + ve;
}
if (pt.status_rc == 'COMPLETED'){
opac = 0.75;
fillopac = 0.6;
}
var shig = Math.sqrt(pt[crit.legend] * pt[crit.legend]);
if (shig - crit.shi > 100){
rad = 50;
}else if (shig - crit.shi > 20){
rad = 25;
}
}
mma = {tc: textColor, lc:lineColor, fc: fillColor, op: opac, filop: fillopac, rd: rad, pn: ve, wt: weight};
return mma;
}
function compareValues(key, order = 'asc') {
return function innerSort(a, b) {
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
// property doesn't exist on either object
return 0;
}
const varA = (typeof a[key] === 'string')
? a[key].toUpperCase() : a[key];
const varB = (typeof b[key] === 'string')
? b[key].toUpperCase() : b[key];
let comparison = 0;
if (varA > varB) {
comparison = 1;
} else if (varA < varB) {
comparison = -1;
}
return (
(order === 'desc') ? (comparison * -1) : comparison
);
};
}
function checkIfIncluded(pt,crit){
var rtn = true;
// var year = parseInt(pt.permission_date.substring(0,4),10)
var lookAtTxt = false;
var txtCheck = pt[crit.txtfield];
if (txtCheck.length > 0 && crit.txtsearch.length > 0){
console.log("Checking text field for " + pt.permission_id);
txtCheck = txtCheck.match(/\w/g).join("").toUpperCase();
crit.txtsearch = crit.txtsearch.match(/\w/g).join("").toUpperCase();
lookAtTxt = true;
}
if (crit.dtlow > pt.permission_date || crit.dthigh < pt.permission_date){
rtn = false;
}else if (crit.borough != "All Boroughs" && crit.borough != pt.borough_name){
rtn = false;
}else if ((crit.stat == "3" && pt.status_rc != "COMPLETED") || (crit.stat == "2" && pt.status_rc != "SUBMITTED" && pt.status_rc != "STARTED") || (pt.status_rc == 'LAPSED') || (pt.status_rc == 'SUPERSEDED')){
rtn = false;
}else if (Math.sqrt(pt.housingImpact * pt.housingImpact) < crit.shi){
rtn = false;
}else if (lookAtTxt){
if (txtCheck.includes(crit.txtsearch) == false){
rtn = false;
}
}else{
//console.log(txtCheck);
//console.log(crit.txtsearch);
//console.log(crit.txtfield);
}
return rtn;
}
self.getCriteria = function(){
var crit = {
showlabels: self.ctlRef["tbShowLabels"].checked,
legend: self.ctlRef["legDropdown"].options[self.ctlRef.legDropdown.selectedIndex].value,
stat: self.ctlRef["statusDropdown"].options[self.ctlRef.statusDropdown.selectedIndex].value,
txtfield: self.ctlRef["txtFieldDropdown"].options[self.ctlRef.txtFieldDropdown.selectedIndex].value,
txtsearch: self.ctlRef["txtSearch"].value,
stat: self.ctlRef["statusDropdown"].options[self.ctlRef.statusDropdown.selectedIndex].value,
borough: self.ctlRef["borDropdown"].options[self.ctlRef.borDropdown.selectedIndex].value,
shi: self.ctlRef["shiSlider"].value,
dtlow: self.ctlRef["datFrom"].value,
dthigh: self.ctlRef["datTo"].value,
color: self.ctlRef["colDropdown"].options[self.ctlRef.colDropdown.selectedIndex].value
};
return crit
}
self.displaySummary = function(){
self.meta.tabs.openTabById(self.id);
var summtext = "<p><strong>Housing impact for designated type:</strong> " + self.totalimpact.toString() + "</p>\n";
summtext += "<table><tr><td></td>";
var ttlYear = {};
for (var y = self.summYearRange[0]; y <= self.summYearRange[1]; y++){
summtext+= "<td>" + y.toString() + "</td>";
ttlYear[y.toString()] = 0;
}
summtext+= "<td>Total</td></tr>\n";
for (var i=1; i < self.borItems.length; i++){ //i starts at 1 to miss 'All Boroughs' item
summtext += "<tr><td>" + self.borItems[i].itemName + "</td>";
var b = self.borItems[i].val;
var ttlBor = 0;
for (var y = self.summYearRange[0]; y <= self.summYearRange[1]; y++){
impact = self.borItems[i].housingImpact[y.toString()];
console.log("Impact at " + y.toString() + " in " + self.borItems[i].itemName + " is " + impact.toString());
summtext += "<td>" + impact.toString() + "</td>";
ttlBor += impact;
ttlYear[y.toString()] += impact;
}
summtext += "<td>" + ttlBor.toString() + "</td></tr>\n";
}
summtext += "<tr><td>Total</td>";
for (var y = self.summYearRange[0]; y <= self.summYearRange[1]; y++){
summtext+= "<td>" + ttlYear[y.toString()].toString() + "</td>";
}
summtext += "</tr></table>\n";
self.ctlRef.summaryData.innerHTML = summtext;
}
self.displaySearchList = function(){
self.meta.tabs.openTabById(self.id);
var sortBy = self.ctlRef["sortByDropdown"].options[self.ctlRef.sortByDropdown.selectedIndex].value;
console.log("Sorting by:" + sortBy);
var sortOrder = self.ctlRef["sortOrderDropdown"].options[self.ctlRef.sortOrderDropdown.selectedIndex].value;
var searchtext = "";
//self.searchSet.sort(function(a, b){return a.planapp[sortBy]});
if (sortBy == "housingImpact"){
self.searchSet.sort(function(a, b){return a.planapp[sortBy]-b.planapp[sortBy]});
}else{
self.searchSet.sort((a, b) => (a.planapp[sortBy] > b.planapp[sortBy]) ? 1 : -1);
}
if (sortOrder == "desc"){
self.searchSet.reverse();
}
if (self.examinedApp == null){
for (var i=0; i<self.searchSet.length; i++){
searchtext += "<searchEntry class='seDefault' id='se" + self.searchSet[i].planapp.permission_id + "'>" + self.searchSet[i].btnView.html + self.searchSet[i].btnGoto.html
+ self.searchSet[i].html + "</searchEntry>\n";
}
}else{
for (var i=0; i<self.searchSet.length; i++){
cssClass = "seDefault";
if (self.searchSet[i].planapp.permission_id == self.examinedApp.permission_id){
console.log("one examined app found");
cssClass = "seSelected";
}
searchtext += "<searchEntry class='" + cssClass + "' id='se" + self.searchSet[i].planapp.permission_id +
"'>" + self.searchSet[i].btnView.html + self.searchSet[i].btnGoto.html + self.searchSet[i].html + "</searchEntry>\n";
}
}
self.ctlRef.searchList.innerHTML = searchtext;
if (self.examinedApp != null){
document.getElementById('se' + self.examinedApp.permission_id).scrollIntoView();
}
self.initSearchButtons();
}
self.initSearchButtons = function(){
for (var i=0; i<self.searchSet.length; i++){
self.searchSet[i].btnView.ctl = self.meta.doc.getElementById(self.searchSet[i].btnView.id);
self.searchSet[i].btnGoto.ctl = self.meta.doc.getElementById(self.searchSet[i].btnGoto.id);
self.searchSet[i].btnView.ctl.onclick = self.searchSet[i].btnView.input;
self.searchSet[i].btnGoto.ctl.onclick = self.searchSet[i].btnGoto.input;
}
}
self.gotoEntryOnMap = function(pt, bolOpen){
var z = 15;
self.meta.map.setView([pt.lat,pt.lon],z);
self.examinedApp = pt;
self.displayDetails(pt, bolOpen);
}
self.displayDetails = function(pt, bolOpen){
self.meta.pointer.refocus(pt.lat, pt.lon);
self.pt = pt;
if (bolOpen){
self.meta.tabs.openTabById(self.id);
self.activateInnerTab("tabDetails");
}
text = "<p><strong>Borough Ref:</strong> " + pt.borough_ref + "</p>";
text += "<p><strong>Address:</strong> " + pt.prim_add_obj_name + " " + pt.street + ", " + pt.post_code + "</p>";
text += "<p><strong>Borough:</strong> " + pt.borough_name + "</p>";
text += "<p><strong>Status: </strong> " + pt.status_rc + "</p>";
text += "<p><strong>Permission granted on:</strong> " + pt.permission_date + "</p>";
if (pt.status_rc == "COMPLETED"){
text += "<p><strong>Completed on: </strong> " + pt.completed_date + "</p>";
} else {
text += "<p><strong>Permission lapses on: </strong>" + pt.permission_lapses_date + "</p>";
}
text += "<p><strong>Description:</strong> " + pt.descr + "</p>";
self.ctlRef.coreAppDetails.innerHTML = text;
self.ctlRef.shAppStageDetails = "<h3>Application Stages</h3>";
var opt = [];
var as = pt.appStages.appStage;
var appstagedd = self.ctlRef.appStageDropdown;
appstagedd.options[0] = null;
if (Array.isArray(as)){
for (var i=0; i < as.length; i++){
// opt.push({value: i.toString(), text: as[i].permission_date + " - " + as[i].borough_ref});
var option = self.meta.doc.createElement('option');
option.text = as[i].permission_date + " - " + as[i].borough_ref
option.value = i.toString();
appstagedd.add(option, i);
}
}else{
var option = self.meta.doc.createElement('option');
option.text = as.permission_date + " - " + as.borough_ref
option.value = "0";
appstagedd.add(option, 0);
}
self.displayAppStage();
}
self.deleteThis = function(){
self.removeMarks();
self = null;
}
self.displayAppStage = function(){
var text = ""
var table = [ {id: "housingTable", title: "Housing Unit", exist: "existingHousing", prop:"proposedHousing", type: "housingtype", cat: self.resCat, unit: "units"},
{id: "openSpaceTable", title: "Hectares of Space", exist: "existingOpenSpace", prop:"proposedOpenSpace", type: "spacetype", cat: self.spaceCat, unit: "hectares"},
{id: "floorspaceTable", title: "Non-residential Floorspace", exist: "existNonResFloorspace", prop:"propNonResFloorspace", type: "planninguseclass", cat: self.planCat, unit: "floorspace"},
{id: "nonResAccomTable", title: "Other Accommodation", exist: "existNonResAccom", prop:"propNonResAccom", type: "planninguseclass", cat: self.planCat, unit: "accom"}
]
asn = Number(self.ctlRef.appStageDropdown.options[self.ctlRef.appStageDropdown.selectedIndex].value);
for (var i=0; i<table.length; i++){
text += makeTable(self.pt, asn, table[i]);
}
self.ctlRef.appStageDetails.innerHTML = text;
}
function makeTable(pt, asn, fld){
var erl = getExistingLines(pt, fld["exist"]);
var existH = getAppLines(erl, fld);
appStages = pt.appStages.appStage;
var prl = getLinesFromAppStages(appStages, asn, fld["prop"]);
var propH = getAppLines(prl, fld);
var ht = "";
if (!!prl || !!erl){
var hl = createChangeList(existH, propH, fld["cat"]);
var ht = makeTableHTML(hl, fld["title"], fld["id"]);
}
return ht;
}
function getExistingLines(pt, type){
var erl;
//console.log("looking for " + type);
if (pt[type] == null){
erl = null;
} else {
erl = pt[type]["line"];
console.log(type + "detected")
}
return erl;
}
function getLinesFromAppStages(appStages, l, type){
var prl;
//console.log("Looking for " + type);
if (Array.isArray(appStages)){
// l = appStages.length - 1;
if (appStages[l][type] == null){
prl = null;
}else{
prl = appStages[l][type]["line"];
}
} else {
if (appStages[type] == null){
prl = null;
}else{
prl = appStages[type]["line"];
}
}
return prl;
}
function getAppLines(rl, fld){
var rh = {};
var type = fld.type;
var unit = fld.unit;
if (Array.isArray(rl)){
for(i=0; i< rl.length; i++){
rh[rl[i][type]] = rl[i][unit];
}
}else if (!!rl){
rh[rl[type]] = rl[unit];
}
return rh;
}
function createChangeList(erh, prh, catset){
var krc = Object.keys(catset);
var hl = [];
//console.log("Creating " + type + " table");
for (var i=0; i<krc.length; i++){
//console.log("calculating " + type + " line");
var ev = 0;
var pv = 0;
var val = false;
if (!!erh[krc[i]]){
ev = erh[krc[i]];
//console.log("Adding existing : " + ev);
val = true;
}
if (!!prh[krc[i]]){
pv = prh[krc[i]];
//console.log("Adding proposed : " + ev);
val = true;
}
if (val){
var l;
l = {
htype: catset[krc[i]],
exist: parseFloat(ev),
prop: parseFloat(pv),
net: pv - ev
}
hl.push(l);
//console.log("type: " + l.htype + "; existing: " + l.exist + "; proposed: " + l.prop + "; net: " + l.net);
}
}
var ttl = { htype: "Total", exist: 0, prop: 0, net: 0 };
for (var i=0; i<hl.length; i++){
//console.log("Datatype...");
//console.log(typeof hl[i].exist);
ttl.exist += parseFloat(hl[i].exist);
ttl.prop += parseFloat(hl[i].prop);
ttl.net += parseFloat(hl[i].net);
}
hl.push(ttl);
return hl;
}
function makeTableHTML(hl, title, id){
//console.log("Making table");
if (id == "openSpaceTable"){
rnd = 3;
console.log("Open space table processed");
}else{
rnd = 0;
console.log("Other table processed");
}
hTable = "<strong>" + title + "</strong>\n<table id='" + id + "'>\n<tr>\n\t<th>Type</th>\n\t<th>Existing</th>\n\t<th>%</th>\n\t<th>Proposed</th>\n\t<th>%</th>\n\t<th>Change</th>\n</tr>\n";
for (i=0; i<hl.length; i++){
existPc = Math.round((hl[i].exist / hl[hl.length-1].exist) *100);
propPc = Math.round((hl[i].prop / hl[hl.length-1].prop) *100);
hTable += "<tr>\n\t<td>" + hl[i].htype + "</td>\n";
hTable += "\t<td>" + hl[i].exist.toFixed(rnd) + "</td>\n";
hTable += "\t<td>" + existPc + "%</td>\n";
hTable += "\t<td>" + hl[i].prop.toFixed(rnd) + "</td>\n";
hTable += "\t<td>" + propPc + "%</td>\n";
var pm = "";
if (hl[i].net > 0){
pm = "+";
}
hTable += "\t<td>" + pm + hl[i].net.toFixed(rnd) + "</td>\n</tr>\n";
}
hTable +="</table>\n";
return hTable;
}
}