From 08ebe1e519659bd5485d7b7a04838c130a3e9b56 Mon Sep 17 00:00:00 2001 From: fly Date: Sat, 4 Jan 2014 17:41:50 +0400 Subject: [PATCH] Added: new plugin fly-links --- plugins/fly-links.user.js | 346 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 plugins/fly-links.user.js diff --git a/plugins/fly-links.user.js b/plugins/fly-links.user.js new file mode 100644 index 00000000..8c15944a --- /dev/null +++ b/plugins/fly-links.user.js @@ -0,0 +1,346 @@ +// ==UserScript== +// @id fly-links@fly +// @name IITC plugin: Fly Links +// @category Layer +// @version 0.1.0.@@DATETIMEVERSION@@ +// @updateURL @@UPDATEURL@@ +// @downloadURL @@DOWNLOADURL@@ +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Calculate how to link the portals to create the largest tidy set of nested fields. Enable from the layer chooser. +// @include https://www.ingress.com/intel* +// @include http://www.ingress.com/intel* +// @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @grant none +// ==/UserScript== + +@@PLUGINSTART@@ + +// PLUGIN START //////////////////////////////////////////////////////// + +// use own namespace for plugin +window.plugin.flyLinks = function() {}; + +// const values +window.plugin.flyLinks.MAX_PORTALS_TO_OBSERVE = 1000; +window.plugin.flyLinks.MAX_PORTALS_TO_LINK = 100; +// zoom level used for projecting points between latLng and pixel coordinates. may affect precision of triangulation +window.plugin.flyLinks.PROJECT_ZOOM = 16; + +window.plugin.flyLinks.STROKE_STYLE = { + color: '#FF0000', + opacity: 1, + weight: 1.5, + clickable: false, + dashArray: [6,4], + smoothFactor: 10, +}; + +window.plugin.flyLinks.levelLayerGroup = null; + +window.plugin.flyLinks.updateLayer = function() { + window.plugin.flyLinks.levelLayerGroup.clearLayers(); + var ctrl = $('.leaflet-control-layers-selector + span:contains("Fly links")').parent(); + if (Object.keys(window.portals).length > window.plugin.flyLinks.MAX_PORTALS_TO_OBSERVE) { + ctrl.addClass('disabled').attr('title', 'Too many portals: ' + Object.keys(window.portals).length); + return; + } + + var locations = []; + + var bounds = map.getBounds(); + $.each(window.portals, function(guid, portal) { + var ll = portal.getLatLng(); + if (bounds.contains(ll)) { + var p = map.project(portal.getLatLng(), window.plugin.flyLinks.PROJECT_ZOOM); + locations.push(p); + } + }); + + var distance = function(a, b) { + return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); + }; + + var drawLink = function(a, b, style) { + var alatlng = map.unproject (a, window.plugin.flyLinks.PROJECT_ZOOM); + var blatlng = map.unproject (b, window.plugin.flyLinks.PROJECT_ZOOM); + + var poly = L.polyline([alatlng, blatlng], style); + poly.addTo(window.plugin.flyLinks.levelLayerGroup); + } + + var filterLocations = function(locations) { + var bounds = map.getBounds(); + var minp = map.project(bounds.getNorthWest(), window.plugin.flyLinks.PROJECT_ZOOM); + var maxp = map.project(bounds.getSouthEast(), window.plugin.flyLinks.PROJECT_ZOOM); + var center = map.project(map.getCenter(), window.plugin.flyLinks.PROJECT_ZOOM); + + var minx = minp.x; + var miny = minp.y; + var maxx = maxp.x; + var maxy = maxp.y; + + var r = Math.min((maxx - minx) / 6, (maxy - miny) / 6); + + var rindex = []; + + for (var i = 0; i < locations.length; ++i) { + if (distance(locations[i], center) <= r) { + rindex.push(i); + } + } + + if (rindex.length == 0) + return []; + + var dist = []; + var isSelected = []; + for (var i = 0; i < locations.length; ++i) { + dist[i] = -1; + isSelected[i] = false; + } + + dist[rindex[0]] = 0; + + var maxdist = 0; + while (true) { + var mini = -1; + for (var i = 0; i < rindex.length; ++i) { + if (!isSelected[rindex[i]] && dist[rindex[i]] != -1) + if (mini == -1 || dist[mini] > dist[rindex[i]]) + mini = rindex[i]; + } + if (mini == -1) + break; + isSelected[mini] = true; + if (maxdist < dist[mini]) + maxdist = dist[mini]; + for (var i = 0; i < locations.length; ++i) { + if (isSelected[i]) + continue; + var dst = distance(locations[mini], locations[i]); + if (dist[i] == -1 || dist[i] > dst) { + dist[i] = dst; + } + } + } + + while (true) { + var mini = -1; + for (var i = 0; i < locations.length; ++i) { + if (!isSelected[i] && dist[i] != -1 && dist[i] <= maxdist) + if (mini == -1 || dist[mini] > dist[i]) + mini = i; + } + if (mini == -1) + break; + isSelected[mini] = true; + rindex.push(mini); + for (var i = 0; i < locations.length; ++i) { + if (isSelected[i]) + continue; + var dst = distance(locations[mini], locations[i]); + if (dist[i] == -1 || dist[i] > dst) { + dist[i] = dst; + } + } + } + + var result = []; + for (var i = 0; i < rindex.length; ++i) + result.push(locations[rindex[i]]); + + return result; + } + + var locations = filterLocations(locations); + + if (locations.length > window.plugin.flyLinks.MAX_PORTALS_TO_LINK) { + ctrl.addClass('disabled').attr('title', 'Too many portals (linked/observed): ' + locations.length + '/' + Object.keys(window.portals).length); + return; + } + ctrl.removeClass('disabled').attr('title', 'portals (linked/observed): ' + locations.length + '/' + Object.keys(window.portals).length); + + var EPS = 1e-9; + var det = function(a, b, c) { + return a.x * b.y - a.y * b.x + b.x * c.y - b.y * c.x + c.x * a.y - c.y * a.x; + } + + var convexHull = function(points) { + if (points.length < 3) + return []; + var result = []; + var func = function _func(ai, bi, index) { + var maxd = 0; + var maxdi = -1; + var a = points[ai]; + var b = points[bi]; + var _index = []; + for (var i = 0; i < index.length; ++i) { + var c = points[index[i]]; + var d = -det(a, b, c); + if (d > EPS) { + _index.push(index[i]); + } + if (maxd < d - EPS) { + maxd = d; + maxdi = index[i]; + } + } + if (maxdi != -1) { + _func(ai, maxdi, _index); + _func(maxdi, bi, _index); + } else { + result.push(ai); + } + } + var minxi = 0; + var maxxi = 0; + var index = []; + for (var i = 0; i < points.length; ++i) { + index.push(i); + if (points[minxi].x > points[i].x) + minxi = i; + if (points[maxxi].x < points[i].x) + maxxi = i; + } + func(minxi, maxxi, index); + func(maxxi, minxi, index); + return result; + } + + var index = convexHull(locations); + + var triangulate = function(index, locations) { + if (index.length == 0) + return []; + var data = []; + var subtriangulate = function _subtriangulate(ai, bi, ci, index) { + var _i = [ai, bi, ci].sort(function(a,b){return a-b;}); + if (data[_i[0]] === undefined) + data[_i[0]] = []; + if (data[_i[0]][_i[1]-_i[0]] === undefined) + data[_i[0]][_i[1]-_i[0]] = []; + if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] === undefined) { + var _index = []; + for (var i = 0; i < index.length; ++i) { + var detc = det(locations[ai], locations[bi], locations[index[i]]); + var deta = det(locations[bi], locations[ci], locations[index[i]]); + var detb = det(locations[ci], locations[ai], locations[index[i]]); + if (deta > EPS && detb > EPS && detc > EPS) { + _index.push(index[i]); + } + } + var besth = 0; + var besthi = -1; + if (_index.length == 0) { + var a = locations[ai]; + var b = locations[bi]; + var c = locations[ci]; + var s = Math.abs(det(a, b, c)); + var ch = s / distance(a, b); + var ah = s / distance(b, c); + var bh = s / distance(c, a); + besth = Math.min(ah, bh, ch); + besthi = -1; + } else { + var besths = 0; + for (var i = 0; i < _index.length; ++i) { + var ch = _subtriangulate(ai, bi, _index[i], _index); + var ah = _subtriangulate(bi, ci, _index[i], _index); + var bh = _subtriangulate(ci, ai, _index[i], _index); + var _besth = Math.min(ah, bh, ch); + var _besths = ah + bh + ch; + if (besth < _besth || Math.abs(besth - _besth) <= EPS && besths < _besths) { + besth = _besth; + besths = _besths; + besthi = _index[i]; + } + } + } + data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] = [besth, besthi]; + } + return data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][0]; + } + var subindex = []; + for (var i = 0; i < locations.length; ++i) { + subindex.push(i); + } + var bestt = []; + for (var len = 1; len <= index.length - 1; ++len) { + bestt[len] = []; + for (var k = 0; k < index.length - len; ++k) { + var t = 0; + var tlen = -1; + for (var _len = 1; _len <= len - 1; ++_len) { + var _t = 0; + $.each([bestt[_len][k][0], bestt[len-_len][k+_len][0], subtriangulate(index[k], index[k+_len], index[k+len], subindex)], function(guid, __t) { + if (__t == 0) + return; + if (_t == 0 || _t > __t) + _t = __t; + }); + if (t == 0 || t < _t) { + t = _t; + tlen = _len; + } + } + bestt[len][k] = [t, tlen]; + } + } + + // построить рёбра или треугольники + var edges = []; + var makesubtriangulation = function _makesubtriangulation(ai, bi, ci) { + var _i = [ai, bi, ci].sort(function(a,b){return a-b;}); + if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1] != -1) { + _makesubtriangulation(ai, bi, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]); + _makesubtriangulation(bi, ci, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]); + _makesubtriangulation(ci, ai, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]); + edges.push(new window.plugin.flyLinks.Edge(locations[ai], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]])); + edges.push(new window.plugin.flyLinks.Edge(locations[bi], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]])); + edges.push(new window.plugin.flyLinks.Edge(locations[ci], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]][1]])); + } + } + var maketriangulation = function _maketriangulation(len, a) { + edges.push(new window.plugin.flyLinks.Edge(locations[index[a]], locations[index[a+len]])); + if (bestt[len][a][1] == -1) + return; + makesubtriangulation(index[a], index[a+bestt[len][a][1]], index[a+len]); + _maketriangulation(bestt[len][a][1], a); + _maketriangulation(len - bestt[len][a][1], a + bestt[len][a][1]); + } + maketriangulation(index.length - 1, 0); + return edges; + } + + var edges = triangulate(index, locations); + + $.each(edges, function(idx, edge) { + drawLink(edge.a, edge.b, window.plugin.flyLinks.STROKE_STYLE); + }); +} + +window.plugin.flyLinks.Edge = function(a, b) { + this.a = a; + this.b = b; +} + +window.plugin.flyLinks.setup = function() { + window.plugin.flyLinks.levelLayerGroup = new L.LayerGroup(); + + window.addHook('mapDataRefreshEnd', function(e) { + window.plugin.flyLinks.updateLayer(); + }); + + window.map.on('moveend', function() { + window.plugin.flyLinks.updateLayer(); + }); + + window.addLayerGroup('Fly links', window.plugin.flyLinks.levelLayerGroup, true); +} + +var setup = window.plugin.flyLinks.setup; + +// PLUGIN END ////////////////////////////////////////////////////////// + +@@PLUGINEND@@