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
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");
});
});