{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# First step" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial we will use [Brython](http://brython.info/), an implementation of Python written in javascript and Python, to access the OpenLayers javascript library and to manage the data to be used in the maps. To integrate Brython in the IPython notebook we are using an extension for the notebook called [brythonmagic](https://github.com/kikocorreoso/brythonmagic) that provides a new magic cell, **%%brython**, that allow us to write and execute Brython code in the notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Installation of the brythonmagic IPython extension" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As stated before, we will use [Brython](http://www.brython.info), and [brythonmagic](https://github.com/kikocorreoso/brythonmagic) so first of all we need to load the extension and the Brython library.\n", "\n", "So, let's load the extension:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!pip install brythonmagic" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%load_ext brythonmagic" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And the brython js lib:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "\n", " require(\n", " [\n", " \"https://cdn.rawgit.com/brython-dev/brython/master/www/src/brython_dist.js\"\n", " ], \n", " function() {\n", " console.log(\"Loaded js code from https://cdn.rawgit.com/brython-dev/brython/master/www/src/brython_dist.js!\");\n", " }\n", " ); \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from brythonmagic import load_brython_dev\n", "load_brython_dev()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**\\[It is highly recommended that, at least, you read the [brythonmagic docs](https://github.com/kikocorreoso/brythonmagic#usage) to understand what it does. It is also recommended to have a quick look at the [Brython docs](http://www.brython.info/doc/en/index.html)].**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Warning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to load javascript libraries in a safety way you should try to use https instead of http when possible (read more [here](http://mail.scipy.org/pipermail/ipython-dev/2014-July/014572.html)). If you don't trust the source and/or the source cannot be loaded using https then you could download the javascript library and load it from a local location." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conventions used in the following tutorial." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following tutorial I will try to follow several conventions to try to make it more readable.\n", "\n", "Code in cells that are not code cells:\n", "\n", "\n", "* a block of code will appear as follows:\n", "\n", "```python\n", "# This is a block of code\n", "print(\"Hello world!\")\n", "```\n", "\n", "\n", "* Python/Brython code commented in a line of text will appear as follows, **this is a piece of Python/Brython code inline with the text**\n", "\n", "\n", "* Javascript code commented in a line of text will appear as follows, **this is a piece of javascript code inline with the text**\n", "\n", "\n", "* Most of new code used in a code cell will be commented in a paragraph starting with **[NEW CODE]**\n", "\n", "\n", "* When the Python and the javascript code is not exactly the same I will try to comment how the code would be in javascript." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# What is OpenLayers?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**OpenLayers** is an open source, **client side** *JavaScript* library for making **interactive web maps**, viewable in nearly any modern web browser. Since it is a client side library, it requires no special server side software or settings — you can use it without even downloading anything!\n", "\n", "The website for OpenLayers is located at [http://openlayers.org/](http://openlayers.org/). To begin, we need to download a copy of OpenLayers (or, we can directly link to the library — this is what we will do in the present tutorial). You can download the compressed library as either a .tar.gz or .zip, but both contain the same files.\n", "\n", "So, before continuing let's load the OpenLayers library." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "\n", " require(\n", " [\n", " \"https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.11/OpenLayers.js\"\n", " ], \n", " function() {\n", " console.log(\"Loaded js code from https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.11/OpenLayers.js!\");\n", " }\n", " ); \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from brythonmagic import load_js_lib\n", "load_js_lib(\"https://cdnjs.cloudflare.com/ajax/libs/openlayers/2.11/OpenLayers.js\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Right now OpenLayers3 (ol3) is in active development and OpenLayers2 is in maintenance mode. The main idea of ol3 is to simplify the use of the library. From their web page:\n", "\n", "*We've begun the development effort to make the next major version of OpenLayers a reality. OpenLayers 3 is a comprehensive rewrite of the library, targeting the latest in HTML5 and CSS3 features. The library will continue to have broad support for projections, standard protocols, and editing functionality from OpenLayers 2.x. The new version of the library will focus on performance improvements, lighter builds, prettier visual components, an improved API, and more. Some of the major highlights are:*\n", "\n", "\n", "* *WebGL promises to bring 3D capabilities and increased performance for all mapping needs to the latest browsers. OpenLayers 3.0 will offer WebGL, while degrading nicely in less capable browsers.*\n", "\n", "\n", "* *Cesium: The OpenLayers community will also integrate the new Cesium library to enable full 3D spinning globe capabilities directly into the 3.0 release.*\n", "\n", "\n", "* *Closure Compiler: By utilizing the Closure Compiler, applications developers will be able to create smaller and faster libraries, easing the use of the extensive OpenLayers 3.0 toolkit.*\n", "\n", "\n", "* *A new codebase: This offers an opportunity to clean up some of the “clunky” ways of doing things in OpenLayers. The team will also create with new API designs, which will be more accessible to all.*\n", "\n", "\n", "* *High-quality documentation: The new release will also feature documentation with fresh examples and default designs in OpenLayers 3.0. Making a toolkit standout is about more than the actual code.*\n", "\n", "\n", "In this tutorial we will be using OpenLayers2." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Main pieces of our mapping application" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As this is just an introductory tutorial we will be working mainly with the following classes:\n", "\n", "* [`OpenLayers.Map`](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html) : an instance of this class will be the main piece of the mapping app. Their [methods](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.Functions) will be called when we want to zoom to areas, keep track of the layers, play with events,...\n", "\n", "\n", "* [`OpenLayers.Layer`](http://dev.openlayers.org/apidocs/files/OpenLayers/Layer-js.html) : The info/data/... of our map will be an instance of `OpenLayers.Layer` or one of its subclasses. Each map will need, at least, a layer, the base layer." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### First example: A simple map with a base layer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we create some simple HTML code. This HTML code will contain our map. We will not use complicated HTML code during the tutorial to keep it simple and to be focused in *'How to create interactive maps in the browser with Python'*. There is a lot of amazing [resources](http://www.w3schools.com/) to learn about HTML and CSS." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html=\"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now the interesting part. To make OpenLayers(2) available to Brython we need to 'load' the OpenLayers object/namespace to Brython using **OpenLayers = window.OpenLayers**. We will use the **new** method injected by Brython to the Javascript object that would behave similarly as if we were using Javascript constructors, (ie functions used with the Javascript keyword **new**).\n", "\n", "The code is as follows:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " \n", "
\n", " \n", "\n" ] } ], "source": [ "%%brython -h html -p\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex1')\n", "\n", "layer1 = OpenLayers.Layer.OSM.new()\n", "\n", "mymap.addLayer(layer1)\n", "\n", "mymap.zoomToMaxExtent()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pretty simple!!\n", "\n", "Ok, let's dissect the code in the Brython cell above.\n", "\n", "**%%brython -h html -p** :\n", "\n", "* Indicates that the code cell is written using Brython and we use some options for the Brython code cell, `-h` to use the HTML code defined in the `html` variable and `-p` to print the final HTML code generated below the generated map. In this example, the generated code should be something like the following:\n", "\n", "```html\n", " \n", "
\n", " \n", "```\n", "\n", "the `-p` option only provides information and it isn't required to run the Brython code cell.\n", "\n", "**from browser import window**\n", "\n", "* **browser** is a special module available only in Brython that allows to interact with the browser, the document, the window,...\n", "\n", "**OpenLayers = window.OpenLayers**\n", "\n", "* This way we are making the **OpenLayers** object/namespace (JS lib) accesible to the Brython namespace.\n", "\n", "**mymap = OpenLayers.Map.new('map_ex1')**\n", "\n", "* Here we are instantiating the map. It is similar to this javascript code: **var mymap = new OpenLayers.Map('map_ex1');**\n", "\n", "**layer1 = OpenLayers.Layer.OSM.new()**\n", "\n", "* Here we create a simple [raster layer using OSM (OpenStreetMaps) tiles](http://dev.openlayers.org/docs/files/OpenLayers/Layer/OSM-js.html). It is similar to this javascript code: **var layer1 = new OpenLayers.Layer.OSM();**\n", "\n", "**mymap.addLayer(layer1)**\n", "\n", "* We add the created layer to the created map. \n", "* In other cells we will use **mymap.addLayers([layer1, layer2,...])**. We can add several layers once using the method `addLayers` or we can add several layers using several times the method `addLayer`. The following:\n", "\n", "````\n", "map.addLayer(layer1)\n", "map.addLayer(layer2)\n", "````\n", "would be equivalent to:\n", "````\n", "map.addLayers([layer1, layer2])\n", "````\n", "\n", "**mymap.zoomToMaxExtent()**\n", "\n", "* And, finally, we set the zoom to the map. In this case we set the zoom to be the maximum extent." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The map, the fundamental piece!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As commented before, an instance of [`OpenLayers.Map`](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.OpenLayers.Map) will be one of the main parts of our mapping application. It accepts the following inputs:\n", "\n", "\n", "* the name of the DOM element where the map will be rendered, as we saw in our first example, and\n", "\n", "\n", "* a Python dictionary (a javascript object) with the options to be considered (projection to be used, units, projection to display data,...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Main map options" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [projection](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.projection): indicates the projection to use to render the map. It uses [codes](http://spatialreference.org/) from the European Petroleum Survey Group (EPSG). Map projections are beyond the scope of this tutorial but you can find excellent information on the internet. You have to take into account that the WMS layers will be requested using the defined projection for the map if you don't define a map projection for the layer. Owing to this you must be sure the WMS server accepts the projection defined for the map. During the present tutorial we are using the projection [EPSG:4326](http://spatialreference.org/ref/epsg/wgs-84/). Other popular projection for interactive maps is [EPSG:3857](http://wiki.openstreetmap.org/wiki/EPSG:3857) that is the one used by Google and [OpenStreetMap](www.openstreetmap.org). The [EPSG:3857](http://wiki.openstreetmap.org/wiki/EPSG:3857) is also known as 900913 (900913 is GOOGLE written using numbers...).\n", "\n", "\n", "* [units](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.units): a string indicating if the units are in decimal degrees, meters, inches,... It is only mandatory if both map and layers do not define a projection.\n", "\n", "\n", "* [maxExtent and minExtent](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.maxExtent): If provided as an array, the array should consist of four values (left, bottom, right, top). The maximum and the minimum extent for the map, respectively.\n", "\n", "\n", "* [numZoomLevels](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.numZoomLevels): The default value is set to 16 and normally is a good value.\n", "\n", "\n", "* ...\n", "\n", "You can set and/or change the options value at any moment using the **OpenLayers.Map** [methods](http://dev.openlayers.org/apidocs/files/OpenLayers/Map-js.html#OpenLayers.Map.Functions). Some of these *options will not work with Brython* (I have to analyse why) and it is better to use methods to modify a default parameter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Let's see a new example using some options in the map (using methods)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we define the HTML code where the map will be rendered (div element with `id=\"map_ex2\"`) and another div (div element with `id=\"data\"`) where we will print some data of the actual map. The information from the map is included in the function **get_data**. \n", "\n", "**[NEW CODE]** In the example below there is some Brython specific code like the operator `<=`. This operator is a little bit controversial. You can read the last question in the [FAQ of the Brython docs to learn more](http://brython.info/doc/en/static_index.html?page=faq). If you don't like this operator or you think it is confusing you can use an alternative syntax. You can change:\n", "\n", "`element <= element_child`\n", "\n", "with\n", "\n", "`element.appendChild(element_child)`" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html=\"\"\"
\n", "
\n", "
\n", "
\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", "
\n", "
\n", "
\n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import document, html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer (default)\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"},\n", " {\"wrapDateLine\": True})\n", "mymap = OpenLayers.Map.new()\n", "mymap.addLayer(layer1)\n", "\n", "# Set the center\n", "location = OpenLayers.LonLat.new(0, 40)\n", "mymap.setCenter(location, 5)\n", "# We use render method instead of defining the container in the map instance\n", "mymap.render('map_ex2')\n", "\n", "# Now let's extract some info of the actual map and show it in a div container below:\n", "my_div = document[\"data\"]\n", "my_div <= html.BUTTON('grab the info!', Id = \"button_ex2\", Class = \"btn\")\n", "my_div <= html.DIV(Id = \"container_data\")\n", "\n", "def get_data(ev):\n", " data_div = document[\"container_data\"]\n", " data_div.html = \"\"\n", " print('entro')\n", " data_div <= html.P(\"lon center = \" + str(mymap.getCenter().lon))\n", " data_div <= html.P(\"lat center = \" + str(mymap.getCenter().lat))\n", " data_div <= html.P(\"Projection = \" + str(mymap.getProjection()))\n", " data_div <= html.P(\"Bounds (left) = \" + str(mymap.getExtent().left))\n", " data_div <= html.P(\"Bounds (right) = \" + str(mymap.getExtent().right))\n", " data_div <= html.P(\"Bounds (bottom) = \" + str(mymap.getExtent().bottom))\n", " data_div <= html.P(\"Bounds (top) = \" + str(mymap.getExtent().top))\n", " #...\n", " \n", "document[\"button_ex2\"].bind(\"click\", get_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**[NEW CODE]** In the example above we have used **OpenLayers.LonLat.new(0, 40)** (**new OpenLayers.LonLat(0, 40)** in javascript). It is an [object that represents a pair of longitude and latitude coordinates](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/BaseTypes/LonLat-js.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# What is a layer?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A layer is basically a way to show multiple levels of information and each level is independent of each other. As we saw in the previous example, when we want to actually create a layer, we create an object from an OpenLayers Layer class.\n", "\n", "OpenLayers has many different Layer classes, each allowing you to connect to a different type of map server 'back end.' For example, if you want to connect to a WMS map server, you would use the `Layer.WMS` class, and if you want to use Google Maps you'd use the `Layer.Google` class. Each layer object is independent of other layer objects, so doing things to one layer won't necessarily affect the other.\n", "\n", "Whatever the purpose of your web map application is, you will need at least one layer to have a usable map, at least one Base layer. All other layers that 'sit above' the base layer are called Overlay layers. These are the two 'types' of layers in OpenLayers." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Base layers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A **base layer** is at the very bottom of the layer list, and all other layers are on top of it. The base layer is always visible and determines some map properties such as projection and zoom levels.\n", "\n", "The order of the other layers can change, but the base layer is always below the overlay layers. By default, the first layer that you add to your map acts as the base layer. You can, however, change the property of any layer on your map to act as the base layer (by setting the `isBaseLayer` property to `True`). As said before, a map can have more than one base layer but only one of them can be active at a time. When one base layer is turned on, all the other base layers are turned off. In addition, if you add more than one flagged base layer to the map, the first base layer added will be used as the active base layer of the map." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### An example including several potential base layers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we define the container where the map will be included:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html=\"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now, we define two layers and both of them will be treated as base layers but the first one defined would be the base layer as we do not define explicitly which one should be used. The default layer will be a basic map while the other base layer will be the blue marble imagery. We will add a control to choose the base layer to be shown (later we will talk more about predefined controls in OpenLayers)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex3')\n", "\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer (default)\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"},\n", " {\"wrapDateLine\": True})\n", "\n", "layer2 = OpenLayers.Layer.WMS.new(\"Other base layer, Blue Marble\",\n", " \"http://maps.opengeo.org/geowebcache/service/wms\",\n", " {\"layers\":\"bluemarble\"},\n", " {\"wrapDateLine\": True})\n", "\n", "mymap.addLayers([layer1, layer2])\n", "\n", "# We will define this later.\n", "control = OpenLayers.Control.LayerSwitcher.new()\n", "mymap.addControl(control)\n", "\n", "location = OpenLayers.LonLat.new(20, 50)\n", "mymap.setCenter(location, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**[NEW CODE]** In the definition of the layers in the Brython code cell above we included the option **wrapDateLine** and was set to **True**. There might be situations where you do not want your map ends at -180 or +180 longitude degrees as you are working in that area and need a continuous map. This option helps you to achieve this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Arguments of the [`OpenLayers.Layer.WMS`](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Layer/WMS-js.html) layer used in the example above." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the previous example we have seen that we used several arguments in the definition of **layer1** or **layer2**. See, for instance, **layer1** definition below:\n", "```python\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer (default)\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"},\n", " {\"wrapDateLine\": True})\n", "```\n", "\n", "The arguments used are as follows:\n", "\n", "\n", "* `Name`, the first parameter, **\"Base layer (default)\"**, defines the name of the layer.\n", "\n", "\n", "* `Url`, the second parameter, **\"http://vmap0.tiles.osgeo.org/wms/vmap0\"** in this case, is the url of the map server.\n", "\n", "\n", "* `Params`, the third parameter, **{\"layers\": \"basic\"}**, is a Python dictionary (javascript anonymous object). This parameter specifies server side settings that affect the map image, which the WMS server returns. The key:value pairs you pass in here will be appended (more or less) to the URL that OpenLayers generates when it makes requests to the map server. An example of one of the urls generated to get a tile in this example is the following: \n", "\n", " http://vmap0.tiles.osgeo.org/wms/vmap0?LAYERS=basic&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&FORMAT=image%2Fjpeg&SRS=EPSG%3A4326&BBOX=0,45,22.5,67.5&WIDTH=256&HEIGHT=256. \n", "\n", " The params parameter depends on the map server and it is out of the scope of this tutorial to explain all the possibilities.\n", "\n", "\n", "* `Options`, the fourth parameter, **{\"wrapDateLine\": True}** in this case, is a Python dictionary (javascript anonymous object). The options dictionary/object contains [properties for the client side OpenLayers Layer object](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Layer-js.html#OpenLayers.Layer.Properties). These are the settings for the layer object itself, so all Layer classes have this parameter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overlay layers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Overlay layers** (non base layers), however, do not behave the way the base layers work, turning on or off overlay layers will not affect other overlay layers. Base layers are similar to radio buttons, only one can be active at a time. Overlay layers are similar to check boxes, you can have as many on or off as you'd like. Any layer that is not a base layer is called an overlay layer. Like we talked about, the order that you add layers to your map is important. Every time you add a layer to the map, it is placed above the previous one, take this into account!!!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## An example with a Base layer and an Overlay layer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As usual, first the HTML code where the map will be rendered." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html = \"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now the script code. In this case we will use the **OpenLayers.Layer.Image** of the **OpenLayers.Layer** class. More about this subclass [here](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Layer/Image-js.html)." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex4')\n", "\n", "layer1 = OpenLayers.Layer.Image.new('Python',\n", " 'https://farm6.staticflickr.com/5257/5513104816_d73f41eddb_o.jpg',\n", " OpenLayers.Bounds.new(-180,-112.5,180,112.5),\n", " OpenLayers.Size.new(3264,2448),\n", " {\"numZoomLevels\": 7, \n", " \"maxResolution\":.750})\n", "\n", "layer2 = OpenLayers.Layer.Image.new('My Python',\n", " 'https://www.python.org/static/community_logos/python-logo-master-v3-TM.png',\n", " OpenLayers.Bounds.new(-100,-45,100,45),\n", " OpenLayers.Size.new(601,203),\n", " {\"numZoomLevels\": 7, \n", " \"maxResolution\":.750,\n", " \"isBaseLayer\": False,\n", " \"opacity\": 0.25})\n", "\n", "mymap.addLayers([layer1, layer2])\n", "\n", "# We will define this later.\n", "control = OpenLayers.Control.LayerSwitcher.new()\n", "mymap.addControl(control)\n", "\n", "location = OpenLayers.LonLat.new(0, 0)\n", "mymap.setCenter(location, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above we have used one image as a base layer and a new one as an overlay layer. Some arguments used are the same defined above, `name, url` and `options`. \n", "\n", "**[NEW CODE]** We have used two new parameters, \n", "\n", "\n", "* `extent`, i.e. the following in the script **OpenLayers.Bounds.new(-100,-45,100,45)** (equivalent javascript code would be **new OpenLayers.Bounds(-100,-45,100,45)**). This [parameter establish the bounding box](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/BaseTypes/Bounds-js.html#OpenLayers.Bounds.OpenLayers.Bounds) for the image, and \n", "\n", "\n", "* `size`, i.e. the following in the script **OpenLayers.Size.new(601, 203)** (equivalent javascript code would be **new OpenLayers.Size(601, 203)**). This [parameter is the width and height](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/BaseTypes/Size-js.html#OpenLayers.Size) of the image.\n", "\n", "In the options parameter we have included **\"opacity\": 0.25** that tells the layer to have an opacity of 0.25 (values between 0 to make the layer transparent or 1 to be opaque)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Other type of layers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is out of the scope of this tutorial talk about all the available subclasses of the **OpenLayers.Layer** class but at least you should know there are other available as `Google`, `Bing`, `Yahoo`, `Mapguide`, `Vector`,...\n", "\n", "Until now we have seen raster layers (`WMS, Image, Google, Bing,`...) The `Vector` layer will be explained below as it is quite usual and helpful and it is a way to data interesting data in a geographical context." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Vector layers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First of all, what is a vector in this context? A vector uses geometrical shapes based on math equations to form an\n", "image. As main characteristics, the quality is preserved when you zoom in and vector graphics are not constrained to a grid, so they preserve shape at all scales.\n", "\n", "The vector data must be rendered to be seen. OpenLayers supports three ways to render the vector layer: SVG (``tag in HTML5), Canvas (``tag in HTML5) and VML (special case for dirty browsers that don't follow the standards like IE).\n", "\n", "When we are working with vector layers we make use of other classes. The Vector class makes use of the Feature class to show objects on the layer. And in turn for the Feature class to work, it needs to use the Geometry class to create geometry objects. Therefore, the actual vector objects in your vector layers are Feature objects which are composed of Geometry objects. It seems more complicated than using raster layers so let's see an example before to see a vector layer working." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## An example of a vector layer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The easiest way to better understand what is a Vector layer, what is a feature, what is a geometry, etc, is to play a little bit with it and later we will dive deeper on the concepts. First the HTML code:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html = \"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we will draw a map using OSM as the base layer and an empty vector layer. We also add a new control - more on OpenLayers built-in controls later - to draw some elements in the vector layer. Play a little bit with it, located on the upper rigt side of your map, and later we will continue:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex5')\n", "\n", "layer1 = OpenLayers.Layer.OSM.new()\n", "layer_vector = OpenLayers.Layer.Vector.new('My vector layer')\n", "\n", "mymap.addLayers([layer1, layer_vector])\n", "\n", "# We will define this later.\n", "control_vector = OpenLayers.Control.EditingToolbar.new(layer_vector)\n", "mymap.addControl(control_vector)\n", "\n", "location = OpenLayers.LonLat.new(0, 40)\n", "mymap.setCenter(location, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**[NEW CODE]** We have used a new built-in control and a new layer type, \n", "\n", "\n", "+ **control_vector = OpenLayers.Control.EditingToolbar.new(layer_vector)** (equivalent javascript code would be **var control_vector = new OpenLayers.Control.EditingToolbar(layer_vector)**). This control allow us to insert some information attached to the vector layer defined (**layer_vector** in the example above). More on controls later.\n", "\n", "\n", "* **layer_vector = OpenLayers.Layer.Vector.new('My vector layer')** (equivalent javascript code would be **var layer_vector = new OpenLayers.Layer.Vector('My vector layer')**). This is an `OpenLayers.Layer` subclass to work with vector information." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `OpenLayers.Layer.Vector` subclass has many [methods](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Layer/Vector-js.html#OpenLayers.Layer.Vector.Functions) to manipulate data and to add interactivity to the features, the layer and the map itself. Let's work with some of them. In the following paragraphs we will try to explain how to add some features programmatically using some methods of the vector layer class.\n", "\n", "As I stated before, The Vector class makes use of the Feature class to show objects on the layer. And in turn for the Feature class to work, it needs to use the Geometry class to create geometry objects. The `Feature.Vector` class uses the Geometry class to store geometry information (i.e., it stores geographic information) about a feature.\n", "\n", "If you have some knowledge of GIS you will have worked with [points](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Geometry/Point-js.html), [polygons](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Geometry/Polygon-js.html), [linear rings](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Geometry/LinearRing-js.html), [lines or paths](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Geometry/MultiLineString-js.html),... All these 'things' are usually called geometries in the GIS world. OpenLayers has several subclasses that inherit from the `OpenLayers.Geometry` class to define geometries. For example, to create a Point you can use the following javascript code, **new OpenLayers.Geometry.Point(20, 40);**, i.e., a point at the coordinate 20, 40.\n", "\n", "The Feature class is what the Vector class uses to actually show Geometry objects on the map. The base Feature class is composed of two things—Geometry objects (as you've seen), and attributes. The attributes contain data associated with the feature. There is only one subclass we'll be making use of — the `OpenLayers.Feature.Vector` class. Like its parent Feature class, it is composed of a Geometry object and contains an attributes property. In addition, there is a style property which controls what the feature looks like. The feature object will accept a geometry object, some attributes related with the object (a Python dictionary/a javascript anonymous object) and a style object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll continue with our 'learn by doing' strategy so here is a new example to practice with geometries, features and vector layers. " ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html = \"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will plot data from the Hurricane Vince, occurred in 2005 around the Iberian Peninsula (Portugal and Spain)." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex6')\n", "\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"})\n", "layer_vector_points = OpenLayers.Layer.Vector.new('Points')\n", "\n", "# Hurricane Vince data\n", "# http://www.nhc.noaa.gov/pdf/TCR-AL242005_Vince.pdf\n", "lats = [32.9, 33.0, 33.1, 33.2, 33.4, 33.8, 34.1, 34.3, \n", " 34.5, 34.7, 35.4, 36.1, 36.7, 37.7, 37.2]\n", "lons = [-20.6, -20.3, -20.1, -20.0, -19.6, -19.3, -18.9, \n", " -18.3, -17.2, -15.3, -12.8, -10.5, -8.3, -6.0, -7.1]\n", "\n", "# Points of the Vince locations each 6 hours\n", "feats = []\n", "for lon, lat in zip(lons, lats):\n", " point = OpenLayers.Geometry.Point.new(lon, lat)\n", " feat = OpenLayers.Feature.Vector.new(point)\n", " feats.append(feat)\n", "layer_vector_points.addFeatures(feats)\n", "\n", "#mymap.addLayer(layer1)\n", "mymap.addLayers([layer1,layer_vector_points])\n", "\n", "# We will define this later.\n", "control = OpenLayers.Control.LayerSwitcher.new()\n", "mymap.addControl(control)\n", "\n", "center = OpenLayers.LonLat.new(-10, 35)\n", "mymap.setCenter(center, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**[NEW CODE]** We have used a new built-in control and a new layer type, \n", "\n", "\n", "+ **point = OpenLayers.Geometry.Point.new(lon, lat)** (equivalent javascript code would be **var point = new OpenLayers.Geometry.Point(lon, lat)**). This control allow us to create point geometries at desired locations.\n", "\n", "\n", "* **feat = OpenLayers.Feature.Vector.new(point)** (equivalent javascript code would be **var feat = new OpenLayers.Feature.Vector(point)**). It allows us to include geometry information in the vector layer.\n", "\n", "\n", "* **layer_vector_points.addFeatures(feats)** (code is similar in javascript). Add the feature to the layer." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Adding built-in controls to your map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without controls, our map will not be interactive so, in essence, controls allow us to interact with the map. There are several built-in controls provided by the OpenLayers library: controls to zoom in and out, to play with layers, to show a scale bar, to measure distances,... We already have seen some of this controls.\n", "\n", "The `OpenLayers.Control` class is the base class for all the controls and contains the\n", "common properties and methods that a control can have. Some controls are added automatically to the map. In the previous examples we saw that controls to zoom in and zoom out are available by default, we saw that we can use the mouse to move the map,..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see a new map including some non-default controls. In this case we will add a scale line and a control to display the position of the mouse. First the html to be used in the example:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html = \"\"\"
\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now the Brython code:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex7')\n", "\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"})\n", "\n", "mymap.addLayer(layer1)\n", "\n", "# Scale bar control.\n", "control_scale = OpenLayers.Control.ScaleLine.new()\n", "# Mouse position control.\n", "control_mouse_pos = OpenLayers.Control.MousePosition.new()\n", "\n", "mymap.addControls([control_scale, control_mouse_pos])\n", "\n", "center = OpenLayers.LonLat.new(-10, 35)\n", "mymap.setCenter(center, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**[NEW CODE]** We have used two new built-in controls, \n", "\n", "\n", "+ **control = OpenLayers.Control.ScaleLine.new()** (equivalent javascript code would be **var control = new OpenLayers.Control.ScaleLine()**). This control will display a scale bar. If you zoom in or zoom out you will see how the scale bar is updated.\n", "\n", "\n", "* **control = OpenLayers.Control.MousePosition.new()** (equivalent javascript code would be **var control = new OpenLayers.Control.MousePosition()**). It will display the coordinates of the mouse location over the map.\n", "\n", "Remember that we saw other non-default controls in previous examples:\n", "\n", "\n", "* `OpenLayers.Control.LayerSwitcher`. It adds a control to play with layers.\n", "\n", "\n", "* `OpenLayers.Control.EditingToolbar(`*layer*`)`. It add controls to create and edit vector features." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## OpenLayers.Control class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [`OpenLayers.Control` class](http://dev.openlayers.org/releases/OpenLayers-2.13.1/doc/apidocs/files/OpenLayers/Control-js.html) in the base class used by the built-in controls that we have been using in the previous examples. Some methods available on each subclass are `destroy()`, `moveTo(location)`, `activate()`, `deactivate()` and `draw()`. If you are very clever you will guess the actions obtained using these methods, if you are like me you could visit the official docs linked above :-P" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will see some of the subclasses of the `OpenLayers.Control` class:\n", "\n", "\n", "* `OpenLayers.Control.Permalink`. This control will add a link (Permalink) with the current longitude, latitude, zoom and visible layers of the map. It will appear in the bottom right side in the following example.\n", "\n", "\n", "* `OpenLayers.Control.Attribution`. It show some information of the actual map (or whatever you want). It is added using the attribution property in the layer you want to provide information. It will appear in the bottom right side in the following example.\n", "\n", "\n", "* `OpenLayers.Control.KeyboardDefaults`. If we set this control we can control our map by pressing the arrow keys, plus/minus keys, or home keys, the map will be moved or zoomed. There is one relevant property we should take a look at.\n", "\n", "\n", "* `OpenLayers.Control.Graticule`. It adds a grid to the map.\n", "\n", "\n", "* `OpenLayers.Control.Navigation`. This control is added by default to the map and it provides the ability to interact with the map using the mouse.\n", "\n", "\n", "* `OpenLayers.Control.NavigationHistory`. Yes, you guessed it. History of the navigation. This is not shown in the example below.\n", "\n", "\n", "* `OpenLayers.Control.NavToolbar`, `OpenLayers.Control.PanZoom`, `OpenLayers.Control.PanZoomBar`, `OpenLayers.Control.ZoomPanel` and `OpenLayers.Control.PanPanel`. These controls offer the same than that we can get using the mouse, controls to pan and zoom and displace around the map. They are located in the top left side of the map. In the example below we use `OpenLayers.Control.PanZoomBar`.\n", "\n", "\n", "* `OpenLayers.Control.Scale`. It adds ratio scale information on the right bottom side of the map.\n", "\n", "\n", "* `OpenLayers.Control.OverviewMap`. It adds a panel to see the displayed map, current position map, in a more general map. It is a toggle-able window located on the right bottom side of the map." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [], "source": [ "html = \"\"\"
\"\"\"" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ " \n", "
\n", " \n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%brython -h html\n", "from browser import window\n", "\n", "OpenLayers = window.OpenLayers\n", "\n", "mymap = OpenLayers.Map.new('map_ex8',\n", " # the following line will clean the default controls\n", " {\"controls\": []})\n", "\n", "layer1 = OpenLayers.Layer.WMS.new(\"Base layer\",\n", " \"http://vmap0.tiles.osgeo.org/wms/vmap0\",\n", " {\"layers\": \"basic\"},\n", " # The following line is used to add the attribution\n", " {\"attribution\": \"Python cool maps factory\"})\n", "\n", "mymap.addLayer(layer1)\n", "\n", "# Permalink control.\n", "control_perma = OpenLayers.Control.Permalink.new()\n", "\n", "# Attribution control (default control). \n", "# As we have removed all default controls, see the map instance,\n", "# we should explicitly use this.\n", "control_attrib = OpenLayers.Control.Attribution.new()\n", "\n", "# Graticule control.\n", "control_grat = OpenLayers.Control.Graticule.new()\n", "\n", "# Toolbar to move up, down, left and right, zoom and pan.\n", "control_toolbar = OpenLayers.Control.PanZoomBar.new()\n", "\n", "# Position of the mouse in the map coordinates.\n", "control_mouse_pos = OpenLayers.Control.MousePosition.new()\n", "\n", "# Default keyboard controls.\n", "control_keyb = OpenLayers.Control.KeyboardDefaults.new()\n", "\n", "# Add scale information.\n", "control_scale = OpenLayers.Control.Scale.new()\n", "\n", "# Navigation control. This control is added by default.\n", "# As we have removed all default controls, see the map instance,\n", "# we should explicitly use this.\n", "control_navi = OpenLayers.Control.Navigation.new()\n", "\n", "# OverviewMap control.\n", "control_overview = OpenLayers.Control.OverviewMap.new()\n", "\n", "mymap.addControls([control_perma, control_attrib, control_grat,\n", " control_toolbar, control_mouse_pos,\n", " control_keyb, control_scale,\n", " control_navi, control_overview])\n", "\n", "center = OpenLayers.LonLat.new(-10, 35)\n", "mymap.setCenter(center, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could also create custom controls using OpenLayers (this is out of the scope of the present tutorial) or using vanilla Brython/javascript (see example 2 in this tutorial to see a simple case)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Final remarks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the present tutorial we have seen very briefly how to create maps, how to use raster and vector layers, how to add controls, how to create custom controls,..., using Python/Brython instead of Javascript. Maybe some OpenLayers stuff will not work by default with Brython (if you find an issue, please, let me know) but most of this javascript library is accesible by Brython using a more pythonic syntax.\n", "\n", "If you want to learn more about OpenLayers you have excellent books [here](http://www.packtpub.com/search?keys=openlayers&sort=0&types=0&forthcoming=1&available=1&count=20&op=Go).\n", "\n", "If you have suggestions, want to provide feedback, report bugs, ask questions,..., you can open an issue [here](https://github.com/kikocorreoso/brythonmagic/issues?state=closed)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 0 }