// ==UserScript==
// @id iitc-plugin-draw-tools@breunigs
// @name IITC plugin: draw tools
// @category Layer
// @version 0.7.0.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Allow drawing things onto the current map so you may plan your next move.
// @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 ////////////////////////////////////////////////////////
// use own namespace for plugin
window.plugin.drawTools = function() {};
window.plugin.drawTools.loadExternals = function() {
try { console.log('Loading leaflet.draw JS now'); } catch(e) {}
@@INCLUDERAW:external/leaflet.draw.js@@
@@INCLUDERAW:external/spectrum/spectrum.js@@
try { console.log('done loading leaflet.draw JS'); } catch(e) {}
window.plugin.drawTools.boot();
$('head').append('');
$('head').append('');
}
window.plugin.drawTools.getMarkerIcon = function(color) {
if (typeof(color) === 'undefined') {
console.warn('Color is not set or not a valid color. Using default color as fallback.');
color = '#a24ac3';
}
var svgIcon = window.plugin.drawTools.markerTemplate.replace(/%COLOR%/g, color);
return L.divIcon({
iconSize: new L.Point(25, 41),
iconAnchor: new L.Point(12, 41),
html: svgIcon,
className: 'leaflet-iitc-custom-icon',
// L.divIcon does not use the option color, but we store it here to
// be able to simply retrieve the color for serializing markers
color: color
});
}
window.plugin.drawTools.currentColor = '#a24ac3';
window.plugin.drawTools.markerTemplate = '@@INCLUDESTRING:images/marker-icon.svg.template@@';
window.plugin.drawTools.setOptions = function() {
window.plugin.drawTools.lineOptions = {
stroke: true,
color: window.plugin.drawTools.currentColor,
weight: 4,
opacity: 0.5,
fill: false,
clickable: true
};
window.plugin.drawTools.polygonOptions = L.extend({}, window.plugin.drawTools.lineOptions, {
fill: true,
fillColor: null, // to use the same as 'color' for fill
fillOpacity: 0.2
});
window.plugin.drawTools.editOptions = L.extend({}, window.plugin.drawTools.polygonOptions, {
dashArray: [10,10]
});
delete window.plugin.drawTools.editOptions.color;
window.plugin.drawTools.markerOptions = {
icon: window.plugin.drawTools.currentMarker,
zIndexOffset: 2000
};
}
window.plugin.drawTools.setDrawColor = function(color) {
window.plugin.drawTools.currentColor = color;
window.plugin.drawTools.currentMarker = window.plugin.drawTools.getMarkerIcon(color);
window.plugin.drawTools.lineOptions.color = color;
window.plugin.drawTools.polygonOptions.color = color;
window.plugin.drawTools.markerOptions.icon = window.plugin.drawTools.currentMarker;
plugin.drawTools.drawControl.setDrawingOptions({
polygon: { shapeOptions: plugin.drawTools.polygonOptions },
polyline: { shapeOptions: plugin.drawTools.lineOptions },
circle: { shapeOptions: plugin.drawTools.polygonOptions },
marker: { icon: plugin.drawTools.markerOptions.icon },
});
}
// renders the draw control buttons in the top left corner
window.plugin.drawTools.addDrawControl = function() {
var drawControl = new L.Control.Draw({
draw: {
rectangle: false,
polygon: {
title: 'Add a polygon\n\n'
+ 'Click on the button, then click on the map to\n'
+ 'define the start of the polygon. Continue clicking\n'
+ 'to draw the line you want. Click the first or last\n'
+ 'point of the line (a small white rectangle) to\n'
+ 'finish. Double clicking also works.',
shapeOptions: window.plugin.drawTools.polygonOptions,
snapPoint: window.plugin.drawTools.getSnapLatLng,
},
polyline: {
title: 'Add a (poly) line.\n\n'
+ 'Click on the button, then click on the map to\n'
+ 'define the start of the line. Continue clicking\n'
+ 'to draw the line you want. Click the last\n'
+ 'point of the line (a small white rectangle) to\n'
+ 'finish. Double clicking also works.',
shapeOptions: window.plugin.drawTools.lineOptions,
snapPoint: window.plugin.drawTools.getSnapLatLng,
},
circle: {
title: 'Add a circle.\n\n'
+ 'Click on the button, then click-AND-HOLD on the\n'
+ 'map where the circle’s center should be. Move\n'
+ 'the mouse to control the radius. Release the mouse\n'
+ 'to finish.',
shapeOptions: window.plugin.drawTools.polygonOptions,
snapPoint: window.plugin.drawTools.getSnapLatLng,
},
// Options for marker (icon, zIndexOffset) are not set via shapeOptions,
// so we have merge them here!
marker: L.extend({}, window.plugin.drawTools.markerOptions, {
title: 'Add a marker.\n\n'
+ 'Click on the button, then click on the map where\n'
+ 'you want the marker to appear.',
snapPoint: window.plugin.drawTools.getSnapLatLng,
repeatMode: true
}),
},
edit: {
featureGroup: window.plugin.drawTools.drawnItems,
edit: {
title: 'Edit drawn items',
selectedPathOptions: window.plugin.drawTools.editOptions,
},
remove: {
title: 'Delete drawn items'
},
},
});
window.plugin.drawTools.drawControl = drawControl;
map.addControl(drawControl);
//plugin.drawTools.addCustomButtons();
window.plugin.drawTools.setAccessKeys();
for (var toolbarId in drawControl._toolbars) {
if (drawControl._toolbars[toolbarId] instanceof L.Toolbar) {
drawControl._toolbars[toolbarId].on('enable', function() {
setTimeout(window.plugin.drawTools.setAccessKeys, 10);
});
}
}
}
window.plugin.drawTools.setAccessKeys = function() {
var expr = /\s*\[\w+\]$/;
// there is no API to add accesskeys, so have to dig in the DOM
// must be same order as in markup. Note that each toolbar has a container for save/cancel
var accessKeys = [
'l', 'p', 'o', 'm', // line, polygon, circle, marker
'a', // cancel (_abort)
'e', 'd', // edit, delete
's', 'a', // save, cancel
];
var buttons = window.plugin.drawTools.drawControl._container.getElementsByTagName('a');
for(var i=0;i size) return true;
candidates.push([dist, ll]);
});
if(candidates.length === 0) return unsnappedLatLng;
candidates = candidates.sort(function(a, b) { return a[0]-b[0]; });
return new L.LatLng(candidates[0][1].lat, candidates[0][1].lng); //return a clone of the portal location
}
window.plugin.drawTools.save = function() {
var data = [];
window.plugin.drawTools.drawnItems.eachLayer( function(layer) {
var item = {};
if (layer instanceof L.GeodesicCircle || layer instanceof L.Circle) {
item.type = 'circle';
item.latLng = layer.getLatLng();
item.radius = layer.getRadius();
item.color = layer.options.color;
} else if (layer instanceof L.GeodesicPolygon || layer instanceof L.Polygon) {
item.type = 'polygon';
item.latLngs = layer.getLatLngs();
item.color = layer.options.color;
} else if (layer instanceof L.GeodesicPolyline || layer instanceof L.Polyline) {
item.type = 'polyline';
item.latLngs = layer.getLatLngs();
item.color = layer.options.color;
} else if (layer instanceof L.Marker) {
item.type = 'marker';
item.latLng = layer.getLatLng();
item.color = layer.options.icon.options.color;
} else {
console.warn('Unknown layer type when saving draw tools layer');
return; //.eachLayer 'continue'
}
data.push(item);
});
localStorage['plugin-draw-tools-layer'] = JSON.stringify(data);
console.log('draw-tools: saved to localStorage');
}
window.plugin.drawTools.load = function() {
try {
var dataStr = localStorage['plugin-draw-tools-layer'];
if (dataStr === undefined) return;
var data = JSON.parse(dataStr);
window.plugin.drawTools.import(data);
} catch(e) {
console.warn('draw-tools: failed to load data from localStorage: '+e);
}
}
window.plugin.drawTools.import = function(data) {
$.each(data, function(index,item) {
var layer = null;
var extraOpt = {};
if (item.color) extraOpt.color = item.color;
switch(item.type) {
case 'polyline':
layer = L.geodesicPolyline(item.latLngs, L.extend({},window.plugin.drawTools.lineOptions,extraOpt));
break;
case 'polygon':
layer = L.geodesicPolygon(item.latLngs, L.extend({},window.plugin.drawTools.polygonOptions,extraOpt));
break;
case 'circle':
layer = L.geodesicCircle(item.latLng, item.radius, L.extend({},window.plugin.drawTools.polygonOptions,extraOpt));
break;
case 'marker':
var extraMarkerOpt = {};
if (item.color) extraMarkerOpt.icon = window.plugin.drawTools.getMarkerIcon(item.color);
layer = L.marker(item.latLng, L.extend({},window.plugin.drawTools.markerOptions,extraMarkerOpt));
window.registerMarkerForOMS(layer);
break;
default:
console.warn('unknown layer type "'+item.type+'" when loading draw tools layer');
break;
}
if (layer) {
window.plugin.drawTools.drawnItems.addLayer(layer);
}
});
runHooks('pluginDrawTools', {event: 'import'});
}
//Draw Tools Options
// Manual import, export and reset data
window.plugin.drawTools.manualOpt = function() {
var html = '
');
$('.drawtools-alert').delay(2500).fadeOut();
}
window.plugin.drawTools.optCopy = function() {
if (typeof android !== 'undefined' && android && android.shareString) {
android.shareString(localStorage['plugin-draw-tools-layer']);
} else {
var stockWarnings = {};
var stockLinks = [];
window.plugin.drawTools.drawnItems.eachLayer( function(layer) {
if (layer instanceof L.GeodesicCircle || layer instanceof L.Circle) {
stockWarnings.noCircle = true;
return; //.eachLayer 'continue'
} else if (layer instanceof L.GeodesicPolygon || layer instanceof L.Polygon) {
stockWarnings.polyAsLine = true;
// but we can export them
} else if (layer instanceof L.GeodesicPolyline || layer instanceof L.Polyline) {
// polylines are fine
} else if (layer instanceof L.Marker) {
stockWarnings.noMarker = true;
return; //.eachLayer 'continue'
} else {
stockWarnings.unknown = true; //should never happen
return; //.eachLayer 'continue'
}
// only polygons and polylines make it through to here
var latLngs = layer.getLatLngs();
// stock only handles one line segment at a time
for (var i=0; i40) stockWarnTexts.push('Warning: Stock intel may not work with more than 40 line segments - there are '+stockLinks.length);
if (stockWarnings.noCircle) stockWarnTexts.push('Warning: Circles cannot be exported to stock intel');
if (stockWarnings.noMarker) stockWarnTexts.push('Warning: Markers cannot be exported to stock intel');
if (stockWarnings.unknown) stockWarnTexts.push('Warning: UNKNOWN ITEM TYPE');
var html = '
or, export as a link for the standard intel map (for non IITC users)
'
+'';
if (stockWarnTexts.length>0) {
html += '
'+stockWarnTexts.join('
')+'
';
}
dialog({
html: html,
width: 600,
dialogClass: 'ui-dialog-drawtoolsSet-copy',
title: 'Draw Tools Export'
});
}
}
window.plugin.drawTools.optExport = function() {
if(typeof android !== 'undefined' && android && android.saveFile) {
android.saveFile('IITC-drawn-items.json', 'application/json', localStorage['plugin-draw-tools-layer']);
}
}
window.plugin.drawTools.optPaste = function() {
var promptAction = prompt('Press CTRL+V to paste (draw-tools data or stock intel URL).', '');
if(promptAction !== null && promptAction !== '') {
try {
// first see if it looks like a URL-format stock intel link, and if so, try and parse out any stock drawn items
// from the pls parameter
if (promptAction.match(new RegExp("^(https?://)?(www\\.)ingress\\.com/intel.*[?&]pls="))) {
//looks like a ingress URL that has drawn items...
var items = promptAction.split(/[?&]/);
var foundAt = -1;
for (var i=0; iImport failed');
}
}
}
window.plugin.drawTools.optImport = function() {
if (window.requestFile === undefined) return;
window.requestFile(function(filename, content) {
try {
var data = JSON.parse(content);
window.plugin.drawTools.drawnItems.clearLayers();
window.plugin.drawTools.import(data);
console.log('DRAWTOOLS: reset and imported drawn tiems');
window.plugin.drawTools.optAlert('Import Successful.');
// to write back the data to localStorage
window.plugin.drawTools.save();
} catch(e) {
console.warn('DRAWTOOLS: failed to import data: '+e);
window.plugin.drawTools.optAlert('Import failed');
}
});
}
window.plugin.drawTools.optReset = function() {
var promptAction = confirm('All drawn items will be deleted. Are you sure?', '');
if(promptAction) {
delete localStorage['plugin-draw-tools-layer'];
window.plugin.drawTools.drawnItems.clearLayers();
window.plugin.drawTools.load();
console.log('DRAWTOOLS: reset all drawn items');
window.plugin.drawTools.optAlert('Reset Successful. ');
runHooks('pluginDrawTools', {event: 'clear'});
}
}
window.plugin.drawTools.snapToPortals = function() {
var dataParams = getMapZoomTileParameters(getDataZoomForMapZoom(map.getZoom()));
if (dataParams.level > 0) {
if (!confirm('Not all portals are visible on the map. Snap to portals may move valid points to the wrong place. Continue?')) {
return;
}
}
if (mapDataRequest.status.short != 'done') {
if (!confirm('Map data has not completely loaded, so some portals may be missing. Do you want to continue?')) {
return;
}
}
var visibleBounds = map.getBounds();
// let's do all the distance calculations in screen space. 2d is much easier, should be faster, and is more than good enough
// we'll pre-project all the on-screen portals too, to save repeatedly doing it
var visiblePortals = {};
$.each(window.portals, function(guid,portal) {
var ll = portal.getLatLng();
if (visibleBounds.contains(ll)) {
visiblePortals[guid] = map.project(ll);
}
});
if (Object.keys(visiblePortals).length == 0) {
alert('Error: No portals visible in this view - nothing to snap points to!');
return;
}
var findClosestPortalLatLng = function(latlng) {
var testpoint = map.project(latlng);
var minDistSquared = undefined;
var minGuid = undefined;
for (var guid in visiblePortals) {
var p = visiblePortals[guid];
var distSquared = (testpoint.x-p.x)*(testpoint.x-p.x) + (testpoint.y-p.y)*(testpoint.y-p.y);
if (minDistSquared == undefined || minDistSquared > distSquared) {
minDistSquared = distSquared;
minGuid = guid;
}
}
return minGuid ? portals[minGuid].getLatLng() : undefined; //should never hit 'undefined' case - as we abort when the list is empty
};
var changedCount = 0;
var testCount = 0;
window.plugin.drawTools.drawnItems.eachLayer(function(layer) {
if (layer.getLatLng) {
//circles and markers - a single point to snap
var ll = layer.getLatLng();
if (visibleBounds.contains(ll)) {
testCount++;
var newll = findClosestPortalLatLng(ll);
if (!newll.equals(ll)) {
layer.setLatLng(new L.LatLng(newll.lat,newll.lng));
changedCount++;
}
}
} else if (layer.getLatLngs) {
var lls = layer.getLatLngs();
var layerChanged = false;
for (var i=0; i 0) {
runHooks('pluginDrawTools',{event:'layersSnappedToPortals'}); //or should we send 'layersEdited'? as that's effectively what's happened...
}
alert('Tested '+testCount+' points, and moved '+changedCount+' onto portal coordinates');
window.plugin.drawTools.save();
}
window.plugin.drawTools.boot = function() {
// add a custom hook for draw tools to share it's activity with other plugins
pluginCreateHook('pluginDrawTools');
window.plugin.drawTools.currentMarker = window.plugin.drawTools.getMarkerIcon(window.plugin.drawTools.currentColor);
window.plugin.drawTools.setOptions();
//create a leaflet FeatureGroup to hold drawn items
window.plugin.drawTools.drawnItems = new L.FeatureGroup();
//load any previously saved items
plugin.drawTools.load();
//add the draw control - this references the above FeatureGroup for editing purposes
plugin.drawTools.addDrawControl();
//start off hidden. if the layer is enabled, the below addLayerGroup will add it, triggering a 'show'
$('.leaflet-draw-section').hide();
//hide the draw tools when the 'drawn items' layer is off, show it when on
map.on('layeradd', function(obj) {
if(obj.layer === window.plugin.drawTools.drawnItems) {
$('.leaflet-draw-section').show();
}
});
map.on('layerremove', function(obj) {
if(obj.layer === window.plugin.drawTools.drawnItems) {
$('.leaflet-draw-section').hide();
}
});
//add the layer
window.addLayerGroup('Drawn Items', window.plugin.drawTools.drawnItems, true);
//place created items into the specific layer
map.on('draw:created', function(e) {
var type=e.layerType;
var layer=e.layer;
window.plugin.drawTools.drawnItems.addLayer(layer);
window.plugin.drawTools.save();
if(layer instanceof L.Marker) {
window.registerMarkerForOMS(layer);
}
runHooks('pluginDrawTools',{event:'layerCreated',layer:layer});
});
map.on('draw:deleted', function(e) {
window.plugin.drawTools.save();
runHooks('pluginDrawTools',{event:'layersDeleted'});
});
map.on('draw:edited', function(e) {
window.plugin.drawTools.save();
runHooks('pluginDrawTools',{event:'layersEdited'});
});
//add options menu
$('#toolbox').append('DrawTools Opt');
$('head').append('');
}
var setup = window.plugin.drawTools.loadExternals;
// PLUGIN END //////////////////////////////////////////////////////////
@@PLUGINEND@@