Mireo WEB map Developer’s Guide

Version information

This document is developer’s guide for Mireo WEB map API version 0.8.

Table of Contents

Audience

This documentation is designed for people familiar with

JavaScript
programming and object-oriented programming concepts. There are many
JavaScript
tutorials
available on the Web.

This conceptual documentation is designed to let you quickly start
exploring and developing applications with the Mireo WEB maps API. We also
publish the Mireo WEB maps
API Reference
.

Loading the Mireo WEB maps API

The Mireo WEB maps API is loaded into HTML page using script tag, typically:

    <script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8">

The query string of the URL contains parameters:

  • fun=load_api: mandatory
  • scope=europe-osm: mandatory, if scope is not specified, API will not be loaded
  • v=0.8: mandatory, if version is not specified, API will not be loaded
  • lang=en_uk: optional, if language is not specified, default is used (English UK)
  • excl_imgs=0: optional, if exclusion image flags are not specified, API with support for all stock images is loaded

The complete list of available scopes for the commercial users of API can be provided in direct contact to Mireo.

The last version is currently 0.8, normally one previous version (currently 0.5) is still supported. We strongly advise use of latest version.

The lang parameter is relevant for transforming results returned by invoking
find_journey method of a
mireo.router object, when advices are requested (i.e.
with_advices is set to non zero value). These advices will be generated
using specified language.

Currently supported values are

  • en_uk: English UK
  • hr: Croatian
  • pt_br: Portuguese Brazilian
  • gr: Greek

Additionally, special value none is also supported, in this case Advisor part of the API, used internally by the
mireo.router class, is not loaded at all. This means using
with_advices is meaningless, result will have no advice list.

The excl_imgs is bitmask specifying what image data (and code using it) not to return with the API. The default value 0
means all stock imagery files will be returned. The bits in the mask mean:

Naturally, setting this value to 3 will not return either of them.

The purpose of this parameter is to enable users who do not want to use (some of) stock imagery (perhaps they want to use their own), to have
option not to load stock imagery thus greatly reducing the shear size of the loaded API.

Regarding mireo.advisor_stock_icons not being returned, it is worth noting
that unlike the lang=none situation, the advisor part of the API is returned, and route result object will contain list
of advices (if with_advices was set), only the stock icons representing
particular advice/maneuver will not be available.

Cached Loading the Mireo WEB maps API

The method for loading API as described in
Loading the Mireo WEB maps API
returns complete API as one chunk of data, that is not cached (i.e. caching headers are not returned in response).

A different approach is to return API in several chunks that are returned with caching HTTP headers, and for this approach it makes difference
if API is loaded synchronously or asynchronously. For backward compatibility reasons, this approach is implemented with new REST function
load_api_cached.

This function takes the same parameters as load_api, and in this case loads API synchronously. If an application
requires asynchronously loaded API, additional parameter &callback=<function> must be provided.

	<script type="text/javascript" src="http://world.mireo.hr/wm2/api?fun=load_api_cached&scope=europe-osm&v=0.8"></script>
	<script type="text/javascript" src="../js/eur-demo.js"></script>
	<script>
		window.onload = run_demo;
	</script>

To load API synchronously, we first load script that calls load_api_cached without callback parameter. In the next line,
we load JavaScript file containing run_demo function, and then finally we assign run_demo to
window.onload callback.

View sample on synchronous API loading.

	<script type="text/javascript" src="../js/eur-demo.js"></script>
	<script type="text/javascript" src="http://world.mireo.hr/wm2/api?fun=load_api_cached&scope=europe-osm&v=0.8&callback=run_demo"></script>

To load API asynchronously, the JavaScript file containing function run_demo must be loaded first, and then
load_api_cached is called with callback parameter.

View sample on asynchronous API loading.

Hello World

The easiest way to start is to see simple example code. The code shown below will display map
centered south of the Zagreb, Croatia.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
    <title>Mireo Map development</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
            margin: 0; padding: 0; }
        #map-container {
            position: absolute;
            left: 6px; top: 6px; 
            right: 6px; bottom: 6px; 
            border: 1px solid #cccccc;
        }
    </style>

    <script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8">
    </script>
    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 16
                /*, scale_control: false, zoom_control: false*/ });
            wm.on('tap',function(e){
                alert('map tapped:\n\tpt: '+e.pt+'\n\twgs: '+e.wgs);
            });
        };
    </script>
</head>

<body>
    <div id="map-container"></div>
</body>
</html>

View this example.

This simple example illustrates some steps that always have to be made:

  1. application must be declared as HTML5
  2. Mireo WEB map API must be loaded
  3. DOM element that will contain map must declared
  4. The map must be loaded

View this example.

Declaring your application as HTML5

It is highly recommended that application is declared as HTML5, which is obtained by the very first line:

<!DOCTYPE html>

We also recommend to explicitly set the UTF-8 as character set, since search engine expects UTF-8 text to be passed.
This is obtained by the line:

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

Finally, we define some style elements:

    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
            margin: 0; padding: 0; }
        #map-container {
            position: absolute;
            left: 6px; top: 6px; 
            right: 6px; bottom: 6px; 
            border: 1px solid #cccccc;
        }
    </style>

The body and html styles are needed for the standards mode of some browsers.

The map-container defines the style for the map container HTML element itself.

Loading Mireo WEB map API

The Mireo WEB map API itself is loaded with line:

    <script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8">
    </script>

The function has to be load_api (?fun=load_api), and version has to be 0.8 (?v=0.8).
The region your application covers is defined by scope (?scope=europe-osm), which is the europe in this example.

Map DOM elements

The body of the document must contain the HTML element where map will be drawn. In this example, the DIV is used:

    <div id="map-container"></div>

Note that it must have the id defined, so style defined above can be applied to it.

Loading the Map

Finally, the map has to be created and loaded. This is done with following code:

    </script>
    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 16
                /*, scale_control: false, zoom_control: false*/ });
            wm.on('tap',function(e){
                alert('map tapped:\n\tpt: '+e.pt+'\n\twgs: '+e.wgs);
            });
        };
    </script>

The map object is passed the HTML element it will be drawn within,
along with initial center position and zoom level. The commented-out options show how could
scale_control and zoom_control
be disabled.

Map events

Map is capable of handling normally expected events (dragging, zooming in on mouse wheel event), but from the developer’s point of view,
interesting capability is to handle
tap
events on the map, as following code snippet indicates:

            wm.on('tap',function(e){
                alert('map tapped:\n\tpt: '+e.pt+'\n\twgs: '+e.wgs);
            });

The more profound example of using tap can be found in the
searching the map example.

Back to top.

Marker on the map

The mireo.map.marker class represents icon drawn on the the map.
It is often used in conjuction with some other classes, but can be used as standalone object on the map as well.

A sample code that presents possibilites of the mireo.map.marker class:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
    <title>Mireo Map development</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
            margin: 0; padding: 0; }
        #map-container {
            position: absolute;
            left: 156px; top: 6px; 
            right: 6px; bottom: 6px; 
            border: 1px solid #cccccc;
        }
        #event-log {
            position: absolute;
            left: 6px; top: 6px;
            bottom: 6px;
            width: 145px;
            border: 1px solid #cccccc;
            background-color: #FAFAFA;
            overflow: scroll;
            display: block;
            color: #A01040;
            font-weight: bold;
        }
    </style>
    <script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8">
    </script>
    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 16 });

            var m1 = new mireo.map.marker({
                icon: new mireo.map.marker.image({
                    url: "../img/pins.png",
                    offset: new mireo.base.point(36 * 4, 0),
                    anchor: new mireo.base.point(18, 48),
                    size: new mireo.base.size(36, 48),
					img_size: new mireo.base.size(324, 48)
                }),
                handle_input: true,
                draggable: false,
                title: 'Sample marker',
                position: center,
                z_order: 100,
                map: wm
            });
            
            var event_div = document.getElementById('event-log');
            
            m1.on("tap",function(e){
                m1.draggable(!m1.draggable());
                event_div.innerHTML+="m1 tapped<br>";
                event_div.innerHTML+=" (draggable: "+m1.draggable()+")<br>";
            });
            m1.on("select",function(e){event_div.innerHTML+="m1 select<br>";});
            m1.on("unselect",function(e){event_div.innerHTML+="m1 unselect<br>";});
            m1.on("long_press_begin",function(e){event_div.innerHTML+="m1 lp begin<br>";});
            m1.on("long_press_cancel",function(e){event_div.innerHTML+="m1 lp cancel<br>";});
            m1.on("long_press_end",function(e){event_div.innerHTML+="m1 lp end<br>";});
            m1.on("pan_start",function(e){event_div.innerHTML+="m1 pan start<br>";});
            m1.on("pan_move",function(e){event_div.innerHTML+="m1 pan move<br>";});
            m1.on("pan_end",function(e){event_div.innerHTML+="m1 pan end<br>";});
            m1.on("changed",function(e){event_div.innerHTML+="m1 changed<br>";});
            
            var display_mouseoverout = false;
            m1.on("multitap",function(e){
                display_mouseoverout = !display_mouseoverout;
                event_div.innerHTML+="m1 multitapped<br>";
                event_div.innerHTML+=" (mouseover/out: "+display_mouseoverout+")<br>";
            });
            m1.on("mouseover",function(e){
                if(!display_mouseoverout) return;
                event_div.innerHTML+="m1 mouseover<br>";
            });
            m1.on("mouseout",function(e){
                if(!display_mouseoverout) return;
                event_div.innerHTML+="m1 mouseout<br>";
            });
	        var m2 = new mireo.map.marker({
	            icon: mireo.stock_pins.pin_circle_red(),
	            handle_input: true,
	            draggable: false,
	            title: 'Sample stock marker',
	            position: pos,
	            z_order: 100,
	            map: wm
	        });
	        m2.icon().text("2", { "font-size": "12px" });
	        m2.on("tap", function(e) {
	            m2.draggable(!m2.draggable());
	            event_div.innerHTML += "m2 tapped
"; event_div.innerHTML += " (draggable: " + m2.draggable() + ")
"; }); }; var clear_log_console = function() { document.getElementById('event-log').innerHTML = ""; }; </script> </head> <body> <div id="event-log" onclick=clear_log_console()></div> <div id="map-container"></div> </body> </html>

View the example of marker.

Naturally, map object has to be created first.

Compared to simple map source code sample, we have added style definition for the second div element
that plays the role of the "event log":

        #event-log {
            position: absolute;
            left: 6px; top: 6px;
            bottom: 6px;
            width: 145px;
            border: 1px solid #cccccc;
            background-color: #FAFAFA;
            overflow: scroll;
            display: block;
            color: #A01040;
            font-weight: bold;
        }

The creation of the map marker is done with following code:

            var m1 = new mireo.map.marker({
                icon: new mireo.map.marker.image({
                    url: "../img/pins.png",
                    offset: new mireo.base.point(36 * 4, 0),
                    anchor: new mireo.base.point(18, 48),
                    size: new mireo.base.size(36, 48)
                }),
                handle_input: true,
                draggable: false,
                title: 'Sample marker',
                position: center,
                z_order: 100,
                map: wm
            });

Each marker instance has to have icon/image associated with it (without it, it is simply not visible).
We have specified url to the image file on server disk, and also some offset within this image file (several “pins” are contained).

The handle_input flag must be set to true if the intended use of this object requires it to react
to input events.

The draggable flag is set to false, so this particular marker is not draggable on the map.

The title sets the standard HTML5 title attribute, the
position places this marker on the map. The
z_order value specifies the relative z-order to the map.

Finally, the map parameter is crucial, because each map object adds itself to specified map, and if omitted, it would have
been null, and consequently would not be seen on the map.

Event handling

The code that follows creation of the marker instance demostrates how to use the event handling via
on function, for each event accepted by
marker class.

The first handler is for the "tap" event, and it actually does something aside writing to the console:

            m1.on("tap",function(e){
                m1.draggable(!m1.draggable());
                event_div.innerHTML+="m1 tapped<br>";
                event_div.innerHTML+=" (draggable: "+m1.draggable()+")<br>";
            });

In this example, each tap on the marker changes its draggable state.
So, if you press on this marker and start dragging, you will see that actually the whole map is being dragged. But if you tap it first and then repeat this action,
you will see that the marker is being dragged on the (still) map.

The following events:

            m1.on("select",function(e){event_div.innerHTML+="m1 select<br>";});
            m1.on("unselect",function(e){event_div.innerHTML+="m1 unselect<br>";});
            m1.on("long_press_begin",function(e){event_div.innerHTML+="m1 lp begin<br>";});
            m1.on("long_press_cancel",function(e){event_div.innerHTML+="m1 lp cancel<br>";});
            m1.on("long_press_end",function(e){event_div.innerHTML+="m1 lp end<br>";});

are just being logged to console, and are applicable to non-draggable marker.
The select/unselect correspond to usual mouse down/up events (to actually get the unselect event, you need to depress mouse really fast).
The long press events happen if you hold mouse down a while: the difference between the end and cancel is whether you moved the mouse in the meantime (cancel), or not (end).

The following event hanlders for this object:

            m1.on("pan_start",function(e){event_div.innerHTML+="m1 pan start<br>";});
            m1.on("pan_move",function(e){event_div.innerHTML+="m1 pan move<br>";});
            m1.on("pan_end",function(e){event_div.innerHTML+="m1 pan end<br>";});
            m1.on("changed",function(e){event_div.innerHTML+="m1 changed<br>";});

are observable if and only if the marker is draggable, and these implementations only log to the console.

For the multitap event handler, we have added toggling of the variable defined previously:

            var display_mouseoverout = false;
            m1.on("multitap",function(e){
                display_mouseoverout = !display_mouseoverout;
                event_div.innerHTML+="m1 multitapped<br>";
                event_div.innerHTML+=" (mouseover/out: "+display_mouseoverout+")<br>";
            });

, so we control how will the remaining two event handlers behave:

            m1.on("mouseover",function(e){
                if(!display_mouseoverout) return;
                event_div.innerHTML+="m1 mouseover<br>";
            });
            m1.on("mouseout",function(e){
                if(!display_mouseoverout) return;
                event_div.innerHTML+="m1 mouseout<br>";
            });

So, if the display_mouseoverout is set to false, you will not see anything logged to the console, and multitap modifies this flag.
The mouseover and mouseout handlers are always available, i.e. even if both draggable and
handle_input are set to false.

Finally, we defined additional function:

        var clear_log_console = function() {
            document.getElementById('event-log').innerHTML = "";
        };

whose sole purpose is to clear the text in the "event log".

The body element has new div element:

    <div id="event-log" onclick=clear_log_console()></div>

Back to top.

Arrow on the map

The mireo.map.arrow object is simply arrow drawn on the map, defined with its
position and angle.
The object itself does not take any inputs, and its state should be modified programatically.

The code snippet:

    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 16 });
            var pts = [
                new mireo.wgs.point(center.lat - 20 / 1000, center.lng - 150 / 1000),
                new mireo.wgs.point(center.lat - 20 / 1000, center.lng + 150 / 1000)];
            var angles = [-135, 135];
            var cur_idx = 0;
                
            var ar = new mireo.map.arrow({
                z_order: 30,
                position: pts[0],
                angle: angles[0],
                map: wm
            });
                
            wm.on('tap',function(e){
                cur_idx = ++cur_idx % 2;
                ar.position(pts[cur_idx], angles[cur_idx]);
            });
        };
    </script>

