Automatyczna Google Mapa

Poniższy tekst będzie w innej formie. Zamiast pisać typowy tutorial, w którym się wymądrzam, opiszę wam po prostu przypadek, kiedy podchodziłem do pewnego zadania.

Jakiś czas temu odezwał się do mnie pewien ktoś, kto poprosił mnie o pomoc przy skrypcie, który miał automatycznie pokazywać na mapie wczytywane z serwera pozycje. Sprawdziłem w internecie i skrypt ten pojawiał się na kilku forach. Problem w tym, że był on bardzo źle napisany. Postanowiłem więc napisać go od podstaw.

Celem więc stało się stworzenie mapy, na której skrypt automatycznie zaznaczał by pobrane z serwera pozycje. Dodatkowo w formie treningu postanowiłem rysować trasę między tymi markerami.

Zanim zaczniemy pisać kod zobacz prawie końcowy efekt (a na końcu artykułu dowiesz się czemu prawie):

Zobacz demo

Bardzo prosty html

Pierwszą rzeczą jaką zrobiłem było wrzucenie na bardzo prostą stronkę Google Mapy i dodania jej prostego stylowania:

<!DOCTYPE>
<html>
<head>
    <title>Mapa</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
	<div id="map"></div>

	<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=KLUCZ"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script type="text/javascript" src="js/scripts.js"></script>
</body>
</html>

Od jakiegoś czasu polityka Map Googla się zmieniła i teraz aby takie wstawić na stronę musimy wygenerować klucz API. Klucz taki generowałem na stronie https://developers.google.com/maps/documentation/javascript/get-api-key

A następnie wstawiłem go do url skryptu, który wskazywał na Google Map:

https://maps.googleapis.com/maps/api/js?key=KLUCZ

Następnie napisałem proste stylowanie dla takiej mapy:

html, body {
    height:100%;
    box-sizing: border-box;
    width:100%;
    padding:0;
    margin:0;
}
body {
    background: #fff;
    width:100%;
    height:100%;
    margin:0;
}
#map {
    background: #eee;
    height:100%;
    border:3px solid #fff;
    box-sizing: border-box;
}

Plan działania

Pistanie skryptu zacząłem od kartki i ołówka (chociaż to kłamstwo, bo w przypadku tak prostego skryptu rozrysowałem to sobie w myślach).
Plan działania miałem podzielony na części:

  1. Na początku zainicjuję mapę
  2. Następnie odpalę pętlę, w której będę cyklicznie wczytywać dane z serwera
  3. Po każdym takim wczytaniu utworzę markery na mapie
  4. Oraz narysuję linie między nimi
  5. * (punkt opcjonalny) – dodam możliwość czyszczenia tych danych oraz centrowania mapy na markerach

Odpalenie mapy

Idąc zgodnie z planem na początku utworzyłem funkcję, która zainicjuje mapę:

(function() {
	var map = null; //mapa google

	function createMap() {
	    var mapOptions = {
	        zoom: 10,
	        zoomControl: true,
	        streetViewControl: true,
	        mapTypeId: google.maps.MapTypeId.ROADMAP,
	        center: new google.maps.LatLng(52.229676, 21.012229),
	        disableDefaultUI: true
	    };
	    map = new google.maps.Map(document.getElementById("map"), mapOptions);
	    mapTooltip = new google.maps.InfoWindow();
	}

	$(function() {
		createMap();
	});
})();

Mapę podstawiłem pod „globalną” zmienną naszego closure. Dzięki temu bez problemu mogłem się do niej odwoływać w innych funkcjach tego modułu. Najczęściej zmieniałem domyślny wygląd mapy za pomocą konfiguracji ze storny https://snazzymaps.com/ (wstawiając je do powyższego obiektu mapOptions), ale tym razem zostawiłem styl somyślny.

Główna pętla

Kolejną punktem było stworzenie głównej pętli, która będzie cyklicznie pobierać dane z serwera, wywołując cyklicznie odpowiednie funkcje:

(function() {
	var map = null; //mapa google

	function createMap() {
	    ...
	}

	function mainLoop() {
		loadNewMarkers();
		...
	}

	$(function() {
		createMap();
		mainLoop();
	});
})();

Wczytywanie danych z serwera

Kolejną funkcją, którą napisałem była funkcja odpowiedzialna za wczytywanie danych z serwera czyli loadNewMarkers(). Zanim ją napisałem przygotowałem sobie prostego jsona którego mogłem wczytywać (pamiętaj, że skrypt ten był miał z założenia operować na podobnych danych co ten stary, więc z góry wiedziałem jakie dane powinienem otrzymywać):

{
    "data": [
        {
            "id" : 1,
            "lat": "53.436904",
            "lng": "14.550267",
            "iconUrl": "assets/images/map-marker.png",
            "time": "00:42:33"
        }
    ]
}

Ale taki json do testów się nie nadawał, bo po odwołaniu do niego zwracał zawsze takie same dane, więc na szybko napisałem prosty skrypt, który zwracał mi losowe dane:

function random()
{
    return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax();
}

echo json_encode(array(
    'data' => array(
        array(
            "id"      => random(),
            "lat"     => 52 + random() + random() + random(),
            "lng"     => 14 + random() + random() + random(),
            "iconUrl" => "assets/images/map-marker.png",
            "time"    => date('Y/m/d H:i:s')
        )
    )
));

Gdy miałem skrypt serwerowy (generate_coords.php), napisałem wspomnianą funkcję:

(function() {
	var map = null; //mapa google

	function createMap() {
	    ...
	}

	function loadNewMarkers() {
	    var newMarkersData = [];

	    $.ajax({
	        type: "GET",
	        url: 'generate_coords.php',
	        dataType: "json",
	        cache: false,
	        success: function (json) {
	            json.data.forEach(function(el, i) {
	                //sprawdzam, czy ostatni marker jest inny od wczytanego
	                if (!markers.length || (markers.length && (markers[markers.length-1].getPosition().lat() !== el.lat || markers[markers.length-1].getPosition().lng() !== el.lng))) {
	                    newMarkersData.push({
	                        'lat' : el.lat,
	                        'lng' : el.lng,
	                        'iconUrl' : el.iconUrl,
	                        'time' : el.time,
	                        'text' : markers.length + i + 1
	                    });
	                }
	            });

	            drawLineBetweenNewMarkers(newMarkersData);
	            createNewMarkers(newMarkersData);

	            setTimeout(function() {
					mainLoop();
	            }, 3000)
	        },
	        error: function() {
	            console.warn('Błąd wczytania danych');
	        }
	    });
	}

	function mainLoop() {
		loadNewMarkers();
	}

	$(function() {
		createMap();
		mainLoop();
	});
})();

Niby jest to klasyczne podejście. W callbacku success ajax dostaję wczytane dane i mogę wywołać dodatkowe funkcje które na nich operują. Ale nie za bardzo mi się podobało w tym przypadku. Funkcja mainLoop() jak sama nazwa wskazuje powinna być główną funkcją-pętlą,
czyli po wykonaniu wszystkich czynności powinna wywołać samą siebie jeszcze raz. Niestety w powyższym skrypcie było to niemożliwe.
Wywoływana funkcja loadNewMarkers() wczytuje dane asynchronicznie. Dopiero po ich wczytaniu mogą być wykonane kolejne funkcje rysujące markery i linie między nimi dopiero po wykonaniu tych czynności możemy ponownie odpalić funkcję mainLoop(). Czyli nasza główna funkcja rozbita jest na dwie funkcje. A przecież mogło by być o wiele gorzej, bo przecież mogłem mieć tutaj więcej operacji asynchronicznych.

Gdybym mógł tylko taką asynchroniczną funkcję jakoś kolejkować – „hej – obiecuję ci, że funkcja loadNewMarkers wczyta dane, a jak tylko to zrobi, wykonaj inne czynności”. Na szczęście mogłem to zrobić. Jakiś czas temu dość intensywnie pracowałem z Angularowym $http, a tam na porządku dziennym jest stosowanie obietnic – Promisów, czyli kolejkowania asynchronicznych zapytań (głównie stosowałem to do grupowania np dwóch oddzielnych zapytań czekając na ich zakończenie). jQuery udostępnia obiekt $.Deferred, który jest jQuerową implementacją promisów.

Jak działa taki obiekt? Funkcja w pierwszej kolejności zwraca obiekt Deferred() i jest w ten sposób dodawana do kolejki. Następnie w tej funkcji wykonywane są jakieś akcje asynchroniczne – np ajax. Gdy się zakończą, zwracane jest albo resolve(data) gdy jest zakończona pomyślnie, lub reject(data) gdy zakończyła się niepowodzeniem:

var exampleAsync = function() {
	var deffered = $.Deferred();

	$.ajax({ //to sie wykona za chwile
		url : 'example.php',
		success : function(ret) {
			deffered.resolve(ret);
		},
		error: function(error) {
			deffered.reject(error);
		}
	})
	return deffered; //w pierwszej kolejności wykona się kod nie asynchroniczny - czyli zwrócony zostanie deffered
}

$.when(exampleAsync()).then(function(ret) {
	//gdy pomyślnie
}, function(error) {
	//gdy źle
})
//kiedy zakonczą się operacje w ajax1, ajax2
$.when(ajax1, ajax2).then(function(ret) {
	... ret[0] pochodzi z ajax1, ret[1] pochodzi z ajax2
}, function(err) {
	... err[0] pochodzi z ajax1, err[1] pochodzi z ajax2
})

Po zastosowaniu obiektu $.Deffered kod wyglądał tak:

(function() {
	var map = null; //mapa google

	function createMap() {
	    ...
	}

	function loadNewMarkers() {
	    var deffer = jQuery.Deferred();
	    var newMarkersData = [];

	    $.ajax({
	        type: "GET",
	        url: 'generate_coords.php',
	        dataType: "json",
	        cache: false,
	        success: function (json) {
	            json.data.forEach(function(el, i) {
	                if (!markers.length || (markers.length && (markers[markers.length-1].getPosition().lat() !== el.lat || markers[markers.length-1].getPosition().lng() !== el.lng))) {
	                    newMarkersData.push({
	                        'lat' : el.lat,
	                        'lng' : el.lng,
	                        'iconUrl' : el.iconUrl,
	                        'time' : el.time,
	                        'text' : markers.length + i + 1
	                    });
	                }
	            });

	            deffer.resolve(newMarkersData);
	        },
	        error: function() {
	            deffer.reject();
	        }
	    });

	    return deffer.promise();
	}

	function createNewMarkers(markersData) {
        markersData.forEach(function(el, i) {
            var newMarker = createSingleMarker(el);
            markers.push(newMarker);
        });
    }

	function mainLoop() {
		$.when(loadNewMarkers()).then(function(newMarkers) {
			drawLineBetweenNewMarkers(newMarkersData);
            createNewMarkers(newMarkersData);

            setTimeout(function() {
				mainLoop();
            }, 3000)
		}, function(error) {
			console.warn('Błąd wczytania danych');
		});
	}

	$(function() {
		...
	});
})();

Dzięki temu funkcja loadNewMarkers() stała się zamkniętym pudełkiem, który tylko coś zwraca, a główna funkcja-pętla wreszcie zaczęła sama siebie wywoływać (pętle tak już mają).

Tworzenie markerów na mapie

Kolejne punkty były już bardzo proste. Miałem już wczytane dane w zmiennej newMarkersData, więc wystarczyło napisać funkcję tworzącą markery:

(function() {
	var map = null; // obiekt globalny
    var markers = []; //markery na mapie

	function createNewMarkers(markersData) {
	    markersData.forEach(function(el, i) {
	        var newMarker = createSingleMarker(el);
	        markers.push(newMarker);
	    });
	}

	function createSingleMarker(ob) {
		//okreslenie grafiki markera
	    var image = {
	        url: ob.iconUrl,
	        size: new google.maps.Size(39, 36), //rozmiar ikony
	        origin: new google.maps.Point(0, 0), //pozycja na spricie
	        anchor: new google.maps.Point(20, 31), //x,y miejsca w ktore wskazuje ikona - grot ikony
	        labelOrigin: new google.maps.Point(19, 15) //pozycja teksty labelki
	    };

		//ksztalt klikalnej powierzchni markera
	    var shape = {
	        coords: [1, 1, 1, 20, 18, 20, 18, 1],
	        type: 'poly'
	    };

		//każdy marker będzie miał numer porządkowy. Poniżej go tworzymy
	    var label = new MarkerWithLabel({
	        color : '#fff',
	        fontFamily : 'sans-serif',
	        fontSize : '12px',
	        fontWeight : 'bold',
	        text : '' + ob.text
	    });

		//tworzymy marker z powyższymi danymi
	    var marker = new google.maps.Marker({
	        position: new google.maps.LatLng(ob.lat, ob.lng),
	        icon: image,
	        map: map,
	        shape: shape,
	        label: label,
	        labelOrigin: new google.maps.Point(26.5, 20),
	        zIndex: 1
	    });
	    marker.txt = '<strong>Godzina:</strong><br />' + ob.time + '<br />' + ob.lat + '-' + ob.lng;

		//tworzymy pokazywanie dymka po kliknięciu na marker
	    google.maps.event.addListener(marker, "click", function() {
	        mapTooltip.setPosition(marker.getPosition());
	        mapTooltip.setContent(marker.txt);
	        mapTooltip.open(map);
	    });

	    return marker;
	}

	function loadNewMarkers() {
		...
	}

	function mainLoop() {
		...
	}

	$(function() {
		...
	});
})();

Druga funkcja może wydawać się trochę skomplikowana, ale praca tutaj polegała głównie na kilku zapytaniach do Google (1, 2).

Chciałem dodatkowo na każdym markerze wypisywać liczbę porządkową. Okazało się, że aby móc korzystać z tekstowych labelek na markerach, musimy do naszego html dodać dodatkową bibliotekę:

<script src="https://cdn.rawgit.com/googlemaps/v3-utility-library/master/markerwithlabel/src/markerwithlabel.js"></script>

Rysowanie linii między markerami

Kolejną funkcją jaką stworzyłem była funkcja rysująca linie między markerami:

(function() {
	var map = null; //obiekt globalny
	var mapTooltip = null; 	//okienko z informacjami po kliknięciu na marker
    var markers = []; //markery na mapie
    var mapLines = []; //linie na mapie

	function drawLineBetweenNewMarkers(markersData) {
	    var linesCoordinates = [];

	    //bede rysowal linie miedzy ostatnim markerem a pierwszym z nowo wczytanych
	    if (markers.length) {
	        linesCoordinates.push({
	            lat : markers[markers.length-1].getPosition().lat(),
	            lng : markers[markers.length-1].getPosition().lng()
	        })
	    }

		//nowo wczytane...
	    markersData.forEach(function(el, i) {
	        linesCoordinates.push({
	            lat : el.lat,
	            lng : el.lng
	        });
	    });

	    var line = new google.maps.Polyline({
	        path: linesCoordinates, //przekazuję powyżej zebrane markery
	        geodesic: true,
	        strokeColor: '#DB3737',
	        strokeOpacity: 0.7,
	        strokeWeight: 2
	    });

	    line.setMap(map);

	    mapLines.push(line);
	}

	function createNewMarkers(markersData) {
		...
	}

	function createSingleMarker(ob) {
		...
	}

	function loadNewMarkers() {
	    ...
	}

	function mainLoop() {
		...
	}

	$(function() {
		...
	})
})();

Nie chciałem za każdym razem rysować linii między wszystkimi markerami, dlatego rysowałem tylko między ostatnio wczytanymi (czyli te przekazane jako atrybut markersData). Dodatkowo trzeba było narysować linię między istniejącymi na mapie markerami a tymi wczytanymi w ostatnim zapytaniu. Pobierałem więc ostatni marker z mapy i wstawiałem go wraz z ostatnio wczytanymi do wspólnej tablicy, którą następnie przekazywałem do metody Polyline, którą udostępnia Google mapa. Wszystko jest w powyższym kodzie :)
Dodatkowo tak jak w przypadku markerów wszystkie linie wrzucałem do wspólnej tablicy mapLines.

Czemu ta funkcja jest wywoływana przed funkcją createNewMarkers()? Jak powyżej wspomniałem, chciałem też narysować linię między ostatnim istniejącym na mapie markerem, a tymi ostatnio wczytanymi. Gdybym przed funkcją drawLineBetweenNewMarkers() wywołał createNewMarkers(), wtedy nowo wczytane markery trafiły by już na mapę, czyli nie mógł bym pobrać ostatniego markera z mapy, bo był by tym samym co ostatni z wczytanych.

Centrowanie mapy

Po wczytaniu pierwszych markerów chciałem ułatwić użytkownikowi życie centrując mapę na wczytanych markerach. Takiego centrowania na markerach nie robiłem non stop, ponieważ uniemożliwiło by to użytkownikowi przewijanie mapy. Wystarczyło wycentrować tylko po wczytaniu pierwszego i drugiego markera:

(function() {
	var map = null; // obiekt globalny
	var mapTooltip = null; 	//okienko z informacjami po kliknięciu na marker
    var markers = []; //markery na mapie
    var mapLines = []; //linie na mapie
	var centerMap = false; //centrujemy mapę tylko jeden raz

	function centerCanvas() {
        //powiekszamy obszar tak by obejmowal markery
        var bounds = new google.maps.LatLngBounds();
        markers.forEach(function(el, i) {
            var latlng = new google.maps.LatLng(el.getPosition().lat(), el.getPosition().lng());
            bounds.extend( latlng );
        });

        if (markers.length > 1) {
            map.fitBounds( bounds );
        }
        map.setCenter( bounds.getCenter() );
    }

	function drawLineBetweenNewMarkers(markersData) {
	    ...
	}

	function createNewMarkers(markersData) {
		...
	}

	function createSingleMarker(ob) {
		...
	}

	function loadNewMarkers() {
	    ...
	}

	function mainLoop() {
		$.when( loadNewMarkers() ).then(
            function(markersData) {
                drawLineBetweenNewMarkers(markersData);
                createNewMarkers(markersData);

                //dopiero po wczytaniu min 2 markerow centrujemy
                //potem juz nie bedziemy centrowac
                if (markers.length && !centerMap) {
                    centerCanvas();
                    if (markers.length >=2) {
                        centerMap = true;
                    }
                }
                setTimeout(function() {
                    mainLoop();
                }, mapInterval);
            }
        );
	}

	$(function() {
		...
	})
})();

Czyszczenie mapy

Ostatnimi funkcjami jakie dodałem były te czyszczące mapę. Dzięki temu, że zarówno markery jak i linie wrzucałem do tablic markers i mapLines czyszczenie takie było banalnie proste (1, 2):

(function() {
	var map = null; //obiekt globalny
	var mapTooltip = null; 	//okienko z informacjami po kliknięciu na marker
    var markers = []; //markery na mapie
    var mapLines = []; //linie na mapie
	var centerMap = false; //centrujemy mapę tylko jeden raz

	function clearMarkers() {
        markers.forEach(function(el, i) {
            el.setMap(null);
        });
        markers = [];
    }

    function clearLines() {
        mapLines.forEach(function(el, i) {
            el.setMap(null);
        });
        mapLines = [];
    }

	function centerCanvas() {
        ...
    }

	function drawLineBetweenNewMarkers(markersData) {
	    ...
	}

	function createNewMarkers(markersData) {
		...
	}

	function createSingleMarker(ob) {
		...
	}

	function loadNewMarkers() {
	    ...
	}

	function mainLoop() {
		...
	}

	$(function() {
		...
	})
})();

Wystarczyło teraz te funkcje podpiąć pod przycisk, który dodałem do strony:

(function() {
	var map = null; //obiekt globalny
	var mapTooltip = null; 	//okienko z informacjami po kliknięciu na marker
    var markers = []; //markery na mapie
    var mapLines = []; //linie na mapie
	var centerMap = false; //centrujemy mapę tylko jeden raz

	function clearMarkers() {
        ...
    }

    function clearLines() {
        ...
    }

	function centerCanvas() {
        ...
    }

	function drawLineBetweenNewMarkers(markersData) {
	    ...
	}

	function createNewMarkers(markersData) {
		...
	}

	function createSingleMarker(ob) {
		...
	}

	function loadNewMarkers() {
	    ...
	}

	function mainLoop() {
		...
	}

	$(function() {
		createMap();
        mainLoop();

		$('.clear-map').on('click', function(el) {
            clearLines();
            clearMarkers();
            centerMap = false;
        });
	})
})();

<div id="map"></div>
<button class="clear-map">Wyczyść mapę</button>
.clear-map {
    position: fixed;
    top:20px;
    right:20px;
    background: #E33D3D;
    border:0;
    padding:13px 30px;
    color:#fff;
    font-size:14px;
    text-transform: uppercase;
    font-weight: bold;
    font-family: sans-serif;
    transition:0.3s background-color;
    cursor: pointer;
}
.clear-map:hover {
    background: #B21A1A;
}

No i tyle. W zasadzie mapa była gotowa:

Zobacz demo

Socket.io

Ale nie do końca podobało mi się, że muszę wywoływać w pętli zapytania do serwera. Jakieś takie to mało optymalne. Od razu w głowie pojawił mi się stary problem budowy czatów w php. Kto tego próbował ten będzie wiedział o czym piszę. PHP w połączeniu z Ajaxem nie za bardzo się do tego nadawał (niestety nie wiem czy teraz jest lepiej) właśnie z powodu konieczności ciągłego odpytywania się serwera o nowe informacje. W naszym przypadku odpytujemy serwer co 3 sekundy (3000ms), ale w czatach dla komfortu rozmowy takie opdytywanie powinno być o wiele szybsze. Czyli zapchanie łącza gwarantowane.

W Node ten problem nie istnieje. Skorzystałem z biblioteki socket.io. Po odpaleniu skryptu na serwerze emituje on event który zawiera dane. Po stronie przeglądarki zamiast wciąż się pytać serwera o nowe dane, po prostu nasłuchujemy eventy (możemy też eventy nadawać, ale w naszym przypadku nie jest to potrzebne). Jeżeli taki event z danymi wykryjemy, odpalamy stosowne akcje. Czyli praktycznie jak z klasycznymi eventami w JS…

Korzystając z tutoriala ze strony http://socket.io/get-started/chat/ napisałem prosty skrypt index.js działający po stronie serwera i generujący przykładowe dane takie same jak wcześniej zwracał PHP. Do skryptu dodałem bibliotekę moment(), tak by łatwo móc zwrócić sformatowaną datę, oraz ustawiłem serwowanie statycznych plików js/css/images:

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var moment = require('moment');

app.get('/', function(req, res){  
  res.sendFile(__dirname + '/index.html');
});

//ustawiam statyczne pliki css, js, images
app.use('/images', express.static(__dirname + '/images'));
app.use('/js', express.static(__dirname + '/js'));
app.use('/css', express.static(__dirname + '/css'));

//sobie slucham
http.listen(3000, function(){
  console.log('listening on *:3000');
});

//odpalam socket.io renderujac przykladowe dane
io.on('connection', function(socket){
  console.log('user connected');
  var ob = [
        {
            "id"      : Math.random(),
            "lat"     : 52 + Math.random() + Math.random() + Math.random(),
            "lng"     : 14 + Math.random() + Math.random() + Math.random(),
            "iconUrl" : "/images/map-marker.png",
            "time"    : moment().format('YYYY/MM/DD HH:mm:ss')
        }
    ];

    io.emit('dataMapEvent', ob);

    socket.on('disconnect', function(){
        console.log('user disconnected');
    });
});

setInterval(function() {
    var ob = [
        {
            "id"      : Math.random(),
            "lat"     : 52 + Math.random() + Math.random() + Math.random(),
            "lng"     : 14 + Math.random() + Math.random() + Math.random(),
            "iconUrl" : "/images/map-marker.png",
            "time"    : moment().format('YYYY/MM/DD HH:mm:ss')
        }
    ];

    io.emit('dataMapEvent', ob);
}, 3000)

Następnie odpaliłem go za pomocą polecenia node index.js (wszystko jest we wspomnianym tutorialu).

Ze skryptu mapy który wcześniej napisałem wyrzuciłem całą główną pętlę mainLoop() oraz funkcję loadNewMarkers() (dane dostaniemy wraz z eventem), a zamiast niej podpiąłem się za pomocą biblioteki socket.io pod emitowany z serwera event:

(function() {
	var map = null; //obiekt globalny
	var mapTooltip = null; 	//okienko z informacjami po kliknięciu na marker
    var markers = []; //markery na mapie
    var mapLines = []; //linie na mapie
    var centerMap = false; //centrujemy mapę tylko jeden raz

	function clearMarkers() {
        ...
    }

    function clearLines() {
        ...
    }

	function centerCanvas() {
        ...
    }

	function drawLineBetweenNewMarkers(markersData) {
	    ...
	}	

	function createNewMarkers(markersData) {
		...
	}

	function createSingleMarker(ob) {
		...
	}

	function mainLoop() {
		...
	}

	$(function() {
        createMap();

        var centerMap = false;
        socket.on('dataMapEvent', function(res){
            drawLineBetweenNewMarkers(res);
            createNewMarkers(res);            
            
            //centrujemy przy 1, potem juz nie centrujemy
            //poniewaz powodowalo by to, ze uzytkownik nie mogl by przesuwac mapy
            if (markers.length && !centerMap) {
                centerCanvas();    
                if (markers.length >=2) {
                    centerMap = true;            
                }
            }            
        });            

        $('.clear-map').on('click', function(el) {
            clearLines();
            clearMarkers();
            centerMap = false;
        });
    })
})();

Niestety końcowego efektu Ci nie pokażę, bo mój hosting na to nie pozwala :) (jak tylko pojawi się tutaj więcej takich materiałów obiecuję poprawę). Paczkę z tym rozwiązaniem możesz pobrać tutaj i sprawdzić na swoim komputerze. Jak nie wiesz jak to odpalić, daj znać – jakoś podziałamy.

A i pytanie na koniec. Czy taka forma tekstu i lisingów ze skróconymi kropkowanymi funkcjami jest znośna?

Komentarze

  • Mateusz Tomaszewski

    świetny poradnik :)