Fix leader length, add transparent pixels option
This commit is contained in:
parent
4fb075d28c
commit
0a04418eee
@ -1,5 +1,6 @@
|
||||
### Change Log for Node-RED Worldmap
|
||||
|
||||
- v2.21.4 - fix speed leader length. Add transparentPixels option.
|
||||
- v2.21.3 - Add zoom to bounds action. Adjust map layers max zoom levels.
|
||||
- v2.21.2 - Expand ship nav to ship navigation.
|
||||
- v2.21.1 - Fix ui check callback to not use .
|
||||
|
@ -11,6 +11,7 @@ map web page for plotting "things" on.
|
||||
|
||||
### Updates
|
||||
|
||||
- v2.21.4 - fix speed leader length. Add transparentPixels option..
|
||||
- v2.21.3 - Add zoom to bounds action. Adjust map layers max zoom levels.
|
||||
- v2.21.2 - Expand ship nav to ship navigation.
|
||||
- v2.21.1 - Fix ui check callback to not use .
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-contrib-web-worldmap",
|
||||
"version": "2.21.3",
|
||||
"version": "2.21.4",
|
||||
"description": "A Node-RED node to provide a web page of a world map for plotting things on.",
|
||||
"dependencies": {
|
||||
"@turf/bezier-spline": "~6.5.0",
|
||||
|
@ -72,6 +72,7 @@
|
||||
<script src="leaflet/leaflet.latlng-graticule.js"></script>
|
||||
<script src="leaflet/VectorTileLayer.umd.min.js"></script>
|
||||
<script src="leaflet/Semicircle.js"></script>
|
||||
<script src="leaflet/L.TileLayer.PixelFilter.js"></script>
|
||||
<script src="leaflet/dialog-polyfill.js"></script>
|
||||
|
||||
<script src="images/emoji.js"></script>
|
||||
|
152
worldmap/leaflet/L.TileLayer.PixelFilter.js
Normal file
152
worldmap/leaflet/L.TileLayer.PixelFilter.js
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* L.TileLayer.PixelFilter
|
||||
* https://github.com/greeninfo/L.TileLayer.PixelFilter
|
||||
* http://greeninfo-network.github.io/L.TileLayer.PixelFilter/
|
||||
*/
|
||||
L.tileLayerPixelFilter = function (url, options) {
|
||||
return new L.TileLayer.PixelFilter(url, options);
|
||||
}
|
||||
|
||||
L.TileLayer.PixelFilter = L.TileLayer.extend({
|
||||
// the constructor saves settings and throws a fit if settings are bad, as typical
|
||||
// then adds the all-important 'tileload' event handler which basically "detects" an unmodified tile and performs the pxiel-swap
|
||||
initialize: function (url, options) {
|
||||
options = L.extend({}, L.TileLayer.prototype.options, {
|
||||
matchRGBA: null,
|
||||
missRGBA: null,
|
||||
pixelCodes: [],
|
||||
crossOrigin: 'Anonymous', // per issue 15, this is how you do it in Leaflet 1.x
|
||||
}, options);
|
||||
L.TileLayer.prototype.initialize.call(this, url, options);
|
||||
L.setOptions(this, options);
|
||||
|
||||
// go ahead and save our settings
|
||||
this.setMatchRGBA(this.options.matchRGBA);
|
||||
this.setMissRGBA(this.options.missRGBA);
|
||||
this.setPixelCodes(this.options.pixelCodes);
|
||||
|
||||
// and add our tile-load event hook which triggers us to do the pixel-swap
|
||||
this.on('tileload', function (event) {
|
||||
this.applyFiltersToTile(event.tile);
|
||||
});
|
||||
},
|
||||
|
||||
// settings setters
|
||||
setMatchRGBA: function (rgba) {
|
||||
// save the setting
|
||||
if (rgba !== null && (typeof rgba !== 'object' || typeof rgba.length !== 'number' || rgba.length !== 4) ) throw "L.TileLayer.PixelSwap expected matchRGBA to be RGBA [r,g,b,a] array or else null";
|
||||
this.options.matchRGBA = rgba;
|
||||
|
||||
// force a redraw, which means new tiles, which mean new tileload events; the circle of life
|
||||
this.redraw(true);
|
||||
},
|
||||
setMissRGBA: function (rgba) {
|
||||
// save the setting
|
||||
if (rgba !== null && (typeof rgba !== 'object' || typeof rgba.length !== 'number' || rgba.length !== 4) ) throw "L.TileLayer.PixelSwap expected missRGBA to be RGBA [r,g,b,a] array or else null";
|
||||
this.options.missRGBA = rgba;
|
||||
|
||||
// force a redraw, which means new tiles, which mean new tileload events; the circle of life
|
||||
this.redraw(true);
|
||||
},
|
||||
setPixelCodes: function (pixelcodes) {
|
||||
// save the setting
|
||||
if (typeof pixelcodes !== 'object' || typeof pixelcodes.length !== 'number') throw "L.TileLayer.PixelSwap expected pixelCodes to be a list of triplets: [ [r,g,b], [r,g,b], ... ]";
|
||||
this.options.pixelCodes = pixelcodes;
|
||||
|
||||
// force a redraw, which means new tiles, which mean new tileload events; the circle of life
|
||||
this.redraw(true);
|
||||
},
|
||||
|
||||
// extend the _createTile function to add the .crossOrigin attribute, since loading tiles from a separate service is a pretty common need
|
||||
// and the Canvas is paranoid about cross-domain image data. see issue #5
|
||||
// this is really only for Leaflet 0.7; as of 1.0 L.TileLayer has a crossOrigin setting which we define as a layer option
|
||||
_createTile: function () {
|
||||
var tile = L.TileLayer.prototype._createTile.call(this);
|
||||
tile.crossOrigin = "Anonymous";
|
||||
return tile;
|
||||
},
|
||||
|
||||
// the heavy lifting to do the pixel-swapping
|
||||
// called upon 'tileload' and passed the IMG element
|
||||
// tip: when the tile is saved back to the IMG element that counts as a tileload event too! thus an infinite loop, as wel as comparing the pixelCodes against already-replaced pixels!
|
||||
// so, we tag the already-swapped tiles so we know when to quit
|
||||
// if the layer is redrawn, it's a new IMG element and that means it would not yet be tagged
|
||||
applyFiltersToTile: function (imgelement) {
|
||||
// already processed, see note above
|
||||
if (imgelement.getAttribute('data-PixelFilterDone')) return;
|
||||
|
||||
// copy the image data onto a canvas for manipulation
|
||||
var width = imgelement.width;
|
||||
var height = imgelement.height;
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
var context = canvas.getContext("2d");
|
||||
context.drawImage(imgelement, 0, 0);
|
||||
|
||||
// create our target imagedata
|
||||
var output = context.createImageData(width, height);
|
||||
|
||||
// extract out our RGBA trios into separate numbers, so we don't have to use rgba[i] a zillion times
|
||||
var matchRGBA = this.options.matchRGBA, missRGBA = this.options.missRGBA;
|
||||
if (matchRGBA !== null) {
|
||||
var match_r = matchRGBA[0], match_g = matchRGBA[1], match_b = matchRGBA[2], match_a = matchRGBA[3];
|
||||
}
|
||||
if (missRGBA !== null) {
|
||||
var miss_r = missRGBA[0], miss_g = missRGBA[1], miss_b = missRGBA[2], miss_a = missRGBA[3];
|
||||
}
|
||||
|
||||
// go over our pixel-code list and generate the list of integers that we'll use for RGB matching
|
||||
// 1000000*R + 1000*G + B = 123123123 which is an integer, and finding an integer inside an array is a lot faster than finding an array inside an array
|
||||
var pixelcodes = [];
|
||||
for (var i=0, l=this.options.pixelCodes.length; i<l; i++) {
|
||||
var value = 1000000 * this.options.pixelCodes[i][0] + 1000 * this.options.pixelCodes[i][1] + this.options.pixelCodes[i][2];
|
||||
pixelcodes.push(value);
|
||||
}
|
||||
|
||||
// iterate over the pixels (each one is 4 bytes, RGBA)
|
||||
// and see if they are on our list (recall the "addition" thing so we're comparing integers in an array for performance)
|
||||
// per issue #5 catch a failure here, which is likely a cross-domain problem
|
||||
try {
|
||||
var pixels = context.getImageData(0, 0, width, height).data;
|
||||
} catch(e) {
|
||||
throw "L.TileLayer.PixelFilter getImageData() failed. Likely a cross-domain issue?";
|
||||
}
|
||||
for(var i = 0, n = pixels.length; i < n; i += 4) {
|
||||
var r = pixels[i ];
|
||||
var g = pixels[i+1];
|
||||
var b = pixels[i+2];
|
||||
var a = pixels[i+3];
|
||||
|
||||
// bail condition: if the alpha is 0 then it's already transparent, likely nodata, and we should skip it
|
||||
if (a == 0) {
|
||||
output.data[i ] = 255;
|
||||
output.data[i+1] = 255;
|
||||
output.data[i+2] = 255;
|
||||
output.data[i+3] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// default to matching, so that if we are not in fact filtering by code it's an automatic hit
|
||||
// number matching trick: 1000000*R + 1000*G + 1*B = 123,123,123 a simple number that either is or isn't on the list
|
||||
var match = true;
|
||||
if (pixelcodes.length) {
|
||||
var sum = 1000000 * r + 1000 * g + b;
|
||||
if (-1 === pixelcodes.indexOf(sum)) match = false;
|
||||
}
|
||||
|
||||
// did it match? either way we push a R, a G, and a B onto the image blob
|
||||
// if the target RGBA is a null, then we push exactly the same RGBA as we found in the source pixel
|
||||
output.data[i ] = match ? (matchRGBA===null ? r : match_r) : (missRGBA===null ? r : miss_r);
|
||||
output.data[i+1] = match ? (matchRGBA===null ? g : match_g) : (missRGBA===null ? g : miss_g);
|
||||
output.data[i+2] = match ? (matchRGBA===null ? b : match_b) : (missRGBA===null ? b : miss_b);
|
||||
output.data[i+3] = match ? (matchRGBA===null ? a : match_a) : (missRGBA===null ? a : miss_a);
|
||||
}
|
||||
|
||||
// write the image back to the canvas, and assign its base64 back into the on-screen tile to visualize the change
|
||||
// tag the tile as having already been updated, so we don't process a 'load' event again and re-process a tile that surely won't match any target RGB codes, in an infinite loop!
|
||||
context.putImageData(output, 0, 0);
|
||||
imgelement.setAttribute('data-PixelFilterDone', true);
|
||||
imgelement.src = canvas.toDataURL();
|
||||
}
|
||||
});
|
@ -1983,7 +1983,7 @@ function setMarker(data) {
|
||||
else if (data.heading !== undefined) { track = data.heading; }
|
||||
else if (data.bearing !== undefined) { track = data.bearing; }
|
||||
if (track != undefined) { // if there is a heading
|
||||
if (data.speed != null && !data.length) { // and a speed - lets convert to a leader length
|
||||
if (data.speed != null && data.length === undefined) { // and a speed - lets convert to a leader length
|
||||
data.length = parseFloat(data.speed || "0") * 60;
|
||||
var re1 = new RegExp('kn|knot|kt','i');
|
||||
var re2 = new RegExp('kph|kmh','i');
|
||||
@ -1992,7 +1992,7 @@ function setMarker(data) {
|
||||
else if ( re2.test(""+data.speed) ) { data.length = data.length * 0.44704; }
|
||||
else if ( re3.test(""+data.speed) ) { data.length = data.length * 0.277778; }
|
||||
}
|
||||
if (data.length != null) {
|
||||
if (data.length !== undefined) {
|
||||
if (polygons[data.name] != null && !polygons[data.name].hasOwnProperty("_layers")) {
|
||||
map.removeLayer(polygons[data.name]);
|
||||
}
|
||||
@ -2452,6 +2452,11 @@ function doCommand(cmd) {
|
||||
else if (cmd.map.url.slice(-4).toLowerCase() === ".pbf") {
|
||||
overlays[cmd.map.overlay] = VectorTileLayer(cmd.map.url, cmd.map.opt);
|
||||
}
|
||||
else if (cmd.map.hasOwnProperty("transparentPixels")) {
|
||||
cmd.map.opt.pixelCodes = cmd.map.transparentPixels;
|
||||
cmd.map.opt.matchRGBA = [ 0,0,0,0 ];
|
||||
overlays[cmd.map.overlay] = L.tileLayerPixelFilter(cmd.map.url, cmd.map.opt);
|
||||
}
|
||||
else {
|
||||
overlays[cmd.map.overlay] = L.tileLayer(cmd.map.url, cmd.map.opt);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
<script src="leaflet/Leaflet.Coordinates.js"></script>
|
||||
<script src="leaflet/leaflet.latlng-graticule.js"></script>
|
||||
<script src="leaflet/Semicircle.js"></script>
|
||||
<script src="leaflet/L.TileLayer.PixelFilter.js"></script>
|
||||
<script src="leaflet/dialog-polyfill.js"></script>
|
||||
|
||||
<script src="images/emoji.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user