View the example of arrow on the map.

As usually, first we define the map itself, and then we define two sets of position-angle values.

The z_order as usually defines relative z ordering to other map objects, while
position and angle
define its initial state. As for any other map object, the instance of map must be passed in order to be added
to the map object collection.

Finally, we have added tap handler to the map, that modifies the state of this arrow object:

            wm.on('tap',function(e){
                cur_idx = ++cur_idx % 2;
                ar.position(pts[cur_idx], angles[cur_idx]);
            });

Therefore, on each tap the arrow will change its state by invoking the position function.

Back to top.

Polygon on the map

The mireo.map.polygon object draws a polygon on the map, with either interior or exterior
(inverted set to true)
of the polygon being shaded. The polygon might be editable. Only one of polygons
can be selected, and that polygon will change color to blue and
corners of it (and barycenter) are represented by draggable "marker-holders", i.e. objects that can be dragged similar to the
markers.

New polygon can be manually created, using interactive_draw_polygon feature of the
map.

The code sample drawing polygon (from sample-polygon.js):

window.onload = function() {

	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var pts = [
		// p1
		new mireo.wgs.point(center.lat - 10 / 1000, center.lng - 10 / 1000),
		new mireo.wgs.point(center.lat + 0 / 1000, center.lng - 10 / 1000),
		new mireo.wgs.point(center.lat + 10 / 1000, center.lng - 20 / 1000),
		new mireo.wgs.point(center.lat + 0 / 1000, center.lng - 20 / 1000),
		// p2
		new mireo.wgs.point(center.lat + 5 / 1000, center.lng - 5 / 1000),
		new mireo.wgs.point(center.lat + 15 / 1000, center.lng - 15 / 1000),
		new mireo.wgs.point(center.lat + 15 / 1000, center.lng + 15 / 1000),
		// p3
		new mireo.wgs.point(center.lat + 0 / 1000, center.lng + 0 / 1000),
		new mireo.wgs.point(center.lat + 10 / 1000, center.lng + 20 / 1000),
		new mireo.wgs.point(center.lat - 15 / 1000, center.lng + 20 / 1000),
		new mireo.wgs.point(center.lat - 10 / 1000, center.lng + 0 / 1000)
		];
	var polygons = [];

	var map_div = document.getElementById('map-container');
	var wm = new mireo.map(map_div, { center: center, zoom: 13 });

	wm.on("long_press_begin", function(e) {
		if (this._drawing) return;
		this._drawing = true;
		wm.interactive_draw_polygon(e.wgs, draw_finished);
	}, this);

	wm.on("key_up", function(e) {
		if (e.key_code == 27 /* ESC */) {
			wm.interactive_draw_polygon_cancel();
			this._drawing = false;
		}
		if (e.key_code == 46 /* DEL */) {
			for (var i = 0; i < polygons.length; i++) {
				if (polygons[i].selected()) {
					polygons[i].map(null);
					polygons.splice(i, 1);
					break;
				}
			}
		}
	}, this);

	var draw_finished = function(polygon) {
		this._drawing = false;
		if (!polygon) return;
		polygon.on("right_mouseup", function(e) { on_right_mouse_up(polygon); });
		polygon.style({
			shape: {
				fill: "hsla(" + Math.round(Math.random() * 4) * 30 + ", 100%, 50%, 0.7)",
			}
		});
		polygons.push(polygon);
	}

	var on_right_mouse_up = function(polygon) {
		var editable = !polygon.editable();
		polygon.editable(editable);
		polygon.selected(editable);
		polygon.style({
			edge: mireo.utils.update(polygon.style().edge, {
				stroke: editable ? "rgba(0, 0, 255, 0.7)" : polygon.style().shape.fill
			})
		});
	}

	var p1 = new mireo.map.polygon({
		editable: true,
		z_order: 10,
		points: pts.slice(0, 4),
		map: wm
	});
	p1.on("right_mouseup", function(e) { on_right_mouse_up(p1); });

	var p2 = new mireo.map.polygon({
		editable: false,
		style: {
			shape: {
				fill: "rgba(255, 255, 0, 0.5)"
			},
			edge: {
				line_width: 1,
				line_cap: "round",
				line_join: "round",
				miter_limit: 0,
				stroke: "rgba(255, 255, 0, 1)"
			}
		},
		z_order: 100,
		points: pts.slice(4, 7),
		map: wm
	});
	p2.on("right_mouseup", function(e) { 
		p2.inverted(!p2.inverted());
	});

	var p3 = new mireo.map.polygon({
		editable: false,
		z_order: 10,
		points: pts.slice(7, 11),
		style: {
			shape: {
				fill: "rgba(255, 0, 0, 0.5)"
			},
			edge: {
				line_width: 3,
				line_cap: "round",
				line_join: "round",
				miter_limit: 0,
				stroke: "rgba(255, 0, 0, 0.7)"
			}
		},
		map: wm
	});
	p3.on("right_mouseup", function(e) { on_right_mouse_up(p3); });


	polygons.push(p1);
	polygons.push(p2);
	polygons.push(p3);
};

View the example of polygon on the map.

We first create map itself, and prepare array of pts that we will use
to create polygons, and also prepare the polygons array as storage container for created polygons.

In the following lines of code:

	wm.on("long_press_begin", function(e) {
		if (this._drawing) return;
		this._drawing = true;
		wm.interactive_draw_polygon(e.wgs, draw_finished);
	}, this);

	wm.on("key_up", function(e) {
		if (e.key_code == 27 /* ESC */) {
			wm.interactive_draw_polygon_cancel();
			this._drawing = false;
		}
		if (e.key_code == 46 /* DEL */) {
			for (var i = 0; i < polygons.length; i++) {
				if (polygons[i].selected()) {
					polygons[i].map(null);
					polygons.splice(i, 1);
					break;
				}
			}
		}
	}, this);

	var draw_finished = function(polygon) {
		this._drawing = false;
		if (!polygon) return;
		polygon.on("right_mouseup", function(e) { on_right_mouse_up(polygon); });
		polygon.style({
			shape: {
				fill: "hsla(" + Math.round(Math.random() * 4) * 30 + ", 100%, 50%, 0.7)",
			}
		});
		polygons.push(polygon);
	}

	var on_right_mouse_up = function(polygon) {
		var editable = !polygon.editable();
		polygon.editable(editable);
		polygon.selected(editable);
		polygon.style({
			edge: mireo.utils.update(polygon.style().edge, {
				stroke: editable ? "rgba(0, 0, 255, 0.7)" : polygon.style().shape.fill
			})
		});
	}

we prepare the interactive polygon drawing on the wm object.
We have chosen to use long press begin event to enter the
interactive polygon drawing mode.
The interactive polygon drawing mode functions in following manner:

  • long press begin starts the interactive drawing mode: the cursor changes to cross-hair
  • moving mouse around map draws candidate edge of the polygon
  • making a tap on the map adds new point to the polygon, thus defining edge
  • dragging the map can normally be done when in interactive drawing mode
  • when tap is made into the marker-holder of the starting point, polyong is closed, map creates polygon object, exits the interactive polygon mode and calls
    the callback provided: in our example, that is the draw_finished function

