How to add relevant scale bars on inset maps using tmap - viewport

I used tmap to create the plot attached. However, I would like to add a scale bar to the inset map, but I haven't been able to figured out how to do that. Can someone please help me?
Here are the codes that I used to create the attached map:
main_map <- tmap::tm_shape(main_map_df) +
tmap::tm_polygons(
col = "var.q5",
palette = c("#CCCCCC", "#999999", "#666666", "#333333", "#000000"),
#alpha = 0.7,
lwd = 0.5,
title = "") +
tmap::tm_layout(
frame = FALSE,
legend.outside = TRUE,
legend.hist.width = 5,
legend.text.size = 0.5,
fontfamily = "Verdana") +
tmap::tm_scale_bar(
position = c("LEFT", "BOTTOM"),
breaks = c(0, 10, 20),
text.size = 0.5
) +
tmap::tm_compass(position = c("LEFT", "TOP"))
inset_map <- tmap::tm_shape(inset_map_df) +
tmap::tm_polygons() +
tmap::tm_shape(main_map_df) +
tm_fill("grey50") +
tmap::tm_scale_bar(
position = c("LEFT", "BOTTOM"),
breaks = c(0, 10, 20),
text.size = 0.5
)
# Combine crude rate map (inset + main) =====
tiff(
"main_map_w_iset.tiff",
height = 1200,
width = 1100,
compression = "lzw",
res = 300
)
main_map
print(
inset_map,
vp = viewport(
x = 0.7,
y = 0.18,
width = 0.3,
height = 0.3,
clip = "off")
)
dev.off()
Thank you!

Here's a simple example using the World data set:
library(tidyverse)
library(tmap)
library(grid)
data("World")
# main map
tm_main <- World %>%
filter(name == "Australia") %>%
tm_shape() +
tm_polygons(col = "red",
alpha = .5) +
tm_scale_bar()
# inset map
tm_inset <- tm_shape(World) +
tm_polygons(col = "gray",
alpha = .5) +
tm_scale_bar()
vp <- viewport(x = .615, y = .5, width = .6, height = .6, just = c("right", "top"))
# final map
tmap_save(tm_main, filename = "test_inset.png", insets_tm = tm_inset, insets_vp = vp,
height = 200, width = 200, units = "mm")

Related

Custom coordinates and axis range on leaflet.js

