1

I am creating a map for a school project, involving the click-to-zoom function here and a scale bar from the model here without the zoom function from the script. I managed to program both in my source code, but I would like the scale bar to respond correctly when I click-to-zoom on a country, by having the right values adapted to the scale I am zooming to. Here is the function for the click-to-zoom :

function clicked(d) {

var bounds = path.bounds(d),
  dx = bounds[1][0] - bounds[0][0],
  dy = bounds[1][1] - bounds[0][1],
  x = (bounds[0][0] + bounds[1][0]) / 2,
  y = (bounds[0][1] + bounds[1][1]) / 2,
  scale = .9 / Math.max(dx / width, dy / height),
  translate = [width / 2 - scale * x, height / 2 - scale * y];

pays.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + 
"px").style("width", 1.5 / scale + "px").style("height", 1.5 / scale + "px").attr("transform", 
"translate(" + translate + ")scale(" + scale + ")");
villes.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + 
"px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
capitales.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + 
"px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
labels.selectAll("text").transition().duration(750).style("font-size", 11 / scale + 
"px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}

And here is the one from the scalebar :

    // Start Scale ---------------------------------------------------------
function scale() {
// baseWidth refers to ideal scale width on the screen it also is the width of the initial measurement point
var g = svg.append("g");
var baseWidth = width / 4;
var p1 = projection.invert([width/2 - baseWidth/2, height / 2]);
var p2 = projection.invert([width/2 + baseWidth/2, height / 2]);
var distance = getDistance(p1,p2);
var unit = "m"; 
var multiply = 1; 
var bestFit = 1;
var increment = 0.1; // This could be scaled to map width maybe width/10000;
var scaleDistance = 0;
var scaleWidth = 0;

if ( distance > 1000 ) { 
    unit = "km"; multiply = 0.001;          
}
// Adjust distance to a round(er) number
var i = 0;
while (i < 400) {
    var temp = getDistance( projection.invert([ width/2 - (baseWidth / 2) + (increment * i), height / 2 ]),  projection.invert([ width/2 + baseWidth/2 - (increment * i), height / 2 ]));
    var ratio = temp / temp.toPrecision(1);

    // If the second distance is moving away from a cleaner number, reverse direction.
    if (i == 1) {
        if (Math.abs(1 - ratio) > bestFit) { increment = - increment; }
    }
    // If we are moving away from a best fit after that, break
    else if (i > 2) {
        if (Math.abs(1 - ratio) > bestFit) { break }
    }               
    // See if the current distance is the cleanest number
    if (Math.abs(1-ratio) < bestFit) {
        bestFit = Math.abs(1 - ratio); 
        scaleDistance = temp; 
        scaleWidth = (baseWidth) - (2 * increment * i);
    }
    i++;
}

// Now to build the scale           
var bars = [];
var smallBars = 10; 
var bigBars = 4;
var odd = true;
var label = false;

// Populate an array to represent the bars on the scale
for (i = 0; i < smallBars; i++) {
    if (smallBars - 1 > i ) { label = false; } else { label = true; }
    bars.push( {width: 1 / (smallBars * (bigBars + 1)), offset: i / (smallBars * (bigBars + 1)), label: label, odd: odd } );
    odd = !odd;
    }
for (i = 0; i < bigBars; i++) {
    bars.push( {width: 1 / (bigBars + 1), offset: (i + 1) / (bigBars + 1), label: true, odd: odd } );
    odd = !odd;
    }

// Append the scale
var scaleBar = g.selectAll(".scaleBar")
    .data(bars);

      // enter bars with no width
      scaleBar
            .enter()
            .append("rect")
            .attr("x", 20)
            .attr("y", height - 40)
            .attr("height",20)
            .attr("width",0)
            .attr("class","scaleBar")
            .merge(scaleBar) // merge so that rect are updates if they are in the enter selection or the update selection.
            .transition()
    .attr("x", function(d) { return d.offset * scaleWidth + 20 })
    //.attr("y", height - 30)
    .attr("width", function(d) { return d.width * scaleWidth})
    //.attr("height", 10)
    .attr("fill", function (d) { if (d.odd) { return "#eee"; } else { return "#222"; } })
            .duration(1000);

    g.selectAll(".scaleText").remove();

g.selectAll(".scaleText") 
    .data(bars).enter()
    .filter( function (d) { return d.label == true })
    .append("text")
    .attr("class","scaleText")
    .attr("x",0)
    .attr("y",0)
    .style("text-anchor","start")
    .text(function(d) { return d3.format(",")(((d.offset + d.width) * scaleDistance).toPrecision(2) * multiply); })
    .attr("transform", function(d) { return "translate("+ ((d.offset + d.width) * scaleWidth + 20 )+","+ (height - 45) +") rotate(-45)" })
            .style("opacity",0)
            .transition()
            .style("opacity",1)
            .duration(1000);


g.append("text")
    .attr("x", scaleWidth/2 + 20)
    .attr("y", height - 5)
    .text( function() { if(unit == "km") { return "Kilometres"; } else { return "metres";}  })
    .style("text-anchor","middle")            
            .attr("class","scaleText")
            .style("opacity",0)
            .transition()
            .style("opacity",1)
            .duration(1000);
 }
 // End Scale -----------------------------------------
 scale(); 

 function getDistance(p1,p2) { 

 var lat1 = p1[1];
var lat2 = p2[1];
var lon1 = p1[0];
var lon2 = p2[0];

var R = 6371e3; // metres
var φ1 = lat1* Math.PI / 180;
var φ2 = lat2* Math.PI / 180;
var Δφ = (lat2-lat1)* Math.PI / 180;
var Δλ = (lon2-lon1)* Math.PI / 180;

var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
        Math.cos(φ1) * Math.cos(φ2) *
        Math.sin(Δλ/2) * Math.sin(Δλ/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

var distance = R * c;

return distance;

}

My level in d3.js mapping is weak, I will be thankful for any clues or solutions !

fefe78
  • 31
  • 3
  • If you were zooming by modifying the projection (rather than scaling the SVG) you could just update the scale as [so](https://bl.ocks.org/Andrew-Reid/raw/04822ce16381356b961a39d014037542) - but an SVG zoom transform will require a few tweaks - I'll look a bit closer later this weekend. – Andrew Reid Dec 15 '19 at 01:44
  • Thank you. I managed to do so in the same code, but I can't get the same scale as when I click on a railway to zoom to scale (the function clicked is linked to this layer) – fefe78 Dec 17 '19 at 08:58
  • No, it wouldn't work with your approach because you are not modifying the projection on zoom - the SVG is independent of the projection scale and translate. You need an extra step. Sorry if I wasn't clear above. I think I have a solution - however if you are using the composite AlbersUsa projection like the one in your link, Alaska should have a different scale bar which will be difficult to implement. – Andrew Reid Dec 17 '19 at 16:17
  • The projection I am using here is mercator actually, centered on western europe. Therefore, your solution may work in that case – fefe78 Dec 17 '19 at 16:29

2 Answers2

0

The scale bar example assumes that two coordinates on either side (horizontally) of [width/2,height/2] are representative of the map. This center coordinate is fixed. The example uses projection.invert() to calculate the real world distance between these two points.

The zoom example uses an SVG transform to zoom and pan the map. Updating the zoom transform is done independently of the projection, so projection.invert() will always return the same distance as long as the coordinates are fixed and independent of the transform.

In this example, the scale bar is updated when the projection is updated, but we can modify it so that we can take into account a zoom transform as well.

The scale bar example uses the following to get the two initial reference center points:

var baseWidth = width / 4;
var p1 = projection.invert([width/2 - baseWidth/2, height / 2]);
var p2 = projection.invert([width/2 + baseWidth/2, height / 2]);

This question covers how to convert coordinates to zoom coordinates:

var xy = d3.mouse(this);         // relative to specified container
var transform = d3.zoomTransform(selection.node());
var xy1 = transform.invert(xy);  // relative to zoom

So we can make a few modifications:

// Points relative to parent container:
var xy1 = [width/2 - baseWidth/2, height/2];
var xy2 = [width/2 + baseWidth/2, height/2];

// Zoom transform:
var transform = d3.zoomTransform(g.node());

// Points relative to zoom:
xy1 = transform.invert(xy1);
xy2 = transform.invert(xy2);

Now we have two points with the zoom transform applied and where they need to be. We can now proceed as before with one other change. The original scale bar example adjusted its length to a nice round number using:

var temp = getDistance( projection.invert([ width/2 - (baseWidth / 2) + (increment * i), height / 2 ]),  projection.invert([ width/2 + baseWidth/2 - (increment * i), height / 2 ]));

We need to update this to use the points relative to the zoom transform and scale the incremental length in accordance with the zoom scale:

var temp = getDistance( projection.invert([xy1[0] + (increment * i/transform.k), xy1[1]]),  projection.invert([ xy2[0] - (increment * i/transform.k), xy2[1] ]));

There are simpler ways to get round numbers then the approach I've taken in the example, but as screen distances are often not linear, this may introduce more error than otherwise needed

Here's an updated example (I have placed the zoom in it's own g container so as to not apply the zoom to it - there are a few changes due to this. I've also been lazy: the zoom only interacts with the land in this example).

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
0

Thank you very much, I really appreciate the effort you did for solving my problem. But I'm afraid I might not quite get the complete way to include your code. Here is the new version, I still don't get a change in the scalebar values by clicking on entities to zoom.

var startYear = 1990,
    currentYear = startYear;

const width = 960, height = 600;

const path = d3.geoPath();

const projection = d3.geoMercator()
    .center([9, 47])
    .scale(1000)
    .translate([width/2, height/2]);

path.projection(projection);

const svg = d3.select('#carte')
    .append("svg")
    .attr("id", "svg")
    .attr("width", width)
    .attr("height", height);


/***************************************************************************/
/*************************************** AJOUT DES OBJETS SUR LA CARTE *****/
/***************************************************************************/

const pays = svg.append("g");
pays.selectAll("path")
    // La variable geojson est créée dans le fichier JS qui contient le GeoJSON
    .data(geojson_ue.features)
    .enter()
    .append("path")
    .attr("d", path)
    // Sémiologie (par défaut) des objets
    .style("fill", "#e6e6e6")
    .style("stroke-width", 3)
    .style("stroke", "#fff");

const pays2 = svg.append("g");
pays.selectAll("path")
    // La variable geojson est créée dans le fichier JS qui contient le GeoJSON
    .data(geojson_pays.features)
    .enter()
    .append("path")
    .attr("d", path)
    // Sémiologie (par défaut) des objets
    .style("fill", "rgba(232, 232, 232,0.8)")
    .style("stroke-width", .5)
    .style("stroke", "#fff");

const rail = svg.append("g");
rail.selectAll("path")
    // La variable geojson est créée dans le fichier JS qui contient le GeoJSON
    .data(geojson_rail.features)
    .enter()
    .append("path")
    .attr("d", path)
    .attr("stroke-opacity",0)
    .attr("fill-opacity",0)
    .on("click", clicked);

// VILLES ET CAPITALES

const villes = svg.append("g");
villes.selectAll("path")
    // La variable geojson est créée dans le fichier JS qui contient le GeoJSON
    .data(geojson_villes.features)
    .enter()
    .append("path")
    .attr("d", path)
//  Sémiologie (par défaut) des objets
    .style("fill", "black")
    .style("stroke", "white")
    .style("stroke-width", 0.5)
    .attr("stroke-opacity",0)
    .attr("fill-opacity",0);


const capitales = svg.append("g");
capitales.selectAll("path")
    // La variable geojson est créée dans le fichier JS qui contient le GeoJSON
    .data(geojson_capitales.features)
    .enter()
    .append("path")
    .attr("d", path)
    // Sémiologie (par défaut) des objets
    .style("fill", "#180093")
    ;


/***************************************************************************/
/**************************** PREVOIR UNE ACTION AU CLIC SUR UN BOUTON *****/
/***************************************************************************/

//BOUTONS
$("#action1").click(function(){
    affiche_rail(1980, projection.scale);
    document.getElementById("legende").innerHTML = '<img src ="Untitled-1.png"/>';
});

$("#action2").click(function(){
    affiche_rail(1990, projection.scale);
    document.getElementById("legende").innerHTML = '<img src ="Untitled-2.png"/>';
});

$("#action3").click(function(){
    affiche_rail(2000, projection.scale);
    document.getElementById("legende").innerHTML = '<img src ="Untitled-3.png"/>';
});

$("#action4").click(function(){
    affiche_rail(2010, projection.scale);
    document.getElementById("legende").innerHTML = '<img src ="Untitled-4.png"/>';
});

$("#action5").click(function(){
    affiche_rail(2020, projection.scale);
    document.getElementById("legende").innerHTML = '<img src ="Untitled-5.png"/>';
});

$("#dezoom").click(function(){

    var bounds = path.bounds(),
        dx = bounds[1][0] - bounds[0][0],
        dy = bounds[1][1] - bounds[0][1],
        x = (bounds[0][0] + bounds[1][0]) / 2,
        y = (bounds[0][1] + bounds[1][1]) / 2,
        scale = 1 / Math.max(dx / width, dy / height),
        translate = [width / 2 - scale * x, height / 2 - scale * y];

    pays.selectAll("path").transition().duration(750).style("stroke-width", "1.5px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    villes.selectAll("path").transition().duration(750).style("stroke-width", 0.5).attr("d", path.pointRadius(4.5)).attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    capitales.selectAll("path").transition().duration(750).style("stroke-width", 3).attr("d", path.pointRadius(4.5)).attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    rail.selectAll("path").transition().duration(750).style("stroke-width", "2px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
});



var tooltip = d3.select("body").append("div") 
        .attr("class", "tooltip")       
        .style("opacity", 0);

var tooltip2 = d3.select("body").append("div") 
        .attr("class", "tooltip")       
        .style("opacity", 0);




// FONCTION D'AFFICHAGE
function affiche_rail(date, scale){ // Affiche les lignes en fonction de la date de construction 

    rail.selectAll("path")
        .attr("stroke", function(d,i){ // Couleur de contour des lignes concernées
            if (geojson_rail.features[i].properties.Date_Const < date){
                return "grey"
            }
            else if (geojson_rail.features[i].properties.Date_Const == date) {
                return "red"
            }
        })
        .attr("stroke-opacity", function(d,i){ // Opacité des contours des lignes
            if (geojson_rail.features[i].properties.Date_Const <= date){
                return "1"
            }
            else if (geojson_rail.features[i].properties.Date_Const > date) {
                return "0"
            }
        })
        .attr("fill-opacity", "0") // 0 sinon c'est moche
        // Sémiologie (par défaut) des objets
        .style("stroke-width", 2)
        .style("stroke-dasharray", function(d,i){
            if (geojson_rail.features[i].properties.Date_Const == 2020){
                return 2
            }
        });

    villes.selectAll("path")
        .attr("stroke-opacity", function(d,i){ // Opacité des contours des points
            if (geojson_villes.features[i].properties.date <= date){
                return "1"
            }
            else if (geojson_villes.features[i].properties.date > date) {
                return "0"
            }
        })
        .attr("fill-opacity", function(d,i){ // Opacité des contours des points
            if (geojson_villes.features[i].properties.date <= date){
                return "1"
            }
            else if (geojson_villes.features[i].properties.date > date) {
                return "0"
            }
        })
        // Sémiologie (par défaut) des objets
        .style("fill", "black")
        .style("stroke", "white")
        .style("stroke-width", 0.5);

    // infos sur les villes au survol
    villes.selectAll("path").filter(function(d) {
        return d.properties.date <= date;
    }).on("mouseover", function(d) {
        d3.select(this)
            .style("fill", "blue")
        .style("stroke", "black")
        .style("stroke-width", 1.5/scale)
        .style("cursor", "pointer");
        tooltip.transition()    
                .duration(200)    
                .style("opacity", .9);    
                tooltip.html(d.properties.nom)  
                .style("left", (d3.event.pageX) + "px")   
                .style("top", (d3.event.pageY - 28) + "px");
    }).on("mouseout", function(d) {
        d3.select(this)
            .style("fill", "black")
        .style("stroke", "white")
        .style("stroke-width", 2/scale)
        tooltip.transition()    
                .duration(500)    
                .style("opacity", 0);
    }).on("click",function(d){
        // tooltip2.transition()    
                // .duration(200)    
                // .style("opacity", .9);    
        // tooltip2.html(d.properties.date)  
                // .style("left", (d3.event.pageX) + "px")   
                // .style("top", (d3.event.pageY - 28) + "px");
        tooltip.html(d.properties.nom + "<br />Date : " + d.properties.date);
    });

    //infos sur les capitales au survol
    capitales.selectAll("path").filter(function(d) {
        return d.properties.nom != "NULL";
    }).on("mouseover", function(d) {
        d3.select(this)
            .style("fill", "blue")
        .style("stroke", "black")
        .style("stroke-width", 1.5/scale)
        .style("cursor", "pointer");
        tooltip.transition()    
                .duration(200)    
                .style("opacity", .9);    
                tooltip.html(d.properties.nom)  
                .style("left", (d3.event.pageX) + "px")   
                .style("top", (d3.event.pageY - 28) + "px");
    }).on("mouseout", function(d) {
        d3.select(this)
            .style("fill", "#180093")
            .style("stroke", "rgba(0,0,0,0)")
        tooltip.transition()    
                .duration(500)    
                .style("opacity", 0);
    });

    // Changer le style des lignes au survol
    rail.selectAll("path").filter(function(d) {
        return d.properties.Date_Const <= date;
    }).on("mouseover", function(d) {
        d3.select(this)
            .style("cursor", "pointer")
            .style("fill-opacity", 0)
            .style("stroke", "blue")
            .style("stroke-width", 7/scale)
        tooltip.transition()    
            .duration(200)
            .style("opacity", .9); 
            tooltip.html("Ligne " + d.properties.LGV + ". Ouverte en " + d.properties.ouverture + ".")  
            .style("left", (d3.event.pageX) + "px")   
            .style("top", (d3.event.pageY - 28) + "px");
    }).on("mouseout", function(d) {
        d3.select(this)
            .style("fill-opacity", 0)
            .style("stroke", function(d,i){ 
                if (geojson_rail.features[i].properties.Date_Const < date){
                    return "grey"
                }
                else if (geojson_rail.features[i].properties.Date_Const == date) {
                    return "red"
                }
            })
            .style("stroke-width", 2/scale)
        tooltip.transition()    
            .duration(500)    
            .style("opacity", 0);
    });
}

function clicked(d) {

    var bounds = path.bounds(d),
        dx = bounds[1][0] - bounds[0][0],
        dy = bounds[1][1] - bounds[0][1],
        x = (bounds[0][0] + bounds[1][0]) / 2,
        y = (bounds[0][1] + bounds[1][1]) / 2,
        scale = .5 / Math.max(dx / width, dy / height),
        translate = [width / 2 - scale * x, height / 2 - scale * y];

    pays.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + "px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    pays2.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + "px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    villes.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale + "px").attr("d", path.pointRadius(1.5)).attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    capitales.selectAll("path").transition().duration(750).attr("d", path.pointRadius(1.5)).attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    rail.selectAll("path").transition().duration(750).style("stroke-width", 1.5 / scale +"%").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}

   // zoom
   var zoom = d3.zoom()
     .on("end",zoomed)

   rail.call(zoom);

   function zoomed() {
     rail.attr("transform",d3.event.transform);
     scale();
   }
//*/
  // Start Scale ---------------------------------------------------------
  function scale() {
    // baseWidth refers to ideal scale width on the screen it also is the width of the initial measurement point
    var baseWidth = width / 4;

    // Points relative to parent container:
    var xy1 = [width/2 - baseWidth/2, height/2];
    var xy2 = [width/2 + baseWidth/2, height/2];
    // Zoom transform:
    var transform = d3.zoomTransform(rail.node());
    // Points relative to zoom:
    xy1 = transform.invert(xy1);
    xy2 = transform.invert(xy2);

    // With a few changes below:
    var p1 = projection.invert(xy1);
    var p2 = projection.invert(xy2);
    var distance = getDistance(p1,p2);
    var unit = "m"; 
    var multiply = 1; 
    var bestFit = 1;
    var increment = 0.1; // This could be scaled to map width maybe width/10000;
    var scaleDistance = 0;
    var scaleWidth = 0;

    if ( distance > 1000 ) { 
        unit = "km"; multiply = 0.001;          
    }
    // Adjust distance to a round(er) number
    var i = 0;
    while (i < 400) {
        var temp = getDistance( projection.invert([xy1[0] + (increment * i/transform.k), xy1[1]]),  projection.invert([ xy2[0] - (increment * i/transform.k), xy2[1] ]));
        var ratio = temp / temp.toPrecision(1);

        // If the second distance is moving away from a cleaner number, reverse direction.
        if (i == 1) {
            if (Math.abs(1 - ratio) > bestFit) { increment = - increment; }
        }
        // If we are moving away from a best fit after that, break
        else if (i > 2) {
            if (Math.abs(1 - ratio) > bestFit) { break }
        }               
        // See if the current distance is the cleanest number
        if (Math.abs(1-ratio) < bestFit) {
            bestFit = Math.abs(1 - ratio); 
            scaleDistance = temp; 
            scaleWidth = (baseWidth) - (2 * increment * i);
        }
        i++;
    }

    // Now to build the scale           
    var bars = [];
    var smallBars = 10; 
    var bigBars = 4;
    var odd = true;
    var label = false;

    // Populate an array to represent the bars on the scale
    for (i = 0; i < smallBars; i++) {
        if (smallBars - 1 > i ) { label = false; } else { label = true; }
        bars.push( {width: 1 / (smallBars * (bigBars + 1)), offset: i / (smallBars * (bigBars + 1)), label: label, odd: odd } );
        odd = !odd;
        }
    for (i = 0; i < bigBars; i++) {
        bars.push( {width: 1 / (bigBars + 1), offset: (i + 1) / (bigBars + 1), label: true, odd: odd } );
        odd = !odd;
        }

    // Append the scale
    var scaleBar = rail.selectAll(".scaleBar")
        .data(bars);
          // enter bars with no width
          scaleBar
                .enter()
                .append("rect")
                .attr("x", 20)
                .attr("y", height - 40)
                .attr("height",20)
                .attr("width",0)
                .attr("class","scaleBar")
                .merge(scaleBar) // merge so that rect are updates if they are in the enter selection or the update selection.
                .transition()
        .attr("x", function(d) { return d.offset * scaleWidth + 20 })
        //.attr("y", height - 30)
        .attr("width", function(d) { return d.width * scaleWidth})
        //.attr("height", 10)
        .attr("fill", function (d) { if (d.odd) { return "#eee"; } else { return "#222"; } })
                .duration(1000);
        rail.selectAll(".scaleText").remove();
    rail.selectAll(".scaleText") 
        .data(bars).enter()
        .filter( function (d) { return d.label == true })
        .append("text")
        .attr("class","scaleText")
        .attr("x",0)
        .attr("y",0)
        .style("text-anchor","start")
        .text(function(d) { return d3.format(",")(((d.offset + d.width) * scaleDistance).toPrecision(2) * multiply); })
        .attr("transform", function(d) { return "translate("+ ((d.offset + d.width) * scaleWidth + 20 )+","+ (height - 45) +") rotate(-45)" })
                .style("opacity",0)
                .transition()
                .style("opacity",1)
                .duration(1000);
    rail.append("text")
        .attr("x", scaleWidth/2 + 20)
        .attr("y", height - 5)
        .text( function() { if(unit == "km") { return "kilometers"; } else { return "metres";}  })
        .style("text-anchor","middle")            
                .attr("class","scaleText")
                .style("opacity",0)
                .transition()
                .style("opacity",1)
                .duration(1000);
   }
    // End Scale -----------------------------------------
   scale();


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/* Latitude/longitude spherical geodesy tools                         (c) Chris Veness 2002-2016  */
/*                                                                                   MIT Licence  */
/* www.movable-type.co.uk/scripts/latlong.html                                                    */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html                       */
function getDistance(p1,p2) { 

    var lat1 = p1[1];
    var lat2 = p2[1];
    var lon1 = p1[0];
    var lon2 = p2[0];

    var R = 6371e3; // metres
    var φ1 = lat1* Math.PI / 180;
    var φ2 = lat2* Math.PI / 180;
    var Δφ = (lat2-lat1)* Math.PI / 180;
    var Δλ = (lon2-lon1)* Math.PI / 180;
    var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
            Math.cos(φ1) * Math.cos(φ2) *
            Math.sin(Δλ/2) * Math.sin(Δλ/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var distance = R * c;

return distance;

}
fefe78
  • 31
  • 3