block by pnavarrc 10a308446c42d5b8b066

Equidistant lines and Location API

Full Screen

Equidistant lines and HTML5 Location API.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Equidistant Lines</title>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
</head>
<body>

  <style>

  body {
    margin: 0;
    cursor: pointer;
  }

  svg {
    margin: 5px;
    background-color: #A7DBD8;
  }

  .sphere {
    fill: none;
    stroke: #79A09E;
  }

  .land {
    fill: #E0E4CC;
    stroke: #ACAF9F;
    stroke-width: 1;
  }

  .graticule {
    fill: none;
    stroke: #79A09E;
    stroke-width: 1;
    stroke-dasharray: 1,1;
  }

  .equidistant {
    stroke: #490A3D;
    stroke-width: 1;
    fill: none;
  }

  .dist-km {
    fill: #490A3D;
    font-family: 'Helvetica Neue';
    font-weight: bold;
    font-size: 12px;
  }

  </style>

  <div id="map-container"></div>

  <script>

    // Set the dimensions of the map
    var width  = 960,
        height = 480;

    // Generate angles. Each degree is about 111 km
    var angles = d3.range(10, 180, 10);

    // Create a selection for the container div and append the svg element
    var div = d3.select('#map-container'),
        svg = div.append('svg');

    // Set the size of the SVG element
    svg.attr('width', width).attr('height', height);

    // Create and configure a geographic projection
    var projection = d3.geo.equirectangular()
      .translate([width / 2, height / 2])
      .scale(height / Math.PI);

    // Create and configure a path generator
    var pathGenerator = d3.geo.path()
      .projection(projection);

    // Create and configure the graticule generator (one line every 20 degrees)
    var graticule = d3.geo.graticule()
      .step([10, 10]);

    // Retrieve the geographic data asynchronously
    d3.json('countries.geojson', function(err, data) {

      // Throw errors on getting or parsing the file
      if (err) { throw err; }

      // Shpere
      var sphere = svg.selectAll('path.sphere').data([{type: 'Sphere'}]);

      sphere.enter().append('path').classed('sphere', true);
      sphere.attr('d', pathGenerator);
      sphere.exit().remove();

      // Graticule lines (behind the land)
      var lines = svg.selectAll('path.graticule').data([graticule()]);

      lines.enter().append('path').classed('graticule', true);
      lines.attr('d', pathGenerator);
      lines.exit().remove();

      // Land
      var land = svg.selectAll('path.land').data([data]);

      land.enter().append('path').classed('land', true);
      land.attr('d', pathGenerator);
      land.exit().remove();
    });

    var origin;

    svg.on('click', function(ev) {

      // Get the (x, y) position of the mouse (relative to the SVG element)
      var pos = d3.mouse(svg.node()),
          px = pos[0],
          py = pos[1];

      // Compute the corresponding geographic coordinates using the inverse projection
      var coords = projection.invert([px, py]);
      origin = coords;

      // Circles
      var circleGen = d3.geo.circle()
        .origin(coords)
        .angle(function(d) { return d; })
        .precision(1);

      var circlesData = [];
      angles.forEach(function(angle) {
        circleGen.angle(angle);
        circlesData.push(circleGen());
      });
      circlesData.reverse();

      var equidistantPaths = svg.selectAll('path.equidistant').data(circlesData);

      equidistantPaths.enter().append('path')
        .classed('equidistant', true);

      equidistantPaths
        .attr('d', pathGenerator);

      equidistantPaths.exit().remove();

    });

    svg.on('mousemove', function(ev) {

      if (!origin) { return ; }

      // Get the (x, y) position of the mouse (relative to the SVG element)
      var pos = d3.mouse(svg.node()),
          px = pos[0],
          py = pos[1];

      // Compute the corresponding geographic coordinates using the inverse projection
      var coords = projection.invert([px, py]);

      // Calculate the angular and km distance between origin and the mouse position
      var distRad = d3.geo.distance(origin, coords),
          distKm = 40e3 * distRad / (2 * Math.PI);

      // Update the labels
      var label = svg.selectAll('text.dist-km').data([distKm]);

      label.enter().append('text').classed('dist-km', true);

      label
        .attr('x', px + 15)
        .attr('y', py)
        .text(d3.round(distKm, 0) + ' km');

      label.exit().remove();
    });

  </script>

</body>
</html>

Makefile

# Download and Transform the 1:50m Country Shapefiles from Natural Earth
# http://www.naturalearthdata.com/downloads/110m-physical-vectors/

URL = http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/110m/physical/ne_110m_land.zip

# Download the zip file from the Natural Earth server
ne_110m_land.zip:
	curl -LO $(URL)

# Unzip the shapefiles
ne_110m_land.shp: ne_110m_land.zip
	unzip ne_110m_land.zip
	touch ne_110m_land.shp

# Convert the shapefiles to GeoJSON
land.geojson: ne_110m_land.shp
	ogr2ogr -f GeoJSON land.geojson ne_110m_land.shp

# Convert the GeoJSON file to TopoJSON
land.topojson: countries.json
	topojson -p -o land.topojson land.geojson

# Remove source and temporary files
clean:
	rm ne_110m_land*