{"id":3074,"date":"2024-01-03T20:43:29","date_gmt":"2024-01-04T01:43:29","guid":{"rendered":"https:\/\/xlinesoft.com\/blog\/?p=3074"},"modified":"2024-01-04T12:54:46","modified_gmt":"2024-01-04T17:54:46","slug":"displaying-website-visitors-on-the-map","status":"publish","type":"post","link":"https:\/\/xlinesoft.com\/blog\/2024\/01\/03\/displaying-website-visitors-on-the-map\/","title":{"rendered":"Displaying website visitors on the map"},"content":{"rendered":"<p>Our goal is to display website visitors on the map, similar to the screenshot below.<\/p>\n<p>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 <a href=\"https:\/\/ip2location.com\">ip2location.com<\/a>.<\/p>\n<p>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.<\/p>\n<p><a href=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map-600x288.png\" alt=\"\" width=\"600\" height=\"288\" class=\"alignnone size-medium wp-image-3075\" srcset=\"https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map-600x288.png 600w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map-1024x491.png 1024w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map-768x368.png 768w, https:\/\/xlinesoft.com\/blog\/wp-content\/uploads\/2024\/01\/interactive_map.png 1471w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/a><br \/>\n<!--more--><\/p>\n<p>There is also a <a href=\"https:\/\/youtu.be\/Xb5vmbVPe_g\">YouTube video<\/a> that provides more details of this project.<\/p>\n<p>1. Database structure. <\/p>\n<p>We are going to need two tables, &#8216;users&#8217; and &#8216;ip2location&#8217;. The following is the script for MySQL database. <\/p>\n<div class=\"my-syntax-highlighter\">\n<pre><textarea id=\"mshighlighter\" class=\"mshighlighter\" language=\"sql\" name=\"mshighlighter\" >\r\nCREATE 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;\r\nCREATE 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;<\/textarea><\/pre>\n<\/div>\n<p>Please note that this SQL script only creates &#8216;ip2location&#8217; but doesn&#8217;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.<\/p>\n<p>2. Insert a code snippet into a page where you want to display the map. In our case it would be the menu page. The code itself is very simple, it merely outputs the div with &#8216;map&#8217; ID. <\/p>\n<p><strong>PHP code:<\/strong><\/p>\n<div class=\"my-syntax-highlighter\">\n<pre><textarea id=\"mshighlighter\" class=\"mshighlighter\" language=\"php\" name=\"mshighlighter\" >\r\necho \"<div id='map' style=''><\/div>\";<\/textarea><\/pre>\n<\/div>\n<p>3. AfterApplicationInitialized event<\/p>\n<p><strong>PHP code:<\/strong><\/p>\n<div class=\"my-syntax-highlighter\">\n<pre><textarea id=\"mshighlighter\" class=\"mshighlighter\" language=\"php\" name=\"mshighlighter\" >\r\n\r\n\/\/ convert IP address to a decimal number in order to perform a database search\r\nfunction ip_to_decimal($ip_address) {\r\n    $parts = explode('.', $ip_address);\r\n    $decimal_ip = 0;\r\n    foreach ($parts as $part) {\r\n        $decimal_ip = $decimal_ip * 256 + (int) $part;\r\n    }\r\n    return $decimal_ip;\r\n}\r\n\r\nfunction saveCurrentUserData(){\r\n\t\t$ip = $_SERVER[\"REMOTE_ADDR\"];\r\n\t\tif( empty($ip) )\r\n\t\t\treturn false;\r\n\t\t$userRs = DB::Select(\"users\",array(\"ip\" => $ip));\r\n\t\t$user = $userRs->fetchAssoc();\r\n\t\tif( $user ){\r\n\t\t\tDB::Update(\"users\", array(\"last_activity\" => date(\"Y-m-d H:i:s\")) ,array(\"ip\" => $ip));\r\n\t\t}\r\n\t\telse{\r\n\t\t\t$decimalip = ip_to_decimal($ip);\r\n\t\t\t$coordsRs = DB::Query(\"select * from ip2location where \".$decimalip.\" BETWEEN ip_start and ip_end\");\r\n\r\n\t\t\t$coords = $coordsRs->fetchAssoc();\r\n\t\t\tif( $coords ){\r\n\t\t\t\t$userData = array(\"ip\" => $ip,\r\n                                            \"lat\" => $coords[\"LATITUDE\"], \r\n                                            \"lng\" =>  $coords[\"LONGITUDE\"],\r\n                                             \"last_activity\" => date(\"Y-m-d H:i:s\"));\r\n\t\t\t\tDB::Insert(\"users\",$userData);\r\n\t\t\t}\r\n\t\t}\r\n}\r\nif( postvalue(\"getActiveUsers\") ){\r\n\t$interval = 10;  \r\n        \/\/ we only display on the map users that accessed any page in the last ten minutes\r\n\r\n\tsaveCurrentUserData();\r\n\t$dateCondition = date(\"Y-m-d H:i:s\",time() - ($interval*60));\r\n\t$userRs = DB::Query(\"select * from users where last_activity > '\".$dateCondition.\"'\");\r\n\t$latLng = array();\r\n\r\n        $userData = $userRs->fetchAssoc();\r\n\twhile( $userData ){\r\n\t\t$coordsInfo = array(\"id\" => $userData['id'], \r\n                              \"lat\" => $userData['lat'],\r\n                              \"lng\" => $userData['lng'],\r\n                               \"active\" => false);\r\n\r\n\t\tif( ( time() - strtotime($userData[\"last_activity\"]) ) <=60 ) {\r\n\t\t    $coordsInfo['active'] = true;\r\n                }\r\n\t\t$latLng[] = $coordsInfo;\r\n                $userData = $userRs->fetchAssoc();\r\n\t}\r\n\techo my_json_encode($latLng);\r\n\texit();\r\n}<\/textarea><\/pre>\n<\/div>\n<p>4. custom_function.js <\/p>\n<p>The following Javascript code goes to Event Editor -> custom_function.js section. <\/p>\n<pre>\r\n$(document).ready(function() {\r\n\r\n    $(\"#map\").width($(\".r-fluid\").width());\r\n    var height = $(window).height() - $(\"#map\").offset().top - 30;\r\n    $(\"#map\").height(height);\r\n\r\n    window.mapObj = new OpenLayers.Map(\"map\", {\r\n        controls: [\r\n            new OpenLayers.Control.PanZoomBar(),\r\n\r\n            new OpenLayers.Control.Navigation()\r\n        ],\r\n    });\r\n\r\n    var layer = new OpenLayers.Layer.OSM();\r\n\r\n    mapObj.addLayer(layer);\r\n\r\n    window.markersList = new OpenLayers.Layer.Markers(\"Markers\");\r\n    mapObj.addLayer(markersList);\r\n    mapObj.zoomToMaxExtent();\r\n\r\n    updateMarkers();\r\n    setInterval(updateMarkers, 5000);\r\n\r\n    function updateMarkers() {\r\n\r\n        $.post(\"\", {\r\n            getActiveUsers: true\r\n        }, function(response) {\r\n\r\n            var coordsArr = JSON.parse(response),\r\n                activeIds = coordsArr.map(function(coords) {\r\n                    return coords.id;\r\n                }),\r\n                allIds = markersList.markers.map(function(marker) {\r\n                    return marker.id;\r\n                });\r\n\r\n\r\n            $.each(coordsArr, function(i, latLon) {\r\n\r\n                if (!allIds.includes(latLon.id)) {\r\n                    addMarker(latLon.id, latLon.lat, latLon.lng, latLon.active);\r\n                } else {\r\n                    var curMarker = markersList.markers.find(function(marker) {\r\n                        return marker.id == latLon.id\r\n                    });\r\n                    if (curMarker.active != latLon.active) {\r\n                        $(curMarker.icon.imageDiv).toggleClass(\"active\", latLon.active);\r\n                    }\r\n                }\r\n            });\r\n            allIds = markersList.markers.map(function(marker) {\r\n                return marker.id;\r\n            });\r\n\r\n            for (var i = 0; i < allIds.length; i++) {\r\n\r\n                if (allIds[i] != undefined &#038;&#038; !activeIds.includes(allIds[i])) {\r\n\r\n                    var markerToRemove = markersList.markers.find(function(marker) {\r\n                        return marker.id == allIds[i]\r\n                    });\r\n                    markersList.removeMarker(markerToRemove);\r\n                }\r\n\r\n            }\r\n\r\n            function addMarker(id, lat, lng, active) {\r\n\r\n                var lonLat = new OpenLayers.LonLat(lng, lat)\r\n                    .transform(\r\n                        new OpenLayers.Projection(\"EPSG:4326\"), \/\/ transform from WGS 1984\r\n                        mapObj.getProjectionObject() \/\/ to Spherical Mercator Projection\r\n                    );\r\n                var icon = new OpenLayers.Icon(\"\", new OpenLayers.Size(15, 15));\r\n                var marker = new OpenLayers.Marker(lonLat, icon);\r\n                if (active) {\r\n                    $(marker.icon.imageDiv).addClass(\"active\");\r\n                }\r\n\r\n                marker.id = id;\r\n                marker.active = active;\r\n                markersList.addMarker(marker);\r\n\r\n                return marker;\r\n            }\r\n        });\r\n\r\n\r\n    }\r\n    updateMarkers();\r\n\r\n\r\n});\r\n<\/pre>\n<p>5. CSS code ( Style Editor -> Modify CSS )<\/p>\n<p>We use this CSS code to customize and prettify the default look of OSM map. <\/p>\n<div class=\"my-syntax-highlighter\">\n<pre><textarea id=\"mshighlighter\" class=\"mshighlighter\" language=\"css\" name=\"mshighlighter\" >\r\n.olTileImage {\r\n    filter: brightness(48%) contrast(256%);\r\n}\r\n\r\n[id^=\"OL_Icon\"] .olAlphaImg {\r\n    background: white;\r\n    cursor: pointer;\r\n    border-radius: 100%;\r\n  }\r\n  [id^=\"OL_Icon\"].active .olAlphaImg {\r\n    animation: pulse 2s infinite;\r\n    box-shadow: 10px 10px 10px rgba(255,255,255, 0.7);\r\n    width:20px !important;\r\n    height: 20px !important;\r\n  }\r\n\r\n  @-webkit-keyframes pulse {\r\n    0% {\r\n      -webkit-box-shadow: 10px 10px 10px rgba(255,255,255, 0.7);\r\n    }\r\n    70% {\r\n        -webkit-box-shadow: 0 0 0 10px rgba(255,255,255, 0);\r\n    }\r\n    100% {\r\n        -webkit-box-shadow: 0 0 0 0 rgba(255,255,255, 0);\r\n    }\r\n  }\r\n  @keyframes pulse {\r\n    0% {\r\n      -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0.7);\r\n      box-shadow: 0 0 0 0 rgba(255,255,255, 0.7);\r\n    }\r\n    70% {\r\n        -moz-box-shadow: 0 0 0 10px rgba(255,255,255, 0);\r\n        box-shadow: 0 0 0 10px rgba(255,255,255, 0);\r\n    }\r\n    100% {\r\n        -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0);\r\n        box-shadow: 0 0 0 0 rgba(255,255,255, 0);\r\n    }\r\n  }<\/textarea><\/pre>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>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.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16,95,1,8],"tags":[],"_links":{"self":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/3074"}],"collection":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/comments?post=3074"}],"version-history":[{"count":33,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/3074\/revisions"}],"predecessor-version":[{"id":3108,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/posts\/3074\/revisions\/3108"}],"wp:attachment":[{"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/media?parent=3074"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/categories?post=3074"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/xlinesoft.com\/blog\/wp-json\/wp\/v2\/tags?post=3074"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}