In case user would like to manually abort the interactive drawing mode, all s/he should do is press ESC key. This is not inherent to the
polygon object, that is convenience code provided by this sample.

The key up event handler for our wm object also handles the
DEL key, to provide user means to remove polygon from the map. Naturally, the
selected polygon will be removed. Note the
call to polygon.map(null): it is necessary to first remove object from the map, and then delete
it proper.

In the draw_finish function, we add right_mouseup event handler
to each newly created polygon, and that handler toggles the editable state by
calling the on_right_mouseup function: if the state changed from non-editable to editable, the edges are emphasized.

Finally, we create 3 polyons and add them to the map.

	var p1 = new mireo.map.polygon({
		editable: true,
		z_order: 10,
		points: pts.slice(0, 4),
		map: wm
	});
	p1.on("right_mouseup", function(e) { on_right_mouse_up(p1); });

The p1 polyon is made editable, we set the
z_order to 10 and take first 4 points from pts to create it. We
hook it to the on_right_mouseup function for right_mouseup events,
just like those manually created, so we can toggle editable state of it.

	var p2 = new mireo.map.polygon({
		editable: false,
		style: {
			shape: {
				fill: "rgba(255, 255, 0, 0.5)"
			},
			edge: {
				line_width: 1,
				line_cap: "round",
				line_join: "round",
				miter_limit: 0,
				stroke: "rgba(255, 255, 0, 1)"
			}
		},
		z_order: 100,
		points: pts.slice(4, 7),
		map: wm
	});
	p2.on("right_mouseup", function(e) { 
		p2.inverted(!p2.inverted());
	});

The p2 we create with custom style and non editable, and set
z_order to 100 (value greater then the other 2). We do not hook the
right_mouseup event to on_right_mouseup function (to toggle
editable state), but we hook it to function that toggles the
inverted state.
Note that for inverted polygon, mouse input is taken for events on the "outside" of the polygon.
This is why we have set larger z_order to it.

	var p3 = new mireo.map.polygon({
		editable: false,
		z_order: 10,
		points: pts.slice(7, 11),
		style: {
			shape: {
				fill: "rgba(255, 0, 0, 0.5)"
			},
			edge: {
				line_width: 3,
				line_cap: "round",
				line_join: "round",
				miter_limit: 0,
				stroke: "rgba(255, 0, 0, 0.7)"
			}
		},
		map: wm
	});
	p3.on("right_mouseup", function(e) { on_right_mouse_up(p3); });

The p3 is made non editable, with custom style.
We set the same z_order and the same
right_mouseup event handler as for p1.

	polygons.push(p1);
	polygons.push(p2);
	polygons.push(p3);

Finally, we push all 3 of them to the polygons array, so they could be deleted just like manually made ones.

Back to top.

Polyline on the map

This sample demonstrates how to draw mireo.map.polyline objects on map:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
	<title>Sample polyline</title>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
	        margin: 0; padding: 0; }
        #map-container {
	        position: absolute;
	        left: 6px; top: 6px; 
	        right: 6px; bottom: 6px; 
	        border: 1px solid #cccccc;
        }
    </style>
	<script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8"></script>
	<script>
	    window.onload = function() {
	        var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);

	        var map_div = document.getElementById('map-container');
	        var wm = new mireo.map(map_div, {
	            center: center,
	            zoom: 16
	        });

	        // polyline test
	        var pts1 = [
		        new mireo.wgs.point(center.lat - 150 / 10000, center.lng - 150 / 10000),
		        new mireo.wgs.point(center.lat + 0 / 10000, center.lng - 50 / 10000),
		        new mireo.wgs.point(center.lat + 50 / 10000, center.lng - 100 / 10000),
		        new mireo.wgs.point(center.lat + 70 / 10000, center.lng + 50 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng + 100 / 10000)
	        ];
	        var levels1 = [17, 16, 17, 17, 17];

			var pts2 = [
		        new mireo.wgs.point(center.lat - 150 / 10000, center.lng - 450 / 10000),
		        new mireo.wgs.point(center.lat + 0 / 10000, center.lng - 350 / 10000),
		        new mireo.wgs.point(center.lat + 50 / 10000, center.lng - 500 / 10000),
		        new mireo.wgs.point(center.lat + 70 / 10000, center.lng - 250 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng - 200 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng + 100 / 10000)
			];
	        var levels2 = [17, 16, 15, 22, 22, 22];
			
			var path1 = new mireo.map.path( {
					points: pts1,
					levels: levels1
				});
	        var poly1 = new mireo.map.polyline({
				map: wm,
				z_order: 12
			});
			poly1.path(path1);
			
			var poly2 = new mireo.map.polyline({
				map: wm,
				path: new mireo.map.path({
					points: pts2,
					levels: levels2,
					width: 12,
					color: "red"
				})
			});

			var to_thicker = true;
			setInterval(function() {
				poly1.path(to_thicker ? new mireo.map.path({
					points: pts1,
					levels: levels1,
					width: 25,
					color: "rgba(0,255,100,0.45)"
				}) : path1);
				poly1.z_order(to_thicker ? 10 : 12);
				to_thicker = !to_thicker;
			}, 3000);
	    };
	</script>
</head>
<body>
    <div id="map-container"></div>
</body>
</html>

View the example of polyline on the map.

After standard page setup, we create the
The mireo.map object itself:

	        var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);

	        var map_div = document.getElementById('map-container');
	        var wm = new mireo.map(map_div, {
	            center: center,
	            zoom: 16
	        });

Then we prepare two couples of arrays:

	        // polyline test
	        var pts1 = [
		        new mireo.wgs.point(center.lat - 150 / 10000, center.lng - 150 / 10000),
		        new mireo.wgs.point(center.lat + 0 / 10000, center.lng - 50 / 10000),
		        new mireo.wgs.point(center.lat + 50 / 10000, center.lng - 100 / 10000),
		        new mireo.wgs.point(center.lat + 70 / 10000, center.lng + 50 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng + 100 / 10000)
	        ];
	        var levels1 = [17, 16, 17, 17, 17];

			var pts2 = [
		        new mireo.wgs.point(center.lat - 150 / 10000, center.lng - 450 / 10000),
		        new mireo.wgs.point(center.lat + 0 / 10000, center.lng - 350 / 10000),
		        new mireo.wgs.point(center.lat + 50 / 10000, center.lng - 500 / 10000),
		        new mireo.wgs.point(center.lat + 70 / 10000, center.lng - 250 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng - 200 / 10000),
		        new mireo.wgs.point(center.lat - 70 / 10000, center.lng + 100 / 10000)
			];
	        var levels2 = [17, 16, 15, 22, 22, 22];

i.e. we prepare
points and
levels for 2
path objects.

			var path1 = new mireo.map.path( {
					points: pts1,
					levels: levels1
				});
	        var poly1 = new mireo.map.polyline({
				map: wm,
				z_order: 12
			});
			poly1.path(path1);

Then we create path1 with default
color and
width, poly1 with
z_order greater than
default value, so it is drawn over any other
polyline with default
z_order,
but without
path set in options. Therefore we have to explicitly call
path method of poly1.

			var poly2 = new mireo.map.polyline({
				map: wm,
				path: new mireo.map.path({
					points: pts2,
					levels: levels2,
					width: 12,
					color: "red"
				})
			});

We then create poly2, this time providing it with
path in constructor. We explicitly set non-default
width and
color, and leave default
z_order (which is 10).

			var to_thicker = true;
			setInterval(function() {
				poly1.path(to_thicker ? new mireo.map.path({
					points: pts1,
					levels: levels1,
					width: 25,
					color: "rgba(0,255,100,0.45)"
				}) : path1);
				poly1.z_order(to_thicker ? 10 : 12);
				to_thicker = !to_thicker;
			}, 3000);

Finally, we set the interval to change
path of the poly1 every 3 seconds.

Circle on the map

The mireo.map.circle object draws a circle on the map.
A circle can be editable or not. The editable circle will have either
"marker holders" (similar to the editable mireo.map.polygon), or real
images placed in center and somewhere on the perimiter.

The code sample for drawing circle:

    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 16
                /*, scale_control: false, zoom_control: false*/ });

            var icon_center = new mireo.map.marker.image({
                url: "../img/pins.png",
                offset: new mireo.base.point(36 * 2, 0),
                anchor: new mireo.base.point(18, 48),
                size: new mireo.base.size(36, 48)
            });
            var icon_radius = new mireo.map.marker.image({
                url: "../img/circle_green.png",
                offset: new mireo.base.point(0, 0),
                anchor: new mireo.base.point(12, 12),
                size: new mireo.base.size(24, 24)
            });    
                 
            var c1 = new mireo.map.circle({
                z_order: 20,
                center: center,
                radius: 10000,
                editable: true,
                center_icon: icon_center,
                radius_icon: icon_radius,
                map: wm
            });    

            var state = 0;                
            wm.on('tap',function(e){
                state = ++state % 3;
                console.log('new state ' + state);
                if(state == 1) {
                    c1.center_icon(null);
                    c1.radius_icon(null);
                }
                else if (state == 2) {
                    c1.editable(false);
                }
                else {
                    c1.center_icon(icon_center);
                    c1.radius_icon(icon_radius);
                    c1.editable(true);
                }
            });
        };
    </script>

View the example of circle on the map.

After creating mireo.map object, two
icons are created, to be used as
radius icon and
center icon. The
circle object is created with these two icons, and in
editable state. These icons are naturally draggable.

Finally, tap handler is added to the map itself, that illustrates how will circle behave
when editability is modified, or if icons are removed.

Back to top.

Route on the map

The mireo.map.route object draws a
route on the map.
The
route object returned by
mireo.router is compliant, but not necessarily the only way to provide "route" data.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
	<title>Mireo Map development</title>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
	        margin: 0; padding: 0; }
        #map-container {
	        position: absolute;
	        left: 6px; top: 6px; 
	        right: 380px; bottom: 6px; 
	        border: 1px solid #cccccc;
        }
		#route-options {
            position: absolute;
            top: 6px; right: 6px;
            width: 354px; height: 50px;
	        border: 1px solid #cccccc;
            background-color: white;
            font-family: arial, sans-serif;
            font-size:13px;
            padding: 6px;
		}
 	    #route-container {
            position: absolute;
            top: 68px; bottom: 6px; right: 6px;
            width: 354px; 
			margin-top: 6px;
            border: 1px solid #cccccc;
            background-color: white;
            font-family: arial, sans-serif;
            font-size:13px;
            padding: 6px;
            overflow-y: scroll;
            overflow-x: hidden;
        }
	</style>
	<script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8&lang=en_uk"></script>
	<script type="text/javascript" src="../js/sample-route.js"></script>
