Geocoding using Google Maps API v3? - google-maps-api-3

At the moment I use a latlang to specify the location:
<script type="text/javascript">
function initialize() {
var myLatlng = new google.maps.LatLng(52.097303, 0.275820);
var myOptions = {
zoom: 15,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var contentString = '<div id="bubbleContent">'+'<img src="images/logo.png" alt="logo" width="200" height="35"/>'
'</div>';
var infowindow = new google.maps.InfoWindow({
content: contentString,
});
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
});
infowindow.open(map,marker);
}
</script>
How do I use geocoding to use an address instead? e.g. Cambridge, United Kingdom
Thanks

var map;
var geocoder;
var address;
function initialize() {
map = new GMap2(document.getElementById("map_canvas"));
map.setCenter(new GLatLng(40.730885,-73.997383), 15);
map.addControl(new GLargeMapControl);
GEvent.addListener(map, "click", getAddress);
geocoder = new GClientGeocoder();
}
function getAddress(overlay, latlng) {
if (latlng != null) {
address = latlng;
geocoder.getLocations(latlng, showAddress);
}
}
function showAddress(response) {
map.clearOverlays();
if (!response || response.Status.code != 200) {
alert("Status Code:" + response.Status.code);
} else {
place = response.Placemark[0];
point = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
marker = new GMarker(point);
map.addOverlay(marker);
marker.openInfoWindowHtml(
'>b<orig latlng:>/b<' + response.name + '>br/<' +
'>b<latlng:>/b<' + place.Point.coordinates[1] + "," + place.Point.coordinates[0] + '>br<' +
'>b<Status Code:>/b<' + response.Status.code + '>br<' +
'>b<Status Request:>/b<' + response.Status.request + '>br<' +
'>b<Address:>/b<' + place.address + '>br<' +
'>b<Accuracy:>/b<' + place.AddressDetails.Accuracy + '>br>' +
'>b>Country code:>/b< ' + place.AddressDetails.Country.CountryNameCode);
}
}

Related

GoogleMaps Markerclusterer InfoWindow same location, Cluster get content from Markers

