mmg
Version:
Simple markers for Modest Maps
307 lines (263 loc) • 9.38 kB
JavaScript
function mmg() {
[].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b<c&&(!(b in this)||this[b]!==a);b++);return b^c?b:-1;});
var m = {},
// external list of geojson features
features = [],
// internal list of markers
markers = [],
// the absolute position of the parent element
position = null,
// a factory function for creating DOM elements out of
// GeoJSON objects
factory = null,
// a sorter function for sorting GeoJSON objects
// in the DOM
sorter = null,
// a list of urls from which features can be loaded.
// these can be templated with {z}, {x}, and {y}
urls,
// map bounds
left = null,
right = null;
// reposition a single marker element
function reposition(marker) {
// remember the tile coordinate so we don't have to reproject every time
if (!marker.coord) marker.coord = m.map.locationCoordinate(marker.location);
var pos = m.map.coordinatePoint(marker.coord);
var pos_loc;
// If this point has wound around the world, adjust its position
// to the new, onscreen location
if (pos.x < 0) {
pos_loc = new MM.Location(marker.location.lat, marker.location.lon);
pos_loc.lon += Math.ceil((left.lon - marker.location.lon) / 360) * 360;
pos = m.map.locationPoint(pos_loc);
marker.coord = m.map.locationCoordinate(pos_loc);
} else if (pos.x > m.map.dimensions.x) {
pos_loc = new MM.Location(marker.location.lat, marker.location.lon);
pos_loc.lon -= Math.ceil((marker.location.lon - right.lon) / 360) * 360;
pos = m.map.locationPoint(pos_loc);
marker.coord = m.map.locationCoordinate(pos_loc);
}
pos.scale = 1;
pos.width = pos.height = 0;
MM.moveElement(marker.element, pos);
}
m.draw = function() {
if (!m.map) return;
left = m.map.pointLocation(new MM.Point(0, 0));
right = m.map.pointLocation(new MM.Point(m.map.dimensions.x, 0));
for (var i = 0; i < markers.length; i++) {
reposition(markers[i]);
}
};
m.add = function(marker) {
if (!marker || !marker.element) return null;
parent.appendChild(marker.element);
markers.push(marker);
return marker;
};
m.remove = function(marker) {
if (!marker) return null;
parent.removeChild(marker.element);
markers.splice(markers.indexOf(marker), 1);
return marker;
};
m.markers = function(x) {
if (!arguments.length) return markers;
};
// Public data interface
m.features = function(x) {
// Return features
if (!arguments.length) return features;
// Clear features
while (parent.hasChildNodes()) {
// removing lastChild iteratively is faster than
// innerHTML = ''
// http://jsperf.com/innerhtml-vs-removechild-yo/2
parent.removeChild(parent.lastChild);
}
// clear markers representation
markers = [];
// Set features
if (!x) x = [];
features = x;
features.sort(sorter);
for (var i = 0; i < x.length; i++) {
m.add({
element: factory(x[i]),
location: new MM.Location(x[i].geometry.coordinates[1], x[i].geometry.coordinates[0]),
data: x[i]
});
}
if (m.map && m.map.coordinate) m.map.draw();
return m;
};
m.url = function(x, callback) {
if (!arguments.length) return urls;
if (typeof reqwest === 'undefined') throw 'reqwest is required for url loading';
if (typeof x === 'string') x = [x];
urls = x;
function add_features(x) {
if (x && x.features) m.features(x.features);
if (callback) callback(x.features, m);
}
reqwest((urls[0].match(/geojsonp$/)) ? {
url: urls[0] + (~urls[0].indexOf('?') ? '&' : '?') + 'callback=grid',
type: 'jsonp',
jsonpCallback: 'callback',
success: add_features,
error: add_features
} : {
url: urls[0],
type: 'json',
success: add_features,
error: add_features
});
return m;
};
m.extent = function() {
var ext = [{ lat: Infinity, lon: Infinity}, { lat: -Infinity, lon: -Infinity }];
var ft = m.features();
for (var i = 0; i < ft.length; i++) {
var coords = ft[i].geometry.coordinates;
if (coords[0] < ext[0].lon) ext[0].lon = coords[0];
if (coords[1] < ext[0].lat) ext[0].lat = coords[1];
if (coords[0] > ext[1].lon) ext[1].lon = coords[0];
if (coords[1] > ext[1].lat) ext[1].lat = coords[1];
}
return ext;
};
// Factory interface
m.factory = function(x) {
if (!arguments.length) return factory;
factory = x;
return m;
};
m.factory(function defaultFactory(feature) {
var d = document.createElement('div');
d.className = 'mmg-default';
d.style.position = 'absolute';
return d;
});
m.sort = function(x) {
if (!arguments.length) return sorter;
sorter = x;
return m;
};
m.sort(function(a, b) {
return b.geometry.coordinates[1] -
a.geometry.coordinates[1];
});
m.destroy = function() {
if (this.parent.parentNode) {
this.parent.parentNode.removeChild(this.parent);
}
};
// The parent DOM element
var parent = document.createElement('div');
parent.style.cssText = 'position: absolute; top: 0px;' +
'left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0';
m.parent = parent;
return m;
}
function mmg_interaction(mmg) {
var mi = {},
tooltips = [],
exclusive = true,
hide_on_move = true,
formatter;
mi.formatter = function(x) {
if (!arguments.length) return formatter;
formatter = x;
return mi;
};
mi.formatter(function(feature) {
var o = '',
props = feature.properties;
if (props.title) {
o += '<strong>' + props.title + '</strong><br />';
}
if (props.description) {
o += props.description;
}
return o;
});
mi.hide_on_move = function(x) {
if (!arguments.length) return hide_on_move;
hide_on_move = x;
return mi;
};
mi.exclusive = function(x) {
if (!arguments.length) return exclusive;
exclusive = x;
return mi;
};
mi.bind_marker = function(marker) {
marker.element.onclick = function(e) {
if (exclusive && tooltips.length > 0) {
mmg.remove(tooltips.pop());
}
var tooltip = document.createElement('div');
tooltip.className = 'wax-movetip';
var intip = tooltip.appendChild(document.createElement('div'));
intip.className = 'wax-intip';
intip.innerHTML = formatter(marker.data);
// Here we're adding the tooltip to the dom briefly
// to gauge its size. There should be a better way to do this.
document.body.appendChild(tooltip);
intip.style.marginTop = -(
(marker.element.offsetHeight * 0.5) +
tooltip.offsetHeight + 10) + 'px';
document.body.removeChild(tooltip);
var t = {
element: tooltip,
data: {},
location: marker.location.copy()
};
tooltips.push(t);
mmg.add(t);
mmg.draw();
};
};
if (mmg && mmg.map) {
mmg.map.addCallback('panned', function() {
if (hide_on_move) {
while (tooltips.length) {
mmg.remove(tooltips.pop());
}
}
});
var markers = mmg.markers();
for (var i = 0; i < markers.length; i++) {
mi.bind_marker(markers[i]);
}
} else {
if (console) console.log('mmg must be added to a map before interaction is assigned');
}
return mi;
}
function simplestyle_factory(feature) {
var sizes = {
small: [20, 50],
medium: [30, 70],
large: [35, 90]
};
var fp = feature.properties || {};
var size = fp['marker-size'] || 'medium';
var symbol = (fp['marker-symbol']) ? '-' + fp['marker-symbol'] : '';
var color = fp['marker-color'] || '7e7e7e';
color = color.replace('#', '');
var d = document.createElement('div');
d.className = 'simplestyle-marker';
var ds = d.style;
ds.position = 'absolute';
ds.width = sizes[size][0] + 'px';
ds.height = sizes[size][1] + 'px';
ds.marginTop = -(sizes[size][1] / 2) + 'px';
ds.marginLeft = -(sizes[size][0] / 2) + 'px';
ds.backgroundImage = 'url(http://a.tiles.mapbox.com/v3/marker/' +
'pin-' + size[0] + symbol + '+' + color + '.png)';
ds.textIndent = '-10000px';
d.innerHTML = fp.title || '';
return d;
}