A tiny standalone JS library for classification. It is like a hidden treasure, which I never had the chance to test it. Within the project page there are a few examples related to OL2 but nothing related to OL3. So I though it would be nice to share my experience and give a boost to all GIS geeks looking forward to use the library in conjunction with OL3.
It currently supports the following statistical methods
- equal intervals
- quantiles
- standard deviation (seems to have a small bug here)
- arithmetic progression
- geometric progression
- jenks (natural breaks)
- uniques values
- user defined classification
If you are too impatient ...... get a snapshot
But lets examine our code step by step:
Here is your html:
Nothing special. A selector for the method selected, a number spinner to get the number classes and two divs. One for the map and one to place the generated legend. And of course a button to draw our thematic map.
<select id="methodselector" class="form-control"> <option value="method_EI" >Equal Interval</option> <option value="method_Q" selected>Quantile</option> <option value="method_SD" >Standard Deviation</option> <option value="method_AP" >Arithmetic Progression</option> <option value="method_GP" >Geometric Progression</option> <option value="method_CJ">Class Jenks</option> </select> Number of Classes: <input type="number" id="classcount" min="1" value="5" max="10"> <input type="button" id="drawitbtn" value='draw themmatic'/> <div id="map" class="map"></div> <div id='legend'></div>
Dont forget to reference the necessary js, css libs within your html file. These are::
<link rel="stylesheet" type="text/css" href="https://openlayers.org/en/v3.19.1/css/ol.css" /> <script src="https://openlayers.org/en/v3.19.1/build/ol-debug.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script> <script src="https://www.intermezzo-coop.eu/mapping/geostats/lib/geostats.js"></script>
Here is your css:
Again nothing special. Just a few classes to support the legend.
.geostats-legend div { margin: 3px 10px 5px 10px } .geostats-legend-title { font-weight: bold; margin-bottom: 4px; } .geostats-legend-block { border: 1px solid #555555; display: block; float: left; height: 12px; margin: 0 5px 0 20px; width: 20px; } .geostats-legend-counter { font-size: 0.8em; color: #666; font-style: italic; }
And finally your JAVASCRIPT:
just go through the comments of code
$('#drawitbtn').click(drawIt); //some global vars here var classSeries; var classColors; //color start from var colorFrom ='FFFFFF'; //color end to var colorTo = '1A8B16'; var defaultStyle = new ol.style.Style({ fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.3)' }), stroke: new ol.style.Stroke({ color: 'rgba(0, 255, 0, 1)', width: 1 }), text: new ol.style.Text({ font: '12px Calibri,sans-serif', fill: new ol.style.Fill({ color: '#000' }), stroke: new ol.style.Stroke({ color: '#fff', width: 3 }) }) }); //our methods here var vectorLayer = new ol.layer.Vector({ style:defaultStyle, source: new ol.source.Vector({ url: 'https://gist.githubusercontent.com/ptsagkis/dacbe5a42856dee041294b54579095d4/raw/730879d0d4909622b273818235b9c7f510bab5ee/countries_siplified.geojson', format: new ol.format.GeoJSON({ defaultDataProjection:'EPSG:4326', featureProjection:'EPSG:3857' }) }) }); var map = new ol.Map({ layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }), vectorLayer ], target: 'map', view: new ol.View({ center: [0, 0], zoom: 1 }) }); /** * do the themmatic */ function drawIt(){ var countryPopVals = new Array(); vectorLayer.getSource().getFeatures().forEach(function(feat) { countryPopVals.push(feat.get("POP2005")) }); console.info("countryPopVals",countryPopVals); getAndSetClassesFromData(countryPopVals, getClassNum(), getMethod()); vectorLayer.setStyle(setStyle); } /** * @data {Array} the array of numbers (these are the pop data for all countries) * @numclasses {Integer} get the number of classes * @method {String} get the classification method * * * set geostats object * set the series * set the colors ramp * */ function getAndSetClassesFromData(data, numclasses, method) { var serie = new geostats(data); var legenLabel = ""; if (method === "method_EI") { serie.getClassEqInterval(numclasses); methodLabel = "Equal Interval"; } else if (method === "method_Q") { serie.getClassQuantile(numclasses); methodLabel = "Quantile"; } else if (method === "method_SD") { serie.getClassStdDeviation(numclasses); methodLabel = "Standard Deviation "; } else if (method === "method_AP") { serie.getClassArithmeticProgression(numclasses); methodLabel = "Arithmetic Progression"; } else if (method === "method_GP") { serie.getClassGeometricProgression(numclasses); methodLabel = "Geometric Progression "; } else if (method === "method_CJ") { serie.getClassJenks(numclasses); methodLabel = "Class Jenks"; } else { alert("error: no such method.") } var colors_x = chroma.scale([colorFrom, colorTo]).colors(numclasses) serie.setColors(colors_x); document.getElementById('legend').innerHTML = serie.getHtmlLegend(null, "World Population</br> Method:"+methodLabel, 1); classSeries = serie; classColors = colors_x; } /** * function to verify the style for the feature */ function setStyle(feat,res) { var currVal = parseFloat(feat.get("POP2005")); var bounds = classSeries.bounds; var numRanges = new Array(); for (var i = 0; i < bounds.length-1; i++) { numRanges.push({ min: parseFloat(bounds[i]), max: parseFloat(bounds[i+1]) }); } var classIndex = verifyClassFromVal(numRanges, currVal); var polyStyleConfig = { stroke: new ol.style.Stroke({ color: 'rgba(255, 0, 0,0.3)', width: 1 }) }; var textStyleConfig = {}; var label = res < 5000 ? feat.get('NAME') : ''; if (classIndex !== -1) { polyStyleConfig = { stroke: new ol.style.Stroke({ color: 'rgba(0, 0, 255, 1.0)', width: 1 }), fill: new ol.style.Stroke({ color: hexToRgbA(classColors[classIndex],0.7) }) }; textStyleConfig = { text: new ol.style.Text({ text: label, font: '12px Calibri,sans-serif', fill: new ol.style.Fill({ color: "#000000" }), stroke: new ol.style.Stroke({ color: "#FFFFFF", width: 2 }) }), geometry: function(feature) { var retPoint; if (feature.getGeometry().getType() === 'MultiPolygon') { retPoint = getMaxPoly(feature.getGeometry().getPolygons()).getInteriorPoint(); } else if (feature.getGeometry().getType() === 'Polygon') { retPoint = feature.getGeometry().getInteriorPoint(); } return retPoint; } } }; var textStyle = new ol.style.Style(textStyleConfig); var style = new ol.style.Style(polyStyleConfig); return [style, textStyle]; } //*************helper functions this point forward***************//
function verifyClassFromVal(rangevals, val) { var retIndex = -1; for (var i = 0; i < rangevals.length; i++) { if (val >= rangevals[i].min && val <= rangevals[i].max) { retIndex = i; } } return retIndex; } /** * get the user selected method */ function getMethod(){ var elem = document.getElementById("methodselector"); var val = elem.options[elem.selectedIndex].value; return val; } /** * get the user selected number of classes */ function getClassNum(){ var elem = document.getElementById("classcount"); return parseInt(elem.value); } /** * convert hex to rgba * */ function hexToRgbA(hex,opacity) { var c; if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) { c = hex.substring(1).split(''); if (c.length == 3) { c = [c[0], c[0], c[1], c[1], c[2], c[2]]; } c = '0x' + c.join(''); return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ','+opacity+')'; } throw new Error('Bad Hex'); } /** * get the maximum polygon out of the supllied array of polygon * used for labeling the bigger one */ function getMaxPoly(polys) { var polyObj = []; //now need to find which one is the greater and so label only this for (var b = 0; b < polys.length; b++) { polyObj.push({ poly: polys[b], area: polys[b].getArea() }); } polyObj.sort(function(a, b) { return a.area - b.area }); return polyObj[polyObj.length - 1].poly; }
Enjoy all of you
This comment has been removed by the author.
ReplyDelete