i have problem, MarkerCluster undefined content from Markers.
I used MarkerCluster in same address (location) gridSize: 1
If pressing Cluster zoomOnClick:false, Cluster must getMarkers content
and open infowindow. but I get undefined undefined .
I chink this problem on this
google.maps.event.addListener(markerCluster, 'clusterclick', function(cluster) {
var markers = cluster.getMarkers();
var content = '';
for (var i = 0; i < markers.length; i++) {
var marker = markers[i];
content += marker.name;
content += ("<br>");
}
var infowindow = new google.maps.InfoWindow();
infowindow.setPosition(cluster.getCenter());
infowindow.close();
infowindow.setContent(content);
infowindow.open(map);
google.maps.event.addListener(map, 'zoom_changed', function() {
infowindow.close();
});
});
Full code:
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(55.7522200, 37.6155600),
zoom: 11
});
var infoWindow = new google.maps.InfoWindow;
var iconPurple = new google.maps.MarkerImage('mapimages/purple.svg', new google.maps.Size(33, 31));
var iconBlue = new google.maps.MarkerImage('mapimages/blue.svg', new google.maps.Size(33, 31));
var iconYellow = new google.maps.MarkerImage('mapimages/yellow.svg', new google.maps.Size(33, 31));
var iconGreen = new google.maps.MarkerImage('mapimages/green.svg', new google.maps.Size(33, 31));
var iconPink = new google.maps.MarkerImage('mapimages/pink.svg', new google.maps.Size(33, 31));
var iconBrown = new google.maps.MarkerImage('mapimages/brown.svg', new google.maps.Size(33, 31));
var customIcons = [];
customIcons["room1"] = iconPurple;
customIcons["room2"] = iconBlue;
customIcons["room3"] = iconYellow;
customIcons["room4"] = iconGreen;
customIcons["room5"] = iconPink;
customIcons["room6"] = iconBrown;
var options = {
gridSize: 1,
zoomOnClick:false,
imagePath: 'mapimages/m'
};
downloadUrl(markers.php', function(data) {
var xml = data.responseXML;
var markerArray = [];
var markers = xml.documentElement.getElementsByTagName('marker');
Array.prototype.forEach.call(markers, function(markerElem) {
var name = markerElem.getAttribute("name");
var address = markerElem.getAttribute("address");
var metro = markerElem.getAttribute("metro");
var type = markerElem.getAttribute("type");
var date = markerElem.getAttribute("date");
var status = markerElem.getAttribute("status");
var inrequest = markerElem.getAttribute("inrequest");
var phone = markerElem.getAttribute("phone");
var message = markerElem.getAttribute("message");
var area_room = markerElem.getAttribute("area_room");
var price = markerElem.getAttribute("price");
var floore = markerElem.getAttribute("floore");
var floors = markerElem.getAttribute("floors");
var rooms_furniture = markerElem.getAttribute("rooms_furniture");
var kitchen_furniture = markerElem.getAttribute("kitchen_furniture");
var repaint = markerElem.getAttribute("repaint");
var comment = markerElem.getAttribute("comment");
var distance = markerElem.getAttribute("distance");
var point = new google.maps.LatLng(
parseFloat(markerElem.getAttribute('lat')),
parseFloat(markerElem.getAttribute('lng')));
var html = "<h1>" + "м. " + metro + ", " + address + "</h1>"
+ "<h2>" + phone + " " + name + "</h2>"
+ "<h3>" + type + " " + area_room + "&nbspм " + price + " р. этаж " + floore + "/" + floors + "</h3>"
+ "<p>" + rooms_furniture + " " + kitchen_furniture + " " + repaint + "</p>"
+ "<h5>" + distance + " " + comment + " " + message + "</h5>" + "<h6>" + date + " " + inrequest + " " + status + "</h6>";
var marker = new google.maps.Marker({
map: map,
position: point,
icon: customIcons[type],
animation: google.maps.Animation.DROP
});
markerArray.push(marker);
marker.addListener('click', function() {
infoWindow.setContent(html);
infoWindow.open(map, marker);
});
});
var markerCluster = new MarkerClusterer(map, markerArray, options);
google.maps.event.addListener(markerCluster, 'clusterclick', function(cluster) {
var markers = cluster.getMarkers();
var content = '';
for (var i = 0; i < markers.length; i++) {
var marker = markers[i];
content += marker.name;
content += ("<br>");
}
var infowindow = new google.maps.InfoWindow();
infowindow.setPosition(cluster.getCenter());
infowindow.close();
infowindow.setContent(content);
infowindow.open(map);
google.maps.event.addListener(map, 'zoom_changed', function() {
infowindow.close();
});
});
});
}
function downloadUrl(url,callback) {
var request = window.ActiveXObject ?
new ActiveXObject('Microsoft.XMLHTTP') :
new XMLHttpRequest;
request.onreadystatechange = function() {
if (request.readyState == 4) {
request.onreadystatechange = doNothing;
callback(request, request.status);//right here
}
};
request.open('GET', url, true);
request.send(null);
}
function doNothing() {}
enter image description here
The google.maps.Marker objects in the MarkerClusterer are not the same "marker" XML elements you are parsing to create the google.maps.Markers. The won't have a name property, unless you create one.
var marker = new google.maps.Marker({
map: map,
position: point,
icon: customIcons[type],
animation: google.maps.Animation.DROP,
name: name // <-- add this
});
proof of concept fiddle

Cannot get my polyline drawn or visible