</head>
<body>
    <div id="map-container"></div>
	<div>
		<div id="route-options">
			<table  style="width:100%; border-collapse:collapse;border-spacing:0;line-height:18px;">
				<tr>
					<td>Route interactive:</td>
					<td>
						<input id="rt-interactive-yes" type="radio" name="input" value="yes">Yes</input>
					</td>
					<td>
						<input id="rt-interactive-no" type="radio" name="input" value="no" checked>No</input>
					</td>
				</tr>
				<tr>
					<td>Stroke type:</td>
					<td>
						<input id="rt-stroke-line" type="radio" name="stroke" value="line" checked>Line</input>
					</td>
					<td>
						<input id="rt-stroke-arrows" type="radio" name="stroke" value="arrows">Arrow</input>
					</td>
				</tr>
			</table>
		</div>
		<div id="route-container">
			<table style="width:100%; border-collapse:collapse;border-spacing:0;line-height:18px; " id="dir-tbl">
			</table>
		</div>
	</div>
</body>
</html>

The HTML file defines 3 <DIV< elements: one for drawing map (as usual), one for managing route
handle_input
and
stroke_type option. The third is to display the advices list returned from server.

The code of the sample itself is in sample-route.js file:

window.onload = function() {
	var map_div = document.getElementById('map-container');
	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var wm = new mireo.map(map_div, { center: center, zoom: 18});
	var pts = [
		new mireo.wgs.point(center.lat - 150 / 1000, center.lng - 150 / 1000),
		new mireo.wgs.point(center.lat + 70 / 1000, center.lng +  50 / 1000),
		new mireo.wgs.point(center.lat - 70 / 1000, center.lng + 100 / 1000)
	];
	var router = new mireo.router();
	var route_drawer = new mireo.map.route({
		map: wm
	});
	var route_interactive = function(event) {
		route_drawer.handle_input(event.target.value == 'yes');
	};
	var stroke_options = function(event) {
		route_drawer.stroke_type(event.target.value);
	};
	
	document.getElementById('rt-interactive-yes').onclick = route_interactive;
	document.getElementById('rt-interactive-no').onclick = route_interactive;
	document.getElementById('rt-stroke-line').onclick = stroke_options;
	document.getElementById('rt-stroke-arrows').onclick = stroke_options;
	
	var create_destination_title = function() {
		return "Route destination, length: " + 
			route_drawer.route().length + ", duration: " + 
			route_drawer.route().duration;
	};
	var mk1 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_dark_blue(),
		draggable: true,
		title: "Route departure!",
		position: pts[0],
		z_order: 100,
		map: wm,
		visible: false
	});

	var mk2 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_yellow(),
		draggable: true,
		title: "Way point!",
		position: pts[1],
		z_order: 100,
		map: wm,
		visible: false
	});

	var mk3 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 5, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		draggable: true,
		title: create_destination_title(),
		position: pts[2],
		z_order: 100,
		map: wm,
		visible: false
	});

	var _row_click = function(center) {
		wm.set_center_and_zoom(center,1);
	};
	function clear_directions() {
		var dtbl = document.getElementById('dir-tbl');
		while (dtbl.rows.length > 0)
			dtbl.deleteRow(0);
	}
   var direction_row = function(num, txt, dist, ad, tbl){
		var first_row = tbl.insertRow(-1);
		var icon_cell = first_row.insertCell(-1);
		icon_cell.setAttribute("style", "vertical-align:top; padding:0; cursor:pointer;");
		icon_cell.appendChild(mireo.advisor_stock_icons.create_icon(ad));
	   
		var advice_cell = first_row.insertCell(-1);
		advice_cell.setAttribute("style", "width:100%; vertical-align:top; padding:0; cursor:pointer;");
		advice_cell.innerHTML = 
			'' + num + '.' +
			'' + txt + '';
		
		first_row.insertCell(-1); // empty padding

		var second_row = tbl.insertRow(-1);
		second_row.setAttribute("style","cursor:pointer;");
		second_row.insertCell(-1); // empty padding

		var hline = second_row.insertCell(-1);
		hline.setAttribute("style", "vertical-align:middle; padding: 0; height:1px;");
		hline.innerHTML = '
'; var dtxt = second_row.insertCell(-1); dtxt.setAttribute("style", "text-align:right; color:#a6a6a6; vertical-align:top;padding:0 6px 0 4px;"); dtxt.innerHTML = '
' + dist + '
'; first_row.onclick = function() { _row_click(ad.pt); } second_row.onclick = function() { _row_click(ad.pt); } }; var update_directions = function(rt){ var dtbl = document.getElementById('dir-tbl'); var rows = ''; for (var t = 0; t < rt.trips.length; ++t) { var trip = rt.trips[t]; for (var a = 0; a < trip.advices.length; ++a) { var ad = trip.advices[a]; direction_row(a + 1, ad.text, (ad.meters / 1000).toFixed(1) + ' km', ad, dtbl); } } }; router.find_journey({ points: [pts[0], pts[1], pts[2]], with_advices: 1 }, function(result) { route_drawer.route(result); update_directions(result, wm); mk3.title(create_destination_title()); }); var update_route = function() { clear_directions(); router.find_journey({ points: [mk1.position(), mk2.position(), mk3.position()], with_advices: 1 }, function(result) { route_drawer.route(result); update_directions(result, wm); mk3.title(create_destination_title()); }); }; mk1.on("changed", update_route); mk2.on("changed", update_route); mk3.on("changed", update_route); var show_icons = false; wm.on("tap", function(e){ show_icons = !show_icons; mk1.visible(show_icons); mk2.visible(show_icons); mk3.visible(show_icons); }); wm.on("right_mousedown", function(e) { route_drawer.remove_highlight_route_point(); }); };

View the example of route on the map.

After standard creation of map object, we create 3
wgs points that will be departure, way-point and destination for the route. Then we create
mireo.router object that will do the route calculations and
mireo.map.route object that will draw the actual route.

	var route_interactive = function(event) {
		route_drawer.handle_input(event.target.value == 'yes');
	};
	var stroke_options = function(event) {
		route_drawer.stroke_type(event.target.value);
	};
	
	document.getElementById('rt-interactive-yes').onclick = route_interactive;
	document.getElementById('rt-interactive-no').onclick = route_interactive;
	document.getElementById('rt-stroke-line').onclick = stroke_options;
	document.getElementById('rt-stroke-arrows').onclick = stroke_options;

We then define 2 functions to handle onclick events for the radio buttons that change the
mireo.map.route.handle_input state and
mireo.map.route.stroke_type of route_drawer object.

Next, we create 3 mireo.map.marker objects, as departure, way-point and destination positions, and
set them to be invisible and draggable. We also create create_destination_title function, that updates the title of the marker designated to the destination position.

	var _row_click = function(center) {
		wm.set_center_and_zoom(center,1);
	};
	function clear_directions() {
		var dtbl = document.getElementById('dir-tbl');
		while (dtbl.rows.length > 0)
			dtbl.deleteRow(0);
	}
   var direction_row = function(num, txt, dist, ad, tbl){
		var first_row = tbl.insertRow(-1);
		var icon_cell = first_row.insertCell(-1);
		icon_cell.setAttribute("style", "vertical-align:top; padding:0; cursor:pointer;");
		icon_cell.appendChild(mireo.advisor_stock_icons.create_icon(ad));
	   
		var advice_cell = first_row.insertCell(-1);
		advice_cell.setAttribute("style", "width:100%; vertical-align:top; padding:0; cursor:pointer;");
		advice_cell.innerHTML = 
			'' + num + '.' +
			'' + txt + '';
		
		first_row.insertCell(-1); // empty padding

		var second_row = tbl.insertRow(-1);
		second_row.setAttribute("style","cursor:pointer;");
		second_row.insertCell(-1); // empty padding

		var hline = second_row.insertCell(-1);
		hline.setAttribute("style", "vertical-align:middle; padding: 0; height:1px;");
		hline.innerHTML = '
'; var dtxt = second_row.insertCell(-1); dtxt.setAttribute("style", "text-align:right; color:#a6a6a6; vertical-align:top;padding:0 6px 0 4px;"); dtxt.innerHTML = '
' + dist + '
'; first_row.onclick = function() { _row_click(ad.pt); } second_row.onclick = function() { _row_click(ad.pt); } }; var update_directions = function(rt){ var dtbl = document.getElementById('dir-tbl'); var rows = ''; for (var t = 0; t < rt.trips.length; ++t) { var trip = rt.trips[t]; for (var a = 0; a < trip.advices.length; ++a) { var ad = trip.advices[a]; direction_row(a + 1, ad.text, (ad.meters / 1000).toFixed(1) + ' km', ad, dtbl); } } };

In this block of code, we have defined function that handle the route object, that
will be returned by invoking
mireo.router.find_journey on router object we declared. This code
is based on the fact that we will ask for advices when invoking mireo.router.find_journey.
In each table row, we invoke
mireo.advisor_stock_icons.create_icon to get the icon representing each
manouver, by passing
advice object from route result.

	router.find_journey({ points: [pts[0], pts[1], pts[2]],  with_advices: 1 }, function(result) { 
		route_drawer.route(result);
		update_directions(result, wm); 
		mk3.title(create_destination_title());
	});

We calculate the initial route by invoking
mireo.router.find_journey on router object, providing 3 points
for departure, via point and destination, and stating that we want list of advices returned. In the callback, we pass this result to
route_drawer object, update the directions (advices) table, and modify the title of the destination
marker.

	var update_route = function() {
		clear_directions();
		router.find_journey({ points: [mk1.position(), mk2.position(), mk3.position()],  with_advices: 1 }, 
			function(result) {
				route_drawer.route(result);
				update_directions(result, wm); 
				mk3.title(create_destination_title());
		});
	};

Then we create update_route function, that recalculates route based on current markers’ positions.
Dragging any of the 3 markers will cause route recalculation. In callback we naturally first clear previous advices.

We then add special
changed input handler to all 3
markers, that invokes the update_route function.
The changed event is fired by marker when it has been dragged to new position.

We then add the
tap
handler on the wm object itself, that toggles visibility of all 3 markers.

Finally, we add the "right_moousedown" handler on the wm object that
removes hightlighted point from
route_drawer object.

Back to top.

The mireo.geocoder class is used to search objects on the map. The search can be "global"
(i.e., searching all loaded maps by phrase), or "local" (i.e. searching in the proximity of some location).

The complete code sample that illustrates how searching can be used:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
    <title>Mireo Map development</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
            margin: 0; padding: 0; }
        #map-container {
            position: absolute;
            left: 256px; top: 62px; 
            right: 6px; bottom: 6px; 
            border: 1px solid #cccccc;
        }
        #input-container {
            padding-left: 256px; padding-top: 16px;
        }
        #result-container {
            position: absolute;
            left: 6px; top: 6px;
            bottom: 6px;
            width: 245px;
            border: 1px solid #cccccc;
            background-color: #FAFAFA;
            overflow: scroll;
            display: block;
            color: #A01040;
            font-weight: bold;
        }
    </style>
    <script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8">
    </script>
    
    <script>
        window.onload = function() {
            var map_div = document.getElementById('map-container');
            var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
            var wm = new mireo.map(map_div, { center: center, zoom: 18 });
            var geocoder = new mireo.geocoder();
            t.init(wm, geocoder);
            wm.on("tap", function(e) {
                t.find_closest(e.wgs);
            });
        };
        var invoke_search = function() {
            var input_fld = document.getElementById('search_string');
            t.find(input_fld.value);
        };
		var checkEnter = function(e) {
			if(e && e.keyCode == 13)
				invoke_search();
		};
        var t = (function() {
            var _map = null;
            var _geocoder = null;
            var _result_marker = null;
            var _create_title = function(ga) {
                var title = (ga.POI && ga.POI.length) ? ga.POI + ',\n' : '';
                title += (title.length ? '\n' : '') +  ga.street ? ga.street + 
                    (ga.house_no ? ' ' + ga.house_no + ',\n' : ',\n') : '';
		        title += (title.length ? '\n ' : ' ') + ga.city;
                title += ga.country;
                return title;
            };
            var _clear_marker = function(){
                if(_result_marker) {
                    _result_marker.map(null);
                    _result_marker = null;
                }
				_map.clear_street_overlay();
            }
            var _create_clickable_child = function(div, ga) {
                var display_text = document.createTextNode(_create_title(ga));
                var par = document.createElement("p");
                var node = document.createElement("a");
                var attr = document.createAttribute("href");
                attr.nodeValue = "";
                node.attributes.setNamedItem(attr);
                node.appendChild(display_text);
                node.onclick = function(e) {
                    if(e.preventDefault) 
                        e.preventDefault();
                    else 
                        e.returnValue = false;
                    _clear_marker();
                    _result_marker = new mireo.map.marker({
                            icon: new mireo.map.marker.image({
                                url: "../img/pins.png",
                                offset: new mireo.base.point(36 * 3, 0),
                                anchor: new mireo.base.point(18, 48),
                                size: new mireo.base.size(36, 48)
								img_size: new mireo.base.size(324, 48)
                            }),
                            title: _create_title(ga),
                            position: ga.pt,
                            z_order: 100,
                            map: _map
                    });
	                _map.set_street_overlay(ga, true);
		            if (parseInt(ga.type) < 1000 || parseInt(ga.type) ≥ 2000)
		                _map.center(_result_marker.position());
                };
                par.appendChild(node);
                div.appendChild(par);
            };
            var _display_results = function(result) {
                var div = document.getElementById("result-container");
                while(div.hasChildNodes())
                    div.removeChild(div.lastChild);
                for(var i = 0; i < result.length; ++i) {
                    _create_clickable_child(div, result[i]);
                }
            };
            return {
                init : function(wm, geocoder) {
                    _map = wm; _geocoder = geocoder;
                },
                find: function(srch) {
                    _clear_marker();
                    _geocoder.geocode({ phrase: srch }, function(result){
                        _result_marker = new mireo.map.marker({
                                icon: new mireo.map.marker.image({
                                    url: "../img/pins.png",
                                    offset: new mireo.base.point(36 * 3, 0),
                                    anchor: new mireo.base.point(18, 48),
                                    size: new mireo.base.size(36, 48),
									img_size: new mireo.base.size(324, 48)
                                }),
                                title: _create_title(result[0]),
                                position: result[0].pt,
                                z_order: 100,
                                map: _map
                        });
                        _map.center(_result_marker.position());
                        _display_results(result);
                    });    
                },
                find_closest: function(wgs_p) {
                    _clear_marker();
                    _geocoder.geocode({ wgs_pt: wgs_p }, function(result){
                        var input_fld = document.getElementById('search_string');
                        input_fld.value = _create_title(result[0]);
                    });
                }
            }
        })();
    </script>
