I'm using OpenLayers 3 for deep-zooming into high resolution images (no geo-coordinates, only pixels).
And XYZ is the format that I need as projection, but I have a problem with adding one more tiled layer, because it should have different projection.
I.e. my main layer has sizes (X) x (Y) in pixels, but second layer has sizes (X * k) x (Y * k). Therefore the layers don't correspond to each other. The tile size is the same in both cases.
Here is the code:
const imWidth = 100440;
const imHeight = 221312;
const primaryImageProjection = new ol.proj.Projection({
code: 'PIXELS',
units: 'pixels',
extent: [0, 0, imWidth, imHeight],
getPointResolution: function (resolution, point) {
return resolution;
}
});
ol.proj.addProjection(primaryImageProjection);
var heatMapProj = new ol.proj.Projection({
code: 'HEATMAP',
units: 'pixels',
extent: [0, 0, imWidth, imWidth],
getPointResolution: function (resolution, point) {
return resolution;
}
});
ol.proj.addProjection(heatMapProj);
const koef = 21.5;
ol.proj.addCoordinateTransforms('PIXELS', 'HEATMAP',
function (coordinate) {
return [coordinate[0] * koef, coordinate[1] * koef];
},
function (coordinate) {
return [coordinate[0] / koef, coordinate[1] / koef];
});
this.map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
crossOrigin: crossOrigin,
tileSize: [512, 512],
tileUrlFunction: (tileCoord, pixelRatio, projection) => {
const tileUrlTemplate = 'http://.../?lvl={z}&tx={x}&ty={y}';
const z = 9 - tileCoord[0];
const tileUrl = tileUrlTemplate
.replace("{z}", (z).toString())
.replace("{x}", (tileCoord[1]).toString())
.replace("{y}", (((-tileCoord[2]) - 1)).toString());
return tileUrl;
},
projection: 'PIXELS'
})
}),
new ol.layer.Tile({
source: new ol.source.XYZ({
crossOrigin: crossOrigin,
tileSize: [512, 512],
tileUrlFunction: (tileCoord, pixelRatio, projection) => {
const heatMapUrlTemplate = 'http://...?lvl={z}&tx={x}&ty={y}';
var z = 4 - tileCoord[0];
const heatMapUrl = heatMapUrlTemplate
.replace('{z}', (z).toString())
.replace('{x}', (tileCoord[1]).toString())
.replace('{y}', (((-tileCoord[2]) - 1)).toString());
return heatMapUrl;
},
projection: 'HEATMAP'
}),
opacity: 0.5
})
],
controls: [],
target: this.mapSelector,
renderer: "canvas",
view: new ol.View({
projection: 'PIXELS'
})
});
How can I solve this? Maybe projections are not the right way and it can be solved differently.
PS: also you can see that I'm using the function getPointResolution. Otherwise I see error messages in console: "transform should be defined". And after dubugging I found that OpenLayers makes some transformations internally from my projections into "EPSG:4326" and back. I wouldn't expect to have any spherical coordinates inside, since I'm using only flat images, but it looks that OpenLayers need it anyway.
Raster reprojection (i.e. different projections for the layers of a map) is not an experimental feature. But to make it work for you, you need to register transformation functions between the projections of your layers, using ol.proj.addCoordinateTransforms. Without seeing your code it is hard to guess, but something like this should do the trick:
var proj1 = new ol.proj.Projection({
code: 'ZOOMIFY1',
units: 'pixels',
extent: [0, -Y, X, 0]
});
var proj2 = new ol.proj.Projection({
code: 'ZOOMIFY2',
units: 'pixels',
extent: [0, -Y * k, X * k, 0]
});
ol.proj.addCoordinateTransforms(proj1, proj2,
function(coordinate) {
return [coordinate[0] * k, coordinate[1] * k];
},
function(coordinate) {
return [coordinate[1] / k, coordinate[2] / k];
});
Now you can go ahead, configure your two Zoomify layers - the first with proj1 as projection and the second with proj2, and they should align just fine.
Note that the above is untested. If you can provide a working snippet or JSFiddle, I can refine my answer if something is not working.
Related
Current highcharts support free drawing of a line segment annotation. But I want to be able to draw a line based on the data points on the chart series. Basically I want to implement:
click on first point in the chart series to select the first data point
click on the second point on the chart to select the second data point
draw a line segment with those two data points
I should be able to edit the segment just like current highcharts annotations. The edit dialog should show line segment option plus two data points which I can edit.
Any idea how to implement this?
Let's start with this demo: https://jsfiddle.net/BlackLabel/7xvr85wt/
Select two points by clicking on them & holding the shift, next click on the button. Is that an output that you want to achieve? Now, what do you want to add to this segment line?
const chart = Highcharts.chart('container', {
series: [{
data: [43934, 52503, 57177, 69658, 97031, 119931, 137133, 154175],
allowPointSelect: true,
}]
});
function drawPath(points) {
points.length = 2;
const x1 = points[0].plotX + chart.plotLeft,
x2 = points[1].plotX + chart.plotLeft,
y1 = points[0].plotY + chart.plotTop,
y2 = points[1].plotY + chart.plotTop;
chart.renderer.path(['M', x1, y1, 'L', x2, y2])
.attr({
'stroke-width': 2,
stroke: 'red'
})
.add();
}
const btn = document.getElementById('btn');
btn.addEventListener('click', function() {
const selectedPoints = [];
chart.series[0].points.forEach(p => {
if (p.selected) {
selectedPoints.push(p);
}
})
drawPath(selectedPoints);
});
API: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#path
I am using Konva.js to render some colourful boxes.
I have a Stage with some set size and regions within that Stage, bound by points (e.g. rectangles, but they can be any polygons).
I then need to add some number of smaller rects inside this area such that they look nice and are uniformly distributed.
E.g.
// these numbers are an example only, irrelevant
let width = 500 * 1.936402653140851;
let height = 500;
// setup stage, boilerplate
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
// region layer - boundaries
var regionLayer = new Konva.Layer({
scale: {x: width/4963, y: height/2563} // these numbers are an example only, irrelevant
});
stage.add(regionLayer);
// for this example, a single region, L-shaped on it's side
let box = new Konva.Line({
name: 'region-1',
points: [1067, 681,
3337, 681,
3337, 987,
3037, 987,
3037, 787,
1067, 787],
closed: true,
fill: 'blue',
opacity: 0.5
});
regionLayer.add(box);
regionLayer.draw();
// layer to draw rects in
let rectsLayer = new Konva.Layer({
scale: {x: width/4963, y: height/2563} // these numbers are an example only, irrelevant
});
for (let i = 0; i < 90; i++) { // e.g. some number up to X
let rect = new Konva.Rect({
width: 20,
height: 20,
fill: 'red',
x: 1067 + 40 * (i + 1),
y: 681 + 20
});
rectsLayer.add(rect);
}
stage.add(rectsLayer);
rectsLayer.draw();
The result of the above is something like this
What I'd like to have, is those boxes filling free area within the boundaries of my polygon only.
I read a whole heap about Delauney triangulation, Voronoi diagrams and sequencing for normal distribution to make it happen, but sadly I came up with zilch on practical approach of doing this.
https://jsfiddle.net/x4aksz1y/2/
I am trying to create a reference line that runs through the origin and passes from negative to positive. See an example of what i am trying to achieve - see the threshold line. This threshold line must run through all three x, y coordinates (-1,-45,000), (0.0), (1, 45,000).
enter image description here
Below is my work so far.
http://jsfiddle.net/catio6/rhf6yre5/1/
I've looked at this for reference but have had had no luck after several hours of attempts of replicating this with all three x, y coordinates (-1,-45,000), (0.0), (1, 45,000): http://jsfiddle.net/phpdeveloperrahul/XvjfL/
$(function () {
$('#container').highcharts({
xAxis: {
categories: ['Jan', 'Feb', 'Mar']
},
series: [{
data: [29.9, 71.5, 256]
}]
}, function(chart) { // on complete
console.log("chart = ");
console.log(chart);
//chart.renderer.path(['M', 0, 0, 'L', 100, 100, 200, 50, 300, 100])
chart.renderer.path(['M', 75, 223.5,'L', 259, 47])//M 75 223.5 L 593 223.5
.attr({
'stroke-width': 2,
stroke: 'red'
})
.add();
});
});
So Highcharts doesn't have, as far as I know, a way to define a line that goes from/to infinity.
One idea I had to solve this issue for you is dynamically calculate the values for the line series based on your data. The idea is simple: Given the maximum values for X and Y you want to plot, we just need to limit the axis to a certain value that makes sense and calculate the values for the asymptote series in order to make it seems infinite. My algorithm looks like this:
// Get all your other data in a well formated way
let yourData = [
{x: 0.57, y: 72484},
{x: 0.57, y: 10000}
];
// Find which are the maximum x and y values
let maxX = yourData.reduce((max, v) => max > v.x ? max : v.x, -999999999);
let maxY = yourData.reduce((max, v) => max > v.y ? max : v.y, -999999999);
// Now you will limit you X and Y axis to a value that makes sense due the maximum values
// Here I will limit it to 10K above or lower on Y and 2 above or lower on X
let maxXAxis = maxX + 2;
let minXAxis = - (maxX + 2);
let maxYAxis = maxY + 10000;
let minYAxis = -(maxY + 10000);
// Now you need to calculate the values for the Willingness to pay series
let fn = (x) => 45000 * x; // This is the function that defines the Willingness to pay line
// Calculate the series values
let willingnessSeries = [];
for(let i = Math.floor(minXAxis); i <= Math.ceil(maxXAxis); i++) {
willingnessSeries.push([i, fn(i)]);
}
Here is a working fiddle: http://jsfiddle.net/n5xg1970/
I tested with several values for your data and all of them seem to be working ok.
Hope it helps
Regards
QUESTION:
How can we render a feature to a canvas using a style but not using a map?
BACKGROUND:
I have a sample which renders a geometry to a canvas honoring the ol3 style but it only runs with the unbuilt version of openlayers (ol-debug.js) and because it makes use of private functions (ol.vec.Mat4).
works when using ol-debug.js
fails when using ol.js
One alternative is to create a map, add a vector layer, set the style on the feature, add the feature to the layer and remove all the events/controls from the map so it looks like a canvas.
A second is to use goog.vec.Mat4.
let scale = Math.min(canvas.width / ol.extent.getWidth(extent), canvas.height / ol.extent.getHeight(extent));
console.log("scale", scale);
let transform = Mat4.makeTransform2D(identity,
canvas.width / 2, canvas.height / 2, // translate to origin
scale, -scale, //scale
0, // rotation
-center[0], -center[1] // translate back
);
console.log("transform", transform);
let renderer = new ol.render.canvas.Immediate(ctx, 1, extent, transform, 1);
renderer.drawFeature(feature, style);
A third is similar to the second in that I take on the responsibility of transforming the geometry into pixel coordinates before using ol.render.toContext, as demonstrated in this example.
I think that about exhausts it? Or is there another way?
Doh! Found an example right on the openlayers site!
In that sample coordinates are already pixels:
<!DOCTYPE html>
<html>
<head>
<title>Render geometries to a canvas</title>
<link rel="stylesheet" href="http://openlayers.org/en/v3.17.1/css/ol.css" type="text/css">
<script src="http://openlayers.org/en/v3.17.1/build/ol.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById('canvas');
var vectorContext = ol.render.toContext(canvas.getContext('2d'), {size: [100, 100]});
var fill = new ol.style.Fill({color: 'blue'});
var stroke = new ol.style.Stroke({color: 'black'});
var style = new ol.style.Style({
fill: fill,
stroke: stroke,
image: new ol.style.Circle({
radius: 10,
fill: fill,
stroke: stroke
})
});
vectorContext.setStyle(style);
vectorContext.drawGeometry(new ol.geom.LineString([[10, 10], [90, 90]]));
vectorContext.drawGeometry(new ol.geom.Polygon([[[2, 2], [98, 2], [2, 98], [2, 2]]]));
vectorContext.drawGeometry(new ol.geom.Point([88, 88]));
</script>
</body>
</html>
But as the question indicates, translation -> scale -> translation transforms the data:
function render(canvas: HTMLCanvasElement, line: ol.Coordinate[], style: ol.style.Style) {
let extent = ol.extent.boundingExtent(line);
let [dx, dy] = ol.extent.getCenter(extent);
let [sx, sy] = [canvas.width / ol.extent.getWidth(extent), canvas.height / ol.extent.getHeight(extent)];
line= translate(line, [-dx, -dy]);
line= scale(line, [Math.min(sx, sy), -Math.min(sx, sy)]);
line= translate(line, [canvas.width / 2, canvas.height / 2]);
let feature = new ol.Feature({
geometry: new ol.geom.Polygon([line]),
style: style
});
let vtx = ol.render.toContext(canvas.getContext("2d"));
vtx.drawFeature(feature, style);
}
Here is my TRS logic:
function translate(points: number[][], vector: number[]) {
return points.map(p => vector.map((v, i) => v + p[i]));
}
function rotate(points: number[][], a: number) {
return points.map(p => {
let [x, y, cos, sin] = [p[0], p[1], Math.cos(a), Math.sin(a)];
return [
x * cos - y * sin,
x * sin + y * cos
];
});
}
function scale(points: number[][], vector: number[]) {
return points.map(p => vector.map((v, i) => v * p[i]));
}
Can forio contour be used to plot points on a sphere so that the sphere can be rotated and zoomed? Or do I need to do this in d3.js? Or possibly some Juila package? I would like to integrate this into a forio epicenter project and also make it interactive with the underlying data.
I'm not exactly sure what you mean by 'plot points on a sphere', in any case, the 'sphere chart' is not part of the base functionality of Contour, but you can write an extension that does what you want. One important point is that Contour (and d3 in general) has native support for 2d shapes but not 3d shapes, so you'd have to implement projecting the sphere into 2d screen space.
If you can tell me a bit more about what you're trying to do, maybe I can be of more help. In the meantime, here's a simple example of an extension that plots points on a 2d circle (data is angles in this case)
http://jsfiddle.net/tmzsudv5/
Contour.export('round', function (data, layer, options) {
var r = 100;
var theta = 2 * Math.PI / 180;
var centerX = options.chart.width / 2;
var centerY = options.chart.height / 2;
layer.selectAll('circle').data(data[0].data).enter()
.append('circle')
.attr('class', 'dot')
.attr('r', 1)
.attr('cx', function(d, i) { return r * Math.cos(d.y * theta) + centerX; })
.attr('cy', function(d, i) { return r * Math.sin(d.y * theta) + centerY; });
});
var data = _.range(100).map(function(n) { return Math.floor(Math.random() * 360); });
new Contour({
el: '.chart',
})
.round(data)
.render();