I have a raster image with dimensions (in pixels) 16384-by-12288 which is successfully rendered in leaflet. I am using a my own CRS and I am placing point (0,0) at the bottomleft corner of the image point (16384, 12288) at its topright using the option: transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256).
The axes of my image, however, have range x:[6150, 1370] and y:[12987, 18457]
How can I tell leaflet to use my range as a system of coordinates please? Hence a marker at location (6150, 12987) will correspond and show up at the bottomleft corner: (0,0). I have done this manually using the function below:
var grid = {x0: 6150, // range of plot in Matlab
x1: 13751,
y0: 12987,
y1: 18457};
var img = [16384,
12288];
function project(p, img, grid) {
var x = p[0],
y = p[1];
xx = img[0] / (grid.x1 - grid.x0) * (x - grid.x0);
yy = img[1] / (grid.y1 - grid.y0) * (y - grid.y0);
return [xx, yy]
}
I was wondering however that there must a more streamlined and better way to do this. My code is:
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('myImage/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6
}).addTo(map);
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");
I think I did some progress. In case someone faces something similar in the future, here is my code: (comments more than welcome)
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
var mapSW = [0, 16384],
mapNE = [12288, 0];
var roi = { //range of interest
x0: 6150,
x1: 13751,
y0: 12987,
y1: 18457
};
a = img[0] / (roi.x1 - roi.x0)
b = -img[0] / (roi.x1 - roi.x0) * roi.x0
c = img[1] / (roi.y1 - roi.y0)
d = -img[1] / (roi.y1 - roi.y0) * roi.y0
// This transformation maps a point in pixel dimensions to our user defined roi
var t = new L.Transformation(a, b, c, d);
// The transformation in this CRS maps the the bottom right corner to (0,0) and the topleft to (256, 256)
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('map/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6,
}).addTo(map);
// map.setMaxBounds(new L.LatLngBounds(
// map.unproject(mapSW, map.getMaxZoom()),
// map.unproject(mapNE, map.getMaxZoom()),
// ));
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");
var marker = L.marker(xy([10000, 0]), {
draggable: true
}).addTo(map);
marker.bindPopup("");
marker.on("dragend", function(e) {
m = marker.getLatLng();
proj = map.project(m, map.getMaxZoom());
marker.getPopup().setContent('Clicked ' +m.toString() + '<br />' +
'Pixels ' + proj.toString())
.openOn(map);
})
L.control.scale({
imperial: false
}).addTo(map);
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
var p = t.transform(L.point(roi.x1, roi.y1));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(10000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(13000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(6150, 18000));
L.circleMarker(xy([p.x, p.y])).addTo(map);

How to rotate image inside HTML5 canvas without drawing it?

I'm generating image programmatically inside canvas.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// here I have some code in loop setting individual pixels
// ...
//
// save image to variable
var dataURL = canvas.toDataURL();
How can I rotate created image by 90 degrees?
EDIT:
This is not duplicate because I don't draw image, it is never visible. I only want to generate it, rotate it and save to variable.
EDIT2:
I'm trying to rotate it with this code:
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
But it doesn't work
EDIT3:
This is more complex example of my code:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.setPixel = function (x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
case 1:
var color = '#ffff00',
type = 'w'
break
case 3:
var rgb = (256 - parseInt(pixels[i][j]) - minus.grass).toString(16),
color = '#00' + rgb + '00',
type = 'g'
break
case 4:
var rgb = (256 - parseInt(pixels[i][j]) - minus.hills).toString(16),
color = '#' + rgb + rgb + '00',
type = 'h'
break
case 5:
var rgb = (parseInt(pixels[i][j]) + minus.mountains).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
case 6:
var rgb = (parseInt(pixels[i][j]) + minus.snow).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
}
if (i % fieldSize == 0 && j % fieldSize == 0) {
if (notSet(fields[y])) {
fields[y] = []
}
fields[y][x] = type
x++
}
canvas.setPixel(i, j, color)
}
if (i % fieldSize == 0) {
x = 0
y++
}
}
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
var token = {
type: 'save',
map: canvas.toDataURL('image/png')
}
ws.send(JSON.stringify(token))
To rotate image by 90 degrees I had to put
ctx.translate(0, canvas.height)
ctx.rotate(270 * Math.PI / 180)
before
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
// ... drawing pixels
}
}
}

add geometry from vertex and vertex nromal

