Our goal is to display website visitors on the map, similar to the screenshot below.
We will convert their IP address to lat/lng coordinates and display those markers on OpenStreetMap map. To perform the conversion of IP addresses to lat/lng pairs we are going to use the geolocation data from ip2location.com.
We will display users that were active in the last ten minutes. If the user had some activity in the last 60 seconds, their dot will be pulsing.
There is also a YouTube video that provides more details of this project.
We are going to need two tables, ‘users’ and ‘ip2location’. The following is the script for MySQL database.
CREATE TABLE `ip2location`(`id` int NOT NULL AUTO\_INCREMENT, `ip\_start` decimal(20,6) NULL, `ip\_end` decimal(20,6) NULL, `STATE` varchar(50) NULL, `COUNTRY` varchar(50) NULL, `REGION` varchar(50) NULL, `CITY` varchar(100) NULL, `LATITUDE` double NULL, `LONGITUDE` double NULL, PRIMARY KEY (`id`))CHARACTER SET utf8;CREATE TABLE `users`(`id` int NOT NULL AUTO\_INCREMENT, `ip` varchar(50) NOT NULL DEFAULT '0', `lat` double NOT NULL DEFAULT 0, `lng` double NOT NULL DEFAULT 0, `last\_activity` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`))CHARACTER SET utf8;
Please note that this SQL script only creates ‘ip2location’ but doesn’t come with the data. The data set itself is about 300Mb and you can download it for free at https://lite.ip2location.com/database/ip-country.
PHP code:
echo "<div id='map' style=''></div>";
3. AfterApplicationInitialized event
PHP code:
// convert IP address to a decimal number in order to perform a database searchfunction ip\_to\_decimal($ip\_address) { $parts = explode('.', $ip\_address); $decimal\_ip = 0; foreach ($parts as $part) { $decimal\_ip = $decimal\_ip * 256 + (int) $part; } return $decimal\_ip;}function saveCurrentUserData(){$ip = $\_SERVER["REMOTE\_ADDR"];if( empty($ip) )return false;$userRs = DB::Select("users",array("ip" => $ip));$user = $userRs->fetchAssoc();if( $user ){DB::Update("users", array("last\_activity" => date("Y-m-d H:i:s")) ,array("ip" => $ip));}else{$decimalip = ip\_to\_decimal($ip);$coordsRs = DB::Query("select * from ip2location where ".$decimalip." BETWEEN ip\_start and ip\_end");$coords = $coordsRs->fetchAssoc();if( $coords ){$userData = array("ip" => $ip, "lat" => $coords["LATITUDE"], "lng" => $coords["LONGITUDE"], "last\_activity" => date("Y-m-d H:i:s"));DB::Insert("users",$userData);}}}if( postvalue("getActiveUsers") ){$interval = 10; // we only display on the map users that accessed any page in the last ten minutessaveCurrentUserData();$dateCondition = date("Y-m-d H:i:s",time() - ($interval*60));$userRs = DB::Query("select * from users where last\_activity > '".$dateCondition."'");$latLng = array(); $userData = $userRs->fetchAssoc();while( $userData ){$coordsInfo = array("id" => $userData['id'], "lat" => $userData['lat'], "lng" => $userData['lng'], "active" => false);if( ( time() - strtotime($userData["last\_activity"]) ) <=60 ) { $coordsInfo['active'] = true; }$latLng[] = $coordsInfo; $userData = $userRs->fetchAssoc();}echo my\_json\_encode($latLng);exit();}
4. custom_function.js
The following Javascript code goes to Event Editor -> custom_function.js section.
$(document).ready(function() { $("#map").width($(".r-fluid").width()); var height = $(window).height() - $("#map").offset().top - 30; $("#map").height(height); window.mapObj = new OpenLayers.Map("map", { controls: [ new OpenLayers.Control.PanZoomBar(), new OpenLayers.Control.Navigation() ], }); var layer = new OpenLayers.Layer.OSM(); mapObj.addLayer(layer); window.markersList = new OpenLayers.Layer.Markers("Markers"); mapObj.addLayer(markersList); mapObj.zoomToMaxExtent(); updateMarkers(); setInterval(updateMarkers, 5000); function updateMarkers() { $.post("", { getActiveUsers: true }, function(response) { var coordsArr = JSON.parse(response), activeIds = coordsArr.map(function(coords) { return coords.id; }), allIds = markersList.markers.map(function(marker) { return marker.id; }); $.each(coordsArr, function(i, latLon) { if (!allIds.includes(latLon.id)) { addMarker(latLon.id, latLon.lat, latLon.lng, latLon.active); } else { var curMarker = markersList.markers.find(function(marker) { return marker.id == latLon.id }); if (curMarker.active != latLon.active) { $(curMarker.icon.imageDiv).toggleClass("active", latLon.active); } } }); allIds = markersList.markers.map(function(marker) { return marker.id; }); for (var i = 0; i < allIds.length; i++) { if (allIds[i] != undefined && !activeIds.includes(allIds[i])) { var markerToRemove = markersList.markers.find(function(marker) { return marker.id == allIds[i] }); markersList.removeMarker(markerToRemove); } } function addMarker(id, lat, lng, active) { var lonLat = new OpenLayers.LonLat(lng, lat) .transform( new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 mapObj.getProjectionObject() // to Spherical Mercator Projection ); var icon = new OpenLayers.Icon("", new OpenLayers.Size(15, 15)); var marker = new OpenLayers.Marker(lonLat, icon); if (active) { $(marker.icon.imageDiv).addClass("active"); } marker.id = id; marker.active = active; markersList.addMarker(marker); return marker; } }); } updateMarkers();});
5. CSS code ( Style Editor -> Modify CSS )
We use this CSS code to customize and prettify the default look of OSM map.
.olTileImage { filter: brightness(48%) contrast(256%);}[id^="OL\_Icon"] .olAlphaImg { background: white; cursor: pointer; border-radius: 100%; } [id^="OL\_Icon"].active .olAlphaImg { animation: pulse 2s infinite; box-shadow: 10px 10px 10px rgba(255,255,255, 0.7); width:20px !important; height: 20px !important; } @-webkit-keyframes pulse { 0% { -webkit-box-shadow: 10px 10px 10px rgba(255,255,255, 0.7); } 70% { -webkit-box-shadow: 0 0 0 10px rgba(255,255,255, 0); } 100% { -webkit-box-shadow: 0 0 0 0 rgba(255,255,255, 0); } } @keyframes pulse { 0% { -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0.7); box-shadow: 0 0 0 0 rgba(255,255,255, 0.7); } 70% { -moz-box-shadow: 0 0 0 10px rgba(255,255,255, 0); box-shadow: 0 0 0 10px rgba(255,255,255, 0); } 100% { -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0); box-shadow: 0 0 0 0 rgba(255,255,255, 0); } }