// ==UserScript== // @id iitc-plugin-portals-count@yenky // @name IITC plugin: Show total counts of portals // @category Info // @version 0.1.2.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ // @description [@@BUILDNAME@@-@@BUILDDATE@@] Display a list of all localized portals by level and faction. // @include https://*.ingress.com/intel* // @include http://*.ingress.com/intel* // @match https://*.ingress.com/intel* // @match http://*.ingress.com/intel* // @include https://*.ingress.com/mission/* // @include http://*.ingress.com/mission/* // @match https://*.ingress.com/mission/* // @match http://*.ingress.com/mission/* // @grant none // ==/UserScript== @@PLUGINSTART@@ // PLUGIN START //////////////////////////////////////////////////////// /* whatsnew * 0.1.0 : display graphs * 0.0.10 : show in nav drawer on mobile devices * 0.0.9 : fix for new intel map * 0.0.8 : use dialog() instead of alert() * 0.0.6 : ignoring outside bounds portals (even if close to) * 0.0.5 : changed table layout, added some colors * 0.0.4 : reverse show order of portals, using MAX_PORTAL_LEVEL now for array, changed table layout to be more compact, cleaned up code * 0.0.3 : fixed incorrect rounded portal levels, adjusted viewport * 0.0.2 : fixed counts to be reset after scrolling * 0.0.1 : initial release, show count of portals */ // use own namespace for plugin window.plugin.portalcounts = { BAR_TOP: 20, BAR_HEIGHT: 180, BAR_WIDTH: 25, BAR_PADDING: 5, RADIUS_INNER: 70, RADIUS_OUTER: 100, CENTER_X: 200, CENTER_Y: 100, }; //count portals for each level available on the map window.plugin.portalcounts.getPortals = function (){ //console.log('** getPortals'); var self = window.plugin.portalcounts; var displayBounds = map.getBounds(); self.enlP = 0; self.resP = 0; self.neuP = 0; self.PortalsEnl = new Array(); self.PortalsRes = new Array(); for(var level = window.MAX_PORTAL_LEVEL; level > 0; level--){ self.PortalsEnl[level] = 0; self.PortalsRes[level] = 0; } $.each(window.portals, function(i, portal) { var level = portal.options.level; var team = portal.options.team; // just count portals in viewport if(!displayBounds.contains(portal.getLatLng())) return true; switch (team){ case 1 : self.resP++; self.PortalsRes[level]++; break; case 2 : self.enlP++; self.PortalsEnl[level]++; break; default: self.neuP++; break; } }); //get portals informations from IITC var minlvl = getMinPortalLevel(); var total = self.neuP + self.enlP + self.resP; var counts = ''; if(total > 0) { counts += ''; //'+self.enlP+' Portal(s)'; for(var level = window.MAX_PORTAL_LEVEL; level > 0; level--){ counts += ''; if(minlvl > level) counts += ''; else counts += ''; counts += ''; } counts += ''; counts += '
EnlightenedResistance
Level '+level+'zoom in to see portals in this level'+self.PortalsEnl[level]+''+self.PortalsRes[level]+'
Total:'+self.enlP+''+self.resP+'
Neutral:'; if(minlvl > 0) counts += 'zoom in to see unclaimed portals'; else counts += self.neuP; counts += '
'; var svg = $('').css('margin-top', 10); var all = self.PortalsRes.map(function(val,i){return val+self.PortalsEnl[i]}); all[0] = self.neuP; // bar graphs self.makeBar(self.PortalsEnl, 'Enl', COLORS[2], 0 ).appendTo(svg); self.makeBar(all , 'All', '#FFFFFF', 1*(self.BAR_WIDTH + self.BAR_PADDING)).appendTo(svg); self.makeBar(self.PortalsRes, 'Res', COLORS[1], 2*(self.BAR_WIDTH + self.BAR_PADDING)).appendTo(svg); // pie graph var g = $('') .attr('transform', self.format('translate(%s,%s)', self.CENTER_X, self.CENTER_Y)) .appendTo(svg); // inner parts - factions self.makePie(0, self.resP/total, COLORS[1]).appendTo(g); self.makePie(self.resP/total, (self.neuP + self.resP)/total, COLORS[0]).appendTo(g); self.makePie((self.neuP + self.resP)/total, 1, COLORS[2]).appendTo(g); // outer part - levels var angle = 0; for(var i=self.PortalsRes.length-1;i>=0;i--) { if(!self.PortalsRes[i]) continue; var diff = self.PortalsRes[i] / total; self.makeRing(angle, angle+diff, COLORS_LVL[i]).appendTo(g); angle += diff; } var diff = self.neuP / total; self.makeRing(angle, angle+diff, COLORS_LVL[0]).appendTo(g); angle += diff; for(var i=0;i') .attr({ x1: self.resP') .attr({ x1: self.resP').append(svg).html(); } else { counts += '

No Portals in range!

'; } // I've only seen the backend reduce the portals returned for L4+ or further out zoom levels - but this could change // UPDATE: now seen for L2+ in dense areas (map zoom level 14 or lower) if (getMinPortalLevel() >= 2) { counts += '

Warning: Portal counts can be inaccurate when zoomed out