</head>
<body>
    <div id="result-container"></div>
    <div id="input-container onKeyPress="checkEnter(event)">
        <form">
            <label for="search_string">Enter search string:</label>
            <input type="text" size="128" id="search_string" />
            <input type="button" value="Submit" onclick="invoke_search()" />        
        </form>
    </div>
    <div id="map-container"></div>
</body>
</html>

View the example of searching the map.

In the head of the document, compared to simple map example we have added:

        #input-container {
            padding-left: 256px; padding-top: 16px;
        }
        #result-container {
            position: absolute;
            left: 6px; top: 6px;
            bottom: 6px;
            width: 245px;
            border: 1px solid #cccccc;
            background-color: #FAFAFA;
            overflow: scroll;
            display: block;
            color: #A01040;
            font-weight: bold;
        }

to define style for the additional div elements in the body.

In the window.onload function, we have added creation of the mireo.geocoder object,
that will be used to perform the searching. Then we initialize the singleton t with wm and geocoder, and
finally we add "tap" handler to the map:

            wm.on("tap", function(e) {
                t.find_closest(e.wgs);
            });

that will utilize the map wgs position under mouse of the map tap event.
This code will invoke the geocoder’s search near the point mode, via
find_closest
method in singleton t.

Next, we define the function:

        var invoke_search = function() {
            var input_fld = document.getElementById('search_string');
            t.find(input_fld.value);
        };

that will invoke the geocoder’s search by phrase mode, via
find
method in singleton t.

		var checkEnter = function(e) {
			if(e && e.keyCode == 13)
				invoke_search();
		};

This function handles onKeyPress event for input field, to invoke search if user hit the ENTER key.

Then we implement the singleton itself:

        var t = (function() {
            var _map = null;
            var _geocoder = null;
            var _result_marker = null;
            var _create_title = function(ga) {
                var title = (ga.POI && ga.POI.length) ? ga.POI + ',\n' : '';
                title += (title.length ? '\n' : '') +  ga.street ? ga.street + 
                    (ga.house_no ? ' ' + ga.house_no + ',\n' : ',\n') : '';
		        title += (title.length ? '\n ' : ' ') + ga.city;
                title += ga.country;
                return title;
            };
            var _clear_marker = function(){
                if(_result_marker) {
                    _result_marker.map(null);
                    _result_marker = null;
                }
				_map.clear_street_overlay();
            }
            var _create_clickable_child = function(div, ga) {
                var display_text = document.createTextNode(_create_title(ga));
                var par = document.createElement("p");
                var node = document.createElement("a");
                var attr = document.createAttribute("href");
                attr.nodeValue = "";
                node.attributes.setNamedItem(attr);
                node.appendChild(display_text);
                node.onclick = function(e) {
                    if(e.preventDefault) 
                        e.preventDefault();
                    else 
                        e.returnValue = false;
                    _clear_marker();
                    _result_marker = new mireo.map.marker({
                            icon: new mireo.map.marker.image({
                                url: "../img/pins.png",
                                offset: new mireo.base.point(36 * 3, 0),
                                anchor: new mireo.base.point(18, 48),
                                size: new mireo.base.size(36, 48)
								img_size: new mireo.base.size(324, 48)
                            }),
                            title: _create_title(ga),
                            position: ga.pt,
                            z_order: 100,
                            map: _map
                    });
	                _map.set_street_overlay(ga, true);
		            if (parseInt(ga.type) < 1000 || parseInt(ga.type) ≥ 2000)
		                _map.center(_result_marker.position());
                };
                par.appendChild(node);
                div.appendChild(par);
            };
            var _display_results = function(result) {
                var div = document.getElementById("result-container");
                while(div.hasChildNodes())
                    div.removeChild(div.lastChild);
                for(var i = 0; i < result.length; ++i) {
                    _create_clickable_child(div, result[i]);
                }
            };
            return {
                init : function(wm, geocoder) {
                    _map = wm; _geocoder = geocoder;
                },
                find: function(srch) {
                    _clear_marker();
                    _geocoder.geocode({ phrase: srch }, function(result){
                        _result_marker = new mireo.map.marker({
                                icon: new mireo.map.marker.image({
                                    url: "../img/pins.png",
                                    offset: new mireo.base.point(36 * 3, 0),
                                    anchor: new mireo.base.point(18, 48),
                                    size: new mireo.base.size(36, 48),
									img_size: new mireo.base.size(324, 48)
                                }),
                                title: _create_title(result[0]),
                                position: result[0].pt,
                                z_order: 100,
                                map: _map
                        });
                        _map.center(_result_marker.position());
                        _display_results(result);
                    });    
                },
                find_closest: function(wgs_p) {
                    _clear_marker();
                    _geocoder.geocode({ wgs_pt: wgs_p }, function(result){
                        var input_fld = document.getElementById('search_string');
                        input_fld.value = _create_title(result[0]);
                    });
                }
            }
        })();

We declare _map and _geocoder variables that will be initialized by the init function, and then
declare the _result_marker variable where we shall keep the marker of the found (global) result.

Since the mireo.geocoder.geocode function returns an array of
geoaddresses, we have implemented internal function _create_title function that will create
suitable string representation of an geoaddress:

            var _create_title = function(ga) {
                var title = (ga.POI && ga.POI.length) ? ga.POI + ',\n' : '';
                title += (title.length ? '\n' : '') +  ga.street ? ga.street + 
                    (ga.house_no ? ' ' + ga.house_no + ',\n' : ',\n') : '';
		        title += (title.length ? '\n ' : ' ') + ga.city;
                title += ga.country;
                return title;
            };

Following internal function:

            var _clear_marker = function(){
                if(_result_marker) {
                    _result_marker.map(null);
                    _result_marker = null;
                }
				_map.clear_street_overlay();
            }

does the removal of current (if existing) marker: it is important to remove it from the map first.

After marker removal, the mireo.map.clear_street_overlay function is called on
_map object to remove the street overlay polygon, if any was set.

The function _create_clickable_child takes the div element we use for the display of results, and one
geoaddress object to create a link in the passed div element:

            var _create_clickable_child = function(div, ga) {
                var display_text = document.createTextNode(_create_title(ga));
                var par = document.createElement("p");
                var node = document.createElement("a");
                var attr = document.createAttribute("href");
                attr.nodeValue = "";
                node.attributes.setNamedItem(attr);
                node.appendChild(display_text);
                node.onclick = function(e) {
                    if(e.preventDefault) 
                        e.preventDefault();
                    else 
                        e.returnValue = false;
                    _clear_marker();
                    _result_marker = new mireo.map.marker({
                            icon: new mireo.map.marker.image({
                                url: "../img/pins.png",
                                offset: new mireo.base.point(36 * 3, 0),
                                anchor: new mireo.base.point(18, 48),
                                size: new mireo.base.size(36, 48),
								img_size: new mireo.base.size(324, 48)
                            }),
                            title: _create_title(ga),
                            position: ga.pt,
                            z_order: 100,
                            map: _map
                    });
	                _map.set_street_overlay(ga, true);
		            if (parseInt(ga.type) < 1000 || parseInt(ga.type) ≥ 2000)
		                _map.center(_result_marker.position());
                };
                par.appendChild(node);
                div.appendChild(par);
            };

The text of the link is created using the same _create_title function, and it is assinged the onclick function
to handle clicks on it, that clears existing marker, creates new one from passed geoaddress
and then repositions view on the map. The call to
mireo.map.set_street_overlay will draw an overlay polyline on top of the street that was
returned in ga, but only if the ga object is not POI. The true passed to it will
reposition map view to see complete bounding box of the street overlay. The following line checks if the ga is POI, and if so,
centers map on POI location.

The last "internal" function, _display_results, clears existing links to search results and creates new ones.

