Showing posts with label ol3. Show all posts
Showing posts with label ol3. Show all posts

Friday, December 15, 2017

Mapping Fear of Crime using Opelayers3

As part of my PHD research I had to create a WebGIS platform to present questionnaire answers for varius questions related to the Urban Fear Of Crime. 


The platform created under the Urban Fear of Crime Project supports two main goals through a user friendly, intuitive and easy to use interface. On the one hand, the collection of data by our online survey questionnaire and on the other, the analysis of submitted answers. Analyses can refer to one or more questions while their outcomes and results are provided as one or two-way tables and charts. Respectively, cartographic visualizations of variables (questions) can be 2D or 3D respectively in the form of point density or thematic maps at a respondent or country level. Filters can always be applied to data (answers and respondents) selecting subsets either by attribute or by dynamically drawing the geometric shape of the study area on the screen or finally, by uploading a respective schematic file (KML). Results can then be exported both in an image (png) or data (CSV, GeoJSN) file format. 

We are currently working on and we will soon be able to allow certified users (professors, teachers, researchers and students as well as practitioners) to set up their own research groups and teams whose submitted by their members data (completed questionnaires) they will have the ability to download in a CSV file in order to be further processed and analyzed for their own research purposes and projects.

I would greatly appreciate it if you could spend a few minutes of your time to fill our online survey questionnaire. Please be assured that all responses are anonymous and will strictly be used for the completion of my doctoral dissertation thesis.
Once you finish with questionnaire you may refresh the map webpage  and see your answers mapped.

For the completion of the project the following libraries has been used:


1. openlayers 3 . For the WebGis capabilities.
2. Chart.js. For the creation of Graphs and Charts
3. ol3-ext from Viglino.  Using two lib extensions as follows:
 3.1 OL3-AnimatedCluster. For drawing animated clusters on map
 3.2 ol.style.Chart . For drawing charts on map 
4. sidebar-v2 for ol3. Just a side bar over the map as a map control
5. Ol3-geocoder.  An ol3 geocoding control
6. Jquery + bootstrap for the UI.



Thursday, April 13, 2017

Open Layers 3 - Playing with Projections

I big issue when plotting GIS maps is the projection of your data. Open layers 3 gives much more flexibility, than Open layers 2 used to do, on that field. With the latest versions of OL3 you can even reproject rasters and tiles on the client side. This is awesome in it?
But how is it working?
Lets go through some example cases.

 Case 1.I am not a mapping geek. I just want to show a map.
This is the simplest case. Just load a tile layer and let ol3 to choose the projection. This would be the EPSG:3857  as most public tile layers services provide their tile data in EPSG:3857. This is OSM, Google, Bing map etc. So ,just to make clear, if you dont provide any projection during map config EPSG:3857 shall be used and any layer we add on map should be reprojected on that projection on client side if needed.

 ------> Here is an example (CASE1). And its fiddle here

 var map = new ol.Map({  
     layers: [  
      new ol.layer.Tile({  
       source: new ol.source.OSM()  
      })  
     ],  
     target: 'map',  
     view: new ol.View({  
      center: [0, 0],  
      zoom: 2  
     })  
    });  


 Case 2.I am a bit more advanced user. So I want to have control of the map projection
Now if you want to give your map a projection other than EPSG:3857 then you have to assign it to the ol.view of your map. In this case there are 2 sub cases.

   - subcase1. I want to project in EPSG:4326
In this subcase there is no special treatment. Just assign EPSG:4326 on the view of your map.


-------> Here is an example (CASE2 - subcase1). And its fiddle here

 var map = new ol.Map({  
  layers: [  
   new ol.layer.Tile({  
    source: new ol.source.OSM()  
   })  
  ],  
  target: 'map',  
  view: new ol.View({  
   projection:'EPSG:4326',//here is your 4326 projection
   center: [0, 0],  
   zoom: 2  
  })  
 });  

- subcase2. I want to project in a projection other than 4326 or 3857 and the projection code I am looking for exist in the epsg.io database.
In this subcase ol3 need an external library to go on. This is proj4js.js (further info and download here)
So we need to follow the steps below.
1. Load the external library within our html page
2. Search for our projection in the http://epsg.io/. Once we find the projection we are looking for  we go to Export --> Proj4js and then we do copy-paste the javascript definition of our projection.