'; } var total = self.enlP + self.resP + self.neuP; var title = total + ' ' + (total == 1 ? 'portal' : 'portals'); if(window.useAndroidPanes()) { $('
' + '
' + title + '
' + counts + '
').appendTo(document.body); } else { dialog({ html: '
' + counts + '
', title: 'Portal counts: ' + title, width: 'auto' }); } } window.plugin.portalcounts.makeBar = function(portals, text, color, shift) { var self = window.plugin.portalcounts; var g = $('').attr('transform', 'translate('+shift+',0)'); var sum = portals.reduce(function(a,b){ return a+b }); var top = self.BAR_TOP; if(sum != 0) { for(var i=portals.length-1;i>=0;i--) { if(!portals[i]) continue; var height = self.BAR_HEIGHT * portals[i] / sum; $('') .attr({ x: 0, y: top, width: self.BAR_WIDTH, height: height, fill: COLORS_LVL[i] }) .appendTo(g); top += height; } } $('') .html(text) .attr({ x: self.BAR_WIDTH * 0.5, y: self.BAR_TOP * 0.75, fill: color, 'text-anchor': 'middle' }) .appendTo(g); return g; }; window.plugin.portalcounts.makePie = function(startAngle, endAngle, color) { if(startAngle == endAngle) return $([]); // return empty element query var self = window.plugin.portalcounts; var large_arc = (endAngle - startAngle) > 0.5 ? 1 : 0; var labelAngle = (endAngle + startAngle) / 2; var label = Math.round((endAngle - startAngle) * 100) + '%'; startAngle = 0.5 - startAngle; endAngle = 0.5 - endAngle; labelAngle = 0.5 - labelAngle; var p1x = Math.sin(startAngle * 2 * Math.PI) * self.RADIUS_INNER; var p1y = Math.cos(startAngle * 2 * Math.PI) * self.RADIUS_INNER; var p2x = Math.sin(endAngle * 2 * Math.PI) * self.RADIUS_INNER; var p2y = Math.cos(endAngle * 2 * Math.PI) * self.RADIUS_INNER; var lx = Math.sin(labelAngle * 2 * Math.PI) * self.RADIUS_INNER / 1.5; var ly = Math.cos(labelAngle * 2 * Math.PI) * self.RADIUS_INNER / 1.5; // for a full circle, both coordinates would be identical, so no circle would be drawn if(startAngle == 0.5 && endAngle == -0.5) p2x -= 1E-5; var text = $('') .attr({ 'text-anchor': 'middle', 'dominant-baseline' :'central', x: lx, y: ly }) .html(label); var path = $('') .attr({ fill: color, d: self.format('M %s,%s A %s,%s 0 %s 1 %s,%s L 0,0 z', p1x,p1y, self.RADIUS_INNER,self.RADIUS_INNER, large_arc, p2x,p2y) }); return path.add(text); // concat path and text }; window.plugin.portalcounts.makeRing = function(startAngle, endAngle, color) { var self = window.plugin.portalcounts; var large_arc = (endAngle - startAngle) > 0.5 ? 1 : 0; startAngle = 0.5 - startAngle; endAngle = 0.5 - endAngle; var p1x = Math.sin(startAngle * 2 * Math.PI) * self.RADIUS_OUTER; var p1y = Math.cos(startAngle * 2 * Math.PI) * self.RADIUS_OUTER; var p2x = Math.sin(endAngle * 2 * Math.PI) * self.RADIUS_OUTER; var p2y = Math.cos(endAngle * 2 * Math.PI) * self.RADIUS_OUTER; var p3x = Math.sin(endAngle * 2 * Math.PI) * self.RADIUS_INNER; var p3y = Math.cos(endAngle * 2 * Math.PI) * self.RADIUS_INNER; var p4x = Math.sin(startAngle * 2 * Math.PI) * self.RADIUS_INNER; var p4y = Math.cos(startAngle * 2 * Math.PI) * self.RADIUS_INNER; // for a full circle, both coordinates would be identical, so no circle would be drawn if(startAngle == 0.5 && endAngle == -0.5) { p2x -= 1E-5; p3x -= 1E-5; } return $('') .attr({ fill: color, d: self.format('M %s,%s ', p1x, p1y) + self.format('A %s,%s 0 %s 1 %s,%s ', self.RADIUS_OUTER,self.RADIUS_OUTER, large_arc, p2x,p2y) + self.format('L %s,%s ', p3x,p3y) + self.format('A %s,%s 0 %s 0 %s,%s ', self.RADIUS_INNER,self.RADIUS_INNER, large_arc, p4x,p4y) + 'Z' }); }; window.plugin.portalcounts.format = function(str) { var re = /%s/; for(var i = 1; i < arguments.length; i++) { str = str.replace(re, arguments[i]); } return str; } window.plugin.portalcounts.onPaneChanged = function(pane) { if(pane == 'plugin-portalcounts') window.plugin.portalcounts.getPortals(); else $('#portalcounts').remove() }; var setup = function() { if(window.useAndroidPanes()) { android.addPane('plugin-portalcounts', 'Portal counts', 'ic_action_data_usage'); addHook('paneChanged', window.plugin.portalcounts.onPaneChanged); } else { $('#toolbox').append(' Portal counts'); } $('head').append(''); } // PLUGIN END ////////////////////////////////////////////////////////// @@PLUGINEND@@