I load a model with vertex and vertex nromal,
for (var i = 0, vindex = 0; i < triangle.length; i++, vindex += 3) {
x = parseFloat(triangle[i].attributes.getNamedItem('x1').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y1').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z1').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x2').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y2').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z2').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x3').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y3').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z3').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
var face = new THREE.Face3(vindex, vindex + 1, vindex + 2);
face.color.setHex(this.faceColor || this.defaultcolor);
face.vertexNormals = [];
nx = parseFloat(triangle[i].attributes.getNamedItem('nx1').value);
ny = parseFloat(triangle[i].attributes.getNamedItem('ny1').value);
nz = parseFloat(triangle[i].attributes.getNamedItem('nz1').value);
face.vertexNormals.push(new THREE.Vector3(-nx, -ny, -nz));
nx1 = parseFloat(triangle[i].attributes.getNamedItem('nx2').value);
ny1 = parseFloat(triangle[i].attributes.getNamedItem('ny2').value);
nz1 = parseFloat(triangle[i].attributes.getNamedItem('nz2').value);
face.vertexNormals.push(new THREE.Vector3(-nx1, -ny1, -nz1));
nx2 = parseFloat(triangle[i].attributes.getNamedItem('nx3').value);
ny2 = parseFloat(triangle[i].attributes.getNamedItem('ny3').value);
nz2 = parseFloat(triangle[i].attributes.getNamedItem('nz3').value);
face.vertexNormals.push(new THREE.Vector3(-nx2, -ny2, -nz2));
face.normal.set((nx + nx1 + nx2) / 3, (ny + ny1 + ny2) / 3,(nz + nz1 + nz2) / 3);
this.geometry.faces.push(face);
}
this.material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: true , opacity: 1, transparent: 0 });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.name = this.id;
this.mesh.updateMatrix();
this.mesh.matrixAutoUpdate = false;
scene.add(this.mesh);
the house below, front face is invisible, so front wall and left wall is invisible, we can see through the inside of house, but I want it to show all walls and not see through, could anyone help me?
after I change to Lambert material it still show house inside, I've tried, cw,ccw, or invert index of vertex, invert normal. could any body help?
it is possible there is something wrong with the face UV's. try making the material applied doublesided.
seems find the answer.
that's because part of the house model position.z < 0, and camera's near < 0, maybe three.js z-buffer clear negative = 0, z-buffer determines the sheltery relation.

RGB Color not showing

I have some normalized points for (Red, Blue, Green). When I convert them to RGB, I get values such as:
R: 0.23
G: 0.44
B: 0.33
However, this is not showing up as any color but white when I color my face on my tubegeometry. This happens to almost all RGB values except the main ones such as 255, 0, 0 or 0, 255, 0
var rVal = parseFloat(rLow + normalizedGr * (rHigh - rLow));
var gVal = parseFloat(gLow + normalizedGr * (gHigh - gLow));
var bVal = parseFloat(bLow + normalizedGr * (bHigh - bLow));
var logColor = new THREE.Color().setRGB(rVal, gVal, bVal);
for (var j = startingPoint; j < endingPoint; j++) {
tube.faces[j].color = logColor;
}
var mesh = THREE.SceneUtils.createMultiMaterialObject(tube, [new THREE.MeshLambertMaterial({ color: color, vertexColors: THREE.FaceColors })]);
According to http://threejs.org/docs/#Reference/Math/Color setRGB( r, g, b ) sets the color from RGB values between 0 and 1.

html5 canvas, how to export a 2x image?

I designed a web app with html5 canvas. To export an image, the code will be below:
var img = canvas.toDataURL("image/png");
Is there any way to export a 2x image?
It is for hdpi display like apple retina display.
Yes there are a few ways but every time you stretch a non vector image you will get some pixel distortion. However if its only two times the size you could get away with it using nearest neighbor. The below example shows two different methods, one is just stretching the image, the other uses nearest neighbor with a zoom factor of two.
Live Demo
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
canvas2 = document.getElementById("canvas2"),
ctx2 = canvas2.getContext("2d"),
tempCtx = document.createElement('canvas').getContext('2d'),
img = document.getElementById("testimg"),
zoom = 2;
tempCtx.drawImage(img, 0, 0);
var imgData = tempCtx.getImageData(0, 0, img.width, img.height).data;
canvas.width = img.width * zoom;
canvas.height = img.height * zoom;;
// nearest neighbor
for (var x = 0; x < img.width; ++x) {
for (var y = 0; y < img.height; ++y) {
var i = (y * img.width + x) * 4;
var r = imgData[i];
var g = imgData[i + 1];
var b = imgData[i + 2];
var a = imgData[i + 3];
ctx.fillStyle = "rgba(" + r + "," + g + "," + b + "," + (a / 255) + ")";
ctx.fillRect(x * zoom, y * zoom, zoom, zoom);
}
}
// stretched
ctx2.drawImage(img, 0, 0, 140, 140);
#phrogz has a great example of this here as well, showing a few different ways you can accomplish image re-sizing.

Resources