Draw svg points from geoJSON query and image using d3 library

Miguel

I can draw the image map and i can retrieve the geometry path, however and despite the geoData link be correct it is not drawing any geometry in the map. Been seeing some examples from stack and other's places and the examples are similar to this ones.

Any idea why the data is not being read (i am assuming the problem lies there with the data being retrieve) ?

Thanks in advance

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<body>
    <div id="map" style="position:absolute; width:400px; height:600px; overflow:hidden; border: 1px solid red;">
        <img
            src="https://sig.cm-figfoz.pt/arcgis/rest/services/Internet/MunisigWeb_DadosContexto/MapServer/export?dpi=96&transparent=true&format=png8&layers=show:1,2,4,7,8,12,13&bbox=-58842.91417500004,43056.64792531277,-58667.48812500015,43317.59417468761&bboxSR=3763&imageSR=3763&size=400,595&f=image">
    </div>
    <svg overflow="hidden" width="400" height="600" id="map_gc"
        style="touch-action: none; will-change: transform; overflow: visible; position: absolute; transform: translate3d(0x, 0px, 0px);">
    </svg>
    <script>
        var width = 400;
        var height = 600;

        var chosenProjection = d3.geoMercator()
            .scale(0)
            .translate([0, 0])

        var path = d3.geoPath()
            .projection(chosenProjection);

        var OutputJSON = "https://sig.cm-figfoz.pt/arcgis/rest/services/Internet/MunisigWeb_DadosContexto/MapServer/12/query?where=NOMERUA+LIKE+%27%25Beco+da+F%C3%A9%25%27+AND+LUG11DESIG+LIKE+%27%25Franco%25%27&text=&objectIds=&time=&geometry=&geometryType=esriGeometryPolygon&inSR=&spatialRel=esriSpatialRelIntersects&relationParam=&outFields=&returnGeometry=true&returnTrueCurves=false&maxAllowableOffset=&geometryPrecision=&outSR=&returnIdsOnly=false&returnCountOnly=false&orderByFields=&groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&gdbVersion=&returnDistinctValues=false&resultOffset=&resultRecordCount=&queryByDistance=&returnExtentsOnly=false&datumTransformation=&parameterValues=&rangeValues=&f=pjson"

        d3.json(OutputJSON, function (error, data) {

            d3.select("svg").append("path")
                .attr("d", path(data));
        });
    </script>
</body>

Andrew Reid

As of d3v5, d3.json returns a promise. You'll need to use the format d3.json("url").then(function(data) {... instead of d3.json("url", function(data) { ...)

Also, your data is not actually geojson. ESRI has its own json format, which is very much not geojson. It appears ESRI has built more geojson support into its products lately, but there are also plenty of options to convert shapefiles to geojson (ESRI's json format is relatively rare as compared with other formats). See mapshaper for an online shapefile → geojson conversion tool. Invalid geojson does not generate an error with D3, it merely doesn't result in any path data being returned by d3.geoPath. So. to address this, you'll need to use a valid geojson data source.

Having fixed the above two points you'll load the data successfully and append a path to your SVG.

Except, that'll lead to your next question: where is my feature? The above two points might address your most pressing issues, but it likely leads to a new issue, which I'll touch on briefly:

Where's My Feature?

You don't have aligned coordiantes between the image and the "geojson". The image has a coordinate system measured in pixels: the top left pixel is at [0,0] and the bottom right at [400,595]. Your json's coordinates are decidedely outside of this range, eg [-58696.7258,43252.2659].

It appears you may have anticipated this by using a D3 projection. While this is a separate issue, you've set your scale value to 0. The scale value determines the width of the map: the default value is about 152, which means that a single radian (of longitude) should stretch across 152 pixels. A value of zero means that a single radian should stretch across no pixels at all, which means the feature is not visible no matter what.

Your "geojson" data is projected, as in it does not contain longitude latitude pairs on a three dimensional globe. Instead, it contains Cartesian points on a two dimensional surface. D3 geo projections take the former. So, if we want to use that type of projection, we could "unproject" the data so that it uses WGS84 coordinates. After this we need to create a projection that matches the image.

Alternatively, we might be able to apply a transform to the data so that the transformed data occupies the same coordinate space as the image.

Option 1: Unproject the data:

If

  • We know what coordinate system the geojson uses
  • We know the geographic bounds of the image
  • We know the projection type/coordinate system used to create the image

We can unproject the data and project it to match the image.

With unprojected data we can pass geojson with WGS84 coordinates to d3.geoPath - however, that is not the coordinate system of the image (as [400,595] is not a valid long/lat pair). We need to match the two coordinate systems.

We then emulate the projection that was used to create the image, by following a process such as this to match a d3-geoProjection to some arbitrary projection. (see also).

As the image's source's projection is very likely to not be in a coordinate system that uses pixels as its base unit, we'll need to get its bounding box, in degrees, to properly scale the projected geojson. Without this, we can't know what geographic point pixel [0,0] corresponds to.

Option 2: Transform the data

If:

  • Both image and geojson use the same projection
  • Have the same extent

Then we can use a transform to align the two. We can do this by using d3.geoIdentity() which includes a method named fitExtent([[left,top],[right,bottom]],geojsonObject).

As the image's coordinates are in pixels, we need to compress/stretch the geojson's coordinates to the same pixel extent as the image, fitExtent does just this (top/left/bottom/right values are in pixels). If the image is anchored at [0,0] then we'd use: d3.geoIdentity().fitExtent([[0,0],[400,595]],geojsonObject)

We may need to mirror the geojson over the y axis as geographic convention usually increases y values as one moves north, while SVG increases y values as one moves down the screen, we can use identity.reflectY() to achieve this.

If the extents are different - we could still create a transform function using d3.geoTransform, it just gets a bit more complicated.


Either way, only with additional information can we try and align the two different coordinate systems: pixels vs arbitrary projected coordinate system. Should you need, a new question should have this information to allow a definitive answer.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related