#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> |