Skip to content

Instantly share code, notes, and snippets.

@milkbread
Last active April 5, 2018 21:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save milkbread/5885443 to your computer and use it in GitHub Desktop.
Save milkbread/5885443 to your computer and use it in GitHub Desktop.
HTML: D3 Geodata Basics - example for the tutorial on digital-geography
<!DOCTYPE html>
<html>
<head>
<title>Testmap</title>
<meta charset="utf-8" />
<script src="http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="roads.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<style>
@import url(http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.css);
#overlay{
fill:None;
stroke:#ff00ff;
stroke-width:4px;
}
</style>
</head>
<body>
<div id="map" style="width: 960px; height: 300px"></div>
<script>
var map = L.map('map').setView([52.52,13.384], 13);
var toolserver = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png');
var stamen = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: 'Add some attributes here!'}).addTo(map);
var baseLayers = {"stamen": stamen, "toolserver-mapnik":toolserver};
var geojson = L.geoJson(roads, {
onEachFeature: onEachFeature
}).addTo(map)
var overlays = {
"geoJson": geojson
};
function onEachFeature(feature, layer){
if (feature.properties) {
layer.bindPopup("<b>" + feature.properties.street + "</b> is " + feature.properties.length + "km long.");
}
}
var svgContainer= d3.select(map.getPanes().overlayPane).append("svg");
var group= svgContainer.append("g").attr("class", "leaflet-zoom-hide");
var path = d3.geo.path().projection(project);
d3.json("roads2.js", function(collection) {
console.log([collection])
console.log(roads)
var roadsTest = [collection];
var geojson_d3 = L.geoJson(roadsTest, {
onEachFeature: onEachFeature
})
overlays["geojson_d3"] = geojson_d3;
d3.json("roads2_topo.json", function(error, topology) {
//console.log(topology)
var collection2 = topojson.feature(topology, topology.objects.roads2);
var roadsTopoJSON = [collection2];
console.log(roadsTopoJSON)
var geojson_tj = L.geoJson(roadsTopoJSON, {
onEachFeature: onEachFeature
});
overlays["geojson_topojson"] = geojson_tj;
//console.log(collection.features[0].geometry.coordinates)
var control = L.control.layers(baseLayers, overlays).addTo(map);
//***described code can be found in the function 'setFeature()'...I need that as function to the 'hide/show overlay'
var feature;
setFeature();
//****
var bounds = d3.geo.bounds(collection2);
reset();
map.on("viewreset", reset);
map.on("drag", reset);
feature.on("mousedown",function(d){
var coordinates = d3.mouse(this);
//console.log(d,coordinates,map.layerPointToLatLng(coordinates))
L.popup()
.setLatLng(map.layerPointToLatLng(coordinates))
.setContent("<b>" + d.properties.street + "</b> is " + d.properties.length + "km long.")
.openOn(map);
});
var transition_destination = -1;
feature.on("mousemove",function(d){
d3.select(this).transition().duration(2500).ease('bounce')
.style("stroke","#0f0")
.attr("transform", "translate(0,"+transition_destination*50+")");
transition_destination=transition_destination*(-1);
})
function reset() {
bounds = [[map.getBounds()._southWest.lng, map.getBounds()._southWest.lat],[map.getBounds()._northEast.lng, map.getBounds()._northEast.lat]]
var bottomLeft = project(bounds[0]),
topRight = project(bounds[1]);
svgContainer.attr("width", topRight[0] - bottomLeft[0])
.attr("height", bottomLeft[1] - topRight[1])
.style("margin-left", bottomLeft[0] + "px")
.style("margin-top", topRight[1] + "px");
group.attr("transform", "translate(" + -bottomLeft[0] + "," + -topRight[1] + ")");
feature.attr("d", path);
}
//******Additional: hide/show overlay ******
var content = "hide overlay", color='#070';
svgContainer.append("text").text(content)
.attr("x", 50).attr("y", 50)
.style("font-size","30px").style("stroke",color)
.on("mouseover",function(d){
if(content=='hide overlay'){
content='show overlay';color='#f70';
group.selectAll('path').remove();
}
else {
content='hide overlay';color='#070';
setFeature();
reset();
}
d3.select(this).text(content).style("stroke",color)
});
//this is just a function from the existing code...as I need it to restore the removed paths
function setFeature(){
feature = group.selectAll("path")
.data(collection2.features)
.enter()
.append("path")
.attr("id","overlay");
}
//***************************
})
})
function project(point) {
var latlng = new L.LatLng(point[1], point[0]);
var layerPoint = map.latLngToLayerPoint(latlng);
return [layerPoint.x, layerPoint.y];
}
</script>
</body>
</html>
var roads = [{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 0, "properties": { "id": 0, "street": "number 1", "length": 1 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.424244, 52.539179 ], [ 13.421705701409467, 52.539576815907466 ], [ 13.42183003062355, 52.539687723163105 ], [ 13.422166851598536, 52.540291556826226 ], [ 13.42236706832899, 52.540650784116721 ], [ 13.4226, 52.54064 ] ] } }
,
{ "type": "Feature", "id": 9, "properties": { "id": 10, "street": "number 2", "length": 10 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.40031, 52.51801 ], [ 13.3996705721827, 52.517765112439875 ], [ 13.399670572182709, 52.517765112439861 ], [ 13.399176455188746, 52.517654943693323 ], [ 13.397880188568315, 52.517573576643279 ], [ 13.397615860238052, 52.518724568870446 ], [ 13.397225437613784, 52.519266719174603 ], [ 13.397065264229472, 52.519479922561445 ], [ 13.396694863278245, 52.519650484525975 ], [ 13.396414559855696, 52.519668758982903 ], [ 13.396134256433148, 52.519760131153504 ], [ 13.395183226963786, 52.520460644814797 ], [ 13.394102056619669, 52.5212646989009 ], [ 13.393401298063297, 52.521776362020844 ], [ 13.393501406428491, 52.521812909158513 ], [ 13.39383176403364, 52.521989553228643 ], [ 13.393921861562315, 52.522062647118865 ], [ 13.394974927632834, 52.521762139072038 ] ] } }
]
}
];
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 0, "properties": { "id": 0, "street": "number 1", "length": 1 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.424244, 52.539179 ], [ 13.421705701409467, 52.539576815907466 ], [ 13.42183003062355, 52.539687723163105 ], [ 13.422166851598536, 52.540291556826226 ], [ 13.42236706832899, 52.540650784116721 ], [ 13.4226, 52.54064 ] ] } }
,
{ "type": "Feature", "id": 9, "properties": { "id": 10, "street": "number 2", "length": 10 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.40031, 52.51801 ], [ 13.3996705721827, 52.517765112439875 ], [ 13.399670572182709, 52.517765112439861 ], [ 13.399176455188746, 52.517654943693323 ], [ 13.397880188568315, 52.517573576643279 ], [ 13.397615860238052, 52.518724568870446 ], [ 13.397225437613784, 52.519266719174603 ], [ 13.397065264229472, 52.519479922561445 ], [ 13.396694863278245, 52.519650484525975 ], [ 13.396414559855696, 52.519668758982903 ], [ 13.396134256433148, 52.519760131153504 ], [ 13.395183226963786, 52.520460644814797 ], [ 13.394102056619669, 52.5212646989009 ], [ 13.393401298063297, 52.521776362020844 ], [ 13.393501406428491, 52.521812909158513 ], [ 13.39383176403364, 52.521989553228643 ], [ 13.393921861562315, 52.522062647118865 ], [ 13.394974927632834, 52.521762139072038 ] ] } }
]
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Begin with a simple map ... already seen in D3 mapping basics posts
<!DOCTYPE html>
<html>
<head>
<title>Testmap</title>
<meta charset="utf-8" />
<script src="http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.js"></script>
<style>
@import url(http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.css);
</style>
</head>
<body>
<div id="map" style="width: 960px; height: 300px"></div>
<script>
var map = L.map('map').setView([52.52,13.384], 13);
var toolserver = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png');
var stamen = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: 'Add some attributes here!'}).addTo(map);
var baseLayers = {"stamen": stamen, "toolserver-mapnik":toolserver};
L.control.layers(baseLayers).addTo(map);</script>
</body>
</html>
Now we add some very simple data to the map, as we have already done it in the post (geoJSON & Leaflet)
- import the data file
<script src="roads.js"></script>
- add the data to the map
var geojson = L.geoJson(roads, {
onEachFeature: onEachFeature
}).addTo(map)
- define the 'onEachFeature' function
function onEachFeature(feature, layer){
if (feature.properties) {
layer.bindPopup("<b>" + feature.properties.street + "</b> is " + feature.properties.length + "km long.");
}
}
--> remember what this function does? (http://leafletjs.com/reference.html#geojson) The documentation sais, a function that is called while each element is created! Means only called one time, when map is initialized!
- finally, throw geojson into an 'object' like the 'baseLayers'
var overlays = {
"geoJson": geojson
};
- and add it to the layer control
L.control.layers(baseLayers, overlays).addTo(map);
Now, we have basic map with data overlay, can switch between layers and can un-/show the overlay! We can click it and get information on it visualised in a pop-up!
Let's try to simulate that with D3.
- 1st of all we need a 'container', where we can throw in our data. Leaflet offers an 'overlayPane' ... there we'll add our 'svgContainer' ...
var svgContainer = d3.select(map.getPanes().overlayPane).append("svg"),
...and a group to it ...
g = svg.append("g").attr("class", "leaflet-zoom-hide");
...you remember, because of the 'clean playground' (last post) ... Do you wonder about the class-attribute? This class is hidden, by Leaflet, when the map is zoomed. In result, our data overlay will get invisible whenever the user zooms in/out and is visible when this action is done!
Now ... we'll the same data as in the previous (geoJSON & Leaflet) tutorial --> roads.js
But ... we have to change this file! Can you remember ... we had to add some content to the roads.js - file ... meaning the 'featureCollection' has to be allocated as variable, to be accessible for Leaflet. You can either use the original data (when you made the previous tutorial) or you reset the file and store it as roads2.js:
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 0, "properties": { "id": 0, "street": "number 1", "length": 1 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.424244, 52.539179 ], [ 13.421705701409467, 52.539576815907466 ], [ 13.42183003062355, 52.539687723163105 ], [ 13.422166851598536, 52.540291556826226 ], [ 13.42236706832899, 52.540650784116721 ], [ 13.4226, 52.54064 ] ] } }
,
{ "type": "Feature", "id": 9, "properties": { "id": 10, "street": "number 2", "length": 10 }, "geometry": { "type": "LineString", "coordinates": [ [ 13.40031, 52.51801 ], [ 13.3996705721827, 52.517765112439875 ], [ 13.399670572182709, 52.517765112439861 ], [ 13.399176455188746, 52.517654943693323 ], [ 13.397880188568315, 52.517573576643279 ], [ 13.397615860238052, 52.518724568870446 ], [ 13.397225437613784, 52.519266719174603 ], [ 13.397065264229472, 52.519479922561445 ], [ 13.396694863278245, 52.519650484525975 ], [ 13.396414559855696, 52.519668758982903 ], [ 13.396134256433148, 52.519760131153504 ], [ 13.395183226963786, 52.520460644814797 ], [ 13.394102056619669, 52.5212646989009 ], [ 13.393401298063297, 52.521776362020844 ], [ 13.393501406428491, 52.521812909158513 ], [ 13.39383176403364, 52.521989553228643 ], [ 13.393921861562315, 52.522062647118865 ], [ 13.394974927632834, 52.521762139072038 ] ] } }
]
}
We have to do this, as D3 can handle geoJSON-files directly. And this is advantageous especially for huge dataset! I don't want to be forced to add some content to a file of, say 1000 elements ... that is not a cool workflow, isn't it?
So ... open the file and examine the loaded data ...
d3.json("roads2.js", function(collection) {
console.log(collection)
})
...you see a 'featureCollection'? ... everything was loaded!
***Notice! Everything that follows has to be included to d3.json({}) command!***
But what can I do with this data? ... hmmm ... let's compare it to the roads.js data...
console.log(roads)
...aha...roads.js --> offers an array containing one 'featureCollection'-object and roads2.js --> offers one 'featureCollection'-object ... ok .. that means
console.log([collection])
console.log(roads)
...now they are identic ... wherefore ... I could also add the roads2.js data as an overlay! (Remember to replace the 'L.control.layers()'!)
d3.json("roads2.js", function(collection) {
console.log([collection])
console.log(roads)
var roadsTest = [collection];
var geojson2 = L.geoJson(roadsTest, {
onEachFeature: onEachFeature
})
overlays["geoJson2"] = geojson2;
L.control.layers(baseLayers, overlays).addTo(map);
})
Cool .. now we're able to open a geoJSON file without editing it previously! In result, we've got the data 2 times in the map and can select them in the layer control!
Let's climb up to the maserclass, by making this also with a TopoJSON file.
Maybe you need some background knowledge on that, but that would be too much for this tutorial! Have a look at:
- basic information (https://github.com/mbostock/topojson)
- installation on Ubuntu 12.04 (http://milkator.wordpress.com/2013/02/23/installing-topojson-on-ubuntu-12-04/)
- how I used it the first time (http://milkator.wordpress.com/2013/02/25/making-a-map-of-germany-with-topojson/)
- how the inventor (Mike Bostock) describes how to use it (http://bost.ocks.org/mike/map/)
Mike Bostock hosted a cool tutorial on using topjson () which I have already re-implemented (). I think this should be enough to check out how to make a TopoJSON-file
I'll go on by making a TopoJSON - version of our dataset 'roads2.js' (use the pure version!!!)
- topojson --id-property id -p -o roads2_topo.json roads2.js
(--id-property --> the property which is used as ID, -p --> transfer all properties (give it a value and it transfers only this one), -o --> output file)
Add the corresponding library 'topojson.js' to our example:
<script src="http://d3js.org/topojson.v1.min.js"></script>
Open the TopoJSON-file and examine it...
d3.json(jsonURL, function(error, topology) {
console.log(topology)
})
...looks different, doesn't it? But don't worry it's pretty simple to get it as well-known geoJSON...
var collection2 = topojson.feature(topology, topology.objects.roads2);
var roadsTopoJSON = [collection2];
...there you go! Now, we'll just make the same as above (again...remember to replace the 'L.control.layers')...
d3.json("roads2_topo.json", function(error, topology) {
//console.log(topology)
var collection2 = topojson.feature(topology, topology.objects.roads2);
var roadsTopoJSON = [collection2];
//console.log(roadsTopoJSON)
var geojson3 = L.geoJson(roadsTopoJSON, {
onEachFeature: onEachFeature
})
overlays["geoJson3"] = geojson3;
L.control.layers(baseLayers, overlays).addTo(map);
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment