#D3 + TopoJSON -- Aggregation ##this is a second demonstration of my algorithm for aggregating adjacent polygons based on their redundant arc geometries
- origin is the file RKMapping_clipping_1.1.html
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script src="http://cdn.leafletjs.com/leaflet-0.5.1/leaflet.js"></script> | |
<!--<script src="https://gist.github.com/milkbread/5713629/raw/RKMapping_0.4.js"></script>--> | |
<script src="../static/RKMapping_0.4.4.js"></script> <!--http://bl.ocks.org/milkbread/raw/5779939--> | |
<script src="../static/RKAggregation.js"></script> <!--http://bl.ocks.org/milkbread/raw/5829814 this function needs RKMapping.js additionally--> | |
<style> | |
@import url(http://bl.ocks.org/milkbread/raw/5713683/myStyles.css); | |
@import url(http://cdn.leafletjs.com/leaflet-0.5/leaflet.css); | |
#map { | |
width: 960px; | |
height: 600px; | |
} | |
#polys:hover{ | |
fill:rgba(200,0,0,0.8); | |
} | |
#polys{ | |
stroke-width:1px; | |
stroke:rgba(255,255,255,1); | |
} | |
#polys2{ | |
stroke-width:3px; | |
stroke:rgba(255,0,0,1); | |
fill:rgba(0,255,0,0.5) | |
} | |
.overlay_poly{ | |
fill:rgba(100,100,100,0.3); | |
} | |
.overlay_multipoly{ | |
fill:rgba(0,0,0,0.7); | |
} | |
.clipped_poly{ | |
fill:rgba(255,0,0,0.7); | |
} | |
#delaunay{ | |
fill:none; | |
stroke:rgba(0,0,255,1); | |
stroke-width:1px; | |
} | |
#points{ | |
fill:#000;; | |
} | |
</style> | |
</head> | |
<body> | |
<div id=map></div> | |
<script> | |
//The background map | |
//var map = L.map('map').setView([49.9, 7.76], 8); | |
var map = L.map('map').setView([50.19, 7.58], 11); | |
//var map = L.map('map').setView([49.53, 8.17], 11); | |
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}; | |
L.control.layers(baseLayers).addTo(map); | |
//initial D3 stuff | |
var overLayer = new mapOverlay(map); | |
var infoContainer = d3.select("body").append("info").text("This will show you some information!") | |
d3.select("body").append("br") | |
var mouseInfo = d3.select("body").append("info").text("") | |
var thousendsSep = d3.format(","), maximum = 0; | |
//load data | |
d3.json("../gists/milkbread/raw/5754788/vg250_clipped3_topo.json", function(error, topology) { | |
//d3.json("../gists/milkbread/raw/5754788/vg250_clipped_mini_topo.json", function(error, topology) { | |
//get the pure 'data-object', containing only id, type & arcs | |
var pure_states = topology.objects.vg250_gem_clipped4; | |
var maxID = 0; | |
pure_states.geometries.forEach(function(d){if (d.id>maxID)maxID=d.id}) | |
console.log(maxID) | |
//initialise a storage for the removed Features | |
var removedFeatureStorage = new remFeatureStorage(); | |
//create a feature storage | |
var featureStorage = new featureObjStorage(); | |
//add each pure 'data-object' to the feature storage | |
var new_id = 2; | |
pure_states.geometries.forEach(function(d){ | |
if(d.type=='MultiPolygon'){ | |
d.arcs.forEach(function(polygon){ | |
var newFeature = new Object(); | |
newFeature.type='Polygon'; | |
newFeature.id=maxID+new_id; | |
new_id++; | |
newFeature.properties=d.properties; | |
newFeature.arcs=polygon | |
featureStorage.addFeatObj(newFeature) | |
}) | |
} | |
else featureStorage.addFeatObj(d) }) | |
//get all feature objects from the storage ... feature objects contain all necessary objects and functions for the aggregation | |
var featuresObjects = featureStorage.featureObjects; | |
//sort the features in dependence of their shape size | |
featuresObjects.sort(function(a,b){return (b.properties.SHAPE_AREA-a.properties.SHAPE_AREA)}) | |
//get all arc-geometries of the selected objects 'pure_states'...can be simplified directly | |
var arcCollection = getArcs(topology, featuresObjects); | |
//check whose size is too small...it it is, push its id into an array | |
var aggregFeatures = []; | |
featuresObjects.forEach(function(d){ | |
if(d.properties.SHAPE_AREA< 6500){ | |
if(d.type=='Polygon')aggregFeatures.push([d.id,-1]) | |
else if(d.type=='MultiPolygon'){ | |
//console.log(d.geometry.length) | |
for(var i=d.geometry.length-1;i>=0;i--){ | |
//aggregFeatures.push([d.id,i]) | |
} | |
//aggregFeatures.push([d.id,1]) | |
//aggregFeatures.push([d.id,0]) | |
} | |
} | |
else return; | |
}) | |
//aggregate each feature, whose id is stored in this array...feature objects are directly edited by this function | |
aggregFeatures.forEach(function(d){ | |
//console.log("###Begin the aggregation###") | |
doAggregation(d[0], d[1]) | |
//console.log("###Finished the aggregation###") | |
}) | |
//build geometries from feature and their arcs | |
polygons = getGeometries(featuresObjects, arcCollection) | |
//add the geometries to the data overlay | |
overLayer.addGeometries(polygons,"path"); | |
//build a GeometryCollection, for the visualisation of features | |
var polyCollection = {type: 'GeometryCollection', geometries:polygons}; | |
map.on("viewreset", reset); | |
doStyling(); | |
reset(); | |
//***end of basic operations | |
//###FUNCTIONS | |
function doAggregation(id, fixed_multiPolyIndex_){ | |
var testSuccess = false; | |
var featuresObjectsIndizes = featureStorage.getIndizes(); //stores the IDs of all features ... improves search & -speed | |
var selectedFeatureIndex = featuresObjectsIndizes.indexOf(id); //get the index of the current feature in the storage | |
var featureObject = featuresObjects[selectedFeatureIndex]; //get the current feature from the storage, position in array = 'selectedFeatureIndex' | |
//console.log("This is the id of the analysed feature: ",featureObject.id, "... it is a: ",featureObject.type) | |
var neighbors = featureObject.getNeighbors(arcCollection, fixed_multiPolyIndex_); //get all neighbors of the current feature ... 'fixed_multiPolyIndex_' indicates the position of the chosen MultiPolygon-part | |
//console.log("These are my neighbors: ",neighbors) | |
if(neighbors.length>0){ //only go on when there are some neighbors | |
//~~~~Find/Define the partner of aggregation ... Different solutions are possible | |
var sorted_neighbors = sortArcs(neighbors, arcCollection) | |
console.log("id of analysed feature: ",id, "... and the sorted neighbors: ", sorted_neighbors) | |
var longest_neigh = checkNeighborExistence2(id, arcCollection, removedFeatureStorage, sorted_neighbors, featuresObjects, featuresObjectsIndizes) | |
//console.log(longest_neigh.id) | |
//~~~~~~~~~~~~~~~~ | |
if(longest_neigh!=undefined){ //is undefined...when all possible neighbors where checked and are all removed | |
//console.log("...that means we want to aggregate this feature: ",longest_neigh.id," (add to candidate)", " with this feature: ",featureObject.id, "(origin) ") | |
//now we have to find the corresponding feature | |
var addToCandidateIndex = featuresObjectsIndizes.indexOf(longest_neigh.id); | |
//-1 means ... nothing found ... should not happen, as this is already catched by checkNeighborExistence() | |
//if(addToCandidateIndex==-1){ addToCandidateIndex = checkNeighborExistence_alternative(removedFeatureStorage, featuresObjectsIndizes, longest_neigh.id); } | |
var addToCandidate = featuresObjects[addToCandidateIndex]; | |
//console.log("...and with help of the index: "+addToCandidateIndex+" ...I can find the add to candidate: ",addToCandidate) | |
//console.log("test: ",removedFeatureStorage) | |
//if there is more than one neighboring arc for the addToCandidate ... but not completely necessary | |
var additionalNeighborArcs = checkForRedundantFeatures(sorted_neighbors, longest_neigh) | |
//aggregate the selected feature to the 'best fitting' neighbor | |
var removedArc = addToCandidate.aggregateFeature(featureObject, longest_neigh, additionalNeighborArcs, arcCollection); | |
//console.log("This is the removed arc: ",removedArc) | |
var originIndex = featuresObjectsIndizes.indexOf(featureObject.id) | |
//console.log("Did I find the origin to remove it: ",originIndex) | |
//remove the -origin- feature from the storage | |
//console.log("Das array vorm Entfernen: ",featuresObjects.length) | |
featureStorage.removeFeatObj(featureObject, removedFeatureStorage, addToCandidate, fixed_multiPolyIndex_) | |
//console.log(" ... und danach: ",featuresObjects.length) | |
testSuccess = true; | |
}else console.log("!!!Cannot find any neighbor that was not already removed...fix this bug...please!!!",id) | |
}else console.log(id + " - There is no neighbor!!!") | |
return testSuccess; | |
} | |
//Basic functions ... that are not related to the polygonal aggregation! | |
//function for aggregation on click | |
function visualiseFeatures(data){ | |
//This is a test on detecting which part of a MultiPolygon has to be edited ... !!!HAS TO BE REPLACED!!! against a check who fits to the used criterium ... currently the criterium is the click! | |
//console.log(data, d3.geo.bounds(data),d3.mouse(this)); | |
var mouse_coords = map.layerPointToLatLng(d3.mouse(this)) | |
//console.log(mouse_coords, mouse_coords.lat) | |
var fixed_multiPolyIndex = -1; //this variable is only defined for MultiPolygons and tells me which one of the 'multi'-possibilities the selected on is...by its index | |
if(data.type=='MultiPolygon')data.coordinates.forEach(function(d,i){ | |
var this_bounds = d3.geo.bounds({type: 'Polygon', coordinates: d}) | |
////console.log(this_bounds) | |
if((mouse_coords.lng>this_bounds[0][0] && mouse_coords.lng<this_bounds[1][0]) && (mouse_coords.lat>this_bounds[0][1] && mouse_coords.lat<this_bounds[1][1]))fixed_multiPolyIndex = i; | |
}) | |
//++++++++++++++ | |
overLayer.removeAll(); | |
doAggregation(data.id, fixed_multiPolyIndex); | |
polygons = getGeometries(featuresObjects, arcCollection) | |
overLayer.addGeometries(polygons,"path"); | |
overLayer.showAll(); | |
doStyling(); | |
} | |
function doStyling(){ | |
overLayer.features.attr("class",function(d){if(d.clipped==true) return "clipped_poly"; else {if(d.type=="Polygon")return "overlay_poly"; else if(d.type=="MultiPolygon") return "overlay_multipoly"; }}) | |
.attr("id","polys") | |
.on("mouseover",showInfo) | |
.on("mouseup",visualiseFeatures); | |
} | |
function reset(){ | |
var start_time = Date.now(); | |
overLayer.resetView(d3.geo.bounds(polyCollection)); | |
overLayer.showAll(); | |
console.log("initial load took: "+((Date.now() - start_time)/1000).toFixed(2)+"s") | |
} | |
function showInfo(value){ | |
mouseInfo.text("Center: "+map.getCenter()+" Zoom: "+map.getZoom()) | |
infoContainer.text("You hovered over: "+value.properties.GEN +' - Type: '+value.type +' - ID: '+value.id +' - Area: ' + thousendsSep(value.properties.SHAPE_AREA)) | |
//console.log(' - neighbors: ', polygons_indexed[value.id].neighbors) | |
} | |
function zoomToObject(d){ | |
var local_bounds = d3.geo.bounds(d), | |
center = new L.LatLng(local_bounds[0][1]+((local_bounds[1][1]-local_bounds[0][1])/2),local_bounds[0][0]+((local_bounds[1][0]-local_bounds[0][0])/2)), | |
southWest = new L.LatLng(local_bounds[0][1], local_bounds[0][0]), | |
northEast = new L.LatLng(local_bounds[1][1], local_bounds[1][0]); | |
var local_bounds = new L.LatLngBounds(southWest, northEast); | |
map.setView( center, map.getBoundsZoom( local_bounds)); | |
} | |
}) | |
function reShow(maxVal){ | |
maximum = maxVal; | |
overLayer.showAllFiltered(maxVal); | |
} | |
</script> | |
</body> | |
</html> |