3. Next step is to add the line of code we just copied to our javascript section.
So lets say we are looking for EPSG:2100 which is the National Greek Projection. Then we need to add this line of code:
 proj4.defs("EPSG:2100","+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=-199.87,74.79,246.62,0,0,0,0 +units=m +no_defs");  

So now ol3 is aware about our new projection. It would also be good to add the extent for our new projection, using the following

 var 2100extent= [-34387.6695, 3691163.5140, 1056496.8434, 4641211.3222];  
 proj.get('EPSG:2100').setExtent(2100extent);  

So lets put in all together now. We want our map to be projected on EPSG:2100 and thus all layers added on map to be reprojected on EPSG:2100


 var extent2100 = [-34387.6695, 3691163.5140, 1056496.8434, 4641211.3222];  
  proj4.defs("EPSG:2100","+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=-199.87,74.79,246.62,0,0,0,0 +units=m +no_defs");  
 ol.proj.get('EPSG:2100').setExtent(extent2100);  
 var layer = new ol.layer.Tile({  
     source: new ol.source.OSM()  
   });  
 var map = new ol.Map({  
  layers: [layer],  
  target: 'map',  
  view: new ol.View({  
   projection: 'EPSG:2100',  
    center: [510457.300375, 4150059.924838],  
   zoom: 4  
  })  
 });  


An here is a fiddle

What if I want to add a wms layer to the map with my custom projection???? Do I need to tell ol3 so reproject my wms layer?????

Well, the direct answer is no you dont have to. Unless you want the reprojection to take place on the server side. Which is not normal for myself. Reprojecting on the server side would stretch my server. So as long as I have the option to do it on client and so not heavy-load my server I prefer to do it on client.

So lets go back to the question. You just have to cofigure your WMS layer and add it to your map. Like so:


 var wmslayer = new ol.layer.Image({  
      source: new ol.source.ImageWMS({  
       url: 'http://suite.opengeo.org/geoserver/wms',  
       params: {'LAYERS': 'opengeo:countries'},  
       serverType: 'geoserver'  
      })  
     }) 
map.addLayer(wmslayer);


And here is one more fiddle to see it live

TO BE CONTINUED WITH FEW MORE CASES ..........

Thursday, December 8, 2016

Integrating OL3 with geostats.js to produce Thematic Maps for Socio-Economic Data

Lately, I had a requirement building a "thematic mapping" application using openlayers 3 in order to visualise socio-economic data. OL3 does not provide any functionality related to statistical mapping. So googling it, I fell into geostats .
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
So lets go for an example. Lets build a thematic population map for  world countries.

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

Monday, June 27, 2016

KML,GPX,GeoJson,WKT Online Parser and Viewer

Here is a simple online formats parser, I have recently develop, and I would like to share.
May handle KML,GPX,GeoJson,WKT formats. It is just an html file with a few javascript. May be freely downloaded from here.
You may view the online parser here (full screen)
Or get a snapshot

Friday, January 22, 2016

openlayers3 (ol3) Magnifying Control

Ispired from official ol3 Layer Spy example, I have made a magnifying custom control.
Make map cursor act as a magnifying lense. Click on M button
This ol3 control is also hosted on gitgub. Here is the link to downlaod (includes example and configuration options).
No dependency on third party libs. Just ol3.
Here is a fiddle

Monday, December 7, 2015

open layers new version 3.11.2

open layers version 3.11.2 launched.
release notes may be found here --> release notes
Latest version may be downloaded from here -->v3.11.2.zip.
Still wait, for the vector api, the donut polygon support (intermediate solution for donut polygon may be found here -->draw hole fiddle ) as well as the undo/redo interaction. Hope to get these on next version.
One of the great things with this latest version is the raster reprojection support. This includes tiles as well. That means you can view bing maps, OSM etc within your local projection. Or even display your local projection tiles to bing maps projection. Great thing .... in it?
Some examples to present this functionality may be found here
--> reprojection-by-code.html
--> reprojection-wgs84.html
--> raster reprojection.html
There is also a new version for the ol3-cesium based on this new open layers version.
ol3-cesium v1.10 may be downloaded from here -->ol3_cesium_v1.10
Good news is that OL3-Cesium is now built together with OL3. check it here 
Enjoy all of you GIS Dudes 

Monday, November 16, 2015

openlayers 3 - Layer Control/Switcher - Extjs

