Skip to content

Instantly share code, notes, and snippets.

@milkbread
Last active December 19, 2015 12:49
Show Gist options
  • Save milkbread/5957651 to your computer and use it in GitHub Desktop.
Save milkbread/5957651 to your computer and use it in GitHub Desktop.
D3: TopoJSON: Animated demonstration on polygon simplification I

Animated Demonstration of Data-Driven Polygon Simplification with the help of TopoJSON

This demonstration was created in preparation for the EDC-Entwicklerforum at the Dresden University of Technologies in 2013.

It should basically demonstrate:

  • the 'beauty' of managing and sharing Code via GitHub-Gist & bl.ocks.org
  • in how far 'TopoJSON' is applicable and benificial for simplification of polygons

Especially bl.ocks.org enables the parallel view of...

  • JavaScript-, CSS- and HTML-Documents
  • descriptions on the code (or project) in a readme.md file
  • the resulting visualisation That is really benifitial for understanding foreign code!!!

What can you do?

Basically, you see a background map, made with Lealfet, and surely you have already recognized the 'animation selection menu'!

You've got 3 options:

  • 'Start the complete animation'
  • start each part animation ('...without topology', '...regarding topology', 'data-driven...', 'Result')
  • test the simplification of arcs manually ('Simplify manually')

What do you see?

'...without topology'

You can see the status-qou! Say we've got a GeoJSON-file containing the boundaries of the german states. The effect of applying a simple point reduction (simplification) on these polygons is obvious ... it destroys the topology of our data. Whereby we see a lot of 'holes'/'leaks' along the boundaries!

'...regarding topology'

TopoJSON helps us to avoid the destruction of topology, because each redundant line segment it stored only once ... called 'Arc'! You can see the effect in the animation of the TopoJSON-file with the same content... we just reduce the number of points (simplify) of each line segment ('Arc') and preserve the Start- & End-Point of each one! In result, we can - at least - keep the topology at these points and along the boundaries...but the form of the german states is still heavily distorted when we apply a very high 'tolerance' to the simplification algorithm.

'data-driven...'

We are able to avoid this, by adding more data to the TopoJSON-file. I added another TopoJSON-file to the demo, which contains the boundaries of german counties, additionally. As the counties share boundaries also with the states, do these consist now of way more line segements ('Arcs')!!! You can see that when the 'ids' are displayed! And this helps already...we just do the same as before, just simplify the 'Arcs' and preserve Start-& End-Points. The effect is great! The topology is basically preserved much better than before, even for the maximum tolerance, and also the form of the german states enables you to deduce the original form!

You see the TopoJSON-file, respectively the data within, defines how much you can simplfiy (& distort) the polygonal geometries. Following the typonomy of D3 ... I understand this as 'data-driven degree of simplification'!

'Result'

Finally, you can see the resulting Polygons, built from the simplified 'Arcs'!

  • No 'holes' or 'leaks' ... thanks to the uniqueness of 'Arcs'!
  • The form looks pretty similar to the original form ... thanks to the detailedness of the TopoJSON-file

Links & Sources