After init function, we have the "global" search implementation code:

                find: function(srch) {
                    _clear_marker();
                    _geocoder.geocode({ phrase: srch }, function(result){
                        _result_marker = new mireo.map.marker({
                                icon: new mireo.map.marker.image({
                                    url: "../img/pins.png",
                                    offset: new mireo.base.point(36 * 3, 0),
                                    anchor: new mireo.base.point(18, 48),
                                    size: new mireo.base.size(36, 48),
									img_size: new mireo.base.size(324, 48)
                                }),
                                title: _create_title(result[0]),
                                position: result[0].pt,
                                z_order: 100,
                                map: _map
                        });
                        _map.center(_result_marker.position());
                        _display_results(result);
                    });    
                },

We first clear the current marker, and then invoke the mireo.geocoder.geocode function:
the object we passed to it has the "phrase" property defined and initialized, so the search by phrase will be executed. The second parameter is the
callback function that will be invoked with the search result, and here we implement the desired behavior of this example: we create the
marker object and position it to the position of the first found result.
We then call the _display_results function to add links to all results to the left div element.
The maximum number of returned results will be 30.

And finally, we implement the "local" search implementation mode:

                find_closest: function(wgs_p) {
                    _clear_marker();
                    _geocoder.geocode({ wgs_pt: wgs_p }, function(result){
                        var input_fld = document.getElementById('search_string');
                        input_fld.value = _create_title(result[0]);
                    });
                }

We first clear the current marker, and then invoke the mireo.geocoder.geocode function: the object we passed to it
has the "wgs_pt" property defined and initialized (and "phrase" intentionally omitted!), so the search by proximity will be executed.
The second parameter is the callback function that will be invoked with the search result,
and here we implement the desired behavior of this example: we take the one and only returned geoaddress, convert it to
string representation and set that as text to the input field.

To have the example completed, we need to add to the body (beside the map container):

    <div id="result-container"></div>
    <div id="input-containeronKeyPress="checkEnter(event)">
        <form">
            <label for="search_string">Enter search string:</label>
            <input type="text" size="128" id="search_string" />
            <input type="button" value="Submit" onclick="invoke_search()" />        
        </form>
    </div>

Back to top.

Info windows

This sample illustrates how to create and display
info windows on
map.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
	<title>Sample info window</title>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
	        margin: 0; padding: 0; }
        #map-container {
	        position: absolute;
	        left: 6px; top: 6px; 
	        right: 6px; bottom: 6px; 
	        border: 1px solid #cccccc;
        }
    </style>
	<script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=europe-osm&v=0.8"></script>
	<script type="text/javascript" src="../js/sample-info-window.js"></script>

</head>
<body>
    <div id="map-container"></div>
</body>
</html>

As we can see, the implementation of this sample is in separate js file:

window.onload = function() {
	var center1 = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var center2 = new mireo.wgs.point(center1.lat, center1.lng - 150 / 1000);
	var center3 = new mireo.wgs.point(center1.lat, center1.lng + 150 / 1000);
	
	var map_div = document.getElementById('map-container');
	var mapopts = { center: center1, zoom: 16 };
	var wm = new mireo.map(map_div, mapopts);
	
	var start_info_close_handler = function() {
		mk1.visible(true);
		mk2.visible(true);
		mk3.visible(true);
	};
	
	var start_info_window = new mireo.map.info_window({
		position: center1,
		auto_close: true,
		arrow_pos: mireo.map.info_window.no_arrow,
		info_content: '<div style=\"padding: 20px;font-size: 40px;font-type: bold\">This is info window demo.<br>Click outside to start.</div>',
		map: wm,
		close_handler: start_info_close_handler
	});
	
	var _arrow_pos = mireo.map.info_window.arrow_left;
	var _arrow_position = function() {
		return (++_arrow_pos) % 4;
	};
	
	var _arrow_offset = function(position) {
		switch(position) {
			case mireo.map.info_window.arrow_left: return new mireo.base.point(20, 0);
			case mireo.map.info_window.arrow_top: return new mireo.base.point(0, 0);
			case mireo.map.info_window.arrow_right: return new mireo.base.point(-20, 0);
			case mireo.map.info_window.arrow_bottom: return new mireo.base.point(0, -50);
		}
		return new mireo.base.point(0,0);
	};
	
	var mk1 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 1, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 1",
		position: center1,
		z_order: 100,
		map: wm,
		visible: false
	});
	
	var cont1 = document.createElement('div');
	cont1.innerHTML = 'This info window has custom close button.<br>' +
		'When you drag "parent" marker, it will go with it.<br>' +
		'When you close it and click on marker again,<br>' +
		'it will be created with different arrow position<br><br>.' +
		'<a href=\"http://www.mireo.hr/documentation/map-api08/api-reference#mireo-map-info-window-class\">Documentation</a> ';
	cont1.style.paddingTop = "40px";
	
	var delete_infobox_callback = function(ib) {
		if (ib == first_info_box) first_info_box = null;
		else if (ib == second_info_box) second_info_box = null;
		ib = null;
	};
	var first_info_box = null;
	var second_info_box = null;
	mk1.on("tap", function(e) {
		if (first_info_box) {
			return;
		}
		var arrow_position = _arrow_position();
		var po = _arrow_offset(arrow_position);
		first_info_box = new mireo.map.info_window({
			position: mk1.position(),
			map: wm,
			info_content: cont1,
			close_handler: delete_infobox_callback,
			pix_offset: po,
			arrow_pos: arrow_position,
	        close_button: {
				url: '../img/custom-window-close-icon.png',
				size: new mireo.base.size(32, 32)
	        }
		});
		first_info_box.on("tap", function(e) {
			return true;
		});
	});
	mk1.on("update", function(e) {
		if (!first_info_box)
			return;
		first_info_box.position(mk1.position());
	});
	
	var mk2 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 2, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 2",
		position: center2,
		z_order: 100,
		map: wm,
		visible: false
	});
	var cont2 = document.createElement('div');
	cont2.innerHTML = 'This info window has default close button.<br/>' +
		'When you drag "parent" marker, it will stay put.<br/><br/>' +
		'<a href=\"http://www.mireo.hr/documentation/map-api08/api-reference#mireo-map-info-window-class\">Documentation</a> <br/>' + 
		'Write something:<br/> <input type=\"text\" size=\"25\" value=\"\" />';
	cont2.style.paddingRight = "25px";

	mk2.on("tap", function(e) {
		if (second_info_box) {
			return;
		}
		second_info_box = new mireo.map.info_window({
			position: mk2.position(),
			map: wm,
			info_content: cont2,
			pix_offset: new mireo.base.point(20, 0),
			close_handler: delete_infobox_callback
		});
	});

	var mk3 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 3, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 3",
		position: center3,
		z_order: 100,
		map: wm,
		visible: false
	});
	
	var cont3 = '<div><h2 style=\"font: bold 16px arial helvetica\">Complex info window</h2>' +
        '<p style=\"font: italic 14px/20px times\">This content is made of custom styled elements.</p>' +
        '<p style=\"font: 12px/14px Verdana\;padding-top: 10px;text-indent: 30px">' +
        'The heading level 2 has custom set style, and so does the previous paragraph.<br>' +
        'But guess what, this paragraph also has custom stlye.</p>' +
        '<p style=\"font: 12px/14px Courier;padding-top: 15px;text-indent: 15px;max-width: 200px\">' +
        '<br><br></p>' +
        '<p>This paragraph uses default content style, as you can see.<br>' +
        'It is here out merely to point out that you could,<br>' +
        'after autoclosing this window, try the long press on map<br>' +
        'and see what happens</p>' +
        '<p><a href=\"http://www.mireo.hr\">mireo</a></p>' +
        '<p style=\"color: red;font: italic 10px/14px Courier;padding-top: 15px;text-indent: 5px;max-width: 150px\">' +
        'Hint: it will be more fun to do it over some settlement than over clearly uninhabbited area without streets etc etc.' +
        '</p></div>';
		
	mk3.on("tap", function(e) {
		var autoclosable = new mireo.map.info_window({
			position: mk3.position(),
			map: wm,
			info_content: cont3,
			auto_close: true,
			arrow_pos: mireo.map.info_window.arrow_right,
			pix_offset: new mireo.base.point(-20, 0)
		});
	});
	
	var geocoder = new mireo.geocoder();
	wm.on("long_press_begin", function(e) {
		geocoder.geocode({ wgs_pt: e.wgs }, function(r) {
			var result = r[0];
			var hdr = 'Geo address under point:';
			var tbl = '';
			if (result.POI && result.POI.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">POI</td><td style=\"padding: 5px 10px 5px 5px\">' + result.POI + '</td></tr>';
			if (result.street && result.street.length) {
				var s = result.street;
				if (result.house_no && result.house_no.length)
					s += ', ' + result.house_no;
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Address</td><td style=\"padding: 5px 10px 5px 5px\">' + s + '</td></tr>';
			}
			if (result.city && result.city.length) {
				var s = result.city;
				if (result.PLZ && result.PLZ.length)
					s += ', ' + result.PLZ;
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">City</td><td style=\"padding: 5px 10px 5px 5px\">' + s + '</td></tr>';
			}
			if (result.area && result.area.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Area</td><td style=\"padding: 5px 10px 5px 5px\">' + result.area + '</td></tr>';
			if (result.country && result.country.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Country</td><td style=\"padding: 5px 10px 5px 5px\">' + result.country + '</td></tr>';
			if (tbl.length == 0)
				hdr = 'Location under point:';
			tbl += '<tr><td colspan=\"2\"style=\"font: bold 10px/12px; color: red\">' + result.pt.lat + ' , ' + result.pt.lng + '</td></tr>';
			var content = '<div><h2 style=\"font: bold 16px arial helvetica\">' + hdr + '</h2>' +
		    '<p><table border=\"1px solid\" style=\"font: bold 12px arial helvetica;border: 1px solid #C0C0C0; border-collapse: collapse\">';
			content += tbl;
			content += '</table></p>';
			content += '</div>';

			var auto_close_iw = new mireo.map.info_window({
				info_content: content,
				position: e.wgs,
				map: wm,
				auto_close: true,
				pix_offset: new mireo.base.point(20, 0)
			});
		});
		return true;
	});
}

In:

	var center1 = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var center2 = new mireo.wgs.point(center1.lat, center1.lng - 150 / 1000);
	var center3 = new mireo.wgs.point(center1.lat, center1.lng + 150 / 1000);
	
	var map_div = document.getElementById('map-container');
	var mapopts = { center: center1, zoom: 16 };
	var wm = new mireo.map(map_div, mapopts);

we first define 3 locations, where we shall position
markers, and create map object.

	var start_info_close_handler = function() {
		mk1.visible(true);
		mk2.visible(true);
		mk3.visible(true);
	};
	
	var start_info_window = new mireo.map.info_window({
		position: center1,
		auto_close: true,
		arrow_pos: mireo.map.info_window.no_arrow,
		info_content: '<div style=\"padding: 20px;font-size: 40px;font-type: bold\">This is info window demo.<br>Click outside to start.</div>',
		map: wm,
		close_handler: start_info_close_handler
	});

Then we create start_info_close_handler function, that we pass as
close_handler function to the start_info_window
object we create next. This info window is made
auto closable, thus a
tap outside it will close it: the start_info_close_handler will make
3 markers we create later
visible. The start_info_window is made without
arrow, so center of it will be placed on the
position, and info_content is passed as string.

	var _arrow_pos = mireo.map.info_window.arrow_left;
	var _arrow_position = function() {
		return (++_arrow_pos) % 4;
	};
	
	var _arrow_offset = function(position) {
		switch(position) {
			case mireo.map.info_window.arrow_left: return new mireo.base.point(20, 0);
			case mireo.map.info_window.arrow_top: return new mireo.base.point(0, 0);
			case mireo.map.info_window.arrow_right: return new mireo.base.point(-20, 0);
			case mireo.map.info_window.arrow_bottom: return new mireo.base.point(0, -50);
		}
		return new mireo.base.point(0,0);
	};

Here we create some helper code that changes
arrow position, and calculates appropriate
pixel offset so that info window does not overlap the &qout;parent"
marker.

	var mk1 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 1, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 1",
		position: center1,
		z_order: 100,
		map: wm,
		visible: false
	});
	
	var cont1 = document.createElement('div');
	cont1.innerHTML = 'This info window has custom close button.<br>' +
		'When you drag "parent" marker, it will go with it.<br>' +
		'When you close it and click on marker again,<br>' +
		'it will be created with different arrow position<br><br>.' +
		'<a href=\"http://www.mireo.hr/documentation/map-api08/api-reference#mireo-map-info-window-class\">Documentation</a> ';
	cont1.style.paddingTop = "40px";
	
	var delete_infobox_callback = function(ib) {
		if (ib == first_info_box) first_info_box = null;
		else if (ib == second_info_box) second_info_box = null;
		ib = null;
	};
	var first_info_box = null;
	var second_info_box = null;
	mk1.on("tap", function(e) {
		if (first_info_box) {
			return;
		}
		var arrow_position = _arrow_position();
		var po = _arrow_offset(arrow_position);
		first_info_box = new mireo.map.info_window({
			position: mk1.position(),
			map: wm,
			info_content: cont1,
			close_handler: delete_infobox_callback,
			pix_offset: po,
			arrow_pos: arrow_position,
	        close_button: {
				url: '../img/custom-window-close-icon.png',
				size: new mireo.base.size(32, 32)
	        }
		});
		first_info_box.on("tap", function(e) {
			return true;
		});
	});
	mk1.on("update", function(e) {
		if (!first_info_box)
			return;
		first_info_box.position(mk1.position());
	});

Here we create mk1 marker, and then we create HTML DIV element, the cont1. This
DIV will be passed as
info window content to the info window object that will be created when
mk1 is
tapped.

We have created function delete_infobox_callback, and 2 variables first_info_box and
first_info_box. The delete_infobox_callback will be passed as
close_handler when corresponding info window objects are created.

In the tap handler, we make sure we create only one info window per tap. Here we use previously
prepared functions to change arrow position and pixel offset each time new info window is created (naturally, the previous instance had to be closed first).
An info window instance created this way uses custom
close button.

Finally, we also attach changed event handler to the mk1 marker, so
when it is
dragged to new position, the position of the "buddy" info window is
also changed.

	var mk2 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 2, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 2",
		position: center2,
		z_order: 100,
		map: wm,
		visible: false
	});
	var cont2 = document.createElement('div');
	cont2.innerHTML = 'This info window has default close button.<br/>' +
		'When you drag "parent" marker, it will stay put.<br/><br/>' +
		'<a href=\"http://www.mireo.hr/documentation/map-api08/api-reference#mireo-map-info-window-class\">Documentation</a> <br/>' + 
		'Write something:<br/> <input type=\"text\" size=\"25\" value=\"\" />';
	cont2.style.paddingRight = "25px";

	mk2.on("tap", function(e) {
		if (second_info_box) {
			return;
		}
		second_info_box = new mireo.map.info_window({
			position: mk2.position(),
			map: wm,
			info_content: cont2,
			pix_offset: new mireo.base.point(20, 0),
			close_handler: delete_infobox_callback
		});
	});

Quite analogously, we create mk2 marker, cont2 DIV element, and assign
tap handler to the mk2 marker, to create info window. We do not assign the
changed event handler to the mk2 marker, so if user drags it, the
"buddy" info window will stay put.

