I’ve been working on a project with GPS data, where a map plotting would be handy. At first I thought about using Google Maps, but it’s always better
go open source, and since I’m an enthusiast of
OpenStreetMap, I started searching a way to do it, and I found the
OpenLayers project, which is insanely cool.
My goal: plot some small images on the map (icons), and when the cursor goes over an image, a popup would appear. On my way to make it happen, I had to develop some interesting things, that I share now with everyone, in the hope that it can be useful.
The first difficulty I found was to make OpenLayers understand
GPS coordinates without much fuss. To do so, you must have a coordinate transformation, which is not exactly trivial. Below is a function to make this conversion trivial, yes, so that you’ll never need to worry about it again. The
map
argument is the map object you’re working upon, and
lon
and
lat
are floating point numbers.
function Geo(map, lat, lon) {
return new OpenLayers.LonLat(lon, lat)
.transform(new OpenLayers.Projection('EPSG:4326'),
map.getProjectionObject());
}
// Example usage:
var map = new OpenLayers.Map('myMapDivId');
map.addLayer(new OpenLayers.Layer.OSM());
var coor = Geo(map, -5.773274, -35.204948);
map.setCenter(coor, 8);
Second, the image (icon) plotting. When you plot an image at a given point, this image is centered at that point. I wanted to plot an image of a
pushpin, which points at the wrong location if centered – the very end of the pushpin will point to a location below the center of the image. It had to be moved above, so that the bottom of the image was exactly over the point.
The function below creates the image and plots it with a bottom offset, if needed. The
img
argument is a valid image URL,
cx
and
cy
are the size of the image in pixels, and
bottomOffset
is a boolean value. The function is
asychronous and waits the image to be loaded; use the
onDone
callback, which receives the new marker as argument.
function PlotMarker(map, img, lat, lon, bottomOffset, onDone) {
var imgObj = new Image();
imgObj.src = img;
imgObj.onload = function() {
var sz = new OpenLayers.Size(imgObj.width, imgObj.height);
var off = new OpenLayers.Pixel(-(sz.w / 2),
bottomOffset ? -sz.h : -(sz.w / 2));
var ico = new OpenLayers.Icon(img, sz, off);
if(onDone !== undefined && onDone !== null)
onDone(new OpenLayers.Marker(Geo(map, lat, lon), ico));
};
}
// Example usage:
var map = new OpenLayers.Map('myMapDivId');
map.addLayer(new OpenLayers.Layer.OSM());
var markers = new OpenLayers.Layer.Markers('Markers');
map.addLayer(markers);
PlotMarker(map, 'pushpin.png', -5.773274, -35.204948,
true, // it's a pushpin, I want it bottom-aligned
function(mk) {
markers.addMarker(mk);
});
Below you have a
fully functional web application composed of three files, which displays an OpenStreetMap map at the whole page, loads points from a separated JSON file, and renders it accordingly. There’s no server-side processing involved, but the JSON file could easily be something processed by the server, which would load the data from some sort of database. I used
jQuery library to handle the mouseover event which shows the popup over the images, but it’s not really necessary if you want to use something else.
1) index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Rodrigo's map</title>
<style>
* { -moz-box-sizing:border-box; -webkit-box-sizing:border-box; box-sizing:border-box; }
body { margin:0; font:10pt Arial; color:#242424; }
#mapArea { position:absolute; width:100%; height:100%; }
</style>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
<script src="http://openlayers.org/api/2.13.1/OpenLayers.js"></script>
<script src="index.js"></script>
</head>
<body>
<div id="mapArea"></div>
</body>
</html>
2) index.js
function PlotData(divId, data) {
function Geo(map, lat, lon) {
return new OpenLayers.LonLat(lon, lat)
.transform(new OpenLayers.Projection('EPSG:4326'), map.getProjectionObject());
}
function PlotMarker(map, img, lat, lon, bottomOffset, onDone) {
var imgObj = new Image();
imgObj.src = img;
imgObj.onload = function() {
var sz = new OpenLayers.Size(imgObj.width, imgObj.height);
var off = new OpenLayers.Pixel(-(sz.w / 2),
bottomOffset ? -sz.h : -(sz.w / 2));
var ico = new OpenLayers.Icon(img, sz, off);
if(onDone !== undefined && onDone !== null)
onDone(new OpenLayers.Marker(Geo(map, lat, lon), ico));
};
}
var map = new OpenLayers.Map(divId);
map.addLayer(new OpenLayers.Layer.OSM());
map.setCenter(Geo(map, data.center[0], data.center[1]), data.zoom);
var markers = new OpenLayers.Layer.Markers('Markers');
map.addLayer(markers);
var $map = $('#'+divId),
$det = $('<div></div>').css({
'position':'absolute',
'padding':'0 4px',
'display':'none',
'box-shadow':'2px 2px 3px #999',
'background-color':'rgba(255,255,255,.85)',
'border':'1px solid #AAA'
}).appendTo('body');
$.each(data.points, function(i, pt) {
PlotMarker(map, pt.icon, pt.coor[0], pt.coor[1], true, function(mk) {
markers.addMarker(mk);
mk.events.on({
mouseover: function(ev) {
$det.html(pt.text).css({
left: (ev.pageX < $(document).width() / 2) ?
ev.pageX+'px' : (ev.pageX - $det.outerWidth())+'px',
top: (ev.pageY < $(document).height() / 2) ?
ev.pageY+'px' : (ev.pageY - $det.outerHeight())+'px',
display: 'block'
});
$map.css('cursor', 'pointer');
},
mouseout: function(ev) {
$det.empty().css('display', 'none');
$map.css('cursor', 'auto');
}
});
});
});
}
$(document).ready(function() {
$.getJSON('data.json', function(data) {
PlotData('mapArea', data);
});
});
3) data.json – assumes image “pushpin.png” exists
{
"center": [ -5.797942,-35.211782 ],
"zoom": 14,
"points": [
{
"coor": [ -5.799500,-35.21951 ],
"icon": "pushpin.png",
"text": "First point<br/>Anything here"
},
{
"coor": [ -5.790982,-35.19409 ],
"icon": "pushpin.png",
"text": "Other <b>point</b> here"
},
{
"coor": [ -5.802083,-35.20877 ],
"icon": "pushpin.png",
"text": "Something else<br/>placed here"
}
]
}
Enjoy.