Search code examples
angulard3.jsgeojsontopojson

Property 'features' does not exist on type 'Feature<Point, { [name: string]: any; }>'


I'm new to Angular and I'm trying to build a map of germany with Angular/d3. The map data is stored in a Topojson file plz_map_ger.json:

{
"type": "Topology",
"arcs": [...],
"transform": {...},
"objects": {
      "plz5stellig": {...}
      }
 }

This is my code to draw the map:

import * as d3 from "d3";
import * as t from 'topojson';

...

d3.json("../../assets/plz_map_ger.json")
  .then(function(top:any) {
    g.selectAll('path')
      .data(t.feature(top, top.objects.plz5stellig).features)
      .enter()
      .append('path')
      .attr('d', path)
      .attr("class","kreisgrenzen")
      .on("click", function() {
        d3.select(this).attr("class","selected-kreis");
      });

However I get the following compile error:

error TS2339: Property 'features' does not exist on type 'Feature<Point, { [name: string]: any; }>'.

What do I need to do to fix this?

Edit: When I hover over the error in VS Code i get the following message:

Property 'features' does not exist on type 'Feature<Point, { [name: string]: any; }>'.ts(2339)

I use the following Topojson file (this file is a bit simplified, however the structure remains the same) created with mapshaper.org: gist.github.com/.../plz_map_ger.json


Solution

  • According to the types the function feature() returns either a Feature or a FeatureCollection. Only the FeatureCollection will have the .features attribute that you are looking for.

    Checking the code of the TopoJSON Package (line 4 - 8) we can see that the a FeatureCollection is only returned, if the topology has GeometryCollection as its type.

    export default function(topology, o) {
      return o.type === "GeometryCollection"
      ? {type: "FeatureCollection", features: o.geometries.map(function(o) { return feature(topology, o); })}
          : feature(topology, o);
    }
    

    You are loading the topology asynchronously, so it is impossible for the compiler to know, whether its .type is GeometryCollection or not.

    In order to solve the issue you will need to have the GeoJSON types installed (npm i @types/geojson).

    You can then either set the type of a temporary variable

        ...
        d3.json("../../assets/plz_map_ger.json")
          .then(function(top:any) {
    
              // This row sets the temporary variable
              let mapFeatures: FeatureCollection = t.feature(top, top.objects.plz5stellig)
    
               g.selectAll('path')
    
              // We use the temporary variable here
              .data(mapFeatures.features)
              .enter()
              .append('path')
              .attr('d', path)
              .attr("class","kreisgrenzen")
              .on("click", function() {
                d3.select(this).attr("class","selected-kreis");
              });
          });
    

    Or you could explicitly cast the collection to a Feature Collection (thanks to @altocumulus)

      ...
        d3.json("../../assets/plz_map_ger.json")
          .then(function(top:any) {
               g.selectAll('path')
              // explicit cast
              .data((t.feature(top, top.objects.plz5stellig) as GeometryCollection).features)
              .enter()
              .append('path')
              .attr('d', path)
              .attr("class","kreisgrenzen")
              .on("click", function() {
                d3.select(this).attr("class","selected-kreis");
              });
          });