I try to draw a polyline according to this example:
https://developers.google.com/maps/documentation/javascript/examples/geometry-encodings.
My map is displayed, markers are displayed as well but the polyline is not drawn, or not visible.
I don't see what's wrong.
My javascript is:
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&libraries=geometry"></script>
$( document ).ready(function($) {
var map;
var poly;
var iconBase = 'https://maps.google.com/mapfiles/kml/shapes/';
var ndgIconBase = 'http://example.com/img/';
var clickMarker;
nextWaypoint = parseInt(document.getElementById('agpoiwaypoints_size').innerHTML, 10);
countWaypoint = nextWaypoint;
function initialize() {
var mapCanvas = document.getElementById('map-canvas');
var content;
content = document.getElementById('agpoi-0-lat').innerHTML
var lat0 = parseFloat(content.substr(content.indexOf(":")+1));
content = document.getElementById('agpoi-0-lng').innerHTML
var lng0 = parseFloat(content.substr(content.indexOf(":")+1));
content = document.getElementById('agpoi-1-lat').innerHTML
var lat1 = parseFloat(content.substr(content.indexOf(":")+1));
content = document.getElementById('agpoi-1-lng').innerHTML
var lng1 = parseFloat(content.substr(content.indexOf(":")+1));
var mapOptions = {
center: new google.maps.LatLng((lat1 + lat0) / 2, (lng1 + lng0) / 2),
zoom: 18,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(mapCanvas, mapOptions);
var polyOptions = {
strokeColor: '#000000',
strokeOpacity: 1.0,
strokeWeight: 3,
map: map
};
poly = new google.maps.Polyline(polyOptions);
new google.maps.Marker({
position: {lat: lat0, lng: lng0},
map: map,
icon: ndgIconBase + 'myLogo.png'
});
new google.maps.Marker({
position: {lat: lat1, lng: lng1},
map: map,
icon: ndgIconBase + 'myLogo.png'
});
google.maps.event.addListener(map, 'click', function(e) {
placeMarker(e.latLng, map);
});
var position = {lat: lat0, lng: lng0};
var path = poly.getPath();
path.push(position);
var content;
for (i = 0; i < countWaypoint; i++) {
content = document.getElementById("agpoiwaypoints-" + i + "-latitude").value;
lat = parseFloat(content);
content = document.getElementById("agpoiwaypoints-" + i + "-longitude").value;
lng = parseFloat(content);
position = {lat: lat, lng: lng};
new google.maps.Marker({
position: position,
map: map
});
path = poly.getPath();
path.push(position);
}
position = {lat: lat1, lng: lng1};
path = poly.getPath();
path.push(position);
}
google.maps.event.addDomListener(window, 'load', initialize);
google.maps.event.addDomListener(window, 'click', placeMarker);
function placeMarker(position, map) {
if (clickMarker == 0) {
clickMarker = new google.maps.Marker({
position: position,
map: map
});
} else {
clickMarker.setPosition( position );
}
}
$("body").on("click", ".remove_agpoiwaypoint", function (e) {
e.preventDefault();
});
$("body").on("click", ".add_agpoiwaypoint", function (e) {
e.preventDefault();
var path = poly.getPath();
var position = clickMarker.getPosition();
path.push(position);
});
});
When you do path = poly.getPath(); you're simply getting an array and assigning it to a variable called path. Then when you do path.push(position); you're merely adding things into that variable... it's not updating the polyline's path.
Instead you then need to update the polyline too, e.g. then you could also do
poly.setPath(path);

show infowindow has ID on Googlemaps by URL parameter

This js code is for showing customized Google Maps. I want to add function to this code. URL parameter as ID and show infowindow has this ID on Google Maps. For example "http://www.tabikobo.com/newyork/map/uptown/?=2962"(this ID is 2966).ID is loaded as 'id' in XMLdata.
var uptownMap,defLng = -73.963245,defLat = 40.779438,
san ={
init :
function(){
$(document).ready(function(){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://maps.googleapis.com/maps/api/js?sensor=false&callback=san.putGmap";
document.body.appendChild(script);
});
},
putGmap :
function(){
var myLatlng = new google.maps.LatLng(defLat, defLng);
var myOptions = {
zoom: 15,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
uptownMap = new google.maps.Map(document.getElementById("gmapArea"), myOptions);
san.getXmlData();
},
getXmlData : function(){
$.ajax({
type: 'GET',
cache: true,
async: true,
url: '/newyork/map_uptown_xml/',
datatype: 'xml',
success: san.parseXmlData
});
},
parseXmlData: function (xml) {
//a single infowindow for all markers
var infowindow = new google.maps.InfoWindow({
pixelOffset: new google.maps.Size(0, 10)
});
var i = 0,
id, name, url, lat, lng, copy, lead, ename, tag;
$("<ol/>")
.attr('id', 'gmapAnchor')
.appendTo('div#gmapController');
$(xml)
.find('item')
.each(function (i) {
i++;
id = $(this)
.find('description id')
.text();
name = $(this)
.find('description name')
.text();
url = $(this)
.find('description path')
.text();
lat = $(this)
.find('description lat')
.text();
lng = $(this)
.find('description lng')
.text();
tag = $(this)
.find('description tag')
.text();
tag = tag.slice(5, 20);
console.log(tag)
var customIcons = {
hotel:
{
icon: 'http://www.tabikobo.com/newyork/img/icon_hotel.png'
},
var icon = customIcons[tag] || {};
var myLatLng = new google.maps.LatLng(lat, lng);
var beachMarker = new google.maps.Marker({
position: myLatLng,
map: uptownMap,
icon: icon.icon,
});
var htmlTmpl = {
hotel:
{
content: '<div class="infoWinWrapper"><strong><a href="' +
url + '">' + name +
'</a></strong><br />' + ename +
'<br />' + lead + '</div>'
},
};
var htmlTmpl = htmlTmpl[tag] || {};
//click-listener for marker
google.maps.event.addListener(beachMarker, 'click',
function () {
//update the content
infowindow.setContent(
'<div class="hook">' + htmlTmpl
.content + '</div>');
infowindow.open(uptownMap, this);
google.maps.event.addListenerOnce(
infowindow, 'domready',
function () {
var l = $('.hook')
.parent()
.parent()
.parent()
.siblings()
.addClass("infoBox");
});
});
//Creat a Tag
san.putData(name, url, lat, lng, i);
});
},
putData : function(name, url, lat, lng, num){
var x = num;
x += '';
if(x.length == 1){
var y = '0' + x;
}else {
y = x;
}
san.setEvent();
},
setEvent : function(){
$("ul#area_list li a").bind('mouseover', function(){
$(this).parent().find('span.lat').text();
var point = new google.maps.LatLng(
$(this).parent().find('span.lat').text(),
$(this).parent().find('span.lng').text()
);
uptownMap.setZoom(17);
uptownMap.setCenter(point);
});
$("#btnResetZoom a").bind('click', function(){
var point = new google.maps.LatLng(defLat, defLng);
uptownMap.setZoom(15);
uptownMap.setCenter(point);
return false;
});
}
}
window.onload = san.init();
I solived myself! thanks.
var PageMapID = location.href.split('?=');
var PageMapID2 = PageMapID[PageMapID.length -1];
if (id == PageMapID2) {
$(document).ready(function(){
infowindow.open(uptownMap,beachMarker);
infowindow.setContent('<div class="hook">' + htmlTmpl.content + '</div>');
var latlng = new google.maps.LatLng(lat, lng);
uptownMap.setCenter(latlng);
});
}

Errors with fitBounds in Google Maps

I'm having problems with fitBounds in my code. Whether I put extend.bounds and fitBounds in the createMarker function or the getData function I keep getting "bounds not defined". Right now I have the bounds statements commented out but you can see where I tried them.
google.load('visualization', '1', {'packages':['table']});
console.log("right after google load")
var map;
var markers = [];
var infoWindow = new google.maps.InfoWindow();
var bounds = new google.maps.LatLngBounds ();
var gicons = [];
gicons[1] = new google.maps.MarkerImage('http://6tango.com/MLH/Map_Icons/flower_blue_shadow.png');
gicons[2] = new google.maps.MarkerImage('http://6tango.com/MLH/Map_Icons/flower_yellow_shadow.png');
gicons[3] = new google.maps.MarkerImage('http://6tango.com/MLH/Map_Icons/flower_green_shadow.png');
var tinyicon = [];
tinyicon[1] = 'http://6tango.com/MLH/Map_Icons/flower_blue_small.png';
tinyicon[2] = 'http://6tango.com/MLH/Map_Icons/flower_yellow_small.png';
tinyicon[3] = 'http://6tango.com/MLH/Map_Icons/flower_green_small.png';
function initialize(bounds) {
console.log("initialize function")
var ca = new google.maps.LatLng(33.75,-117.9);
map = new google.maps.Map(document.getElementById('map'), {
center: ca,
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControl: true,
overviewMapControl: false,
overviewMapControlOptions: {
opened: true
}
});
var sql = encodeURIComponent("SELECT * FROM 1O8mQ6csgl4gapp6uzW- OjUtRK05eTRiB8oCc5DY");
var query = new google.visualization.Query('http://www.google.com/fusiontables/gvizdata?tq=' + sql);
query.send(getData);
}
function getData(response,bounds) {
console.log("function getData")
var dt = response.getDataTable();
var side_html = '<table >';
for (var i = 0; i < dt.getNumberOfRows(); i++) {
var lat = dt.getValue(i,5);
var lng = dt.getValue(i,6);
var icontype = dt.getValue(i,0);
var name = dt.getValue(i,1);
var a1 = dt.getValue(i,2);
var a2 = dt.getValue(i,3);
var address = dt.getValue(i,4);
var cross = dt.getValue(i,7);
var map = dt.getValue(i,8);
var photo = dt.getValue(i,9);
var t1 = dt.getValue(i,10);
var t2 = dt.getValue(i,11);
var t3 = dt.getValue(i,12);
var t4 = dt.getValue(i,13);
var t5 = dt.getValue(i,14);
var t6 = dt.getValue(i,15);
var handicap = dt.getValue(i,16);
var pt = new google.maps.LatLng(lat, lng);
//bounds.extend(pt);
//map.fitBounds(bounds);
var html = [
'<div style="width:500px;height:100%;border:1px solid;background-color:#f0f0f0"><div style="float:left;width:188px;padding:3px"><p><b>',
name + '</b><br>',
a1 + '<br>',
a2 + '</p><p><b>C/S - Directions:</b><br>',
cross + '</p><p><b>Thomas Guide: </b>',
map + '</p><p><img src="http://kk6t.com/images/handicap_mlh.png">',
handicap + '</p></div>',
'<div style="float:left;padding:5px 5px 2px 5px;margin:2px 2px 0 0;border:1px solid black;background-color:#ffffff"><img src="',
photo + '" width="292"></div>',
'<div style="clear:both;padding:0 5px;height:200px;overflow:auto;background-color:white;margin:5px 2px;border:1px solid black">',
'<p style="padding-bottom:5px">',
t1 + '</p><p>' + t2 + '</p><p>' + t3 + '</p><p>' + t4 + '</p><p>' + t5 + '</p><p>' + t6 + '</p>/div></div>'
].join('');
if (icontype < 1)
{
side_html += '<tr><td style="padding:5px 0 5px 10px" bgcolor="#83f859"><span>' + name + '</span></td></tr>';
}
else
{
side_html += '<tr><td ><img src=' + tinyicon[icontype] + '> ' + name + '</td></tr>';
}
createMarker(pt, html, name, icontype, bounds);
}
console.log("finished with markers")
side_html += '</tbody> \
</table>';
document.getElementById("side_bar").innerHTML = side_html;
}
function createMarker(point,info,name,icontype,bounds) {
console.log("function createMarker")
var marker = new google.maps.Marker({
position: point,
map: map,
title: name,
icon: gicons[icontype]
});
//bounds.extend(point);
markers.push(marker);
google.maps.event.addListener(marker, 'click', function() {
infoWindow.close();
infoWindow.setContent(info);
infoWindow.open(map,marker);
});
}
function myclick(num) {
console.log("function myclick")
google.maps.event.trigger(markers[num], "click");
}
You have naming faults with code.
First
var bounds = new google.maps.LatLngBounds ();
later
bounds.extend(pt);
map.fitBounds(bounds);
then
bounds.extend(point);
I would remove the first constructor from GLOBAL and place it in getData() before the loop.The bounds.extend(pt); after createMarker() without bounds parameter. Finally map.fitBounds(bounds); after loop.
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < dt.getNumberOfRows(); i++) {
...
...
var pt = new google.maps.LatLng(lat, lng);
...
...
createMarker(pt, html, name, icontype);//no bounds
bounds.extend(pt);
...
...
}
map.fitBounds(bounds);

Text Overlay - Displayed in wrong Position

I have implemented the accepted answer to this Question. When I run the html file, the text which I'm writing over the Google Map marker is displayed in wrong location (Refer the picture error.png). Can anyone help me to display the text exactly over the marker?.
Marker.html
<html><head>
<title> Hi </title>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function TxtOverlay(pos, txt, cls, map){
this.pos = pos;
this.txt_ = txt;
this.cls_ = cls;
this.map_ = map;
this.div_ = null;
this.setMap(map);
}
TxtOverlay.prototype = new google.maps.OverlayView();
TxtOverlay.prototype.onAdd = function(){
var div = document.createElement('DIV');
div.className = this.cls_;
div.innerHTML = this.txt_;
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
div.style.left = ( position.x) + 'px';
div.style.top = ( position.y)+ 'px';
var panes = this.getPanes();
panes.floatPane.appendChild(div);
}
TxtOverlay.prototype.draw = function(){
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
var div = this.div_;
div.style.left = ( position.x) + 'px';
div.style.top = ( position.y)+ 'px';
}
TxtOverlay.prototype.onRemove = function(){
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
TxtOverlay.prototype.hide = function(){
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
TxtOverlay.prototype.show = function(){
if (this.div_) {
this.div_.style.visibility = "visible";
}
}
TxtOverlay.prototype.toggle = function(){
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
}
else {
this.hide();
}
}
}
TxtOverlay.prototype.toggleDOM = function(){
if (this.getMap()) {
this.setMap(null);
}
else {
this.setMap(this.map_);
}
}
var map;
function init(){
var latlng = new google.maps.LatLng(37.90695894, -122.07920128);
var myOptions = {
zoom: 4,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map"), myOptions);
var marker = new google.maps.Marker({
position: latlng,
map: map,
title: "Hello World!",
icon:"./bluepin.PNG"
});
customTxt = "<div>1</div>"
txt = new TxtOverlay(latlng,customTxt,"customBox",map )
}
</script>
<style>
.customBox {
background: AARRGGBB;
border: 1px;
color:#ffffff;
position: relative;
}
</style>
</head>
<body onload="init()">
<div id="map" style="width: 600px; height: 600px;">
</div>
</body>
</html>
You need to offset the div so it appears over the marker. An offset of (-5, -30) works for this marker and the number "1".
function TxtOverlay(pos, txt, cls, map, offset){
this.pos = pos;
this.txt_ = txt;
this.cls_ = cls;
this.map_ = map;
this.offset_ = offset;
this.div_ = null;
this.setMap(map);
}
TxtOverlay.prototype.onAdd = function(){
var div = document.createElement('DIV');
div.className = this.cls_;
div.innerHTML = this.txt_;
this.div_ = div;
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
div.style.left = ( position.x + this.offset_.x) + 'px';
div.style.top = ( position.y + this.offset_.y)+ 'px';
var panes = this.getPanes();
panes.floatPane.appendChild(div);
}
TxtOverlay.prototype.draw = function(){
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
var div = this.div_;
div.style.left = ( position.x + this.offset_.x) + 'px';
div.style.top = ( position.y + this.offset_.y)+ 'px';
}
txt = new TxtOverlay(latlng,customTxt,"customBox",map,new google.maps.Point(-5,-30) )
Example
you might also look atthe MarkerWithLabel utility library
Example

Resources