Reputation:
I am trying to zoom a map in canvas.
var projection = d3.geoMercator()
projection.fitExtent([[margin.left, margin.top], [width, height]], land);
var path = d3.geoPath().projection(projection).context(context);
When I apply the translate and scale in to the canvas context, it works perfectly. But it does not return the correct latitude and longitude when i call var latlong = projection.invert(d3.mouse(this));
since the projection is not translated accordingly.
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.on("zoom", zoomByContext);
function zoomByContext() {
var transform = d3.event.transform;
context.clearRect(0, 0, width, height);
context.save();
context.translate(transform.x, transform.y);
context.lineWidth = 0.5 / transform.k;
context.scale(transform.k, transform.k);
renderFeature();
context.restore();
}
So I tried reprojecting the projection like blow. But it goes to top left corner when I zoom with the below code.
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.on("zoom", zoomByProjection);
function zoomByProjection() {
var transform = d3.event.transform;
projection.translate([transform.x, transform.y]);
projection.scale(scale * transform.k);
renderFeature();
}
And I have called the zoom like this
canvas.call(zoom, d3.zoomIdentity
.translate(projection.translate())
.scale(projection.scale()));
Upvotes: 1
Views: 855
Reputation: 38171
For the first approach you need to invert the zoom prior to inverting the xy coordinates into long lat coordinates:
var transform = d3.zoomTransform(this);
var xy = transform.invert(d3.mouse(this));
var longlat = projection.invert(xy);
We get the mouse position in pixel coordinates, invert it to zoom coordinates, and then invert that to geographic coordinates.
This should demonstrate the above:
var width = 960;
var height = 500;
var canvas = d3.select("canvas");
var context = canvas.node().getContext("2d")
var projection = d3.geoMercator();
var path = d3.geoPath(projection,context);
d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
if (error) throw error;
renderFeature();
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.on("zoom", zoomByContext);
canvas.call(zoom);
function zoomByContext() {
var transform = d3.event.transform;
context.clearRect(0, 0, width, height);
context.save();
context.translate(transform.x, transform.y);
context.lineWidth = 0.5 / transform.k;
context.scale(transform.k, transform.k);
renderFeature();
context.restore();
}
function renderFeature() {
context.beginPath();
path(topojson.mesh(world));
context.stroke();
}
canvas.on("click", function() {
var transform = d3.zoomTransform(this);
var xy = transform.invert(d3.mouse(this));
var longlat = projection.invert(xy);
console.log(longlat);
})
});
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
The second approach is a little trickier, if the projection's translate is [0,0]
the approach you have will work, however this is rarely the case. The default value is [480,250]
(assumes a canvas that is 960x500), and fitSize
and fitExtent
don't position the feature by modifying rotate and center, but rather translate. So you need to account for the initial translate when modifying the projection (just as you have done with scale):
var transform = d3.event.transform;
projection.translate([transform.x+translate[0]*transform.k, transform.y+translate[1]*transform.k]);
projection.scale(scale * transform.k);
Here translate
is an array holding the initial translate values
And here's a demonstration that should demonstrate the above:
var width = 960;
var height = 500;
var canvas = d3.select("canvas");
var context = canvas.node().getContext("2d")
var projection = d3.geoMercator().center([105,3]).scale(1200).translate([2000,0]);
var path = d3.geoPath(projection,context);
var scale = projection.scale();
var translate = projection.translate();
d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
if (error) throw error;
renderFeature();
var zoom = d3.zoom()
.scaleExtent([0.1, Infinity])
.on("zoom", zoomByProjection);
canvas.call(zoom);
function zoomByProjection() {
context.clearRect(0, 0, width, height);
var transform = d3.event.transform;
projection.translate([transform.x+translate[0]*transform.k, transform.y+translate[1]*transform.k]);
projection.scale(scale * transform.k);
renderFeature();
}
function renderFeature() {
context.beginPath();
path(topojson.mesh(world));
context.stroke();
}
});
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
Upvotes: 2