function animationSelektor(divContainer, data, width, height){
var numElements = 18;
var partSize = width / numElements;
this.partSize = partSize;
var headHeight = 15, offset = height-headHeight;
var superiorDownAction, lowerDownAction;
this.container = divContainer;
this.svgContainer = this.container.append('svg')
.attr("width", width)
.attr("height", height)
.style('border-radius','15 15 0 0')
.style('-moz-border-radius','15 15 0 0');
//BACKGROUND
this.backgr = this.svgContainer.append('rect')
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.style('fill','#c8ba09');
//PROGRESS INDICATOR
this.progress = this.svgContainer.append('rect')
.attr("id", 'progessor')
.attr("x", 0)
.attr("y", 0)
.attr("width", 0)
.attr("height", 14)
.style('opacity',0.6)
.style('fill','#a00');
var progress = this.progress
//HEADING - MAKE COMPLETE ANIMATION
this.svgContainer.append('text')
.attr("x", width/2)
.attr("y", 10)
.attr('id','selMain')
.text('Start the complete animimation');
this.svgContainer.append('rect')
.on('mouseover',function(d){d3.select(this).style('fill','#055')})
.on('mouseout',function(d){d3.select(this).style('fill','#000')})
.on('mousedown',supDownAction)
.datum('all')
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", 14)
.style('opacity',0.2)
.style('fill','#000');
//SINGLE - CLICKABLE RECTANGLES
this.group = this.svgContainer.append('g');
/*this.group.selectAll('text').data(data).enter().append('text')
.attr("x", function(d,i){return (partSize*i)+(partSize/2)})
.attr("y", 25+headHeight)
.style('font-size', 12)
.style('fill','#fff')
.style('text-anchor','middle')
.text(function(d){return d});*/
this.group.selectAll('rect').data(data).enter().append('rect')
//.on('mouseover',function(d){d3.select(this).style('fill','#fff')})
//.on('mouseout',function(d){d3.select(this).style('fill','#c8ba09')})
.on('mousedown',lowDownAction)
.attr("x", function(d,i){return partSize*i})
.attr("y", headHeight)
.attr("width", partSize)
.attr("height", offset)
.style('fill-opacity',0.7)
.style('stroke-opacity',0.3)
.style('fill','#c8ba09')
.style('stroke','#fff');
//SUPERIOR (GROUP) - CLICKABLE RECTANGLES
this.group2 = this.svgContainer.append('g');
//SUPERIOR I
this.group2.append('text')
.attr("x", (partSize*4)/2)
.attr("y", 10+headHeight)
.attr('id','selSuperior')
.text('Polygon simplification without topology');
this.group2.append('rect')
.on('mouseover',function(d){d3.select(this).style('fill','#055')})
.on('mouseout',function(d){d3.select(this).style('fill','#fff')})
.on('mousedown',supDownAction)
.datum('basics')
.attr("x", 0)
.attr("y", headHeight)
.attr("width", partSize*4)
.attr("height", 14)
.style('fill','#fff')
.style('opacity',0.2);
//SEPERATOR I
this.group2.append('line')
.attr('x1',(partSize*4))
.attr('y1',headHeight)
.attr('x2',(partSize*4))
.attr('y2',height)
.style('stroke','#413a01')
.style('stroke-width',1.5)
//SUPERIOR II
this.group2.append('text')
.attr("x", (partSize*4)+(partSize*5)/2)
.attr("y", 10+headHeight)
.attr('id','selSuperior')
.text('Polygon simplification regarding topology');
this.group2.append('rect')
.on('mouseover',function(d){d3.select(this).style('fill','#055')})
.on('mouseout',function(d){d3.select(this).style('fill','#fff')})
.on('mousedown',supDownAction)
.datum('consistency')
.attr("x", (partSize*4))
.attr("y", headHeight)
.attr("width", (partSize*5))
.attr("height", 14)
.style('fill','#fff')
.style('opacity',0.2);
//SEPERATOR II
this.group2.append('line')
.attr('x1',(partSize*5)+(partSize*4))
.attr('y1',headHeight)
.attr('x2',(partSize*5)+(partSize*4))
.attr('y2',height)
.style('stroke','#413a01')
.style('stroke-width',1.5)
//SUPERIOR III
this.group2.append('text')
.attr("x", ((partSize*4)+(partSize*5)+(partSize*7)/2))
.attr("y", 10+headHeight)
.attr('id','selSuperior')
.text('Data-driven polygon simplification');
this.group2.append('rect')
.on('mouseover',function(d){d3.select(this).style('fill','#055')})
.on('mouseout',function(d){d3.select(this).style('fill','#fff')})
.on('mousedown',supDownAction)
.datum('data_driven') .attr("x", (partSize*5)+(partSize*4))
.attr("y", headHeight)
.attr("width", (partSize*7))
.attr("height", 14)
.style('fill','#fff')
.style('opacity',0.2);
//SEPERATOR III
this.group2.append('line')
.attr('x1',(partSize*5)+(partSize*4)+(partSize*7))
.attr('y1',headHeight)
.attr('x2',(partSize*5)+(partSize*4)+(partSize*7))
.attr('y2',height)
.style('stroke','#413a01')
.style('stroke-width',1.5)
//SUPERIOR IV
this.group2.append('text')
.attr("x", ((partSize*4)+(partSize*5)+(partSize*7)+(partSize*2)/2))
.attr("y", 10+headHeight)
.attr('id','selSuperior')
.text('Result');
this.group2.append('rect')
.on('mouseover',function(d){d3.select(this).style('fill','#055')})
.on('mouseout',function(d){d3.select(this).style('fill','#fff')})
.on('mousedown',supDownAction)
.datum('final_geom')
.attr("x", (partSize*5)+(partSize*4)+(partSize*7))
.attr("y", headHeight)
.attr("width", (partSize*2))
.attr("height", 14)
.style('fill','#fff')
.style('opacity',0.2);
this.group3 = this.svgContainer.append('g');
//FUNCTIONS
this.supDownAction=supDownAction;
function supDownAction(d){
if(superiorDownAction==undefined)console.log('This is currently only a dummy for the superior rectangle...give it content by .defineSupDownAction()')
else superiorDownAction(d);
}
this.defineSupDownAction=defineSupDownAction;
function defineSupDownAction(function_){
superiorDownAction=function_;
}
this.lowDownAction=lowDownAction;
function lowDownAction(d,i){
if(lowerDownAction==undefined)console.log('This is currently only a dummy for the lower rectangle...give it content by .defineLowDownAction()')
else lowerDownAction(d,i);
}
this.defineLowDownAction=defineLowDownAction;
function defineLowDownAction(function_){
lowerDownAction=function_;
}
this.moveProgressor=moveProgressor;
function moveProgressor(width, duration){
progress.attr('y',0).attr("x", 0).attr("width", 0).transition().duration(duration).ease('linear').attr("width", width);
}
this.defineProgressor=defineProgressor;
function defineProgressor(x,width, duration){
progress.attr('y',headHeight).attr("x", x).attr("width", 0).transition().duration(duration).ease('linear').attr("width", width);
}
}
<!DOCTYPE html>
<html>
<head>
<title>Demonstration fo ArcGeometries 3</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="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://mourner.github.io/simplify-js/simplify.js"></script>
<script src="http://bl.ocks.org/milkbread/raw/5779939/RKMapping_0.4.5.js"></script>
<script src="http://bl.ocks.org/milkbread/raw/5829814/RKAggregation_1.0.js"></script> <!--this function needs RKMapping.js additionally-->
<script src="http://bl.ocks.org/milkbread/raw/5947143/crossfilter.min.js"></script> <!--NOT WRITTEN BY ME!!!-->
<script src="loadAdditionalData.js"></script>
<script src="animationSelektor.js"></script>
<style>
@import url(http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.css);
@import url(simplyArcDemoStyles.css);
</style>
</head>
<body>
<h1>Animated Demonstration of Data-Driven<br> Polygon Simplification with the help of TopoJSON</h1>
<div id="selectAnimation" style="width: 960px; height: 30px"></div>
<div id="info"style="width: 960px;"></div>
<div id="map" style="width: 960px; height: 500px"></div>
<script>
//0. DEFINE SOME INITIAL VARIABLES
var strokeColors = ['rgb(255,0,0)','rgb(0,150,0)'],
geometryFinal = [],
geometryGlobal = [];
topologyGlobal = [];
//0.1 READ THE HASH OF THE URL
var hashValue = window.location.hash;
if(hashValue.indexOf('#') == -1)hashValue = [0.1];
else hashValue = hashValue.replace('#','').split(':');
//0.2 Leaflet Map
var map = L.map('map').setView([51, 11], 5);
var data_attrib = " | Data: <a href='http://www.geodatenzentrum.de/geodaten/gdz_rahmen.gdz_div?gdz_spr=eng&gdz_akt_zeile=5&gdz_anz_zeile=1&gdz_unt_zeile=18&gdz_user_id=0' target='_blank'>&copy; GeoBasis-DE / BKG 2013</a> | <a href='http://d3js.org/'>D3.js</a> | <a href='https://github.com/mbostock/topojson/wiki'>TopoJSON</a> | <a href='http://mourner.github.io/simplify-js/'>Simplify</a>"
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: "Map: <a href='http://www.openstreetmap.org/'>&copy; OpenStreetMap </a>contributers" + data_attrib});
var esri = L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}.png', {attribution: "Map: <a href='http://www.arcgis.com/home/item.html?id=c4ec722a1cd34cf0a23904aadf8923a0'>ArcGIS - World Physical Map</a>" + data_attrib});
var stamen = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: "Map: <a href='http://maps.stamen.com/#toner/12/37.7706/-122.3782'>Stamen Design</a>" + data_attrib}).addTo(map);
var baseLayers = {"stamen": stamen, "OpenStreetMap":osm, "World Physical Map":esri};
var control = L.control.layers(baseLayers).addTo(map);
//0.3 Map overlay - BASICS
var clickCounter = 0;
var svgContainer= d3.select(map.getPanes().overlayPane).append("svg")
var group= svgContainer.append("g").attr("class", "leaflet-zoom-hide");
var path2 = d3.geo.path().projection(project);
//0.4.1 INFO-VISUALISATION &&
//5.1 SLIDER FOR MANUAL SIMPLIFCATION
var tableRow = d3.select('#info').append('table').attr('border',0).append('tr')
var heading = tableRow.append('td').attr('width',960/2).append('text').attr('id','head').text('Load data...')
var countInfo = tableRow.append('td').attr('width',960/2-80).append('text')
var selectMan = tableRow.append('td').attr('width',80).attr('align','center').datum('off')
var selectManText = selectMan.append('text_').text('Simplify manually')
var sliderContainer = d3.select('#info').append('svg').attr('width',960).attr('height',0);
var slider = sliderContainer.append('rect')
.attr("x", 0).attr("y", 0)
.attr("width", 960).attr("height", 25)
.style('fill','#a00').style('opacity',0.4);
var indicator = sliderContainer.append('rect')
.attr("x", 0).attr("y", 0)
.attr("width", 8).attr("height", 25)
.style('fill','#000').style('opacity',0.4);
var sliderScale = d3.scale.linear().domain([0,1]).range([0,960]);
//1. OPEN BASIC DATA
var selMaxVal = 0, counter, position;
d3.json("vg250_states_topo.json", function(error, topology) {
//1.1 DEFINE BASIC DATA
var pure_features = topology.objects.vg250_bld;
var featureStorage = new featureObjStorage();
pure_features.geometries.forEach(function(d){ featureStorage.addFeatObj(d); })
var featuresObjects = featureStorage.featureObjects;
//1.2 BASIC POLYGONS
var basicObjects = topojson.feature(topology, pure_features).features;
//1.3 INITIAL SIMPLIFICATION OF BASIC POLYGONS
basicObjects = basicObjects.map(function(data){
data.geometry.coordinates = polygonSimplification2(data, parseFloat(hashValue[0]))
return data;
})
//1.4 GET BOUNDS OF DATA - NEEDED FOR THE SIZE OF THE SVG-CONTAINER
var overlayBounds = d3.geo.bounds(topojson.feature(topology, pure_features));
//2. GET THE ARCS OF THE BASIC POLYGONS
var arcCollection = getArcs(topology, featuresObjects);
//2.1 SIMPLIFY EACH ARC && 2.2 PUSH THEM TO AN ARRAY ... because, we need an array of objects not an object
var arcCollectionArray_ = [], maxLength = 0, i=0;
for (arc in arcCollection){
var cache = arcCollection[arc];
//simplify LineStrings of the arcs
cache.geometry.coordinates = pointsConversion(simplify(pointsConversion(cache.geometry.coordinates, 'toObject'),parseFloat(hashValue[0]),false), 'toArray')
cache.id=i;
//push simplified version to an array
arcCollectionArray_.push(cache)
//directly get the maximum value (needed for the slider)
if(maxLength<cache.length)maxLength=cache.length;
i++;
}
//2.3 SETUP BASIC ARCS AS CROSSFILTER
var arcCollectionCF = crossfilter(arcCollectionArray_);
var arcsLenDim = arcCollectionCF.dimension(function(d){return d.length;})
filter();
//3. LOAD A 2nd DATASET WITH A HIGHER DENSITY
var globalData_bld2 = [];
loadAdditionalData("vg250_bld_krs_topo.json", 'bld', globalData_bld2);
var globalData_krs2 = [];
loadAdditionalData("vg250_bld_krs_topo.json", 'krs', globalData_krs2);
//TELL THE USER THAT DATA WAS LOADED ... not 100% exactly, as it is not sure, that the 2nd dataset in already loaded
heading.transition().duration(1000).text('Data was completely loaded...start an animation!')
map.on("viewreset", reset);
reset();
//0.4.2 CLICK ON THE MAIN SVG-CONTAINER AND START EACH ANIMATION
/*svgContainer.on('mouseup',mainClick);
function mainClick(){
console.log(clickCounter);
if(clickCounter==0)selectBlock('basics')
else if(clickCounter==1)selectBlock('consistency')
else if(clickCounter==2)selectBlock('data_driven')
else if(clickCounter==3)selectBlock('final_geom')
else window.location.reload()
clickCounter++;
}*/
//5.2 DEFINE THE INTERACTIONS FOR THE MANUAL SIMPLIFICATION
selectMan.on('mousedown',function(d){
var status, trans, text;
if(d=='on'){status='off'; trans = 0; text = 'Simplify manually'}
else {status='on'; trans = 20; text = 'Hide slider'}
selectMan.datum(status);
sliderContainer.transition().duration(1500).attr('height',trans)
selectManText.text(text);
if(d=='off') showFeatures(10000 * 0, [0, globalData_bld2[1].length], 'arcs2', 'These are the more detailled arcs of the german states ... use the slider to simplify!')
else showFeatures(10000 * 0, [globalData_bld2[1].length, 0], 'arcs2', 'Remove the Arcs of the german states')
})
slider.on('mousemove',sliderAction);
function sliderAction(){
var value = (d3.mouse(this)[0]);
indicator.attr("x", value-4)
countInfo.text('Number of geometries: ' + globalData_bld2[1].length + ' | Tolerance: ' + sliderScale.invert(value).toFixed(5))
group.selectAll("path").attr("d", setPath_);
function setPath_(d){
var cache = pointsConversion(simplify(pointsConversion(d.geometry.coordinates, 'toObject'),sliderScale.invert(value),false), 'toArray')
return path2({type: d.geometry.type, coordinates: cache})
}
}
//0.5.1 INITIALIZE THE VARIABLES TO SETUP THE ANIMATION-SELECTION
var all = {'a1':0,'a2':1,'a3':2,'a4':3,'b1':4,'b2':5,'b3':6,'b4':7,'b5':8, 'c1':9, 'c2':10, 'c3':11, 'c4':12, 'c5':13, 'c6':14, 'c7':15, 'd1':16, 'd2':17};
var animateWhat = 'final_geom'; //'all' | 'basics' | 'consistency' | 'data_driven' | 'final_geom'
var endless = true;
var delays = getDelays(animateWhat);
//0.5.2 SETUP THE SELECTION FOR THE ANIMATIONS
var tags = [];
for (key in all)tags.push(key)
var animSel = new animationSelektor(d3.select('#selectAnimation'), tags, 960, 30)
animSel.defineSupDownAction(selectBlock)
//0.5.3 FUNTION THAT LISTENS FOR THE SELECTED ANIMATION
function selectBlock(d){
animateWhat = d; //what animation was chosen ...GLOBAL
delays = getDelays(animateWhat); //which delays are needed for this animation ...GLOBAL
animation(); //apply the chosen animation
//show the progress of each
var partSize = animSel.partSize;
if(d=='basics')animSel.defineProgressor(0,partSize*4, 10000*4)
else if(d=='consistency')animSel.defineProgressor(partSize*4,partSize*5, 10000*5)
else if(d=='data_driven')animSel.defineProgressor(partSize*(4+5),partSize*7, 10000*7)
else if(d=='final_geom')animSel.defineProgressor(partSize*(4+5+7),partSize*2, 10000*2)
else if(d=='all')animSel.moveProgressor(partSize*(4+5+7+2), 10000*(4+5+7+2))
}
//Can be started automatically, but needs a timeout, because the 2nd dataset has to be fully loaded!
//setTimeout(animation,3000)
//MAIN FUNCTION ... EXECUTES ALL THE AUTOMATIC ANIMATIONS
function animation() {
console.log(animateWhat, delays)
if(animateWhat=='all' || animateWhat=='basics'){
//1. BASIC SITUATION
showFeatures(10000 * delays.a1, [0, 16], 'polygon', 'German States as Polygons (GeoJSON)')
simplifyCurrentGeometries(10000 * delays.a2, [0, 1], 'poly', 'Simplify Polygons to high Tolerance ... failures on topology');
simplifyCurrentGeometries(10000 * delays.a3, [1, 0.1], 'poly', 'Simplify Polygons to adequate Tolerance ... failures on topology');
removeFeatures(10000 * delays.a4, [0,16], basicObjects);
}
if(animateWhat=='all' || animateWhat=='consistency'){
//2. Demonstrating the Arcs and how we can keep the topology
showFeatures(10000 * delays.b1, [0, arcCollectionCF.groupAll().reduceCount().value()], 'arcs', 'Arcs of the German States as LineStrings (TopoJSON)')
showFeatures(10000 * delays.b2, [0, arcCollectionCF.groupAll().reduceCount().value()], 'label', 'Start- & End-points of the Arcs')
simplifyCurrentGeometries(10000 * delays.b3, [0, 1], 'arc', 'Simplify the Arcs to high Tolerance ... preserve topology');
simplifyCurrentGeometries(10000 * delays.b4, [1, 0.1], 'arc', 'Simplify the Arcs to adequate Tolerance ... preserve topology');
removeFeatures(10000 * delays.b5, [0,215]);
}
if(animateWhat=='all' || animateWhat=='data_driven'){
//3. Demonstrating the data-driven degree of simplification
showFeatures(10000 * delays.c1, [0, globalData_krs2[1].length], 'arcs2', 'Arcs of the German States as LineStrings (TopoJSON ... including German Counties)')
showFeatures(10000 * delays.c2, [globalData_krs2[1].length, globalData_bld2[1].length], 'arcs2', 'Remove the Arcs of the German Counties')
showFeatures(10000 * delays.c3, [0, globalData_bld2[1].length], 'label2', 'Show the labels of the Arcs')
showFeatures(10000 * delays.c4, [globalData_bld2[1].length, 0], 'label2', 'Remove the labels of the Arcs')
simplifyCurrentGeometries(10000 * delays.c5, [0, 1], 'arc', 'Simplify the Arcs to high Tolerance ... data-driven degree of simplification');
simplifyCurrentGeometries(10000 * delays.c6, [1, 0.1], 'arc', 'Simplify the Arcs to adequate Tolerance ... data-driven degree of simplification');
showFeatures(10000 * delays.c7, [globalData_bld2[1].length, 0], 'arcs2', 'Clean up the SVG-Visualisation Area!')
}
//4. Finally, show the resulting polygons
if(animateWhat=='all' || animateWhat=='final_geom'){
showFinalGeometries(10000 * delays.d1);
removeFeatures(10000 * delays.d2, [0,16], geometryFinal);
}
//5. Let it never stop...
if(endless == true) {
var counter = 0;
for(del in delays){counter++;}
svgContainer.transition()
.delay(10000*counter)
.duration(1000)
.each("end", animation);
}
}
//SUPPORTING FUNCTIONS OF THE MAIN FUNCTION
//show final features ... needed 1 time
function showFinalGeometries(delay){
//simplify arcs before converting them
for (arc in globalData_bld2[2]){
globalData_bld2[2][arc].coordinates = pointsConversion(simplify(pointsConversion(globalData_bld2[2][arc].coordinates, 'toObject'),parseFloat(hashValue[0]),false), 'toArray');
}
geometryFinal = getGeometries(globalData_bld2[0], globalData_bld2[2])
showFeatures(delay, [0, 16], 'cons_geometry', 'Simplified Polygons of German States with preserved Topology and data-driven degree')
}
//show features ... needed 9 times
function showFeatures(delay, range, type, head){
svgContainer.transition()
.delay(delay)
.duration(5000)
.tween("precision", function() {
heading.text(head);
var pos = d3.interpolateRound(range[0], range[1]);
return function(t) {
position = pos(t);
if(type=='arcs' || type=='label')geometryGlobal = arcsLenDim.top(position);
if(type=='arcs2'){
geometryGlobal = globalData_krs2[1].slice(0,position);
}
else if(type=='label'){
topologyGlobal = topology;
addPoints();
}
else if(type=='label2'){
geometryGlobal = globalData_krs2[1].slice(0,position);
addLabels();
}
else if(type=='polygon'){
geometryGlobal = basicObjects.slice(0,position);
}
else if(type=='cons_geometry'){
geometryGlobal = geometryFinal.slice(0,position);
}
if(type=='arcs' || type=='arcs2' || type=='polygon' || type=='cons_geometry') countInfo.text('Number of geometries: ' + geometryGlobal.length);
if(type=='arcs' || type=='arcs2') render3('line');
if(type=='polygon' || type=='cons_geometry') render3('polygon');
};
})
}
//remove the features ... needed 3 times
function removeFeatures(delay, range, data){
svgContainer.transition()
.delay(delay)
.duration(5000)
.tween("precision", function() {
var pos = d3.interpolateRound(range[1], range[0]);
return function(t) {
position = pos(t);
if (data!=undefined) geometryGlobal = data.slice(0,position);
else{
geometryGlobal = geometryGlobal.slice(0,position);
}
heading.text('Clean up the SVG-Visualisation Area!');
countInfo.text('Number of geometries: ' + geometryGlobal.length)
group.selectAll("path").data(geometryGlobal, function(d){return d.geometry.coordinates})
.transition().duration(5000)
.attr("transform","translate(0,0) scale(-1,1)")
.remove()
group.selectAll("circle").remove();
};
})
}
//simplify the geometries that lie currently in the svg-container ... 6 times, each animated simplification
function simplifyCurrentGeometries(delay, range, type, value){
svgContainer.transition()
.delay(delay)
.duration(5000)
.tween("precision", function() {
var sim = d3.interpolate(range[0], range[1]);
return function(t) {
simpleVal = sim(t);
group.selectAll("path").attr("d", setPath_)
heading.text(value);
if(type!='arc')countInfo.text('Number of geometries: ' + geometryGlobal.length + ' | Tolerance: ' + simpleVal.toFixed(5))
else countInfo.text('Number of geometries: ' + globalData_bld2[1].length + ' | Tolerance: ' + simpleVal.toFixed(5))
function setPath_(d){
if(type=='poly') var cache = polygonSimplification2(d, simpleVal)
else if(type=='arc') var cache = pointsConversion(simplify(pointsConversion(d.geometry.coordinates, 'toObject'),simpleVal,false), 'toArray')
return path2({type: d.geometry.type, coordinates: cache})
}
};
})
}
//THIS IS THE ANIMATION OF GEOMETRIES ... CALLED WITHIN THE TRANSITION-TWEEN
function render3(style){
//1. JOIN
var feature = group.selectAll("path").data(geometryGlobal, function(d){return d.geometry.coordinates})
//3. ENTER
feature.enter()
.append("path")
.attr("class",style)
.attr('id',function(d){return "arc"+d.id})
.on('mouseover', function(d){d3.select(this).style('stroke',strokeColors[0])})
.on('mouseout', function(d){d3.select(this).style('stroke',strokeColors[1])})
.style('stroke',strokeColors[0])
.attr("d", path2)
.attr("transform","translate(0,0) scale(-1,1)")
.transition().duration(4000)
.attr("transform","translate(0,0) scale(1,1)")
.transition().duration(1000)
.style('stroke',strokeColors[1]);
//5. EXIT
feature.exit().remove();
function setPath(d){ return path2(d.geometry)}
}
//THIS IS THE ANIMATION OF LABELS ... CALLED WITHIN THE TRANSITION-TWEEN
function addLabels(){
//1. JOING
var labels = group.selectAll("text").data(geometryGlobal, function(d){return d.geometry.coordinates});
//3. ENTER
labels.enter()
.append('text')
.attr('font-size',12)
.text(function(d){if(d.members.length == 2) return d.id})
.attr("fill",'#00f')
.attr('x',function(d){return path2.centroid({'type':'Feature','geometry':d.geometry})[0]})
.attr('y',function(d){return path2.centroid({'type':'Feature','geometry':d.geometry})[1]})
.attr("opacity",0)
.transition().duration(3000)
.attr("opacity",0.7)
//5. EXIT
labels.exit().transition().duration(3000).attr("opacity","0").remove();
}
function addPoints(){
var tf = topologyGlobal.transform,
kx = tf.scale[0],
ky = tf.scale[1],
dx = tf.translate[0],
dy = tf.translate[1];
group.selectAll("circle")
.data(topologyGlobal.arcs)
.enter().append("circle")
.attr("cx", function(d) { return project([d[0][0] * kx + dx, d[0][1] * ky + dy])[0] })
.attr("cy", function(d) { return project([d[0][0] * kx + dx, d[0][1] * ky + dy])[1] })
.attr("r", 0)
.attr("fill", '#f00')
.attr("opacity", 0)
.transition().duration(2000)
.attr("opacity", 0.7)
.attr("r", 20)
.transition().delay(2200).duration(2000)
.attr("r", 2)
//.transition().delay(3500+3000).duration(3000).attr("opacity", 0.3).remove();
}
//RESET THE SVG-OVERLAY AND THE GROUPS WHEN THE ZOOMLEVEL OF THE MAP HAS CHANGED
function reset() {
var bottomLeft = project(overlayBounds[0]),
topRight = project(overlayBounds[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] + ")");
group.selectAll("path").attr("d", path2)
group.selectAll('text')
.attr('x',function(d){return path2.centroid({'type':'Feature','geometry':d.geometry})[0]})
.attr('y',function(d){return path2.centroid({'type':'Feature','geometry':d.geometry})[1]})
}
//GET THE CORRESPONDING DELAYS FOR EACH POSSIBLE ANIMATION
function getDelays(animateWhat_){
switch(animateWhat_)
{
case 'all':
var delays = all;
break;
case 'basics':
var delays = {'a1':0,'a2':1,'a3':2,'a4':3};
break;
case 'consistency':
var delays = {'b1':0,'b2':1,'b3':2,'b4':3,'b5':4};
break;
case 'data_driven':
var delays = {'c1':0, 'c2':1, 'c3':2, 'c4':3, 'c5':4, 'c6':5, 'c7':6};
break;
case 'final_geom':
var delays = {'d1':0, 'd2':1};
break;
}
return delays;
}
//DOES THE FILTERING ON A CROSSFILTER-OBJECT ... not really needed!
function filter(){
//Filtering
arcsLenDim.filterAll(); //reset filter
var origCount = arcCollectionCF.groupAll().reduceCount().value(); //count elements initially
//arcsLenDim.filter(function(d){return (d < selMaxVal)}) //filter the dataset
counter = arcCollectionCF.groupAll().reduceCount().value(); //count elements after filtering
}
});
//PROJECT POINT OF OVERLAY TO LEAFLET-SVG-COORDINATES (LayerPoint)
function project(point) {
var latlng = new L.LatLng(point[1], point[0]);
var layerPoint = map.latLngToLayerPoint(latlng);
return [layerPoint.x, layerPoint.y];
}
//CONVERT POINTS 'toARRAY' || 'toOBJECT' - NEEDED FOR THE SIMPLIFICATION LIBRARY simplify.js
function pointsConversion(points, direction){
var cache;
if(direction=='toObject'){
cache = points.map(function(point){return {'y':point[0],'x':point[1]}});
}
else if(direction=='toArray'){
cache = points.map(function(point){return [point.y,point.x]});
}
return cache;
}
//SIMPLE POLYGON SIMPLIFICATION - WITHOUT ANY ADHERANCE OF TOPOLOGY
function polygonSimplification2(feature, maxVal_){
if (feature.geometry.type=='Polygon')var cache_ = readPoly(feature.geometry.coordinates.slice(0));
else if (feature.geometry.type=='MultiPolygon'){
var cache_ = feature.geometry.coordinates.slice(0);
cache_ = cache_.map(function(single_poly){return readPoly(single_poly); })
}
return cache_;
function readPoly(polygon){
return polygon.map(function(poly_part){
return pointsConversion(simplify(pointsConversion(poly_part, 'toObject'),maxVal_,false), 'toArray')
})
}
}
</script>
</body>
</html>
function loadAdditionalData(file, object, globalDataVariable_){
d3.json(file, function(error, topology) {
//globalDataVariable_[0] = topology;
//set the featureStorage and the featureObjects
//console.log(topology)
if(object=='bld')var pure_features = topology.objects.vg250_bld;
else if(object=='krs')var pure_features = topology.objects.vg250_krs;
var featureStorage = new featureObjStorage();
pure_features.geometries.forEach(function(d){ featureStorage.addFeatObj(d); })
var featuresObjects = featureStorage.featureObjects;
globalDataVariable_[0] = featuresObjects;
var arcCollection = getArcs(topology, featuresObjects);
//we need an array of objects not an object, so loop all arcs ... simplify them AND push them to an array
var arcCollectionArray_ = [], maxLength = 0, i=0;
for (arc in arcCollection){
var cache = arcCollection[arc];
//simplify LineStrings of the arcs
cache.geometry.coordinates = pointsConversion(simplify(pointsConversion(cache.geometry.coordinates, 'toObject'),parseFloat(hashValue[0]),false), 'toArray')
cache.id=i;
//push simplified version to an array
arcCollectionArray_.push(cache)
//directly get the maximum value (needed for the slider)
if(maxLength<cache.length)maxLength=cache.length;
i++;
}
globalDataVariable_[2] = arcCollection;
globalDataVariable_[1] = arcCollectionArray_;
})
}
@import url(http://fonts.googleapis.com/css?family=Arapey);
#map{
border-radius: 0px 0px 15px 15px;
}
#selSuperior{
font-size: 12px;
fill:#a00;
text-anchor:middle;
}
#selMain{
font-size: 12px;
fill:#600;
text-anchor:middle;
}
body {
background: #8AA767;
margin: 2em auto 3em auto;
position: relative;
width: 960px;
}
h1{
font-family: "Arapey", serif;
text-shadow: 2px 2px #c2c2c2;
text-align: center;
}
text{
}
#arcs{
overflow-x: auto;
overflow-y: auto;
padding-bottom: 15px;
max-width: 100%;
}
#info {
background-color:#c8ba09;
}
text_ {
font-size:10px;
opacity:0.5;
}
#head {
font-size:16px;
}
.polygon{
fill:#ccc;
stroke-width:1.5px;
opacity:0.8;
}
.polygon:hover{
opacity:0.2;
fill:#0f0;
}
.line{
fill:none;
stroke-width:2.5px;
opacity:0.8;
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment