Search code examples
javascriptd3.jsgeojsonesri

D3.js - Drawing points on map fails due to wrong projection


I'm currently working on a project which draws a map based on geo data from a JSON file using D3.js.

Now I have another JSON file with locations in it and I want to draw those locations as points on my map.

I'm doing this as follows:

svg.selectAll('circle')
.data(redCrossLocations.features)
.enter()
.append('circle')
.attr('cx', function(d) {
  var x = d.geometry.x
  var y = d.geometry.y
  return projection([x.toString(), y.toString()])[0]
})
.attr('cy', function(d) {
  var x = d.geometry.x
  var y = d.geometry.y
  return projection([x.toString(), y.toString()])[1]
})
.attr('r', 10)
.attr('text',function(d) {
  return d.attributes.DienstSt
})
.style('fill', 'yellow')

But when I have a look at the circles with Firebug those circles have very high cx and cy values which do not fit the map coordinates at all. As a result, the points are not positioned and not visible on the map.

This is how it looks like when investigating the html with Firebug:

<svg id="chart-330a29bc-10a6-422f-b7a4-b79213e63a0f" viewBox="0 0 1700 850">
<g transform="">
<svg width="425" height="425">
<rect width="850" height="850" style="stroke: black; fill: none;">
<path d="M406.7479670817884,199.90340867304621L407.06008919224314,199.57933852526912L407.7100188308723,198.8568991193315L408.1015443102342,198.34893921523326L408.14613950961393,198.29087863446694L408.68509042962614,197.6062181990019L408.79620200385034,197.4452283080136L409.08485295673836,197.0327872268681L409.31646456821454,196.6144688478962L409.62291817269215,195.96797874850017L409.78145919928556,195.49428734522007L409.912011977608,194.9738064907906L409.986809685407,194.39973754590756L410.0058080374765,193.7122278...00837217,205.1538807821671L401.9104729365481,204.65327053784495L402.62461611946765,203.79979531967183L403.17787116003547,203.1799602826395L403.20639083074093,203.1993550356201L403.308689649577,203.08799997778988L403.42697542668066,202.95942665539405L403.5903878256004,202.78322302641755L405.8101836239257,200.80217267343687L405.8688615178462,200.74629337105216L405.89149125655877,200.7271611214237L405.95836191302715,200.66712537209605L406.1133158381808,200.52594198620864L406.3833670058257,200.28190447086672Z" style="fill: red; stroke-width: 1; stroke: black;">
<circle cx="189993884.995462" cy="-29942.633406634715" r="10" text="Salzburg Stadt" style="fill: yellow;">
<circle cx="186702567.5263016" cy="6214.613271863014" r="10" text="Lamprechtshausen" style="fill: yellow;">
<circle cx="191422937.49531066" cy="NaN" r="10" text="Mattsee" style="fill: yellow;">
<circle cx="196518708.70128453" cy="NaN" r="10" text="Straßwalchen" style="fill: yellow;">

In hope that it helps finding a solution, here is the map generating code:

const svg = d3.select(`#${chartId}`)
    svg
    .append("svg")
    .attr("width", width / 2).attr("height", height / 2)

  var center = d3.geo.centroid(salzburg)
  var scale  = 1;
  var offset = [width / 2, height / 2];
  var projection =
    d3.geo.mercator()
      .scale(scale)
      .center(center)
      .translate(offset);

  // create the path
  var path = d3.geo.path().projection(projection);

  // using the path determine the bounds of the current map and use
  // these to determine better values for the scale and translation
  var bounds  = path.bounds(salzburg);
  var hscale  = scale * width  / (bounds[1][0] - bounds[0][0]);
  var vscale  = scale * height / (bounds[1][1] - bounds[0][1]);
  var scale   = (hscale < vscale) ? hscale : vscale;
  var offset  = [width - (bounds[0][0] + bounds[1][0]) / 2.1,
    height - (bounds[0][1] + bounds[1][1]) / 2.4];

  projection = d3.geo.mercator().center(center)
    .scale(scale).translate(offset);
  path = path.projection(projection);

  svg.append("rect").attr('width', width).attr('height', height)
    .style('stroke', 'black').style('fill', 'none');

  svg.selectAll("path").data(salzburg.features).enter().append("path")
    .attr("d", path)
    .style("fill", "red")
    .style("stroke-width", "1")
    .style("stroke", "black")

The redCrossLocations look like this:

"features": [{
    "attributes": {
      "OBJECTID": 1,
      "DienstSt": "Salzburg Stadt",
      "Art": "Bezirksstelle",
      "Ort": "Salzburg",
      "Strasse": "Sterneckstraße",
      "Hnr": "32",
      "Bezirk": "Stadt Salzburg und Flachgau",
      "Typ": null
    },
    "geometry": {
      "x": 429395.48199999984,
      "y": 296716.59500000067
    }
  }, {
    "attributes": {
      "OBJECTID": 2,
      "DienstSt": "Lamprechtshausen",
      "Art": "Ortsstelle",
      "Ort": "Lamprechtshausen",
      "Strasse": "Schulstraße",
      "Hnr": "1",
      "Bezirk": "Stadt Salzburg und Flachgau",
      "Typ": null
    },
    "geometry": {
      "x": 421963.38900000043,
      "y": 317197.9179999996
    }
  },
...

Does anyone know, why I get this absolutely wrong point coordinates?


Solution

  • First of all, when searching for details taken from your redCrossLocations it appears to be downloaded from this source. Inspecting the full file reveals two issues with your geo input data:

    1. Your JSON seems to be Esri's JSON format, wheras D3 expects GeoJSON as its input. Because your JSON is not compatible with GeoJSON you will have to convert it.

    2. The file has a definition of "spatialReference":{"wkid":31258,"latestWkid":31258} which refers to the projected coordinate system MGI_Austria_GK_M31 (EPSG::31258) which is also not the right one to use when it comes to D3. To make it usable for D3 you need to transform it to the geographic coodinate system WGS 84 (EPSG::4326).

    Fortunately, you can use the ogr2ogr tool to do both conversions in a single run:

    ogr2ogr -f "GeoJSON" -t_srs "EPSG::4326" "RotesKreuz_Dienststellen.json"
    

    Additionaly, there is an option -s_srs available, to explicitely specify the source coordinate system (-s_srs "EPSG:31258") which is not needed in your case, because this will be derived from the input file.

    There is also a ogr2ogr web client available to do the conversion and transformation online. Using that form you may upload your file and specify the "Target SRS" to be EPSG:4326 which will transform the coordinate system and convert it to GeoJSON.