The second_info_box create this way uses default
close button (since it is not
auto closable, it must have close button).

	var mk3 = new mireo.map.marker({
		icon: new mireo.map.marker.image({
			url: "../img/pins.png",
			offset: new mireo.base.point(36 * 3, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		handle_input: true,
		draggable: true,
		title: "Marker 3",
		position: center3,
		z_order: 100,
		map: wm,
		visible: false
	});
	
	var cont3 = '<div><h2 style=\"font: bold 16px arial helvetica\">Complex info window</h2>' +
        '<p style=\"font: italic 14px/20px times\">This content is made of custom styled elements.</p>' +
        '<p style=\"font: 12px/14px Verdana\;padding-top: 10px;text-indent: 30px">' +
        'The heading level 2 has custom set style, and so does the previous paragraph.<br>' +
        'But guess what, this paragraph also has custom stlye.</p>' +
        '<p style=\"font: 12px/14px Courier;padding-top: 15px;text-indent: 15px;max-width: 200px\">' +
        '<br><br></p>' +
        '<p>This paragraph uses default content style, as you can see.<br>' +
        'It is here out merely to point out that you could,<br>' +
        'after autoclosing this window, try the long press on map<br>' +
        'and see what happens</p>' +
        '<p><a href=\"http://www.mireo.hr\">mireo</a></p>' +
        '<p style=\"color: red;font: italic 10px/14px Courier;padding-top: 15px;text-indent: 5px;max-width: 150px\">' +
        'Hint: it will be more fun to do it over some settlement than over clearly uninhabbited area without streets etc etc.' +
        '</p></div>';
		
	mk3.on("tap", function(e) {
		var autoclosable = new mireo.map.info_window({
			position: mk3.position(),
			map: wm,
			info_content: cont3,
			auto_close: true,
			arrow_pos: mireo.map.info_window.arrow_right,
			pix_offset: new mireo.base.point(-20, 0)
		});
	});

Yet again, we create mk3 marker, and cont3 for the info window content, this time as
string. In the tap handler of the
mk3, we create
auto closable info window.

	var geocoder = new mireo.geocoder();
	wm.on("long_press_begin", function(e) {
		geocoder.geocode({ wgs_pt: e.wgs }, function(r) {
			var result = r[0];
			var hdr = 'Geo address under point:';
			var tbl = '';
			if (result.POI && result.POI.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">POI</td><td style=\"padding: 5px 10px 5px 5px\">' + result.POI + '</td></tr>';
			if (result.street && result.street.length) {
				var s = result.street;
				if (result.house_no && result.house_no.length)
					s += ', ' + result.house_no;
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Address</td><td style=\"padding: 5px 10px 5px 5px\">' + s + '</td></tr>';
			}
			if (result.city && result.city.length) {
				var s = result.city;
				if (result.PLZ && result.PLZ.length)
					s += ', ' + result.PLZ;
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">City</td><td style=\"padding: 5px 10px 5px 5px\">' + s + '</td></tr>';
			}
			if (result.area && result.area.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Area</td><td style=\"padding: 5px 10px 5px 5px\">' + result.area + '</td></tr>';
			if (result.country && result.country.length > 0)
				tbl += '<tr><td style=\"padding: 5px 5px 5px 10px\">Country</td><td style=\"padding: 5px 10px 5px 5px\">' + result.country + '</td></tr>';
			if (tbl.length == 0)
				hdr = 'Location under point:';
			tbl += '<tr><td colspan=\"2\"style=\"font: bold 10px/12px; color: red\">' + result.pt.lat + ' , ' + result.pt.lng + '</td></tr>';
			var content = '<div><h2 style=\"font: bold 16px arial helvetica\">' + hdr + '</h2>' +
		    '<p><table border=\"1px solid\" style=\"font: bold 12px arial helvetica;border: 1px solid #C0C0C0; border-collapse: collapse\">';
			content += tbl;
			content += '</table></p>';
			content += '</div>';

			var auto_close_iw = new mireo.map.info_window({
				info_content: content,
				position: e.wgs,
				map: wm,
				auto_close: true,
				pix_offset: new mireo.base.point(20, 0)
			});
		});
		return true;
	});

Finally, we create instance of
mireo.geocoder class, and assign
long press begin handler to the wm object. It takes the
position where long press was fired, and passes it to the
mireo.geocoder.geocode function of geocoder object. The resulting
geo address object is used to fill the HTNL element that is
passed as info content to auto closable info window.

View the example of info windows the map.

Back to top.

Clusterer

This sample demonstrate the use of
mireo.clusterer class. The
mireo.clusterer can be used to cluster groups of map objects (typically
markers) on high
zoom levels, where one cluster icon represents many map objects.

The implementation of the sample is in sample-clusterer.js file:

window.onload = function() {
	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var map_div = document.getElementById('map-container');
	var wm = new mireo.map(map_div, { center: center, zoom: 24 });
	
	wm.on("tap", function(e) {
		wm.zoom(36);
	});
	var test_num_markers = 20000;
	
	var generate_markers = function(num_markers) {
	    var map_sw = mireo.wgs.wproj.point_to_wgs(new mireo.base.point(resx.map_defs.bounds.x1, resx.map_defs.bounds.y1));
	    var map_ne = mireo.wgs.wproj.point_to_wgs(new mireo.base.point(resx.map_defs.bounds.x2, resx.map_defs.bounds.y2));
	    var lng_span = map_ne.lng - map_sw.lng;
	    var lat_span = map_ne.lat - map_sw.lat;
	    var markers = [];
	    for(var i = 0; i < num_markers; ++i) {
	        var pt = new mireo.wgs.point(map_sw.lat + Math.random() * lat_span, map_sw.lng + Math.random() * lng_span);
            var m = new mireo.map.marker({
                icon: new mireo.map.marker.image({
		            url: "../img/pins.png",
		            offset: new mireo.base.point(36 * 1, 0),
		            anchor: new mireo.base.point(18, 48),
		            size: new mireo.base.size(36, 48)
                }),
                position: pt,
                draggable: true
            });
            markers.push(m);
	    }
	    return markers;
	};
    var markers = generate_markers(test_num_markers);

    var icon_provider = function(cluster_markers, total_markers) {
    	var number = cluster_markers.length.toString();
    	var icon = cluster_markers.length / total_markers < 0.01 ? mireo.stock_pins.circle_dark_blue() : mireo.stock_pins.circle_red();
    	icon.text(number, { "font-size": 17 - 2 * number.length + "px" })
    	return icon;
    }

    var create_clusterer = function() {
        var tick_clusterer_start = new Date();
        var m_clusterer = new mireo.map.clusterer({
            map: wm,
            markers: markers,
            grid_size: 60,
            average_center: false,
            cluster_icon_provider: icon_provider
           });
    };
    window.setTimeout(create_clusterer, 2000);
};

View this example.

window.onload = function() {
	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var map_div = document.getElementById('map-container');
	var wm = new mireo.map(map_div, { center: center, zoom: 24 });
	
	wm.on("tap", function(e) {
		wm.zoom(36);
	});
};

As usual, we first create the wm as
mireo.map object. For convenience, we have added
tap event handler, that changes
map zoom level to the highest possible value.

	var test_num_markers = 20000;
	
	var generate_markers = function(num_markers) {
	    var map_sw = mireo.wgs.wproj.point_to_wgs(new mireo.base.point(resx.map_defs.bounds.x1, resx.map_defs.bounds.y1));
	    var map_ne = mireo.wgs.wproj.point_to_wgs(new mireo.base.point(resx.map_defs.bounds.x2, resx.map_defs.bounds.y2));
	    var lng_span = map_ne.lng - map_sw.lng;
	    var lat_span = map_ne.lat - map_sw.lat;
	    var markers = [];
	    for(var i = 0; i < num_markers; ++i) {
	        var pt = new mireo.wgs.point(map_sw.lat + Math.random() * lat_span, map_sw.lng + Math.random() * lng_span);
            var m = new mireo.map.marker({
                icon: new mireo.map.marker.image({
		            url: "../img/pins.png",
		            offset: new mireo.base.point(36 * 1, 0),
		            anchor: new mireo.base.point(18, 48),
		            size: new mireo.base.size(36, 48)
                }),
                position: pt,
                draggable: true
            });
            markers.push(m);
	    }
	    return markers;
	};
    var markers = generate_markers(test_num_markers);

Then we define number of markers to test (20000), function generate_markers that creates test markers, and invoke it to generate
markers array.

    var icon_provider = function(cluster_markers, total_markers) {
    	var number = cluster_markers.length.toString();
    	var icon = cluster_markers.length / total_markers < 0.01 ? mireo.stock_pins.circle_dark_blue() : mireo.stock_pins.circle_red();
    	icon.text(number, { "font-size": 17 - 2 * number.length + "px" })
    	return icon;
    }

    var create_clusterer = function() {
        var tick_clusterer_start = new Date();
        var m_clusterer = new mireo.map.clusterer({
            map: wm,
            markers: markers,
            grid_size: 60,
            average_center: false,
            cluster_icon_provider: icon_provider
           });
    };
    window.setTimeout(create_clusterer, 2000);

Then we define icon_provider function to be passed as
icon provider for the clusterer object. This function provides icon to
be used to represent one cluster of map objects.

Finally, we define function create_clusterer that will actually create the
mireo.clusterer object. We pass the markers array, and the
icon_provider to the constructor.

Back to top.

Multiple resolution support

With this version, Mireo WEB Maps API has full support for high resolution (i.e. retina) devices.

However, the availability, or usability, or high resolution is actually dictated by the availability of high resolution tiles for requested scope. This is
encapsulated in
mireo.device.scale function. This function by default returns 1 (meaning no scale), and it will
return 2 (meaning full high resolution for scale 2 is supported) if both the device that requested API has high resolution screen, and the requested scope of
API has tiles in high resolution available.

The HTML file (mres-hr-demo-html):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>Croatia multi resolution demo</title>

    <style type="text/css">
        html { height: 100%; }
        body { height: 100%;
	        margin: 0; padding: 0; }
        #map-container {
	        position: absolute;
	        left: 6px; top: 6px; 
	        right: 6px; bottom: 6px; 
	        border: 1px solid #cccccc;
        }
    </style>

	<script type="text/javascript" src="http://world.mireo.hr/api?fun=load_api&scope=hr-taxi&v=0.8"></script>
	<script type="text/javascript" src="../js/multires.js"></script>
	<script type="text/javascript" src="../js/mres-demo.js"></script>
</head>
<body>
    <div id="map-container"></div>
</body>
</html>

loads API with scope hr-taxi, which incidently has the full high resolution support. After loading API itself, it loads the
multires.js file which will be presented shortly, and the mres-demo.js with the actual sample code.

View multi resolution demo for scope hr-taxi.

function resource_name(name, scale) {
    if(!scale || scale < 2) return name;

    var dot = name.lastIndexOf('.');
    if(dot != -1) {
        var extension = name.substring(dot);
        name = name.substring(0, dot);
    }
    name += scale + 'x';

    if(extension) {
        name += extension;
    }
    return name;
}

The multires.js file contains just one convenience function, that for given resource name and scale used, returns proper resource name. As it can
be seen, if scale is greater than 1, it will insert the scale_x before the dot.

window.onload = function() {

	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);
	var center2 = new mireo.wgs.point(45.1546, 18.029514);
	var pts = [
		new mireo.wgs.point(center.lat - 150 / 1000, center.lng - 150 / 1000),
		new mireo.wgs.point(center.lat +  0 / 1000, center.lng -  50 / 1000),
		new mireo.wgs.point(center.lat + 50 / 1000, center.lng - 100 / 1000),
		new mireo.wgs.point(center.lat + 70 / 1000, center.lng +  50 / 1000),
		new mireo.wgs.point(center.lat - 70 / 1000, center.lng + 100 / 1000)
	];
	
	var map_div = document.getElementById('map-container');
	var wm = new mireo.map(map_div, { center: center, zoom: 16/*, scale_control: false, zoom_control: false*/ });

	var router = new mireo.router();
	var r1 = new mireo.map.route({
		map: wm
	});

	router.find_journey(
		{ 
			points: [pts[0], pts[3], pts[4]],
			rtype: mireo.router.fastest,
			vtype: mireo.router.passenger,
			avoids: 0,
			with_advices: 0
		}, function(result) { 
		r1.route(result); 
	});

	var mk1 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_dark_blue(),
		draggable: true,
		title: "Draggable Marker 1!",
		position: pts[0],
		z_order: 100,
		map: wm
	});

	var mk2 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_yellow(),
		draggable: true,
		title: "Draggable Marker 2!",
		position: pts[4],
		z_order: 100,
		map: wm
	});
	
	var update_route = function() {
		router.find_journey({ 
			points: [mk1.position(), mk2.position()],
			rtype: mireo.router.fastest,
			vtype: mireo.router.taxi,
			avoids: 0,
			with_advices: 0
		}, function(result) {
			r1.route(result);
		});
	};

	mk1.on("changed", update_route);
	mk2.on("changed", update_route);

	var a1 = new mireo.map.arrow({
		z_order: 30,
		position: center,
		angle: -135,
		map: wm
	});
	
	var p1 = new mireo.map.polygon({
		editable: true,
		z_order: 10,
		points: pts,
		map: wm
	});

	var c1 = new mireo.map.circle({
		z_order: 20,
		center: center,
		radius: 10000,
		editable: true,
		center_icon: new mireo.map.marker.image({
			url: resource_name("../img/pins.png", mireo.device.scale()),
			offset: new mireo.base.point(36 * 2, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		radius_icon: new mireo.map.marker.image({
			url: resource_name("../img/circle_green.png", mireo.device.scale()),
			offset: new mireo.base.point(0, 0),
			anchor: new mireo.base.point(12, 12),
			size: new mireo.base.size(24, 24)
		}),
		map: wm
	});	

	var mk3 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_green(),
		handle_input: true,
		title: "Non-draggable marker!",
		position: new mireo.wgs.point(45.1546, 18.029514),
		z_order: 100,
		map: wm
	});
};

Finally, in the mres-demo.js, we load the sample code itself. All the features this sample demonstrates are explained in other
chapters, so we shall concentrate on important differences here. First of all, the use of any API built-in "resources", such as
mireo.stock_pins object for marker images, do not require any special handling.


	var c1 = new mireo.map.circle({
		z_order: 20,
		center: center,
		radius: 10000,
		editable: true,
		center_icon: new mireo.map.marker.image({
			url: resource_name("../img/pins.png", mireo.device.scale()),
			offset: new mireo.base.point(36 * 2, 0),
			anchor: new mireo.base.point(18, 48),
			size: new mireo.base.size(36, 48),
			img_size: new mireo.base.size(324, 48)
		}),
		radius_icon: new mireo.map.marker.image({
			url: resource_name("../img/circle_green.png", mireo.device.scale()),
			offset: new mireo.base.point(0, 0),
			anchor: new mireo.base.point(12, 12),
			size: new mireo.base.size(24, 24)
		}),
		map: wm
	});	

However, any resource external to the API does require special handling. For the url property of dictionary passed to
mireo.map.marker.image constructor, we have used the
resource_name function to properly set image file url, passing non-scaled resource name and the
scale supported by API (that depends on the scope parameter).
This is equally used for both center_icon and radius_icon markers. If you compare this to the
circle sample code, you will see that this is the only difference.

So, if you load both of these samples to high resolution screen device, the url parameter will end up being
"../img/pins2x.png" (and "../img/circle_green2x.png") for the scope hr-taxi, while it will be
"../img/pins.png" (and "../img/circle_green.png") for the scope europe.

If you however load both of these samples on low resolution screen device (standard monitor), the url parameter will end up being
"../img/pins.png" (and "../img/circle_green.png") regardless of the scope.

Back to top.

Coverage on the map

The mireo.coverage object is used to calculate coverage from location
obtained by click on the map, and display it as
mireo.map.polygon object.

The code snippet:

window.onload = function() {
	if(!window.mireo) {
		window.setTimeout(window.onload, 200);
		return;
	}

	var time = 240;
	
	var center = new mireo.wgs.point(45.779369079813755, 15.934864282608032);

	var map_div = document.getElementById('map-container');
	var wm = new mireo.map(map_div, { center: center, zoom: 13 });
	var coverage = new mireo.coverage();
	var p1 = new mireo.map.polygon({
		editable: false,
		z_order: 10,
		 map: wm
	});

	var m1 = new mireo.map.marker({
		icon: mireo.stock_pins.pin_circle_dark_blue(),
		map: wm
	});

	wm.on("tap", function (e) {
		coverage.get_coverage({ point: e.wgs, time: time }, function (points, levels) {
			p1.levels(levels); // setting of polygon douglas peucker levels does not call polygon redraw
			p1.points(points); // polygon points are updated and polygon redraw is called automatically
			m1.position(e.wgs);
		});
	}, this);

	wm.on("long_press_begin", function(e) {
		time = prompt("Enter time", "240");

		if (time == null) {
			time = 240;
		}
	}, this);
};

View the example of coverage on the map.

As usually, first we define the map itself, we set default coverage time (240 s), and create
one mireo.map.marker
and one mireo.map.polygon object.

We have added tap nad long_press_begin handlers to the map, first one calculates coverage and updates marker and polygon:

	wm.on("tap", function (e) {
		coverage.get_coverage({ point: e.wgs, time: time }, function (points, levels) {
			p1.levels(levels); // setting of polygon douglas peucker levels does not call polygon redraw
			p1.points(points); // polygon points are updated and polygon redraw is called automatically
			m1.position(e.wgs);
		});
	}, this);

The long_press_begin handler launches prompt to modify the coverage time value:

	wm.on("tap", function (e) {
		coverage.get_coverage({ point: e.wgs, time: time }, function (points, levels) {
			p1.levels(levels); // setting of polygon douglas peucker levels does not call polygon redraw
			p1.points(points); // polygon points are updated and polygon redraw is called automatically
			m1.position(e.wgs);
		});
	}, this);

Back to top.