Web Geo Data Vorehttps://webgeodatavore.com/2016-03-04T15:40:00+01:00Add GeoJSON content in QGIS: short recipes2016-03-04T15:40:00+01:00Thomas Gratiertag:webgeodatavore.com,2016-03-04:add-geojson-content-in-qgis-short-recipes.html<h1>Add simply GeoJSON dataset from QGIS</h1>
<h2>Recipe 1: add QGIS built-in data</h2>
<p>You can reuse the QGIS contributors map dataset directly from QGIS without any downloading using PyQGIS. It's a GeoJSON file available within your installed program.</p>
<p>Just run in QGIS Python console the following</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">qgis.core</span> <span class="kn">import</span> <span class="n">QgsApplication</span>
<span class="n">geojson_contributors</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">QgsApplication</span><span class="o">.</span><span class="n">developersMapFilePath</span><span class="p">()),</span>
<span class="s">'contributors.json'</span>
<span class="p">)</span>
<span class="k">print</span> <span class="n">geojson_contributors</span>
<span class="n">iface</span><span class="o">.</span><span class="n">addVectorLayer</span><span class="p">(</span><span class="n">geojson_contributors</span><span class="p">,</span> <span class="s">'QGIS contributors'</span><span class="p">,</span> <span class="s">'ogr'</span><span class="p">)</span>
</pre></div>
<h2>Recipe 2: add remote GeoJSON</h2>
<p>This is quite handy to get the remote data. You need to be aware that it's a good solution when remote content change or for demo. However, it's not always a good idea for production where your data can stop being available.</p>
<ul>
<li>Try to do it by going to the website <a href="http://geojson.xyz">geojson.xyz</a> to get an URL data source.</li>
</ul>
<p>The data are coming from <a href="http://www.naturalearthdata.com/">Natural Earth Data</a>, the main advantage here is the fact GeoJSON file are delivered via a CDN (Content Delivery Network) whose goal is according to <a href="https://en.wikipedia.org/wiki/Content_delivery_network">Wikipedia definition</a></p>
<blockquote>
<p>to serve content to end-users with high availability and high performance</p>
</blockquote>
<ul>
<li>Choose a layer source</li>
<li>Copy it URL</li>
</ul>
<p>Now, you've got an URL to a remote GeoJSON, you can add the GeoJSON from the UI or via PyQGIS.</p>
<h3>From the GUI</h3>
<p>The following screencast illustrates the way to add remote GeoJSON from GUI</p>
<p><img alt="Add remote GeoJSON from QGIS" src="/images/qgis-add-remote-geojson.gif" /></p>
<p>You can't choose the layer name when you load the data source: it's the default one.</p>
<h3>From the QGIS Python console</h3>
<p>The solution throught the QGIS Python console is just this one liner. You just need to change the URL.</p>
<div class="highlight"><pre><span class="n">iface</span><span class="o">.</span><span class="n">addVectorLayer</span><span class="p">(</span><span class="s">'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_populated_places.geojson'</span><span class="p">,</span> <span class="s">'Populated places'</span><span class="p">,</span> <span class="s">'ogr'</span><span class="p">)</span>
</pre></div>Add GeoJSON content in QGIS: short recipes2016-03-04T15:40:00+01:00Thomas Gratiertag:webgeodatavore.com,2016-03-04:en/add-geojson-content-in-qgis-short-recipes.html<h1>Add simply GeoJSON dataset from QGIS</h1>
<h2>Recipe 1: add QGIS built-in data</h2>
<p>You can reuse the QGIS contributors map dataset directly from QGIS without any downloading using PyQGIS. It's a GeoJSON file available within your installed program.</p>
<p>Just run in QGIS Python console the following</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">qgis.core</span> <span class="kn">import</span> <span class="n">QgsApplication</span>
<span class="n">geojson_contributors</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">QgsApplication</span><span class="o">.</span><span class="n">developersMapFilePath</span><span class="p">()),</span>
<span class="s">'contributors.json'</span>
<span class="p">)</span>
<span class="k">print</span> <span class="n">geojson_contributors</span>
<span class="n">iface</span><span class="o">.</span><span class="n">addVectorLayer</span><span class="p">(</span><span class="n">geojson_contributors</span><span class="p">,</span> <span class="s">'QGIS contributors'</span><span class="p">,</span> <span class="s">'ogr'</span><span class="p">)</span>
</pre></div>
<h2>Recipe 2: add remote GeoJSON</h2>
<p>This is quite handy to get the remote data. You need to be aware that it's a good solution when remote content change or for demo. However, it's not always a good idea for production where your data can stop being available.</p>
<ul>
<li>Try to do it by going to the website <a href="http://geojson.xyz">geojson.xyz</a> to get an URL data source.</li>
</ul>
<p>The data are coming from <a href="http://www.naturalearthdata.com/">Natural Earth Data</a>, the main advantage here is the fact GeoJSON file are delivered via a CDN (Content Delivery Network) whose goal is according to <a href="https://en.wikipedia.org/wiki/Content_delivery_network">Wikipedia definition</a></p>
<blockquote>
<p>to serve content to end-users with high availability and high performance</p>
</blockquote>
<ul>
<li>Choose a layer source</li>
<li>Copy it URL</li>
</ul>
<p>Now, you've got an URL to a remote GeoJSON, you can add the GeoJSON from the UI or via PyQGIS.</p>
<h3>From the GUI</h3>
<p>The following screencast illustrates the way to add remote GeoJSON from GUI</p>
<p><img alt="Add remote GeoJSON from QGIS" src="/images/qgis-add-remote-geojson.gif" /></p>
<p>You can't choose the layer name when you load the data source: it's the default one.</p>
<h3>From the QGIS Python console</h3>
<p>The solution throught the QGIS Python console is just this one liner. You just need to change the URL.</p>
<div class="highlight"><pre><span class="n">iface</span><span class="o">.</span><span class="n">addVectorLayer</span><span class="p">(</span><span class="s">'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_populated_places.geojson'</span><span class="p">,</span> <span class="s">'Populated places'</span><span class="p">,</span> <span class="s">'ogr'</span><span class="p">)</span>
</pre></div>Ajout de services CSW pour chercher des données depuis QGIS2016-02-03T19:20:00+01:00Thomas Gratiertag:webgeodatavore.com,2016-02-03:ajout-services-csw-dans-qgis.html
<p>Même si on peut passer par des sites web pour rechercher des données, il devient de plus en plus facile de les rechercher directement depuis QGIS. On peut s'appuyer sur le format CSW pour cela.</p>
<p>Nous ne rentrerons pas dans les détails du fonctionnement du standard CSW en lui-même mais simplement sur comment nous avons récupéré une liste de serveurs CSW pour la France que vous pouvez réutiliser, adapter à vos besoins.</p>
<h2>Installation du plugin "MetaSearch Catalogue Client"</h2>
<p>Pour pouvoir chercher des données CSW, il faut installer le plugin en allant dans le menu "Extension" > "Installer/Gérer les extensions". Chercher avec le terme "CSW" ou bien le nom exact du plugin "MetaSearch Catalogue Client". Vous devriez voir un menu "Internet". Allez dans le sous-menu "MetaSearch" puis dans "MetaSearch".</p>
<p>Vous allez à partir de là pouvoir suivre <a href="http://docs.qgis.org/2.8/fr/docs/user_manual/plugins/plugins_metasearch.html">le tutoriel officiel en Français du plugin</a></p>
<h2>Récupération de serveurs "France"</h2>
<p>La première partie ne montre pas l'intérêt du billet de blog mais les prérequis. La valeur qu'on vous apporte ici est de vous mettre à disposition une liste de points d'entrée CSW spécifiques à la France.</p>
<p>En effet, il existe <a href="https://inspire.data.gouv.fr/">un portail</a> des différents serveurs INSPIRE qui sert à communiquer avec la plateforme http://data.gouv.fr. Il met à disposition une liste de serveurs CSW. Nous avons donc récupéré cette liste pour que vous puissiez directement avoir un fichier compatible avec le plugin "MetaSearch Catalogue Client" et directement importable. Pour le code qui récupère les données, allez sur <a href="https://gist.github.com/ThomasG77/7c7510074ef1aeb34578/">ce GIST</a>.</p>
<p>Pour télécharger la liste promise, <a href="https://gist.githubusercontent.com/ThomasG77/7c7510074ef1aeb34578/raw/ff26c2c73af176d2091da96899f7fa08e99557cc/inspire_data_gouv_fr.xml">suivez ce lien</a> et enregistrez le fichier.</p>
<p>Attention, même si l'approche proposée facilite la vie, il reste encore beaucoup de cas où on a des problèmes avec les serveurs "en face" ou bien où on est redirigé vers des pages web plutôt que de pouvoir récupérer la données directement. I lfaudrait à la fois améliorer le plugin et à la fois corrigé des erreurs côté serveurs CSW consommés.</p>
<p>Dans ce cas, passez directement par le site <a href="https://inspire.data.gouv.fr/services/by-protocol/csw">https://inspire.data.gouv.fr partie CSW</a></p>
<h2>Questions/remarques</h2>
<p>Si vous avez des remarques ou des idées pour enrichir cette première approche, <a href="http://webgeodatavore.com/contact.html">contactez-nous</a>.</p>
<p>Si vous avez un besoin professionnel, nous assurons du support, du conseil et de la formation sur QGIS et le développement de plugins QGIS Python. Par ailleurs, nous pouvons vous aider sur les données ouvertes (OpenStreetMap, portails publics,...), l'architecture et le développement de solutions d'applications cartographiques en ligne (client et serveur).</p>Create QGIS curves from Python API2016-01-26T17:30:00+01:00Thomas Gratiertag:webgeodatavore.com,2016-01-26:create-qgis-curve-from-python-api.html
<p>This article was inspired by the question by a discussion about new QGIS geometries due to the publication of a diagram about main QGIS Python classes.</p>
<p>We choose to demonstrate how you can build a curve with three points using the new QGIS geometry API.</p>
<div style="text-align:center"><img src="/images/qgis-curve-demo.jpg" alt="QGIS curve demo" title="QGIS curve demo" /></div>
<div style="clear:both;"></div>
<p>I had some months ago to make a representation for <a href="https://rawgit.com/ThomasG77/datadouane_raw/master/flux_vin_export_mars_2015.html">import/export flux for wines</a>.
In this case, I choose to use <a href="https://github.com/springmeyer/arc.js">arc.js</a> through Node to generate the curves lines.</p>
<p>As I had published another document to display <a href="https://github.com/webgeodatavore/qgis-class-diagram">main parts of the QGIS Python API</a> in another context, I got a feedback from a QGIS dev about using the "old" geometry way within QGIS.</p>
<blockquote class="twitter-tweet" lang="fr"><p lang="en" dir="ltr"><a href="https://twitter.com/ThomasG77">@ThomasG77</a> <a href="https://twitter.com/antoniolocandro">@antoniolocandro</a> Ok - I noticed the geometry classes are out of date in that diagram since >= 2.10. See <a href="https://t.co/GdFsDSopRE">https://t.co/GdFsDSopRE</a></p>— Nyall Dawson (@nyalldawson) <a href="https://twitter.com/nyalldawson/status/677230130129801216">16 Décembre 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Due to this previous experiments, I choose to investigate how to create curves using QGIS with the new QGIS geometry API.</p>
<p>You will find an example to use the new geometry related class <code>QgsCircularStringV2</code> to display curves within QGIS.</p>
<div class="highlight"><pre><span class="c"># Some import are optional depending of the context (code executed through the console or via a plugin)</span>
<span class="kn">from</span> <span class="nn">qgis.core</span> <span class="kn">import</span> <span class="n">QgsCircularStringV2</span><span class="p">,</span> <span class="n">QgsPointV2</span><span class="p">,</span> <span class="n">QgsGeometry</span><span class="p">,</span> <span class="n">QgsVectorLayer</span><span class="p">,</span> <span class="n">QgsMapLayerRegistry</span>
<span class="kn">from</span> <span class="nn">qgis.utils</span> <span class="kn">import</span> <span class="n">iface</span>
<span class="c"># Begin a new project</span>
<span class="n">iface</span><span class="o">.</span><span class="n">newProject</span><span class="p">()</span>
<span class="c"># Create a QgsCircularStringV2</span>
<span class="n">circularRing</span> <span class="o">=</span> <span class="n">QgsCircularStringV2</span><span class="p">()</span>
<span class="c"># Set first point, intermediate point for curvature and end point</span>
<span class="n">circularRing</span><span class="o">.</span><span class="n">setPoints</span><span class="p">([</span>
<span class="n">QgsPointV2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
<span class="n">QgsPointV2</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
<span class="n">QgsPointV2</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">)]</span>
<span class="p">)</span>
<span class="c"># Check if the geometry has curves</span>
<span class="k">print</span> <span class="n">circularRing</span><span class="o">.</span><span class="n">hasCurvedSegments</span><span class="p">()</span>
<span class="c"># Create geometry using the instance of QgsCircularStringV2</span>
<span class="n">geom_from_curve</span> <span class="o">=</span> <span class="n">QgsGeometry</span><span class="p">(</span><span class="n">circularRing</span><span class="p">)</span>
<span class="c"># Create a feature</span>
<span class="n">fet</span> <span class="o">=</span> <span class="n">QgsFeature</span><span class="p">()</span>
<span class="c"># Assign the geometry</span>
<span class="n">fet</span><span class="o">.</span><span class="n">setGeometry</span><span class="p">(</span><span class="n">geom_from_curve</span><span class="p">)</span>
<span class="c"># Create a memory layer</span>
<span class="n">layer</span> <span class="o">=</span> <span class="n">QgsVectorLayer</span><span class="p">(</span>
<span class="s">"LineString?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"</span><span class="p">,</span>
<span class="s">"temporary_points"</span><span class="p">,</span>
<span class="s">"memory"</span>
<span class="p">)</span>
<span class="c"># Add the layer</span>
<span class="n">QgsMapLayerRegistry</span><span class="o">.</span><span class="n">instance</span><span class="p">()</span><span class="o">.</span><span class="n">addMapLayer</span><span class="p">(</span><span class="n">layer</span><span class="p">)</span>
<span class="c"># Add the feature to the layer provider</span>
<span class="n">pr</span> <span class="o">=</span> <span class="n">layer</span><span class="o">.</span><span class="n">dataProvider</span><span class="p">()</span>
<span class="n">pr</span><span class="o">.</span><span class="n">addFeatures</span><span class="p">([</span><span class="n">fet</span><span class="p">])</span>
<span class="c"># Update extent</span>
<span class="n">layer</span><span class="o">.</span><span class="n">updateExtents</span><span class="p">()</span>
<span class="c"># Zoom to extent</span>
<span class="n">iface</span><span class="o">.</span><span class="n">mapCanvas</span><span class="p">()</span><span class="o">.</span><span class="n">setExtent</span><span class="p">(</span><span class="n">layer</span><span class="o">.</span><span class="n">extent</span><span class="p">())</span>
<span class="n">iface</span><span class="o">.</span><span class="n">mapCanvas</span><span class="p">()</span><span class="o">.</span><span class="n">refresh</span><span class="p">()</span>
</pre></div>
<p>I didn't inspect thoroughly how to manage the curves bending. Feel free to try out. Any feedback is welcome!</p>
<p>You can look at <a href="http://changelog.linfiniti.com/en/qgis/2.12.0/entry/tool-to-add-circular-strings-with-start-point-curv/">this new tool</a> that landed in QGIS 2.12 to edit curves.</p>
<p>Finally, you can also look for a database oriented solution to use curves on <a href="http://gis.stackexchange.com/questions/5204/curved-point-to-point-route-maps">Geographic Information Systems Stack Exchange</a></p>Edit GeoJSON without QGIS native support2015-11-05T10:30:00+01:00Thomas Gratiertag:webgeodatavore.com,2015-11-05:edit-geojson-without-qgis-native-support.html
<p>This article was inspired by the question below on Twitter from <a href="https://twitter.com/kelsosCorner">Nathaniel V. KELSO</a>, the main guy behind "Natural Earth Data" ;) (Thanks for your work!)</p>
<blockquote class="twitter-tweet" lang="fr"><p lang="en" dir="ltr">¿Is it possible to live edit a GeoJSON file in <a href="https://twitter.com/hashtag/QGIS?src=hash">#QGIS</a>? I can read them in and export them, but the yellow pencil won't engage.</p>— Nathaniel V. KELSO (@kelsosCorner) <a href="https://twitter.com/kelsosCorner/status/662317579394912257">5 Novembre 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>We choose to demonstrate a way to make GeoJSON content editable possible via a workaround.</p>
<p>OGR/GDAL, the library used by QGIS to read geographic data, does not support GeoJSON update (a crowdfounding to pay someone to do could be a good idea IMO!)</p>
<p>The workflow most people uses: Save GeoJSON as shapefile, edit the shapefile, save edit, convert new shapefile to GeoJSON, then repeat for each edit.
In simple case, they use <a href="http://geojson.io">GeoJSON.io website</a></p>
<p>One alternate and simpler way to manage this issue is to use "Memory provider" layer.
We quote the Python QGIS cookbook to define it.</p>
<blockquote>
<p>Memory provider is intended to be used mainly by plugin or 3rd party app developers.
It does not store data on disk, allowing developers to use it as a fast backend for some temporary layers.</p>
</blockquote>
<p>The big picture is to transfer content from GeoJSON to this type of layer, editable (contrary to GeoJSON), as a workaround.</p>
<p>We can copy the GeoJSON content to a Memory provider Layer with below code (go to <code>Extension</code> > <code>Python console</code> and copy/paste)</p>
<div class="highlight"><pre><span class="c"># Select the GeoJSON layer in the legend then get the current active layer</span>
<span class="n">layer</span> <span class="o">=</span> <span class="n">qgis</span><span class="o">.</span><span class="n">utils</span><span class="o">.</span><span class="n">iface</span><span class="o">.</span><span class="n">activeLayer</span><span class="p">()</span>
<span class="c"># Get info from the GeoJSON layer to reuse later</span>
<span class="n">provider</span> <span class="o">=</span> <span class="n">layer</span><span class="o">.</span><span class="n">dataProvider</span><span class="p">()</span>
<span class="n">fields</span> <span class="o">=</span> <span class="n">provider</span><span class="o">.</span><span class="n">fields</span><span class="p">()</span>
<span class="c"># Specify layer type for editing GeoJSON content</span>
<span class="c"># It can be "Point", "LineString", "Polygon", "MultiPoint",</span>
<span class="c"># "MultiLineString", or "MultiPolygon". Forget about mixed geometries here</span>
<span class="n">type_layer</span> <span class="o">=</span> <span class="s">'Point'</span>
<span class="c"># Create layer</span>
<span class="n">vl</span> <span class="o">=</span> <span class="n">QgsVectorLayer</span><span class="p">(</span><span class="s">"</span><span class="si">%s</span><span class="s">?crs=epsg:4326&index=yes"</span> <span class="o">%</span> <span class="n">type_layer</span><span class="p">,</span> <span class="s">"temporary_points"</span><span class="p">,</span> <span class="s">"memory"</span><span class="p">)</span>
<span class="n">pr</span> <span class="o">=</span> <span class="n">vl</span><span class="o">.</span><span class="n">dataProvider</span><span class="p">()</span>
<span class="c"># Prepare fields to copy fields from GeoJSON layer to "Memory provider" layer</span>
<span class="n">new_fields</span> <span class="o">=</span> <span class="p">[</span><span class="n">QgsField</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="p">(),</span> <span class="n">f</span><span class="o">.</span><span class="n">type</span><span class="p">(),</span> <span class="n">f</span><span class="o">.</span><span class="n">typeName</span><span class="p">(),</span> <span class="n">f</span><span class="o">.</span><span class="n">length</span><span class="p">(),</span> \
<span class="n">f</span><span class="o">.</span><span class="n">precision</span><span class="p">(),</span> <span class="n">f</span><span class="o">.</span><span class="n">comment</span><span class="p">())</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">fields</span><span class="p">]</span> <span class="c"># Python Comprehension list</span>
<span class="c"># Copy attribute to the "Memory provider" layer</span>
<span class="n">pr</span><span class="o">.</span><span class="n">addAttributes</span><span class="p">(</span><span class="n">new_field</span><span class="p">)</span>
<span class="n">vl</span><span class="o">.</span><span class="n">updateFields</span><span class="p">()</span>
<span class="n">pr</span><span class="o">.</span><span class="n">addFeatures</span><span class="p">([</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">provider</span><span class="o">.</span><span class="n">getFeatures</span><span class="p">()])</span>
<span class="c"># Update extent</span>
<span class="n">vl</span><span class="o">.</span><span class="n">updateExtents</span><span class="p">()</span>
<span class="c"># Add layer to current canvas</span>
<span class="n">QgsMapLayerRegistry</span><span class="o">.</span><span class="n">instance</span><span class="p">()</span><span class="o">.</span><span class="n">addMapLayer</span><span class="p">(</span><span class="n">vl</span><span class="p">)</span>
</pre></div>
<p>To avoid to do this operation in the Python console each time for the same layer, you have to save your current project.
Then, you can save the not so temporary "Memory provider" layer with extension <a href="http://plugins.qgis.org/plugins/MemoryLayerSaver/">"Memory plugin saver"</a> in your current project and reopen it later.
You can now edit the "Memory provider" layer with the "GeoJSON content as you wish and if you really want GeoJSON again, just do a "Save as" right clicking on the temporary layer legend to overwrite your GeoJSON layer.</p>
<p>The "save, edit" workflow is working like if you were using a shapefile in QGIS.</p>
<p>If you need remote editing, a possibility could be to create a button to update the file if a web service to update remote content was available.</p>
<p>Enjoy and feel free to make us a feedback here or via <a href="http://twitter.com/thomasg77">Twitter</a>.</p>
<p>PS: As stated by <a href="https://twitter.com/antoniolocandro/status/662376146739269632">Antonio Locandro</a>, it could be also possible to code to adapt the sample plugin for editing CSV from <a href="http://spatialgalaxy.net/2015/03/13/faking-a-data-provider-with-python/">"Faking a Data Provider with Python"</a> for GeoJSON. We just didn't want to code here ;)</p>Python geocoders clients comparison2015-03-12T10:30:00+01:00Thomas Gratiertag:webgeodatavore.com,2015-03-12:python-geocoders-clients-comparison.html
<p>In Python ecosystem, there are few libraries for call calling geocoding web services. Sometimes, directly using raw Python instead can be faster.</p>
<p>Until recently, <code>GeoPy</code> was the library for the job but we recently found a new project called <code>Geocoder</code> and where wondering why it seems to do the same job. We didn't really found a clue but choose instead to compare both solutions.</p>
<p>Let's see how they differ.</p>
<p><em>Repositories</em></p>
<ul>
<li><a href="https://github.com/DenisCarriere/geocoder"><code>Geocoder</code></a></li>
<li><a href="https://github.com/geopy/geopy"><code>GeoPy</code></a></li>
</ul>
<h2>Python version support:</h2>
<p><strong>Winner</strong>: <code>GeoPy</code></p>
<p><strong>Explanation</strong>: <code>GeoPy</code> supports CPython 2.7, CPython 3.2, CPython 3.4, PyPy, and PyPy3 whereas <code>Geocoder</code> supports "only" CPython 2.7 and 3.4.</p>
<h2>Command Line</h2>
<p><strong>Winner</strong>: <code>Geocoder</code></p>
<p><strong>Explanation</strong>: There is none on the <code>GeoPy</code> side.</p>
<h2>Formats support</h2>
<p><strong>Winner</strong>: <code>Geocoder</code></p>
<p><strong>Explanation</strong>: Better because there are WKT and GeoJSON output whereas only in Nominatim in <code>GeoPy</code>.
Moreover, you can pipe results, combined with command line abilities.</p>
<h2>Confidence</h2>
<p><strong>Draw</strong></p>
<p><strong>Explanation</strong>: in fact, it's not really library dependend but it varies from each provider.</p>
<h2>Providers</h2>
<p><strong>Draw</strong></p>
<p><strong>Explanation</strong>: It depends of the choosen/required geocoder(s)</p>
<p>There are 17 providers on <code>GeoPy</code> and 18 providers on <code>Geocoder</code> (included one nearly obsolete, TomTom)
We didn't inspect deeper the reverse-geocoding abilities but both are available.</p>
<p>You can see a summary below.</p>
<table style="border:1px solid black;border-collapse:collapse;">
<tr><th style="border:1px solid black;">Geocoder</th><th style="border:1px solid black;">GeoPy</th></tr>
<tr><td style="border:1px solid black;">ArcGIS</td><td style="border:1px solid black;">ArcGIS</td></tr>
<tr><td style="border:1px solid black;">Baidu Maps</td><td style="border:1px solid black;">Baidu Maps</td></tr>
<tr><td style="border:1px solid black;">Bing</td><td style="border:1px solid black;">Bing</td></tr>
<tr><td style="border:1px solid black;">CanadaPost</td><td style="border:1px solid black;"> </td></tr>
<tr><td style="border:1px solid black;">FreeGeoIP</td><td style="border:1px solid black;"> </td></tr>
<tr><td style="border:1px solid black;"> </td><td style="border:1px solid black;">GeocodeFarm</td></tr>
<tr><td style="border:1px solid black;">Geocoder.ca</td><td style="border:1px solid black;"> </td></tr>
<tr><td style="border:1px solid black;"> </td><td style="border:1px solid black;">geocoder.us</td></tr>
<tr><td style="border:1px solid black;">Geonames</td><td style="border:1px solid black;">Geonames</td></tr>
<tr><td style="border:1px solid black;">GeoOttawa</td><td style="border:1px solid black;"> </td></tr>
<tr><td style="border:1px solid black;">Google</td><td style="border:1px solid black;">Google</td></tr>
<tr><td style="border:1px solid black;">HERE</td><td style="border:1px solid black;"> </td></tr>
<tr><td style="border:1px solid black;"> </td><td style="border:1px solid black;">IGN France</td></tr>
<tr><td style="border:1px solid black;">MapQuest</td><td style="border:1px solid black;">MapQuest</td></tr>
<tr><td style="border:1px solid black;">MaxMind</td><td style="border:1px solid black;">MaxMind</td></tr>
<tr><td style="border:1px solid black;"> </td><td style="border:1px solid black;">NaviData</td></tr>
<tr><td style="border:1px solid black;">OpenCage</td><td style="border:1px solid black;">OpenCage</td></tr>
<tr><td style="border:1px solid black;">OpenStreetMap</td><td style="border:1px solid black;">OpenStreetMap</td></tr>
<tr><td style="border:1px solid black;"> </td><td style="border:1px solid black;">SmartyStreets</td></tr>
<tr><td style="border:1px solid black;">TomTom (dead so not useful anymore except for "old" TomTom clients e.g <a href="https://geocoder.tomtom.com">https://geocoder.tomtom.com</a></td></tr>
<tr><td style="border:1px solid black;">What3Words</td><td style="border:1px solid black;">What3Words</td></tr>
<tr><td style="border:1px solid black;">Yahoo</td><td style="border:1px solid black;">Yahoo</td></tr>
<tr><td style="border:1px solid black;">Yandex</td><td style="border:1px solid black;">Yandex</td></tr>
</table>
<h2>Niceties</h2>
<p>On both side, you can find some advantages.</p>
<p>On <code>GeoPy</code>, you can calculate distance between points using Haversine formula.</p>
<p>On <code>Geocoder</code>, you get for free the GeoIP support using <code>FreeGeoIP</code> and <code>MaxMind</code>. You can determine your location from your IP or get an IP for a location. You can also manage <em>Elevation</em> with the <code>Google Elevation API</code> and find Time Zone</p>
<p>Both libraries can use proxies or autocompletion on returned results (using <code>raw</code> on GeoPy) when using IPython.</p>
<h2>Conclusion</h2>
<p>In our opinion, <code>GeoPy</code> really focus on (reverse)geocoding only whereas <code>Geocoder</code> is also about including more than the usual geocoding functions, in particular with the command line support.</p>
<p>We cannot choose a winner, both projects are too interesting.</p>
<p>Feel free to comment if any mistake was made on our side or you think some additions can be useful.</p>
<p>PS: Due to <a href="https://twitter.com/DenisCarriere/status/577839127732871168">feedback</a>, we updated supported geocoders list.</p>Discover how to use jq, a JSON manipulation command line, with GeoJSON2015-03-12T10:20:00+01:00Thomas Gratiertag:webgeodatavore.com,2015-03-12:jq-json-manipulation-command-line-with-geojson.html
<p>Mostly, when we need to manipulate GeoJSON, we do it using Python or Node.js/io.js.</p>
<p>Sometimes, it can be interesting to manipulate GeoJSON from command line. We will see how along the blog post.</p>
<p>Do not hesitate to make us feedback about content.</p>
<p>The shorter description of jq is as below :</p>
<blockquote>
<p>jq is a lightweight and flexible command-line JSON processor.</p>
</blockquote>
<p>If you are a command line addict, you will like the official description</p>
<blockquote>
<p>jq is like sed for JSON data – you can use it to slice and filter and map and transform structured data with the same ease that sed, awk, grep and friends let you play with text.</p>
</blockquote>
<p>As an addict to geo data, we frequently need to use <a href="http://geojson.org">GeoJSON</a>, a subset of JSON for geographic objects.</p>
<p>So, let's try using a GeoJSON file <code>countries.geojson</code>. Here, we will only play with <code>FeatureCollection</code>.</p>
<p>For all commands, we consider you are using a <em>Unix-like</em> system. If you are on Windows, you will have to deal on your own with characters escaping.</p>
<p>Let's start!</p>
<h2>Get the data</h2>
<div class="highlight"><pre>wget https://raw.github.com/datasets/geo-boundaries-world-110m/master/countries.geojson
</pre></div>
<h2>Count GeoJSON features</h2>
<div class="highlight"><pre>jq <span class="s1">'.features |length'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre>177
</pre></div>
<h2>Combine both result from two keys without filtering features</h2>
<div class="highlight"><pre>jq <span class="s1">'{type: .type , features: .features}'</span> countries.geojson
</pre></div>
<p>Return the same content as countries.geojson</p>
<h2>Filter on countries</h2>
<div class="highlight"><pre>jq <span class="s1">'{type: .type , features: [ .features[]| select( .properties.sovereignt == "France") ] }'</span> countries.geojson
</pre></div>
<p>Return a GeoJSON string where only features with <code>sovereignt</code> property equal "France" are kept.</p>
<h2>Get properties keys</h2>
<div class="highlight"><pre>jq <span class="s1">'.features[0:1][0].properties |keys'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre><span class="o">[</span>
<span class="s2">"abbrev"</span>,
<span class="s2">"abbrev_len"</span>,
<span class="s2">"adm0_a3"</span>,
<span class="s2">"adm0_a3_is"</span>,
<span class="s2">"adm0_a3_un"</span>,
<span class="s2">"adm0_a3_us"</span>,
<span class="s2">"adm0_a3_wb"</span>,
<span class="s2">"adm0_dif"</span>,
<span class="s2">"admin"</span>,
<span class="s2">"brk_a3"</span>,
<span class="s2">"brk_diff"</span>,
<span class="s2">"brk_group"</span>,
<span class="s2">"brk_name"</span>,
<span class="s2">"continent"</span>,
<span class="s2">"economy"</span>,
<span class="s2">"featurecla"</span>,
<span class="s2">"fips_10"</span>,
<span class="s2">"formal_en"</span>,
<span class="s2">"formal_fr"</span>,
<span class="s2">"gdp_md_est"</span>,
<span class="s2">"gdp_year"</span>,
<span class="s2">"geou_dif"</span>,
<span class="s2">"geounit"</span>,
<span class="s2">"gu_a3"</span>,
<span class="s2">"homepart"</span>,
<span class="s2">"income_grp"</span>,
<span class="s2">"iso_a2"</span>,
<span class="s2">"iso_a3"</span>,
<span class="s2">"iso_n3"</span>,
<span class="s2">"labelrank"</span>,
<span class="s2">"lastcensus"</span>,
<span class="s2">"level"</span>,
<span class="s2">"long_len"</span>,
<span class="s2">"mapcolor13"</span>,
<span class="s2">"mapcolor7"</span>,
<span class="s2">"mapcolor8"</span>,
<span class="s2">"mapcolor9"</span>,
<span class="s2">"name"</span>,
<span class="s2">"name_alt"</span>,
<span class="s2">"name_len"</span>,
<span class="s2">"name_long"</span>,
<span class="s2">"name_sort"</span>,
<span class="s2">"note_adm0"</span>,
<span class="s2">"note_brk"</span>,
<span class="s2">"pop_est"</span>,
<span class="s2">"pop_year"</span>,
<span class="s2">"postal"</span>,
<span class="s2">"region_un"</span>,
<span class="s2">"region_wb"</span>,
<span class="s2">"scalerank"</span>,
<span class="s2">"sov_a3"</span>,
<span class="s2">"sovereignt"</span>,
<span class="s2">"su_a3"</span>,
<span class="s2">"su_dif"</span>,
<span class="s2">"subregion"</span>,
<span class="s2">"subunit"</span>,
<span class="s2">"tiny"</span>,
<span class="s2">"type"</span>,
<span class="s2">"un_a3"</span>,
<span class="s2">"wb_a2"</span>,
<span class="s2">"wb_a3"</span>,
<span class="s2">"wikipedia"</span>,
<span class="s2">"woe_id"</span>
<span class="o">]</span>
</pre></div>
<h2>When you may need to add CRS</h2>
<div class="highlight"><pre>jq --arg crs <span class="s1">'{"type": "name", "properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}}'</span> <span class="s1">'{type: .type , crs: $crs|fromjson, features: [ .features[]| select( .properties.sovereignt == "France") ] }'</span> countries.geojson
</pre></div>
<p>Return same content as <code>countries.geojson</code> but with additional CRS </p>
<h2>Get min value (if numeric)</h2>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.pop_est] | min'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre>-99
</pre></div>
<p>In fact, -99 means "no data"</p>
<h2>Get max value</h2>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.pop_est] | max'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre>1338612970
</pre></div>
<h2>Get unique values (return array)</h2>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.economy] | unique'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre><span class="o">[</span>
<span class="s2">"1. Developed region: G7"</span>,
<span class="s2">"2. Developed region: nonG7"</span>,
<span class="s2">"3. Emerging region: BRIC"</span>,
<span class="s2">"4. Emerging region: MIKT"</span>,
<span class="s2">"5. Emerging region: G20"</span>,
<span class="s2">"6. Developing region"</span>,
<span class="s2">"7. Least developed region"</span>
<span class="o">]</span>
</pre></div>
<h2>Get unique (return list, useful to pipe with other unix command)</h2>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.economy] | unique[]'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre><span class="s2">"1. Developed region: G7"</span>
<span class="s2">"2. Developed region: nonG7"</span>
<span class="s2">"3. Emerging region: BRIC"</span>
<span class="s2">"4. Emerging region: MIKT"</span>
<span class="s2">"5. Emerging region: G20"</span>
<span class="s2">"6. Developing region"</span>
<span class="s2">"7. Least developed region"</span>
</pre></div>
<h2>Sort only value, not the object (return array)</h2>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.pop_est] | sort'</span> countries.geojson
</pre></div>
<p>Return</p>
<div class="highlight"><pre><span class="o">[</span>
-99,
140,
3140,
3802,
57600,
…
…
198739269,
240271522,
313973000,
1166079220,
1338612970
<span class="o">]</span>
</pre></div>
<h2>Sort only value, not the object (return list, useful to pipe with other unix commands)</h2>
<p>Ten minimum values with :</p>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.pop_est] | sort[]'</span> countries.geojson | head
</pre></div>
<p>Return</p>
<div class="highlight"><pre>-99
140
3140
3802
57600
218519
227436
265100
306694
307899
</pre></div>
<p>Ten maximum values with :</p>
<div class="highlight"><pre>jq <span class="s1">'[.features[].properties.pop_est] | sort[]'</span> countries.geojson | tail
</pre></div>
<p>Return</p>
<div class="highlight"><pre>127078679
140041247
149229090
156050883
176242949
198739269
240271522
313973000
1166079220
1338612970
</pre></div>
<h2>Alternatives to manipulate JSON with command line</h2>
<ul>
<li><a href="https://github.com/ddopson/underscore-cli">Underscore-Cli</a></li>
</ul>
<p>You can see <a href="https://github.com/ddopson/underscore-cli#alternatives">this list of alternatives</a> and also <a href="http://orangevolt.blogspot.fr/2012/12/8-ways-to-query-json-structures.html">this blog post</a></p>
<p>As you may need to beautify JSON output, you will see below some command line tools for this purpose</p>
<ul>
<li>Built-in module in Python with <code>python -mjson.tool file.json</code></li>
<li><a href="http://jsbeautifier.org">jsbeautifier</a></li>
<li>Underscore-Cli</li>
</ul>
<h2>Ressources</h2>
<ul>
<li><a href="https://github.com/stedolan/jq/issues/610">https://github.com/stedolan/jq/issues/610</a></li>
<li><a href="https://doublebyteblog.wordpress.com/2014/12/03/json-to-geojson-with-jq/">https://doublebyteblog.wordpress.com/2014/12/03/json-to-geojson-with-jq/</a></li>
<li><a href="http://blog.mapillary.com/technology/2014/08/12/jq-power.html">http://blog.mapillary.com/technology/2014/08/12/jq-power.html</a></li>
<li><a href="https://stedolan.github.io/jq/manual/">https://stedolan.github.io/jq/manual/</a></li>
</ul>
<h2>Comments, improvements</h2>
<p>We have done some mistakes. you see some improvements to add, feel free to comment or <a href="/contact.html">contact us</a>.</p>Ajout du support du géocodeur de l'IGN dans Geopy2015-01-06T10:20:00+01:00Thomas Gratiertag:webgeodatavore.com,2015-01-06:ajout-support-geocodeur-ign-dans-geopy.html<!-- PELICAN_BEGIN_SUMMARY -->
<p>Suite à <a href="http://www.forumsig.org/showthread.php/39877-Interroger-le-service-OpenLS-de-l-API-IGN">des discussions</a> sur le géocodage par le géocodeur de l'IGN, nous avons trouvé intéressant d'implémenter son support dans la bibliothèque Python Geopy. Le géocodage, pour rappel, c'est le fait d'associer des adresses à des coordonnées géographiques.</p>
<!-- PELICAN_END_SUMMARY -->
<p>{% notebook Geocodage_IGN_geopy.ipynb %}</p>
<h2>Aide possible</h2>
<p>Si vous avez des besoins liés au géocodage ou que certaines fonctions sont manquantes, nous pouvons les implémenter. N'hésitez pas à <a href="http://webgeodatavore.net/contact.html">nous contacter</a>.</p>Leaflet et browserify: améliorez vos pratiques de développement2014-12-17T15:40:00+01:00Thomas Gratiertag:webgeodatavore.com,2014-12-17:leaflet-browserify.html
<p>Depuis quelques temps déjà nous regardons l'activité assez hallucinante en terme de production de code de la société MapBox (qui fait un produit éponyme MapBox.JS basé sur Leaflet).</p>
<p>Nous avons constaté qu'ils s'inspiraient beaucoup d'outils venant du monde de Node JS, qui permet du développement JavaScript côté serveur. Ce développement de Node JS s'est accompagné de nouveaux outils qui permettent d'améliorer son processus de travail pour le développement d'interfaces cartographiques dans le navigateur. L'un des outils les plus importants est Browserify. Celui-ci permet avec NPM (le gestionnaire de bibliothèques de Node JS) de facilement modulariser son développement. Nous allons expliquer à quoi il peut servir en illustrant ce cas en passant d'un code avec Leaflet "simple" à un autre qui utilise Browserify.</p>
<p>Cet article prend principalement son inspiration de cet <a href="http://learnjs.io/blog/2013/11/08/leaflet-basics/">article anglais</a>.</p>
<p>Nous allons donc voir comment on faisait "Avant"</p>
<h2>Avant</h2>
<p>On considère qu'on ne veut pas s'appuyer sur un CDN (une url qui permet de ne pas télécharger la bibliothèque) car celui-ci bien que plus performant nous oblige d'avoir Internet, implique d'augmenter le nombre d'appels à des fichiers externes. Sur fixe, ce n'est pas le plus problématique mais si vous ciblez le mobile, la latence sur ces supports est mauvaise (la latence est le temps qu'il faut pour votre navigateur appelle une ressource et la récupère). Pour la contrer, il faut diminuer le nombre d'appels.</p>
<h3>Les étapes classiques</h3>
<p>Sans se préoccuper du language de programmation choisi côté serveur, les étapes classiques pour débuter un projet sont du type:</p>
<ul>
<li>
<p>création du dossier racine du projet</p>
</li>
<li>
<p>création d'un fichier index.html à la racine du dossier créé</p>
</li>
<li>
<p>création d'un répertoire nommé <code>static</code> (certains préfèrent <code>assets</code> comme nom) qui contient un répertoire vendor pour les bibliothèques ne faisant pas partie du code de l'application ainsi qu'un répertoire <code>js</code>, <code>css</code> et <code>img</code> (ou <code>images</code>) et potentiellement un répertoire <code>data</code> si vous avez des GeoJSON, des CSV ou des KML dans la nature.</p>
</li>
<li>
<p>téléchargement de vos bibliothèques externes dans le répertoire <code>vendor</code> (il y a surement d'autres conventions).</p>
</li>
</ul>
<p>Au final, vous utilisez de nombreuses bibliothèques, vous allez devoir télécharger, dézipper puis gérer les versions de vos bibliothèques vous-même. (en supposant que vous utilisez un gestionnaire de version type Git ou SVN) puis référencer chacune des resources (css, js principalement) dans votre fichier index.html.</p>
<p>L'inconvénient, c'est la gestion de dépendances de vos bibliothèques. Si le projet change de main, que vous déléguez ou partez du projet, ceux qui passeront derrière vont devoir retrouver chaque version des bibliothèques utilisées.</p>
<p>Par ailleurs, vous ne compressez pas par défaut votre code si vous en avez besoin pour un déploiement en production. Vous vous dites "je le ferais quand on sera en production" et au final, le code restera comme ça (PS: d'expérience, ça m'est arrivé...). Pour les performances comme la maintenabilité du code, on repassera...</p>
<h3>Code et structure classique d'un projet JavaScript</h3>
<p>Voici un petit exemple de structure "classique" avec Leaflet reprenant quelques idées de <a href="http://openbeermap.github.io">OpenBeerMap</a> mais de manière plus simplifiée :</p>
<p>Nous utilisons en plus de Leaflet, le plugin "Leaflet Dynamic JSON Layer" pour appeler les données de l'Overpass API (les données de OpenStreetMap (OSM) en tant que service).</p>
<div style="text-align:center"><img src="/images/leaflet_project_tree_no_browserify.png" alt="Structure projet Leaflet classique sans Browserify" title="Structure projet Leaflet classique sans Browserify" /></div>
<p><strong>index.html</strong></p>
<div class="highlight"><pre><span class="cp"><!doctype html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Exemple de Leaflet sans Browserify<span class="nt"></title></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"static/vendor/leaflet/leaflet.css"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"static/css/style.css"</span><span class="nt">></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"static/vendor/leaflet/leaflet.js"</span><span class="nt">></script></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"static/vendor/leaflet-layerjson/leaflet-layerjson.min.js"</span><span class="nt">></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"map"</span><span class="nt">></div></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"static/js/app.js"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p><strong>style.css</strong></p>
<div class="highlight"><pre><span class="cp">#map {</span>
<span class="nl">height:</span> <span class="mi">800</span><span class="n">px</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p><strong>app.js</strong></p>
<div class="highlight"><pre><span class="c1">// Instancier la carte </span>
<span class="kd">var</span> <span class="kt">map</span> <span class="o">=</span> <span class="nx">L.map</span><span class="p">(</span><span class="s1">'map'</span><span class="p">,</span> <span class="p">{</span>
<span class="nx">scrollWheelZoom</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">});</span>
<span class="c1">// Donner un centre et un zoom à la carte</span>
<span class="kt">map</span><span class="bp">.</span><span class="nx">setView</span><span class="p">(</span><span class="err">[</span><span class="mf">47.2061</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.5519</span><span class="cp">]</span>, 13);
// Passer une chaine pour l'attribution
var attribution = 'Tuiles mise à disposition par <span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://openstreetmap.se/"</span> <span class="na">target=</span><span class="s">"_blank"</span><span class="nt">></span>OpenStreetMap Suède<span class="nt"></a></span> <span class="ni">&mdash;</span> Données <span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.openstreetmap.org/copyright"</span><span class="nt">></span><span class="ni">&copy;</span> les contributeurs OpenStreetMap<span class="nt"></a></span>';
// Déclarer le motif pour les urls des tuiles qui seront utilisées
var tiles = 'http://{s}.tile.openstreetmap.se/hydda/full/{z}/{x}/{y}.png';
// Créer la couche de tuiles en passant l'url des tuiles, les sous-domaines,
// le seuil de zoom et enfin l'attribution
var layer = L.tileLayer(tiles, {
subdomains: "1234",
maxZoom: 18,
attribution: attribution
});
// Enfin, terminer en ajoutant la couche avec les tuiles sur la carte
layer.addTo(map);
// On construit l'icone une seule fois comme elle on a choisi de faire
// un point = même icone dans tous les cas
var icon = new L.Icon({
iconUrl:'static/img/beer1.png',
iconSize: new L.Point(32, 37),
iconAnchor: new L.Point(18, 37),
popupAnchor: new L.Point(0, -37)
});
// Déclaration d'une couche qu'on appelle via un webservice puis qu'on ajoute à la carte
L.layerJSON({
url: 'http://overpass-api.de/api/interpreter?data=<span class="cp">[</span><span class="nb">out</span><span class="p">:</span><span class="nx">json</span><span class="cp">]</span>;node({lat1},{lon1},{lat2},{lon2})<span class="cp">[</span><span class="n">amenity</span><span class="o">=</span><span class="nx">bar</span><span class="cp">]</span>;out;',
propertyItems: 'elements',
propertyTitle: 'tags.name',
propertyLoc: <span class="cp">[</span><span class="s1">'lat'</span><span class="p">,</span><span class="s1">'lon'</span><span class="cp">]</span>,
buildIcon: function(data, title) {
return icon;
},
buildPopup: function(data, marker) {
return data.tags.name || null;
}
}).addTo(map);
</pre></div>
<p>Pour l'image du verre de bière, la source est <a href="https://openbeermap.github.io/assets/img/beer1.png">https://openbeermap.github.io/assets/img/beer1.png</a> et elle est placée dans le répertoire static/img.</p>
<p>Il a fallu télécharger deux bibliothèques js. Pour la deuxième, nous n'avons gardé qu'un fichier plutôt que l'ensemble des fichiers.</p>
<p>Le résultat visuel obtenu va être du type ci dessous :</p>
<div style="text-align:center"><img src="/images/leaflet_browserify_demo.png" alt="Structure projet Leaflet classique sans Browserify" title="Structure projet Leaflet classique sans Browserify" /></div>
<p>Avant de passer à l'après, il faut noter que nous avons pollué l'objet global: tapez <code>map</code> dans la console du debugger du navigateur et vous allez voir...
Cette pratique d'utiliser des variables globales en JavaScript est considérée comme mauvaise: le nom des variables dans plusieurs parties du code peuvent être les mêmes et générer des erreurs dans votre application. Si vous êtes sur une petite application gérée seule, vous arriverez peut être à éviter les problèmes mais si vous êtes plusieurs développeurs ou même que votre application grossit, vous allez rapidement comprendre l'intérêt de cette pratique.</p>
<p>La façon la plus courante de régler ce cas est de tout mettre dans une IIFE (<a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression">"Immediately-invoked function expression"</a>): vous entourez tout le code JavaScript précédent en commençant par <code>(function() {</code> et en terminant par <code>})();</code>. L'inconvénient dans ce cas est qu'on ne peut plus accéder aux variables pour manipuler la carte ultérieurement. Généralement, on fait un compromis qui permet de gérer des variables publiques et privées en changeant la forme de l'IIFE. Pour cela, voir le lien wikipédia sur l'IIFE, plus complet ainsi que les design patterns <a href="http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript">"Module"</a> et <a href="http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript">Revealing Module</a></p>
<h2>L'après</h2>
<p>Même si nous allons vous présenter Browserify, il existe un panel d'outils complémentaires mais que nous n'allons pas aborder: l'article déjà assez long deviendrait "imbuvable". Regardez les mots clés Bower, ComponentJS, Gulp, Grunt en particulier.</p>
<h3>De nouvelles dépendances dans vos outils</h3>
<p>Vous allez comparativement au cas avec Leaflet "simple" devoir sortir la "machine de guerre". Nous entendons par là que vous n'allez pas utiliser seulement un éditeur de texte et votre navigateur pour travailler.</p>
<p>Le développement avec Browserify s'appuie sur Node JS. Sans dire de faire du code JavaScript côté serveur (une voire la raison initiale de Node JS), il devient de plus en plus incontournable pour le développement côté client. En effet, il permet d'utiliser des fonctions très pratiques pour vous rendre productif (indépendamment d'être dans un contexte cartographique). D'ailleurs, cette tendance se confirme comme le montre <a href="http://blog.npmjs.org/post/101775448305/npm-and-front-end-packaging">cet article du blog NPM</a> (l'installateur de bibliothèques de Node JS)</p>
<p>Pour ne pas faire que du théorique, installons NodeJS</p>
<h3>Installez Node JS</h3>
<p>Allez sur <a href="http://nodejs.org">http://nodejs.org</a>, cliquez sur le bouton <code>INSTALL</code>. Normalement, le navigateur va détecter si vous êtes sous Windows, Linux ou Mac. Pour Linux ou Mac, suivez le <a href="http://openclassrooms.com/courses/des-applications-ultra-rapides-avec-node-js/installer-node-js">tutoriel de OpenClassRooms</a></p>
<p>Sous Windows, le téléchargement doit être un fichier msi ou exe. Si ce n'était pas le cas, vous devriez aller sur la page <code>DOWNLOADS</code> puis allez cliquer sur Windows Installer (.msi) en 64bits. Certains tutoriels privilégient la version 32 bits mais normalement avec un PC de moins de 5 ans, vous êtes en 64 bits.
Exécutez-le fichier téléchargé dans tous les cas.</p>
<p>Ouvrez un terminal et lancer</p>
<div class="highlight"><pre><span class="n">node</span> <span class="o">-</span><span class="n">v</span>
</pre></div>
<p>S'il n'y a pas d'erreurs, vous devriez voir un numéro de version retourné comme <code>v0.10.33</code>.</p>
<h3>Le chargement des modules</h3>
<p>Il faut comprendre que quand on développe "habituellement" (sans Browserify), on doit gérer les dépendances entre les différents fichiers JavaScript. si vous développer avec 30 fichiers js, vous devez inclure 30 balises <code><script src=""></code>
Cela n'est pas très pratique et quand vous allez devoir compresser les fichiers, il faudra faire des changements pour ne faire plus qu'un appel à un seul script.</p>
<h4>AMD</h4>
<p>Vous pouvez aussi gérer les dépendances avec un standard comme <code>AMD</code>.
Honnêtement, nous ne le trouvons pas la syntaxe très lisible (une question de goût et d'habitude onc inutile de "troller") car il faut déclarer à la main les dépendances au début des fichiers plutôt qu'au moment où on en a besoin.</p>
<p>Vous trouverez un exemple ci-dessous pour vous donner un aperçu de la syntaxe. Il pourrait être traduit "Quand la page est chargée, appelez ./js/common.js. Lorsque ce dernier est chargé, la racine pour appeler les fichiers js est déclaré à /js (grâce à une instruction <code>require.config</code> non présentée dans l'exemple) et on appelle un deuxième fichier depuis js/app/main.js"</p>
<p><strong>app.js</strong></p>
<div class="highlight"><pre><span class="nx">require</span><span class="p">(</span><span class="cp">[</span><span class="s1">'./js/common'</span><span class="cp">]</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">common</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">//js/common définit la baseUrl à js/ ainsi on ne peut</span>
<span class="c1">//demander que 'app/main1' ici plutôt que 'js/app/main1'</span>
<span class="nx">require</span><span class="p">(</span><span class="cp">[</span><span class="s1">'app/main1'</span><span class="cp">]</span><span class="p">);</span>
<span class="p">});</span>
</pre></div>
<p>L'intérêt de AMD est qu'il permet de faire des appels conditionnels: vous pouvez ne charger qu'une partie du JavaScript et quand vous en avez besoin, vous pouvez grâce au système de dépendances, charger de nouvaux fichiers au moment où vous utilisez une fonction particulière par exemple.</p>
<p>Nous pensons néanmoins qu'il n'y a pas que la taille du fichier JavaScript initial chargé qui compte mais aussi le nombre d'appels à différents fichiers JS: cela joue sur la latence déjà évoquée (le temps de demander une ressource puis de l'obtenir, voir <a href="https://fr.wikipedia.org/wiki/Latence_%28informatique%29">l'article de Wikipédia</a>). Le chargement asynchrone AMD perd alors une partie de son intérêt.</p>
<p>Il faut aussi noter que vous pouvez ne pas utiliser le chargement asynchrone avec AMD et compacter tous les fichiers en un et qu'une solution intermédiaire peut être de compacter certains groupes de fichiers js et de continuer à en charger quelque uns de manière asynchrone. Par ailleurs, vous pouvez gérer la dépendance sur des CSS. Comme quoi, ce n'est pas notre préférence mais l'apport d'AMD reste intéressant.</p>
<h4>CommonJS, une autre choix pour gérer des dépendances</h4>
<p>Une solution alternative est d'utiliser CommonJS, un standard utilisé par Node JS qui permet de gérer les dépendances. C'est ce système qui est utilisé par Browserify. </p>
<p>Pour reprendre l'exemple précédent, vous pouvez faire pour déclarer la dépendance, en suivant ce standard, ajouter dans le fichier app/main1.js</p>
<div class="highlight"><pre><span class="n">var</span> <span class="n">common</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="err">'</span><span class="p">.</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">common</span><span class="err">'</span><span class="p">);</span>
</pre></div>
<p>Mais Browserify n'offre pas que le support pour le rôle de gestion des dépendances, il permet aussi de créer des modules ou bien de faire l'équivalent des "include" (pour ceux qui connaissent PHP) c'est à dire de faire d'appeler d'autres fichiers dans un fichier et donc de modulariser votre code facilement.</p>
<h3>Changez les fichiers pour utiliser NPM</h3>
<p>Revenons à notre exemple Leaflet et changeons les choses pour être "compatible Browserify".</p>
<p>Créons un fichier package.json pour gérer les dépendances dans NPM (gestionnaire de dépendances de Node JS)</p>
<p>Pour cela, lancer à la racine du projet, en ligne de commande:</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">init</span>
</pre></div>
<p>et répondre aux questions. Pour le fichier principal, remplacez <code>index.js</code> par <code>app.js</code></p>
<p>Ne vous inquiétez pas trop, il n'y a pas de mauvaises réponses! Pas de bonnet d'âne!</p>
<p>Vous devriez avoir un nouveau fichier package.json similaire à ci-dessous :</p>
<div class="highlight"><pre><span class="p">{</span>
<span class="s2">"name"</span><span class="o">:</span> <span class="s2">"leaflet_browserify"</span><span class="o">,</span>
<span class="s2">"version"</span><span class="o">:</span> <span class="s2">"0.0.0"</span><span class="o">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Leaflet browserify demo"</span><span class="o">,</span>
<span class="s2">"main"</span><span class="o">:</span> <span class="s2">"app.js"</span><span class="o">,</span>
<span class="s2">"scripts"</span><span class="o">:</span> <span class="err">{</span>
<span class="s2">"test"</span><span class="o">:</span> <span class="s2">"echo \"Error: no test specified\" && exit 1"</span>
<span class="p">}</span><span class="o">,</span>
<span class="s2">"keywords"</span><span class="o">:</span> <span class="cp">[</span>
<span class="s2">"leaflet"</span><span class="p">,</span>
<span class="s2">"map"</span>
<span class="cp">]</span><span class="o">,</span>
<span class="s2">"author"</span><span class="o">:</span> <span class="s2">"Thomas Gratier"</span><span class="o">,</span>
<span class="s2">"license"</span><span class="o">:</span> <span class="s2">"MIT"</span>
<span class="err">}</span>
</pre></div>
<p>Pour l'instant, il nes sert à rien, juste à déclarer le nom du projet (sauf si par exemple, nous voulions le mettre à disposition sur https://www.npmjs.org, le site qui sert à partager les paquets Node JS)</p>
<p>Ajoutons lui les dépendances pour charger Leaflet et leaflet-layerjson avec</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">install</span> <span class="n">leaflet</span> <span class="n">leaflet</span><span class="o">-</span><span class="n">layerjson</span> <span class="o">--</span><span class="n">save</span>
</pre></div>
<p>Inspectez le fichier package.json pour voir des changements avec l'ajout d'un bloc après la licence:</p>
<div class="highlight"><pre><span class="p">...</span>
<span class="s">"dependencies"</span><span class="o">:</span> <span class="p">{</span>
<span class="s">"leaflet"</span><span class="o">:</span> <span class="s">"^0.7.3"</span><span class="p">,</span>
<span class="s">"leaflet-layerjson"</span><span class="o">:</span> <span class="s">"^0.1.5"</span>
<span class="p">}</span>
</pre></div>
<p>Constatez l'apparition d'un répertoire node_modules qui contient lui-même deux répertoires <code>leaflet</code> et <code>leaflet-layerjson</code>.
En fait, <code>npm install leaflet leaflet-layerjson</code> sert à dire "récupère-moi les paquets leaflet leaflet-layerjson" et l'option <code>--save</code> sert elle à dire "garde-moi la trace de ces dépendances entre mon projet et les paquets dans le fichier package.json" (le bloc <code>dependencies</code>)</p>
<p>Normalement, votre projet dans le gestionnaire de version doit ignorer le répertoire node_modules sauf cas particulier (cherchez le mot clé .gitignore dans un moteur de recherche pour en savoir plus si vous utilisez Git)</p>
<h3>Installation de Browserify et Beefy</h3>
<p>Maintenant, installons Browserify et un autre utilitaire nommé Beefy (installez-le, ne réfléchissez pas pourquoi pour le moment) avec:</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">install</span> <span class="n">browserify</span> <span class="n">beefy</span> <span class="o">--</span><span class="n">save</span><span class="o">-</span><span class="n">dev</span>
</pre></div>
<p>Nous n'avons pas utilisé l'option <code>-g</code> qui veut dire globale, c'est à dire que les utilitaires ne seront pas spécifiques à ce projet (un répertoire avec package.json à la racine).
Il vaut mieux éviter les effets de bord. Browserify ou Beefy peuvent être utilisés sur d'autres projets sur votre machine et si la version change entre les projets vous allez probablement avoir des erreurs.</p>
<h3>Changement du code v1</h3>
<p>remplaçons le code de index.html avec:</p>
<div class="highlight"><pre><span class="cp"><!doctype html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Exemple de Browserify avec Leaflet<span class="nt"></title></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"node_modules/leaflet/dist/leaflet.css"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"static/css/style.css"</span><span class="nt">></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"map"</span><span class="nt">></div></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"static/js/bundle.js"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>Dans app.js, on ajoute au début du fichier</p>
<div class="highlight"><pre><span class="c1">// Ajouter Leaflet.js</span>
<span class="k">var</span> <span class="no">L</span> <span class="o">=</span> <span class="n">require</span><span class="p">('</span><span class="n">leaflet</span><span class="p">');</span>
<span class="c1">// Puis le plugin pour le JSON (utilisé pour l'Overpass API)</span>
<span class="n">require</span><span class="p">('</span><span class="n">leaflet</span><span class="o">-</span><span class="n">layerjson</span><span class="p">');</span>
<span class="c1">// Indiquer le chemin vers le dossier d'images de Leaflet</span>
<span class="no">L</span><span class="p">.</span><span class="n">Icon</span><span class="p">.</span><span class="n">Default</span><span class="p">.</span><span class="n">imagePath</span> <span class="o">=</span> <span class="p">'</span><span class="n">node_modules</span><span class="o">/</span><span class="n">leaflet</span><span class="o">/</span><span class="k">dist</span><span class="o">/</span><span class="n">images</span><span class="o">/</span><span class="p">';</span>
</pre></div>
<p>La première ligne indique qu'on veut charger Leaflet et qu'on affectera le retour de Leaflet à la variable <code>L</code> (celle par défaut déjà utilisée par Leaflet).</p>
<p>La deuxième permet de faire comme un include donc d'insérer le code de leaflet-layerjson directement au fichier app.js. On devrait être normalement obligé de déclarer explicitement le chemin vers le fichier <code>leaflet-layerjson.min.js</code> car le fichier <code>package.json</code> dans <code>node_modules/leaflet-layerjson</code> ne contient pas de bloc <code>main</code> qui permet de faire un simple <code>require('leaflet-layerjson');</code> par défaut (une PR a été ouverte).</p>
<p>Heureusemement, il est possible pour éviter ce problème, de créer un alias pour cela en utilisant le block <code>browser</code> dans package.json (comme ci-dessous)</p>
<div class="highlight"><pre><span class="s">"dependencies"</span><span class="o">:</span> <span class="p">{</span>
<span class="s">"leaflet"</span><span class="o">:</span> <span class="s">"^0.7.3"</span><span class="p">,</span>
<span class="s">"leaflet-layerjson"</span><span class="o">:</span> <span class="s">"^0.1.5"</span>
<span class="p">},</span>
<span class="s">"browser"</span><span class="o">:</span> <span class="p">{</span>
<span class="s">"leaflet-layerjson"</span><span class="o">:</span> <span class="s">"./node_modules/leaflet-layerjson/dist/leaflet-layerjson.min.js"</span>
<span class="p">},</span>
</pre></div>
<p>Dans les deux cas avec <code>require</code>, le code est pris depuis les répertoires dans node_modules.</p>
<p>La dernière ligne de <code>app.js</code>, elle, permet si on utilise les icônes par défaut de Leaflet, d'avoir le "bon" chemin vers les images.</p>
<p>Maintenant, il est de temps de lancer Browserify en ligne de commande</p>
<div class="highlight"><pre><span class="p">.</span><span class="o">/</span><span class="n">node_modules</span><span class="o">/</span><span class="p">.</span><span class="n">bin</span><span class="o">/</span><span class="n">browserify</span> <span class="k">static</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">app</span><span class="p">.</span><span class="n">js</span> <span class="o">></span> <span class="k">static</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">bundle</span><span class="p">.</span><span class="n">js</span>
</pre></div>
<p>Ouvrez dans votre navigateur index.html et vous devriez avoir un rendu identique à précédemment.
Ce qui change ici c'est qu'on n'appelle plus qu'un fichier JavaScript au lieu de trois en passant par <code>require</code> et on rassemblé les trois fichiers en un grâce à Browserify. Il faut noter qu'un truc moins sympa est la référence au fichier CSS dans node_modules/leaflet/dist/leaflet.css et qu'on a pour le moment 2 CSS alors qu'on pourrait aussi compacter.</p>
<p>Vous allez nous demander, que vous vous voyez mal faire le "c.." à chaque fois à relancer la ligne de commande de Browserify au moindre changement.</p>
<p>C'est justement là qu'intervient Beefy, c'est un utilitaire qui permet de surveiller les changements sur des fichiers, de recharger le navigateur lors de changement de code (à la "LiveReload", un autre utilitaire pour cela) et de servir les pages comme si on avait déjà déployé sur un serveur du type Apache ou Nginx (2 des serveurs web les plus répandus du Net)</p>
<div class="highlight"><pre><span class="p">.</span><span class="o">/</span><span class="n">node_modules</span><span class="o">/</span><span class="p">.</span><span class="n">bin</span><span class="o">/</span><span class="n">beefy</span> <span class="k">static</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">app</span><span class="p">.</span><span class="n">js</span><span class="o">:</span><span class="k">static</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">bundle</span><span class="p">.</span><span class="n">js</span> <span class="o">--</span><span class="n">live</span> <span class="o">--</span><span class="n">open</span>
</pre></div>
<p>La ligne dit "prend-moi le contenu de static/js/app.js, met-le dans un nouveau fichier static/js/bundle.js, sers-moi le contenu à l'adresse http://127.0.0.1:9966 (par défaut), ouvre-le navigateur et recharge la page si il y a des changements</p>
<p>Le navigateur par défaut doit s'ouvrir tout seul sinon ouvrez-le à l'adresse http://127.0.0.1:9966 et voyez le résultat.</p>
<p>Essayez par exemple d'éditer le fichier app.js en ajoutant la chaine "console.log(L.Icon.Default.imagePath);" après la ligne <code>L.Icon.Default.imagePath = ...</code> et observez le navigateur recharger la page si vous sauvegardez le fichier.</p>
<p>Vous pouvez aussi supprimer le répertoire static/vendor car il ne sert plus à rien maintenant.</p>
<h3>Changement de code v2</h3>
<p>On peut procéder à plusieurs améliorations :</p>
<ul>
<li>ne plus avoir à taper le code chaque fois pour lancer Beefy et/ou Browserify en sauvegardant les instructions pour les lancer</li>
<li>"minifier" le js dans Browserify: celui-ci est rassemblé dans un seul fichier bundle.js mais sa taille n'est pas optimisée. La minification, c'est le fait de compacter (en supprimant espaces et retours à la ligne inutiles) et parfois de renommer les variables en des noms plus courts (on parle d'obfuscation) afin de limiter la taille du JavaScript qui sera chargé dans le navigateur.</li>
<li>"Assembler et minifier le css en un"</li>
</ul>
<p>On va utiliser une fonctionnalité de package.json. Il est possible d'ajouter une bloc scripts qui contient des clés valeurs avec la clé pour le nom de la commande et la valeur pour la commande qu'on va exécuter, associée à la clé.</p>
<p>Par exemple, ajoutez dans le bloc <code>scripts</code> existant:</p>
<div class="highlight"><pre><span class="s">"scripts"</span><span class="o">:</span> <span class="p">{</span>
<span class="s">"test"</span><span class="o">:</span> <span class="s">"echo </span><span class="se">\"</span><span class="s">Error: no test specified</span><span class="se">\"</span><span class="s"> && exit 1"</span><span class="p">,</span>
<span class="s">"echoer"</span><span class="o">:</span> <span class="s">"echo </span><span class="se">\"</span><span class="s">It's working</span><span class="se">\"</span><span class="s">"</span>
<span class="p">}</span>
</pre></div>
<p>Puis pour voir le fonctionnement, lancez :</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">run</span> <span class="n">echoer</span>
</pre></div>
<p>ou son équivalent, plus long:</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">run</span><span class="o">-</span><span class="n">script</span> <span class="n">echoer</span>
</pre></div>
<p>Vous allez voir que le code est bien exécuté. Il faut noter que même si le nom de la clé dans le bloc scripts peut être arbitrairement choisi, certains noms de clés ont un rôle particulier (voir https://www.npmjs.org/doc/misc/npm-scripts.html à ce propos)
ainsi dans <code>scripts</code>, remplacez les deux lignes par :</p>
<div class="highlight"><pre><span class="s">"start"</span><span class="o">:</span> <span class="s">"./node_modules/.bin/beefy static/js/app.js:static/js/bundle.js --live"</span>
</pre></div>
<p>Puis lancez:</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">start</span> <span class="o">--</span> <span class="o">--</span><span class="n">open</span>
</pre></div>
<p>Celui-ci équivaut à lancer <code>npm run start</code> ou <code>npm run-script start</code> mais en passant l'option --open en plus (depuis la version 2.0 de NPM)</p>
<p>Maintenant, on va passer à la gestion des CSS et à minification en général. Pour cela, il faut installer deux outils supplémentaires:</p>
<div class="highlight"><pre><span class="n">npm</span> <span class="n">install</span> <span class="n">clean</span><span class="o">-</span><span class="n">css</span> <span class="o">--</span><span class="n">save</span><span class="o">-</span><span class="n">dev</span>
<span class="n">npm</span> <span class="n">install</span> <span class="n">uglify</span><span class="o">-</span><span class="n">js</span> <span class="o">--</span><span class="n">save</span><span class="o">-</span><span class="n">dev</span>
</pre></div>
<p>Au niveau de package.json, on ajoute après la section <code>start</code> ce qui suit (en n'oubliant pas la virgule à la fin de la ligne <code>start</code>):</p>
<div class="highlight"><pre><span class="s">"deploystatic"</span><span class="o">:</span> <span class="s">"npm run minifycss && npm run bundle"</span><span class="p">,</span>
<span class="s">"prestart"</span><span class="o">:</span> <span class="s">"npm run minifycss"</span><span class="p">,</span>
<span class="s">"bundle"</span><span class="o">:</span> <span class="s">"./node_modules/.bin/browserify static/js/app.js | ./node_modules/.bin/uglifyjs > static/js/bundle.js"</span><span class="p">,</span>
<span class="s">"minifycss"</span><span class="o">:</span> <span class="s">"./node_modules/.bin/cleancss -o static/css/styles.min.css static/css/style.css"</span>
</pre></div>
<p>Changer dans les références, au niveau du fichier index.html, les appels à <code>style.css</code> par <code>styles.min.css</code></p>
<p>Au final, ce qu'on a fait, c'est préparer </p>
<h2>Quelques pistes à explorer</h2>
<p>Dans les idées à explorer, vous pourriez utiliser <code>jshint</code> ou <code>htmlhint</code> pour améliorer la qualité de votre code.</p>
<p>Vous devriez aussi apprendre à rendre compatible des modules initialement incompatibles avec Browserify (on est gentil, les liens fournis donnent de quoi faire).</p>
<p>Vous pouvez aussi vous pencher sur <code>UMD</code> (Universal Module Definition), qui permet d'unifier la gestion des modules AMD, CommonJS.</p>
<p>Vous pouvez aussi jeter un oeil au support des modules dans la version à venir de JavaScript (ES6)</p>
<p>Le code est disponible sur Github <a href="https://github.com/ThomasG77/leaflet-browserify">https://github.com/ThomasG77/leaflet-browserify</a> avec des tags <code>before</code> et <code>after</code> pour facilement vous répérer.</p>
<p>N'hésitez pas à commenter ici ou à nous faire un retour sur <a href="http://twitter.com/ThomasG77">le compte Twitter ThomasG77</a>.</p>
<h2>Réferences utilisées et/ou liées pour aller plus loin:</h2>
<p><em>Français:</em></p>
<ul>
<li><a href="http://putaindecode.fr/posts/js/browserify-all-the-things/">http://putaindecode.fr/posts/js/browserify-all-the-things/</a></li>
<li><a href="http://putaindecode.fr/posts/nodejs/napa-ou-comment-telecharger-package-napa-package-json/">http://putaindecode.fr/posts/nodejs/napa-ou-comment-telecharger-package-napa-package-json/</a></li>
</ul>
<p><em>Anglais:</em></p>
<ul>
<li><a href="http://jondavidjohn.com/keeping-it-local-with-npm-scripts/">http://jondavidjohn.com/keeping-it-local-with-npm-scripts/</a></li>
<li><a href="http://dontkry.com/posts/code/browserify-and-the-universal-module-definition.html">http://dontkry.com/posts/code/browserify-and-the-universal-module-definition.html</a></li>
<li><a href="http://dontkry.com/posts/code/using-npm-on-the-client-side.html">http://dontkry.com/posts/code/using-npm-on-the-client-side.html</a></li>
<li><a href="http://lincolnloop.com/blog/untangle-your-javascript-browserify/">http://lincolnloop.com/blog/untangle-your-javascript-browserify/</a></li>
<li><a href="https://oncletom.io/2014/self-contained-node-scripts/">https://oncletom.io/2014/self-contained-node-scripts/</a></li>
<li><a href="https://github.com/thlorenz/browserify-shim">https://github.com/thlorenz/browserify-shim</a></li>
</ul>De la recherche de données géographiques2014-09-27T10:20:00+02:00Thomas Gratiertag:webgeodatavore.com,2014-09-27:de-la-recherche-geographique.html
<p>Récemment, la mission Etalab, la structure qui gère la politique d’ouverture et de partage des données (au niveau de l'Etat) a procédé à des améliorations qu'il s'agisse de lifting graphique ou d'ajout de fonctionnalités nouvelles. Un grand bond avait déjà été fait entre la V1 basée sur un CMS présentant une liste de données et la v2 ayant pris un virage plus communautaire et étant vraiment mieux pensée.
Parmi les nouvelles fonctionnalités, la recherche par "région" a attiré notre attention.
</p>
<h2>La recherche par régions</h2>
<p>Celle-ci est proposée en beta comme annoncée ci-dessous.</p>
<blockquote class="twitter-tweet" lang="fr"><p>[Nouveautés] Une carte pour découvrir les jeux de données disponibles dans chaque région <a href="http://t.co/qxReCLKuVu">http://t.co/qxReCLKuVu</a> (encore en mode beta :-)</p>— datagouvfr (@datagouvfr) <a href="https://twitter.com/datagouvfr/status/512597391309307904">18 Septembre 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>En utilisant la fonctionnalité, nous avons été un peu surpris du comportement de l'interface.
En effet, quand on prend le département Loire-Atlantique, la ville de Nantes n'apparait pas. Il nous est venu la réflexion suivante qui est qu'est ce qu'on entend par "régions". Du fait de l'annonce Twitter (limité à 140 caractères), "Régions" pouvait porter à confusions. Il est dans ce cas plus assimilé à une entité administrative en général qu'aux 22 régions (en attendant le changement futur). </p>
<p>Après avoir compris cela, la question du résultat retournée par la recherche par région reste problématique. Ce qui apparait important est la manière dont on cherche :</p>
<ul>
<li>
<p>on cherche les données qui recouvrent la zone géographique entièrement (A) ou partiellement (B)</p>
</li>
<li>
<p>on cherche les données qui sont contenues dans la zone géographique (C)</p>
</li>
<li>
<p>on cherche les données qui ont la même emprise que la "région" (D)</p>
</li>
<li>
<p>on cherche les données qui sont produites par une entité à l'échelle de la région considérée mais sans localisation précise (E). C'est une donnée de la région mais la question "quelle partie de la région recouvre-t-elle?" est sans réponse.</p>
</li>
</ul>
<div style="text-align:center"><img src="/images/cas_recouvrements_regions_donnees.png" alt="Recherche par régions, quelques cas" title="Recherche par régions, quelques cas" /></div>
<p>Cela peut encore paraître abstrait, nous allons voir des cas pratiques pour illustrer.</p>
<h2>La problématique par les scénarios</h2>
<h3>Je suis Nantais et je veux savoir quelles données sont disponibles sur ma ville?</h3>
<p>Je cherche par EPCI et en zoomant, je tombe sur "Communauté urbaine Nantes Métropole, 25 jeux de données"
En dehors du nombre de jeux de données qui n'est pas que de 25 sur Nantes, il se pose le problème que j'attend qu'on me propose les données du département qui couvre l'emprise de la ville. Par exemple, le jeu de données "Horaires des écluses du domaine fluvial public de Loire-Atlantique : l'écluse Saint-Félix" concerne une écluse gérée par le département mais située sur Nantes même. Un autre exemple est celui des "Routes départementales de Loire-Atlantique". Nantes n'est pas traversée par ces routes bien sûr.</p>
<h3>Je suis citoyen d'Ancenis, j'aimerais savoir quelles données ouvertes sont disponibles?</h3>
<p>Pour donner un peu de contexte, Ancenis est une sous-préfecture de la Loire Atlantique, environ à 35 km à l'Est de Nantes.
Si je cherche au niveau départemental, je vais voir que des données sont disponibles à cette échelle. Cette supputation suppose que l'utlisateur final lorsqu'il cherche sur Ancenis ne commence pas immédiatement par la région "Commune française".
En commençant par cela, le pauvre citoyen risque de pleurer en se posant la question où peut se passer ce fameux "OpenData" dont il a entendu parler dans des légendes et mythes urbains.</p>
<h2>Bilan de l'actuel</h2>
<h3>Forces de la solution :</h3>
<ul>
<li>
<p>Identifier les acteurs mettant à disposition des données ouvertes. Cela peut permettre de montrer un leadership local sur des zones ou une absence de celui-ci si on adopte une vision inverse. On pourrait assimiler cela à la carte des initiatives <a href="http://www.opendata-map.org">http://www.opendata-map.org</a> avec le lien vers les jeux de données.</p>
</li>
<li>
<p>Avoir les jeux de données à la "bonne échelle". En effet, un utilisateur selon son degré d'expertise ne pourra pas savoir extraire les données uniquement sur sa zone de recherche par exemple pour sa commune depuis les données départementales. Il pourra difficilement qualifier qu'une donnée ayant été produite pour une vocation départementale, la précision est insufisante pour son besoin.</p>
</li>
<li>
<p>Permettre de classer les données non géographiques grâce à l'acteur car toutes les données n'ont pas un caractère géographique et ne sont pas "situables". L'exercice géographique précédent montre ses limites.</p>
</li>
</ul>
<h3>Faiblesses de la solution :</h3>
<ul>
<li>
<p>Par opposition à l'identification des acteurs, cela met en avant un hiérarchie. On pourrait presque parler d'une vision jacobinisme avec la mise en valeur de l'initiative d'un acteur à une échelle.</p>
</li>
<li>
<p>Risque de causer une incompréhension car elle ne répond pas immédiatement à la problématique du "Je suis sur un territoire, je veux savoir quelles données ouvertes sont présentes voir exploitables, indépendamment de l'échelle administrative". L'exemple dejà cité des écluses ou des routes départeentales l'illustre. On n'a donc l'impression selon l'échelle de la région choisie en entrée, ce n'est plus "Paris ou le Désert Français" mais "l'OpenData et les déserts Français".
Il n'y aurait aucune donnée ouverte sur des territoires en France alors que par expérience en tant que réutilisateur de données ouvertes, nous savons que les données communes GeoFla de l'IGN ou celles extraites de OpenStreetMap couvrent toute la France malgré quelques limites.</p>
</li>
<li>
<p>Savoir quelle plateforme fournie la donnée n'est pas un but en soi. L'essentiel est de permettre un recherche efficace indépendamment de l'acteur impliqué dans la production des données ouvertes.</p>
</li>
</ul>
<p>Même si nous sommes critique, le but au final est savoir ce que les utilisateurs du portail data.gouv.fr attendent de la recherche par région. Quelle est la plus value actuelle, celle qu'apporterait un recherche recouvrante? D'autres propositions sont surement possibles et nous serions très contents qu'elles émergent.</p>
<h2>Propositions</h2>
<p>Si on adopte une proposition recouvrante c'est à dire qui renvoie les jeux de données disponibles sur un territoire indépendamment de l'échelle lors de la recherche par région, cela va provoquer un autre problème.
En effet, comment distinguer les territoires à forte densité de données OpenData car maintenant, on est sûr que toute la France est au moins couverte par GéoFla par exemple. Rien ne ressort alors que le but d'une carte (même si ce n'est pas que ça) peut être d'aider à la recherche ou de faire ressortir les contrastes entre lieux.
Ainsi une alternative serait de faire ressortir les contrastes en comptant le nombre de jeux de données recouvrant un territoire. Par conséquent, une ville comme Nantes, aura le cumul du nombre de jeux de données départements et communes. Le choix dans la partie de droite des régions ne servirait qu'à zoomer et à ouvrir un popup du nombre de données disponibles sur le territoire.</p>
<p>Cependant, les limites de notre approche sont :</p>
<ul>
<li>
<p>nous n'avons clairement pas maquetté tout mais nous serions heureux que cela puisse interpeller afin d'améliorer l'expérience utilisateur sur data.gouv.fr</p>
</li>
<li>
<p>le problème des données non localisées reste en suspens.</p>
</li>
<li>
<p>la temporalité : la mise en place du système proposé peut même s'il nous semble plus pratique en terme d'ergonomie, implique surement des cas particuliers qui techniquement peuvent s'avérer plus coûteux en temps et/ou argent comme la conception actuelle est partie d'autres scénarios d'usage</p>
</li>
</ul>
<h2>Au delà de la partie "Recherche par régions"</h2>
<p>Comme la plate-forme propose aussi une recherche par période de temps, cela ouvre une autre question. Quel futur pour l'archivage des données ouvertes en séries temporelles?
Plusieurs réflexions y sont liées. On a par exemple, la réflexion de Christian Quest, président d'OpenStreetMap, sur les "millésimes" de jeux de données pour les communes. En effet, une réorganisation des données géographiques a des impacts sur le calcul des indicateurs statistiques qui leur sont associés. La réforme des régions à venir va clairement impacter cela par exemple.
On a aussi la problématique du stockage physique et des formats envisagés pour cela.
Même si le domaine des archives numériques est vraiment hors de notre d'expertise, en tant que citoyen, il ouvre de nombreuses questions en particulier quand on voit <a href="http://parisculturesociale.over-blog.com/article-archives-nationales-la-memoire-de-la-republique-en-peril-124269207.html">les difficultés rencontrées</a> avec le stockage physique des archives nationales. A court terme, la problématique "archivages" n'est pas encore importante mais la pérennité sur des dizaines d'années sera plus complexe à priori. Une réflexion a été entamée, a déjà eu lieu sur ce sujet?</p>Ouverture du site de Web Geo Data Vore2014-09-19T08:20:00+02:00Thomas Gratiertag:webgeodatavore.com,2014-09-19:ouverture-du-site.html<h2>Une nouvelle aventure</h2>
<p>Depuis la fin de l'année dernière, notre projet de devenir notre propre patron commençait à nous titiller.
Il nous a fallu du temps pour assurer la transition entre le passage du salariat à la création mais chemin faisant l'opération se réalise doucement mais sûrement.
Nous avons fait le choix de rester dans la continuité de nos précédentes expériences. Après plus de 8 ans à faire de la géomatique, nous avons toujours cette passion et cette curiosité que nous espérons mettre à votre service!</p>
<h2>Nos services</h2>
<p>Cela peut se faire à travers :</p>
<ul>
<li>
<p>de l'accompagnement en géomatique, qu'il s'agisse de transitionner d'un modèle propriétaire à un modèle libre ou même d'avoir des solutions mixtes. Nous avons la compétence bureautique comme en ligne même si notre expertise est plus côté WebMapping. Au final, l'essentiel pour nous reste de répondre à votre besoin.</p>
</li>
<li>
<p>de la conception et du développement sur mesure de solution géomatique tant bureautique que web côté client comme serveur</p>
</li>
<li>
<p>de la formation. Dans ce cadre notre expertise est très forte sur le WebMapping que ce soit avec OpenLayers 2 & 3 ou bien Leaflet. Elle est surtout liée à nos compétences de développement côté JavaScript. Nous pouvons aussi vous initier même si vos compétences ne sont pas liées à la cartographie et/ou au développement informatique.</p>
</li>
</ul>
<p>Pour la formation, notre offre n'est pas encore complètement définie.</p>
<h2>Contactez-nous</h2>
<p>Le site s'enrichira dans les semaines et mois à venir.</p>
<p>N'hésitez pas à <a href="/contact.html">nous contacter</a> pour nous poser des questions sur vos besoins.</p>