Lately , I had a requirement for a layer contol-switcher tool for openlayers 3 (OL3). The one currently exists here is good enough if you expect to get something like OL2 used to have. But in my case this wasn't enough.
So, I had to do it from scratch. Searching on the web for a fancy jquery tree , supporting drag-drop between tree nodes, grouping of tree nodes, check box support, image support etc, I was surprised I couldn't find something very impressive to use. So I ended up with extjs tree panel , which has all the needed functionality and it is quit impressive. Though, it is not jquery so extjs library needs to be loaded.

I have made a github project you may see and freely download here. Note that the project is on its very early stages and many things will probably change. Just download the rar file and open it. Double click in any example to see it in action.

If any of you, boys and girls, look forward to contribute please let me know. I would be more than grateful.

Some examples up and running to get the idea.

basic example:
http://ptsagkis.github.io/extjs_ol3_layercontrol/example.html
minimalistic example:
http://ptsagkis.github.io/extjs_ol3_layercontrol/example0_minimalistic.html
example with options:
http://ptsagkis.github.io/extjs_ol3_layercontrol/example1_withoptions.html
switching themes:
http://ptsagkis.github.io/extjs_ol3_layercontrol/example3_themes.html
legend icons from geoserver:
http://ptsagkis.github.io/extjs_ol3_layercontrol/example3.html
add wfs vector layer dynamic and asign loadstart, loadend, loaderror events 
http://ptsagkis.github.io/extjs_ol3_layercontrol/example4_dynamic_add_layer.html
use an extjs layout for best UI experience
http://ptsagkis.github.io/extjs_ol3_layercontrol/example5_extjslayout.html
change visibility programmticaly and fire the listener to toggle check box
http://ptsagkis.github.io/extjs_ol3_layercontrol/example6_vector.html



Tuesday, September 22, 2015

Openlayers 3 - load start / load end events on layers

With the latest versions of OL3 we have the ability to listen for the 'loadstart' and 'loadend' events on layers exist on our map. In openlayers 2 that was quite clear.

With the latest versions of  openlayers 3, things start to be a bit more clear, though not as clear as it used to be on OL2.

So for TILE (ol.layer.Tile) layers our code snip should look like:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//declare the layer
var osmLayer =  new ol.layer.Tile({
      source: new ol.source.OSM()
    });
//asign the listeners on the source of tile layer
osmLayer.getSource().on('tileloadstart', function(event) {
//replace with your custom action
document.getElementById('tilesloaderindicatorimg').src = 'css/images/tree_loading.gif';
     });

osmLayer.getSource().on('tileloadend', function(event) {
//replace with your custom action
document.getElementById('tilesloaderindicatorimg').src = 'css/images/ok.png';
     });
osmLayer.getSource().on('tileloaderror', function(event) {
//replace with your custom action        
document.getElementById('tilesloaderindicatorimg').src = 'css/images/no.png';
     });

For WMS layers the approach is a bit different:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//declare the layer
var wmsLayer =   new ol.layer.Image({
    source: new ol.source.ImageWMS({
      attributions: [new ol.Attribution({
        html: '&copy; ' +
            '<a href="http://www.geo.admin.ch/internet/geoportal/' +
            'en/home.html">' +
            'National parks / geo.admin.ch</a>'
      })],
      crossOrigin: 'anonymous',
      params: {'LAYERS': 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung'},
      serverType: 'mapserver',
      url: 'http://wms.geo.admin.ch/'
    })
  });

//and now asign the listeners on the source of it
 var lyrSource = wmsLayer.getSource();
      lyrSource.on('imageloadstart', function(event) {
      console.log('imageloadstart event',event);
      //replace with your custom action
      var elemId = event.target.params_.ELEMENTID;
      document.getElementById(elemId).src = 'css/images/tree_loading.gif'; 
      });

      lyrSource.on('imageloadend', function(event) {
       console.log('imageloadend event',event);
      //replace with your custom action
      var elemId = event.target.params_.ELEMENTID;
      document.getElementById(elemId).src = 'css/images/ok.png'; 
      });

      lyrSource.on('imageloaderror', function(event) {
       console.log('imageloaderror event',event);
      //replace with your custom action
      var elemId = event.target.params_.ELEMENTID;
      document.getElementById(elemId).src = 'css/images/no.png'; 
      });  

For WFS Vecotr layers things are even more complicated:

so I made a fiddle you may see here -->fiddle

fiddle preview -->

Urban Growth Lab - UGLab

Proud to release a first version of the UGLab project .  UGLab is a python open source project capable to execute Urban Growth predictions f...